diff --git a/src/controllers/login_controller.rs b/src/controllers/login_controller.rs index 65f77f8..cb0cb4e 100644 --- a/src/controllers/login_controller.rs +++ b/src/controllers/login_controller.rs @@ -11,6 +11,7 @@ use crate::controllers::base_controller::{FatalErrorPage, redirect_user, redirec use crate::data::login_redirect_query::LoginRedirectQuery; use crate::data::remote_ip::RemoteIP; use crate::data::session_identity::{SessionIdentity, SessionStatus}; +use crate::data::user::{TwoFactor, User}; struct BaseLoginPage { danger: Option, @@ -34,6 +35,14 @@ struct PasswordResetTemplate { min_pass_len: usize, } +#[derive(Template)] +#[template(path = "login/choose_second_factor.html")] +struct ChooseSecondFactorTemplate<'a> { + _p: BaseLoginPage, + factors: &'a [TwoFactor], +} + + #[derive(serde::Deserialize)] pub struct LoginRequestBody { login: String, @@ -87,6 +96,11 @@ pub async fn login_route( return redirect_user(&format!("/reset_password?redirect={}", query.redirect.get_encoded())); } + // Check if the user has to valide a second factor + if SessionIdentity(&id).need_2fa_auth() { + return redirect_user(&format!("/2fa_auth?redirect={}", query.redirect.get_encoded())); + } + // Try to authenticate user if let Some(req) = &req { login = req.login.clone(); @@ -105,6 +119,9 @@ pub async fn login_route( return if user.need_reset_password { SessionIdentity(&id).set_status(SessionStatus::NeedNewPassword); redirect_user(&format!("/reset_password?redirect={}", query.redirect.get_encoded())) + } else if user.has_two_factor() { + SessionIdentity(&id).set_status(SessionStatus::Need2FA); + redirect_user(&format!("/2fa_auth?redirect={}", query.redirect.get_encoded())) } else { redirect_user(query.redirect.get()) }; @@ -159,7 +176,7 @@ pub struct PasswordResetQuery { /// Reset user password route pub async fn reset_password_route(id: Identity, query: web::Query, req: Option>, - users: web::Data>, ) -> impl Responder { + users: web::Data>) -> impl Responder { let mut danger = None; if !SessionIdentity(&id).need_new_password() { @@ -203,4 +220,37 @@ pub async fn reset_password_route(id: Identity, query: web::Query, + users: web::Data>) -> impl Responder { + if !SessionIdentity(&id).need_2fa_auth() { + return redirect_user_for_login(query.redirect.get()); + } + + let user: User = users.send(users_actor::GetUserRequest(SessionIdentity(&id).user_id())) + .await.unwrap().0.expect("Could not find user!"); + + HttpResponse::Ok().content_type("text/html").body( + ChooseSecondFactorTemplate { + _p: BaseLoginPage { + page_title: "Two factor authentication", + danger: None, + success: None, + app_name: APP_NAME, + redirect_uri: query.redirect.get_encoded(), + }, + factors: &user.two_factor, + } + .render() + .unwrap(), + ) } \ No newline at end of file diff --git a/src/data/session_identity.rs b/src/data/session_identity.rs index c982774..1286041 100644 --- a/src/data/session_identity.rs +++ b/src/data/session_identity.rs @@ -9,7 +9,7 @@ pub enum SessionStatus { Invalid, SignedIn, NeedNewPassword, - NeedMFA, + Need2FA, } impl Default for SessionStatus { @@ -89,6 +89,12 @@ impl<'a> SessionIdentity<'a> { .unwrap_or(false) } + pub fn need_2fa_auth(&self) -> bool { + self.get_session_data() + .map(|s| s.status == SessionStatus::Need2FA) + .unwrap_or(false) + } + pub fn is_admin(&self) -> bool { self.get_session_data().unwrap_or_default().is_admin } diff --git a/src/data/user.rs b/src/data/user.rs index 5bab0bb..35269ed 100644 --- a/src/data/user.rs +++ b/src/data/user.rs @@ -26,6 +26,13 @@ impl TwoFactor { TwoFactorType::TOTP(_) => "Authenticator app" } } + + pub fn login_url(&self, redirect_uri: &str) -> String { + match self.kind { + TwoFactorType::TOTP(_) => format!("/2fa_totp?id={}&redirect_uri={}", + self.id.0, redirect_uri) + } + } } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] diff --git a/src/main.rs b/src/main.rs index ee0430f..959a961 100644 --- a/src/main.rs +++ b/src/main.rs @@ -111,6 +111,7 @@ async fn main() -> std::io::Result<()> { .route("/login", web::post().to(login_controller::login_route)) .route("/reset_password", web::get().to(login_controller::reset_password_route)) .route("/reset_password", web::post().to(login_controller::reset_password_route)) + .route("/2fa_auth", web::get().to(login_controller::choose_2fa_method)) // Logout page .route("/logout", web::get().to(login_controller::logout_route)) diff --git a/templates/login/choose_second_factor.html b/templates/login/choose_second_factor.html new file mode 100644 index 0000000..c351656 --- /dev/null +++ b/templates/login/choose_second_factor.html @@ -0,0 +1,23 @@ +{% extends "base_login_page.html" %} +{% block content %} + +
+

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

+ + {% for factor in factors %} +

+ + {{ factor.name }}
+ {{ factor.type_str() }} +
+

+ {% endfor %} +
+ + + + + +{% endblock content %} \ No newline at end of file