mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## Summary - add database-backed v2 invite links with optional maximum-use limits and atomic final-slot redemption - preserve v1 invite compatibility while adding exhausted/expired/invalid client handling across desktop, web, and mobile - emit structured claim-outcome logs with community, invite ID, outcome, maximum uses, and post-claim count ## Verification - `cargo fmt --all -- --check` - `cargo test -p buzz-db` (85 passed, 134 Postgres-dependent ignored) - `cargo clippy -p buzz-db --all-targets -- -D warnings` - desktop `npm run typecheck` - push hook: desktop checks/tests, desktop Tauri tests, Rust tests, and branch-skew passed - Postgres integration tests were previously reviewed green at the pre-rebase tree; local rerun on this session was unavailable because Postgres/Docker were not running - mobile push-hook check could not start because Flutter is unavailable locally --------- Signed-off-by: Kalvin Chau <kalvin@block.xyz> Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz> Co-authored-by: npub1c4alndp82zyt9veaklm5d965quss79vlhk9awv7qu5erwhmf42qqlvc25c <c57bf9b4275088b2b33db7f746975407210f159fbd8bd733c0e532375f69aa80@buzz.block.builderlab.xyz> Co-authored-by: npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc <5288f082d91aff2de5bc8be23fbfa01ca2e3859cac57a91b7a3fa9f12349505c@buzz.block.builderlab.xyz> Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
34 lines
1.8 KiB
SQL
34 lines
1.8 KiB
SQL
-- Use-limited invite links: durable invite records for atomic redemption.
|
|
--
|
|
-- Stateless HMAC bearer tokens (v1) cannot enforce use limits: their signed
|
|
-- payload is immutable and no invite row exists to record consumption. This
|
|
-- migration introduces a durable `relay_invites` table that stores only the
|
|
-- SHA-256 hash of an opaque v2 code, never the reusable bearer secret itself.
|
|
--
|
|
-- Every lookup binds both (community_id, token_hash) so a code presented on
|
|
-- the wrong tenant host returns Invalid — there is no cross-tenant lookup by
|
|
-- hash alone. `FOR UPDATE` during claim serializes concurrent claims for one
|
|
-- invite across relay processes; membership insertion, join-policy evidence,
|
|
-- and use_count increment share a single commit so exactly one claimant can
|
|
-- win the final slot.
|
|
--
|
|
-- max_uses is optional: NULL means unlimited (preserving current behavior).
|
|
-- use_count is always incremented for new members, even when unlimited, for
|
|
-- observability. role is pinned to 'member' — invite links never grant admin.
|
|
CREATE TABLE relay_invites (
|
|
community_id UUID NOT NULL REFERENCES communities(id),
|
|
id UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
token_hash BYTEA NOT NULL CHECK (length(token_hash) = 32),
|
|
role TEXT NOT NULL DEFAULT 'member' CHECK (role = 'member'),
|
|
max_uses INTEGER CHECK (max_uses BETWEEN 1 AND 10000),
|
|
use_count INTEGER NOT NULL DEFAULT 0 CHECK (use_count >= 0),
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
created_by TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (community_id, id),
|
|
UNIQUE (community_id, token_hash),
|
|
CHECK (max_uses IS NULL OR use_count <= max_uses)
|
|
);
|
|
|
|
CREATE INDEX relay_invites_expires_at_idx ON relay_invites (expires_at);
|