use axum::{
extract::{Query, State},
http::StatusCode,
response::Redirect,
Json,
};
use std::collections::HashMap;
use tracing::{info, warn};
use crate::api::{
auth_bridge::AuthSession,
models::ApiError,
oauth::OAuthError,
server::AppState,
};
fn map_oauth_err(e: OAuthError) -> ApiError {
match &e {
OAuthError::ProviderNotFound(_) => {
ApiError::bad_request(e.to_string())
}
OAuthError::InvalidState => {
ApiError::new(StatusCode::UNAUTHORIZED, "invalid_state", e.to_string())
}
OAuthError::TokenExchange(_) => {
ApiError::new(
StatusCode::BAD_GATEWAY,
"token_exchange_failed",
e.to_string(),
)
}
OAuthError::UserInfoFetch(_) => {
ApiError::new(
StatusCode::BAD_GATEWAY,
"userinfo_fetch_failed",
e.to_string(),
)
}
OAuthError::ConfigError(_) => {
ApiError::new(
StatusCode::INTERNAL_SERVER_ERROR,
"oauth_config_error",
e.to_string(),
)
}
}
}
pub async fn authorize(
State(state): State<AppState>,
Query(params): Query<HashMap<String, String>>,
) -> Result<Redirect, ApiError> {
let provider = params.get("provider").ok_or_else(|| {
ApiError::bad_request("Missing required query parameter: provider")
})?;
info!("OAuth authorize request for provider: {provider}");
let registry = state
.oauth_registry
.as_ref()
.ok_or_else(|| {
ApiError::new(
StatusCode::SERVICE_UNAVAILABLE,
"oauth_not_configured",
"OAuth is not configured on this server",
)
})?;
let (url, oauth_state) = registry.get_authorize_url(provider).map_err(|e| {
warn!("OAuth authorize failed for {provider}: {e}");
map_oauth_err(e)
})?;
info!("OAuth redirecting to provider (state={oauth_state})");
Ok(Redirect::temporary(&url))
}
pub async fn callback(
State(state): State<AppState>,
Query(params): Query<HashMap<String, String>>,
) -> Result<Json<AuthSession>, ApiError> {
let code = params.get("code").ok_or_else(|| {
ApiError::bad_request("Missing required query parameter: code")
})?;
let state_param = params.get("state").ok_or_else(|| {
ApiError::bad_request("Missing required query parameter: state")
})?;
info!("OAuth callback received (state={state_param})");
let registry = state
.oauth_registry
.as_ref()
.ok_or_else(|| {
ApiError::new(
StatusCode::SERVICE_UNAVAILABLE,
"oauth_not_configured",
"OAuth is not configured on this server",
)
})?;
let user_info = registry.exchange_code(code, state_param).await.map_err(|e| {
warn!("OAuth code exchange failed: {e}");
map_oauth_err(e)
})?;
info!(
"OAuth user info received: email={}, provider={}",
user_info.email, user_info.provider
);
let auth = state
.auth_bridge
.as_ref()
.ok_or_else(|| {
ApiError::new(
StatusCode::SERVICE_UNAVAILABLE,
"auth_not_enabled",
"Auth service is not configured",
)
})?;
let session = auth.oauth_sign_in(&user_info).map_err(|e| {
warn!("OAuth sign-in failed for {}: {e}", user_info.email);
ApiError::new(
StatusCode::INTERNAL_SERVER_ERROR,
&e.error,
e.error_description.as_deref().unwrap_or(&e.error),
)
})?;
info!("OAuth sign-in successful: user_id={}", session.user.id);
Ok(Json(session))
}