mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-03 23:27:59 +00:00
collab: Restrict usage of the LLM service to accounts older than 30 days (#16133)
This PR restricts usage of the LLM service to accounts older than 30 days. We now store the GitHub user's `created_at` timestamp to check the GitHub account age. If this is not set—which it won't be for existing users—then we use the `created_at` timestamp in the Zed database. Release Notes: - N/A --------- Co-authored-by: Max <max@zed.dev>
This commit is contained in:
parent
f398ecc3fb
commit
98516b5527
12 changed files with 64 additions and 9 deletions
|
@ -10,7 +10,8 @@ CREATE TABLE "users" (
|
|||
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"metrics_id" TEXT,
|
||||
"github_user_id" INTEGER,
|
||||
"accepted_tos_at" TIMESTAMP WITHOUT TIME ZONE
|
||||
"accepted_tos_at" TIMESTAMP WITHOUT TIME ZONE,
|
||||
"github_user_created_at" TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
CREATE UNIQUE INDEX "index_users_github_login" ON "users" ("github_login");
|
||||
CREATE UNIQUE INDEX "index_invite_code_users" ON "users" ("invite_code");
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "users" ADD COLUMN "github_user_created_at" TIMESTAMP WITHOUT TIME ZONE;
|
|
@ -111,6 +111,7 @@ struct AuthenticatedUserParams {
|
|||
github_user_id: Option<i32>,
|
||||
github_login: String,
|
||||
github_email: Option<String>,
|
||||
github_user_created_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
@ -131,6 +132,7 @@ async fn get_authenticated_user(
|
|||
¶ms.github_login,
|
||||
params.github_user_id,
|
||||
params.github_email.as_deref(),
|
||||
params.github_user_created_at,
|
||||
initial_channel_id,
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -115,6 +115,7 @@ async fn add_contributor(
|
|||
¶ms.github_login,
|
||||
params.github_user_id,
|
||||
params.github_email.as_deref(),
|
||||
params.github_user_created_at,
|
||||
initial_channel_id,
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -65,6 +65,7 @@ impl Database {
|
|||
github_login: &str,
|
||||
github_user_id: Option<i32>,
|
||||
github_email: Option<&str>,
|
||||
github_user_created_at: Option<DateTimeUtc>,
|
||||
initial_channel_id: Option<ChannelId>,
|
||||
) -> Result<()> {
|
||||
self.transaction(|tx| async move {
|
||||
|
@ -73,6 +74,7 @@ impl Database {
|
|||
github_login,
|
||||
github_user_id,
|
||||
github_email,
|
||||
github_user_created_at.map(|time| time.naive_utc()),
|
||||
initial_channel_id,
|
||||
&tx,
|
||||
)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use chrono::NaiveDateTime;
|
||||
|
||||
use super::*;
|
||||
|
||||
impl Database {
|
||||
|
@ -99,6 +101,7 @@ impl Database {
|
|||
github_login: &str,
|
||||
github_user_id: Option<i32>,
|
||||
github_email: Option<&str>,
|
||||
github_user_created_at: Option<DateTimeUtc>,
|
||||
initial_channel_id: Option<ChannelId>,
|
||||
) -> Result<User> {
|
||||
self.transaction(|tx| async move {
|
||||
|
@ -106,6 +109,7 @@ impl Database {
|
|||
github_login,
|
||||
github_user_id,
|
||||
github_email,
|
||||
github_user_created_at.map(|created_at| created_at.naive_utc()),
|
||||
initial_channel_id,
|
||||
&tx,
|
||||
)
|
||||
|
@ -119,6 +123,7 @@ impl Database {
|
|||
github_login: &str,
|
||||
github_user_id: Option<i32>,
|
||||
github_email: Option<&str>,
|
||||
github_user_created_at: Option<NaiveDateTime>,
|
||||
initial_channel_id: Option<ChannelId>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<User> {
|
||||
|
@ -130,6 +135,10 @@ impl Database {
|
|||
{
|
||||
let mut user_by_github_user_id = user_by_github_user_id.into_active_model();
|
||||
user_by_github_user_id.github_login = ActiveValue::set(github_login.into());
|
||||
if github_user_created_at.is_some() {
|
||||
user_by_github_user_id.github_user_created_at =
|
||||
ActiveValue::set(github_user_created_at);
|
||||
}
|
||||
Ok(user_by_github_user_id.update(tx).await?)
|
||||
} else if let Some(user_by_github_login) = user::Entity::find()
|
||||
.filter(user::Column::GithubLogin.eq(github_login))
|
||||
|
@ -138,12 +147,17 @@ impl Database {
|
|||
{
|
||||
let mut user_by_github_login = user_by_github_login.into_active_model();
|
||||
user_by_github_login.github_user_id = ActiveValue::set(Some(github_user_id));
|
||||
if github_user_created_at.is_some() {
|
||||
user_by_github_login.github_user_created_at =
|
||||
ActiveValue::set(github_user_created_at);
|
||||
}
|
||||
Ok(user_by_github_login.update(tx).await?)
|
||||
} else {
|
||||
let user = user::Entity::insert(user::ActiveModel {
|
||||
email_address: ActiveValue::set(github_email.map(|email| email.into())),
|
||||
github_login: ActiveValue::set(github_login.into()),
|
||||
github_user_id: ActiveValue::set(Some(github_user_id)),
|
||||
github_user_created_at: ActiveValue::set(github_user_created_at),
|
||||
admin: ActiveValue::set(false),
|
||||
invite_count: ActiveValue::set(0),
|
||||
invite_code: ActiveValue::set(None),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::db::UserId;
|
||||
use chrono::NaiveDateTime;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::Serialize;
|
||||
|
||||
|
@ -10,6 +11,7 @@ pub struct Model {
|
|||
pub id: UserId,
|
||||
pub github_login: String,
|
||||
pub github_user_id: Option<i32>,
|
||||
pub github_user_created_at: Option<NaiveDateTime>,
|
||||
pub email_address: Option<String>,
|
||||
pub admin: bool,
|
||||
pub invite_code: Option<String>,
|
||||
|
@ -17,8 +19,8 @@ pub struct Model {
|
|||
pub inviter_id: Option<UserId>,
|
||||
pub connected_once: bool,
|
||||
pub metrics_id: Uuid,
|
||||
pub created_at: DateTime,
|
||||
pub accepted_tos_at: Option<DateTime>,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub accepted_tos_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use chrono::Utc;
|
||||
|
||||
use super::Database;
|
||||
use crate::{db::NewUserParams, test_both_dbs};
|
||||
use std::sync::Arc;
|
||||
|
@ -22,7 +24,8 @@ async fn test_contributors(db: &Arc<Database>) {
|
|||
|
||||
assert_eq!(db.get_contributors().await.unwrap(), Vec::<String>::new());
|
||||
|
||||
db.add_contributor("user1", Some(1), None, None)
|
||||
let user1_created_at = Utc::now();
|
||||
db.add_contributor("user1", Some(1), None, Some(user1_created_at), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
|
@ -30,7 +33,8 @@ async fn test_contributors(db: &Arc<Database>) {
|
|||
vec!["user1".to_string()]
|
||||
);
|
||||
|
||||
db.add_contributor("user2", Some(2), None, None)
|
||||
let user2_created_at = Utc::now();
|
||||
db.add_contributor("user2", Some(2), None, Some(user2_created_at), None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::*;
|
||||
use crate::test_both_dbs;
|
||||
use chrono::Utc;
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -100,7 +101,13 @@ async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
|
|||
.user_id;
|
||||
|
||||
let user = db
|
||||
.get_or_create_user_by_github_account("the-new-login2", Some(102), None, None)
|
||||
.get_or_create_user_by_github_account(
|
||||
"the-new-login2",
|
||||
Some(102),
|
||||
None,
|
||||
Some(Utc::now()),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(user.id, user_id2);
|
||||
|
@ -108,7 +115,13 @@ async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
|
|||
assert_eq!(user.github_user_id, Some(102));
|
||||
|
||||
let user = db
|
||||
.get_or_create_user_by_github_account("login3", Some(103), Some("user3@example.com"), None)
|
||||
.get_or_create_user_by_github_account(
|
||||
"login3",
|
||||
Some(103),
|
||||
Some("user3@example.com"),
|
||||
Some(Utc::now()),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(&user.github_login, "login3");
|
||||
|
|
|
@ -4907,6 +4907,9 @@ async fn accept_terms_of_service(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// The minimum account age an account must have in order to use the LLM service.
|
||||
const MIN_ACCOUNT_AGE_FOR_LLM_USE: chrono::Duration = chrono::Duration::days(30);
|
||||
|
||||
async fn get_llm_api_token(
|
||||
_request: proto::GetLlmToken,
|
||||
response: Response<proto::GetLlmToken>,
|
||||
|
@ -4928,6 +4931,14 @@ async fn get_llm_api_token(
|
|||
Err(anyhow!("terms of service not accepted"))?
|
||||
}
|
||||
|
||||
let mut account_created_at = user.created_at;
|
||||
if let Some(github_created_at) = user.github_user_created_at {
|
||||
account_created_at = account_created_at.min(github_created_at);
|
||||
}
|
||||
if Utc::now().naive_utc() - account_created_at < MIN_ACCOUNT_AGE_FOR_LLM_USE {
|
||||
Err(anyhow!("account too young"))?
|
||||
}
|
||||
|
||||
let token = LlmTokenClaims::create(
|
||||
user.id,
|
||||
session.is_staff(),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::db::{self, ChannelRole, NewUserParams};
|
||||
|
||||
use anyhow::Context;
|
||||
use chrono::{DateTime, Utc};
|
||||
use db::Database;
|
||||
use serde::{de::DeserializeOwned, Deserialize};
|
||||
use std::{fmt::Write, fs, path::Path};
|
||||
|
@ -12,6 +13,7 @@ struct GitHubUser {
|
|||
id: i32,
|
||||
login: String,
|
||||
email: Option<String>,
|
||||
created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -107,6 +109,7 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result
|
|||
&github_user.login,
|
||||
Some(github_user.id),
|
||||
github_user.email.as_deref(),
|
||||
Some(github_user.created_at),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{db::ChannelId, tests::TestServer};
|
||||
use call::ActiveCall;
|
||||
use chrono::Utc;
|
||||
use editor::Editor;
|
||||
use gpui::{BackgroundExecutor, TestAppContext};
|
||||
use rpc::proto;
|
||||
|
@ -167,7 +168,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||
server
|
||||
.app_state
|
||||
.db
|
||||
.get_or_create_user_by_github_account("user_b", Some(100), None, None)
|
||||
.get_or_create_user_by_github_account("user_b", Some(100), None, Some(Utc::now()), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -265,7 +266,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
|||
server
|
||||
.app_state
|
||||
.db
|
||||
.add_contributor("user_b", Some(100), None, None)
|
||||
.add_contributor("user_b", Some(100), None, Some(Utc::now()), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
Loading…
Reference in a new issue