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

nordisk/gunnar-ui

crates/gunnar-ui-server/src/first_admin_login.rs

//! **The first admin's passkey IS the estate's operator, and the console reads
//! as them at once** — driven end to end on a fresh estate: the break-glass
//! token registers a real P-256 passkey through `/auth/passkey/register/*`, the
//! passkey logs in through `/auth/passkey/login/finish`, and the session reads
//! `You → Account` and adds an SSH key.
//!
//! Tests only. Owner, 2026-10-04: "a YubiKey is used to register me as an admin
//! user. I can THEN select to ADD ssh keys to it" — no operator step, no
//! attestation panel — and "The UI shall not DO anything such as create a user —
//! it shall CALL gunnar-server".
//!
//! # The fake is test2 as it stood that morning
//!
//! gunnar's identity table with the console, monetize and the bootstrap admin
//! in it, and a namespace `rickard` that a first push with the installer's admin
//! key had conjured (`rickard/t2-scratch`). Its `Access.AddPrincipal` refuses
//! exactly as the real one did ("cannot create the account namespace
//! \"rickard\" …: namespace already exists"), its `Access.AddKey` keeps gunnar's
//! one-free-key rule, and its `Accounts.ClaimOperator` keeps gunnar's: the FED
//! name wins, an ownerless namespace is adopted, the operator is keyless. The
//! last row is the same estate installed with the fix: the account made at
//! install, the front's credential for it enrolled as its first key and laid as
//! this console's tenant seed.
//!
//! Since the identity core (IDENTITY-HOST, 2026-10-27) the fake serves edda's
//! real `IdentityService` over its own people and passkeys: the break-glass
//! token is the core's FED first-login link onto the operator (the token's
//! SHA-256, recorded by the installer), redeemed through `RedeemStart/Finish`.
//!
//! RED on gunnar-ui 72b2ba0 (what test2 ran): the registration asked
//! `AddPrincipal`, was refused, and the passkey bound to nothing — `/api/account/keys`
//! answered 404 "an operator creates it" and `/api/account/console-key` "gunnar has
//! no principal bound to this session".

use std::collections::{BTreeMap, BTreeSet};
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex, OnceLock};

use base64::Engine as _;
use nornir_identity::{Credential, Event, IdentityHost, Invite, Mailer, NoMail, Revocations, Roles, Session, Site, StoreError, User, UserState, UserStore};
use p256::ecdsa::signature::Signer as _;
use sha2::{Digest as _, Sha256};
use tonic::{Request, Response, Status};
use tower::ServiceExt as _;

use crate::api::mirror_gate::Imports;
use crate::pb;

const RP_ID: &str = "test2.example";
const ORIGIN: &str = "https://test2.example";
const BREAK_GLASS: &str = "break-glass-token-of-the-installer";
const B64: base64::engine::general_purpose::GeneralPurpose =
    base64::engine::general_purpose::URL_SAFE_NO_PAD;

// ───────────────────────────────────────────────────────────────────────────
// The fake: test2's gunnar on 2026-10-04 at 08:15
// ───────────────────────────────────────────────────────────────────────────

#[derive(Clone)]
struct Fake {
    principals: Arc<Mutex<BTreeMap<String, pb::Principal>>>,
    namespaces: Arc<Mutex<BTreeSet<String>>>,
    /// (principal, fingerprint, openssh line, comment)
    #[allow(clippy::type_complexity)]
    keys: Arc<Mutex<Vec<(String, String, String, String)>>>,
    /// Every identity call, in order: `"AddPrincipal rickard"`, …
    calls: Arc<Mutex<Vec<String>>>,
    /// The installer's `operator_name`, as gunnar read it from its feed.
    fed_operator: Option<String>,
    clock: Arc<AtomicI64>,
    /// The passkeys gunnar holds: credential id → (principal, SEC1 key).
    #[allow(clippy::type_complexity)]
    passkeys: Arc<Mutex<BTreeMap<Vec<u8>, (String, Vec<u8>)>>>,
    /// Who holds a server-wide Admin (the operator gunnar claimed).
    admins: Arc<Mutex<BTreeSet<String>>>,
    /// The identity core's own records at this gunnar.
    sessions: Arc<Mutex<Vec<Session>>>,
    invites: Arc<Mutex<Vec<Invite>>>,
    revocations: Arc<Mutex<Revocations>>,
    /// The identity core this gunnar serves, over the tables above.
    core: Arc<OnceLock<Arc<nornir_identity::Identity<FirstHost>>>>,
}

fn sha256_hex(bytes: &[u8]) -> String {
    Sha256::digest(bytes).iter().map(|b| format!("{b:02x}")).collect()
}

impl Fake {
    fn test2_morning() -> Fake {
        let fake = Fake {
            principals: Arc::default(),
            namespaces: Arc::default(),
            keys: Arc::default(),
            calls: Arc::default(),
            fed_operator: Some("rickard".to_owned()),
            clock: Arc::new(AtomicI64::new(1_000)),
            passkeys: Arc::default(),
            admins: Arc::default(),
            sessions: Arc::default(),
            invites: Arc::default(),
            revocations: Arc::default(),
            core: Arc::default(),
        };
        let core = Arc::new(nornir_identity::Identity::new(FirstHost { fake: fake.clone() }, nornir_identity::Config::default()));
        fake.core.set(core).unwrap_or_else(|_| unreachable!("one core"));
        for (id, kind) in [
            ("admin", pb::PrincipalKind::User),
            ("gunnar-ui", pb::PrincipalKind::Machine),
            ("monetize", pb::PrincipalKind::Machine),
            ("gunnar-guest", pb::PrincipalKind::Machine),
        ] {
            fake.make(id, kind);
        }
        // The namespace `rickard/t2-scratch`'s first push conjured.
        fake.namespaces.lock().unwrap().insert("rickard".to_owned());
        fake.installer_claims("rickard", true);
        fake
    }

