use crate::app_config::AppConfig; use crate::controllers::{HttpFailure, HttpResult}; use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod}; use crate::extractors::matrix_client_extractor::MatrixClientExtractor; use crate::extractors::session_extractor::MatrixGWSession; use crate::users::{User, UserEmail}; 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: MatrixGWSession, 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().openid_provider().redirect_url, ), })) } #[derive(serde::Deserialize)] pub struct FinishOpenIDLoginQuery { code: String, state: String, } /// Finish OIDC authentication pub async fn finish_oidc( session: MatrixGWSession, 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().openid_provider().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 = User::create_or_update_user(&UserEmail(mail), &user_name).await?; session.set_user(&user)?; Ok(HttpResponse::Ok().finish()) } /// Get current user information pub async fn auth_info(client: MatrixClientExtractor) -> HttpResult { Ok(HttpResponse::Ok().json(client.to_extended_user_info().await?)) } /// Sign out user pub async fn sign_out(auth: AuthExtractor, session: MatrixGWSession) -> HttpResult { match auth.method { AuthenticatedMethod::Cookie => { session.unset_current_user()?; } AuthenticatedMethod::Token(token) => { token.delete(&auth.user.email).await?; } AuthenticatedMethod::Dev => { // Nothing to be done, user is always authenticated } } Ok(HttpResponse::NoContent().finish()) }