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

nordisk/gunnar

xtask/tests/there_is_one_identity_implementation.rs

//! **There is ONE identity implementation** (IDENTITY-HOST, owner GO 2026-10-27):
//! sign-up, login, the passkey ceremony, invites, the session store and step-up
//! are edda's `nornir-identity` (over `nornir-auth-webauthn`'s `ceremony` and
//! `nornir-identity-session`). A product implements `IdentityHost` and serves
//! `IdentityService`; it defines none of the pieces itself.
//!
//! This guard reads the product's source (every `src/**/*.rs` and every
//! `Cargo.toml`; tests, tooling, benches and vendored trees are not product code) and
//! names every definition of a known piece of an own implementation, by file
//! and line: the functions and types Gunnar, gunnar-ui, Holger and holger-ui
//! carried before the move, `webauthn-rs`, and a hand-built `HttpOnly` session
//! cookie. It reads this repository, and its console's beside it when that is
//! checked out (`../gunnar-ui`), and says which it read. No network.

use std::path::{Path, PathBuf};

/// The repositories this guard holds: this one, and its console when it is beside it.
const SUBJECTS: &[(&str, bool)] = &[(".", true), ("../gunnar-ui", false)];

/// `fn <name>(` or `fn <name><`: a function of an own implementation.
const FUNCTIONS: &[(&str, &str)] = &[
    ("sign_up", "sign-up is Identity::sign_up"),
    ("signup", "sign-up is Identity::sign_up"),
    (
        "web_challenge",
        "a login's challenge is Identity::login_start",
    ),
    ("web_login", "a login is Identity::login_finish"),
    (
        "web_register",
        "a passkey registration is Identity::redeem_finish",
    ),
    (
        "register_web_credential",
        "a passkey registration is Identity::redeem_finish",
    ),
    ("login_start", "a login is the core's"),
    ("login_finish", "a login is the core's"),
    ("create_invite", "an invite is Identity::invite"),
    ("check_invite", "an invite is the core's"),
    ("revoke_invite", "an invite is the core's"),
    ("list_invites", "an invite is the core's"),
    (
        "redeem_invite",
        "a redemption is Identity::redeem_start/finish",
    ),
    ("redeem_start", "a redemption is the core's"),
    ("redeem_finish", "a redemption is the core's"),
    ("mint_session", "a session is minted by the core"),
    (
        "verify_session",
        "a session is verified by the core (Identity::session)",
    ),
    ("verify_step_up", "a step-up is Identity::step_up_finish"),
    ("step_up_start", "a step-up is the core's"),
    ("step_up_finish", "a step-up is the core's"),
    ("verify_assertion", "the ceremony is nornir-auth-webauthn's"),
    (
        "verify_attestation",
        "the ceremony is nornir-auth-webauthn's",
    ),
    ("check_ceremony", "the ceremony is nornir-auth-webauthn's"),
    (
        "check_relying_party",
        "the ceremony is nornir-auth-webauthn's",
    ),
    ("client_data", "the ceremony is nornir-auth-webauthn's"),
    (
        "take_registration",
        "the ceremony is nornir-auth-webauthn's",
    ),
    ("take_assertion", "the ceremony is nornir-auth-webauthn's"),
    (
        "room_for_a_ceremony",
        "the ceremony store is nornir-auth-webauthn's",
    ),
    (
        "legacy_credential",
        "a passkey lives at the server, in the core's store",
    ),
    (
        "move_to_gunnar",
        "a passkey lives at the server, in the core's store",
    ),
];

/// `struct|enum|trait|type <Name>`: a type of an own implementation.
const TYPES: &[(&str, &str)] = &[
    ("SessionKey", "a session is the core's opaque token"),
    ("SessionKeys", "a session is the core's opaque token"),
    (
        "SessionFloor",
        "the floors are nornir-identity-session's Revocations",
    ),
    (
        "ConsoleEpoch",
        "the floors are nornir-identity-session's Revocations",
    ),
    ("WebSessions", "a session is the core's"),
    (
        "CeremonyStore",
        "the ceremony store is nornir-auth-webauthn's",
    ),
    ("Challenges", "the ceremony store is nornir-auth-webauthn's"),
    ("NewInvite", "an invite is nornir-identity-session's"),
    ("Invite", "an invite is nornir-identity-session's"),
    ("Revocations", "the floors are nornir-identity-session's"),
    (
        "HostCookie",
        "the session cookie is nornir-identity-session's",
    ),
];