    /// **The installer's word** (Stage L): it claims the operator — a person,
    /// gunnar-admin, an ownerless namespace of that name adopted — and, with
    /// `invite`, records the break-glass token's hash as the invite for his
    /// first passkey. Not the console's call: it is not in `calls`.
    fn installer_claims(&self, name: &str, invite: bool) {
        if !self.principals.lock().unwrap().contains_key(name) {
            self.make(name, pb::PrincipalKind::User);
        }
        self.namespaces.lock().unwrap().insert(name.to_owned());
        self.admins.lock().unwrap().insert(name.to_owned());
        if invite {
            self.core()
                .fed_enrolment(name, &sha256_hex(BREAK_GLASS.as_bytes()), 30 * 86_400, "installer")
                .expect("the installer's fed first-login link");
        }
    }

    fn core(&self) -> &Arc<nornir_identity::Identity<FirstHost>> {
        self.core.get().expect("the fake's core")
    }

    /// The operator's first-login link (the token's hash) is still open.
    fn first_invite_open(&self) -> bool {
        let id = nornir_identity::fed_invite_id(&sha256_hex(BREAK_GLASS.as_bytes()));
        self.invites.lock().unwrap().iter().any(|i| i.id == id && i.used_by.is_none() && !i.revoked)
    }

    /// The principal a call is: by the core session it carried, or by its key.
    fn caller<T>(&self, request: &Request<T>) -> Option<String> {
        if let Some(token) = request.metadata().get(crate::grpc::SESSION_METADATA_KEY).and_then(|v| v.to_str().ok()) {
            return self.core().session(token).ok().map(|v| v.user_id);
        }
        let signer = request
            .metadata()
            .get(crate::signer::CREDENTIAL_METADATA_KEY)
            .and_then(|v| v.to_str().ok())
            .and_then(|v| v.split_whitespace().nth(1).map(str::to_owned))
            .unwrap_or_default();
        self.keys.lock().unwrap().iter().find(|k| k.1 == signer).map(|k| k.0.clone())
    }

    /// **A box no installer fed**: the operator was claimed, and no first
    /// invite recorded.
    fn no_first_invite() -> Fake {
        let fake = Fake::test2_morning();
        let id = nornir_identity::fed_invite_id(&sha256_hex(BREAK_GLASS.as_bytes()));
        fake.core().withdraw_invite(&id, "test").expect("withdrawn");
        fake
    }

    /// **The same estate installed with the fix**: the primary claimed the
    /// operator's account at install (over the conjured `rickard/`) and enrolled
    /// the front's offered credential for it as its first key — `credential` is
    /// that key's OpenSSH line.
    fn installed(credential: &str) -> Fake {
        let fake = Fake::test2_morning();
        fake.keys.lock().unwrap().push((
            "rickard".to_owned(),
            fingerprint_of(credential).expect("a key line"),
            credential.to_owned(),
            "the console's credential for the operator, offered by the front".to_owned(),
        ));
        fake
    }

    /// **A fresh estate where nothing was pushed first**: no `rickard/`
    /// conjured. The old path bound by name here and worked; the claim must
    /// work the same, and still be gunnar's word.
    fn fresh() -> Fake {
        let fake = Fake::test2_morning();
        fake.namespaces.lock().unwrap().clear();
        fake.principals.lock().unwrap().remove("rickard");
        fake.admins.lock().unwrap().clear();
        fake.installer_claims("rickard", true);
        fake
    }

    fn make(&self, id: &str, kind: pb::PrincipalKind) -> pb::Principal {
        let row = pb::Principal {
            id: id.to_owned(),
            name: id.to_owned(),
            kind: kind as i32,
            created_at_unix_ms: self.clock.fetch_add(1_000, Ordering::SeqCst),
        };
        self.principals.lock().unwrap().insert(id.to_owned(), row.clone());
        row
    }

    fn said(&self, call: String) {
        self.calls.lock().unwrap().push(call);
    }

    fn calls(&self) -> Vec<String> {
        self.calls.lock().unwrap().clone()
    }

    fn keys_of(&self, principal: &str) -> Vec<(String, String, String, String)> {
        self.keys
            .lock()
            .unwrap()
            .iter()
            .filter(|k| k.0 == principal)
            .cloned()
            .collect()
    }
}

fn fingerprint_of(line: &str) -> Result<String, Status> {
    ssh_key::PublicKey::from_openssh(line.trim())
        .map(|k| k.fingerprint(ssh_key::HashAlg::Sha256).to_string())
        .map_err(|e| Status::invalid_argument(format!("not an OpenSSH key: {e}")))
}

