Detailed changes
@@ -1,20 +1,17 @@
pub mod events;
pub mod extensions;
-use crate::{AppState, Error, Result, auth, db::UserId, rpc};
-use anyhow::Context as _;
+use crate::{AppState, Error, Result, rpc};
use axum::{
- Extension, Json, Router,
+ Extension, Router,
body::Body,
- extract::{Path, Query},
headers::Header,
http::{self, HeaderName, Request, StatusCode},
middleware::{self, Next},
response::IntoResponse,
- routing::{get, post},
+ routing::get,
};
use axum_extra::response::ErasedJson;
-use serde::{Deserialize, Serialize};
use std::sync::{Arc, OnceLock};
use tower::ServiceBuilder;
@@ -88,7 +85,6 @@ impl std::fmt::Display for SystemIdHeader {
pub fn routes(rpc_server: Arc<rpc::Server>) -> Router<(), Body> {
Router::new()
- .route("/users/:id/access_tokens", post(create_access_token))
.route("/rpc_server_snapshot", get(get_rpc_server_snapshot))
.layer(
ServiceBuilder::new()
@@ -133,56 +129,3 @@ async fn get_rpc_server_snapshot(
) -> Result<ErasedJson> {
Ok(ErasedJson::pretty(rpc_server.snapshot().await))
}
-
-#[derive(Deserialize)]
-struct CreateAccessTokenQueryParams {
- public_key: String,
- impersonate: Option<String>,
-}
-
-#[derive(Serialize)]
-struct CreateAccessTokenResponse {
- user_id: UserId,
- encrypted_access_token: String,
-}
-
-async fn create_access_token(
- Path(user_id): Path<UserId>,
- Query(params): Query<CreateAccessTokenQueryParams>,
- Extension(app): Extension<Arc<AppState>>,
-) -> Result<Json<CreateAccessTokenResponse>> {
- let user = app
- .db
- .get_user_by_id(user_id)
- .await?
- .context("user not found")?;
-
- 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? {
- impersonated_user_id = Some(impersonated_user.id);
- } else {
- return Err(Error::http(
- StatusCode::UNPROCESSABLE_ENTITY,
- format!("user {impersonate} does not exist"),
- ));
- }
- } else {
- return Err(Error::http(
- StatusCode::UNAUTHORIZED,
- "you do not have permission to impersonate other users".to_string(),
- ));
- }
- }
-
- 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: impersonated_user_id.unwrap_or(user_id),
- encrypted_access_token,
- }))
-}
@@ -1,6 +1,6 @@
use crate::{
AppState, Error, Result,
- db::{self, AccessTokenId, Database, UserId},
+ db::{AccessTokenId, Database, UserId},
rpc::Principal,
};
use anyhow::Context as _;
@@ -108,8 +108,6 @@ pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl Into
))
}
-pub const MAX_ACCESS_TOKENS_TO_STORE: usize = 8;
-
#[derive(Serialize, Deserialize)]
pub struct AccessTokenJson {
pub version: usize,
@@ -117,31 +115,6 @@ pub struct AccessTokenJson {
pub token: String,
}
-/// 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,
- impersonated_user_id: Option<UserId>,
-) -> Result<String> {
- const VERSION: usize = 1;
- let access_token = rpc::auth::random_token();
- let access_token_hash = hash_access_token(&access_token);
- let id = db
- .create_access_token(
- user_id,
- impersonated_user_id,
- &access_token_hash,
- MAX_ACCESS_TOKENS_TO_STORE,
- )
- .await?;
- Ok(serde_json::to_string(&AccessTokenJson {
- version: VERSION,
- id,
- token: access_token,
- })?)
-}
-
/// Hashing prevents anyone with access to the database being able to login.
/// As the token is randomly generated, we don't need to worry about scrypt-style
/// protection.
@@ -150,22 +123,6 @@ pub fn hash_access_token(token: &str) -> String {
format!("$sha256${}", BASE64_URL_SAFE.encode(digest))
}
-/// Encrypts the given access token with the given public key to avoid leaking it on the way
-/// to the client.
-pub fn encrypt_access_token(access_token: &str, public_key: String) -> Result<String> {
- use rpc::auth::EncryptionFormat;
-
- /// The encryption format to use for the access token.
- const ENCRYPTION_FORMAT: EncryptionFormat = EncryptionFormat::V1;
-
- let native_app_public_key =
- rpc::auth::PublicKey::try_from(public_key).context("failed to parse app public key")?;
- let encrypted_access_token = native_app_public_key
- .encrypt_string(access_token, ENCRYPTION_FORMAT)
- .context("failed to encrypt access token with public key")?;
- Ok(encrypted_access_token)
-}
-
pub struct VerifyAccessTokenResult {
pub is_valid: bool,
pub impersonator_id: Option<UserId>,
@@ -1,9 +1,9 @@
use super::*;
use anyhow::Context as _;
-use sea_orm::sea_query::Query;
impl Database {
/// Creates a new access token for the given user.
+ #[cfg(any(test, feature = "test-support"))]
pub async fn create_access_token(
&self,
user_id: UserId,
@@ -11,6 +11,8 @@ impl Database {
access_token_hash: &str,
max_access_token_count: usize,
) -> Result<AccessTokenId> {
+ use sea_orm::sea_query::Query;
+
self.transaction(|tx| async {
let tx = tx;
@@ -53,8 +53,7 @@ fn channel_id(room: &Entity<Room>, cx: &mut TestAppContext) -> Option<ChannelId>
mod auth_token_tests {
use collab::auth::{
- AccessTokenJson, MAX_ACCESS_TOKENS_TO_STORE, VerifyAccessTokenResult, create_access_token,
- verify_access_token,
+ AccessTokenJson, VerifyAccessTokenResult, hash_access_token, verify_access_token,
};
use rand::prelude::*;
use scrypt::Scrypt;
@@ -64,6 +63,31 @@ mod auth_token_tests {
use collab::db::{Database, NewUserParams, UserId, access_token};
use collab::*;
+ const MAX_ACCESS_TOKENS_TO_STORE: usize = 8;
+
+ async fn create_access_token(
+ db: &db::Database,
+ user_id: UserId,
+ impersonated_user_id: Option<UserId>,
+ ) -> Result<String> {
+ const VERSION: usize = 1;
+ let access_token = ::rpc::auth::random_token();
+ let access_token_hash = hash_access_token(&access_token);
+ let id = db
+ .create_access_token(
+ user_id,
+ impersonated_user_id,
+ &access_token_hash,
+ MAX_ACCESS_TOKENS_TO_STORE,
+ )
+ .await?;
+ Ok(serde_json::to_string(&AccessTokenJson {
+ version: VERSION,
+ id,
+ token: access_token,
+ })?)
+ }
+
#[gpui::test]
async fn test_verify_access_token(cx: &mut gpui::TestAppContext) {
let test_db = crate::db_tests::TestDb::sqlite(cx.executor());