/// Text that is an own implementation wherever it stands.
const TEXT: &[(&str, &str)] = &[
    (
        "webauthn_rs",
        "the ceremony is nornir-auth-webauthn's, never webauthn-rs",
    ),
    (
        "webauthn-rs",
        "the ceremony is nornir-auth-webauthn's, never webauthn-rs",
    ),
    (
        "HttpOnly",
        "the session cookie is nornir-identity-session's HostCookie",
    ),
];

/// The few lines that carry a known name and are NOT identity, each with its reason.
const NOT_IDENTITY: &[(&str, &str, &str)] = &[
    (
        "crates/gunnar-core/src/key_attestation.rs",
        "fn verify_attestation",
        "an sk-ssh security key's attestation (SSH keys), not the WebAuthn ceremony",
    ),
    (
        "/clans.rs",
        "struct Invite",
        "a clan invitation (membership of a clan), not an enrolment invite",
    ),
    (
        "crates/gunnar-ui-web/",
        "struct Invite",
        "the console page renders the invites the server answers (rendering only)",
    ),
    (
        "crates/gunnar-ui-web/",
        "struct NewInvite",
        "the console page renders the invites the server answers (rendering only)",
    ),
];

fn not_identity(rel: &str, what: &str) -> bool {
    NOT_IDENTITY
        .iter()
        .any(|(path, name, _)| rel.contains(path) && what == *name)
}

fn root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .expect("xtask has a parent")
        .to_path_buf()
}

/// Directories that are not product code: build output, tests, tooling, vendored and bench trees.
const NOT_PRODUCT: &[&str] = &[
    "target",
    "tests",
    "vendor",
    "benches",
    "bench",
    "examples",
    "fixtures",
    "xtask",
    ".git",
    "node_modules",
    "estate-tests",
    "browser-tests",
    "docs",
    "installtest",
    "testdata",
];

fn walk(dir: &Path, in_src: bool, out: &mut Vec<PathBuf>) {
    let Ok(entries) = std::fs::read_dir(dir) else {
        return;
    };
    for e in entries.flatten() {
        let p = e.path();
        let name = e.file_name().to_string_lossy().into_owned();
        if p.is_dir() {
            if !NOT_PRODUCT.contains(&name.as_str()) {
                walk(&p, in_src || name == "src", out);
            }
        } else if (in_src && p.extension().is_some_and(|x| x == "rs")) || name == "Cargo.toml" {
            out.push(p);
        }
    }
}

/// The product files of one repository: every `src/**/*.rs` and every `Cargo.toml`.
fn product_files(repo: &Path) -> Vec<PathBuf> {
    let mut out = Vec::new();
    walk(repo, false, &mut out);
    out.sort();
    out
}

fn defines(code: &str, keyword: &str, name: &str) -> bool {
    let mut rest = code;
    while let Some(at) = rest.find(keyword) {
        let before_ok = at == 0
            || !rest.as_bytes()[at - 1].is_ascii_alphanumeric() && rest.as_bytes()[at - 1] != b'_';
        let after = &rest[at + keyword.len()..];
        if before_ok {
            if let Some(tail) = after.strip_prefix(name) {
                let next = tail.chars().next();
                let boundary = match keyword {
                    "fn " => matches!(next, Some('(') | Some('<')),
                    _ => !next.is_some_and(|c| c.is_alphanumeric() || c == '_'),
                };
                if boundary {
                    return true;
                }
            }
        }
        rest = &rest[at + keyword.len()..];
    }
    false
}

/// Every own-implementation line under `repo`, as `path:line: what — why`.
fn findings(repo: &Path) -> Vec<String> {
    let mut out = Vec::new();
    for file in product_files(repo) {
        let text = std::fs::read_to_string(&file).unwrap_or_default();
        let rel = file
            .strip_prefix(repo)
            .unwrap_or(&file)
            .display()
            .to_string();
        out.extend(findings_in(&rel, &text));
    }
    out
}

