diff --git a/README.md b/README.md index 8486f35..96d5dd6 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ You can configure a list of clients (Relying Parties) in a `clients.yaml` file w default: true # If you want the client to be granted to every users, regardless their account configuration granted_to_all_users: true + # If you want users to have performed recent second factor authentication before accessing this client, set this setting to true + enforce_mfa_auth: true ``` On the first run, BasicOIDC will create a new administrator with credentials `admin` / `admin`. On first login you will have to change these default credentials. @@ -32,7 +34,7 @@ In order to run BasicOIDC for development, you will need to create a least an em * [x] `authorization_code` flow * [x] Client authentication using secrets * [x] Bruteforce protection -* [x] 2 factor authentication +* [x] 2 factors authentication * [x] TOTP (authenticator app) * [x] Using a security key (Webauthn) * [ ] Fully responsive webui diff --git a/src/controllers/openid_controller.rs b/src/controllers/openid_controller.rs index f014243..025f6d1 100644 --- a/src/controllers/openid_controller.rs +++ b/src/controllers/openid_controller.rs @@ -13,7 +13,7 @@ use crate::actors::openid_sessions_actor::{OpenIDSessionsActor, Session, Session use crate::actors::users_actor::UsersActor; use crate::actors::{openid_sessions_actor, users_actor}; use crate::constants::*; -use crate::controllers::base_controller::build_fatal_error_page; +use crate::controllers::base_controller::{build_fatal_error_page, redirect_user}; use crate::data::action_logger::{Action, ActionLogger}; use crate::data::app_config::AppConfig; use crate::data::client::{ClientID, ClientManager}; @@ -21,6 +21,7 @@ use crate::data::code_challenge::CodeChallenge; use crate::data::current_user::CurrentUser; use crate::data::id_token::IdToken; use crate::data::jwt_signer::{JWTSigner, JsonWebKey}; +use crate::data::login_redirect::{get_2fa_url, LoginRedirect}; use crate::data::session_identity::SessionIdentity; use crate::data::user::User; @@ -128,6 +129,7 @@ fn error_redirect(query: &AuthorizeQuery, error: &str, description: &str) -> Htt } pub async fn authorize( + req: HttpRequest, user: CurrentUser, id: Identity, query: web::Query, @@ -142,6 +144,12 @@ pub async fn authorize( Some(c) => c, }; + // Check if 2FA is required + if client.enforce_2fa_auth && user.should_request_2fa_for_critical_functions() { + let uri = get_2fa_url(&LoginRedirect::from_req(&req), true); + return redirect_user(&uri); + } + let redirect_uri = query.redirect_uri.trim().to_string(); if !redirect_uri.starts_with(&client.redirect_uri) { return HttpResponse::BadRequest().body(build_fatal_error_page("Redirect URI is invalid!")); diff --git a/src/data/client.rs b/src/data/client.rs index 7097580..1cdf75d 100644 --- a/src/data/client.rs +++ b/src/data/client.rs @@ -28,6 +28,10 @@ pub struct Client { /// Specify whether a client is granted to all users #[serde(default = "bool::default")] pub granted_to_all_users: bool, + + /// Specify whether recent Second Factor Authentication is required to access this client + #[serde(default = "bool::default")] + pub enforce_2fa_auth: bool, } impl PartialEq for Client { diff --git a/src/data/critical_route.rs b/src/data/critical_route.rs index 0e54e96..f8a2a85 100644 --- a/src/data/critical_route.rs +++ b/src/data/critical_route.rs @@ -20,10 +20,10 @@ impl FromRequest for CriticalRoute { .await .expect("Failed to extract user identity!"); - if current_user.should_request_2fa_for_critical_function() { - let url = get_2fa_url(&LoginRedirect::from_req(&req), true); + if current_user.should_request_2fa_for_critical_functions() { + let uri = get_2fa_url(&LoginRedirect::from_req(&req), true); - return Err(FromRequestRedirect::new(url)); + return Err(FromRequestRedirect::new(uri)); } Ok(Self) diff --git a/src/data/current_user.rs b/src/data/current_user.rs index 1a50b73..d1532ab 100644 --- a/src/data/current_user.rs +++ b/src/data/current_user.rs @@ -22,7 +22,7 @@ pub struct CurrentUser { } impl CurrentUser { - pub fn should_request_2fa_for_critical_function(&self) -> bool { + pub fn should_request_2fa_for_critical_functions(&self) -> bool { self.user.has_two_factor() && self .last_2fa_auth