diff --git a/Cargo.lock b/Cargo.lock index e901af7..49dbaef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,18 @@ version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c6f84b74db2535ebae81eede2f39b947dcbf01d093ae5f791e5dd414a1bf289" +[[package]] +name = "argon2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e554a8638bdc1e4eae9984845306cc95f8a9208ba8d49c3859fd958b46774d" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "async-trait" version = "0.1.73" @@ -222,6 +234,15 @@ dependencies = [ "serde", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -1275,6 +1296,7 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" name = "mail_accounts" version = "0.1.0" dependencies = [ + "argon2", "axum", "axum-extra", "base64 0.21.2", @@ -1287,6 +1309,7 @@ dependencies = [ "once_cell", "openidconnect", "parking_lot", + "secrecy", "serde", "serde_urlencoded", "sqlx", @@ -1581,6 +1604,17 @@ dependencies = [ "regex", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "paste" version = "1.0.14" @@ -2099,6 +2133,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + [[package]] name = "serde" version = "1.0.183" diff --git a/Cargo.toml b/Cargo.toml index d8e62a5..e0ad22d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ authors = ["traxys "] edition = "2021" [dependencies] +argon2 = { version = "0.5.1", features = ["std"] } axum = { version = "0.6.20", features = ["query"] } axum-extra = { version = "0.7.7", features = ["cookie"] } base64 = "0.21.2" @@ -17,6 +18,7 @@ jwt-simple = "0.11.6" once_cell = "1.18.0" openidconnect = "3.3.0" parking_lot = "0.12.1" +secrecy = { version = "0.8.0", features = ["serde"] } serde = { version = "1.0.183", features = ["derive"] } serde_urlencoded = "0.7.1" sqlx = { version = "0.7.1", features = ["runtime-tokio", "postgres", "uuid", "migrate"] } diff --git a/migrations/20230829213719_password.sql b/migrations/20230829213719_password.sql new file mode 100644 index 0000000..cd19c4f --- /dev/null +++ b/migrations/20230829213719_password.sql @@ -0,0 +1,2 @@ +-- Add migration script here +ALTER TABLE accounts ADD COLUMN password TEXT; diff --git a/src/main.rs b/src/main.rs index 67c752a..e8d883b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,10 @@ use std::{ sync::Arc, }; +use argon2::{ + password_hash::{rand_core::OsRng, SaltString}, + Argon2, PasswordHasher, +}; use axum::{ async_trait, extract::{FromRef, FromRequestParts, Path, Query, State}, @@ -27,6 +31,7 @@ use openidconnect::{ AccessTokenHash, AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, TokenResponse, }; +use secrecy::{ExposeSecret, SecretString}; use serde::{Deserialize, Deserializer}; use sqlx::{postgres::PgPoolOptions, PgPool}; use tera::Tera; @@ -331,6 +336,8 @@ enum Error { Tera(#[from] tera::Error), #[error("A JWT error occured")] Jwt(#[from] jwt_simple::Error), + #[error("An argon2 error occured")] + Argon2(#[from] argon2::password_hash::Error), #[error("An internal error occured")] InternalError, } @@ -369,6 +376,10 @@ impl IntoResponse for Error { tracing::error!("JWT error: {e:?}"); InternalError.into_response() } + Error::Argon2(e) => { + tracing::error!("Argon2 error: {e:?}"); + InternalError.into_response() + } } } } @@ -794,6 +805,35 @@ async fn delete_recipient( Ok(Redirect::to("/")) } +#[derive(Deserialize, Debug)] +struct Password { + password: SecretString, +} + +#[tracing::instrument(skip(state))] +async fn set_password( + state: State>, + User(user): User, + Form(password): Form, +) -> Result { + let salt = SaltString::generate(&mut OsRng); + + let argon2 = Argon2::default(); + let password_hash = argon2 + .hash_password(password.password.expose_secret().as_bytes(), &salt)? + .to_string(); + + sqlx::query!( + "UPDATE accounts SET password = $1 WHERE id = $2", + password_hash, + user + ) + .execute(&state.db) + .await?; + + Ok(Redirect::to("/")) +} + #[tokio::main] async fn main() -> color_eyre::Result<()> { color_eyre::install()?; @@ -830,6 +870,7 @@ async fn main() -> color_eyre::Result<()> { .route("/alias/recipient/add", post(add_recipient)) .route("/alias/recipient/delete", post(delete_recipient)) .route("/alias/delete", post(delete_alias)) + .route("/password", post(set_password)) .fallback(page_not_found) .with_state(Arc::new(AppState { db, diff --git a/templates/home.html b/templates/home.html index df6cf15..f44e140 100644 --- a/templates/home.html +++ b/templates/home.html @@ -99,7 +99,19 @@ {% if user_error %}
{{ user_error }}
{% endif %} -

Mails

+

Password

+
+
+ + +
+ +
+

Mails

    {% for mail in mails %}