feat(registration): composable gate framework + invite codes (gate #1)

First of the operator-configurable registration gates (PoW-priced restore and
pay-to-register follow). A NEW wallet must present a valid invite code when
REGISTRATION_REQUIRE_INVITE is on; returning wallets and self-hosting bypass it,
and the active gates are advertised via /capabilities so the wallet shapes
onboarding.

- migration + db: invite_codes table storing only sha256(code) (128-bit codes,
  no pepper needed). Redemption is an atomic single-statement claim
  (UPDATE ... WHERE used_at IS NULL RETURNING) — single-use even under concurrent
  submission (L1 test fires 8 concurrent claims; exactly one wins).
- core::invite: generate (base58, 128-bit) + sha256 hash.
- enforce_invite in extension_register, mirroring enforce_pow (returning bypass;
  ONE literal error for not-found/used/expired — never an oracle).
- RegistrationConfig.require_invite + RegistrationCapability in /capabilities.
- smirk-admin mint-invite --count/--label (prints raw codes once; stores hashes).
- L1 tests (single-use + concurrency race), .env.example, OpenAPI (drift clean).
This commit is contained in:
jwinterm
2026-06-30 17:14:53 -04:00
parent 1e08119c5e
commit 610819bb5d
13 changed files with 371 additions and 1 deletions
+1
View File
@@ -68,6 +68,7 @@ use utoipa::OpenApi;
crate::api::capabilities::ChainCapability,
crate::api::capabilities::FeatureCapabilities,
crate::api::capabilities::RestoreCapability,
crate::api::capabilities::RegistrationCapability,
crate::api::prices::PricesResponse,
crate::api::auth::AuthResponse,
crate::api::auth::UserInfo,