From a11c742e707d6373929e51b481e825a3ad8c3c6a Mon Sep 17 00:00:00 2001 From: shay7sev Date: Tue, 3 Feb 2026 17:31:56 +0800 Subject: [PATCH] feat(sso): sso login --- src/api/handlers/auth.rs | 16 ++++++++++-- src/api/mod.rs | 6 ++--- src/application/services/mod.rs | 4 +++ src/infrastructure/iam_client/mod.rs | 30 +++++++++++----------- src/infrastructure/repositories/article.rs | 2 ++ src/infrastructure/repositories/column.rs | 1 + src/infrastructure/repositories/media.rs | 1 + 7 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/api/handlers/auth.rs b/src/api/handlers/auth.rs index 0d0eb2c..6dddf8b 100644 --- a/src/api/handlers/auth.rs +++ b/src/api/handlers/auth.rs @@ -13,6 +13,7 @@ use crate::api::AppState; #[derive(Debug, Deserialize)] pub struct CallbackQuery { pub code: String, + pub tenant_id: Option, pub next: Option, } @@ -30,7 +31,6 @@ struct Code2TokenData { access_token: String, refresh_token: String, expires_in: usize, - token_type: String, tenant_id: String, user_id: String, } @@ -121,6 +121,17 @@ async fn sso_callback_handler( return Ok(Redirect::temporary(&target).into_response()); } + let tenant_id = q + .tenant_id + .as_deref() + .unwrap_or("") + .trim() + .to_string(); + if uuid::Uuid::parse_str(&tenant_id).is_err() { + let target = resolve_front_error_redirect("missing or invalid tenant_id"); + return Ok(Redirect::temporary(&target).into_response()); + } + let iam_base = std::env::var("IAM_BASE_URL") .or_else(|_| std::env::var("IAM_SERVICE_BASE_URL")) .map_err(|_| AppError::ConfigError("IAM_BASE_URL is required".into()))?; @@ -132,9 +143,10 @@ async fn sso_callback_handler( let http = reqwest::Client::new(); let resp = http .post(format!( - "{}/iam/api/v1/auth/code2token", + "{}/api/v1/auth/code2token", iam_base.trim_end_matches('/') )) + .header("X-Tenant-ID", tenant_id) .json(&Code2TokenRequest { code: q.code, client_id, diff --git a/src/api/mod.rs b/src/api/mod.rs index 9c61478..2a9b587 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -29,7 +29,7 @@ pub fn build_router(state: AppState) -> Router { .nest("/media", handlers::media::router()) .nest("/articles", handlers::article::router()); - let app = Router::new() + Router::new() .route("/favicon.ico", get(|| async { axum::http::StatusCode::NO_CONTENT })) .merge(Scalar::with_url("/scalar", ApiDoc::openapi())) .merge(health) @@ -37,7 +37,5 @@ pub fn build_router(state: AppState) -> Router { .nest("/v1", v1) .layer(axum::middleware::from_fn(catch_panic)) .layer(axum::middleware::from_fn(request_logger)) - .with_state(state); - - app + .with_state(state) } diff --git a/src/application/services/mod.rs b/src/application/services/mod.rs index 7081e19..bef839a 100644 --- a/src/application/services/mod.rs +++ b/src/application/services/mod.rs @@ -47,6 +47,7 @@ impl CmsServices { repositories::column::get_column(&self.pool, tenant_id, id).await } + #[allow(clippy::too_many_arguments)] pub async fn update_column( &self, tenant_id: Uuid, @@ -110,6 +111,7 @@ impl CmsServices { repositories::tag::delete_tag(&self.pool, tenant_id, id).await } + #[allow(clippy::too_many_arguments)] pub async fn create_media( &self, tenant_id: Uuid, @@ -149,6 +151,7 @@ impl CmsServices { repositories::media::delete_media(&self.pool, tenant_id, id).await } + #[allow(clippy::too_many_arguments)] pub async fn create_article( &self, tenant_id: Uuid, @@ -190,6 +193,7 @@ impl CmsServices { repositories::article::list_articles(&self.pool, tenant_id, q).await } + #[allow(clippy::too_many_arguments)] pub async fn update_article( &self, tenant_id: Uuid, diff --git a/src/infrastructure/iam_client/mod.rs b/src/infrastructure/iam_client/mod.rs index 74eecca..b3a765f 100644 --- a/src/infrastructure/iam_client/mod.rs +++ b/src/infrastructure/iam_client/mod.rs @@ -125,10 +125,10 @@ impl IamClient { }; let now = Instant::now(); - if let Some(entry) = self.inner.cache.get(&key).map(|e| e.clone()) { - if entry.expires_at > now { - return Ok(entry.allowed); - } + if let Some(entry) = self.inner.cache.get(&key).map(|e| e.clone()) + && entry.expires_at > now + { + return Ok(entry.allowed); } let remote = self @@ -141,17 +141,17 @@ impl IamClient { Ok(allowed) } Err(e) => { - if let Some(entry) = self.inner.cache.get(&key).map(|e| e.clone()) { - if entry.stale_until > now { - tracing::warn!( - tenant_id = %tenant_id, - user_id = %user_id, - action = "iam_client.degraded_cache", - latency_ms = 0_u64, - error_code = "iam_degraded_cache" - ); - return Ok(entry.allowed); - } + if let Some(entry) = self.inner.cache.get(&key).map(|e| e.clone()) + && entry.stale_until > now + { + tracing::warn!( + tenant_id = %tenant_id, + user_id = %user_id, + action = "iam_client.degraded_cache", + latency_ms = 0_u64, + error_code = "iam_degraded_cache" + ); + return Ok(entry.allowed); } Err(e) } diff --git a/src/infrastructure/repositories/article.rs b/src/infrastructure/repositories/article.rs index aefce46..9dd5d51 100644 --- a/src/infrastructure/repositories/article.rs +++ b/src/infrastructure/repositories/article.rs @@ -29,6 +29,7 @@ async fn list_tag_ids_for_article( Ok(tags) } +#[allow(clippy::too_many_arguments)] pub async fn create_article( pool: &PgPool, tenant_id: Uuid, @@ -109,6 +110,7 @@ pub async fn get_article( Ok(ArticleWithTags { article, tag_ids }) } +#[allow(clippy::too_many_arguments)] pub async fn update_article( pool: &PgPool, tenant_id: Uuid, diff --git a/src/infrastructure/repositories/column.rs b/src/infrastructure/repositories/column.rs index 7df3fd9..92e0f2d 100644 --- a/src/infrastructure/repositories/column.rs +++ b/src/infrastructure/repositories/column.rs @@ -47,6 +47,7 @@ pub async fn get_column(pool: &PgPool, tenant_id: Uuid, id: Uuid) -> Result