Two factor authentication : TOTP #5

Merged
pierre merged 22 commits from twofactors into master 2022-04-20 07:40:51 +00:00
7 changed files with 27 additions and 26 deletions
Showing only changes of commit 806a085c97 - Show all commits

View File

@ -8,44 +8,44 @@ use crate::actors::bruteforce_actor::BruteForceActor;
use crate::actors::users_actor::{ChangePasswordResult, LoginResult, UsersActor}; use crate::actors::users_actor::{ChangePasswordResult, LoginResult, UsersActor};
use crate::constants::{APP_NAME, MAX_FAILED_LOGIN_ATTEMPTS, MIN_PASS_LEN}; use crate::constants::{APP_NAME, MAX_FAILED_LOGIN_ATTEMPTS, MIN_PASS_LEN};
use crate::controllers::base_controller::{FatalErrorPage, redirect_user, redirect_user_for_login}; use crate::controllers::base_controller::{FatalErrorPage, redirect_user, redirect_user_for_login};
use crate::data::login_redirect_query::LoginRedirectQuery; use crate::data::login_redirect::LoginRedirect;
use crate::data::remote_ip::RemoteIP; use crate::data::remote_ip::RemoteIP;
use crate::data::session_identity::{SessionIdentity, SessionStatus}; use crate::data::session_identity::{SessionIdentity, SessionStatus};
use crate::data::user::{FactorID, TwoFactor, User}; use crate::data::user::{FactorID, TwoFactor, User};
struct BaseLoginPage { struct BaseLoginPage<'a> {
danger: Option<String>, danger: Option<String>,
success: Option<String>, success: Option<String>,
page_title: &'static str, page_title: &'static str,
app_name: &'static str, app_name: &'static str,
redirect_uri: String, redirect_uri: &'a LoginRedirect,
} }
#[derive(Template)] #[derive(Template)]
#[template(path = "login/login.html")] #[template(path = "login/login.html")]
struct LoginTemplate { struct LoginTemplate<'a> {
_p: BaseLoginPage, _p: BaseLoginPage<'a>,
login: String, login: String,
} }
#[derive(Template)] #[derive(Template)]
#[template(path = "login/password_reset.html")] #[template(path = "login/password_reset.html")]
struct PasswordResetTemplate { struct PasswordResetTemplate<'a> {
_p: BaseLoginPage, _p: BaseLoginPage<'a>,
min_pass_len: usize, min_pass_len: usize,
} }
#[derive(Template)] #[derive(Template)]
#[template(path = "login/choose_second_factor.html")] #[template(path = "login/choose_second_factor.html")]
struct ChooseSecondFactorTemplate<'a> { struct ChooseSecondFactorTemplate<'a> {
_p: BaseLoginPage, _p: BaseLoginPage<'a>,
factors: &'a [TwoFactor], factors: &'a [TwoFactor],
} }
#[derive(Template)] #[derive(Template)]
#[template(path = "login/opt_input.html")] #[template(path = "login/opt_input.html")]
struct LoginWithOTPTemplate<'a> { struct LoginWithOTPTemplate<'a> {
_p: BaseLoginPage, _p: BaseLoginPage<'a>,
factor: &'a TwoFactor, factor: &'a TwoFactor,
} }
@ -60,7 +60,7 @@ pub struct LoginRequestBody {
pub struct LoginRequestQuery { pub struct LoginRequestQuery {
logout: Option<bool>, logout: Option<bool>,
#[serde(default)] #[serde(default)]
redirect: LoginRedirectQuery, redirect: LoginRedirect,
} }
/// Authenticate user /// Authenticate user
@ -155,7 +155,7 @@ pub async fn login_route(
danger, danger,
success, success,
app_name: APP_NAME, app_name: APP_NAME,
redirect_uri: query.redirect.get_encoded(), redirect_uri: &query.redirect,
}, },
login, login,
} }
@ -177,7 +177,7 @@ pub struct ChangePasswordRequestBody {
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct PasswordResetQuery { pub struct PasswordResetQuery {
#[serde(default)] #[serde(default)]
redirect: LoginRedirectQuery, redirect: LoginRedirect,
} }
/// Reset user password route /// Reset user password route
@ -220,7 +220,7 @@ pub async fn reset_password_route(id: Identity, query: web::Query<PasswordResetQ
danger, danger,
success: None, success: None,
app_name: APP_NAME, app_name: APP_NAME,
redirect_uri: query.redirect.get_encoded(), redirect_uri: &query.redirect,
}, },
min_pass_len: MIN_PASS_LEN, min_pass_len: MIN_PASS_LEN,
} }
@ -232,7 +232,7 @@ pub async fn reset_password_route(id: Identity, query: web::Query<PasswordResetQ
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct ChooseSecondFactorQuery { pub struct ChooseSecondFactorQuery {
#[serde(default)] #[serde(default)]
redirect: LoginRedirectQuery, redirect: LoginRedirect,
} }
@ -253,7 +253,7 @@ pub async fn choose_2fa_method(id: Identity, query: web::Query<ChooseSecondFacto
danger: None, danger: None,
success: None, success: None,
app_name: APP_NAME, app_name: APP_NAME,
redirect_uri: query.redirect.get_encoded(), redirect_uri: &query.redirect,
}, },
factors: &user.two_factor, factors: &user.two_factor,
} }
@ -265,7 +265,7 @@ pub async fn choose_2fa_method(id: Identity, query: web::Query<ChooseSecondFacto
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct LoginWithOTPQuery { pub struct LoginWithOTPQuery {
#[serde(default)] #[serde(default)]
redirect: LoginRedirectQuery, redirect: LoginRedirect,
id: FactorID, id: FactorID,
} }
@ -292,7 +292,7 @@ pub async fn login_with_otp(id: Identity, query: web::Query<LoginWithOTPQuery>,
success: None, success: None,
page_title: "Two-Factor Auth", page_title: "Two-Factor Auth",
app_name: APP_NAME, app_name: APP_NAME,
redirect_uri: query.redirect.get_encoded(), redirect_uri: &query.redirect,
}, },
factor, factor,
}.render().unwrap()) }.render().unwrap())

