feat(sso): sso login

This commit is contained in:
2026-02-03 17:31:56 +08:00
parent 2ddc11eb7b
commit a11c742e70
7 changed files with 39 additions and 21 deletions

View File

@@ -13,6 +13,7 @@ use crate::api::AppState;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct CallbackQuery { pub struct CallbackQuery {
pub code: String, pub code: String,
pub tenant_id: Option<String>,
pub next: Option<String>, pub next: Option<String>,
} }
@@ -30,7 +31,6 @@ struct Code2TokenData {
access_token: String, access_token: String,
refresh_token: String, refresh_token: String,
expires_in: usize, expires_in: usize,
token_type: String,
tenant_id: String, tenant_id: String,
user_id: String, user_id: String,
} }
@@ -121,6 +121,17 @@ async fn sso_callback_handler(
return Ok(Redirect::temporary(&target).into_response()); 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") let iam_base = std::env::var("IAM_BASE_URL")
.or_else(|_| std::env::var("IAM_SERVICE_BASE_URL")) .or_else(|_| std::env::var("IAM_SERVICE_BASE_URL"))
.map_err(|_| AppError::ConfigError("IAM_BASE_URL is required".into()))?; .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 http = reqwest::Client::new();
let resp = http let resp = http
.post(format!( .post(format!(
"{}/iam/api/v1/auth/code2token", "{}/api/v1/auth/code2token",
iam_base.trim_end_matches('/') iam_base.trim_end_matches('/')
)) ))
.header("X-Tenant-ID", tenant_id)
.json(&Code2TokenRequest { .json(&Code2TokenRequest {
code: q.code, code: q.code,
client_id, client_id,

View File

@@ -29,7 +29,7 @@ pub fn build_router(state: AppState) -> Router {
.nest("/media", handlers::media::router()) .nest("/media", handlers::media::router())
.nest("/articles", handlers::article::router()); .nest("/articles", handlers::article::router());
let app = Router::new() Router::new()
.route("/favicon.ico", get(|| async { axum::http::StatusCode::NO_CONTENT })) .route("/favicon.ico", get(|| async { axum::http::StatusCode::NO_CONTENT }))
.merge(Scalar::with_url("/scalar", ApiDoc::openapi())) .merge(Scalar::with_url("/scalar", ApiDoc::openapi()))
.merge(health) .merge(health)
@@ -37,7 +37,5 @@ pub fn build_router(state: AppState) -> Router {
.nest("/v1", v1) .nest("/v1", v1)
.layer(axum::middleware::from_fn(catch_panic)) .layer(axum::middleware::from_fn(catch_panic))
.layer(axum::middleware::from_fn(request_logger)) .layer(axum::middleware::from_fn(request_logger))
.with_state(state); .with_state(state)
app
} }

View File

@@ -47,6 +47,7 @@ impl CmsServices {
repositories::column::get_column(&self.pool, tenant_id, id).await repositories::column::get_column(&self.pool, tenant_id, id).await
} }
#[allow(clippy::too_many_arguments)]
pub async fn update_column( pub async fn update_column(
&self, &self,
tenant_id: Uuid, tenant_id: Uuid,
@@ -110,6 +111,7 @@ impl CmsServices {
repositories::tag::delete_tag(&self.pool, tenant_id, id).await repositories::tag::delete_tag(&self.pool, tenant_id, id).await
} }
#[allow(clippy::too_many_arguments)]
pub async fn create_media( pub async fn create_media(
&self, &self,
tenant_id: Uuid, tenant_id: Uuid,
@@ -149,6 +151,7 @@ impl CmsServices {
repositories::media::delete_media(&self.pool, tenant_id, id).await repositories::media::delete_media(&self.pool, tenant_id, id).await
} }
#[allow(clippy::too_many_arguments)]
pub async fn create_article( pub async fn create_article(
&self, &self,
tenant_id: Uuid, tenant_id: Uuid,
@@ -190,6 +193,7 @@ impl CmsServices {
repositories::article::list_articles(&self.pool, tenant_id, q).await repositories::article::list_articles(&self.pool, tenant_id, q).await
} }
#[allow(clippy::too_many_arguments)]
pub async fn update_article( pub async fn update_article(
&self, &self,
tenant_id: Uuid, tenant_id: Uuid,

View File

@@ -125,11 +125,11 @@ impl IamClient {
}; };
let now = Instant::now(); let now = Instant::now();
if let Some(entry) = self.inner.cache.get(&key).map(|e| e.clone()) { if let Some(entry) = self.inner.cache.get(&key).map(|e| e.clone())
if entry.expires_at > now { && entry.expires_at > now
{
return Ok(entry.allowed); return Ok(entry.allowed);
} }
}
let remote = self let remote = self
.check_permission_remote(tenant_id, permission, access_token) .check_permission_remote(tenant_id, permission, access_token)
@@ -141,8 +141,9 @@ impl IamClient {
Ok(allowed) Ok(allowed)
} }
Err(e) => { Err(e) => {
if let Some(entry) = self.inner.cache.get(&key).map(|e| e.clone()) { if let Some(entry) = self.inner.cache.get(&key).map(|e| e.clone())
if entry.stale_until > now { && entry.stale_until > now
{
tracing::warn!( tracing::warn!(
tenant_id = %tenant_id, tenant_id = %tenant_id,
user_id = %user_id, user_id = %user_id,
@@ -152,7 +153,6 @@ impl IamClient {
); );
return Ok(entry.allowed); return Ok(entry.allowed);
} }
}
Err(e) Err(e)
} }
} }

View File

@@ -29,6 +29,7 @@ async fn list_tag_ids_for_article(
Ok(tags) Ok(tags)
} }
#[allow(clippy::too_many_arguments)]
pub async fn create_article( pub async fn create_article(
pool: &PgPool, pool: &PgPool,
tenant_id: Uuid, tenant_id: Uuid,
@@ -109,6 +110,7 @@ pub async fn get_article(
Ok(ArticleWithTags { article, tag_ids }) Ok(ArticleWithTags { article, tag_ids })
} }
#[allow(clippy::too_many_arguments)]
pub async fn update_article( pub async fn update_article(
pool: &PgPool, pool: &PgPool,
tenant_id: Uuid, tenant_id: Uuid,

View File

@@ -47,6 +47,7 @@ pub async fn get_column(pool: &PgPool, tenant_id: Uuid, id: Uuid) -> Result<Colu
Ok(column) Ok(column)
} }
#[allow(clippy::too_many_arguments)]
pub async fn update_column( pub async fn update_column(
pool: &PgPool, pool: &PgPool,
tenant_id: Uuid, tenant_id: Uuid,

View File

@@ -3,6 +3,7 @@ use common_telemetry::AppError;
use sqlx::PgPool; use sqlx::PgPool;
use uuid::Uuid; use uuid::Uuid;
#[allow(clippy::too_many_arguments)]
pub async fn create_media( pub async fn create_media(
pool: &PgPool, pool: &PgPool,
tenant_id: Uuid, tenant_id: Uuid,