#[tonic::async_trait]
impl pb::access_server::Access for Fake {
    async fn list_principals(
        &self,
        request: Request<pb::ListPrincipalsRequest>,
    ) -> Result<Response<pb::ListPrincipalsResponse>, Status> {
        let name = request.into_inner().name;
        Ok(Response::new(pb::ListPrincipalsResponse {
            principals: self
                .principals
                .lock()
                .unwrap()
                .values()
                .filter(|p| name.is_empty() || p.name == name)
                .cloned()
                .collect(),
            next_page_token: String::new(),
            ..Default::default()
        }))
    }

    /// gunnar's `control_plane::add_principal`: the principal and its account
    /// namespace are one claim, and an existing namespace refuses the whole call.
    async fn add_principal(
        &self,
        request: Request<pb::AddPrincipalRequest>,
    ) -> Result<Response<pb::AddPrincipalResponse>, Status> {
        let asked = request.into_inner();
        let id = if asked.id.is_empty() { asked.name.clone() } else { asked.id };
        self.said(format!("AddPrincipal {id}"));
        if self.principals.lock().unwrap().contains_key(&id) {
            return Err(Status::already_exists(format!("principal {id}")));
        }
        if self.namespaces.lock().unwrap().contains(&id) {
            return Err(Status::already_exists(format!(
                "cannot create the account namespace \"{id}\" for this user: namespace already \
                 exists: {id}. A user's account is the namespace named after their id and cannot \
                 be somebody else's; the principal was not created."
            )));
        }
        self.namespaces.lock().unwrap().insert(id.clone());
        Ok(Response::new(pb::AddPrincipalResponse {
            principal: Some(self.make(&id, pb::PrincipalKind::User)),
        }))
    }

    /// gunnar's one-free-key rule (`key_attestation::Requirement::of`): a
    /// principal that holds a key takes another only with an attestation made by
    /// one it holds — UNLESS the caller IS that principal (gunnar's self-scope,
    /// feat/thin-ui 5456dd9: the call's own signature, by a key the principal
    /// holds, is the consent).
    async fn add_key(
        &self,
        request: Request<pb::AddKeyRequest>,
    ) -> Result<Response<pb::AddKeyResponse>, Status> {
        let caller = self.caller(&request);
        let asked = request.into_inner();
        let fingerprint = fingerprint_of(&asked.openssh_public_key)?;
        let held = self.keys_of(&asked.principal_id);
        let by_itself = caller.as_deref() == Some(asked.principal_id.as_str());
        self.said(format!(
            "AddKey {} attested={}",
            asked.principal_id,
            asked.attestation.is_some()
        ));
        if !held.is_empty() && !by_itself {
            let Some(attestation) = &asked.attestation else {
                return Err(Status::failed_precondition(format!(
                    "principal {} already holds {} key(s), so adding another needs their own \
                     attestation",
                    asked.principal_id,
                    held.len()
                )));
            };
            if !held.iter().any(|k| k.1 == attestation.key_fingerprint) {
                return Err(Status::invalid_argument("the attestation is not by a key the principal holds"));
            }
        }
        self.keys.lock().unwrap().push((
            asked.principal_id.clone(),
            fingerprint.clone(),
            asked.openssh_public_key.clone(),
            asked.comment.clone(),
        ));
        Ok(Response::new(pb::AddKeyResponse {
            key: Some(pb::PublicKey {
                fingerprint,
                algo: "ssh-ed25519".to_owned(),
                principal_id: asked.principal_id,
                comment: asked.comment,
                ..Default::default()
            }),
        }))
    }

    async fn list_credentials(
        &self,
        _request: Request<pb::ListCredentialsRequest>,
    ) -> Result<Response<pb::ListCredentialsResponse>, Status> {
        Ok(Response::new(pb::ListCredentialsResponse {
            credentials: self
                .passkeys
                .lock()
                .unwrap()
                .iter()
                .map(|(id, (principal, _))| pb::WebCredential {
                    credential_id: id.clone(),
                    principal_id: principal.clone(),
                    ..Default::default()
                })
                .collect(),
        }))
    }

    async fn who_am_i(
        &self,
        request: Request<pb::WhoAmIRequest>,
    ) -> Result<Response<pb::WhoAmIResponse>, Status> {
        let principal = self.caller(&request).ok_or_else(|| Status::unauthenticated("no such key"))?;
        let admin = self.admins.lock().unwrap().contains(&principal);
        Ok(Response::new(pb::WhoAmIResponse {
            principal: self.principals.lock().unwrap().get(&principal).cloned(),
            server_level: if admin { pb::GrantLevel::Admin as i32 } else { 0 },
            ..Default::default()
        }))
    }

    async fn list_keys(
        &self,
        request: Request<pb::ListKeysRequest>,
    ) -> Result<Response<pb::ListKeysResponse>, Status> {
        let principal = request.into_inner().principal_id;
        Ok(Response::new(pb::ListKeysResponse {
            keys: self
                .keys_of(&principal)
                .into_iter()
                .map(|(principal_id, fingerprint, _, comment)| pb::PublicKey {
                    fingerprint,
                    algo: "ssh-ed25519".to_owned(),
                    principal_id,
                    comment,
                    ..Default::default()
                })
                .collect(),
        }))
    }
}