View File

@ -1,7 +1,7 @@
#[derive(serde::Serialize, serde::Deserialize, Debug, Eq, PartialEq, Clone)] #[derive(serde::Serialize, serde::Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct LoginRedirectQuery(String); pub struct LoginRedirect(String);
impl LoginRedirectQuery { impl LoginRedirect {
pub fn get(&self) -> &str { pub fn get(&self) -> &str {
match self.0.starts_with('/') && !self.0.starts_with("//") { match self.0.starts_with('/') && !self.0.starts_with("//") {
true => self.0.as_str(), true => self.0.as_str(),
@ -14,7 +14,7 @@ impl LoginRedirectQuery {
} }
} }
impl Default for LoginRedirectQuery { impl Default for LoginRedirect {
fn default() -> Self { fn default() -> Self {
Self("/".to_string()) Self("/".to_string())
} }

View File

@ -12,4 +12,4 @@ pub mod code_challenge;
pub mod open_id_user_info; pub mod open_id_user_info;
pub mod access_token; pub mod access_token;
pub mod totp_key; pub mod totp_key;
pub mod login_redirect_query; pub mod login_redirect;

View File

@ -1,5 +1,6 @@
use crate::data::client::ClientID; use crate::data::client::ClientID;
use crate::data::entity_manager::EntityManager; use crate::data::entity_manager::EntityManager;
use crate::data::login_redirect::LoginRedirect;
use crate::data::totp_key::TotpKey; use crate::data::totp_key::TotpKey;
use crate::utils::err::Res; use crate::utils::err::Res;
@ -27,10 +28,10 @@ impl TwoFactor {
} }
} }
pub fn login_url(&self, redirect_uri: &str) -> String { pub fn login_url(&self, redirect_uri: &LoginRedirect) -> String {
match self.kind { match self.kind {
TwoFactorType::TOTP(_) => format!("/2fa_otp?id={}&redirect_uri={}", TwoFactorType::TOTP(_) => format!("/2fa_otp?id={}&redirect_uri={}",
self.id.0, redirect_uri) self.id.0, redirect_uri.get_encoded())
} }
} }
} }

View File

@ -1,6 +1,6 @@
{% extends "base_login_page.html" %} {% extends "base_login_page.html" %}
{% block content %} {% block content %}
<form action="/login?redirect={{ _p.redirect_uri }}" method="post"> <form action="/login?redirect={{ _p.redirect_uri.get_encoded() }}" method="post">
<div> <div>
<div class="form-floating"> <div class="form-floating">
<input name="login" type="text" required class="form-control" id="floatingName" placeholder="unsername" <input name="login" type="text" required class="form-control" id="floatingName" placeholder="unsername"

View File

@ -17,7 +17,7 @@
<div style="margin-top: 10px;"> <div style="margin-top: 10px;">
<a href="/2fa_auth?redirect=_p.redirect_uri">Sign in using another factor</a><br /> <a href="/2fa_auth?redirect={{ _p.redirect_uri.get_encoded() }}">Sign in using another factor</a><br />
<a href="/logout">Sign out</a> <a href="/logout">Sign out</a>
</div> </div>

View File

@ -1,6 +1,6 @@
{% extends "base_login_page.html" %} {% extends "base_login_page.html" %}
{% block content %} {% block content %}
<form action="/reset_password?redirect={{ _p.redirect_uri }}" method="post" id="reset_password_form"> <form action="/reset_password?redirect={{ _p.redirect_uri.get_encoded() }}" method="post" id="reset_password_form">
<div> <div>
<p>You need to configure a new password:</p> <p>You need to configure a new password:</p>