use crate::types::TenantId;
use super::super::super::catalog::StoredUser;
use super::super::super::identity::Role;
use super::super::super::time::now_secs;
use super::super::hash::{
compute_scram_salted_password, generate_scram_salt, hash_password_argon2,
};
use super::super::record::UserRecord;
use super::core::{CredentialStore, read_lock};
impl CredentialStore {
pub fn prepare_user(
&self,
username: &str,
password: &str,
tenant_id: TenantId,
roles: Vec<Role>,
) -> crate::Result<StoredUser> {
{
let users = read_lock(&self.users)?;
if users.contains_key(username) {
return Err(crate::Error::BadRequest {
detail: format!("user '{username}' already exists"),
});
}
}
let salt = generate_scram_salt();
let scram_salted_password = compute_scram_salted_password(password, &salt);
let password_hash = hash_password_argon2(password, &self.argon2_config)?;
let user_id = self.alloc_user_id()?;
let is_superuser = roles.contains(&Role::Superuser);
let now = now_secs();
Ok(StoredUser {
user_id,
username: username.to_string(),
tenant_id: tenant_id.as_u64(),
password_hash,
scram_salt: salt,
scram_salted_password,
roles: roles.iter().map(|r| r.to_string()).collect(),
is_superuser,
is_active: true,
is_service_account: false,
created_at: now,
updated_at: now,
password_expires_at: self.compute_expiry(),
must_change_password: false,
password_changed_at: now,
default_database_id: 0,
accessible_databases: vec![],
})
}
pub fn prepare_user_update(
&self,
username: &str,
new_password: Option<&str>,
new_roles: Option<Vec<Role>>,
) -> crate::Result<StoredUser> {
let users = read_lock(&self.users)?;
let existing = users
.get(username)
.ok_or_else(|| crate::Error::BadRequest {
detail: format!("user '{username}' not found"),
})?;
if !existing.is_active {
return Err(crate::Error::BadRequest {
detail: format!("user '{username}' is inactive"),
});
}
let mut stored = existing.to_stored();
drop(users);
if let Some(pw) = new_password {
let salt = generate_scram_salt();
stored.scram_salted_password = compute_scram_salted_password(pw, &salt);
stored.scram_salt = salt;
stored.password_hash = hash_password_argon2(pw, &self.argon2_config)?;
stored.password_expires_at = self.compute_expiry();
stored.must_change_password = false;
stored.password_changed_at = now_secs();
}
if let Some(roles) = new_roles {
stored.is_superuser = roles.contains(&Role::Superuser);
stored.roles = roles.iter().map(|r| r.to_string()).collect();
}
stored.updated_at = now_secs();
Ok(stored)
}
pub fn prepare_set_must_change_password(
&self,
username: &str,
required: bool,
) -> crate::Result<StoredUser> {
let users = read_lock(&self.users)?;
let existing = users
.get(username)
.ok_or_else(|| crate::Error::BadRequest {
detail: format!("user '{username}' not found"),
})?;
if !existing.is_active {
return Err(crate::Error::BadRequest {
detail: format!("user '{username}' is inactive"),
});
}
let mut stored = existing.to_stored();
drop(users);
stored.must_change_password = required;
stored.updated_at = now_secs();
Ok(stored)
}
pub fn prepare_set_password_expires_at(
&self,
username: &str,
expires_at: u64,
) -> crate::Result<StoredUser> {
let users = read_lock(&self.users)?;
let existing = users
.get(username)
.ok_or_else(|| crate::Error::BadRequest {
detail: format!("user '{username}' not found"),
})?;
if !existing.is_active {
return Err(crate::Error::BadRequest {
detail: format!("user '{username}' is inactive"),
});
}
let mut stored = existing.to_stored();
drop(users);
stored.password_expires_at = expires_at;
stored.updated_at = now_secs();
Ok(stored)
}
pub fn prepare_set_default_database(
&self,
username: &str,
database_id: u64,
) -> crate::Result<StoredUser> {
let users = read_lock(&self.users)?;
let existing = users
.get(username)
.ok_or_else(|| crate::Error::BadRequest {
detail: format!("user '{username}' not found"),
})?;
if !existing.is_active {
return Err(crate::Error::BadRequest {
detail: format!("user '{username}' is inactive"),
});
}
let mut stored = existing.to_stored();
drop(users);
stored.default_database_id = database_id;
stored.updated_at = now_secs();
Ok(stored)
}
pub fn install_replicated_user(
&self,
stored: &StoredUser,
invalidation: Option<super::super::super::buses::SessionInvalidationReason>,
) {
let mut record = UserRecord::from_stored(stored.clone());
{
let mut next = self.next_user_id.write().unwrap_or_else(|p| p.into_inner());
if stored.user_id + 1 > *next {
*next = stored.user_id + 1;
}
}
let _ = self.commit_user_mutation(&mut record, invalidation);
let mut users = self.users.write().unwrap_or_else(|p| p.into_inner());
users.insert(stored.username.clone(), record);
}
pub fn install_replicated_drop(&self, username: &str) {
let record = {
let mut users = self.users.write().unwrap_or_else(|p| p.into_inner());
users.remove(username)
};
if let Some(record) = record {
let _ = self.purge_user(&record);
}
}
}