Summary
Flow runs a closed beta: registration is open, but using the app requires explicit approval. A new account is gated until an admin approves it, or until the user redeems an invite code. Applies to both consumer users and B2B partners.
Files Changed
Backend (flow-platform)
supabase/migrations/20260622120000_beta_access_gate.sql— gate columns onprofiles,invite_codestable,redeem_invite_code()RPC, backfill.web/lib/admin-auth.ts— sharedgetAdminUserId()for the new routes.web/lib/beta-access.ts— invite-code generation, magic-link builder, Resend approval/invite emails.web/app/api/admin/access/profiles/route.ts+[id]/route.ts— list / approve / reject accounts.web/app/api/admin/access/leads/route.ts— triagewaitlist+partner_leads, invite on approve.web/app/api/admin/access/invites/route.ts+[id]/route.ts— mint / list / revoke codes.web/app/admin/(dashboard)/access/page.tsx+components/dashboard/AdminSidebar.tsx— “Accesso Beta” dashboard.
Key Decisions
- Dedicated
access_statuscolumn, not a reuse ofprofiles.status.statusis the moderation lifecycle (active/suspended/banned);access_status(pending/approved/rejected) is the onboarding lifecycle. The app gate isaccess_status='approved' AND status='active'. Keeps the two concerns auditable and independent. - No change to
handle_new_user()— new profiles inherit the column DEFAULTpending. Existing accounts were backfilled toapprovedin the same migration so the gate does not lock out current users on deploy. - Both unlock mechanisms (per product requirement): a magic link emailed on
approval and a redeemable invite code. Both end at
access_status='approved'. - Codes are admin-only at the table level; redemption goes through a
SECURITY DEFINERRPC so apendinguser (who can’t readinvite_codesnor flip their own gate under RLS) can still self-approve with a valid code. - Admin actions via Next.js API routes, not an Edge Function — consistent
with the existing
web/app/api/admin/*pattern (service_roleclient +admin_actions_log+ Resend), see overview.
Architecture Notes
Two converging paths, both ending at access_status='approved':
- Direct signup → profile born
pending→ admin approves from the dashboard → approval email (magic link + personal code). - Whitelist lead (
waitlist/partner_leads) → admin approves the lead →invite_codesrow minted, invite email sent → user registers (bornpending) and redeems the code → approved. The partner’sroleflows into the code’sintended_role. On redemption the originating lead is advanced to its converted state (waitlist → joined,partner_leads → won) byredeem_invite_code()— migration20260628120000.
Mobile gate implemented
Client-side enforcement now lives in flow-mobile (
feat/beta-access-gate): GoRouter redirect + lock screen + invite-code redemption + Realtime unlock. See feat-beta-access-gate.
Required env
NEXT_PUBLIC_APP_INVITE_URL(deep link base), plus the existingRESEND_API_KEY/WAITLIST_FROM_EMAILused by the marketing waitlist flow.
Admin API surface (web/app/api/admin/access/)
| Route | Method | Purpose |
|---|---|---|
profiles | GET | List accounts by access_status (paginated, search) |
profiles/[id] | POST | {action:'approve'|'reject'} — flip gate, mint code, email |
leads | GET / POST | Triage waitlist/partner leads; approve → invite + email |
invites | GET / POST | List / mint codes (optional email binding, role, max_uses) |
invites/[id] | DELETE | Revoke (soft → status='revoked') |
All gated by getAdminUserId() and logged to admin_actions_log.
Related
- database-schema —
profiles.access_status,invite_codes,redeem_invite_code() - security-bypass-log — the
redeem_invite_codeSECURITY DEFINER bypass - 2026-04-27-architecture-refactor-master-guide — RLS / migration standards followed here