pray-cli 1.9.2

Package manager for the language placed before inference — Prayfile CLI (binary: pray)
use super::*;
use pray_core::auth::RegistryAuthStore;
use pray_core::trust::EmailConfirmationPolicy;
use serde_json::json;
use std::fs;
use std::path::PathBuf;

#[test]
fn registration_delivers_code_outside_http_body() {
    let root = temporary_root("register");
    write_trust(&root, true, true);
    let response = auth_register_response(&root, br#"{"email":"alice@example.com"}"#)
        .expect("registration response");
    let body: serde_json::Value = serde_json::from_slice(&response.body).expect("json");
    let delivered = fs::read_to_string(root.join(".pray/verification-deliveries.jsonl"))
        .expect("delivery file");

    assert_eq!(response.status, 201);
    assert_eq!(body.get("verification_code"), None);
    assert!(delivered.contains("\"email\":\"alice@example.com\""));
    assert!(delivered.contains("\"code\":\""));
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let mode = fs::metadata(root.join(".pray/verification-deliveries.jsonl"))
            .expect("delivery metadata")
            .permissions()
            .mode()
            & 0o777;
        assert_eq!(mode, 0o600);
    }
}

#[test]
fn email_session_and_public_key_enrollment_require_authenticated_workflow() {
    let root = temporary_root("protected");
    write_trust(&root, true, true);
    let session = auth_session_response(&root, br#"{"email":"alice@example.com"}"#)
        .expect("session response");
    let passkey = auth_passkey_enroll_response(
        &root,
        None,
        serde_json::to_vec(&json!({
            "email": "alice@example.com",
            "credential_id": "credential-1",
            "public_key": ssh_public_key(),
        }))
        .expect("passkey body")
        .as_slice(),
    )
    .expect("passkey response");
    let ssh_key = auth_ssh_key_enroll_response(
        &root,
        None,
        serde_json::to_vec(&json!({
            "email": "alice@example.com",
            "public_key": ssh_public_key(),
        }))
        .expect("ssh body")
        .as_slice(),
    )
    .expect("ssh response");

    assert_eq!(session.status, 403);
    assert_eq!(passkey.status, 403);
    assert_eq!(ssh_key.status, 403);
}

#[test]
fn verified_bearer_enrolls_passkey_and_ssh_key() {
    let root = temporary_root("enroll");
    write_trust(&root, true, true);
    auth_register_response(&root, br#"{"email":"alice@example.com"}"#).expect("register");
    let code = latest_delivery_code(&root);
    let verify = auth_verify_response(
        &root,
        serde_json::to_vec(&json!({
            "email": "alice@example.com",
            "code": code,
        }))
        .expect("verify body")
        .as_slice(),
    )
    .expect("verify");
    let verify_body: serde_json::Value = serde_json::from_slice(&verify.body).expect("json");
    let token = verify_body["token"].as_str().expect("token");
    let authorization = format!("Bearer {token}");
    let public_key = ssh_public_key();

    let passkey = auth_passkey_enroll_response(
        &root,
        Some(&authorization),
        serde_json::to_vec(&json!({
            "email": "alice@example.com",
            "credential_id": "credential-1",
            "public_key": public_key,
        }))
        .expect("passkey body")
        .as_slice(),
    )
    .expect("passkey");
    let ssh_key = auth_ssh_key_enroll_response(
        &root,
        Some(&authorization),
        serde_json::to_vec(&json!({
            "email": "alice@example.com",
            "public_key": public_key,
        }))
        .expect("ssh body")
        .as_slice(),
    )
    .expect("ssh");
    let mismatch = auth_passkey_enroll_response(
        &root,
        Some(&authorization),
        serde_json::to_vec(&json!({
            "email": "mallory@example.com",
            "credential_id": "credential-2",
            "public_key": public_key,
        }))
        .expect("mismatch body")
        .as_slice(),
    )
    .expect("mismatch");

    assert_eq!(verify.status, 200);
    assert_eq!(verify_body["kind"], "email");
    assert_eq!(passkey.status, 200);
    assert_eq!(ssh_key.status, 200);
    assert_eq!(mismatch.status, 403);
}

#[test]
fn disabled_key_methods_reject_login_challenges() {
    let root = temporary_root("disabled");
    let store = RegistryAuthStore::open(&root).expect("auth store");
    store
        .register_email("alice@example.com", EmailConfirmationPolicy::Disabled)
        .expect("registration");
    store
        .enroll_passkey("alice@example.com", "credential-1", &ssh_public_key(), None)
        .expect("passkey");

    let response = auth_passkey_challenge_response(&root, br#"{"credential_id":"credential-1"}"#)
        .expect("challenge response");

    assert_eq!(response.status, 403);
}

fn latest_delivery_code(root: &std::path::Path) -> String {
    let text = fs::read_to_string(root.join(".pray/verification-deliveries.jsonl")).expect("file");
    let line = text.lines().last().expect("line");
    let value: serde_json::Value = serde_json::from_str(line).expect("jsonl");
    value["code"].as_str().expect("code").to_string()
}

fn write_trust(root: &std::path::Path, passkeys: bool, ssh_keys: bool) {
    fs::write(
        root.join("v1/trust.json"),
        serde_json::to_vec(&json!({
            "email_confirmation": "required",
            "passkeys_enabled": passkeys,
            "ssh_keys_enabled": ssh_keys,
            "ssh_agent_signing_enabled": true
        }))
        .expect("trust json"),
    )
    .expect("write trust");
}

fn temporary_root(name: &str) -> PathBuf {
    let root = std::env::temp_dir().join(format!(
        "pray-server-auth-{name}-{}-{}",
        std::process::id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("clock")
            .as_nanos()
    ));
    fs::create_dir_all(root.join("v1")).expect("root");
    root
}

fn ssh_public_key() -> String {
    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcH".to_string()
}