/// The own-implementation lines of one product file (`rel` is its path in the repository).
fn findings_in(rel: &str, text: &str) -> Vec<String> {
    let rel = rel.to_string();
    let mut out = Vec::new();
    // a `#[cfg(test)]` module is a test, not product code: from the attribute to the
    // brace that closes the module (`None` = outside one; `Some(depth)` = inside)
    let mut test_mod: Option<i64> = None;
    let mut cfg_test = false;
    for (n, line) in text.lines().enumerate() {
        let trimmed = line.trim();
        if !rel.ends_with("Cargo.toml") {
            if let Some(depth) = test_mod.as_mut() {
                let code = line.split("//").next().unwrap_or("");
                *depth += code.matches('{').count() as i64 - code.matches('}').count() as i64;
                if *depth <= 0 && code.contains('}') {
                    test_mod = None;
                }
                continue;
            }
            if trimmed == "#[cfg(test)]" {
                cfg_test = true;
                continue;
            }
            if cfg_test {
                cfg_test = false;
                let item = trimmed.strip_prefix("pub ").unwrap_or(trimmed);
                if item.starts_with("mod ") && item.contains('{') {
                    let code = line.split("//").next().unwrap_or("");
                    let depth = code.matches('{').count() as i64 - code.matches('}').count() as i64;
                    if depth > 0 {
                        test_mod = Some(depth);
                    }
                    continue;
                }
            }
        }
        let code = line.split("//").next().unwrap_or("");
        let code = if rel.ends_with("Cargo.toml") {
            line.split('#').next().unwrap_or("")
        } else {
            code
        };
        let mut hit = |what: String, why: &str| {
            if !not_identity(&rel, &what) {
                out.push(format!("{rel}:{}: {what}: {why}", n + 1));
            }
        };
        if !rel.ends_with("Cargo.toml") {
            for (name, why) in FUNCTIONS {
                if defines(code, "fn ", name) {
                    hit(format!("fn {name}"), why);
                }
            }
            for (name, why) in TYPES {
                for keyword in ["struct ", "enum ", "trait ", "type "] {
                    if defines(code, keyword, name) {
                        hit(format!("{keyword}{name}"), why);
                    }
                }
            }
        }
        for (needle, why) in TEXT {
            if code.contains(needle) {
                hit(format!("`{needle}`"), why);
            }
        }
    }
    out
}

/// ★ The guard: no product file of this repository (or its console beside it)
/// defines a piece of identity edda's `nornir-identity` owns.
#[test]
fn there_is_one_identity_implementation() {
    let mut all = Vec::new();
    for (rel, required) in SUBJECTS {
        let repo = root().join(rel);
        if !repo.join("Cargo.toml").is_file() {
            assert!(
                !required,
                "{}: the guard's own repository has no Cargo.toml",
                repo.display()
            );
            eprintln!("there_is_one_identity_implementation: {rel} is not checked out beside this repository; it is held by its own copy of this guard");
            continue;
        }
        let found = findings(&repo);
        eprintln!("there_is_one_identity_implementation: read {} files of {rel}, {} own-implementation lines", product_files(&repo).len(), found.len());
        all.extend(found.into_iter().map(|f| format!("{rel}/{f}")));
    }
    assert!(
        all.is_empty(),
        "identity is edda's nornir-identity (IDENTITY-HOST): these lines define their own piece of it ({}):\n{}",
        all.len(),
        all.join("\n")
    );
}

/// A file's own `#[cfg(test)]` module is a test, not product code (CI-RED, 2026-10-30:
/// gunnar-ui's sso.rs asserts its cookie `contains("HttpOnly")` in its tests); the
/// product code before and after it is still read.
#[test]
fn the_scanner_leaves_a_files_test_module_alone() {
    let src = "pub fn cookie() -> String { host_cookie() }\n\
               #[cfg(test)]\n\
               mod tests {\n    \
                   #[test]\n    \
                   fn a_cookie() {\n        \
                       assert!(cookie().contains(\"HttpOnly\"));\n    \
                   }\n\
               }\n\
               pub fn after() -> &'static str { \"HttpOnly\" }\n";
    let found = findings_in("crates/x/src/lib.rs", src);
    assert_eq!(found, vec!["crates/x/src/lib.rs:9: `HttpOnly`: the session cookie is nornir-identity-session's HostCookie".to_string()], "{found:?}");
    let product = findings_in(
        "crates/x/src/lib.rs",
        "pub fn c() -> &'static str { \"; HttpOnly\" }\n",
    );
    assert_eq!(product.len(), 1, "product code is still read: {product:?}");
}

/// The scanner sees what it is meant to and nothing else.
#[test]
fn the_scanner_names_a_definition_and_not_a_call() {
    assert!(defines(
        "    pub async fn web_login(&self) {",
        "fn ",
        "web_login"
    ));
    assert!(defines(
        "fn verify_session<T>(t: T)",
        "fn ",
        "verify_session"
    ));
    assert!(
        !defines("client.web_login(request).await", "fn ", "web_login"),
        "a call is not a definition"
    );
    assert!(
        !defines("fn web_login_page() {}", "fn ", "web_login"),
        "another name"
    );
    assert!(defines("pub struct Invite {", "struct ", "Invite"));
    assert!(
        !defines("pub struct InviteAsk {", "struct ", "Invite"),
        "another type"
    );
    assert!(
        !defines("pub struct MyInvite {", "struct ", "Invite"),
        "another type"
    );
    assert!(defines("pub type Challenges = X;", "type ", "Challenges"));
}