Handle passwords
This commit is contained in:
parent
e6d4f81533
commit
87d99c4875
5 changed files with 102 additions and 1 deletions
44
Cargo.lock
generated
44
Cargo.lock
generated
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"] }
|
||||||
|
|
|
||||||
2
migrations/20230829213719_password.sql
Normal file
2
migrations/20230829213719_password.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- Add migration script here
|
||||||
|
ALTER TABLE accounts ADD COLUMN password TEXT;
|
||||||
41
src/main.rs
41
src/main.rs
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue