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:
@@ -129,6 +129,7 @@ pub struct Config {
|
||||
pub landing: LandingConfig,
|
||||
pub retention: RetentionConfig,
|
||||
pub restore: RestoreConfig,
|
||||
pub registration: RegistrationConfig,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -392,6 +393,16 @@ impl RestoreConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Registration gates beyond PoW — composable, operator-configured, advertised
|
||||
/// via `/capabilities`. Each is a gate the wallet must satisfy to create a NEW
|
||||
/// identity; returning wallets bypass them, and self-hosting bypasses all of
|
||||
/// them (run your own backend). PoW lives in [`PowConfig`]; this holds the rest.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RegistrationConfig {
|
||||
/// Require a valid operator-minted invite code to register a new wallet.
|
||||
pub require_invite: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Load configuration from the environment and validate it (fail-closed).
|
||||
pub fn from_env() -> Result<Self, AppError> {
|
||||
@@ -560,6 +571,9 @@ impl Config {
|
||||
max_depth_days: env_parse("WALLET_MAX_RESTORE_DEPTH_DAYS", 365u32)?,
|
||||
}
|
||||
},
|
||||
registration: RegistrationConfig {
|
||||
require_invite: env_bool("REGISTRATION_REQUIRE_INVITE", false),
|
||||
},
|
||||
};
|
||||
|
||||
cfg.validate()?;
|
||||
@@ -857,6 +871,9 @@ mod tests {
|
||||
policy: RestorePolicy::Bounded,
|
||||
max_depth_days: 365,
|
||||
},
|
||||
registration: RegistrationConfig {
|
||||
require_invite: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user