Expose one set of current policy preconditions

Exercise retained historical receipts and deleted targets through the supported status API; refuse UUID reuse without mutating policy.

Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz>
Signed-off-by: Brad Seiler <seiler@squareup.com>
This commit is contained in:
Brad Seiler
2026-11-06 13:27:03 -05:00
parent 7b495d126f
commit b0d006618e
2 changed files with 205 additions and 10 deletions

View File

@@ -199,7 +199,6 @@ pub async fn community_nip_fi_policy_status(
let fields = mode_fields(&state, Some(policy.requires_nip_fi));
Ok(Json(serde_json::json!({
"community_id": id, "expected_host": host,
"requires_nip_fi": policy.requires_nip_fi, "policy_generation": policy.policy_generation,
"expected_policy_generation": policy.policy_generation,
"expected_requires_nip_fi": policy.requires_nip_fi,
"mode": fields["mode"], "effective_required": fields["effective_required"],

View File

@@ -66,7 +66,64 @@ mod postgres_tests {
.await;
assert_eq!(disabled.status(), StatusCode::SERVICE_UNAVAILABLE);
assert_eq!(read_json(disabled).await["code"], "upgrades_disabled");
for (field, value) in [
("operation_id", serde_json::json!(Uuid::nil())),
("community_id", serde_json::json!(Uuid::nil())),
("expected_host", serde_json::json!(host.to_uppercase())),
("actor_pubkey", serde_json::json!("not-a-pubkey")),
("expected_policy_generation", serde_json::json!(-1)),
("expected_issuer", serde_json::json!("")),
("unknown", serde_json::json!(true)),
] {
let mut invalid = body.clone();
invalid[field] = value;
let response = fresh_upgrade_request(
state.clone(),
&operator,
"POST",
path,
Some(invalid.to_string()),
)
.await;
assert_eq!(response.status(), StatusCode::BAD_REQUEST, "{field}");
assert_eq!(read_json(response).await["code"], "invalid_request");
}
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_changes = true;
for (field, value, code) in [
(
"expected_issuer",
"https://unapproved.example".to_owned(),
"issuer_conflict",
),
(
"expected_host",
"outside.example".to_owned(),
"issuer_conflict",
),
(
"actor_pubkey",
Keys::generate().public_key().to_hex(),
"owner_conflict",
),
(
"expected_owner_pubkey",
Keys::generate().public_key().to_hex(),
"owner_conflict",
),
] {
let mut refused = body.clone();
refused[field] = value.into();
let response = fresh_upgrade_request(
state.clone(),
&operator,
"POST",
path,
Some(refused.to_string()),
)
.await;
assert_eq!(response.status(), StatusCode::CONFLICT);
assert_eq!(read_json(response).await["code"], code);
}
let mut history = Vec::new();
for (generation, desired) in [(1, true), (2, false), (3, true)] {
body["operation_id"] = Uuid::new_v4().to_string().into();
@@ -96,6 +153,73 @@ mod postgres_tests {
accepted["receipt"]["state"],
if desired { "pending" } else { "applied" }
);
if generation == 1 {
let binding = Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config)
.nip_fi_provision_binding
.take();
let mut fresh = body.clone();
let refused_id = Uuid::new_v4();
fresh["operation_id"] = refused_id.to_string().into();
fresh["expected_policy_generation"] = 1.into();
fresh["expected_requires_nip_fi"] = true.into();
let refused = fresh_upgrade_request(
state.clone(),
&operator,
"POST",
path,
Some(fresh.to_string()),
)
.await;
assert_eq!(refused.status(), StatusCode::SERVICE_UNAVAILABLE);
assert_eq!(
read_json(refused).await["code"],
"authorization_unavailable"
);
assert!(state
.db
.community_auth_policy_status(refused_id, &operator.public_key().to_hex())
.await
.unwrap()
.is_none());
let historical = read_json(
fresh_upgrade_request(
state.clone(),
&operator,
"POST",
path,
Some(body.to_string()),
)
.await,
)
.await;
assert_eq!(historical["receipt"]["state"], "unavailable");
assert_eq!(historical["receipt"]["policy_generation"], 1);
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config)
.nip_fi_provision_binding = binding;
let text = body.to_string();
let auth = fresh_upgrade_auth(
&operator,
&format!("http://{INGRESS_HOST}{path}"),
"POST",
Some(text.as_bytes()),
);
let first = operator_request_with_auth(
state.clone(),
"POST",
path,
Some(text.clone()),
auth.clone(),
)
.await;
assert_eq!(first.status(), StatusCode::OK);
let replayed_signature =
operator_request_with_auth(state.clone(), "POST", path, Some(text), auth).await;
assert_eq!(replayed_signature.status(), StatusCode::UNAUTHORIZED);
assert_eq!(
replayed_signature.headers()[header::CACHE_CONTROL],
"no-store"
);
}
history.push(body.clone());
for old in &history {
let id = old["operation_id"].as_str().unwrap();
@@ -125,15 +249,6 @@ mod postgres_tests {
"superseded"
}
);
let legacy = fresh_upgrade_request(
state.clone(),
&operator,
"GET",
&format!("/operator/communities/nip-fi-upgrade?operation_id={id}"),
None,
)
.await;
assert_eq!(legacy.status(), StatusCode::NOT_FOUND);
let replay = fresh_upgrade_request(
state.clone(),
&operator,
@@ -225,6 +340,87 @@ mod postgres_tests {
)
.await;
assert_eq!(invalid.status(), StatusCode::BAD_REQUEST);
// Old acceptance rows remain recoverable through the sole supported
// endpoint, including after their target has been deleted. Their null
// preconditions cannot match a new fenced request with the same UUID.
for target in [*community.id.as_uuid(), Uuid::new_v4()] {
let id = Uuid::new_v4();
sqlx::query("INSERT INTO community_auth_upgrades
(operation_id,target_community_id,expected_host,actor_pubkey,expected_owner_pubkey,
expected_issuer,operator_pubkey,policy_generation) VALUES ($1,$2,$3,$4,$4,$5,$6,1)")
.bind(id).bind(target).bind(&host).bind(owner.public_key().to_hex())
.bind(NIP_FI_ISSUER).bind(operator.public_key().to_hex())
.execute(state.db.pool()).await.unwrap();
let receipt = read_json(
fresh_upgrade_request(
state.clone(),
&operator,
"GET",
&format!("{path}?operation_id={id}"),
None,
)
.await,
)
.await;
assert!(receipt["receipt"]["expected_policy_generation"].is_null());
assert!(receipt["receipt"]["expected_requires_nip_fi"].is_null());
assert_eq!(receipt["receipt"]["requires_nip_fi"], true);
assert_eq!(
receipt["receipt"]["state"],
if target == *community.id.as_uuid() {
"superseded"
} else {
"unavailable"
}
);
if target != *community.id.as_uuid() {
assert!(receipt["receipt"]["current_policy_generation"].is_null());
assert!(receipt["receipt"]["current_requires_nip_fi"].is_null());
}
let mut reuse = history[0].clone();
reuse["operation_id"] = id.to_string().into();
reuse["community_id"] = target.to_string().into();
reuse["expected_policy_generation"] = 4.into();
let conflict = fresh_upgrade_request(
state.clone(),
&operator,
"POST",
path,
Some(reuse.to_string()),
)
.await;
assert_eq!(conflict.status(), StatusCode::CONFLICT);
assert_eq!(read_json(conflict).await["code"], "idempotency_conflict");
let hidden = fresh_upgrade_request(
state.clone(),
&Keys::generate(),
"GET",
&format!("{path}?operation_id={id}"),
None,
)
.await;
assert_eq!(hidden.status(), StatusCode::FORBIDDEN);
}
let current = read_json(
fresh_upgrade_request(state.clone(), &operator, "GET", &current_path, None).await,
)
.await;
assert!(current.get("requires_nip_fi").is_none());
assert!(current.get("policy_generation").is_none());
assert_eq!(current["expected_policy_generation"], 4);
assert_eq!(current["expected_requires_nip_fi"], false);
// The removed route has no operator-auth handler even for signed calls.
for method in ["GET", "POST"] {
let gone = fresh_upgrade_request(
state.clone(),
&operator,
method,
"/operator/communities/nip-fi-upgrade",
(method == "POST").then(|| "{}".into()),
)
.await;
assert_eq!(gone.status(), StatusCode::NOT_FOUND);
}
// The control-plane signature and allowlist remain mandatory.
let outsider = Keys::generate();
let response = fresh_upgrade_request(state, &outsider, "GET", &current_path, None).await;