use crate::app_config::AppConfig; use crate::controllers::{HttpFailure, HttpResult}; use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod}; use crate::extractors::money_session::MoneySession; use crate::services::{tokens_service, users_service}; use actix_remote_ip::RemoteIP; use actix_web::{HttpResponse, web}; use light_openid::primitives::OpenIDConfig; #[derive(serde::Serialize)] struct StartOIDCResponse { url: String, } /// Start OIDC authentication pub async fn start_oidc(session: MoneySession, remote_ip: RemoteIP) -> HttpResult { let prov = AppConfig::get().openid_provider(); let conf = match OpenIDConfig::load_from_url(prov.configuration_url).await { Ok(c) => c, Err(e) => { log::error!("Failed to fetch OpenID provider configuration! {e}"); return Ok(HttpResponse::InternalServerError() .json("Failed to fetch OpenID provider configuration!")); } }; let state = match session.gen_oidc_state(remote_ip.0) { Ok(s) => s, Err(e) => { log::error!("Failed to generate auth state! {e}"); return Ok(HttpResponse::InternalServerError().json("Failed to generate auth state!")); } }; Ok(HttpResponse::Ok().json(StartOIDCResponse { url: conf.gen_authorization_url( prov.client_id, &state, &AppConfig::get().oidc_redirect_url(), ), })) } #[derive(serde::Deserialize)] pub struct FinishOpenIDLoginQuery { code: String, state: String, } /// Finish OIDC authentication pub async fn finish_oidc( session: MoneySession, remote_ip: RemoteIP, req: web::Json, ) -> HttpResult { if let Err(e) = session.validate_state(&req.state, remote_ip.0) { log::error!("Failed to validate OIDC CB state! {e}"); return Ok(HttpResponse::BadRequest().json("Invalid state!")); } let prov = AppConfig::get().openid_provider(); let conf = OpenIDConfig::load_from_url(prov.configuration_url) .await .map_err(HttpFailure::OpenID)?; let (token, _) = conf .request_token( prov.client_id, prov.client_secret, &req.code, &AppConfig::get().oidc_redirect_url(), ) .await .map_err(HttpFailure::OpenID)?; let (user_info, _) = conf .request_user_info(&token) .await .map_err(HttpFailure::OpenID)?; if user_info.email_verified != Some(true) { log::error!("Email is not verified!"); return Ok(HttpResponse::Unauthorized().json("Email unverified by IDP!")); } let mail = match user_info.email { Some(m) => m, None => { return Ok(HttpResponse::Unauthorized().json("Email not provided by the IDP!")); } }; let user_name = user_info.name.unwrap_or_else(|| { format!( "{} {}", user_info.given_name.as_deref().unwrap_or(""), user_info.family_name.as_deref().unwrap_or("") ) }); let user = users_service::create_or_update_user(&mail, &user_name).await?; session.set_user(&user)?; Ok(HttpResponse::Ok().finish()) } /// Get current user information pub async fn auth_info(auth: AuthExtractor) -> HttpResult { Ok(HttpResponse::Ok().json(auth.user)) } /// Sign out user pub async fn sign_out(auth: AuthExtractor, session: MoneySession) -> HttpResult { match auth.method { AuthenticatedMethod::Cookie => { session.unset_current_user()?; } AuthenticatedMethod::Token(token) => { tokens_service::delete(token.user_id(), token.id()).await?; } AuthenticatedMethod::Dev => { // Nothing to be done, user is always authenticated } } Ok(HttpResponse::NoContent().finish()) }