#[tonic::async_trait]
impl pb::accounts_server::Accounts for Fake {
    /// gunnar's `claim_operator_named`: the fed name wins, a person under it is
    /// the answer, otherwise it is made — keyless, its namespace adopted when a
    /// first push left one with no owner.
    async fn claim_operator(
        &self,
        request: Request<pb::ClaimOperatorRequest>,
    ) -> Result<Response<pb::ClaimOperatorResponse>, Status> {
        let asked = request.into_inner().name;
        self.said(format!("ClaimOperator {asked}"));
        let (name, named_by) = match &self.fed_operator {
            Some(fed) => (fed.clone(), "fed"),
            None if !asked.trim().is_empty() => (asked.trim().to_owned(), "asked"),
            None => return Err(Status::failed_precondition("no operator is named for this box")),
        };
        // The operator is gunnar-admin, made or found.
        self.admins.lock().unwrap().insert(name.clone());
        if let Some(held) = self.principals.lock().unwrap().get(&name).cloned() {
            return Ok(Response::new(pb::ClaimOperatorResponse {
                principal: Some(held),
                created: false,
                adopted_namespace: false,
                named_by: named_by.to_owned(),
                bootstrap_invite: false,
            }));
        }
        let adopted = !self.namespaces.lock().unwrap().insert(name.clone());
        Ok(Response::new(pb::ClaimOperatorResponse {
            principal: Some(self.make(&name, pb::PrincipalKind::User)),
            created: true,
            adopted_namespace: adopted,
            named_by: named_by.to_owned(),
            bootstrap_invite: false,
        }))
    }
}

// ── The identity core's host over the fake's tables (what `GunnarHost` is) ──

struct FirstHost {
    fake: Fake,
}

impl UserStore for FirstHost {
    fn user(&self, id: &str) -> Result<Option<User>, StoreError> {
        let principal = self.fake.principals.lock().unwrap().get(id).cloned();
        let admin = self.fake.admins.lock().unwrap().contains(id);
        Ok(principal.map(|p| User {
            groups: if admin { vec!["server-admin".to_owned()] } else { Vec::new() },
            display: p.name,
            email: None,
            state: UserState::Active,
            created: 0,
            id: p.id,
        }))
    }
    fn user_by_email(&self, _: &str) -> Result<Option<User>, StoreError> {
        Ok(None)
    }
    fn users(&self) -> Result<Vec<User>, StoreError> {
        let ids: Vec<String> = self.fake.principals.lock().unwrap().keys().cloned().collect();
        Ok(ids.iter().filter_map(|id| self.user(id).ok().flatten()).collect())
    }
    fn put_user(&self, _: &User) -> Result<(), StoreError> {
        Ok(())
    }
    fn credential(&self, id: &[u8]) -> Result<Option<Credential>, StoreError> {
        Ok(self.fake.passkeys.lock().unwrap().get(id).map(|(user, key)| Credential { id: id.to_vec(), user: user.clone(), public_key: key.clone(), sign_count: 0, created: 0 }))
    }
    fn credentials_of(&self, user: &str) -> Result<Vec<Credential>, StoreError> {
        Ok(self
            .fake
            .passkeys
            .lock()
            .unwrap()
            .iter()
            .filter(|(_, (u, _))| u == user)
            .map(|(id, (u, key))| Credential { id: id.clone(), user: u.clone(), public_key: key.clone(), sign_count: 0, created: 0 })
            .collect())
    }
    /// gunnar's passkey store: a new passkey on its person (said as `Passkey <who>`).
    fn put_credential(&self, c: &Credential) -> Result<(), StoreError> {
        let mut held = self.fake.passkeys.lock().unwrap();
        if !held.contains_key(&c.id) {
            self.fake.said(format!("Passkey {}", c.user));
            held.insert(c.id.clone(), (c.user.clone(), c.public_key.clone()));
        }
        Ok(())
    }
    fn session(&self, id: &str) -> Result<Option<Session>, StoreError> {
        Ok(self.fake.sessions.lock().unwrap().iter().find(|s| s.id == id).cloned())
    }
    fn put_session(&self, session: &Session) -> Result<(), StoreError> {
        let mut all = self.fake.sessions.lock().unwrap();
        all.retain(|s| s.id != session.id);
        all.push(session.clone());
        Ok(())
    }
    fn delete_session(&self, id: &str) -> Result<(), StoreError> {
        self.fake.sessions.lock().unwrap().retain(|s| s.id != id);
        Ok(())
    }
    fn invites(&self) -> Result<Vec<Invite>, StoreError> {
        Ok(self.fake.invites.lock().unwrap().clone())
    }
    fn update_invites(&self, change: &mut dyn FnMut(&mut Vec<Invite>)) -> Result<(), StoreError> {
        change(&mut self.fake.invites.lock().unwrap());
        Ok(())
    }
    fn revocations(&self) -> Result<Revocations, StoreError> {
        Ok(self.fake.revocations.lock().unwrap().clone())
    }
    fn update_revocations(&self, change: &mut dyn FnMut(&mut Revocations)) -> Result<(), StoreError> {
        change(&mut self.fake.revocations.lock().unwrap());
        Ok(())
    }
}

impl IdentityHost for FirstHost {
    fn site(&self) -> Site {
        Site::Private
    }
    fn store(&self) -> &dyn UserStore {
        self
    }
    fn roles(&self, user: &User) -> Roles {
        Roles { admin: user.groups.iter().any(|g| g == "server-admin"), names: user.groups.clone() }
    }
    fn audit(&self, _: Event) {}
    fn mailer(&self) -> &dyn Mailer {
        &NoMail
    }
}

// ───────────────────────────────────────────────────────────────────────────
// The console, a passkey in software, and a browser that keeps the cookie
// ───────────────────────────────────────────────────────────────────────────

