From dfb277d636936b1d820ab4937e13df993bac77ea Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Tue, 26 Mar 2024 21:07:29 +0100 Subject: [PATCH] Can force 2FA authent --- src/controllers/login_api.rs | 5 +--- src/controllers/login_controller.rs | 13 +++++--- src/data/force_2fa_auth.rs | 36 +++++++++++++++++++++++ src/data/mod.rs | 1 + src/data/user.rs | 12 ++++++-- templates/login/choose_second_factor.html | 8 +++-- templates/login/otp_input.html | 2 +- templates/login/webauthn_input.html | 2 +- 8 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 src/data/force_2fa_auth.rs diff --git a/src/controllers/login_api.rs b/src/controllers/login_api.rs index 4b1d001..587a264 100644 --- a/src/controllers/login_api.rs +++ b/src/controllers/login_api.rs @@ -16,6 +16,7 @@ pub struct AuthWebauthnRequest { credential: PublicKeyCredential, } +#[allow(clippy::too_many_arguments)] pub async fn auth_webauthn( id: Identity, req: web::Json, @@ -25,10 +26,6 @@ pub async fn auth_webauthn( users: web::Data>, logger: ActionLogger, ) -> impl Responder { - if !SessionIdentity(Some(&id)).need_2fa_auth() { - return HttpResponse::Unauthorized().json("No 2FA required!"); - } - let user_id = SessionIdentity(Some(&id)).user_id(); match manager.finish_authentication(&user_id, &req.opaque_state, &req.credential) { diff --git a/src/controllers/login_controller.rs b/src/controllers/login_controller.rs index b4f573e..112a7f7 100644 --- a/src/controllers/login_controller.rs +++ b/src/controllers/login_controller.rs @@ -13,6 +13,7 @@ use crate::controllers::base_controller::{ build_fatal_error_page, redirect_user, redirect_user_for_login, }; use crate::data::action_logger::{Action, ActionLogger}; +use crate::data::force_2fa_auth::Force2FAAuth; use crate::data::login_redirect::LoginRedirect; use crate::data::provider::{Provider, ProvidersManager}; use crate::data::session_identity::{SessionIdentity, SessionStatus}; @@ -311,8 +312,9 @@ pub async fn choose_2fa_method( id: Option, query: web::Query, users: web::Data>, + force2faauth: Force2FAAuth, ) -> impl Responder { - if !SessionIdentity(id.as_ref()).need_2fa_auth() { + if !SessionIdentity(id.as_ref()).need_2fa_auth() && !force2faauth.force { log::trace!("User does not require 2fa auth, redirecting"); return redirect_user_for_login(query.redirect.get()); } @@ -329,7 +331,7 @@ pub async fn choose_2fa_method( // Automatically choose factor if there is only one factor if user.get_distinct_factors_types().len() == 1 && !query.force_display { log::trace!("User has only one factor, using it by default"); - return redirect_user(&user.two_factor[0].login_url(&query.redirect)); + return redirect_user(&user.two_factor[0].login_url(&query.redirect, true)); } HttpResponse::Ok().content_type("text/html").body( @@ -360,6 +362,7 @@ pub struct LoginWithOTPForm { } /// Login with OTP +#[allow(clippy::too_many_arguments)] pub async fn login_with_otp( id: Option, query: web::Query, @@ -368,10 +371,11 @@ pub async fn login_with_otp( http_req: HttpRequest, remote_ip: RemoteIP, logger: ActionLogger, + force2faauth: Force2FAAuth, ) -> impl Responder { let mut danger = None; - if !SessionIdentity(id.as_ref()).need_2fa_auth() { + if !SessionIdentity(id.as_ref()).need_2fa_auth() && !force2faauth.force { return redirect_user_for_login(query.redirect.get()); } @@ -446,8 +450,9 @@ pub async fn login_with_webauthn( query: web::Query, manager: WebAuthManagerReq, users: web::Data>, + force2faauth: Force2FAAuth, ) -> impl Responder { - if !SessionIdentity(id.as_ref()).need_2fa_auth() { + if !SessionIdentity(id.as_ref()).need_2fa_auth() && !force2faauth.force { return redirect_user_for_login(query.redirect.get()); } diff --git a/src/data/force_2fa_auth.rs b/src/data/force_2fa_auth.rs new file mode 100644 index 0000000..c419d55 --- /dev/null +++ b/src/data/force_2fa_auth.rs @@ -0,0 +1,36 @@ +use crate::data::current_user::CurrentUser; +use actix_web::dev::Payload; +use actix_web::{web, Error, FromRequest, HttpRequest}; +use std::future::Future; +use std::pin::Pin; + +#[derive(serde::Deserialize)] +pub struct Force2FAAuthQuery { + #[serde(default)] + force_2fa: bool, +} + +pub struct Force2FAAuth { + pub force: bool, +} + +impl FromRequest for Force2FAAuth { + type Error = Error; + type Future = Pin>>>; + + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { + let req = req.clone(); + + let query = web::Query::::from_request(&req, payload) + .into_inner() + .unwrap(); + + Box::pin(async move { + let user = CurrentUser::from_request(&req, &mut Payload::None).await?; + + Ok(Self { + force: query.force_2fa && user.has_two_factor(), + }) + }) + } +} diff --git a/src/data/mod.rs b/src/data/mod.rs index 6e2cacd..20b5826 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -5,6 +5,7 @@ pub mod client; pub mod code_challenge; pub mod current_user; pub mod entity_manager; +pub mod force_2fa_auth; pub mod id_token; pub mod jwt_signer; pub mod login_redirect; diff --git a/src/data/user.rs b/src/data/user.rs index f693756..f3854fa 100644 --- a/src/data/user.rs +++ b/src/data/user.rs @@ -90,11 +90,17 @@ impl TwoFactor { } } - pub fn login_url(&self, redirect_uri: &LoginRedirect) -> String { + pub fn login_url(&self, redirect_uri: &LoginRedirect, force_2fa: bool) -> String { match self.kind { - TwoFactorType::TOTP(_) => format!("/2fa_otp?redirect={}", redirect_uri.get_encoded()), + TwoFactorType::TOTP(_) => format!( + "/2fa_otp?redirect={}&force_2fa={force_2fa}", + redirect_uri.get_encoded() + ), TwoFactorType::WEBAUTHN(_) => { - format!("/2fa_webauthn?redirect={}", redirect_uri.get_encoded()) + format!( + "/2fa_webauthn?redirect={}&force_2fa={force_2fa}", + redirect_uri.get_encoded() + ) } } } diff --git a/templates/login/choose_second_factor.html b/templates/login/choose_second_factor.html index 3cd4c19..c8a23f6 100644 --- a/templates/login/choose_second_factor.html +++ b/templates/login/choose_second_factor.html @@ -5,14 +5,16 @@

You need to validate a second factor to complete your login.

{% for factor in user.get_distinct_factors_types() %} - - Factor icon + + + Factor icon
{{ factor.type_str() }}
{{ factor.description_str() }}
-
+
{% endfor %} diff --git a/templates/login/otp_input.html b/templates/login/otp_input.html index 96b96a2..1a3e7ae 100644 --- a/templates/login/otp_input.html +++ b/templates/login/otp_input.html @@ -28,7 +28,7 @@ diff --git a/templates/login/webauthn_input.html b/templates/login/webauthn_input.html index 5128d28..54d515f 100644 --- a/templates/login/webauthn_input.html +++ b/templates/login/webauthn_input.html @@ -12,7 +12,7 @@