From ab1bea515c41a7e35d8ffdcca3175bca186a62b3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Jan 2024 15:46:36 -0800 Subject: [PATCH 1/4] Store the impersonator id on access tokens created via ZED_IMPERSONATE * Use the impersonator id to prevent these tokens from counting against the impersonated user when limiting the users' total of access tokens. * When connecting using an access token with an impersonator add the impersonator as a field to the tracing span that wraps the task for that connection. * Disallow impersonating users via the admin API token in production, because when using the admin API token, we aren't able to identify the impersonator. Co-authored-by: Marshall --- .../20221109000000_test_schema.sql | 2 + ...0300_add_impersonator_to_access_tokens.sql | 3 + crates/collab/src/api.rs | 4 +- crates/collab/src/auth.rs | 90 +++++++++++---- crates/collab/src/db/queries/access_tokens.rs | 12 +- crates/collab/src/db/tables/access_token.rs | 1 + crates/collab/src/db/tests/db_tests.rs | 104 ++++++++++++++++-- crates/collab/src/rpc.rs | 20 +++- crates/collab/src/tests/test_server.rs | 1 + 9 files changed, 198 insertions(+), 39 deletions(-) create mode 100644 crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 507cf197f7..a7c9331506 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -19,9 +19,11 @@ CREATE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id"); CREATE TABLE "access_tokens" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "user_id" INTEGER REFERENCES users (id), + "impersonator_id" INTEGER REFERENCES users (id), "hash" VARCHAR(128) ); CREATE INDEX "index_access_tokens_user_id" ON "access_tokens" ("user_id"); +CREATE INDEX "index_access_tokens_impersonator_id" ON "access_tokens" ("impersonator_id"); CREATE TABLE "contacts" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql b/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql new file mode 100644 index 0000000000..199706473f --- /dev/null +++ b/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql @@ -0,0 +1,3 @@ +ALTER TABLE access_tokens ADD COLUMN impersonator_id integer; + +CREATE INDEX "index_access_tokens_impersonator_id" ON "access_tokens" ("impersonator_id"); diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index a28aeac9ab..24a8f066b2 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -157,9 +157,11 @@ async fn create_access_token( .ok_or_else(|| anyhow!("user not found"))?; let mut user_id = user.id; + let mut impersonator_id = None; if let Some(impersonate) = params.impersonate { if user.admin { if let Some(impersonated_user) = app.db.get_user_by_github_login(&impersonate).await? { + impersonator_id = Some(user_id); user_id = impersonated_user.id; } else { return Err(Error::Http( @@ -175,7 +177,7 @@ async fn create_access_token( } } - let access_token = auth::create_access_token(app.db.as_ref(), user_id).await?; + let access_token = auth::create_access_token(app.db.as_ref(), user_id, impersonator_id).await?; let encrypted_access_token = auth::encrypt_access_token(&access_token, params.public_key.clone())?; diff --git a/crates/collab/src/auth.rs b/crates/collab/src/auth.rs index df3ded28e4..a32f35fae8 100644 --- a/crates/collab/src/auth.rs +++ b/crates/collab/src/auth.rs @@ -27,6 +27,9 @@ lazy_static! { .unwrap(); } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Impersonator(pub Option); + /// Validates the authorization header. This has two mechanisms, one for the ADMIN_TOKEN /// and one for the access tokens that we issue. pub async fn validate_header(mut req: Request, next: Next) -> impl IntoResponse { @@ -57,28 +60,50 @@ pub async fn validate_header(mut req: Request, next: Next) -> impl Into })?; let state = req.extensions().get::>().unwrap(); - let credentials_valid = if let Some(admin_token) = access_token.strip_prefix("ADMIN_TOKEN:") { - state.config.api_token == admin_token + + // In development, allow impersonation using the admin API token. + // Don't allow this in production because we can't tell who is doing + // the impersonating. + let validate_result = if let (Some(admin_token), true) = ( + access_token.strip_prefix("ADMIN_TOKEN:"), + state.config.is_development(), + ) { + Ok(VerifyAccessTokenResult { + is_valid: state.config.api_token == admin_token, + impersonator_id: None, + }) } else { - verify_access_token(&access_token, user_id, &state.db) - .await - .unwrap_or(false) + verify_access_token(&access_token, user_id, &state.db).await }; - if credentials_valid { - let user = state - .db - .get_user_by_id(user_id) - .await? - .ok_or_else(|| anyhow!("user {} not found", user_id))?; - req.extensions_mut().insert(user); - Ok::<_, Error>(next.run(req).await) - } else { - Err(Error::Http( - StatusCode::UNAUTHORIZED, - "invalid credentials".to_string(), - )) + if let Ok(validate_result) = validate_result { + if validate_result.is_valid { + let user = state + .db + .get_user_by_id(user_id) + .await? + .ok_or_else(|| anyhow!("user {} not found", user_id))?; + + let impersonator = if let Some(impersonator_id) = validate_result.impersonator_id { + let impersonator = state + .db + .get_user_by_id(impersonator_id) + .await? + .ok_or_else(|| anyhow!("user {} not found", impersonator_id))?; + Some(impersonator) + } else { + None + }; + req.extensions_mut().insert(user); + req.extensions_mut().insert(Impersonator(impersonator)); + return Ok::<_, Error>(next.run(req).await); + } } + + Err(Error::Http( + StatusCode::UNAUTHORIZED, + "invalid credentials".to_string(), + )) } const MAX_ACCESS_TOKENS_TO_STORE: usize = 8; @@ -92,13 +117,22 @@ struct AccessTokenJson { /// Creates a new access token to identify the given user. before returning it, you should /// encrypt it with the user's public key. -pub async fn create_access_token(db: &db::Database, user_id: UserId) -> Result { +pub async fn create_access_token( + db: &db::Database, + user_id: UserId, + impersonator_id: Option, +) -> Result { const VERSION: usize = 1; let access_token = rpc::auth::random_token(); let access_token_hash = hash_access_token(&access_token).context("failed to hash access token")?; let id = db - .create_access_token(user_id, &access_token_hash, MAX_ACCESS_TOKENS_TO_STORE) + .create_access_token( + user_id, + impersonator_id, + &access_token_hash, + MAX_ACCESS_TOKENS_TO_STORE, + ) .await?; Ok(serde_json::to_string(&AccessTokenJson { version: VERSION, @@ -137,8 +171,17 @@ pub fn encrypt_access_token(access_token: &str, public_key: String) -> Result, +} + /// verify access token returns true if the given token is valid for the given user. -pub async fn verify_access_token(token: &str, user_id: UserId, db: &Arc) -> Result { +pub async fn verify_access_token( + token: &str, + user_id: UserId, + db: &Arc, +) -> Result { let token: AccessTokenJson = serde_json::from_str(&token)?; let db_token = db.get_access_token(token.id).await?; @@ -154,5 +197,8 @@ pub async fn verify_access_token(token: &str, user_id: UserId, db: &Arc, access_token_hash: &str, max_access_token_count: usize, ) -> Result { @@ -14,19 +15,28 @@ impl Database { let token = access_token::ActiveModel { user_id: ActiveValue::set(user_id), + impersonator_id: ActiveValue::set(impersonator_id), hash: ActiveValue::set(access_token_hash.into()), ..Default::default() } .insert(&*tx) .await?; + let existing_token_filter = if let Some(impersonator_id) = impersonator_id { + access_token::Column::ImpersonatorId.eq(impersonator_id) + } else { + access_token::Column::UserId + .eq(user_id) + .and(access_token::Column::ImpersonatorId.is_null()) + }; + access_token::Entity::delete_many() .filter( access_token::Column::Id.in_subquery( Query::select() .column(access_token::Column::Id) .from(access_token::Entity) - .and_where(access_token::Column::UserId.eq(user_id)) + .cond_where(existing_token_filter) .order_by(access_token::Column::Id, sea_orm::Order::Desc) .limit(10000) .offset(max_access_token_count as u64) diff --git a/crates/collab/src/db/tables/access_token.rs b/crates/collab/src/db/tables/access_token.rs index da7392b98c..81d6f3af60 100644 --- a/crates/collab/src/db/tables/access_token.rs +++ b/crates/collab/src/db/tables/access_token.rs @@ -7,6 +7,7 @@ pub struct Model { #[sea_orm(primary_key)] pub id: AccessTokenId, pub user_id: UserId, + pub impersonator_id: Option, pub hash: String, } diff --git a/crates/collab/src/db/tests/db_tests.rs b/crates/collab/src/db/tests/db_tests.rs index 5332f227ef..98c35aa646 100644 --- a/crates/collab/src/db/tests/db_tests.rs +++ b/crates/collab/src/db/tests/db_tests.rs @@ -146,7 +146,7 @@ test_both_dbs!( ); async fn test_create_access_tokens(db: &Arc) { - let user = db + let user_1 = db .create_user( "u1@example.com", false, @@ -158,14 +158,27 @@ async fn test_create_access_tokens(db: &Arc) { .await .unwrap() .user_id; + let user_2 = db + .create_user( + "u2@example.com", + false, + NewUserParams { + github_login: "u2".into(), + github_user_id: 2, + }, + ) + .await + .unwrap() + .user_id; - let token_1 = db.create_access_token(user, "h1", 2).await.unwrap(); - let token_2 = db.create_access_token(user, "h2", 2).await.unwrap(); + let token_1 = db.create_access_token(user_1, None, "h1", 2).await.unwrap(); + let token_2 = db.create_access_token(user_1, None, "h2", 2).await.unwrap(); assert_eq!( db.get_access_token(token_1).await.unwrap(), access_token::Model { id: token_1, - user_id: user, + user_id: user_1, + impersonator_id: None, hash: "h1".into(), } ); @@ -173,17 +186,19 @@ async fn test_create_access_tokens(db: &Arc) { db.get_access_token(token_2).await.unwrap(), access_token::Model { id: token_2, - user_id: user, + user_id: user_1, + impersonator_id: None, hash: "h2".into() } ); - let token_3 = db.create_access_token(user, "h3", 2).await.unwrap(); + let token_3 = db.create_access_token(user_1, None, "h3", 2).await.unwrap(); assert_eq!( db.get_access_token(token_3).await.unwrap(), access_token::Model { id: token_3, - user_id: user, + user_id: user_1, + impersonator_id: None, hash: "h3".into() } ); @@ -191,18 +206,20 @@ async fn test_create_access_tokens(db: &Arc) { db.get_access_token(token_2).await.unwrap(), access_token::Model { id: token_2, - user_id: user, + user_id: user_1, + impersonator_id: None, hash: "h2".into() } ); assert!(db.get_access_token(token_1).await.is_err()); - let token_4 = db.create_access_token(user, "h4", 2).await.unwrap(); + let token_4 = db.create_access_token(user_1, None, "h4", 2).await.unwrap(); assert_eq!( db.get_access_token(token_4).await.unwrap(), access_token::Model { id: token_4, - user_id: user, + user_id: user_1, + impersonator_id: None, hash: "h4".into() } ); @@ -210,12 +227,77 @@ async fn test_create_access_tokens(db: &Arc) { db.get_access_token(token_3).await.unwrap(), access_token::Model { id: token_3, - user_id: user, + user_id: user_1, + impersonator_id: None, hash: "h3".into() } ); assert!(db.get_access_token(token_2).await.is_err()); assert!(db.get_access_token(token_1).await.is_err()); + + // An access token for user 2 impersonating user 1 does not + // count against user 1's access token limit (of 2). + let token_5 = db + .create_access_token(user_1, Some(user_2), "h5", 2) + .await + .unwrap(); + assert_eq!( + db.get_access_token(token_5).await.unwrap(), + access_token::Model { + id: token_5, + user_id: user_1, + impersonator_id: Some(user_2), + hash: "h5".into() + } + ); + assert_eq!( + db.get_access_token(token_3).await.unwrap(), + access_token::Model { + id: token_3, + user_id: user_1, + impersonator_id: None, + hash: "h3".into() + } + ); + + // Only a limited number (2) of access tokens are stored for user 2 + // impersonating other users. + let token_6 = db + .create_access_token(user_1, Some(user_2), "h6", 2) + .await + .unwrap(); + let token_7 = db + .create_access_token(user_1, Some(user_2), "h7", 2) + .await + .unwrap(); + assert_eq!( + db.get_access_token(token_6).await.unwrap(), + access_token::Model { + id: token_6, + user_id: user_1, + impersonator_id: Some(user_2), + hash: "h6".into() + } + ); + assert_eq!( + db.get_access_token(token_7).await.unwrap(), + access_token::Model { + id: token_7, + user_id: user_1, + impersonator_id: Some(user_2), + hash: "h7".into() + } + ); + assert!(db.get_access_token(token_5).await.is_err()); + assert_eq!( + db.get_access_token(token_3).await.unwrap(), + access_token::Model { + id: token_3, + user_id: user_1, + impersonator_id: None, + hash: "h3".into() + } + ); } test_both_dbs!( diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 9406b4938a..c7bbf7f865 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1,7 +1,7 @@ mod connection_pool; use crate::{ - auth, + auth::{self, Impersonator}, db::{ self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreateChannelResult, CreatedChannelMessage, Database, InviteMemberResult, MembershipUpdated, MessageId, @@ -65,7 +65,7 @@ use std::{ use time::OffsetDateTime; use tokio::sync::{watch, Semaphore}; use tower::ServiceBuilder; -use tracing::{info_span, instrument, Instrument}; +use tracing::{field, info_span, instrument, Instrument}; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); @@ -561,13 +561,17 @@ impl Server { connection: Connection, address: String, user: User, + impersonator: Option, mut send_connection_id: Option>, executor: Executor, ) -> impl Future> { let this = self.clone(); let user_id = user.id; let login = user.github_login; - let span = info_span!("handle connection", %user_id, %login, %address); + let span = info_span!("handle connection", %user_id, %login, %address, impersonator = field::Empty); + if let Some(impersonator) = impersonator { + span.record("impersonator", &impersonator.github_login); + } let mut teardown = self.teardown.subscribe(); async move { let (connection_id, handle_io, mut incoming_rx) = this @@ -839,6 +843,7 @@ pub async fn handle_websocket_request( ConnectInfo(socket_address): ConnectInfo, Extension(server): Extension>, Extension(user): Extension, + Extension(impersonator): Extension, ws: WebSocketUpgrade, ) -> axum::response::Response { if protocol_version != rpc::PROTOCOL_VERSION { @@ -858,7 +863,14 @@ pub async fn handle_websocket_request( let connection = Connection::new(Box::pin(socket)); async move { server - .handle_connection(connection, socket_address, user, None, Executor::Production) + .handle_connection( + connection, + socket_address, + user, + impersonator.0, + None, + Executor::Production, + ) .await .log_err(); } diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index cda0621cb3..ea08d83b6c 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -213,6 +213,7 @@ impl TestServer { server_conn, client_name, user, + None, Some(connection_id_tx), Executor::Deterministic(cx.background_executor().clone()), )) From 69bff7bb77dca9916c07e7890a5abbabdd2d4dff Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Jan 2024 17:35:37 -0800 Subject: [PATCH 2/4] Exclude squawk rule forbidding regular-sized integers --- script/lib/squawk.toml | 4 ++++ script/squawk | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 script/lib/squawk.toml diff --git a/script/lib/squawk.toml b/script/lib/squawk.toml new file mode 100644 index 0000000000..1090e38286 --- /dev/null +++ b/script/lib/squawk.toml @@ -0,0 +1,4 @@ +excluded_rules = [ + "prefer-big-int", + "prefer-bigint-over-int", +] diff --git a/script/squawk b/script/squawk index 0fb3e5a332..68977645d0 100755 --- a/script/squawk +++ b/script/squawk @@ -8,13 +8,12 @@ set -e if [ -z "$GITHUB_BASE_REF" ]; then echo 'Not a pull request, skipping squawk modified migrations linting' - return 0 + exit fi SQUAWK_VERSION=0.26.0 SQUAWK_BIN="./target/squawk-$SQUAWK_VERSION" -SQUAWK_ARGS="--assume-in-transaction" - +SQUAWK_ARGS="--assume-in-transaction --config script/lib/squawk.toml" if [ ! -f "$SQUAWK_BIN" ]; then curl -L -o "$SQUAWK_BIN" "https://github.com/sbdchd/squawk/releases/download/v$SQUAWK_VERSION/squawk-darwin-x86_64" From 9f04fd9019018849655cfe4a1cdb983094a316f6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Jan 2024 17:58:59 -0800 Subject: [PATCH 3/4] For impersonating access tokens, store impersonatee in the new column This way, we don't need an index on both columns --- .../20221109000000_test_schema.sql | 3 +- ...0300_add_impersonator_to_access_tokens.sql | 4 +-- crates/collab/src/api.rs | 11 +++--- crates/collab/src/auth.rs | 13 ++++--- crates/collab/src/db/queries/access_tokens.rs | 14 ++------ crates/collab/src/db/tables/access_token.rs | 2 +- crates/collab/src/db/tests/db_tests.rs | 34 +++++++++---------- docs/src/developing_zed__building_zed.md | 2 +- 8 files changed, 38 insertions(+), 45 deletions(-) diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index a7c9331506..8d8f523c94 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -19,11 +19,10 @@ CREATE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id"); CREATE TABLE "access_tokens" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "user_id" INTEGER REFERENCES users (id), - "impersonator_id" INTEGER REFERENCES users (id), + "impersonated_user_id" INTEGER REFERENCES users (id), "hash" VARCHAR(128) ); CREATE INDEX "index_access_tokens_user_id" ON "access_tokens" ("user_id"); -CREATE INDEX "index_access_tokens_impersonator_id" ON "access_tokens" ("impersonator_id"); CREATE TABLE "contacts" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql b/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql index 199706473f..8c79640cd8 100644 --- a/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql +++ b/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql @@ -1,3 +1 @@ -ALTER TABLE access_tokens ADD COLUMN impersonator_id integer; - -CREATE INDEX "index_access_tokens_impersonator_id" ON "access_tokens" ("impersonator_id"); +ALTER TABLE access_tokens ADD COLUMN impersonated_user_id integer; diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 24a8f066b2..6bdbd7357f 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -156,13 +156,11 @@ async fn create_access_token( .await? .ok_or_else(|| anyhow!("user not found"))?; - let mut user_id = user.id; - let mut impersonator_id = None; + let mut impersonated_user_id = None; if let Some(impersonate) = params.impersonate { if user.admin { if let Some(impersonated_user) = app.db.get_user_by_github_login(&impersonate).await? { - impersonator_id = Some(user_id); - user_id = impersonated_user.id; + impersonated_user_id = Some(impersonated_user.id); } else { return Err(Error::Http( StatusCode::UNPROCESSABLE_ENTITY, @@ -177,12 +175,13 @@ async fn create_access_token( } } - let access_token = auth::create_access_token(app.db.as_ref(), user_id, impersonator_id).await?; + let access_token = + auth::create_access_token(app.db.as_ref(), user_id, impersonated_user_id).await?; let encrypted_access_token = auth::encrypt_access_token(&access_token, params.public_key.clone())?; Ok(Json(CreateAccessTokenResponse { - user_id, + user_id: impersonated_user_id.unwrap_or(user_id), encrypted_access_token, })) } diff --git a/crates/collab/src/auth.rs b/crates/collab/src/auth.rs index a32f35fae8..e6c43df73c 100644 --- a/crates/collab/src/auth.rs +++ b/crates/collab/src/auth.rs @@ -120,7 +120,7 @@ struct AccessTokenJson { pub async fn create_access_token( db: &db::Database, user_id: UserId, - impersonator_id: Option, + impersonated_user_id: Option, ) -> Result { const VERSION: usize = 1; let access_token = rpc::auth::random_token(); @@ -129,7 +129,7 @@ pub async fn create_access_token( let id = db .create_access_token( user_id, - impersonator_id, + impersonated_user_id, &access_token_hash, MAX_ACCESS_TOKENS_TO_STORE, ) @@ -185,7 +185,8 @@ pub async fn verify_access_token( let token: AccessTokenJson = serde_json::from_str(&token)?; let db_token = db.get_access_token(token.id).await?; - if db_token.user_id != user_id { + let token_user_id = db_token.impersonated_user_id.unwrap_or(db_token.user_id); + if token_user_id != user_id { return Err(anyhow!("no such access token"))?; } @@ -199,6 +200,10 @@ pub async fn verify_access_token( METRIC_ACCESS_TOKEN_HASHING_TIME.observe(duration.as_millis() as f64); Ok(VerifyAccessTokenResult { is_valid, - impersonator_id: db_token.impersonator_id, + impersonator_id: if db_token.impersonated_user_id.is_some() { + Some(db_token.user_id) + } else { + None + }, }) } diff --git a/crates/collab/src/db/queries/access_tokens.rs b/crates/collab/src/db/queries/access_tokens.rs index e0db6c5038..af58d51a33 100644 --- a/crates/collab/src/db/queries/access_tokens.rs +++ b/crates/collab/src/db/queries/access_tokens.rs @@ -6,7 +6,7 @@ impl Database { pub async fn create_access_token( &self, user_id: UserId, - impersonator_id: Option, + impersonated_user_id: Option, access_token_hash: &str, max_access_token_count: usize, ) -> Result { @@ -15,28 +15,20 @@ impl Database { let token = access_token::ActiveModel { user_id: ActiveValue::set(user_id), - impersonator_id: ActiveValue::set(impersonator_id), + impersonated_user_id: ActiveValue::set(impersonated_user_id), hash: ActiveValue::set(access_token_hash.into()), ..Default::default() } .insert(&*tx) .await?; - let existing_token_filter = if let Some(impersonator_id) = impersonator_id { - access_token::Column::ImpersonatorId.eq(impersonator_id) - } else { - access_token::Column::UserId - .eq(user_id) - .and(access_token::Column::ImpersonatorId.is_null()) - }; - access_token::Entity::delete_many() .filter( access_token::Column::Id.in_subquery( Query::select() .column(access_token::Column::Id) .from(access_token::Entity) - .cond_where(existing_token_filter) + .and_where(access_token::Column::UserId.eq(user_id)) .order_by(access_token::Column::Id, sea_orm::Order::Desc) .limit(10000) .offset(max_access_token_count as u64) diff --git a/crates/collab/src/db/tables/access_token.rs b/crates/collab/src/db/tables/access_token.rs index 81d6f3af60..22635fb64d 100644 --- a/crates/collab/src/db/tables/access_token.rs +++ b/crates/collab/src/db/tables/access_token.rs @@ -7,7 +7,7 @@ pub struct Model { #[sea_orm(primary_key)] pub id: AccessTokenId, pub user_id: UserId, - pub impersonator_id: Option, + pub impersonated_user_id: Option, pub hash: String, } diff --git a/crates/collab/src/db/tests/db_tests.rs b/crates/collab/src/db/tests/db_tests.rs index 98c35aa646..3e1bdede71 100644 --- a/crates/collab/src/db/tests/db_tests.rs +++ b/crates/collab/src/db/tests/db_tests.rs @@ -178,7 +178,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_1, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h1".into(), } ); @@ -187,7 +187,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_2, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h2".into() } ); @@ -198,7 +198,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_3, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h3".into() } ); @@ -207,7 +207,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_2, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h2".into() } ); @@ -219,7 +219,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_4, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h4".into() } ); @@ -228,7 +228,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_3, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h3".into() } ); @@ -238,15 +238,15 @@ async fn test_create_access_tokens(db: &Arc) { // An access token for user 2 impersonating user 1 does not // count against user 1's access token limit (of 2). let token_5 = db - .create_access_token(user_1, Some(user_2), "h5", 2) + .create_access_token(user_2, Some(user_1), "h5", 2) .await .unwrap(); assert_eq!( db.get_access_token(token_5).await.unwrap(), access_token::Model { id: token_5, - user_id: user_1, - impersonator_id: Some(user_2), + user_id: user_2, + impersonated_user_id: Some(user_1), hash: "h5".into() } ); @@ -255,7 +255,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_3, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h3".into() } ); @@ -263,19 +263,19 @@ async fn test_create_access_tokens(db: &Arc) { // Only a limited number (2) of access tokens are stored for user 2 // impersonating other users. let token_6 = db - .create_access_token(user_1, Some(user_2), "h6", 2) + .create_access_token(user_2, Some(user_1), "h6", 2) .await .unwrap(); let token_7 = db - .create_access_token(user_1, Some(user_2), "h7", 2) + .create_access_token(user_2, Some(user_1), "h7", 2) .await .unwrap(); assert_eq!( db.get_access_token(token_6).await.unwrap(), access_token::Model { id: token_6, - user_id: user_1, - impersonator_id: Some(user_2), + user_id: user_2, + impersonated_user_id: Some(user_1), hash: "h6".into() } ); @@ -283,8 +283,8 @@ async fn test_create_access_tokens(db: &Arc) { db.get_access_token(token_7).await.unwrap(), access_token::Model { id: token_7, - user_id: user_1, - impersonator_id: Some(user_2), + user_id: user_2, + impersonated_user_id: Some(user_1), hash: "h7".into() } ); @@ -294,7 +294,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_3, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h3".into() } ); diff --git a/docs/src/developing_zed__building_zed.md b/docs/src/developing_zed__building_zed.md index 7535ceb4d0..a360be8397 100644 --- a/docs/src/developing_zed__building_zed.md +++ b/docs/src/developing_zed__building_zed.md @@ -14,7 +14,7 @@ - Ensure that the Xcode command line tools are using your newly installed copy of Xcode: ``` - sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer. + sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer ``` * Install the Rust wasm toolchain: From 93d068a7467cc12ad68d4ab1ca5c0bfd9397aa33 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Jan 2024 18:01:38 -0800 Subject: [PATCH 4/4] Update verify_access_token doc comment --- crates/collab/src/auth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab/src/auth.rs b/crates/collab/src/auth.rs index e6c43df73c..dc0374df6a 100644 --- a/crates/collab/src/auth.rs +++ b/crates/collab/src/auth.rs @@ -176,7 +176,7 @@ pub struct VerifyAccessTokenResult { pub impersonator_id: Option, } -/// verify access token returns true if the given token is valid for the given user. +/// Checks that the given access token is valid for the given user. pub async fn verify_access_token( token: &str, user_id: UserId,