Signup without a code queues a pending application; registry approve issues credentials

This commit is contained in:
George Coles
2026-09-15 11:59:30 -04:00
parent 8d74b63b11
commit 3de20ed89d
6 changed files with 266 additions and 54 deletions
+79 -2
View File
@@ -50,7 +50,7 @@ async fn signup_issues_invite_and_enroll_binds_key() {
let response = http
.post(format!("{base}/v1/signup"))
.json(&serde_json::json!({"label": "Alice Dev", "code": "sesame", "attestation": true, "privacy_ack": true}))
.json(&serde_json::json!({"label": "Alice Dev", "code": "sesame", "org": "Alice Dev Org", "representative": "Alice", "email": "alice@example.org", "attestation": true, "privacy_ack": true}))
.send()
.await
.unwrap();
@@ -169,6 +169,83 @@ async fn signup_stores_private_application_and_class() {
assert_eq!(app.class, "enrichment");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn signup_without_code_queues_application_for_approval() {
let root = tempfile::tempdir().unwrap();
let dir = setup_ma(root.path());
let base = spawn_registry_server(&dir, Some("sesame")).await;
let http = reqwest::Client::builder()
.timeout(Duration::from_secs(5))
.build()
.unwrap();
// no code: application queues as pending; no credentials, no member entry
let response = http
.post(format!("{base}/v1/signup"))
.json(&serde_json::json!({
"label": "Pending Co",
"org": "Pending Co Ltd",
"representative": "P. Pending",
"email": "ops@pending.example",
"attestation": true,
"privacy_ack": true
}))
.send()
.await
.unwrap();
assert_eq!(response.status(), reqwest::StatusCode::ACCEPTED);
let body: Value = response.json().await.unwrap();
assert_eq!(body.get("status").and_then(Value::as_str), Some("pending"));
assert!(body.get("credentials").is_none());
let id = "pending-co.frx.invalid";
let signed = registry::load_registry(&dir.join("registry.json")).unwrap();
assert!(
signed.doc.members.iter().all(|member| member.id != id),
"a pending application must not create a member entry"
);
let apps = registry::load_applications(&dir.join("applications.json")).unwrap();
assert_eq!(apps.len(), 1);
assert_eq!(apps[0].id, id);
assert_eq!(apps[0].status, "pending");
// a wrong code is still rejected, not queued
let rejected = http
.post(format!("{base}/v1/signup"))
.json(&serde_json::json!({
"label": "other", "code": "wrong", "org": "O", "representative": "R",
"email": "r@o.example", "attestation": true, "privacy_ack": true
}))
.send()
.await
.unwrap();
assert_eq!(rejected.status(), reqwest::StatusCode::FORBIDDEN);
// MA approves: member stub + invite; enrollment binds the key
commands::registry_approve(&dir, id, None).unwrap();
let signed = registry::load_registry(&dir.join("registry.json")).unwrap();
assert!(signed.doc.members.iter().any(|member| member.id == id));
let apps = registry::load_applications(&dir.join("applications.json")).unwrap();
assert_eq!(apps[0].status, "approved");
let invites = registry::load_invites(&dir.join("invites.json")).unwrap();
let invite = invites.iter().find(|invite| invite.id == id).unwrap();
let key = Keypair::generate();
let enrolled = http
.post(format!("{base}/v1/enroll"))
.json(&serde_json::json!({
"id": id,
"token": invite.token,
"pubkey": key.public_hex(),
}))
.send()
.await
.unwrap();
assert!(enrolled.status().is_success());
let signed = registry::load_registry(&dir.join("registry.json")).unwrap();
assert!(registry::authorized_keys(&signed, now_ts()).contains_key(&key.public_hex()));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn wizard_enrolls_and_writes_config() {
let root = tempfile::tempdir().unwrap();
@@ -177,7 +254,7 @@ async fn wizard_enrolls_and_writes_config() {
let http = reqwest::Client::new();
let body: Value = http
.post(format!("{base}/v1/signup"))
.json(&serde_json::json!({"label": "Wizard Test", "code": "sesame", "attestation": true, "privacy_ack": true}))
.json(&serde_json::json!({"label": "Wizard Test", "code": "sesame", "org": "Wizard Test Org", "representative": "Wiz", "email": "wiz@example.org", "attestation": true, "privacy_ack": true}))
.send()
.await
.unwrap()