struct Rig {
    router: axum::Router,
    webauthn: Arc<crate::auth::Webauthn>,
    fake: Fake,
}

fn console_signer() -> crate::signer::Signer {
    crate::signer::Signer::new(ssh_key::PrivateKey::from(
        ssh_key::private::Ed25519Keypair::from_seed(&[62u8; 32]),
    ))
    .expect("a fixed seed makes a signer")
}

async fn console(fake: Fake) -> Rig {
    console_seeded(fake, None).await
}

/// The console as gunnar-front builds it on an installed estate: its tenant
/// seeds in `seeds` (`<data>/console/seeds`, where the front lays the
/// operator's credential the install enrolled).
async fn console_seeded(fake: Fake, seeds: Option<std::path::PathBuf>) -> Rig {
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
        .await
        .expect("a loopback port");
    let addr = listener.local_addr().expect("its address");
    let serving = fake.clone();
    let identity = nornir_identity::grpc::IdentityGrpc::new(Arc::clone(fake.core())).into_server();
    tokio::spawn(async move {
        tonic::transport::Server::builder()
            .add_service(pb::access_server::AccessServer::new(serving.clone()))
            .add_service(pb::accounts_server::AccountsServer::new(serving))
            .add_service(identity)
            .serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
            .await
    });
    let dir = tempfile::tempdir().expect("a temp dir").keep();
    let store = crate::state::StateStore::open(dir.join("state.redb")).expect("a store");
    let webauthn = Arc::new(
        crate::auth::Webauthn::new(
            crate::auth::RelyingParty {
                rp_id: RP_ID.to_owned(),
                origin: ORIGIN.to_owned(),
                rp_name: "gunnar".to_owned(),
            },
            store.clone(),
        )
        .expect("a subsystem"),
    );
    let endpoint = format!("http://{addr}");
    let router = crate::serve::router(Arc::new(crate::app::App {
        webauthn: Some(webauthn.clone()),
        admin_token: Some(BREAK_GLASS.to_owned()),
        #[cfg(feature = "metered")]
        monetize: None,
        events: crate::sse::EventHub::detached(),
        gunnar: crate::grpc::Gunnar::connect_lazy(&endpoint, Arc::new(console_signer()))
            .expect("a handle"),
        twin: None,
        guest: None,
        reviewers: Arc::new(crate::reviewer::ReviewerKeys::new(
            endpoint.clone(),
            store.clone(),
        )),
        tenants: Arc::new(crate::tenant::TenantKeys::new(endpoint, store).with_seeds(seeds)),
        forges: Default::default(),
        imports: Arc::new(Imports::default()),
        forge_apps: Arc::default(),
        ui_dir: None,
        caches: crate::api::Caches::default(),
        objects: Default::default(),
        readmes: Default::default(),
    }));
    Rig {
        router,
        webauthn,
        fake,
    }
}

/// The YubiKey: a P-256 key and the credential id it reports.
struct Yubikey {
    key: p256::ecdsa::SigningKey,
    id: Vec<u8>,
    counter: u32,
}

impl Yubikey {
    fn new() -> Yubikey {
        Yubikey {
            key: p256::ecdsa::SigningKey::from_bytes(&[0x37u8; 32].into()).expect("a scalar"),
            id: b"the-owners-yubikey".to_vec(),
            counter: 0,
        }
    }

    fn client_json(kind: &str, challenge: &str) -> Vec<u8> {
        serde_json::json!({ "type": kind, "challenge": challenge, "origin": ORIGIN })
            .to_string()
            .into_bytes()
    }

    /// `navigator.credentials.create`: `none` attestation, the attested
    /// credential data with the COSE key.
    fn create(&mut self, challenge: &str) -> (String, String) {
        self.counter += 1;
        let mut auth_data = Sha256::digest(RP_ID.as_bytes()).to_vec();
        auth_data.push(0x01 | 0x04 | 0x40); // UP | UV | AT
        auth_data.extend_from_slice(&self.counter.to_be_bytes());
        auth_data.extend_from_slice(&[0u8; 16]); // aaguid
        auth_data.extend_from_slice(&(self.id.len() as u16).to_be_bytes());
        auth_data.extend_from_slice(&self.id);
        let point = self.key.verifying_key().to_sec1_point(false);
        let cose = ciborium::value::Value::Map(vec![
            (1.into(), 2.into()),
            (3.into(), (-7).into()),
            ((-1).into(), 1.into()),
            ((-2).into(), ciborium::value::Value::Bytes(point.x().expect("x").to_vec())),
            ((-3).into(), ciborium::value::Value::Bytes(point.y().expect("y").to_vec())),
        ]);
        ciborium::into_writer(&cose, &mut auth_data).expect("cbor");
        let object = ciborium::value::Value::Map(vec![
            ("fmt".into(), "none".into()),
            ("attStmt".into(), ciborium::value::Value::Map(vec![])),
            ("authData".into(), ciborium::value::Value::Bytes(auth_data)),
        ]);
        let mut attestation = Vec::new();
        ciborium::into_writer(&object, &mut attestation).expect("cbor");
        (
            B64.encode(Self::client_json("webauthn.create", challenge)),
            B64.encode(attestation),
        )
    }

