尚未偵測到更新。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.