Synaplan
기업을 위한 오픈소스 AI 플랫폼
Crewspace는 Sevyn Labs의 인원·권한 관리 레지스트리
Crewspace is the people-and-permissions register for Sevyn Labs: who is in it, which team they work in, what their role lets them do, and a signed record of every change.
비즈니스TypeScript · PLpgSQLGitHub @xoppersk온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기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.