use axum::{
extract::State,
http::{HeaderMap, StatusCode},
Json,
};
use tracing::{info, warn};
use crate::api::{
auth_bridge::{
AuthError, AuthSession, AuthUser, LogoutRequest, RefreshRequest, SignInRequest,
SignUpRequest,
},
models::ApiError,
server::AppState,
};
fn map_auth_err(e: AuthError) -> ApiError {
let status = match e.error.as_str() {
"validation_failed" | "weak_password" => StatusCode::BAD_REQUEST,
"user_already_exists" => StatusCode::CONFLICT,
"invalid_credentials" | "invalid_token" | "token_expired" | "token_error"
| "invalid_signature" | "token_revoked" => StatusCode::UNAUTHORIZED,
"invalid_refresh_token" => StatusCode::UNAUTHORIZED,
"user_not_found" => StatusCode::NOT_FOUND,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
ApiError::new(status, &e.error, e.error_description.as_deref().unwrap_or(&e.error))
}
fn extract_bearer(headers: &HeaderMap) -> Option<String> {
headers
.get("authorization")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.strip_prefix("Bearer "))
.map(String::from)
}
pub async fn signup(
State(state): State<AppState>,
Json(body): Json<SignUpRequest>,
) -> Result<Json<AuthSession>, ApiError> {
info!("Auth signup request for email: {}", body.email);
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.sign_up(&body.email, &body.password).map_err(|e| {
warn!("Signup failed for {}: {}", body.email, e);
map_auth_err(e)
})?;
info!("User signed up: {}", session.user.id);
Ok(Json(session))
}
pub async fn signin(
State(state): State<AppState>,
Json(body): Json<SignInRequest>,
) -> Result<Json<AuthSession>, ApiError> {
info!("Auth signin request for email: {}", body.email);
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.sign_in(&body.email, &body.password).map_err(|e| {
warn!("Signin failed for {}: {}", body.email, e);
map_auth_err(e)
})?;
info!("User signed in: {}", session.user.id);
Ok(Json(session))
}
pub async fn logout(
State(state): State<AppState>,
Json(body): Json<LogoutRequest>,
) -> Result<StatusCode, ApiError> {
let auth = state
.auth_bridge
.as_ref()
.ok_or_else(|| ApiError::new(StatusCode::SERVICE_UNAVAILABLE, "auth_not_enabled", "Auth service is not configured"))?;
auth.sign_out(&body.refresh_token).map_err(|e| {
warn!("Logout failed: {}", e);
map_auth_err(e)
})?;
info!("User logged out (refresh token revoked)");
Ok(StatusCode::NO_CONTENT)
}
pub async fn refresh(
State(state): State<AppState>,
Json(body): Json<RefreshRequest>,
) -> Result<Json<AuthSession>, ApiError> {
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.refresh(&body.refresh_token).map_err(|e| {
warn!("Token refresh failed: {}", e);
map_auth_err(e)
})?;
info!("Token refreshed for user: {}", session.user.id);
Ok(Json(session))
}
pub async fn get_user(
State(state): State<AppState>,
headers: HeaderMap,
) -> Result<Json<AuthUser>, ApiError> {
let auth = state
.auth_bridge
.as_ref()
.ok_or_else(|| ApiError::new(StatusCode::SERVICE_UNAVAILABLE, "auth_not_enabled", "Auth service is not configured"))?;
let token = extract_bearer(&headers).ok_or_else(|| {
ApiError::unauthorized("Missing or invalid Authorization header")
})?;
let user = auth.get_user(&token).map_err(|e| {
warn!("Get user failed: {}", e);
map_auth_err(e)
})?;
Ok(Json(user))
}