132 lines
3.8 KiB
Rust
132 lines
3.8 KiB
Rust
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<FinishOpenIDLoginQuery>,
|
|
) -> 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())
|
|
}
|