impl done on the user side!
This commit is contained in:
parent
a425780904
commit
f9354527b0
8 changed files with 212 additions and 5 deletions
|
@ -103,6 +103,7 @@ textarea,
|
|||
border-radius: 4pt;
|
||||
padding: 4pt 8pt;
|
||||
background-color: var(--clr-surface-tonal-a10);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
input,
|
||||
|
@ -126,6 +127,7 @@ textarea:hover,
|
|||
button:hover,
|
||||
.btn:hover {
|
||||
border-color: var(--clr-primary-a0);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
textarea {
|
||||
|
@ -157,7 +159,8 @@ section {
|
|||
|
||||
p.form-error,
|
||||
div.form-error,
|
||||
a.error {
|
||||
a.error,
|
||||
button.error {
|
||||
background-color: var(--clr-error-surface);
|
||||
border: 2pt solid var(--clr-error-primary-0);
|
||||
color: var(--clr-error-primary-40);
|
||||
|
@ -165,6 +168,15 @@ a.error {
|
|||
padding: .5em 1em;
|
||||
}
|
||||
|
||||
a.error:hover,
|
||||
button.error:hover,
|
||||
a.error:focus,
|
||||
button.error:focus,
|
||||
a.error:active,
|
||||
button.error:active {
|
||||
border-color: var(--clr-error-primary-40);
|
||||
}
|
||||
|
||||
p.note {
|
||||
font-size: .8em;
|
||||
}
|
||||
|
|
|
@ -54,3 +54,35 @@ pub async fn add_otp_method(
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deletes the given OTP method's configuration for the user
|
||||
pub async fn delete_otp_method(db: &mut DbHook, id: &Uuid, method: &str) -> sqlx::Result<()> {
|
||||
sqlx::query!(
|
||||
"delete from otp where user_id = $1 and otp_method = $2",
|
||||
id,
|
||||
method
|
||||
)
|
||||
.execute(&mut **db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Changes the recovery key for the given OTP method; if there was none defined, will do nothing
|
||||
pub async fn change_recovery_key(
|
||||
db: &mut DbHook,
|
||||
id: &Uuid,
|
||||
otp_method: &str,
|
||||
hashed_recovery_key: &str,
|
||||
) -> sqlx::Result<()> {
|
||||
sqlx::query!(
|
||||
"update otp set recovery_key = $3 where user_id = $1 and otp_method = $2",
|
||||
id,
|
||||
otp_method,
|
||||
hashed_recovery_key
|
||||
)
|
||||
.execute(&mut **db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -84,6 +84,10 @@ fn rocket() -> _ {
|
|||
account::common::export_data,
|
||||
account::otp::show_totp_enable_start,
|
||||
account::otp::handle_totp_enable_start,
|
||||
account::otp::show_confirm_totp_regenerate_key,
|
||||
account::otp::regenerate_key,
|
||||
account::otp::show_confirm_totp_disable,
|
||||
account::otp::handle_confirm_totp_disable,
|
||||
],
|
||||
)
|
||||
.mount(
|
||||
|
|
|
@ -102,6 +102,8 @@ pub async fn handle_totp_enable_start(
|
|||
.await?;
|
||||
}
|
||||
|
||||
auth::otp::remove_secret(cookies);
|
||||
|
||||
Ok(Template::render(
|
||||
"account/otp/confirm",
|
||||
context! {
|
||||
|
@ -111,3 +113,98 @@ pub async fn handle_totp_enable_start(
|
|||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
#[get("/settings/totp/generate-key")]
|
||||
pub async fn show_confirm_totp_regenerate_key(
|
||||
mut db: DollTagsDb,
|
||||
user: User,
|
||||
meta: CommonTemplateState,
|
||||
) -> 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());
|
||||
}
|
||||
|
||||
Ok(Template::render(
|
||||
"account/otp/confirm_regenerate_key",
|
||||
context! {
|
||||
meta,
|
||||
},
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
#[post("/settings/totp/generate-key")]
|
||||
pub async fn regenerate_key(
|
||||
mut db: DollTagsDb,
|
||||
user: User,
|
||||
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());
|
||||
}
|
||||
|
||||
warn!(
|
||||
"[audit|{}] [{}] regenerated TOTP 2FA recovery key",
|
||||
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::change_recovery_key(&mut *db, &user.id, METHOD_TOTP, &hashed_recovery_key).await?;
|
||||
}
|
||||
|
||||
Ok(Template::render(
|
||||
"account/otp/regenerate_key",
|
||||
context! {
|
||||
recovery_key,
|
||||
meta,
|
||||
},
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
#[get("/settings/totp/disable")]
|
||||
pub async fn show_confirm_totp_disable(
|
||||
mut db: DollTagsDb,
|
||||
user: User,
|
||||
meta: CommonTemplateState,
|
||||
) -> 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());
|
||||
}
|
||||
|
||||
Ok(Template::render(
|
||||
"account/otp/disable",
|
||||
context! {
|
||||
meta,
|
||||
},
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
#[post("/settings/totp/disable")]
|
||||
pub async fn handle_confirm_totp_disable(
|
||||
mut db: DollTagsDb,
|
||||
user: User,
|
||||
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());
|
||||
}
|
||||
|
||||
warn!(
|
||||
"[audit|{}] [{}] deactivated TOTP 2FA",
|
||||
client_ip,
|
||||
user.id.to_string(),
|
||||
);
|
||||
|
||||
db::otp::delete_otp_method(&mut *db, &user.id, METHOD_TOTP).await?;
|
||||
|
||||
Ok(Redirect::to(uri!("/account", routes::account::settings::show_settings)).into())
|
||||
}
|
||||
|
|
19
templates/account/otp/confirm_regenerate_key.html.tera
Normal file
19
templates/account/otp/confirm_regenerate_key.html.tera
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "base" %}
|
||||
{% import "macros/form" as form %}
|
||||
{% block title %}Regenerate your recovery key - {% endblock title %}
|
||||
{% block main %}
|
||||
<p>You're about to regenerate a recovery key.</p>
|
||||
|
||||
<p>
|
||||
This will invalidate the current 2FA recovery key and generate a new one
|
||||
which you'll have to store safely.
|
||||
</p>
|
||||
|
||||
<section class="split">
|
||||
<form method="post">
|
||||
<button type="submit" class="error">Regenerate a new key</button>
|
||||
</form>
|
||||
|
||||
<p><a href="/account/settings" class="btn">Or go back to the settings</a></p>
|
||||
</section>
|
||||
{% endblock main %}
|
24
templates/account/otp/disable.html.tera
Normal file
24
templates/account/otp/disable.html.tera
Normal file
|
@ -0,0 +1,24 @@
|
|||
{% extends "base" %}
|
||||
{% import "macros/form" as form %}
|
||||
{% block title %}Disable TOTP 2FA - {% endblock title %}
|
||||
{% block main %}
|
||||
<p>You're about to disable TOTP 2FA.</p>
|
||||
|
||||
<p>
|
||||
TOTP 2FA is an important additional security measure that comes into play when
|
||||
your password gets compromised by someone.
|
||||
</p>
|
||||
<p>
|
||||
If you're about to disable it due to having lost your recovery key, know that
|
||||
you can instead choose to <a href="/account/settings/totp/generate-key">generate a new key</a>.
|
||||
</p>
|
||||
<p>If you still want to disable TOTP 2FA on your account, click on the red button below.</p>
|
||||
|
||||
<section class="split">
|
||||
<form method="post">
|
||||
<button type="submit" class="error">Disable TOTP 2FA</button>
|
||||
</form>
|
||||
|
||||
<p><a href="/account/settings" class="btn">Or go back to the settings</a></p>
|
||||
</section>
|
||||
{% endblock main %}
|
11
templates/account/otp/regenerate_key.html.tera
Normal file
11
templates/account/otp/regenerate_key.html.tera
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% extends "base" %}
|
||||
{% import "macros/form" as form %}
|
||||
{% block title %}Regenerate your recovery key - {% endblock title %}
|
||||
{% block main %}
|
||||
<p>Here's your new recovery key.</p>
|
||||
|
||||
<pre class="recovery-key"><code>{{recovery_key}}</code></pre>
|
||||
|
||||
<p>Make sure to save it somewhere safe, it may come in handy.</p>
|
||||
<p><a href="/account/settings" class="btn">Finish and go back to settings</a></p>
|
||||
{% endblock main %}
|
|
@ -67,18 +67,26 @@
|
|||
</div>
|
||||
|
||||
<section id="otp">
|
||||
{% for method in enabled_otp_methods %}
|
||||
<p>Wow, you already have {{method}} OTP enabled even though it's not yet implemented.</p>
|
||||
{% else %}
|
||||
<h3>Two-factor authentication</h3>
|
||||
|
||||
{% if "totp" in enabled_otp_methods %}
|
||||
<p>You have enabled time-based 2FA, which will prompt you for a code on each login.</p>
|
||||
|
||||
<p>
|
||||
You may <a href="/account/settings/totp/generate-key">regenerate a recovery key</a>
|
||||
in case you lost your current one,
|
||||
or <a href="/account/settings/totp/disable">disable TOTP 2FA altogether</a>.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if enabled_otp_methods|length == 0 %}
|
||||
<p>
|
||||
You don't have two-factor auth enabled.<br />
|
||||
You can add one using your authenticator app of choice by clicking below.
|
||||
</p>
|
||||
|
||||
<a href="/account/settings/totp" class="btn">Enable 2FA with an authenticator</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<section id="data-export">
|
||||
|
|
Loading…
Add table
Reference in a new issue