mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 08:12:24 +00:00
Add app tokens (secondary passwords) to afford cheaper hashes
This commit is contained in:
15
Cargo.lock
generated
15
Cargo.lock
generated
@@ -1980,6 +1980,7 @@ dependencies = [
|
|||||||
"argon2",
|
"argon2",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"password-hash",
|
"password-hash",
|
||||||
|
"pbkdf2",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2000,6 +2001,18 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
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]]
|
[[package]]
|
||||||
name = "pem-rfc7468"
|
name = "pem-rfc7468"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -2459,6 +2472,8 @@ dependencies = [
|
|||||||
"ical",
|
"ical",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"password-auth",
|
"password-auth",
|
||||||
|
"pbkdf2",
|
||||||
|
"rand_core",
|
||||||
"regex",
|
"regex",
|
||||||
"rstest",
|
"rstest",
|
||||||
"rstest_reuse",
|
"rstest_reuse",
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ actix-web-httpauth = "0.8"
|
|||||||
anyhow = { version = "1.0", features = ["backtrace"] }
|
anyhow = { version = "1.0", features = ["backtrace"] }
|
||||||
serde = { version = "1.0", features = ["serde_derive", "derive", "rc"] }
|
serde = { version = "1.0", features = ["serde_derive", "derive", "rc"] }
|
||||||
futures-util = "0.3"
|
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"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
lazy_static = "1.5"
|
lazy_static = "1.5"
|
||||||
|
|||||||
@@ -25,3 +25,5 @@ password-auth = { workspace = true }
|
|||||||
actix-web = { workspace = true }
|
actix-web = { workspace = true }
|
||||||
actix-web-httpauth = { workspace = true }
|
actix-web-httpauth = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
pbkdf2 = { workspace = true }
|
||||||
|
rand_core = { workspace = true }
|
||||||
|
|||||||
@@ -7,18 +7,31 @@ use super::AuthenticationProvider;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct StaticUserStoreConfig {
|
pub struct StaticUserStoreConfig {
|
||||||
users: Vec<User>,
|
users: Vec<UserEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct UserEntry {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub user: User,
|
||||||
|
#[serde(default)]
|
||||||
|
pub app_tokens: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct StaticUserStore {
|
pub struct StaticUserStore {
|
||||||
pub users: HashMap<String, User>,
|
pub users: HashMap<String, UserEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StaticUserStore {
|
impl StaticUserStore {
|
||||||
pub fn new(config: StaticUserStoreConfig) -> Self {
|
pub fn new(config: StaticUserStoreConfig) -> Self {
|
||||||
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]
|
#[async_trait]
|
||||||
impl AuthenticationProvider for StaticUserStore {
|
impl AuthenticationProvider for StaticUserStore {
|
||||||
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error> {
|
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error> {
|
||||||
let user: User = match self.users.get(user_id) {
|
let user_entry: UserEntry = match self.users.get(user_id) {
|
||||||
Some(user) => user.clone(),
|
Some(user) => user.clone(),
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
let password = match &user.password {
|
let password = match &user_entry.user.password {
|
||||||
Some(password) => password,
|
Some(password) => password,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(password_auth::verify_password(token, password)
|
// Try app tokens first since they are cheaper to calculate
|
||||||
.map(|()| user)
|
// They can afford less iterations since they can be generated with high entropy
|
||||||
.ok())
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user