diff --git a/Cargo.lock b/Cargo.lock index 83c0a73..64e3c90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -935,6 +935,15 @@ dependencies = [ "slab", ] +[[package]] +name = "gen_passphrase" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f20bae32fbc2a12fe5c574fc0a9834ba7f70abe51c8efe315dedc7a07fd58287" +dependencies = [ + "nanorand", +] + [[package]] name = "generator" version = "0.7.3" @@ -1061,6 +1070,7 @@ name = "hash" version = "0.0.0" dependencies = [ "argon2", + "gen_passphrase", "nanoid", "nanoid-dictionary", "rand_core", diff --git a/crates/database/queries/users/set_paper_key.sql b/crates/database/queries/users/set_paper_key.sql new file mode 100644 index 0000000..cc9632e --- /dev/null +++ b/crates/database/queries/users/set_paper_key.sql @@ -0,0 +1,5 @@ +update users + +set paper_key = ? + +where id is ? \ No newline at end of file diff --git a/crates/database/sqlx-data.json b/crates/database/sqlx-data.json index 7e61b54..d2980fb 100644 --- a/crates/database/sqlx-data.json +++ b/crates/database/sqlx-data.json @@ -420,6 +420,16 @@ }, "query": "select id,\n created_at as \"created_at: DateTime\",\n updated_at as \"updated_at: DateTime\",\n business_name,\n business_logo,\n url\n\nfrom settings\n\nwhere id is 0\n" }, + "68cfa3d135eb4cdbdbcb3b943518b4ac09c371af689c444eb439a37f91ecf7a5": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Right": 2 + } + }, + "query": "update users\n\nset paper_key = ?\n\nwhere id is ?" + }, "69752cc2a3fc91d4e7a39e0b167695f431380bd40df9638b5df3534715de04b0": { "describe": { "columns": [ diff --git a/crates/database/src/tables/users.rs b/crates/database/src/tables/users.rs index 8992685..e894b96 100644 --- a/crates/database/src/tables/users.rs +++ b/crates/database/src/tables/users.rs @@ -135,4 +135,18 @@ impl Users { Ok((query.rows_affected() == 1).then_some(())) } + + pub async fn set_paper_key( + conn: impl SqliteExecutor<'_>, + id: &str, + paper_key: Option<&str>, + ) -> Result, Error> { + let query: SqliteQueryResult = + sqlx::query_file!("queries/users/set_paper_key.sql", paper_key, id) + .execute(conn) + .await + .map_err(handle_error)?; + + Ok((query.rows_affected() == 1).then_some(())) + } } diff --git a/crates/ezidam/src/icons.rs b/crates/ezidam/src/icons.rs index a0a42d0..7803e7f 100644 --- a/crates/ezidam/src/icons.rs +++ b/crates/ezidam/src/icons.rs @@ -39,7 +39,8 @@ impl Icon { "alert-triangle-large", AlertTriangleLarge, r#""#, "id-badge-2", IdBadge2, r#""#, "user", User, r#""#, - "at", At, r#""# + "at", At, r#""#, + "paperclip-large", PaperclipLarge, r#""# } } @@ -60,6 +61,7 @@ pub fn icons_to_templates(tera: &mut Tera) { Icon::IdBadge2, Icon::User, Icon::At, + Icon::PaperclipLarge, ]; // For each icon, it will output: ("icons/name", "...") diff --git a/crates/ezidam/src/routes/settings.rs b/crates/ezidam/src/routes/settings.rs index 9f6ec5b..96a039d 100644 --- a/crates/ezidam/src/routes/settings.rs +++ b/crates/ezidam/src/routes/settings.rs @@ -13,6 +13,7 @@ pub fn routes() -> Vec { user_settings_personal_form, user_settings_security, user_settings_security_logout_everywhere, + user_settings_security_paper_key, ] } diff --git a/crates/ezidam/src/routes/settings/security.rs b/crates/ezidam/src/routes/settings/security.rs index 3b57336..18b6532 100644 --- a/crates/ezidam/src/routes/settings/security.rs +++ b/crates/ezidam/src/routes/settings/security.rs @@ -4,6 +4,7 @@ use crate::tokens::{ JWT_COOKIE_NAME, JWT_DURATION_MINUTES, REFRESH_TOKEN_COOKIE_NAME, REFRESH_TOKEN_DURATION_DAYS, }; use apps::App; +use hash::PaperKey; use jwt::database::Key; use jwt::PrivateKey; use refresh_tokens::RefreshToken; @@ -126,3 +127,52 @@ pub async fn user_settings_security_logout_everywhere( flash_message, )) } + +#[derive(Debug, FromForm)] +pub struct PaperKeyForm { + pub generate_paper_key: bool, +} + +#[post("/settings/security/paper_key", data = "
")] +pub async fn user_settings_security_paper_key( + mut db: Connection, + jwt_user: JwtUser, + form: Form, +) -> Result> { + let (flash_kind, flash_message) = if form.generate_paper_key { + // Create new paper key + let paper_key = task::spawn_blocking(PaperKey::generate).await??; + + let mut transaction = db.begin().await?; + + // Get user info + let user = User::get_by_login(&mut transaction, &jwt_user.0.subject) + .await? + .ok_or_else(|| Error::not_found("Could not find user"))?; + + // Save paper key + user.set_paper_key(&mut transaction, Some(&paper_key)) + .await?; + + transaction.commit().await?; + + // Safety: safe to unwrap, the value is present + let plain_paper_key = paper_key.plain().unwrap(); + + ( + FlashKind::Success, + format!( + "Your paper key has been generated. It will only be shown once!\ +
{plain_paper_key}
" + ), + ) + } else { + (FlashKind::Warning, "Nothing to do.".into()) + }; + + Ok(Flash::new( + Redirect::to(uri!(user_settings_security)), + flash_kind, + flash_message, + )) +} diff --git a/crates/ezidam/templates/pages/settings/security.html.tera b/crates/ezidam/templates/pages/settings/security.html.tera index 08e882d..4834b65 100644 --- a/crates/ezidam/templates/pages/settings/security.html.tera +++ b/crates/ezidam/templates/pages/settings/security.html.tera @@ -55,7 +55,7 @@

You can use your paper key to reset your password if you forget it.

@@ -79,6 +79,51 @@ + + +