use actix_remote_ip::RemoteIP; use actix_web::web::Data; use actix_web::{HttpResponse, Responder, web}; use light_openid::basic_state_manager::BasicStateManager; use crate::app_config::AppConfig; use crate::controllers::HttpResult; use crate::extractors::auth_extractor::AuthExtractor; use crate::extractors::local_auth_extractor::LocalAuthEnabled; #[derive(serde::Deserialize)] pub struct LocalAuthReq { username: String, password: String, } /// Perform local authentication pub async fn local_auth( local_auth_enabled: LocalAuthEnabled, req: web::Json, auth: AuthExtractor, ) -> impl Responder { if !*local_auth_enabled { log::error!("Local auth attempt while this authentication method is disabled!"); return HttpResponse::UnprocessableEntity().json("Local authentication is disabled!"); } if !AppConfig::get().check_local_login(&req.username, &req.password) { log::error!("Local auth attempt with invalid credentials!"); return HttpResponse::Unauthorized().json("Invalid credentials!"); } auth.authenticate(&req.username); HttpResponse::Accepted().json("Welcome") } #[derive(serde::Serialize)] struct StartOIDCResponse { url: String, } /// Start OIDC authentication pub async fn start_oidc(sm: Data, ip: RemoteIP) -> HttpResult { let prov = match AppConfig::get().openid_provider() { None => { return Ok(HttpResponse::UnprocessableEntity().json("OpenID is disabled!")); } Some(conf) => conf, }; let conf = light_openid::primitives::OpenIDConfig::load_from_url(prov.configuration_url).await?; Ok(HttpResponse::Ok().json(StartOIDCResponse { url: conf.gen_authorization_url( prov.client_id, &sm.gen_state(ip.0)?, &AppConfig::get().oidc_redirect_url(), ), })) } #[derive(serde::Deserialize)] pub struct FinishOpenIDLoginQuery { code: String, state: String, } /// Finish OIDC authentication pub async fn finish_oidc( sm: Data, remote_ip: RemoteIP, req: web::Json, auth: AuthExtractor, ) -> HttpResult { if let Err(e) = sm.validate_state(remote_ip.0, &req.state) { log::error!("Failed to validate OIDC CB state! {e}"); return Ok(HttpResponse::BadRequest().json("Invalid state!")); } let prov = match AppConfig::get().openid_provider() { None => { return Ok(HttpResponse::UnprocessableEntity().json("OpenID is disabled!")); } Some(conf) => conf, }; let conf = light_openid::primitives::OpenIDConfig::load_from_url(prov.configuration_url).await?; let (token, _) = conf .request_token( prov.client_id, prov.client_secret, &req.code, &AppConfig::get().oidc_redirect_url(), ) .await?; let (user_info, _) = conf.request_user_info(&token).await?; 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!")); } }; auth.authenticate(mail); Ok(HttpResponse::Ok().finish()) } #[derive(serde::Serialize)] struct CurrentUser { id: String, } /// Get current authenticated user pub async fn current_user(auth: AuthExtractor) -> impl Responder { HttpResponse::Ok().json(CurrentUser { id: auth.id().unwrap_or_else(|| "Anonymous".to_string()), }) } /// Sign out pub async fn sign_out(auth: AuthExtractor) -> impl Responder { auth.sign_out(); HttpResponse::Ok().finish() }