added recovery key gen and finished init flow

This commit is contained in:
Artemis 2025-03-19 10:31:14 +01:00
parent b37867b018
commit 2264d04ded
4 changed files with 81 additions and 4 deletions

View file

@ -383,6 +383,16 @@ input#ident {
margin-bottom: 1em;
}
pre.recovery-key {
text-align: center;
background-color: var(--clr-surface-tonal-a10);
border: 2pt solid var(--clr-surface-tonal-a50);
border-radius: 4pt;
font-size: 1.2em;
font-weight: bold;
padding: .5em 2em;
}
@media screen and (max-width: 400px) {
header.padded>nav {
display: flex;

View file

@ -4,12 +4,32 @@ use rocket::{form, request::FromParam};
use crate::db::{doll, schema::DollTagsDb};
const SYMBOLS: [char; 36] = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
];
/// Generate a random recovery key, of length 14 (16 with the two `-`)
/// and format `xxxx-xxxxxx-xxxx` using the charset `a-z0-9`
pub fn generate_recovery_key() -> String {
let uniform = Uniform::new_inclusive::<usize, usize>(0, 35);
let mut rng = thread_rng();
let first: String = (1..=4).map(|_| SYMBOLS[uniform.sample(&mut rng)]).collect();
let second: String = (1..=6).map(|_| SYMBOLS[uniform.sample(&mut rng)]).collect();
let third: String = (1..=4).map(|_| SYMBOLS[uniform.sample(&mut rng)]).collect();
format!("{}-{}-{}", first, second, third)
}
/// Generate 10 random doll tags IDs, hoping to have at least 5 of them available
pub fn generate_ids() -> Vec<i32> {
let uniform = Uniform::new_inclusive::<i32, i32>(100_000, 999_999);
let mut rng = thread_rng();
(1..=10).map(|_| uniform.sample(&mut rng)).collect()
}
/// Generates 10 random doll tags IDs and check against the DB to find up to 5 free ones
pub async fn pick_ids(mut db: DollTagsDb) -> Result<Vec<i32>, sqlx::Error> {
let mut ids_bundle = generate_ids();
let occupied_ids = doll::check_ids(&mut *db, &ids_bundle).await?;

View file

@ -1,14 +1,17 @@
use rocket::{form::Form, http::CookieJar, response::Redirect};
use std::net::IpAddr;
use rocket::{form::Form, http::CookieJar, response::Redirect, tokio::task};
use rocket_dyn_templates::{context, Template};
use totp_rs::Secret;
use crate::{
auth,
auth::{self, pw},
db::{
self,
otp::METHOD_TOTP,
schema::{DollTagsDb, User},
},
ids::generate_recovery_key,
pages::CommonTemplateState,
routes::{self, error_handlers::PageResult},
};
@ -52,7 +55,8 @@ pub async fn handle_totp_enable_start(
form: Form<OtpEnableForm>,
user: User,
cookies: &CookieJar<'_>,
_meta: CommonTemplateState,
meta: CommonTemplateState,
client_ip: IpAddr,
) -> PageResult {
if db::otp::has_otp(&mut *db, &user.id, METHOD_TOTP).await? {
return Ok(Redirect::to(uri!("/account", routes::account::settings::show_settings)).into());
@ -70,5 +74,33 @@ pub async fn handle_totp_enable_start(
))?;
}
todo!("meow")
warn!(
"[audit|{}] [{}] enabled TOTP 2FA",
client_ip,
user.id.to_string(),
);
let recovery_key = generate_recovery_key();
{
let recovery_key = recovery_key.clone();
let hashed_recovery_key = task::spawn_blocking(move || pw::hash(&recovery_key)).await??;
db::otp::add_otp_method(
&mut *db,
&user.id,
&totp.get_secret_base32(),
&hashed_recovery_key,
)
.await?;
}
Ok(Template::render(
"account/otp/confirm",
context! {
meta,
recovery_key,
},
)
.into())
}

View file

@ -0,0 +1,15 @@
{% extends "base" %}
{% import "macros/form" as form %}
{% block title %}TOTP enabled - {% endblock title %}
{% block main %}
<p>TOTP 2FA was successfully enabled.</p>
<p>
Before moving on, it's strongly recommended that you save this key somewhere safe as it will be
needed to disable 2FA should you lose access to your 2FA codes.
</p>
<pre class="recovery-key"><code>{{recovery_key}}</code></pre>
<a href="/account/settings" class="btn">Finish and go to settings</a>
{% endblock main %}