fix(server): --session-cookie-secure flag; scope+char-count password; invalid-email test
This commit is contained in:
@@ -21,6 +21,10 @@ pub struct Config {
|
||||
|
||||
/// Send the session cookie with the `Secure` attribute (HTTPS-only). Disable
|
||||
/// only for plain-HTTP self-hosting behind no TLS at all.
|
||||
#[arg(long, env = "SESSION_COOKIE_SECURE", default_value_t = true)]
|
||||
#[arg(
|
||||
long = "session-cookie-secure",
|
||||
env = "SESSION_COOKIE_SECURE",
|
||||
default_value_t = true
|
||||
)]
|
||||
pub cookie_secure: bool,
|
||||
}
|
||||
|
||||
@@ -48,23 +48,26 @@ pub async fn serve(listener: TcpListener, state: AppState) -> anyhow::Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a user from the CLI (admin bootstrap). Reads the password from the
|
||||
/// `BOOTSTRAP_PASSWORD` env var if set, otherwise prompts (hidden input).
|
||||
/// Create a user from the CLI (admin bootstrap). Opens its own connection (CLI
|
||||
/// one-shot); reads the password from the `BOOTSTRAP_PASSWORD` env var if set,
|
||||
/// otherwise prompts (hidden input). The plaintext is not zeroized, but it is
|
||||
/// confined to the scope below and dropped before any network I/O.
|
||||
pub async fn create_user(database_url: &str, email: &str, role: Role) -> anyhow::Result<()> {
|
||||
let email = Email::parse(email).map_err(|err| anyhow::anyhow!("{err}"))?;
|
||||
|
||||
// Read, validate, and hash the password in a scope so the plaintext `String` is
|
||||
// dropped before we open a connection / run any awaits.
|
||||
let password_hash = {
|
||||
let password = match std::env::var("BOOTSTRAP_PASSWORD") {
|
||||
Ok(p) => p,
|
||||
Err(_) => rpassword::prompt_password("Password: ").context("reading password")?,
|
||||
};
|
||||
|
||||
anyhow::ensure!(
|
||||
password.len() >= 8,
|
||||
password.chars().count() >= 8,
|
||||
"password must be at least 8 characters"
|
||||
);
|
||||
|
||||
let password_hash =
|
||||
auth::hash_password(&password).map_err(|err| anyhow::anyhow!("hashing password: {err}"))?;
|
||||
auth::hash_password(&password).map_err(|err| anyhow::anyhow!("hashing password: {err}"))?
|
||||
};
|
||||
|
||||
let db = Db::connect(database_url)
|
||||
.await
|
||||
|
||||
@@ -38,3 +38,13 @@ async fn create_user_persists_and_password_verifies(pool: PgPool) {
|
||||
assert_eq!(user.role, Role::Admin);
|
||||
assert!(auth::verify_password("bootstrap-pw-123", &stored_hash));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_user_rejects_invalid_email() {
|
||||
// The email is parsed before the password is read or the DB is touched, so an
|
||||
// invalid email errors out without reaching the (unreachable) database URL.
|
||||
let err = server::create_user("postgres://unused", "not-an-email", Role::Admin)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(err.to_string().contains("email"), "got: {err}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user