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

nordisk/monetize

crates/monetize-server/src/auth.rs

//! The bearer. `nornir_service::auth_interceptor` is the interceptor (constant-time
//! compare, empty ⇒ open) and `nornir_service::resolve_token` the ladder, both
//! reused as-is — the suite's token ladder is `flag > $MONETIZE_SERVER_TOKEN >
//! token file`, and only the two literals are monetize's.

use std::path::Path;

use std::sync::Arc;

use crate::config::{OPERATOR_TOKEN_ENV, TOKEN_ENV};

pub use nornir_service::{auth_interceptor, TokenSource};

/// The ladder, walked once: `--token` > `$MONETIZE_SERVER_TOKEN` > `token_file`
/// (trimmed; an empty file falls through) > `""` — which the interceptor reads
/// as open, so `cli::serve` REFUSES it (stinky #9).
pub fn resolve(flag: Option<&str>, token_file: &Path) -> (String, TokenSource) {
    nornir_service::resolve_token_whence(flag, TOKEN_ENV, Some(token_file))
}

/// The OPERATOR bearer's ladder, the same shape: `--operator-token` >
/// `$MONETIZE_SERVER_OPERATOR_TOKEN` > `<data_dir>/operator-token` > `""`
/// — which, unlike the service bearer's `""`, means CLOSED: see [`Operator`].
pub fn resolve_operator(flag: Option<&str>, file: &Path) -> (String, TokenSource) {
    nornir_service::resolve_token_whence(flag, OPERATOR_TOKEN_ENV, Some(file))
}

/// **`SignupAdmin`'s door: the service bearer OR the operator bearer**, and
/// which one it was (g-refund-verb, 2026-09-28).
///
/// Every service here is behind ONE bearer, and the customer-facing console
/// backend holds it (it reaches `Signup` through it). `SignupAdmin` now also
/// carries `Refund` and `Settle`, which give a payment back, so that bearer
/// cannot be enough for them. This is the least that separates them: the
/// same interceptor as before for the service bearer (reused, constant-time),
/// and a request whose bearer [`nornir_service::token_matches`] the operator
/// bearer gets the [`Operator`] mark — which `Refund` / `Settle` demand and
/// the reads ignore. Nothing else changes: no other service sees the operator
/// bearer, and every existing caller of `SignupAdmin` keeps working.
///
/// An EMPTY operator bearer marks nobody — the money verbs are then refused
/// to every caller, including an open (dev) server's; a refund is never
/// "open". One equal to the service bearer is treated as empty too: two names
/// for one secret separate nothing.
pub fn signup_admin_interceptor(
    token: Arc<String>,
    operator: Arc<String>,
) -> impl Fn(tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> + Clone {
    let service = auth_interceptor(token.clone());
    let operator = if operator.is_empty() || operator.as_str() == token.as_str() {
        Arc::new(String::new())
    } else {
        operator
    };
    move |mut req: tonic::Request<()>| {
        // A CONTROL caller — a per-call SSHSIG from one of this monetize's
        // console principals, verified at the edge (`control::layer`) — is the
        // operator's own console (monetize.<apex>): the money verbs are its.
        if req
            .extensions()
            .get::<crate::control::ControlCaller>()
            .is_some()
        {
            req.extensions_mut().insert(Operator);
            return Ok(req);
        }
        let supplied = req
            .metadata()
            .get("authorization")
            .and_then(|v| v.to_str().ok())
            .and_then(|s| s.strip_prefix("Bearer "))
            .unwrap_or("")
            .to_owned();
        if !operator.is_empty() && nornir_service::token_matches(&supplied, operator.as_str()) {
            req.extensions_mut().insert(Operator);
            return Ok(req);
        }
        service(req)
    }
}

/// **Who stands behind an EMPTY actor ticket** — the extension a door puts on
/// a `Purchase` call, read by `services::purchase::check_actor`.
///
/// An empty ticket used to mean "the operator" by itself, which made every
/// in-process caller the operator too: the public purchase page (deleted since,
/// OD6) reached the service with no ticket and could buy for, and read the orders
/// of, any tenant. Now an empty ticket means nothing on its own; the caller is what
/// the door that let the request in says it is, and a request through no door
/// is nobody.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Caller {
    /// The bearer (or the control signature) was checked: the operator, or a
    /// console BFF that holds the bearer.
    Door,
}

/// **The SITE an estate-facing call comes from**: the request carried a joined estate's own
/// bearer (minted at its enrolment, monetize-front `attach`), and this is that estate's id.
/// What routes a signup's money to the estate's own account (`Registry::route_for`).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CallerEstate(pub String);

