Removed default admin password, the first admin account needs a configured one. Fixes #10
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
+77
-1
@@ -137,6 +137,42 @@ impl Storage {
|
||||
}
|
||||
}
|
||||
|
||||
/// Environment variable with the password of the first admin account. Takes precedence over the config file.
|
||||
#[cfg(feature = "ssr")]
|
||||
pub const ADMIN_PASSWORD_ENV: &str = "REZERVATOR_ADMIN_PASSWORD";
|
||||
#[cfg(feature = "ssr")]
|
||||
pub const ADMIN_PASSWORD_MIN_LEN: usize = 8;
|
||||
|
||||
/// Setup of the first admin account
|
||||
#[cfg(feature = "ssr")]
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct Admin {
|
||||
initial_password: Option<String>
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
impl Admin {
|
||||
/// Password for the admin account created on first start: the environment variable, or the
|
||||
/// `initial_password` config key. There is no default, a known password must never be used.
|
||||
pub fn initial_password(&self) -> Result<String, String> {
|
||||
resolve_initial_password(std::env::var(ADMIN_PASSWORD_ENV).ok(), self.initial_password.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
fn resolve_initial_password(env: Option<String>, config: Option<String>) -> Result<String, String> {
|
||||
let password = env.filter(|p| !p.is_empty()).or(config.filter(|p| !p.is_empty()))
|
||||
.ok_or(format!(
|
||||
"No admin account exists and no initial password is set. Set the {} environment variable or initial_password in the [admin] section of the config.",
|
||||
ADMIN_PASSWORD_ENV))?;
|
||||
|
||||
if password.chars().count() < ADMIN_PASSWORD_MIN_LEN {
|
||||
return Err(format!("Initial admin password must have at least {} characters.", ADMIN_PASSWORD_MIN_LEN))
|
||||
}
|
||||
|
||||
Ok(password)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[derive(Deserialize)]
|
||||
pub struct Configuration {
|
||||
@@ -146,7 +182,9 @@ pub struct Configuration {
|
||||
mailing: Mailing,
|
||||
logging: Logging,
|
||||
#[serde(default)]
|
||||
storage: Storage
|
||||
storage: Storage,
|
||||
#[serde(default)]
|
||||
admin: Admin
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@@ -169,6 +207,9 @@ impl Configuration {
|
||||
pub fn storage(&self) -> &Storage {
|
||||
&self.storage
|
||||
}
|
||||
pub fn admin(&self) -> &Admin {
|
||||
&self.admin
|
||||
}
|
||||
}
|
||||
|
||||
/// The config file holds passwords and the session key, so it must never lie in a directory
|
||||
@@ -244,6 +285,41 @@ mod tests {
|
||||
std::fs::remove_dir_all(base).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_password_is_required() {
|
||||
assert!(resolve_initial_password(None, None).is_err());
|
||||
assert!(resolve_initial_password(Some("".to_string()), Some("".to_string())).is_err());
|
||||
// The sample config must not ship a usable password
|
||||
assert!(resolve_initial_password(None, sample().admin.initial_password).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_password_from_config() {
|
||||
let admin: Admin = toml::from_str("initial_password = 'correct horse'").unwrap();
|
||||
|
||||
assert_eq!(resolve_initial_password(None, admin.initial_password), Ok("correct horse".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_password_env_takes_precedence() {
|
||||
assert_eq!(
|
||||
resolve_initial_password(Some("from-environment".to_string()), Some("from-config".to_string())),
|
||||
Ok("from-environment".to_string()));
|
||||
// An empty variable does not hide the config value
|
||||
assert_eq!(
|
||||
resolve_initial_password(Some("".to_string()), Some("from-config".to_string())),
|
||||
Ok("from-config".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_password_must_not_be_short() {
|
||||
assert!(resolve_initial_password(Some("admin".to_string()), None).is_err());
|
||||
assert!(resolve_initial_password(None, Some("1234567".to_string())).is_err());
|
||||
assert!(resolve_initial_password(None, Some("12345678".to_string())).is_ok());
|
||||
// Length is counted in characters, not bytes
|
||||
assert!(resolve_initial_password(None, Some("čšřžýáí".to_string())).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_dir_defaults_to_data() {
|
||||
// The sample config has the storage section commented out
|
||||
|
||||
Reference in New Issue
Block a user