Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-ui-server/src/role.rs

//! Who a console session is allowed to be. The BFF's own ladder — monetize sees
//! exactly one principal, the bearer this process holds; this is about which
//! human is driving, enforced here before a call is ever made.

use std::fmt;
use std::str::FromStr;

use serde::{Deserialize, Serialize};

/// A console role. Ordered: each level allows everything below it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
    /// Read the console.
    Viewer,
    /// Read, and nothing on this console mutates below admin today.
    Operator,
    /// Everything: plans, the operator `Entitlement.Set`, passkeys.
    Admin,
}

impl Role {
    pub fn allows(self, needed: Role) -> bool {
        self >= needed
    }

    /// The token in a session payload. `|`-delimited, so never contains `|`.
    pub fn as_token(self) -> &'static str {
        match self {
            Role::Viewer => "viewer",
            Role::Operator => "operator",
            Role::Admin => "admin",
        }
    }
}

impl fmt::Display for Role {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_token())
    }
}

impl FromStr for Role {
    type Err = String;

    fn from_str(s: &str) -> Result<Role, String> {
        match s.trim().to_ascii_lowercase().as_str() {
            "viewer" => Ok(Role::Viewer),
            "operator" => Ok(Role::Operator),
            "admin" => Ok(Role::Admin),
            other => Err(format!("{other:?} is not viewer, operator or admin")),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn the_ladder_climbs_one_way_and_tokens_round_trip() {
        assert!(Role::Admin.allows(Role::Viewer));
        assert!(!Role::Viewer.allows(Role::Operator));
        assert!(!Role::Operator.allows(Role::Admin));
        for role in [Role::Viewer, Role::Operator, Role::Admin] {
            assert_eq!(role.as_token().parse::<Role>().unwrap(), role);
        }
        // Defaulting here would silently promote a corrupted cookie.
        assert!("root".parse::<Role>().is_err());
    }
}