From f7d253de85b8d59d94cf4d645283e0b970b8be91 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sun, 6 Oct 2024 12:54:19 +0200 Subject: [PATCH] Add app tokens (secondary passwords) to afford cheaper hashes --- Cargo.lock | 15 +++++++++ Cargo.toml | 4 ++- crates/store/Cargo.toml | 2 ++ crates/store/src/auth/static_user_store.rs | 38 +++++++++++++++++----- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 607369b..684431c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1980,6 +1980,7 @@ dependencies = [ "argon2", "getrandom", "password-hash", + "pbkdf2", "rand_core", ] @@ -2000,6 +2001,18 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2459,6 +2472,8 @@ dependencies = [ "ical", "lazy_static", "password-auth", + "pbkdf2", + "rand_core", "regex", "rstest", "rstest_reuse", diff --git a/Cargo.toml b/Cargo.toml index bcb17fc..ded4b1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,9 @@ actix-web-httpauth = "0.8" anyhow = { version = "1.0", features = ["backtrace"] } serde = { version = "1.0", features = ["serde_derive", "derive", "rc"] } futures-util = "0.3" -password-auth = "1.0" +password-auth = { version = "1.0", features = ["argon2", "pbkdf2"] } +pbkdf2 = { version = "0.12", features = ["simple"] } +rand_core = { version = "0.6", features = ["std"] } chrono = { version = "0.4", features = ["serde"] } regex = "1.10" lazy_static = "1.5" diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index 46c13e0..c54d56d 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -25,3 +25,5 @@ password-auth = { workspace = true } actix-web = { workspace = true } actix-web-httpauth = { workspace = true } tracing = { workspace = true } +pbkdf2 = { workspace = true } +rand_core = { workspace = true } diff --git a/crates/store/src/auth/static_user_store.rs b/crates/store/src/auth/static_user_store.rs index 30c37f3..80a94b8 100644 --- a/crates/store/src/auth/static_user_store.rs +++ b/crates/store/src/auth/static_user_store.rs @@ -7,18 +7,31 @@ use super::AuthenticationProvider; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct StaticUserStoreConfig { - users: Vec, + users: Vec, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct UserEntry { + #[serde(flatten)] + pub user: User, + #[serde(default)] + pub app_tokens: Vec, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct StaticUserStore { - pub users: HashMap, + pub users: HashMap, } impl StaticUserStore { pub fn new(config: StaticUserStoreConfig) -> Self { Self { - users: HashMap::from_iter(config.users.into_iter().map(|user| (user.id.clone(), user))), + users: HashMap::from_iter( + config + .users + .into_iter() + .map(|user_entry| (user_entry.user.id.clone(), user_entry)), + ), } } } @@ -26,18 +39,27 @@ impl StaticUserStore { #[async_trait] impl AuthenticationProvider for StaticUserStore { async fn validate_user_token(&self, user_id: &str, token: &str) -> Result, Error> { - let user: User = match self.users.get(user_id) { + let user_entry: UserEntry = match self.users.get(user_id) { Some(user) => user.clone(), None => return Ok(None), }; - let password = match &user.password { + let password = match &user_entry.user.password { Some(password) => password, None => return Ok(None), }; - Ok(password_auth::verify_password(token, password) - .map(|()| user) - .ok()) + // Try app tokens first since they are cheaper to calculate + // They can afford less iterations since they can be generated with high entropy + for app_token in &user_entry.app_tokens { + if password_auth::verify_password(token, app_token).is_ok() { + return Ok(Some(user_entry.user)); + } + } + if password_auth::verify_password(token, password).is_ok() { + return Ok(Some(user_entry.user)); + } + + Ok(None) } }