    /// `navigator.credentials.get`.
    fn get(&mut self, challenge: &str) -> (String, String, String) {
        self.counter += 1;
        let client_data = Self::client_json("webauthn.get", challenge);
        let mut auth_data = Sha256::digest(RP_ID.as_bytes()).to_vec();
        auth_data.push(0x01 | 0x04);
        auth_data.extend_from_slice(&self.counter.to_be_bytes());
        let mut message = auth_data.clone();
        message.extend_from_slice(&Sha256::digest(&client_data));
        let signature: p256::ecdsa::Signature = self.key.sign(&message);
        (
            B64.encode(&client_data),
            B64.encode(&auth_data),
            B64.encode(signature.to_der().as_bytes()),
        )
    }
}

struct Answer {
    status: http::StatusCode,
    cookie: Option<String>,
    body: serde_json::Value,
    text: String,
}

impl Rig {
    async fn send(
        &self,
        method: &str,
        uri: &str,
        bearer: Option<&str>,
        cookie: Option<&str>,
        body: Option<serde_json::Value>,
    ) -> Answer {
        let mut request = http::Request::builder()
            .method(method)
            .uri(uri)
            .header(http::header::ORIGIN, ORIGIN)
            .header(http::header::ACCEPT, "application/json");
        if let Some(token) = bearer {
            request = request.header(http::header::AUTHORIZATION, format!("Bearer {token}"));
        }
        if let Some(value) = cookie {
            request = request.header(
                http::header::COOKIE,
                format!("{}={value}", crate::auth::SESSION_COOKIE),
            );
        }
        let request = match body {
            Some(json) => request
                .header(http::header::CONTENT_TYPE, "application/json")
                .body(axum::body::Body::from(json.to_string())),
            None => request.body(axum::body::Body::empty()),
        }
        .expect("a request");
        let response = self.router.clone().oneshot(request).await.expect("the console answers");
        let status = response.status();
        let cookie = response
            .headers()
            .get_all(http::header::SET_COOKIE)
            .iter()
            .filter_map(|v| v.to_str().ok())
            .filter(|v| !v.contains("Max-Age=0"))
            .find_map(|v| {
                v.strip_prefix(&format!("{}=", crate::auth::SESSION_COOKIE))
                    .map(|rest| rest.split(';').next().unwrap_or_default().to_owned())
            });
        let bytes = axum::body::to_bytes(response.into_body(), 1 << 20).await.expect("a body");
        let text = String::from_utf8_lossy(&bytes).into_owned();
        Answer {
            status,
            cookie,
            body: serde_json::from_str(&text).unwrap_or(serde_json::Value::Null),
            text,
        }
    }

    /// **The owner's first visit**: the installer's break-glass token, his
    /// name, his YubiKey — `register/start`, then `register/finish`.
    async fn register_with_the_bootstrap_token(&self, user: &str, yubikey: &mut Yubikey) -> Answer {
        let options = self
            .send("GET", &format!("/auth/passkey/register/start?user={user}"), Some(BREAK_GLASS), None, None)
            .await;
        if options.status != http::StatusCode::OK {
            return options;
        }
        let ceremony = options.body["ceremony_id"].as_str().expect("a ceremony").to_owned();
        let challenge = options.body["publicKey"]["challenge"].as_str().expect("a challenge").to_owned();
        let (client_data, attestation) = yubikey.create(&challenge);
        self.send(
            "POST",
            "/auth/passkey/register/finish",
            Some(BREAK_GLASS),
            None,
            Some(serde_json::json!({
                "ceremony_id": ceremony,
                "user": user,
                "role": "admin",
                "label": "YubiKey",
                "response": { "clientDataJSON": client_data, "attestationObject": attestation },
            })),
        )
        .await
    }

    /// **Then he logs in with it** — the session cookie a browser would keep.
    async fn login(&self, yubikey: &mut Yubikey) -> String {
        let options = self.send("GET", "/auth/passkey/login/start", None, None, None).await;
        assert_eq!(options.status, http::StatusCode::OK, "{}", options.text);
        let ceremony = options.body["ceremony_id"].as_str().expect("a ceremony").to_owned();
        let challenge = options.body["publicKey"]["challenge"].as_str().expect("a challenge").to_owned();
        let (client_data, auth_data, signature) = yubikey.get(&challenge);
        let answer = self
            .send(
                "POST",
                "/auth/passkey/login/finish",
                None,
                None,
                Some(serde_json::json!({
                    "ceremony_id": ceremony,
                    "id": B64.encode(&yubikey.id),
                    "response": {
                        "clientDataJSON": client_data,
                        "authenticatorData": auth_data,
                        "signature": signature,
                    }
                })),
            )
            .await;
        assert_eq!(answer.status, http::StatusCode::OK, "the passkey did not log in: {}", answer.text);
        answer.cookie.expect("a login that worked hands out a session")
    }

    /// The one passkey: gunnar's, on the principal gunnar answers — and the
    /// console's own store holds none (thin layer L).
    fn the_passkeys_principal(&self) -> String {
        let held = self.fake.passkeys.lock().unwrap().clone();
        assert_eq!(held.len(), 1, "one passkey at gunnar: the owner's: {held:?}");
        held.into_values().next().expect("one").0
    }
}

fn an_ssh_key(seed: u8) -> String {
    ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::from_seed(&[seed; 32]))
        .public_key()
        .to_openssh()
        .expect("a public line")
}

// ───────────────────────────────────────────────────────────────────────────
// The rows
// ───────────────────────────────────────────────────────────────────────────

