From da4b2046015fee2517e00f993e4f236831e92532 Mon Sep 17 00:00:00 2001 From: Philippe Loctaux
Date: Mon, 1 May 2023 16:31:58 +0200
Subject: [PATCH] totp: generate backup code, attempt to use backup code when
checking totp, delete backup after successful use
---
crates/ezidam/src/icons.rs | 4 +-
crates/ezidam/src/routes/oauth/totp.rs | 45 +++++++++++---
crates/ezidam/src/routes/settings.rs | 1 +
crates/ezidam/src/routes/settings/security.rs | 62 ++++++++++++++++++-
.../pages/settings/security.html.tera | 61 +++++++++++++++++-
crates/hash/src/secret.rs | 5 +-
crates/users/src/database.rs | 6 +-
crates/users/src/lib.rs | 3 +
8 files changed, 169 insertions(+), 18 deletions(-)
diff --git a/crates/ezidam/src/icons.rs b/crates/ezidam/src/icons.rs
index 2a27661..7834370 100644
--- a/crates/ezidam/src/icons.rs
+++ b/crates/ezidam/src/icons.rs
@@ -44,7 +44,8 @@ impl Icon {
"paperclip-large", PaperclipLarge, r#""#,
"users", Users, r#""#,
"mail", Mail, r#""#,
- "password", Password, r#""#
+ "password", Password, r#""#,
+ "2fa-large", TwoFaLarge, r#""#
}
}
@@ -70,6 +71,7 @@ pub fn icons_to_templates(tera: &mut Tera) {
Icon::Users,
Icon::Mail,
Icon::Password,
+ Icon::TwoFaLarge,
];
// For each icon, it will output: ("icons/name", "")
diff --git a/crates/ezidam/src/routes/oauth/totp.rs b/crates/ezidam/src/routes/oauth/totp.rs
index 26655b9..d4afd7e 100644
--- a/crates/ezidam/src/routes/oauth/totp.rs
+++ b/crates/ezidam/src/routes/oauth/totp.rs
@@ -2,7 +2,7 @@ use crate::routes::oauth::{redirect_uri, AUTHORIZATION_CODE_LEN};
use crate::routes::prelude::*;
use apps::App;
use authorization_codes::AuthorizationCode;
-use hash::SecretString;
+use hash::{Secret, SecretString};
use rocket::http::{Cookie, CookieJar};
use rocket::{get, post};
use users::totp_login_request::TOTP_REQUEST_COOKIE_NAME;
@@ -108,16 +108,36 @@ pub async fn totp_verify(
.totp_secret()
.ok_or_else(|| Error::bad_request("TOTP is not enabled for user"))?;
- // Create totp
- let totp = totp::new(totp_secret, None, user.username().to_string())?;
+ let (check_totp, delete_totp_backup) = match user.totp_backup_hashed().map(Secret::from_hash) {
+ Some(totp_backup) => {
+ let input_code = form.code.to_string();
- // Verify totp code
- if !totp.check_current(form.code)? {
- return Ok(Either::Right(Flash::new(
- Redirect::to(uri!(totp_page(auth_request))),
- FlashKind::Danger,
- "Wrong code. Please try again.",
- )));
+ let totp_backup_matches =
+ task::spawn_blocking(move || totp_backup.compare(&input_code)).await??;
+
+ if totp_backup_matches {
+ // Don't check totp, delete backup
+ (false, true)
+ } else {
+ // Check totp (since the check failed), do not delete backup
+ (true, false)
+ }
+ }
+ None => (true, false),
+ };
+
+ if check_totp {
+ // Create totp
+ let totp = totp::new(totp_secret, None, user.username().to_string())?;
+
+ // Verify totp code
+ if !totp.check_current(form.code)? {
+ return Ok(Either::Right(Flash::new(
+ Redirect::to(uri!(totp_page(auth_request))),
+ FlashKind::Danger,
+ "Wrong code. Please try again.",
+ )));
+ }
}
// Generate authorization code
@@ -131,6 +151,11 @@ pub async fn totp_verify(
// Mark totp token as used
totp_request.use_code(&mut transaction).await?;
+ // Delete totp backup if it got used
+ if delete_totp_backup {
+ user.set_totp_backup(&mut transaction, None).await?;
+ }
+
transaction.commit().await?;
// Delete cookie
diff --git a/crates/ezidam/src/routes/settings.rs b/crates/ezidam/src/routes/settings.rs
index a716148..c4e3065 100644
--- a/crates/ezidam/src/routes/settings.rs
+++ b/crates/ezidam/src/routes/settings.rs
@@ -19,6 +19,7 @@ pub fn routes() -> Vec