Handle passwords

This commit is contained in:
traxys 2023-08-30 00:04:58 +02:00
parent e6d4f81533
commit 87d99c4875
5 changed files with 102 additions and 1 deletions

44
Cargo.lock generated
View file

@ -65,6 +65,18 @@ version = "1.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c6f84b74db2535ebae81eede2f39b947dcbf01d093ae5f791e5dd414a1bf289" 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]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.73" version = "0.1.73"
@ -222,6 +234,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@ -1275,6 +1296,7 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
name = "mail_accounts" name = "mail_accounts"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"argon2",
"axum", "axum",
"axum-extra", "axum-extra",
"base64 0.21.2", "base64 0.21.2",
@ -1287,6 +1309,7 @@ dependencies = [
"once_cell", "once_cell",
"openidconnect", "openidconnect",
"parking_lot", "parking_lot",
"secrecy",
"serde", "serde",
"serde_urlencoded", "serde_urlencoded",
"sqlx", "sqlx",
@ -1581,6 +1604,17 @@ dependencies = [
"regex", "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]] [[package]]
name = "paste" name = "paste"
version = "1.0.14" version = "1.0.14"
@ -2099,6 +2133,16 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "secrecy"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e"
dependencies = [
"serde",
"zeroize",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.183" version = "1.0.183"

View file

@ -5,6 +5,7 @@ authors = ["traxys <quentin@familleboyer.net>"]
edition = "2021" edition = "2021"
[dependencies] [dependencies]
argon2 = { version = "0.5.1", features = ["std"] }
axum = { version = "0.6.20", features = ["query"] } axum = { version = "0.6.20", features = ["query"] }
axum-extra = { version = "0.7.7", features = ["cookie"] } axum-extra = { version = "0.7.7", features = ["cookie"] }
base64 = "0.21.2" base64 = "0.21.2"
@ -17,6 +18,7 @@ jwt-simple = "0.11.6"
once_cell = "1.18.0" once_cell = "1.18.0"
openidconnect = "3.3.0" openidconnect = "3.3.0"
parking_lot = "0.12.1" parking_lot = "0.12.1"
secrecy = { version = "0.8.0", features = ["serde"] }
serde = { version = "1.0.183", features = ["derive"] } serde = { version = "1.0.183", features = ["derive"] }
serde_urlencoded = "0.7.1" serde_urlencoded = "0.7.1"
sqlx = { version = "0.7.1", features = ["runtime-tokio", "postgres", "uuid", "migrate"] } sqlx = { version = "0.7.1", features = ["runtime-tokio", "postgres", "uuid", "migrate"] }

View file

@ -0,0 +1,2 @@
-- Add migration script here
ALTER TABLE accounts ADD COLUMN password TEXT;

View file

@ -5,6 +5,10 @@ use std::{
sync::Arc, sync::Arc,
}; };
use argon2::{
password_hash::{rand_core::OsRng, SaltString},
Argon2, PasswordHasher,
};
use axum::{ use axum::{
async_trait, async_trait,
extract::{FromRef, FromRequestParts, Path, Query, State}, extract::{FromRef, FromRequestParts, Path, Query, State},
@ -27,6 +31,7 @@ use openidconnect::{
AccessTokenHash, AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, AccessTokenHash, AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce,
OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, TokenResponse, OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, TokenResponse,
}; };
use secrecy::{ExposeSecret, SecretString};
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use sqlx::{postgres::PgPoolOptions, PgPool}; use sqlx::{postgres::PgPoolOptions, PgPool};
use tera::Tera; use tera::Tera;
@ -331,6 +336,8 @@ enum Error {
Tera(#[from] tera::Error), Tera(#[from] tera::Error),
#[error("A JWT error occured")] #[error("A JWT error occured")]
Jwt(#[from] jwt_simple::Error), Jwt(#[from] jwt_simple::Error),
#[error("An argon2 error occured")]
Argon2(#[from] argon2::password_hash::Error),
#[error("An internal error occured")] #[error("An internal error occured")]
InternalError, InternalError,
} }
@ -369,6 +376,10 @@ impl IntoResponse for Error {
tracing::error!("JWT error: {e:?}"); tracing::error!("JWT error: {e:?}");
InternalError.into_response() 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("/")) Ok(Redirect::to("/"))
} }
#[derive(Deserialize, Debug)]
struct Password {
password: SecretString,
}
#[tracing::instrument(skip(state))]
async fn set_password(
state: State<Arc<AppState>>,
User(user): User,
Form(password): Form<Password>,
) -> Result<Redirect, Error> {
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] #[tokio::main]
async fn main() -> color_eyre::Result<()> { async fn main() -> color_eyre::Result<()> {
color_eyre::install()?; color_eyre::install()?;
@ -830,6 +870,7 @@ async fn main() -> color_eyre::Result<()> {
.route("/alias/recipient/add", post(add_recipient)) .route("/alias/recipient/add", post(add_recipient))
.route("/alias/recipient/delete", post(delete_recipient)) .route("/alias/recipient/delete", post(delete_recipient))
.route("/alias/delete", post(delete_alias)) .route("/alias/delete", post(delete_alias))
.route("/password", post(set_password))
.fallback(page_not_found) .fallback(page_not_found)
.with_state(Arc::new(AppState { .with_state(Arc::new(AppState {
db, db,

View file

@ -99,7 +99,19 @@
{% if user_error %} {% if user_error %}
<div class="alert alert-danger">{{ user_error }}</div> <div class="alert alert-danger">{{ user_error }}</div>
{% endif %} {% endif %}
<h2 class="title is-2">Mails</h2> <h2>Password</h2>
<form action="/password" method="post">
<div class="form-floating mb-3">
<input type="password"
class="form-control"
id="newPassword"
placeholder="password"
name="password">
<label for="newPassword">New Password</label>
</div>
<button type="submit" class="btn btn-primary">Change password</button>
</form>
<h2 class="title is-2 mt-2">Mails</h2>
<ul class="list-group"> <ul class="list-group">
{% for mail in mails %} {% for mail in mails %}
<li class="list-group-item d-flex justify-content-between align-items-center"> <li class="list-group-item d-flex justify-content-between align-items-center">