/// ★ **Register the YubiKey with the bootstrap token → the passkey IS the
/// operator's account, registered AT gunnar on it** — on test2's gunnar, where
/// `AddPrincipal rickard` is refused because a first push conjured `rickard/`.
/// The installer claimed the operator and recorded the token's hash as his
/// first invite; the console created nothing and decided nothing: it relayed
/// the ceremony to `Access.WebRegister` with the token as the invite's secret,
/// and gunnar registered the passkey on the operator (first login on Stage L).
/// It holds no key of his — it signs as him with the session key gunnar enrols
/// at his login.
#[tokio::test]
async fn the_first_admin_passkey_is_registered_at_gunnar_on_the_operator() {
    let rig = console(Fake::test2_morning()).await;
    let mut yubikey = Yubikey::new();
    let registered = rig.register_with_the_bootstrap_token("rickard", &mut yubikey).await;
    assert_eq!(registered.status, http::StatusCode::OK, "{}", registered.text);

    assert_eq!(
        rig.the_passkeys_principal(),
        "rickard",
        "the first admin passkey is not the operator's — test2's 'gunnar has no principal bound \
         to this session'. gunnar was asked: {:?}",
        rig.fake.calls()
    );
    let calls = rig.fake.calls();
    assert!(calls.contains(&"Passkey rickard".to_owned()), "{calls:?}");
    assert!(
        !calls.iter().any(|c| c.starts_with("ClaimOperator")),
        "the console asks gunnar nothing about who the operator is — the invite says: {calls:?}"
    );
    assert!(!rig.fake.first_invite_open(), "the first login link was redeemed");
    assert!(
        !calls.iter().any(|c| c.starts_with("AddPrincipal")),
        "the console made a user itself instead of calling gunnar for the operator: {calls:?}"
    );
    assert!(
        !calls.iter().any(|c| c.starts_with("AddKey")),
        "the console enrols no key of its own on the operator: {calls:?}"
    );
    assert!(
        !rig.webauthn.admin_token_usable(),
        "the first admin passkey exists: the break-glass token is spent"
    );
}

/// ★ **Then he logs in and the console reads as him — no panel, no operator
/// step.** `You → Account`: his keys answer (his own account), the console's
/// credential is `enrolled` (his session key, which gunnar enrolled at the
/// login), and the panel draws nothing.
#[tokio::test]
async fn after_the_first_login_the_account_page_is_his_and_the_console_reads_as_him() {
    let rig = console(Fake::test2_morning()).await;
    let mut yubikey = Yubikey::new();
    let registered = rig.register_with_the_bootstrap_token("rickard", &mut yubikey).await;
    assert_eq!(registered.status, http::StatusCode::OK, "{}", registered.text);
    let session = rig.login(&mut yubikey).await;

    let keys = rig.send("GET", "/api/account/keys", None, Some(&session), None).await;
    assert_eq!(
        keys.status,
        http::StatusCode::OK,
        "You → Account → SSH keys refused the operator: {}",
        keys.text
    );

    let credential = rig.send("GET", "/api/account/console-key", None, Some(&session), None).await;
    assert_eq!(credential.status, http::StatusCode::OK, "{}", credential.text);
    assert_eq!(credential.body["state"], "enrolled", "{}", credential.text);
    assert_eq!(credential.body["principal_id"], "rickard", "{}", credential.text);
    assert!(
        credential.body.get("refusal").is_none_or(|r| r.as_str() == Some("")),
        "a refusal reached the page: {}",
        credential.text
    );
    let status = rig.send("GET", "/auth/status", None, Some(&session), None).await;
    assert_eq!(status.body["role"], "admin", "the operator is gunnar-admin: {}", status.text);
}

/// ★ **"I can THEN select to ADD ssh keys to it"**: You → Account → SSH keys →
/// Add key lands on the operator's account, added AS HIM (his session key, a
/// key on his own principal: gunnar's self-scope) — nobody is asked to run
/// anything.
#[tokio::test]
async fn then_he_adds_his_ssh_keys_and_gunnar_takes_them_with_no_operator_step() {
    let rig = console(Fake::test2_morning()).await;
    let mut yubikey = Yubikey::new();
    let registered = rig.register_with_the_bootstrap_token("rickard", &mut yubikey).await;
    assert_eq!(registered.status, http::StatusCode::OK, "{}", registered.text);
    let session = rig.login(&mut yubikey).await;

    for (n, seed) in [(1, 81u8), (2, 82u8)] {
        let added = rig
            .send(
                "POST",
                "/api/account/keys",
                None,
                Some(&session),
                Some(serde_json::json!({ "openssh_public_key": an_ssh_key(seed), "comment": format!("laptop {n}") })),
            )
            .await;
        assert_eq!(added.status, http::StatusCode::OK, "Add key #{n} was refused: {}", added.text);
    }
    let held = rig.fake.keys_of("rickard");
    assert_eq!(held.len(), 2, "his two (a core session holds no key at gunnar): {held:?}");
    let listed = rig.send("GET", "/api/account/keys", None, Some(&session), None).await;
    assert_eq!(listed.status, http::StatusCode::OK, "{}", listed.text);
    assert_eq!(
        listed.body["items"].as_array().map(Vec::len),
        Some(2),
        "his page lists HIS two keys: {}",
        listed.text
    );
}

