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

nordisk/monetize

guests/toy-signup/src/lib.rs

//! **A toy signup guest** — what proves the signup ABI end to end without a
//! product: the account and the delivered fact live in the host's durable
//! state (`account/<name>` = the session that made it, `fact/<name>` = the
//! signed fact as delivered), a name another session holds is `NameTaken`, a
//! retry of the same session finishes its own account, and `down = true` in
//! the settings is a product that cannot be reached (`Unavailable`).

use monetize_signup_guest::{kinds, EntitlementFact, Host, Manifest, NameVerdict, PaidSignup, SignupError, SignupGuest, SignupOutcome};

pub const PRODUCT: &str = "toy";

#[derive(Default)]
pub struct ToySignup;

fn holder(host: &dyn Host, name: &str) -> Result<Option<String>, SignupError> {
    host.state_get(&format!("account/{name}")).map(|v| v.map(|b| String::from_utf8_lossy(&b).into_owned())).map_err(SignupError::Unavailable)
}

impl SignupGuest for ToySignup {
    fn manifest(&self) -> Manifest {
        Manifest::of_kind(kinds::SIGNUP, PRODUCT, &["state"])
    }
    fn check(&self, _: &dyn Host, settings: &serde_json::Value) -> Result<(), String> {
        match settings.get("refuse_check").and_then(|v| v.as_bool()) {
            Some(true) => Err("this toy was told to refuse its settings".into()),
            _ => Ok(()),
        }
    }
    fn name_verdict(&self, host: &dyn Host, _: &serde_json::Value, username: &str) -> Result<NameVerdict, SignupError> {
        Ok(match holder(host, username)? {
            Some(_) => NameVerdict::Taken(format!("username {username:?} is taken; choose another")),
            None => NameVerdict::Free,
        })
    }
    fn create(&self, host: &dyn Host, settings: &serde_json::Value, s: &PaidSignup, fact: &EntitlementFact) -> Result<SignupOutcome, SignupError> {
        if settings.get("down").and_then(|v| v.as_bool()) == Some(true) {
            return Err(SignupError::Unavailable("the toy product is down".into()));
        }
        let me = format!("{}:{}", s.vendor, s.session);
        match holder(host, &s.username)? {
            Some(other) if other != me => return Err(SignupError::NameTaken(format!("username {:?} is taken", s.username))),
            _ => {}
        }
        if fact.tenant.0 != s.username || fact.signature.is_empty() {
            return Err(SignupError::Refused("the host handed an entitlement that is not this signup's, or unsigned".into()));
        }
        host.state_put(&format!("account/{}", s.username), me.as_bytes()).map_err(SignupError::Unavailable)?;
        let f = serde_json::to_vec(fact).map_err(|e| SignupError::Unavailable(e.to_string()))?;
        host.state_put(&format!("fact/{}", s.username), &f).map_err(SignupError::Unavailable)?;
        Ok(SignupOutcome::Created { namespace: s.username.clone(), username: s.username.clone() })
    }
}

monetize_signup_guest::export_signup!(ToySignup, grants = [state]);