mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 15:31:38 +02:00
security(auth): harden registration/identity against verified review findings
Adversarial review of the auth/registration surface (findings tracked privately). Backend half: - F2: npub-native register no longer binds an npub onto an existing wallet row on a seed_fingerprint match alone (unauthenticated account takeover). get_or_create_user_by_nostr_pubkey fails closed (409); linking an npub goes through the authenticated /auth/nostr/link flow. Integration test rewritten to assert the refuse-and-409 contract. - F3: `all`-mode gate verifies payment is settled (non-consuming) BEFORE consuming the single-use invite, so a still-confirming payment can't permanently burn the invite. verify_payment_settled split out. - F4: username availability is pre-checked before any gate token is consumed; collisions return 409, not 500 — a taken username no longer burns a paid invoice / invite. - F6: nostr_register now persists the submitted chain keys, so receive-by-username and restore self-verification work for npub wallets. - F10: the pay-to-register not-yet-settled response carries a stable PAYMENT_PENDING code so polling clients don't parse a free-text string. Validated against Postgres: full suite 283 passed / 0 failed.
This commit is contained in:
+31
-12
@@ -1,7 +1,9 @@
|
||||
//! L1 integration: npub-native registration (`POST /auth/nostr/register`) — the
|
||||
//! create-by-npub path (no BTC signature) and the `seed_fingerprint` MERGE dedup
|
||||
//! that must never split one wallet into two identities. Skips without
|
||||
//! `TEST_DATABASE_URL` (see `common`).
|
||||
//! create-by-npub path (no BTC signature) and the `seed_fingerprint` handling.
|
||||
//! A fingerprint match onto a pre-existing row is REFUSED: the endpoint
|
||||
//! proves only npub control, so it must not bind an npub onto an account keyed by
|
||||
//! another (on-file) credential without proof. Skips without `TEST_DATABASE_URL`
|
||||
//! (see `common`).
|
||||
|
||||
mod common;
|
||||
|
||||
@@ -147,7 +149,14 @@ async fn register_replays_are_rejected() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn register_merges_onto_existing_seed_fingerprint_row() {
|
||||
async fn register_refuses_to_bind_npub_onto_existing_seed_fingerprint_row_without_proof() {
|
||||
// SECURITY: the npub-native register endpoint proves control of the NEW
|
||||
// npub only, never of the key ALREADY ON FILE. seed_fingerprint is a lookup
|
||||
// handle the client sends unauthenticated (check-restore / register), not a
|
||||
// credential — so binding an npub onto a pre-existing BTC-anchored row on a
|
||||
// fingerprint MATCH alone would let anyone who learns a victim's fingerprint
|
||||
// take over that account. The endpoint must fail closed (409); the wallet
|
||||
// links its npub through the authenticated POST /auth/nostr/link flow instead.
|
||||
let app = require_app!();
|
||||
let base = app
|
||||
.state
|
||||
@@ -174,21 +183,31 @@ async fn register_merges_onto_existing_seed_fingerprint_row() {
|
||||
.unwrap();
|
||||
assert!(existing.nostr_pubkey.is_none());
|
||||
|
||||
// Register npub-native with the SAME seed -> MERGE onto that row, not a new one.
|
||||
// Register npub-native with the SAME seed -> REJECTED (no unauthenticated merge).
|
||||
let sk = random_signer();
|
||||
let pk = hex::encode(sk.verifying_key().to_bytes());
|
||||
let (status, body) = register(&app, &sk, &base, Some(&fp)).await;
|
||||
assert_eq!(status, StatusCode::OK, "body={body}");
|
||||
assert_eq!(status, StatusCode::CONFLICT, "body={body}");
|
||||
|
||||
let merged = app
|
||||
// The existing row is left UNTOUCHED and the npub binds to nothing.
|
||||
assert!(
|
||||
app.state
|
||||
.db
|
||||
.find_user_by_nostr_pubkey(&pk)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none(),
|
||||
"a rejected register must not bind the npub to any account"
|
||||
);
|
||||
let after = app
|
||||
.state
|
||||
.db
|
||||
.find_user_by_nostr_pubkey(&pk)
|
||||
.get_user(existing.id)
|
||||
.await
|
||||
.unwrap()
|
||||
.expect("npub resolves after register");
|
||||
assert_eq!(
|
||||
merged.id, existing.id,
|
||||
"npub register must MERGE onto the existing seed_fingerprint row, not split identity"
|
||||
.expect("existing row still present");
|
||||
assert!(
|
||||
after.nostr_pubkey.is_none(),
|
||||
"the victim's row must not have gained an npub"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user