Don Kendall 1 week ago
committed by GitHub
parent
commit
c4aaa2508e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 14
      .env.template
  2. 2
      src/api/core/accounts.rs
  3. 32
      src/config.rs
  4. 40
      src/db/models/user.rs

14
.env.template

@ -306,6 +306,20 @@
## The default for new users. If changed, it will be updated during login for existing users.
# PASSWORD_ITERATIONS=600000
## Default KDF type for new user registrations. 0 = PBKDF2, 1 = Argon2id.
## Argon2id is recommended as it is memory-hard and more resistant to GPU-based brute-force attacks.
## When set to 1, CLIENT_KDF_ITERATIONS defaults to 3, CLIENT_KDF_MEMORY to 64, CLIENT_KDF_PARALLELISM to 4.
## Existing users are not affected; they can change their KDF in account settings.
# CLIENT_KDF_TYPE=0
## Default KDF iterations for new user registrations.
## For PBKDF2 (type 0): minimum 100000, default 600000.
## For Argon2id (type 1): minimum 1, default 3.
# CLIENT_KDF_ITERATIONS=600000
## Default Argon2id memory parameter (in MB) for new user registrations. Only used when CLIENT_KDF_TYPE=1.
# CLIENT_KDF_MEMORY=64
## Default Argon2id parallelism parameter for new user registrations. Only used when CLIENT_KDF_TYPE=1.
# CLIENT_KDF_PARALLELISM=4
## Controls whether users can set or show password hints. This setting applies globally to all users.
# PASSWORD_HINTS_ALLOWED=true

2
src/api/core/accounts.rs

@ -1236,7 +1236,7 @@ pub async fn _prelogin(data: Json<PreloginData>, conn: DbConn) -> Json<Value> {
let (kdf_type, kdf_iter, kdf_mem, kdf_para) = match User::find_by_mail(&data.email, &conn).await {
Some(user) => (user.client_kdf_type, user.client_kdf_iter, user.client_kdf_memory, user.client_kdf_parallelism),
None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT, None, None),
None => (User::client_kdf_type_default(), User::client_kdf_iter_default(), User::client_kdf_memory_default(), User::client_kdf_parallelism_default()),
};
Json(json!({

32
src/config.rs

@ -645,6 +645,18 @@ make_config! {
/// Password iterations |> Number of server-side passwords hashing iterations for the password hash.
/// The default for new users. If changed, it will be updated during login for existing users.
password_iterations: i32, true, def, 600_000;
/// Client KDF type |> The default KDF type for new user registrations. 0 = PBKDF2, 1 = Argon2id.
/// Argon2id is recommended as it is memory-hard and resistant to GPU-based attacks.
client_kdf_type: i32, true, def, 0;
/// Client KDF iterations |> The default KDF iterations for new user registrations.
/// For PBKDF2: default 600000. For Argon2id: default 3.
client_kdf_iterations: i32, true, def, 600_000;
/// Client KDF memory (MB) |> The default Argon2id memory parameter (in MB) for new user registrations.
/// Only used when client_kdf_type = 1 (Argon2id). Default: 64.
client_kdf_memory: i32, true, def, 64;
/// Client KDF parallelism |> The default Argon2id parallelism parameter for new user registrations.
/// Only used when client_kdf_type = 1 (Argon2id). Default: 4.
client_kdf_parallelism: i32, true, def, 4;
/// Allow password hints |> Controls whether users can set or show password hints. This setting applies globally to all users.
password_hints_allowed: bool, true, def, true;
/// Show password hint (Know the risks!) |> Controls whether a password hint should be shown directly in the web page
@ -946,6 +958,26 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> {
err!("PASSWORD_ITERATIONS should be at least 100000 or higher. The default is 600000!");
}
if cfg.client_kdf_type < 0 || cfg.client_kdf_type > 1 {
err!("CLIENT_KDF_TYPE must be 0 (PBKDF2) or 1 (Argon2id).");
}
if cfg.client_kdf_type == 0 && cfg.client_kdf_iterations < 100_000 {
err!("CLIENT_KDF_ITERATIONS must be at least 100000 for PBKDF2.");
}
if cfg.client_kdf_type == 1 {
if cfg.client_kdf_iterations < 1 {
err!("CLIENT_KDF_ITERATIONS must be at least 1 for Argon2id.");
}
if cfg.client_kdf_memory < 15 || cfg.client_kdf_memory > 1024 {
err!("CLIENT_KDF_MEMORY must be between 15 and 1024 (MB) for Argon2id.");
}
if cfg.client_kdf_parallelism < 1 || cfg.client_kdf_parallelism > 16 {
err!("CLIENT_KDF_PARALLELISM must be between 1 and 16 for Argon2id.");
}
}
let limit = 256;
if cfg.database_max_conns < 1 || cfg.database_max_conns > limit {
err!(format!("`DATABASE_MAX_CONNS` contains an invalid value. Ensure it is between 1 and {limit}.",));

40
src/db/models/user.rs

@ -102,8 +102,36 @@ pub struct UserStampException {
/// Local methods
impl User {
pub const CLIENT_KDF_TYPE_DEFAULT: i32 = UserKdfType::Pbkdf2 as i32;
pub const CLIENT_KDF_ITER_DEFAULT: i32 = 600_000;
pub fn client_kdf_type_default() -> i32 {
CONFIG.client_kdf_type()
}
pub fn client_kdf_iter_default() -> i32 {
let kdf_type = CONFIG.client_kdf_type();
let configured = CONFIG.client_kdf_iterations();
// If the admin set Argon2id but left iterations at the PBKDF2 default, use sensible Argon2id default
if kdf_type == UserKdfType::Argon2id as i32 && configured >= 100_000 {
3
} else {
configured
}
}
pub fn client_kdf_memory_default() -> Option<i32> {
if CONFIG.client_kdf_type() == UserKdfType::Argon2id as i32 {
Some(CONFIG.client_kdf_memory())
} else {
None
}
}
pub fn client_kdf_parallelism_default() -> Option<i32> {
if CONFIG.client_kdf_type() == UserKdfType::Argon2id as i32 {
Some(CONFIG.client_kdf_parallelism())
} else {
None
}
}
pub fn new(email: &str, name: Option<String>) -> Self {
let now = Utc::now().naive_utc();
@ -140,10 +168,10 @@ impl User {
equivalent_domains: "[]".to_string(),
excluded_globals: "[]".to_string(),
client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT,
client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT,
client_kdf_memory: None,
client_kdf_parallelism: None,
client_kdf_type: Self::client_kdf_type_default(),
client_kdf_iter: Self::client_kdf_iter_default(),
client_kdf_memory: Self::client_kdf_memory_default(),
client_kdf_parallelism: Self::client_kdf_parallelism_default(),
api_key: None,

Loading…
Cancel
Save