/// **An estate-facing service's door**: a joined estate's own bearer marks the call
/// [`CallerEstate`]; anything else is the service bearer's door exactly as before.
pub fn estate_interceptor(
    token: Arc<String>,
    state: Arc<crate::state::AppState>,
) -> impl Fn(tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> + Clone {
    estate_or(state, crate::control::interceptor(token))
}

/// **An estate-facing door that also lets a joined estate in by its OWN bearer**: that
/// bearer marks the call [`CallerEstate`] — never the operator, never the service's own
/// door ([`Caller::Door`]) — and anything else goes to `inner` exactly as before.
pub fn estate_or<I>(
    state: Arc<crate::state::AppState>,
    inner: I,
) -> impl Fn(tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> + Clone
where
    I: Fn(tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> + Clone,
{
    move |mut req: tonic::Request<()>| {
        if req
            .extensions()
            .get::<crate::control::ControlCaller>()
            .is_none()
        {
            let offered = req
                .metadata()
                .get("authorization")
                .and_then(|v| v.to_str().ok())
                .and_then(|s| s.strip_prefix("Bearer "))
                .unwrap_or("")
                .to_owned();
            if let Some(estate) = state.fleet.estate_of_bearer_at(&offered, state.now()) {
                req.extensions_mut().insert(CallerEstate(estate));
                return Ok(req);
            }
        }
        inner(req)
    }
}

/// **The request carried the OPERATOR bearer**, not the service one: the
/// extension [`signup_admin_interceptor`] puts on a `SignupAdmin` call, and
/// what `SignupAdmin.Refund` / `Settle` demand. A marker, never a credential.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Operator;

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

    /// ★ **A joined estate's own bearer names the site the call comes from** (owner
    /// 2026-10-11: gunnar.rs and test2.gunnar.rs on ONE box, each its own Stripe): its
    /// bearer marks the call [`CallerEstate`]; the service bearer passes unmarked; anything
    /// else is refused as before.
    #[test]
    fn an_estates_own_bearer_marks_the_call_with_its_estate() {
        let state = Arc::new(crate::state::AppState::in_memory(
            crate::registry::Registry::new(),
        ));
        state.fleet.know_bearer(
            &crate::fleet::bearer_digest("estate-bearer-of-test2-0123456789"),
            "test2-gunnar-rs",
        );
        let door = estate_interceptor(Arc::new("service-bearer-0123456789abcdef".into()), state);
        let call = |b: &str| {
            let mut r = tonic::Request::new(());
            r.metadata_mut()
                .insert("authorization", format!("Bearer {b}").parse().unwrap());
            door(r)
        };
        let marked =
            call("estate-bearer-of-test2-0123456789").expect("an estate's bearer is let in");
        assert_eq!(
            marked.extensions().get::<CallerEstate>(),
            Some(&CallerEstate("test2-gunnar-rs".into()))
        );
        let service =
            call("service-bearer-0123456789abcdef").expect("the service bearer as before");
        assert_eq!(service.extensions().get::<CallerEstate>(), None);
        assert!(call("nobody").is_err());
    }

    /// ★ **An estate's own bearer opens every door its console calls at the ONE box** —
    /// Products, Purchase, EstateOrder as Signup (owner 2026-10-11: the estate holds no service
    /// bearer) — marked with its estate and NEVER as the operator or the service's own door:
    /// a purchase it starts needs the account's ticket, a refund stays the operator's.
    #[test]
    fn an_estates_own_bearer_opens_the_estate_facing_doors_as_that_estate_only() {
        let state = Arc::new(crate::state::AppState::in_memory(
            crate::registry::Registry::new(),
        ));
        state.fleet.know_bearer(
            &crate::fleet::bearer_digest("estate-bearer-of-test2-0123456789"),
            "test2-gunnar-rs",
        );
        let token = Arc::new("service-bearer-0123456789abcdef".to_string());
        let call =
            |door: &dyn Fn(tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status>,
             b: &str| {
                let mut r = tonic::Request::new(());
                r.metadata_mut()
                    .insert("authorization", format!("Bearer {b}").parse().unwrap());
                door(r)
            };
        let plain = estate_or(state.clone(), crate::control::interceptor(token.clone()));
        let marked = call(&plain, "estate-bearer-of-test2-0123456789")
            .expect("an estate's bearer is let in");
        assert_eq!(
            marked.extensions().get::<CallerEstate>(),
            Some(&CallerEstate("test2-gunnar-rs".into()))
        );
        assert_eq!(
            marked.extensions().get::<Caller>(),
            None,
            "never the service's own door"
        );
        let service =
            call(&plain, "service-bearer-0123456789abcdef").expect("the service bearer as before");
        assert_eq!(
            (
                service.extensions().get::<CallerEstate>(),
                service.extensions().get::<Caller>()
            ),
            (None, Some(&Caller::Door))
        );
        assert!(call(&plain, "nobody").is_err());

        let orders = estate_or(
            state.clone(),
            signup_admin_interceptor(
                token.clone(),
                Arc::new("operator-bearer-0123456789abcdef".into()),
            ),
        );
        let marked = call(&orders, "estate-bearer-of-test2-0123456789")
            .expect("an estate's bearer is let in");
        assert_eq!(
            marked.extensions().get::<CallerEstate>(),
            Some(&CallerEstate("test2-gunnar-rs".into()))
        );
        assert!(
            marked.extensions().get::<Operator>().is_none(),
            "never the operator"
        );
        assert!(call(&orders, "operator-bearer-0123456789abcdef")
            .unwrap()
            .extensions()
            .get::<Operator>()
            .is_some());
        assert!(
            include_str!("lib.rs")
                .matches("crate::auth::estate_or(")
                .count()
                >= 3,
            "Products, Purchase and EstateOrder take it"
        );
    }

    #[test]
    fn the_ladder_prefers_the_flag_then_the_file_and_falls_through_an_empty_file() {
        let dir = std::env::temp_dir().join(format!("monetize-auth-{}", std::process::id()));
        std::fs::create_dir_all(&dir).unwrap();
        let file = dir.join("token");
        std::fs::write(&file, "  from-file \n").unwrap();
        assert_eq!(
            resolve(Some("from-flag"), &file),
            ("from-flag".into(), TokenSource::Flag)
        );
        assert_eq!(
            resolve(Some(""), &file),
            ("from-file".into(), TokenSource::File(file.clone()))
        );
        std::fs::write(&file, "\n").unwrap();
        assert_eq!(resolve(None, &file), (String::new(), TokenSource::Nothing));
        std::fs::remove_dir_all(&dir).unwrap();
    }
}