style: cargo fmt --all for v0.1.3 SSO changes
Some checks failed
CI Pipeline / Rust Format Check (push) Successful in 2s
CI Pipeline / Clippy Lints (push) Successful in 55s
CI Pipeline / Rust Unit Tests (push) Successful in 1m14s
CI Pipeline / Security Audit (push) Successful in 4s
CI Pipeline / Frontend Lint & Type Check (push) Failing after 11s
CI Pipeline / Build .deb & Release (push) Has been skipped
Some checks failed
CI Pipeline / Rust Format Check (push) Successful in 2s
CI Pipeline / Clippy Lints (push) Successful in 55s
CI Pipeline / Rust Unit Tests (push) Successful in 1m14s
CI Pipeline / Security Audit (push) Successful in 4s
CI Pipeline / Frontend Lint & Type Check (push) Failing after 11s
CI Pipeline / Build .deb & Release (push) Has been skipped
This commit is contained in:
@ -216,7 +216,12 @@ async fn azure_callback(
|
|||||||
// Look up code_verifier from sso_sessions
|
// Look up code_verifier from sso_sessions
|
||||||
let sso_session = match state.sso_sessions.remove(&state_token).map(|(_, v)| v) {
|
let sso_session = match state.sso_sessions.remove(&state_token).map(|(_, v)| v) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => return Err(error_redirect("bad_request", "Invalid or expired state token")),
|
None => {
|
||||||
|
return Err(error_redirect(
|
||||||
|
"bad_request",
|
||||||
|
"Invalid or expired state token",
|
||||||
|
))
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read Azure SSO config (including client_secret for token exchange)
|
// Read Azure SSO config (including client_secret for token exchange)
|
||||||
@ -254,7 +259,7 @@ async fn azure_callback(
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(error = %e, "Failed to build HTTP client");
|
tracing::error!(error = %e, "Failed to build HTTP client");
|
||||||
return Err(error_redirect("internal_error", "HTTP client error"));
|
return Err(error_redirect("internal_error", "HTTP client error"));
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let params = [
|
let params = [
|
||||||
@ -268,32 +273,36 @@ async fn azure_callback(
|
|||||||
|
|
||||||
let form_params: Vec<(&str, String)> = params.to_vec();
|
let form_params: Vec<(&str, String)> = params.to_vec();
|
||||||
|
|
||||||
let token_resp = match client
|
let token_resp = match client.post(&token_url).form(&form_params).send().await {
|
||||||
.post(&token_url)
|
|
||||||
.form(&form_params)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(error = %e, "Token exchange request failed");
|
tracing::error!(error = %e, "Token exchange request failed");
|
||||||
return Err(error_redirect("sso_error", &format!("Token exchange failed: {}", e)));
|
return Err(error_redirect(
|
||||||
}
|
"sso_error",
|
||||||
|
&format!("Token exchange failed: {}", e),
|
||||||
|
));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if !token_resp.status().is_success() {
|
if !token_resp.status().is_success() {
|
||||||
let status = token_resp.status();
|
let status = token_resp.status();
|
||||||
let body = token_resp.text().await.unwrap_or_default();
|
let body = token_resp.text().await.unwrap_or_default();
|
||||||
tracing::error!(status = %status, body = %body, "Token exchange failed");
|
tracing::error!(status = %status, body = %body, "Token exchange failed");
|
||||||
return Err(error_redirect("sso_error", &format!("Token exchange failed: HTTP {}", status)));
|
return Err(error_redirect(
|
||||||
|
"sso_error",
|
||||||
|
&format!("Token exchange failed: HTTP {}", status),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let token_data: TokenResponse = match token_resp.json().await {
|
let token_data: TokenResponse = match token_resp.json().await {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(error = %e, "Failed to parse token response");
|
tracing::error!(error = %e, "Failed to parse token response");
|
||||||
return Err(error_redirect("internal_error", "Failed to parse token response"));
|
return Err(error_redirect(
|
||||||
}
|
"internal_error",
|
||||||
|
"Failed to parse token response",
|
||||||
|
));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Verify id_token JWT signature using Azure AD JWKS and validate claims
|
// Verify id_token JWT signature using Azure AD JWKS and validate claims
|
||||||
@ -306,8 +315,11 @@ async fn azure_callback(
|
|||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(error = %e, "Failed to verify id_token");
|
tracing::error!(error = %e, "Failed to verify id_token");
|
||||||
return Err(error_redirect("internal_error", "Failed to verify id_token"));
|
return Err(error_redirect(
|
||||||
}
|
"internal_error",
|
||||||
|
"Failed to verify id_token",
|
||||||
|
));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let email = claims.email.unwrap_or_default();
|
let email = claims.email.unwrap_or_default();
|
||||||
@ -316,7 +328,10 @@ async fn azure_callback(
|
|||||||
let preferred_username = claims.preferred_username.unwrap_or_else(|| email.clone());
|
let preferred_username = claims.preferred_username.unwrap_or_else(|| email.clone());
|
||||||
|
|
||||||
if email.is_empty() || oid.is_empty() {
|
if email.is_empty() || oid.is_empty() {
|
||||||
return Err(error_redirect("sso_error", "Missing email or oid in id_token"));
|
return Err(error_redirect(
|
||||||
|
"sso_error",
|
||||||
|
"Missing email or oid in id_token",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up or create user
|
// Look up or create user
|
||||||
@ -332,7 +347,7 @@ async fn azure_callback(
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(error = %e, "Failed to look up SSO user");
|
tracing::error!(error = %e, "Failed to look up SSO user");
|
||||||
return Err(error_redirect("internal_error", "Database error"));
|
return Err(error_redirect("internal_error", "Database error"));
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let user = match user_opt {
|
let user = match user_opt {
|
||||||
@ -358,7 +373,7 @@ async fn azure_callback(
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(error = %e, "Failed to create SSO user");
|
tracing::error!(error = %e, "Failed to create SSO user");
|
||||||
return Err(error_redirect("internal_error", "Failed to create user"));
|
return Err(error_redirect("internal_error", "Failed to create user"));
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
log_event(
|
log_event(
|
||||||
@ -411,15 +426,18 @@ async fn azure_callback(
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(error = %e, "Failed to issue access token");
|
tracing::error!(error = %e, "Failed to issue access token");
|
||||||
return Err(error_redirect("internal_error", "Token issuance failed"));
|
return Err(error_redirect("internal_error", "Token issuance failed"));
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let raw_refresh = match refresh::issue(&state.db, user.id, None, None).await {
|
let raw_refresh = match refresh::issue(&state.db, user.id, None, None).await {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(error = %e, "Failed to issue refresh token");
|
tracing::error!(error = %e, "Failed to issue refresh token");
|
||||||
return Err(error_redirect("internal_error", "Refresh token issuance failed"));
|
return Err(error_redirect(
|
||||||
}
|
"internal_error",
|
||||||
|
"Refresh token issuance failed",
|
||||||
|
));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
log_event(
|
log_event(
|
||||||
@ -476,8 +494,7 @@ async fn verify_id_token(
|
|||||||
jwks_cache: &Arc<Mutex<JwksCache>>,
|
jwks_cache: &Arc<Mutex<JwksCache>>,
|
||||||
) -> Result<IdTokenClaims, String> {
|
) -> Result<IdTokenClaims, String> {
|
||||||
// 1. Decode JWT header to get the kid
|
// 1. Decode JWT header to get the kid
|
||||||
let header = decode_header(token)
|
let header = decode_header(token).map_err(|e| format!("Failed to decode JWT header: {}", e))?;
|
||||||
.map_err(|e| format!("Failed to decode JWT header: {}", e))?;
|
|
||||||
|
|
||||||
let kid = header.kid.ok_or("JWT header missing 'kid' field")?;
|
let kid = header.kid.ok_or("JWT header missing 'kid' field")?;
|
||||||
|
|
||||||
@ -490,7 +507,7 @@ async fn verify_id_token(
|
|||||||
(Some(_), Some(fetched)) => {
|
(Some(_), Some(fetched)) => {
|
||||||
let elapsed = Utc::now().signed_duration_since(*fetched);
|
let elapsed = Utc::now().signed_duration_since(*fetched);
|
||||||
elapsed.num_seconds() > JWKS_CACHE_TTL_SECS
|
elapsed.num_seconds() > JWKS_CACHE_TTL_SECS
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if needs_fetch {
|
if needs_fetch {
|
||||||
|
|||||||
@ -273,7 +273,10 @@ async fn get_settings(
|
|||||||
let cfg = load_system_config(&state.db).await?;
|
let cfg = load_system_config(&state.db).await?;
|
||||||
// Inject read-only config values from TOML file (not stored in DB)
|
// Inject read-only config values from TOML file (not stored in DB)
|
||||||
let mut cfg = cfg;
|
let mut cfg = cfg;
|
||||||
cfg.insert("sso_callback_url".to_string(), state.config.security.sso_callback_url.clone());
|
cfg.insert(
|
||||||
|
"sso_callback_url".to_string(),
|
||||||
|
state.config.security.sso_callback_url.clone(),
|
||||||
|
);
|
||||||
let azure = fetch_azure_sso_config(&state.db).await?;
|
let azure = fetch_azure_sso_config(&state.db).await?;
|
||||||
Ok(Json(build_settings_response(&cfg, azure)))
|
Ok(Json(build_settings_response(&cfg, azure)))
|
||||||
}
|
}
|
||||||
@ -495,7 +498,10 @@ async fn update_settings(
|
|||||||
let cfg = load_system_config(&state.db).await?;
|
let cfg = load_system_config(&state.db).await?;
|
||||||
// Inject read-only config values from TOML file (not stored in DB)
|
// Inject read-only config values from TOML file (not stored in DB)
|
||||||
let mut cfg = cfg;
|
let mut cfg = cfg;
|
||||||
cfg.insert("sso_callback_url".to_string(), state.config.security.sso_callback_url.clone());
|
cfg.insert(
|
||||||
|
"sso_callback_url".to_string(),
|
||||||
|
state.config.security.sso_callback_url.clone(),
|
||||||
|
);
|
||||||
let azure = fetch_azure_sso_config(&state.db).await?;
|
let azure = fetch_azure_sso_config(&state.db).await?;
|
||||||
Ok(Json(build_settings_response(&cfg, azure)))
|
Ok(Json(build_settings_response(&cfg, azure)))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user