尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Multi-tenant team admin with RBAC and immutable audit log (Next.js + Supabase)
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Crewspace is a multi-tenant team workspace where organizations manage who can do what — members, teams, custom roles with a granular permission matrix, and an immutable audit trail of every privileged action. The core loop is invite → assign → verify: an owner creates a workspace, invites teammates by email, assigns roles whose permissions are explicit and discoverable, and every change lands in an append-only audit log.
This is a portfolio flagship built to demonstrate production-grade RBAC engineering: multi-tenant data isolation via PostgreSQL Row Level Security, defense-in-depth authorization (UI gating → server re-check → RLS), realtime presence, and a tokenized invitation lifecycle.
One outcome metric: authorization p95 ≤ 50 ms — the 95th-percentile latency of the full permission-check path (
user_permissions()resolution plus therequireOrgAccessassertion inside a Server Action). The[orgSlug]layout resolves permissions once per request; RLS carries per-row enforcement with zero extra round trips.
org-logos / avatars buckets, signed URLsBrowser → Next.js Server Action → requireOrgAccess(orgId, permission) → RLS → Postgres
│
▼
user_permissions(org_id, user_id)
SECURITY DEFINER SQL function returning
the union of permission keys — called once
per request in the [orgSlug] layout,
cached per request, never cached grants
Three enforcement layers (threat model assumes an authenticated low-privilege member):
usePermissions(), with plain-language explanations.requireOrgAccess, which re-reads the user, reloads the membership, and re-asserts the permission. No raw Supabase client creation in handlers.auth.uid() → active memberships → org_id. A forged request can never outrun the user's real grants.Database-level guardrails (fire even for service-role connections): prevent_last_owner_loss() trigger (last owner can't be demoted/removed), membership_update_guard() (column-level: role_id changes need members:change_role, is_active needs members:deactivate, never on one's own row), audit_log_no_modify() (audit history is immutable — no UPDATE/DELETE policy exists).
PermissionMatrix UI (resource × action grid, keyboard-operable, dirty-state review bar with diff + affected-member count); role wizard; "My access" transparency panelPrerequisites: Node 22+, pnpm 10+, a Supabase project (or the Supabase CLI).
pnpm install
cp .env.example .env.local # fill in your Supabase URL + keys
# Apply migrations: supabase/migrations/00001_init.sql … 00014_transfer_ownership.sql
# (in order — later files reference objects created earlier)
# Then run supabase/seed.sql for the "Hale & Fern Studio" demo org
pnpm dev
Environment variables (see .env.example): NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY (server only — never in client bundles; CI fails the build if it appears there), RESEND_API_KEY (invitation emails; currently stubbed — accept URLs log in dev).
| Gate | Result |
|---|---|
tsc --noEmit | 0 errors |
eslint | 0 errors, 0 warnings |
vitest run | 18 files, 250 tests, all passing |
next build | 26/26 pages |
| Bundle gate | app shell ~9 KB gzipped (≤180 KB budget) |
Test highlights: 50-test RLS static-analysis suite (RLS enabled on all 10 tenant tables, audit_log has zero update/delete policies, self-role-edit guards, all 18 permission keys seeded, migration-ordering regression tests), invitation state machine (replay/tamper/cross-org/expiry), permission-diff computation, audit-sentence formatters for all 26 audited actions, ownership-transfer validation.
invitation.sent → invitation.accepted → membership.role_changed, each with actor, diff, and IP.role_id vs is_active needing different permissions) can't live in policies alone; it needs the trigger layer (membership_update_guard).SECURITY DEFINER check must actually be definer. role_org_matches_membership() was caught during review: roles RLS hides cross-org roles, so a non-definer check would pass vacuously. Regression-tested."use server" modules — pure constants live in sibling modules (the same trap, twice: invitations constants and transfer validation).user_permissions() — role changes take effect on the member's next fetch, no stale sessions.Built with AI assistance (multi-agent: foundation, invitations, RBAC, directory/teams, audit/settings workers + a QA/hardening pass), with the six planning documents (PRD, technical requirements, app flow, design brief, database schema, implementation plan) as the source of truth. All AI output was reviewed against the specs: every privileged action traced to an audit producer, every RLS policy asserted by the static suite, and the full gate suite (typecheck, lint, 250 tests, build) run before commit. The design follows the project's own design brief (calm B2B admin, indigo accent, mobile-first at 390px).
supabase db push against a scratch project first.sendInvitationEmail() logs the accept URL in dev) until RESEND_API_KEY is wired.