Skip to main content

isb_server/auth/
dev.rs

1//! Switches for developing isb (the repository's preview, scripts/preview),
2//! which only debug builds honour. A release build with either one set
3//! refuses to run rather than ignore it.
4//!
5//! - `ISB_DEV_WEAK_PASSWORDS=1`: passwords of any length, not empty and not
6//!   over [`super::secret::MAX_PASSWORD_LEN`] bytes, wherever the policy is
7//!   checked (the CLI and the daemon).
8//! - `ISB_DEV_SUPERADMIN=EMAIL`: `isb serve`, on loopback listeners only,
9//!   signs every HTTP request that carries no credential in as a superadmin
10//!   ([`super::SuperadminSource::Dev`]), acting as the isb user with that
11//!   email if there is one.
12
13use super::AuthError;
14
15pub const WEAK_PASSWORDS_ENV: &str = "ISB_DEV_WEAK_PASSWORDS";
16pub const SUPERADMIN_ENV: &str = "ISB_DEV_SUPERADMIN";
17
18/// Whether `ISB_DEV_WEAK_PASSWORDS` is on (anything but empty, 0, false,
19/// no, off). An error in a release build when it is.
20pub fn weak_passwords() -> Result<bool, AuthError> {
21    let on = std::env::var(WEAK_PASSWORDS_ENV).is_ok_and(|v| {
22        !matches!(
23            v.trim().to_ascii_lowercase().as_str(),
24            "" | "0" | "false" | "no" | "off"
25        )
26    });
27    debug_only(WEAK_PASSWORDS_ENV, on)?;
28    Ok(on)
29}
30
31/// `ISB_DEV_SUPERADMIN`'s email, lowercased; empty is unset. An error in a
32/// release build when it is set.
33pub fn superadmin(value: Option<&str>) -> Result<Option<String>, AuthError> {
34    let email = value
35        .map(|e| e.trim().to_ascii_lowercase())
36        .filter(|e| !e.is_empty());
37    debug_only(SUPERADMIN_ENV, email.is_some())?;
38    if let Some(e) = &email {
39        if !e.contains('@') || e.contains(char::is_whitespace) {
40            return Err(AuthError::Invalid(format!(
41                "{SUPERADMIN_ENV}={e:?}: an email"
42            )));
43        }
44    }
45    Ok(email)
46}
47
48fn debug_only(name: &str, set: bool) -> Result<(), AuthError> {
49    if set && !cfg!(debug_assertions) {
50        return Err(AuthError::Invalid(format!(
51            "{name} works only in debug builds: unset it"
52        )));
53    }
54    Ok(())
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn superadmin_email() {
63        assert_eq!(superadmin(None).unwrap(), None);
64        assert_eq!(superadmin(Some("  ")).unwrap(), None);
65        assert!(superadmin(Some("not-an-email")).is_err());
66        let got = superadmin(Some(" Dev@Dev.com "));
67        if cfg!(debug_assertions) {
68            assert_eq!(got.unwrap().as_deref(), Some("dev@dev.com"));
69        } else {
70            assert!(
71                got.unwrap_err()
72                    .to_string()
73                    .contains("only in debug builds")
74            );
75        }
76    }
77}