/// ★ **On an estate installed with the fix the operator's account is already
/// there — the install claimed it — and the first passkey is registered on it;
/// the console adopts no key**: since passkeys live at gunnar it signs as him
/// with the session key gunnar enrols at his login, and the credential the
/// install laid as the console's seed is not read.
#[tokio::test]
async fn on_an_installed_estate_the_first_passkey_is_the_installed_operators() {
    let seed = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::from_seed(&[77u8; 32]));
    let credential = format!(
        "{} gunnar-ui-operator",
        seed.public_key().to_openssh().expect("a public line")
    );
    let seeds = tempfile::tempdir().expect("a temp dir").keep();
    std::fs::write(
        seeds.join("rickard"),
        seed.to_openssh(ssh_key::LineEnding::LF).expect("a private line").as_bytes(),
    )
    .expect("the seed is laid");
    let rig = console_seeded(Fake::installed(&credential), Some(seeds)).await;
    let mut yubikey = Yubikey::new();
    let registered = rig.register_with_the_bootstrap_token("rickard", &mut yubikey).await;
    assert_eq!(registered.status, http::StatusCode::OK, "{}", registered.text);
    assert_eq!(rig.the_passkeys_principal(), "rickard");
    let calls = rig.fake.calls();
    assert!(calls.contains(&"Passkey rickard".to_owned()), "{calls:?}");
    assert!(
        !calls.iter().any(|c| c.starts_with("AddKey") || c.starts_with("AddPrincipal")),
        "the console asks gunnar for no key and no principal: {calls:?}"
    );

    let session = rig.login(&mut yubikey).await;
    let credential_state = rig.send("GET", "/api/account/console-key", None, Some(&session), None).await;
    assert_eq!(credential_state.body["state"], "enrolled", "{}", credential_state.text);
    assert_ne!(
        credential_state.body["fingerprint"],
        fingerprint_of(&credential).expect("a key line").as_str(),
        "the console signs with his session key, not a key it holds of his: {}",
        credential_state.text
    );

    let added = rig
        .send(
            "POST",
            "/api/account/keys",
            None,
            Some(&session),
            Some(serde_json::json!({ "openssh_public_key": an_ssh_key(91), "comment": "laptop" })),
        )
        .await;
    assert_eq!(added.status, http::StatusCode::OK, "Add key was refused: {}", added.text);
    // Added BY HIM: the call is signed with his session key — a key on his own
    // principal — so gunnar's self-scope takes it with no attestation, and the
    // console vouches for nobody.
    assert!(
        rig.fake.calls().contains(&"AddKey rickard attested=false".to_owned()),
        "his key is added as him, owing no attestation: {:?}",
        rig.fake.calls()
    );
    assert_eq!(
        rig.fake.keys_of("rickard").len(),
        2,
        "the install's credential and his key"
    );
}

/// **A fresh estate, nothing conjured** — the same door, the same answer:
/// gunnar names the operator (`ClaimOperator`, never `AddPrincipal`), the
/// passkey is registered on it, and his SSH key goes on as him.
#[tokio::test]
async fn on_a_fresh_estate_with_nothing_conjured_the_first_passkey_is_the_operator_too() {
    let rig = console(Fake::fresh()).await;
    let mut yubikey = Yubikey::new();
    let registered = rig.register_with_the_bootstrap_token("rickard", &mut yubikey).await;
    assert_eq!(registered.status, http::StatusCode::OK, "{}", registered.text);
    assert_eq!(rig.the_passkeys_principal(), "rickard");
    let calls = rig.fake.calls();
    assert!(calls.contains(&"Passkey rickard".to_owned()), "{calls:?}");
    assert!(!calls.iter().any(|c| c.starts_with("AddPrincipal")), "{calls:?}");
    assert!(rig.fake.namespaces.lock().unwrap().contains("rickard"), "the account's namespace was made by the claim");
    let session = rig.login(&mut yubikey).await;
    let credential = rig.send("GET", "/api/account/console-key", None, Some(&session), None).await;
    assert_eq!(credential.body["state"], "enrolled", "{}", credential.text);
    let added = rig
        .send(
            "POST",
            "/api/account/keys",
            None,
            Some(&session),
            Some(serde_json::json!({ "openssh_public_key": an_ssh_key(93), "comment": "laptop" })),
        )
        .await;
    assert_eq!(added.status, http::StatusCode::OK, "{}", added.text);
    assert_eq!(rig.fake.keys_of("rickard").len(), 1, "his key");
}

/// ★ **A box no installer fed refuses by gunnar's word, and the token still
/// works**: gunnar holds no first invite, so `WebRegister` refuses the token;
/// the console stores nothing, says what fixes it (`gunnar control account
/// claim-operator --bootstrap-file`), and the break-glass door stays open.
#[tokio::test]
async fn a_box_with_no_first_invite_refuses_by_gunnars_word_and_the_token_still_works() {
    let rig = console(Fake::no_first_invite()).await;
    let mut yubikey = Yubikey::new();
    let refused = rig.register_with_the_bootstrap_token("rickard", &mut yubikey).await;

    assert_eq!(refused.status, http::StatusCode::FORBIDDEN, "{}", refused.text);
    assert!(refused.text.contains("claim-operator"), "the fix is named: {}", refused.text);
    assert!(rig.fake.passkeys.lock().unwrap().is_empty(), "nothing registered");
    assert!(rig.webauthn.admin_token_usable(), "the break-glass token still works");
}