mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 07:21:32 +02:00
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:
@@ -0,0 +1,20 @@
|
||||
-- Operator-minted registration invite codes — one composable registration gate
|
||||
-- (alongside PoW and, later, pay-to-register). v1 is single-use, operator-minted
|
||||
-- via the `smirk-admin mint-invite` CLI; the schema leaves room for multi-use,
|
||||
-- expiry, and referral without a breaking change.
|
||||
--
|
||||
-- Only sha256(code) is stored. The raw code is a bearer secret shown once at
|
||||
-- mint time and never persisted, so a database leak is NOT a list of usable
|
||||
-- codes — codes are >=128-bit random, so the hash is not brute-forceable and no
|
||||
-- pepper is needed.
|
||||
CREATE TABLE invite_codes (
|
||||
code_hash TEXT PRIMARY KEY, -- hex sha256 of the raw code
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
label TEXT, -- optional operator note (batch / who-for)
|
||||
expires_at TIMESTAMPTZ, -- NULL = never (reserved; v1 mints without expiry)
|
||||
used_at TIMESTAMPTZ, -- NULL = unused (single-use)
|
||||
used_by UUID REFERENCES users (id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Fast lookup of still-spendable codes (mint listing / future sweeps).
|
||||
CREATE INDEX idx_invite_codes_unused ON invite_codes (created_at) WHERE used_at IS NULL;
|
||||
Reference in New Issue
Block a user