ui-syncup 0.3.13 → 0.4.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/ai-spec-workflow/SKILL.md +58 -0
- package/.agents/skills/ai-spec-workflow/references/AI_SPECIFICATION_WORKFLOW.md +1434 -0
- package/.agents/skills/ai-spec-workflow/references/templates/design-template.md +729 -0
- package/.agents/skills/ai-spec-workflow/references/templates/requirements-template.md +179 -0
- package/.agents/skills/ai-spec-workflow/references/templates/tasks-template.md +501 -0
- package/.agents/skills/animation-designer/SKILL.md +688 -0
- package/.agents/skills/animation-designer/manifest.yaml +44 -0
- package/.agents/skills/brainstorming/SKILL.md +54 -0
- package/.agents/skills/contract-driven-ui/SKILL.md +270 -0
- package/.agents/skills/dispatching-parallel-agents/SKILL.md +180 -0
- package/.agents/skills/executing-plans/SKILL.md +76 -0
- package/.agents/skills/executing-specs/SKILL.md +53 -0
- package/.agents/skills/finishing-a-development-branch/SKILL.md +200 -0
- package/.agents/skills/github-workflow-automation/SKILL.md +846 -0
- package/.agents/skills/react-best-practices/AGENTS.md +2249 -0
- package/.agents/skills/react-best-practices/README.md +123 -0
- package/.agents/skills/react-best-practices/SKILL.md +121 -0
- package/.agents/skills/react-best-practices/metadata.json +15 -0
- package/.agents/skills/react-best-practices/rules/_sections.md +46 -0
- package/.agents/skills/react-best-practices/rules/_template.md +28 -0
- package/.agents/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/.agents/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/.agents/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/.agents/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/.agents/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/.agents/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/.agents/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/.agents/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/.agents/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/.agents/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/.agents/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/.agents/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/.agents/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/.agents/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/.agents/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/.agents/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/.agents/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/.agents/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/.agents/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/.agents/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/.agents/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/.agents/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/.agents/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/.agents/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/.agents/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/.agents/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/.agents/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/.agents/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/.agents/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/.agents/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/.agents/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/.agents/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/.agents/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/.agents/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/.agents/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/.agents/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/.agents/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/.agents/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/.agents/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/.agents/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/.agents/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/.agents/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/.agents/skills/react-best-practices/rules/server-cache-react.md +26 -0
- package/.agents/skills/react-best-practices/rules/server-parallel-fetching.md +79 -0
- package/.agents/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/.agents/skills/react-ui-patterns/SKILL.md +289 -0
- package/.agents/skills/receiving-code-review/SKILL.md +213 -0
- package/.agents/skills/requesting-code-review/SKILL.md +105 -0
- package/.agents/skills/requesting-code-review/code-reviewer.md +146 -0
- package/.agents/skills/reviewing-code/SKILL.md +28 -0
- package/.agents/skills/shadcn/SKILL.md +240 -0
- package/.agents/skills/shadcn/agents/openai.yml +5 -0
- package/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
- package/.agents/skills/shadcn/assets/shadcn.png +0 -0
- package/.agents/skills/shadcn/cli.md +255 -0
- package/.agents/skills/shadcn/customization.md +202 -0
- package/.agents/skills/shadcn/evals/evals.json +47 -0
- package/.agents/skills/shadcn/mcp.md +94 -0
- package/.agents/skills/shadcn/rules/base-vs-radix.md +306 -0
- package/.agents/skills/shadcn/rules/composition.md +195 -0
- package/.agents/skills/shadcn/rules/forms.md +192 -0
- package/.agents/skills/shadcn/rules/icons.md +101 -0
- package/.agents/skills/shadcn/rules/styling.md +162 -0
- package/.agents/skills/steering-creation/SKILL.md +221 -0
- package/.agents/skills/steering-creation/references/STEERING_CREATION_INSTRUCTION.md +850 -0
- package/.agents/skills/subagent-driven-development/SKILL.md +240 -0
- package/.agents/skills/subagent-driven-development/code-quality-reviewer-prompt.md +20 -0
- package/.agents/skills/subagent-driven-development/implementer-prompt.md +78 -0
- package/.agents/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/.agents/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/.agents/skills/systematic-debugging/SKILL.md +296 -0
- package/.agents/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/.agents/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/.agents/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/.agents/skills/systematic-debugging/find-polluter.sh +63 -0
- package/.agents/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/.agents/skills/systematic-debugging/test-academic.md +14 -0
- package/.agents/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/.agents/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/.agents/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/.agents/skills/test-driven-development/SKILL.md +371 -0
- package/.agents/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/.agents/skills/using-git-worktrees/SKILL.md +217 -0
- package/.agents/skills/using-superpowers/SKILL.md +87 -0
- package/.agents/skills/verification-before-completion/SKILL.md +139 -0
- package/.agents/skills/web-design-guidelines/SKILL.md +36 -0
- package/.agents/skills/writing-plans/SKILL.md +116 -0
- package/.agents/skills/writing-skills/SKILL.md +655 -0
- package/.agents/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/.agents/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/.agents/skills/writing-skills/persuasion-principles.md +187 -0
- package/.agents/skills/writing-skills/render-graphs.js +168 -0
- package/.agents/skills/writing-skills/testing-skills-with-subagents.md +384 -0
- package/.ai/steering/product.md +51 -0
- package/.ai/steering/structure.md +275 -0
- package/.ai/steering/tech.md +188 -0
- package/.claude/agents/database-architect.md +96 -0
- package/.claude/agents/deployment-pipeline-architect.md +122 -0
- package/.claude/agents/nextjs-expert.md +69 -0
- package/.claude/agents/ui-design-expert.md +106 -0
- package/.claudeignore +69 -0
- package/.dockerignore +8 -0
- package/.env.development +86 -0
- package/.env.example +171 -0
- package/.env.production +139 -0
- package/.env.test +58 -0
- package/.gitattributes +2 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +20 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +23 -0
- package/.github/workflows/ci.yml +64 -0
- package/.github/workflows/release.yml +174 -0
- package/.nvmrc +1 -0
- package/.releaserc.json +18 -0
- package/.vercelignore +73 -0
- package/AGENTS.md +544 -0
- package/CHANGELOG.md +69 -0
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +32 -0
- package/Dockerfile +84 -0
- package/LICENSE +21 -0
- package/README.md +328 -59
- package/SECURITY.md +16 -0
- package/bun.lock +3853 -0
- package/cli/README.md +94 -0
- package/cli/bun.lock +306 -0
- package/cli/index.ts +96 -0
- package/cli/package-lock.json +2157 -0
- package/cli/package.json +30 -0
- package/cli/src/commands/backup.ts +78 -0
- package/cli/src/commands/doctor.ts +82 -0
- package/cli/src/commands/init.ts +234 -0
- package/cli/src/commands/logs.ts +26 -0
- package/cli/src/commands/open.ts +23 -0
- package/cli/src/commands/remove.ts +44 -0
- package/cli/src/commands/restart.ts +21 -0
- package/cli/src/commands/restore.ts +90 -0
- package/cli/src/commands/start.ts +26 -0
- package/cli/src/commands/status.ts +25 -0
- package/cli/src/commands/stop.ts +20 -0
- package/cli/src/commands/upgrade.ts +28 -0
- package/cli/src/lib/docker.ts +40 -0
- package/cli/src/lib/env.ts +42 -0
- package/cli/src/lib/ui.ts +43 -0
- package/cli/tsconfig.json +13 -0
- package/cli/tsup.config.ts +12 -0
- package/components.json +24 -0
- package/docker/README.md +430 -0
- package/docker/compose.dev-minio.yml +30 -0
- package/docker/compose.dev.yml +39 -0
- package/docker/compose.local.yml +84 -0
- package/docker/compose.yml +153 -0
- package/docs/VERSIONING.md +101 -0
- package/docs/database/DRIZZLE_COMMANDS_EXPLAINED.md +1779 -0
- package/docs/database/DRIZZLE_ZOD_POSTGRESQL_INSTRUCTION.md +646 -0
- package/docs/database/MIGRATION_BEST_PRACTICES.md +601 -0
- package/docs/database/MIGRATION_ROLLBACK.md +1080 -0
- package/docs/database/MIGRATION_SYSTEM.md +165 -0
- package/docs/database/MIGRATION_TROUBLESHOOTING.md +881 -0
- package/docs/development/ENVIRONMENT_CONFIG.md +896 -0
- package/docs/development/LOCAL_DEVELOPMENT.md +456 -0
- package/docs/development/REMOTE_DATABASE_SETUP.md +786 -0
- package/docs/development/STORAGE_SETUP.md +207 -0
- package/docs/development/SUPABASE_LOCAL_SETUP.md +178 -0
- package/docs/development/TESTING.md +714 -0
- package/docs/feature-architectures/LOADING_ARCHITECTURE.md +343 -0
- package/docs/feature-architectures/NOTIFICATION_ARCHITECTURE.md +858 -0
- package/docs/feature-architectures/RATE_LIMIT_RESET.md +147 -0
- package/docs/feature-architectures/RBAC.md +1132 -0
- package/docs/feature-architectures/RESOURCE_LIMITS.md +69 -0
- package/docs/feature-architectures/SECURITY.md +284 -0
- package/docs/feature-architectures/WORKSPACES.md +278 -0
- package/docs/plans/admin-setup-wizard-routing-plan.md +623 -0
- package/drizzle/0000_purple_wilson_fisk.sql +360 -0
- package/drizzle/0001_drop_instance_public_url.sql +1 -0
- package/drizzle/meta/0000_snapshot.json +3118 -0
- package/drizzle/meta/_journal.json +20 -0
- package/drizzle.config.ts +13 -0
- package/eslint.config.mjs +44 -0
- package/install.sh +180 -0
- package/next.config.ts +91 -0
- package/package.json +128 -22
- package/playwright.config.ts +70 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/logo.svg +11 -0
- package/public/next.svg +1 -0
- package/public/playground/CPM-101/as-is-image.jpg +0 -0
- package/public/playground/CPM-101/to-be-image.jpg +0 -0
- package/public/playground/TEST-1/LinkedIn-skeleton-screen.png +0 -0
- package/public/playground/TEST-1/https___dev-to-uploads.s3.amazonaws.com_uploads_articles_vuahe90ka1mkx9aepmea.webp +0 -0
- package/public/playground/TEST-1/linkedin_skeletonscreen.jpg +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/__tests__/migrate.integration.test.ts +642 -0
- package/scripts/__tests__/migrate.property.test.ts +1714 -0
- package/scripts/__tests__/migrate.test.ts +536 -0
- package/scripts/admin-reset-password.ts +114 -0
- package/scripts/check-email-queue.ts +99 -0
- package/scripts/check-sessions.ts +50 -0
- package/scripts/db-pull-data.sh +73 -0
- package/scripts/force-verify-email.sh +13 -0
- package/scripts/migrate.ts +693 -0
- package/scripts/process-email-queue.ts +26 -0
- package/scripts/reset-db.ts +47 -0
- package/scripts/reset-rate-limit.sh +26 -0
- package/scripts/reset-remote-db.sql +31 -0
- package/scripts/retry-failed-emails.ts +67 -0
- package/scripts/seed.ts +605 -0
- package/scripts/setup-monitoring.sh +440 -0
- package/scripts/sync-migration-tracking.ts +113 -0
- package/scripts/test-ci-error-handling.sh +237 -0
- package/scripts/test-ci-workflow.sh +200 -0
- package/scripts/test-migration.sh +151 -0
- package/scripts/validate-env.ts +25 -0
- package/scripts/validate-migration-system.ts +566 -0
- package/scripts/verify-ci-status-reporting.sh +206 -0
- package/scripts/verify-user-email.sql +22 -0
- package/scripts/verify-vercel-integration.ts +292 -0
- package/seed_data.md +54 -0
- package/src/app/(protected)/(team)/(routes)/[projectSlug]/error.tsx +89 -0
- package/src/app/(protected)/(team)/(routes)/[projectSlug]/loading.tsx +101 -0
- package/src/app/(protected)/(team)/(routes)/[projectSlug]/page.tsx +91 -0
- package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/README.md +192 -0
- package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/error.tsx +58 -0
- package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/loading.tsx +14 -0
- package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/not-found.tsx +47 -0
- package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/page.tsx +91 -0
- package/src/app/(protected)/(team)/projects/page.tsx +16 -0
- package/src/app/(protected)/(team)/team/settings/(section)/instance/page.tsx +52 -0
- package/src/app/(protected)/(team)/team/settings/(section)/integrations/loading.tsx +5 -0
- package/src/app/(protected)/(team)/team/settings/(section)/integrations/page.tsx +23 -0
- package/src/app/(protected)/(team)/team/settings/(section)/members/loading.tsx +5 -0
- package/src/app/(protected)/(team)/team/settings/(section)/members/page.tsx +35 -0
- package/src/app/(protected)/(team)/team/settings/layout.tsx +72 -0
- package/src/app/(protected)/(team)/team/settings/loading.tsx +5 -0
- package/src/app/(protected)/(team)/team/settings/page.tsx +71 -0
- package/src/app/(protected)/dev/auth/README.md +151 -0
- package/src/app/(protected)/dev/auth/page.tsx +590 -0
- package/src/app/(protected)/layout.test.tsx +209 -0
- package/src/app/(protected)/layout.tsx +28 -0
- package/src/app/(protected)/onboarding/page.tsx +27 -0
- package/src/app/(protected)/settings/integrations/page.tsx +23 -0
- package/src/app/(protected)/settings/layout.tsx +26 -0
- package/src/app/(protected)/settings/notifications/page.tsx +26 -0
- package/src/app/(protected)/settings/other/page.tsx +23 -0
- package/src/app/(protected)/settings/page.tsx +23 -0
- package/src/app/(protected)/settings/preferences/page.tsx +23 -0
- package/src/app/(protected)/settings/security/page.tsx +37 -0
- package/src/app/(public)/forgot-password/page.tsx +20 -0
- package/src/app/(public)/invite/project/[token]/error.tsx +50 -0
- package/src/app/(public)/invite/project/[token]/loading.tsx +39 -0
- package/src/app/(public)/invite/project/[token]/page.tsx +156 -0
- package/src/app/(public)/layout.tsx +9 -0
- package/src/app/(public)/privacy-policy/page.tsx +12 -0
- package/src/app/(public)/reset-password/page.tsx +37 -0
- package/src/app/(public)/setup/__tests__/page.test.tsx +30 -0
- package/src/app/(public)/setup/page.tsx +17 -0
- package/src/app/(public)/share/issue/[token]/page.tsx +51 -0
- package/src/app/(public)/sign-in/page.tsx +55 -0
- package/src/app/(public)/sign-up/page.tsx +23 -0
- package/src/app/(public)/verify-email/page.tsx +22 -0
- package/src/app/(public)/verify-email-confirm/page.tsx +40 -0
- package/src/app/api/auth/[...all]/route.ts +6 -0
- package/src/app/api/auth/delete-account/route.ts +134 -0
- package/src/app/api/auth/dev/force-verify/route.ts +180 -0
- package/src/app/api/auth/dev/reset-rate-limit/route.ts +144 -0
- package/src/app/api/auth/dev/sessions/route.ts +172 -0
- package/src/app/api/auth/forgot-password/__tests__/forgot-password.property.test.ts +397 -0
- package/src/app/api/auth/forgot-password/route.ts +277 -0
- package/src/app/api/auth/logout/route.ts +115 -0
- package/src/app/api/auth/me/route.ts +123 -0
- package/src/app/api/auth/providers/__tests__/route.test.ts +236 -0
- package/src/app/api/auth/providers/route.ts +119 -0
- package/src/app/api/auth/resend-verification/route.ts +262 -0
- package/src/app/api/auth/reset-password/__tests__/reset-password.property.test.ts +493 -0
- package/src/app/api/auth/reset-password/__tests__/route.test.ts +284 -0
- package/src/app/api/auth/reset-password/route.ts +251 -0
- package/src/app/api/auth/verify-email/route.ts +232 -0
- package/src/app/api/example-cors/route.ts +61 -0
- package/src/app/api/health/route.ts +14 -0
- package/src/app/api/invite/project/[token]/__tests__/accept-invitation.integration.test.ts +348 -0
- package/src/app/api/invite/project/[token]/decline/route.ts +99 -0
- package/src/app/api/invite/project/[token]/route.ts +269 -0
- package/src/app/api/issues/[issueId]/activities/route.ts +213 -0
- package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/comments/[commentId]/route.ts +486 -0
- package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/comments/route.ts +283 -0
- package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/read/route.ts +242 -0
- package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/route.ts +534 -0
- package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/route.ts +514 -0
- package/src/app/api/issues/[issueId]/attachments/[attachmentId]/route.ts +161 -0
- package/src/app/api/issues/[issueId]/attachments/route.ts +376 -0
- package/src/app/api/issues/[issueId]/route.ts +516 -0
- package/src/app/api/notifications/[id]/read/route.ts +131 -0
- package/src/app/api/notifications/__tests__/notifications.integration.test.ts +350 -0
- package/src/app/api/notifications/read-all/route.ts +72 -0
- package/src/app/api/notifications/route.ts +148 -0
- package/src/app/api/notifications/unread-count/route.ts +77 -0
- package/src/app/api/projects/[id]/activities/route.ts +174 -0
- package/src/app/api/projects/[id]/invitations/[invitationId]/resend/route.ts +99 -0
- package/src/app/api/projects/[id]/invitations/[invitationId]/route.ts +96 -0
- package/src/app/api/projects/[id]/invitations/route.ts +254 -0
- package/src/app/api/projects/[id]/issues/route.ts +452 -0
- package/src/app/api/projects/[id]/join/route.ts +207 -0
- package/src/app/api/projects/[id]/members/[memberId]/route.ts +364 -0
- package/src/app/api/projects/[id]/members/me/route.ts +121 -0
- package/src/app/api/projects/[id]/members/route.ts +129 -0
- package/src/app/api/projects/[id]/route.ts +476 -0
- package/src/app/api/projects/route.ts +394 -0
- package/src/app/api/setup/admin/route.ts +255 -0
- package/src/app/api/setup/complete/__tests__/route.test.ts +60 -0
- package/src/app/api/setup/complete/route.ts +244 -0
- package/src/app/api/setup/config/route.ts +195 -0
- package/src/app/api/setup/export/route.ts +111 -0
- package/src/app/api/setup/health/route.ts +74 -0
- package/src/app/api/setup/import/route.ts +154 -0
- package/src/app/api/setup/status/route.ts +82 -0
- package/src/app/api/setup/workspace/route.ts +252 -0
- package/src/app/api/teams/[teamId]/export/route.ts +115 -0
- package/src/app/api/teams/[teamId]/invitations/[invitationId]/resend/route.ts +132 -0
- package/src/app/api/teams/[teamId]/invitations/[invitationId]/route.ts +117 -0
- package/src/app/api/teams/[teamId]/invitations/route.ts +363 -0
- package/src/app/api/teams/[teamId]/members/[userId]/route.ts +335 -0
- package/src/app/api/teams/[teamId]/members/route.ts +184 -0
- package/src/app/api/teams/[teamId]/members/search/route.ts +202 -0
- package/src/app/api/teams/[teamId]/route.ts +423 -0
- package/src/app/api/teams/[teamId]/switch/route.ts +140 -0
- package/src/app/api/teams/[teamId]/transfer-ownership/route.ts +212 -0
- package/src/app/api/teams/invitations/[token]/accept/route.ts +140 -0
- package/src/app/api/teams/invitations/by-id/[id]/accept/route.ts +98 -0
- package/src/app/api/teams/invitations/by-id/[id]/decline/route.ts +90 -0
- package/src/app/api/teams/route.ts +278 -0
- package/src/app/api/uploads/media/route.ts +118 -0
- package/src/app/api/uploads/presigned/route.ts +49 -0
- package/src/app/api/user/linked-accounts/route.ts +35 -0
- package/src/app/email-preview/page.tsx +11 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/global-error.tsx +21 -0
- package/src/app/layout.tsx +50 -0
- package/src/app/page.tsx +5 -0
- package/src/components/icons/atlassian-icon.tsx +22 -0
- package/src/components/icons/index.ts +1 -0
- package/src/components/layout/SIDEBAR_LAYOUT_BEST_PRACTICES.md +240 -0
- package/src/components/layout/app-shell-header-store.tsx +20 -0
- package/src/components/layout/app-shell-skeleton.tsx +89 -0
- package/src/components/layout/app-shell-wrapper.tsx +32 -0
- package/src/components/layout/app-shell.test.tsx +155 -0
- package/src/components/layout/app-shell.tsx +100 -0
- package/src/components/shared/headers/app-header-configurator.tsx +42 -0
- package/src/components/shared/headers/app-header.tsx +103 -0
- package/src/components/shared/headers/header-user-menu.tsx +247 -0
- package/src/components/shared/headers/index.ts +44 -0
- package/src/components/shared/headers/page-header.tsx +25 -0
- package/src/components/shared/notifications/__tests__/notification-bell.test.tsx +159 -0
- package/src/components/shared/notifications/__tests__/notification-dropdown.test.tsx +296 -0
- package/src/components/shared/notifications/__tests__/notification-item.test.tsx +328 -0
- package/src/components/shared/notifications/index.ts +45 -0
- package/src/components/shared/notifications/notification-actions.tsx +295 -0
- package/src/components/shared/notifications/notification-bell-button.tsx +77 -0
- package/src/components/shared/notifications/notification-dropdown.tsx +160 -0
- package/src/components/shared/notifications/notification-group-item.tsx +268 -0
- package/src/components/shared/notifications/notification-item.tsx +193 -0
- package/src/components/shared/notifications/notification-load-more.tsx +50 -0
- package/src/components/shared/notifications/notification-panel.tsx +49 -0
- package/src/components/shared/notifications/utils.tsx +127 -0
- package/src/components/shared/permission-guard/index.ts +1 -0
- package/src/components/shared/permission-guard/permission-tooltip.tsx +45 -0
- package/src/components/shared/relative-time.tsx +53 -0
- package/src/components/shared/section-container.tsx +32 -0
- package/src/components/shared/service-status-banner.tsx +121 -0
- package/src/components/shared/settings-sidebar/index.ts +2 -0
- package/src/components/shared/settings-sidebar/team-setting-aside.tsx +97 -0
- package/src/components/shared/settings-sidebar/user-settings-aside.tsx +66 -0
- package/src/components/shared/sidebar/app-sidebar.tsx +146 -0
- package/src/components/shared/sidebar/index.ts +36 -0
- package/src/components/shared/sidebar/sidebar-main.tsx +81 -0
- package/src/components/shared/sidebar/sidebar-project.tsx +61 -0
- package/src/components/shared/sidebar/sidebar-team-avatar.tsx +126 -0
- package/src/components/shared/sidebar/sidebar-team-switcher.tsx +185 -0
- package/src/components/shared/sidebar/type.ts +97 -0
- package/src/components/ui/alert-dialog.tsx +157 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/avatar-upload.tsx +147 -0
- package/src/components/ui/avatar.tsx +53 -0
- package/src/components/ui/badge.tsx +46 -0
- package/src/components/ui/breadcrumb.tsx +109 -0
- package/src/components/ui/button.tsx +60 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/checkbox.tsx +32 -0
- package/src/components/ui/collapsible.tsx +33 -0
- package/src/components/ui/command.tsx +184 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/empty.tsx +104 -0
- package/src/components/ui/field.tsx +244 -0
- package/src/components/ui/image-cropper-dialog.tsx +167 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/optimized-image.tsx +220 -0
- package/src/components/ui/pagination.tsx +127 -0
- package/src/components/ui/popover.tsx +48 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/radio-group.tsx +45 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +187 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +139 -0
- package/src/components/ui/sidebar.tsx +733 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +40 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/table.tsx +116 -0
- package/src/components/ui/tabs.tsx +66 -0
- package/src/components/ui/textarea.tsx +23 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/config/__tests__/workspace.property.test.ts +40 -0
- package/src/config/auth.ts +62 -0
- package/src/config/integrations.ts +126 -0
- package/src/config/quotas.ts +20 -0
- package/src/config/roles.ts +463 -0
- package/src/config/settings-nav.ts +39 -0
- package/src/config/team-settings-nav.ts +37 -0
- package/src/config/user-settings-nav.ts +42 -0
- package/src/config/version.ts +1 -0
- package/src/config/workspace.ts +64 -0
- package/src/features/annotations/README.md +283 -0
- package/src/features/annotations/api/annotations-api.ts +194 -0
- package/src/features/annotations/api/comments-api.ts +147 -0
- package/src/features/annotations/api/index.ts +71 -0
- package/src/features/annotations/api/save-annotation.ts +150 -0
- package/src/features/annotations/api/schemas.ts +142 -0
- package/src/features/annotations/components/annotated-attachment-view.tsx +576 -0
- package/src/features/annotations/components/annotation-action-sheet.tsx +140 -0
- package/src/features/annotations/components/annotation-annotations-panel.tsx +213 -0
- package/src/features/annotations/components/annotation-box.tsx +539 -0
- package/src/features/annotations/components/annotation-canvas.tsx +534 -0
- package/src/features/annotations/components/annotation-comment-input.tsx +145 -0
- package/src/features/annotations/components/annotation-context-menu.tsx +164 -0
- package/src/features/annotations/components/annotation-drawer.tsx +231 -0
- package/src/features/annotations/components/annotation-layer.tsx +271 -0
- package/src/features/annotations/components/annotation-pin.tsx +318 -0
- package/src/features/annotations/components/annotation-popover.tsx +562 -0
- package/src/features/annotations/components/annotation-thread-panel.tsx +485 -0
- package/src/features/annotations/components/annotation-thread-preview.tsx +195 -0
- package/src/features/annotations/components/annotation-toolbar.tsx +244 -0
- package/src/features/annotations/components/keyboard-shortcuts-modal.tsx +79 -0
- package/src/features/annotations/docs/ANNOTATIONS_ARCHITECTURE.md +67 -0
- package/src/features/annotations/docs/ANNOTATION_SAVE_ARCHITECTURE.md +422 -0
- package/src/features/annotations/docs/ANNOTATION_SAVE_FEATURE.md +408 -0
- package/src/features/annotations/docs/BOX_ANNOTATION_GUIDE.md +542 -0
- package/src/features/annotations/docs/NEXTSTEP.md +28 -0
- package/src/features/annotations/docs/STALE_CLOSURE_FIX.md +344 -0
- package/src/features/annotations/docs/UNDO_REDO_QUICK_START.md +545 -0
- package/src/features/annotations/docs/local_first_canvas_autosave_architecture.md +674 -0
- package/src/features/annotations/examples/complete-example.tsx +266 -0
- package/src/features/annotations/examples/save-annotation-example.tsx +309 -0
- package/src/features/annotations/hooks/__tests__/use-annotation-permissions.property.test.tsx +493 -0
- package/src/features/annotations/hooks/index.ts +36 -0
- package/src/features/annotations/hooks/use-annotation-batch-save.ts +109 -0
- package/src/features/annotations/hooks/use-annotation-comments.ts +353 -0
- package/src/features/annotations/hooks/use-annotation-drafts.ts +137 -0
- package/src/features/annotations/hooks/use-annotation-edit-state.ts +99 -0
- package/src/features/annotations/hooks/use-annotation-history-tracker.ts +159 -0
- package/src/features/annotations/hooks/use-annotation-integration.ts +916 -0
- package/src/features/annotations/hooks/use-annotation-permissions.ts +210 -0
- package/src/features/annotations/hooks/use-annotation-popover.ts +175 -0
- package/src/features/annotations/hooks/use-annotation-save.ts +208 -0
- package/src/features/annotations/hooks/use-annotation-tools.ts +237 -0
- package/src/features/annotations/hooks/use-annotations-with-history.ts +332 -0
- package/src/features/annotations/hooks/use-auto-save.ts +94 -0
- package/src/features/annotations/index.ts +111 -0
- package/src/features/annotations/types/annotation.ts +201 -0
- package/src/features/annotations/types/index.ts +28 -0
- package/src/features/annotations/utils/history-manager.ts +73 -0
- package/src/features/annotations/utils/index.ts +2 -0
- package/src/features/annotations/utils/map-attachments-to-threads.ts +28 -0
- package/src/features/annotations/utils/position-comment-input.ts +136 -0
- package/src/features/annotations/utils/re-sequence-labels.ts +92 -0
- package/src/features/annotations/utils/validate-annotation-label.ts +120 -0
- package/src/features/auth/api/types.ts +101 -0
- package/src/features/auth/components/__tests__/role-gate.test.tsx +448 -0
- package/src/features/auth/components/__tests__/social-login-buttons.test.tsx +313 -0
- package/src/features/auth/components/auth-card.tsx +36 -0
- package/src/features/auth/components/forgot-password-form.tsx +115 -0
- package/src/features/auth/components/index.ts +14 -0
- package/src/features/auth/components/invite-code-input.tsx +155 -0
- package/src/features/auth/components/invited-user-form.tsx +309 -0
- package/src/features/auth/components/onboarding-form.tsx +195 -0
- package/src/features/auth/components/password-strength-indicator.tsx +113 -0
- package/src/features/auth/components/reset-password-form.tsx +138 -0
- package/src/features/auth/components/role-gate.tsx +124 -0
- package/src/features/auth/components/self-registration-choice.tsx +153 -0
- package/src/features/auth/components/sign-in-form.tsx +159 -0
- package/src/features/auth/components/sign-up-form.tsx +158 -0
- package/src/features/auth/components/social-login-buttons.tsx +219 -0
- package/src/features/auth/hooks/__tests__/use-onboarding.test.tsx +109 -0
- package/src/features/auth/hooks/__tests__/use-session.test.tsx +160 -0
- package/src/features/auth/hooks/index.ts +15 -0
- package/src/features/auth/hooks/use-accept-invitation.ts +194 -0
- package/src/features/auth/hooks/use-delete-account.ts +86 -0
- package/src/features/auth/hooks/use-force-verify.ts +89 -0
- package/src/features/auth/hooks/use-forgot-password.ts +144 -0
- package/src/features/auth/hooks/use-link-account.ts +78 -0
- package/src/features/auth/hooks/use-linked-accounts.ts +88 -0
- package/src/features/auth/hooks/use-onboarding.ts +159 -0
- package/src/features/auth/hooks/use-resend-verification.ts +139 -0
- package/src/features/auth/hooks/use-reset-password.ts +151 -0
- package/src/features/auth/hooks/use-reset-rate-limit.ts +56 -0
- package/src/features/auth/hooks/use-self-registration.ts +202 -0
- package/src/features/auth/hooks/use-session.ts +81 -0
- package/src/features/auth/hooks/use-sessions.ts +59 -0
- package/src/features/auth/hooks/use-sign-in.ts +234 -0
- package/src/features/auth/hooks/use-sign-out.ts +88 -0
- package/src/features/auth/hooks/use-sign-up.ts +194 -0
- package/src/features/auth/hooks/use-unlink-account.ts +100 -0
- package/src/features/auth/hooks/use-verify-email-token.ts +125 -0
- package/src/features/auth/index.ts +75 -0
- package/src/features/auth/screens/forgot-password-screen.tsx +33 -0
- package/src/features/auth/screens/index.ts +7 -0
- package/src/features/auth/screens/onboarding-screen.tsx +49 -0
- package/src/features/auth/screens/reset-password-screen.tsx +33 -0
- package/src/features/auth/screens/sign-in-screen.tsx +61 -0
- package/src/features/auth/screens/sign-up-screen.tsx +37 -0
- package/src/features/auth/screens/verify-email-confirm-screen.tsx +286 -0
- package/src/features/auth/screens/verify-email-screen.tsx +146 -0
- package/src/features/auth/types/index.ts +14 -0
- package/src/features/auth/utils/__tests__/validators.test.ts +331 -0
- package/src/features/auth/utils/password-strength.ts +129 -0
- package/src/features/auth/utils/validators.ts +124 -0
- package/src/features/email-preview/actions/render-email.ts +21 -0
- package/src/features/email-preview/screens/email-preview-screen.tsx +81 -0
- package/src/features/folder-scaffold-template/index.ts +0 -0
- package/src/features/instance-settings/components/index.ts +6 -0
- package/src/features/instance-settings/components/instance-settings-form.tsx +180 -0
- package/src/features/instance-settings/components/instance-status-display.tsx +158 -0
- package/src/features/instance-settings/index.ts +7 -0
- package/src/features/instance-settings/screens/index.ts +5 -0
- package/src/features/instance-settings/screens/instance-settings-screen.tsx +59 -0
- package/src/features/issues/README.md +330 -0
- package/src/features/issues/api/create-issue.ts +19 -0
- package/src/features/issues/api/delete-issue.ts +27 -0
- package/src/features/issues/api/get-issue-activities.ts +58 -0
- package/src/features/issues/api/get-issue-details.ts +25 -0
- package/src/features/issues/api/get-project-issues-server.ts +44 -0
- package/src/features/issues/api/get-project-issues.ts +21 -0
- package/src/features/issues/api/index.ts +44 -0
- package/src/features/issues/api/update-issue.ts +31 -0
- package/src/features/issues/api/upload-attachment.ts +81 -0
- package/src/features/issues/components/activity-timeline.tsx +440 -0
- package/src/features/issues/components/canvas-state-indicator.tsx +90 -0
- package/src/features/issues/components/centered-canvas-view.tsx +739 -0
- package/src/features/issues/components/image-selector.tsx +123 -0
- package/src/features/issues/components/image-upload-zone.tsx +262 -0
- package/src/features/issues/components/infinite-canvas-background.tsx +163 -0
- package/src/features/issues/components/inline-editable-select.tsx +173 -0
- package/src/features/issues/components/inline-editable-text.tsx +225 -0
- package/src/features/issues/components/inline-editable-textarea.tsx +219 -0
- package/src/features/issues/components/inline-editable-user-select.tsx +202 -0
- package/src/features/issues/components/issue-deletion-dialog.tsx +142 -0
- package/src/features/issues/components/issue-details-panel.tsx +101 -0
- package/src/features/issues/components/issues-create-dialog.tsx +578 -0
- package/src/features/issues/components/issues-list-filter.tsx +312 -0
- package/src/features/issues/components/issues-list.tsx +151 -0
- package/src/features/issues/components/issues-priority-badge.tsx +77 -0
- package/src/features/issues/components/issues-status-badge.tsx +100 -0
- package/src/features/issues/components/metadata-section.tsx +389 -0
- package/src/features/issues/components/optimized-attachment-view.tsx +528 -0
- package/src/features/issues/components/optimized-image.tsx +257 -0
- package/src/features/issues/components/panel-header.tsx +186 -0
- package/src/features/issues/components/preload.ts +31 -0
- package/src/features/issues/components/priority-selector.tsx +101 -0
- package/src/features/issues/components/responsive-issue-layout-skeleton.tsx +139 -0
- package/src/features/issues/components/responsive-issue-layout.tsx +617 -0
- package/src/features/issues/components/status-selector.tsx +320 -0
- package/src/features/issues/components/type-selector.tsx +102 -0
- package/src/features/issues/components/upload-progress-overlay.tsx +35 -0
- package/src/features/issues/components/uploaded-image-preview.tsx +173 -0
- package/src/features/issues/components/workflow-control.tsx +318 -0
- package/src/features/issues/components/zoom-controls.tsx +150 -0
- package/src/features/issues/config/index.ts +47 -0
- package/src/features/issues/config/options.ts +323 -0
- package/src/features/issues/config/workflow.ts +102 -0
- package/src/features/issues/docs/ARCHITECTURE_DIAGRAM.md +321 -0
- package/src/features/issues/docs/BACKEND_ARCHITECTURE.md +194 -0
- package/src/features/issues/docs/IMAGE_COMPONENTS_ARCHITECTURE.md +363 -0
- package/src/features/issues/docs/IMAGE_COMPONENTS_IMPORTS.md +412 -0
- package/src/features/issues/docs/IMPLEMENTATION_CHECKLIST.md +210 -0
- package/src/features/issues/docs/ROUTE_SETUP_COMPLETE.md +242 -0
- package/src/features/issues/hooks/index.ts +78 -0
- package/src/features/issues/hooks/use-canvas-transform.ts +255 -0
- package/src/features/issues/hooks/use-create-issue.ts +28 -0
- package/src/features/issues/hooks/use-elastic-scroll.ts +296 -0
- package/src/features/issues/hooks/use-issue-activities.ts +71 -0
- package/src/features/issues/hooks/use-issue-delete.ts +84 -0
- package/src/features/issues/hooks/use-issue-details.ts +70 -0
- package/src/features/issues/hooks/use-issue-filters.ts +50 -0
- package/src/features/issues/hooks/use-issue-update.ts +93 -0
- package/src/features/issues/hooks/use-keyboard-shortcuts.ts +104 -0
- package/src/features/issues/hooks/use-optimized-image.ts +228 -0
- package/src/features/issues/hooks/use-project-issues.ts +14 -0
- package/src/features/issues/index.ts +65 -0
- package/src/features/issues/screens/issue-details-screen.tsx +207 -0
- package/src/features/issues/screens/issue-details-skeletons.tsx +295 -0
- package/src/features/issues/screens/issue-share-screen.tsx +56 -0
- package/src/features/issues/types/index.ts +48 -0
- package/src/features/issues/types/issue.ts +291 -0
- package/src/features/issues/utils/filter-issues.ts +141 -0
- package/src/features/issues/utils/index.ts +14 -0
- package/src/features/legal/index.ts +1 -0
- package/src/features/legal/screens/privacy-policy-screen.tsx +307 -0
- package/src/features/notifications/api/get-notifications.ts +58 -0
- package/src/features/notifications/api/get-unread-count.ts +37 -0
- package/src/features/notifications/api/index.ts +35 -0
- package/src/features/notifications/api/mark-all-as-read.ts +37 -0
- package/src/features/notifications/api/mark-as-read.ts +41 -0
- package/src/features/notifications/api/types.ts +109 -0
- package/src/features/notifications/hooks/__tests__/use-notification-subscription.test.ts +206 -0
- package/src/features/notifications/hooks/index.ts +28 -0
- package/src/features/notifications/hooks/use-mark-all-as-read.ts +106 -0
- package/src/features/notifications/hooks/use-mark-as-read.ts +106 -0
- package/src/features/notifications/hooks/use-notification-subscription.ts +244 -0
- package/src/features/notifications/hooks/use-notification-toast.ts +161 -0
- package/src/features/notifications/hooks/use-notifications.ts +80 -0
- package/src/features/notifications/hooks/use-unread-count.ts +60 -0
- package/src/features/notifications/index.ts +48 -0
- package/src/features/notifications/utils/group-notifications.ts +152 -0
- package/src/features/notifications/utils/index.ts +9 -0
- package/src/features/projects/api/create-invitation.ts +45 -0
- package/src/features/projects/api/create-project.ts +64 -0
- package/src/features/projects/api/delete-project.ts +50 -0
- package/src/features/projects/api/get-project-activities.ts +43 -0
- package/src/features/projects/api/get-project-members.ts +53 -0
- package/src/features/projects/api/get-project.ts +49 -0
- package/src/features/projects/api/get-projects.ts +61 -0
- package/src/features/projects/api/index.ts +27 -0
- package/src/features/projects/api/join-project.ts +52 -0
- package/src/features/projects/api/leave-project.ts +51 -0
- package/src/features/projects/api/list-invitations.ts +36 -0
- package/src/features/projects/api/remove-member.ts +60 -0
- package/src/features/projects/api/resend-invitation.ts +36 -0
- package/src/features/projects/api/revoke-invitation.ts +36 -0
- package/src/features/projects/api/types.ts +286 -0
- package/src/features/projects/api/update-member-role.ts +70 -0
- package/src/features/projects/api/update-project.ts +69 -0
- package/src/features/projects/components/__tests__/project-detail-activity-feed.test.tsx +106 -0
- package/src/features/projects/components/__tests__/project-invitation-dialog.test.tsx +211 -0
- package/src/features/projects/components/__tests__/project-member-manager-dialog.test.tsx +254 -0
- package/src/features/projects/components/index.ts +21 -0
- package/src/features/projects/components/project-actions.tsx +248 -0
- package/src/features/projects/components/project-create-dialog.tsx +410 -0
- package/src/features/projects/components/project-detail-activity-feed.tsx +206 -0
- package/src/features/projects/components/project-detail-header.tsx +103 -0
- package/src/features/projects/components/project-detail-overview.tsx +128 -0
- package/src/features/projects/components/project-icon-selector.test.tsx +49 -0
- package/src/features/projects/components/project-icon-selector.tsx +76 -0
- package/src/features/projects/components/project-invitation-dialog.tsx +368 -0
- package/src/features/projects/components/project-issues.tsx +128 -0
- package/src/features/projects/components/project-leave-button.tsx +69 -0
- package/src/features/projects/components/project-list-card.tsx +246 -0
- package/src/features/projects/components/project-list-filters.tsx +320 -0
- package/src/features/projects/components/project-member-manager-dialog.tsx +419 -0
- package/src/features/projects/components/project-settings-dialog.tsx +204 -0
- package/src/features/projects/components/project-stats.tsx +46 -0
- package/src/features/projects/components/project-title-section.tsx +78 -0
- package/src/features/projects/config/icons.ts +91 -0
- package/src/features/projects/hooks/index.ts +28 -0
- package/src/features/projects/hooks/use-create-invitation.ts +83 -0
- package/src/features/projects/hooks/use-create-project.ts +77 -0
- package/src/features/projects/hooks/use-delete-project.ts +84 -0
- package/src/features/projects/hooks/use-join-project.ts +43 -0
- package/src/features/projects/hooks/use-leave-project.ts +84 -0
- package/src/features/projects/hooks/use-project-activities.ts +39 -0
- package/src/features/projects/hooks/use-project-filters.ts +86 -0
- package/src/features/projects/hooks/use-project-invitations.ts +66 -0
- package/src/features/projects/hooks/use-project-members.ts +57 -0
- package/src/features/projects/hooks/use-project.ts +67 -0
- package/src/features/projects/hooks/use-projects.ts +49 -0
- package/src/features/projects/hooks/use-recent-projects.ts +58 -0
- package/src/features/projects/hooks/use-remove-member.ts +89 -0
- package/src/features/projects/hooks/use-resend-invitation.ts +68 -0
- package/src/features/projects/hooks/use-revoke-invitation.ts +71 -0
- package/src/features/projects/hooks/use-team-member-suggestions.ts +133 -0
- package/src/features/projects/hooks/use-update-member-role.ts +92 -0
- package/src/features/projects/hooks/use-update-project.ts +88 -0
- package/src/features/projects/index.ts +91 -0
- package/src/features/projects/screens/index.ts +3 -0
- package/src/features/projects/screens/invitation-acceptance-screen.tsx +320 -0
- package/src/features/projects/screens/project-detail-screen-wrapper.tsx +47 -0
- package/src/features/projects/screens/project-detail-screen.tsx +661 -0
- package/src/features/projects/screens/projects-list-screen.tsx +161 -0
- package/src/features/projects/types/index.ts +59 -0
- package/src/features/projects/utils/format-helpers.ts +16 -0
- package/src/features/projects/utils/index.ts +2 -0
- package/src/features/projects/utils/role-helpers.ts +25 -0
- package/src/features/setup/api/complete-setup.ts +21 -0
- package/src/features/setup/api/create-admin.ts +21 -0
- package/src/features/setup/api/create-first-workspace.ts +21 -0
- package/src/features/setup/api/get-instance-status.ts +12 -0
- package/src/features/setup/api/get-service-health.ts +12 -0
- package/src/features/setup/api/index.ts +44 -0
- package/src/features/setup/api/save-instance-config.ts +21 -0
- package/src/features/setup/api/types.ts +122 -0
- package/src/features/setup/components/__tests__/setup-wizard-ui.test.tsx +362 -0
- package/src/features/setup/components/admin-account-step.tsx +205 -0
- package/src/features/setup/components/first-workspace-step.tsx +120 -0
- package/src/features/setup/components/index.ts +9 -0
- package/src/features/setup/components/instance-config-step.tsx +107 -0
- package/src/features/setup/components/mail-config-step.tsx +205 -0
- package/src/features/setup/components/sample-data-step.tsx +131 -0
- package/src/features/setup/components/service-health-step.tsx +180 -0
- package/src/features/setup/components/service-status-badge.tsx +50 -0
- package/src/features/setup/components/setup-progress.tsx +103 -0
- package/src/features/setup/components/setup-wizard.tsx +169 -0
- package/src/features/setup/hooks/index.ts +12 -0
- package/src/features/setup/hooks/use-complete-setup.ts +23 -0
- package/src/features/setup/hooks/use-create-admin.ts +25 -0
- package/src/features/setup/hooks/use-create-first-workspace.ts +24 -0
- package/src/features/setup/hooks/use-instance-status.ts +21 -0
- package/src/features/setup/hooks/use-save-instance-config.ts +23 -0
- package/src/features/setup/hooks/use-service-health.ts +21 -0
- package/src/features/setup/hooks/use-setup-wizard.ts +152 -0
- package/src/features/setup/hooks/use-workspace-mode.ts +19 -0
- package/src/features/setup/index.ts +30 -0
- package/src/features/setup/screens/index.ts +1 -0
- package/src/features/setup/screens/setup-screen.tsx +40 -0
- package/src/features/setup/types/index.ts +69 -0
- package/src/features/setup/utils/index.ts +78 -0
- package/src/features/team-settings/components/index.ts +39 -0
- package/src/features/team-settings/components/loading-states.tsx +296 -0
- package/src/features/team-settings/components/permission-guard.tsx +23 -0
- package/src/features/team-settings/components/settings-card.tsx +22 -0
- package/src/features/team-settings/components/settings-context-provider.tsx +51 -0
- package/src/features/team-settings/components/settings-error-boundary.tsx +366 -0
- package/src/features/team-settings/components/settings-navigation.tsx +87 -0
- package/src/features/team-settings/components/settings-section.tsx +23 -0
- package/src/features/team-settings/components/team-danger-zone.tsx +275 -0
- package/src/features/team-settings/components/team-information-form.tsx +116 -0
- package/src/features/team-settings/components/team-invitations-list.tsx +463 -0
- package/src/features/team-settings/components/team-members-list.tsx +342 -0
- package/src/features/team-settings/components/team-permission-guard.tsx +56 -0
- package/src/features/team-settings/components/team-setting-integrations.tsx +27 -0
- package/src/features/team-settings/components/team-setting-member.tsx +28 -0
- package/src/features/team-settings/components/team-settings-general.tsx +131 -0
- package/src/features/team-settings/components/transfer-ownership-modal.tsx +164 -0
- package/src/features/team-settings/components/unauthorized-access.tsx +52 -0
- package/src/features/team-settings/hooks/__tests__/use-team-settings.test.tsx +139 -0
- package/src/features/team-settings/hooks/index.ts +1 -0
- package/src/features/team-settings/hooks/use-team-settings.ts +148 -0
- package/src/features/team-settings/hooks/use-transfer-ownership.ts +45 -0
- package/src/features/team-settings/index.ts +25 -0
- package/src/features/team-settings/screens/index.ts +1 -0
- package/src/features/team-settings/screens/team-settings-screen.tsx +33 -0
- package/src/features/team-settings/types/index.ts +78 -0
- package/src/features/team-settings/utils/index.ts +6 -0
- package/src/features/team-settings/utils/mock-data.ts +40 -0
- package/src/features/teams/api/cancel-invitation.ts +25 -0
- package/src/features/teams/api/create-invitation.ts +28 -0
- package/src/features/teams/api/create-team.ts +14 -0
- package/src/features/teams/api/delete-team.ts +19 -0
- package/src/features/teams/api/get-invitations.ts +25 -0
- package/src/features/teams/api/get-team-members.ts +25 -0
- package/src/features/teams/api/get-team.ts +13 -0
- package/src/features/teams/api/get-teams.ts +19 -0
- package/src/features/teams/api/index.ts +49 -0
- package/src/features/teams/api/leave-team.ts +22 -0
- package/src/features/teams/api/remove-member.ts +25 -0
- package/src/features/teams/api/resend-invitation.ts +26 -0
- package/src/features/teams/api/switch-team.ts +13 -0
- package/src/features/teams/api/types.ts +122 -0
- package/src/features/teams/api/update-member-roles.ts +28 -0
- package/src/features/teams/api/update-team.ts +17 -0
- package/src/features/teams/components/create-team-dialog.tsx +105 -0
- package/src/features/teams/hooks/__tests__/cache-invalidation.property.test.tsx +268 -0
- package/src/features/teams/hooks/index.ts +35 -0
- package/src/features/teams/hooks/use-can-manage-members.ts +21 -0
- package/src/features/teams/hooks/use-can-manage-team.ts +21 -0
- package/src/features/teams/hooks/use-cancel-invitation.ts +52 -0
- package/src/features/teams/hooks/use-create-invitation.ts +59 -0
- package/src/features/teams/hooks/use-create-team.ts +38 -0
- package/src/features/teams/hooks/use-delete-team.ts +43 -0
- package/src/features/teams/hooks/use-invitations.ts +31 -0
- package/src/features/teams/hooks/use-leave-team.ts +43 -0
- package/src/features/teams/hooks/use-remove-member.ts +58 -0
- package/src/features/teams/hooks/use-resend-invitation.ts +52 -0
- package/src/features/teams/hooks/use-switch-team.ts +41 -0
- package/src/features/teams/hooks/use-team-members.ts +31 -0
- package/src/features/teams/hooks/use-team-permissions.ts +102 -0
- package/src/features/teams/hooks/use-team.ts +30 -0
- package/src/features/teams/hooks/use-teams.ts +26 -0
- package/src/features/teams/hooks/use-update-member-roles.ts +64 -0
- package/src/features/teams/hooks/use-update-team.ts +47 -0
- package/src/features/teams/index.ts +111 -0
- package/src/features/user-settings/actions/set-password.ts +63 -0
- package/src/features/user-settings/api/index.ts +16 -0
- package/src/features/user-settings/components/__tests__/security-settings.test.tsx +125 -0
- package/src/features/user-settings/components/delete-account-dialog.tsx +185 -0
- package/src/features/user-settings/components/index.ts +13 -0
- package/src/features/user-settings/components/integrations-list.tsx +152 -0
- package/src/features/user-settings/components/notification-preferences.tsx +112 -0
- package/src/features/user-settings/components/other-settings.tsx +126 -0
- package/src/features/user-settings/components/password-section.tsx +297 -0
- package/src/features/user-settings/components/security-settings.tsx +184 -0
- package/src/features/user-settings/components/user-preferences.tsx +146 -0
- package/src/features/user-settings/hooks/index.ts +8 -0
- package/src/features/user-settings/hooks/use-notification-preferences.ts +65 -0
- package/src/features/user-settings/hooks/use-user-preferences.ts +52 -0
- package/src/features/user-settings/index.ts +22 -0
- package/src/features/user-settings/screens/index.ts +11 -0
- package/src/features/user-settings/screens/integrations-screen.tsx +24 -0
- package/src/features/user-settings/screens/notifications-screen.tsx +29 -0
- package/src/features/user-settings/screens/other-settings-screen.tsx +24 -0
- package/src/features/user-settings/screens/setting-preferences-screen.tsx +24 -0
- package/src/features/user-settings/screens/user-settings-screen.tsx +23 -0
- package/src/features/user-settings/types/index.ts +42 -0
- package/src/features/user-settings/utils/index.ts +8 -0
- package/src/hooks/use-long-press.ts +196 -0
- package/src/hooks/use-media-upload.ts +95 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/use-team.ts +32 -0
- package/src/instrumentation.ts +14 -0
- package/src/lib/__tests__/auth-config.test.ts +166 -0
- package/src/lib/__tests__/config.test.ts +42 -0
- package/src/lib/__tests__/db.test.ts +101 -0
- package/src/lib/__tests__/env.property.test.ts +197 -0
- package/src/lib/__tests__/env.test.ts +177 -0
- package/src/lib/__tests__/health-check.test.ts +87 -0
- package/src/lib/__tests__/oauth-errors.test.ts +184 -0
- package/src/lib/__tests__/oauth-redirect.test.ts +224 -0
- package/src/lib/__tests__/oauth-scopes.test.ts +268 -0
- package/src/lib/__tests__/storage.test.ts +58 -0
- package/src/lib/__tests__/url-validator.test.ts +232 -0
- package/src/lib/api-client.ts +93 -0
- package/src/lib/auth-client.ts +5 -0
- package/src/lib/auth-config.ts +146 -0
- package/src/lib/auth.ts +209 -0
- package/src/lib/config.ts +228 -0
- package/src/lib/cors.ts +96 -0
- package/src/lib/date.ts +16 -0
- package/src/lib/db.ts +88 -0
- package/src/lib/env.ts +489 -0
- package/src/lib/feedback.ts +29 -0
- package/src/lib/health-check.ts +229 -0
- package/src/lib/logger.ts +204 -0
- package/src/lib/oauth-errors.ts +138 -0
- package/src/lib/performance.ts +367 -0
- package/src/lib/query-server.ts +35 -0
- package/src/lib/query.tsx +43 -0
- package/src/lib/resend.ts +36 -0
- package/src/lib/security-headers.ts +165 -0
- package/src/lib/setup-status.ts +34 -0
- package/src/lib/storage.ts +150 -0
- package/src/lib/supabase-client.ts +58 -0
- package/src/lib/testing/test-db.ts +75 -0
- package/src/lib/url-validator.ts +168 -0
- package/src/lib/utils.ts +6 -0
- package/src/mocks/README.md +42 -0
- package/src/mocks/activity.fixtures.ts +281 -0
- package/src/mocks/annotation.fixtures.ts +325 -0
- package/src/mocks/attachment.fixtures.ts +80 -0
- package/src/mocks/email.fixtures.ts +61 -0
- package/src/mocks/index.ts +33 -0
- package/src/mocks/issue.fixtures.ts +364 -0
- package/src/mocks/landing.fixtures.ts +118 -0
- package/src/mocks/notification.fixtures.ts +111 -0
- package/src/mocks/project-activity.fixtures.ts +69 -0
- package/src/mocks/project-invitation.fixtures.ts +95 -0
- package/src/mocks/project-member.fixtures.ts +93 -0
- package/src/mocks/project.fixtures.ts +249 -0
- package/src/mocks/settings.fixtures.ts +56 -0
- package/src/mocks/share.fixtures.ts +48 -0
- package/src/mocks/team-member.fixtures.ts +147 -0
- package/src/mocks/team.fixtures.ts +35 -0
- package/src/mocks/user-settings.fixtures.ts +77 -0
- package/src/mocks/user.fixtures.ts +21 -0
- package/src/providers/theme-provider.tsx +11 -0
- package/src/proxy/__tests__/proxy-setup-gate.test.ts +43 -0
- package/src/proxy.ts +169 -0
- package/src/server/annotations/__tests__/annotation-limit.property.test.ts +291 -0
- package/src/server/annotations/__tests__/attachment-deletion-cascade.property.test.ts +385 -0
- package/src/server/annotations/__tests__/comment-delete-authorization.property.test.ts +419 -0
- package/src/server/annotations/__tests__/sanitize.property.test.ts +302 -0
- package/src/server/annotations/annotation-service.ts +305 -0
- package/src/server/annotations/comment-service.ts +288 -0
- package/src/server/annotations/index.ts +61 -0
- package/src/server/annotations/permission-utils.ts +125 -0
- package/src/server/annotations/sanitize.ts +134 -0
- package/src/server/annotations/types.ts +161 -0
- package/src/server/audit/audit-service.ts +85 -0
- package/src/server/audit/index.ts +11 -0
- package/src/server/audit/types.ts +75 -0
- package/src/server/auth/__tests__/README.md +368 -0
- package/src/server/auth/__tests__/account-linking.integration.test.ts +410 -0
- package/src/server/auth/__tests__/auth-integration.test.ts +811 -0
- package/src/server/auth/__tests__/cookies.test.ts +337 -0
- package/src/server/auth/__tests__/login.property.test.ts +428 -0
- package/src/server/auth/__tests__/oauth-integration.test.ts +555 -0
- package/src/server/auth/__tests__/password.test.ts +194 -0
- package/src/server/auth/__tests__/rate-limiter.test.ts +450 -0
- package/src/server/auth/__tests__/rbac.test.ts +474 -0
- package/src/server/auth/__tests__/session.test.ts +599 -0
- package/src/server/auth/__tests__/signup.property.test.ts +224 -0
- package/src/server/auth/__tests__/token-encryption.test.ts +171 -0
- package/src/server/auth/__tests__/tokens.test.ts +476 -0
- package/src/server/auth/__tests__/verify-email.property.test.ts +372 -0
- package/src/server/auth/cookies.ts +184 -0
- package/src/server/auth/password.ts +94 -0
- package/src/server/auth/rate-limiter.ts +257 -0
- package/src/server/auth/rbac.ts +1168 -0
- package/src/server/auth/session.ts +392 -0
- package/src/server/auth/token-encryption.ts +201 -0
- package/src/server/auth/tokens.ts +397 -0
- package/src/server/db/schema/account.ts +21 -0
- package/src/server/db/schema/annotation-read-status.ts +57 -0
- package/src/server/db/schema/better-auth-verifications.ts +27 -0
- package/src/server/db/schema/email-jobs.ts +24 -0
- package/src/server/db/schema/index.ts +20 -0
- package/src/server/db/schema/instance-settings.ts +42 -0
- package/src/server/db/schema/issue-activities.ts +97 -0
- package/src/server/db/schema/issue-attachments.ts +101 -0
- package/src/server/db/schema/issues.ts +139 -0
- package/src/server/db/schema/notifications.ts +119 -0
- package/src/server/db/schema/project-activities.ts +116 -0
- package/src/server/db/schema/project-invitations.ts +34 -0
- package/src/server/db/schema/project-members.ts +29 -0
- package/src/server/db/schema/projects.ts +46 -0
- package/src/server/db/schema/sessions.ts +17 -0
- package/src/server/db/schema/team-invitations.ts +22 -0
- package/src/server/db/schema/team-members.ts +18 -0
- package/src/server/db/schema/teams.ts +15 -0
- package/src/server/db/schema/user-roles.ts +14 -0
- package/src/server/db/schema/users.ts +17 -0
- package/src/server/db/schema/verification-tokens.ts +15 -0
- package/src/server/email/README.md +258 -0
- package/src/server/email/__tests__/client.property.test.ts +218 -0
- package/src/server/email/__tests__/payload.property.test.ts +205 -0
- package/src/server/email/__tests__/queue.test.ts +407 -0
- package/src/server/email/__tests__/render-template.test.tsx +172 -0
- package/src/server/email/client.ts +70 -0
- package/src/server/email/index.ts +20 -0
- package/src/server/email/providers/console-provider.ts +43 -0
- package/src/server/email/providers/index.ts +5 -0
- package/src/server/email/providers/resend-provider.ts +59 -0
- package/src/server/email/providers/smtp-provider.ts +65 -0
- package/src/server/email/queue.ts +365 -0
- package/src/server/email/render-template.tsx +117 -0
- package/src/server/email/templates/layout.tsx +66 -0
- package/src/server/email/templates/ownership-transfer-email.tsx +75 -0
- package/src/server/email/templates/password-reset-email.tsx +72 -0
- package/src/server/email/templates/project-invitation-email.tsx +70 -0
- package/src/server/email/templates/security-alert-email.tsx +161 -0
- package/src/server/email/templates/team-invitation-email.tsx +68 -0
- package/src/server/email/templates/verification-email.tsx +61 -0
- package/src/server/email/templates/welcome-email.tsx +60 -0
- package/src/server/email/worker.ts +182 -0
- package/src/server/issues/activity-service.ts +422 -0
- package/src/server/issues/attachment-service.ts +379 -0
- package/src/server/issues/index.ts +68 -0
- package/src/server/issues/issue-service.ts +569 -0
- package/src/server/issues/types.ts +233 -0
- package/src/server/notifications/__tests__/notification-service.integration.test.ts +405 -0
- package/src/server/notifications/index.ts +13 -0
- package/src/server/notifications/notification-service.ts +526 -0
- package/src/server/notifications/types.ts +234 -0
- package/src/server/projects/__tests__/activity-logging.integration.test.ts +319 -0
- package/src/server/projects/__tests__/invitation-email.integration.test.ts +258 -0
- package/src/server/projects/__tests__/invitation-service.integration.test.ts +651 -0
- package/src/server/projects/__tests__/member-service.property.test.ts +397 -0
- package/src/server/projects/__tests__/project-service.property.test.ts +116 -0
- package/src/server/projects/activity-service.ts +548 -0
- package/src/server/projects/index.ts +11 -0
- package/src/server/projects/invitation-service.ts +773 -0
- package/src/server/projects/member-service.ts +458 -0
- package/src/server/projects/project-service.ts +491 -0
- package/src/server/projects/schemas.ts +310 -0
- package/src/server/projects/types.ts +166 -0
- package/src/server/projects/utils.ts +128 -0
- package/src/server/setup/__tests__/health-check.property.test.ts +70 -0
- package/src/server/setup/__tests__/sample-data.property.test.ts +82 -0
- package/src/server/setup/__tests__/setup.property.test.ts +95 -0
- package/src/server/setup/health-check-service.ts +294 -0
- package/src/server/setup/index.ts +35 -0
- package/src/server/setup/sample-data-service.ts +233 -0
- package/src/server/setup/setup-service.ts +229 -0
- package/src/server/setup/types.ts +96 -0
- package/src/server/teams/__tests__/export.property.test.ts +542 -0
- package/src/server/teams/__tests__/get-members.integration.test.ts +105 -0
- package/src/server/teams/__tests__/invitation-flow.integration.test.ts +402 -0
- package/src/server/teams/__tests__/invitations.property.test.ts +235 -0
- package/src/server/teams/__tests__/member-management-flow.integration.test.ts +306 -0
- package/src/server/teams/__tests__/members.property.test.ts +180 -0
- package/src/server/teams/__tests__/ownership-transfer.property.test.ts +173 -0
- package/src/server/teams/__tests__/resource-limits.property.test.ts +382 -0
- package/src/server/teams/__tests__/team-context-management.integration.test.ts +396 -0
- package/src/server/teams/__tests__/team-context.property.test.ts +854 -0
- package/src/server/teams/__tests__/team-creation-flow.integration.test.ts +310 -0
- package/src/server/teams/__tests__/team-creation.property.test.ts +280 -0
- package/src/server/teams/errors.ts +396 -0
- package/src/server/teams/export-service.ts +383 -0
- package/src/server/teams/index.ts +10 -0
- package/src/server/teams/invitation-service.ts +708 -0
- package/src/server/teams/member-service.ts +334 -0
- package/src/server/teams/resource-limits.ts +211 -0
- package/src/server/teams/slug.ts +58 -0
- package/src/server/teams/team-context.ts +648 -0
- package/src/server/teams/team-service.ts +660 -0
- package/src/server/teams/types.ts +81 -0
- package/src/server/teams/validation.ts +209 -0
- package/src/styles/globals.css +160 -0
- package/src/types/deployment.ts +39 -0
- package/supabase/config.toml +357 -0
- package/supabase/seed.sql +480 -0
- package/tests/e2e/.gitkeep +0 -0
- package/tests/e2e/QUICK_START.md +98 -0
- package/tests/e2e/README.md +301 -0
- package/tests/e2e/auth.spec.ts +583 -0
- package/tests/e2e/global-setup.ts +23 -0
- package/tests/e2e/global-teardown.ts +23 -0
- package/tests/e2e/helpers/auth-helpers.ts +310 -0
- package/tests/e2e/helpers/test-fixtures.ts +286 -0
- package/tests/e2e/smoke-test.spec.ts +330 -0
- package/tsconfig.json +48 -0
- package/vitest.config.ts +50 -0
- package/vitest.setup.ts +56 -0
- package/dist/index.js +0 -778
|
@@ -0,0 +1,1132 @@
|
|
|
1
|
+
# Role-Based Access Control (RBAC) Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
UI SyncUp uses a comprehensive Role-Based Access Control (RBAC) system to manage user permissions across teams and projects. This document explains the role hierarchy, permissions model, and how to use the RBAC utilities in your code.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Role Hierarchy](#role-hierarchy)
|
|
10
|
+
- [Architecture](#architecture)
|
|
11
|
+
- [Permissions Model](#permissions-model)
|
|
12
|
+
- [Usage Examples](#usage-examples)
|
|
13
|
+
- [API Reference](#api-reference)
|
|
14
|
+
- [Best Practices](#best-practices)
|
|
15
|
+
- [Security Considerations](#security-considerations)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Architecture
|
|
20
|
+
|
|
21
|
+
### Role Storage (Single Source of Truth)
|
|
22
|
+
|
|
23
|
+
UI SyncUp uses a **consolidated role storage** pattern where each resource type has a single source of truth:
|
|
24
|
+
|
|
25
|
+
| Resource Type | Table | Columns |
|
|
26
|
+
|---------------|-------|---------|
|
|
27
|
+
| **Team** | `team_members` | `managementRole`, `operationalRole` |
|
|
28
|
+
| **Project** | `project_members` | `role` |
|
|
29
|
+
|
|
30
|
+
This ensures:
|
|
31
|
+
- ✅ No data inconsistency between tables
|
|
32
|
+
- ✅ Single write location for role changes
|
|
33
|
+
- ✅ Simpler permission queries
|
|
34
|
+
- ✅ Clear ownership of role data
|
|
35
|
+
|
|
36
|
+
### Permission Resolution
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
hasPermission(userId, permission, resourceType, resourceId)
|
|
40
|
+
│
|
|
41
|
+
├── resourceType = "project"
|
|
42
|
+
│ └── Query project_members → Check role permissions
|
|
43
|
+
│
|
|
44
|
+
└── resourceType = "team"
|
|
45
|
+
└── Query team_members → Check management + operational role permissions
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Role Hierarchy
|
|
50
|
+
|
|
51
|
+
### Team Roles (Two-Tier Hierarchy)
|
|
52
|
+
|
|
53
|
+
Team roles are split into **management roles** (control team settings) and **operational roles** (determine content access).
|
|
54
|
+
|
|
55
|
+
#### Management Roles
|
|
56
|
+
|
|
57
|
+
| Role | Description |
|
|
58
|
+
|------|-------------|
|
|
59
|
+
| **TEAM_OWNER** | Full control over team and members. Can transfer ownership and delete team. Must also have an operational role. |
|
|
60
|
+
| **TEAM_ADMIN** | Manage team members, projects, and integrations. Cannot delete team or transfer ownership. Must also have an operational role. |
|
|
61
|
+
|
|
62
|
+
#### Operational Roles (Determine Access)
|
|
63
|
+
|
|
64
|
+
| Role | Level | Description |
|
|
65
|
+
|------|-------|-------------|
|
|
66
|
+
| **TEAM_EDITOR** | 3 | Create and manage issues and annotations. Automatically assigned when user becomes PROJECT_OWNER or PROJECT_EDITOR. |
|
|
67
|
+
| **TEAM_MEMBER** | 2 | View projects and comment on issues. Can be assigned to projects. |
|
|
68
|
+
| **TEAM_VIEWER** | 1 | View-only access to projects and issues. No modifications. |
|
|
69
|
+
|
|
70
|
+
#### Role Combinations
|
|
71
|
+
|
|
72
|
+
Users have **one management role** (optional) + **one operational role** (required):
|
|
73
|
+
|
|
74
|
+
- `TEAM_OWNER` + `TEAM_EDITOR` → Owner who creates issues
|
|
75
|
+
- `TEAM_OWNER` + `TEAM_MEMBER` → Owner who only manages
|
|
76
|
+
- `TEAM_ADMIN` + `TEAM_VIEWER` → Admin with read-only
|
|
77
|
+
- `TEAM_EDITOR` (no management) → Designer/QA
|
|
78
|
+
- `TEAM_MEMBER` (no management) → Developer
|
|
79
|
+
|
|
80
|
+
### Project Roles
|
|
81
|
+
|
|
82
|
+
Use project roles to define permissions for specific projects.
|
|
83
|
+
|
|
84
|
+
| Role | Level | Description |
|
|
85
|
+
|------|-------|-------------|
|
|
86
|
+
| **PROJECT_OWNER** | 4 | Full control over project and its issues. Can manage project members. |
|
|
87
|
+
| **PROJECT_EDITOR** | 3 | Create and manage issues and annotations. Cannot manage project settings. |
|
|
88
|
+
| **PROJECT_DEVELOPER** | 2 | Update issue status and comment. Typical developer workflow. |
|
|
89
|
+
| **PROJECT_VIEWER** | 1 | View-only access to project and issues. No modifications. |
|
|
90
|
+
|
|
91
|
+
### Role Hierarchy Rules
|
|
92
|
+
|
|
93
|
+
1. **Management roles are separate from operational roles** - TEAM_OWNER/ADMIN control settings, operational roles control content access
|
|
94
|
+
2. **Users must have both a management role AND an operational role** if they need both capabilities
|
|
95
|
+
|
|
96
|
+
4. **Higher operational roles inherit lower role permissions** (EDITOR > MEMBER > VIEWER)
|
|
97
|
+
5. **Team roles do not automatically grant project roles** - users must be explicitly assigned to projects
|
|
98
|
+
6. **Project roles only apply to that specific project** - they don't grant access to other projects
|
|
99
|
+
7. **TEAM_OWNER and TEAM_ADMIN can access team settings** regardless of their operational role
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Permissions Model
|
|
104
|
+
|
|
105
|
+
### Permission Categories
|
|
106
|
+
|
|
107
|
+
Permissions are organized into four categories:
|
|
108
|
+
|
|
109
|
+
1. **Team Permissions** - Control team-level operations
|
|
110
|
+
2. **Project Permissions** - Control project-level operations
|
|
111
|
+
3. **Issue Permissions** - Control issue management
|
|
112
|
+
4. **Annotation Permissions** - Control annotation management
|
|
113
|
+
|
|
114
|
+
### Team Permissions
|
|
115
|
+
|
|
116
|
+
| Permission | Description | Roles |
|
|
117
|
+
|------------|-------------|-------|
|
|
118
|
+
| `team:view` | View team details and members | All team roles |
|
|
119
|
+
| `team:update` | Update team settings | OWNER, ADMIN |
|
|
120
|
+
| `team:delete` | Delete the team | OWNER only |
|
|
121
|
+
| `team:manage_members` | Add/remove team members | OWNER, ADMIN |
|
|
122
|
+
|
|
123
|
+
| `team:manage_settings` | Manage team settings | OWNER, ADMIN |
|
|
124
|
+
| `team:transfer_ownership` | Transfer team ownership | OWNER only |
|
|
125
|
+
|
|
126
|
+
### Project Permissions
|
|
127
|
+
|
|
128
|
+
| Permission | Description | Roles |
|
|
129
|
+
|------------|-------------|-------|
|
|
130
|
+
| `project:view` | View project details | All roles |
|
|
131
|
+
| `project:create` | Create new projects | TEAM_OWNER, TEAM_ADMIN, TEAM_EDITOR |
|
|
132
|
+
| `project:update` | Update project settings | TEAM_OWNER, TEAM_ADMIN, PROJECT_OWNER |
|
|
133
|
+
| `project:delete` | Delete the project | TEAM_OWNER, TEAM_ADMIN, PROJECT_OWNER |
|
|
134
|
+
| `project:manage_members` | Add/remove project members | TEAM_OWNER, TEAM_ADMIN, PROJECT_OWNER |
|
|
135
|
+
| `project:manage_settings` | Manage project settings | TEAM_OWNER, TEAM_ADMIN, PROJECT_OWNER |
|
|
136
|
+
|
|
137
|
+
### Issue Permissions
|
|
138
|
+
|
|
139
|
+
| Permission | Description | Roles |
|
|
140
|
+
|------------|-------------|-------|
|
|
141
|
+
| `issue:view` | View issues | All roles |
|
|
142
|
+
| `issue:create` | Create new issues | TEAM_OWNER, TEAM_ADMIN, TEAM_EDITOR, PROJECT_OWNER, PROJECT_EDITOR |
|
|
143
|
+
| `issue:update` | Update issue details and status | TEAM_OWNER, TEAM_ADMIN, TEAM_EDITOR, PROJECT_OWNER, PROJECT_EDITOR, PROJECT_DEVELOPER |
|
|
144
|
+
| `issue:delete` | Delete issues | TEAM_OWNER, TEAM_ADMIN, TEAM_EDITOR, PROJECT_OWNER, PROJECT_EDITOR |
|
|
145
|
+
| `issue:assign` | Assign issues to users | TEAM_OWNER, TEAM_ADMIN, TEAM_EDITOR, PROJECT_OWNER, PROJECT_EDITOR |
|
|
146
|
+
| `issue:comment` | Comment on issues | All roles except VIEWER |
|
|
147
|
+
|
|
148
|
+
### Annotation Permissions
|
|
149
|
+
|
|
150
|
+
| Permission | Description | Roles |
|
|
151
|
+
|------------|-------------|-------|
|
|
152
|
+
| `annotation:view` | View annotations | All roles |
|
|
153
|
+
| `annotation:create` | Create new annotations | TEAM_OWNER, TEAM_ADMIN, TEAM_EDITOR, PROJECT_OWNER, PROJECT_EDITOR |
|
|
154
|
+
| `annotation:update` | Update annotations | TEAM_OWNER, TEAM_ADMIN, TEAM_EDITOR, PROJECT_OWNER, PROJECT_EDITOR |
|
|
155
|
+
| `annotation:delete` | Delete annotations | TEAM_OWNER, TEAM_ADMIN, TEAM_EDITOR, PROJECT_OWNER, PROJECT_EDITOR |
|
|
156
|
+
| `annotation:comment` | Comment on annotations | All roles except VIEWER |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Usage Examples
|
|
165
|
+
|
|
166
|
+
### Assigning Roles
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { assignRole, TEAM_ROLES, PROJECT_ROLES } from '@/server/auth/rbac';
|
|
170
|
+
|
|
171
|
+
// Assign team owner role
|
|
172
|
+
await assignRole({
|
|
173
|
+
userId: 'user_123',
|
|
174
|
+
role: TEAM_ROLES.TEAM_OWNER,
|
|
175
|
+
resourceType: 'team',
|
|
176
|
+
resourceId: 'team_456',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Assign project editor role
|
|
180
|
+
await assignRole({
|
|
181
|
+
userId: 'user_789',
|
|
182
|
+
role: PROJECT_ROLES.PROJECT_EDITOR,
|
|
183
|
+
resourceType: 'project',
|
|
184
|
+
resourceId: 'project_abc',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Assign multiple roles at once (transactional)
|
|
188
|
+
await assignRoles([
|
|
189
|
+
{
|
|
190
|
+
userId: 'user_123',
|
|
191
|
+
role: TEAM_ROLES.TEAM_OWNER,
|
|
192
|
+
resourceType: 'team',
|
|
193
|
+
resourceId: 'team_456',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
userId: 'user_123',
|
|
197
|
+
role: PROJECT_ROLES.PROJECT_OWNER,
|
|
198
|
+
resourceType: 'project',
|
|
199
|
+
resourceId: 'project_abc',
|
|
200
|
+
},
|
|
201
|
+
]);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Checking Permissions
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { hasPermission, PERMISSIONS } from '@/server/auth/rbac';
|
|
208
|
+
|
|
209
|
+
// Check if user can create issues in a project
|
|
210
|
+
const canCreate = await hasPermission({
|
|
211
|
+
userId: 'user_123',
|
|
212
|
+
permission: PERMISSIONS.ISSUE_CREATE,
|
|
213
|
+
resourceId: 'project_456',
|
|
214
|
+
resourceType: 'project',
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (canCreate) {
|
|
218
|
+
// Allow issue creation
|
|
219
|
+
} else {
|
|
220
|
+
// Show error message
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check multiple permissions
|
|
224
|
+
import { hasAllPermissions, hasAnyPermission } from '@/server/auth/rbac';
|
|
225
|
+
|
|
226
|
+
// User must have ALL permissions
|
|
227
|
+
const canManageProject = await hasAllPermissions(
|
|
228
|
+
'user_123',
|
|
229
|
+
[PERMISSIONS.PROJECT_UPDATE, PERMISSIONS.PROJECT_MANAGE_MEMBERS],
|
|
230
|
+
'project_456',
|
|
231
|
+
'project'
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// User must have ANY permission
|
|
235
|
+
const canInteract = await hasAnyPermission(
|
|
236
|
+
'user_123',
|
|
237
|
+
[PERMISSIONS.ISSUE_CREATE, PERMISSIONS.ISSUE_COMMENT],
|
|
238
|
+
'project_456',
|
|
239
|
+
'project'
|
|
240
|
+
);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Requiring Permissions (Guards)
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { requirePermission, requireRole } from '@/server/auth/rbac';
|
|
247
|
+
|
|
248
|
+
// In API route handler
|
|
249
|
+
export async function POST(request: Request) {
|
|
250
|
+
const session = await getSession();
|
|
251
|
+
const { projectId } = await request.json();
|
|
252
|
+
|
|
253
|
+
// Throw error if user doesn't have permission
|
|
254
|
+
await requirePermission({
|
|
255
|
+
userId: session.userId,
|
|
256
|
+
permission: PERMISSIONS.ISSUE_CREATE,
|
|
257
|
+
resourceId: projectId,
|
|
258
|
+
resourceType: 'project',
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Continue with issue creation...
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Require specific role
|
|
265
|
+
await requireRole(
|
|
266
|
+
session.userId,
|
|
267
|
+
TEAM_ROLES.TEAM_ADMIN,
|
|
268
|
+
'team',
|
|
269
|
+
teamId
|
|
270
|
+
);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Querying User Roles
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import {
|
|
277
|
+
getUserRoles,
|
|
278
|
+
getUserTeamRoles,
|
|
279
|
+
getUserProjectRoles,
|
|
280
|
+
getHighestTeamRole,
|
|
281
|
+
getHighestProjectRole,
|
|
282
|
+
} from '@/server/auth/rbac';
|
|
283
|
+
|
|
284
|
+
// Get all roles for a user
|
|
285
|
+
const allRoles = await getUserRoles('user_123');
|
|
286
|
+
|
|
287
|
+
// Get team roles
|
|
288
|
+
const teamRoles = await getUserTeamRoles('user_123', 'team_456');
|
|
289
|
+
|
|
290
|
+
// Get project roles
|
|
291
|
+
const projectRoles = await getUserProjectRoles('user_123', 'project_789');
|
|
292
|
+
|
|
293
|
+
// Get highest role (for display purposes)
|
|
294
|
+
const highestTeamRole = await getHighestTeamRole('user_123', 'team_456');
|
|
295
|
+
// Returns: 'TEAM_OWNER' | 'TEAM_ADMIN' | ... | null
|
|
296
|
+
|
|
297
|
+
const highestProjectRole = await getHighestProjectRole('user_123', 'project_789');
|
|
298
|
+
// Returns: 'PROJECT_OWNER' | 'PROJECT_EDITOR' | ... | null
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
### Updating Roles
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { updateRole, removeRole, demoteWithOwnershipTransfer, getOwnedProjects } from '@/server/auth/rbac';
|
|
307
|
+
|
|
308
|
+
// Upgrade user's role (always safe)
|
|
309
|
+
await updateRole(
|
|
310
|
+
'user_123',
|
|
311
|
+
TEAM_ROLES.TEAM_MEMBER, // old role
|
|
312
|
+
TEAM_ROLES.TEAM_EDITOR, // new role
|
|
313
|
+
'team',
|
|
314
|
+
'team_456'
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// Demote user (may fail if they're PROJECT_OWNER)
|
|
318
|
+
try {
|
|
319
|
+
await updateRole(
|
|
320
|
+
'user_123',
|
|
321
|
+
TEAM_ROLES.TEAM_EDITOR, // old role
|
|
322
|
+
TEAM_ROLES.TEAM_VIEWER, // new role
|
|
323
|
+
'team',
|
|
324
|
+
'team_456'
|
|
325
|
+
);
|
|
326
|
+
} catch (error) {
|
|
327
|
+
if (error.message.includes('DEMOTION_BLOCKED')) {
|
|
328
|
+
// User is PROJECT_OWNER, need to transfer ownership first
|
|
329
|
+
const ownedProjects = await getOwnedProjects('user_123');
|
|
330
|
+
console.log('User owns projects:', ownedProjects);
|
|
331
|
+
// Show UI to select new owners
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Safe demotion with ownership transfer
|
|
336
|
+
await demoteWithOwnershipTransfer(
|
|
337
|
+
'user_123',
|
|
338
|
+
'team_456',
|
|
339
|
+
'TEAM_VIEWER',
|
|
340
|
+
{
|
|
341
|
+
'project_1': 'user_789', // Transfer project_1 to user_789
|
|
342
|
+
'project_2': 'user_789', // Transfer project_2 to user_789
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Remove a role
|
|
347
|
+
await removeRole({
|
|
348
|
+
userId: 'user_123',
|
|
349
|
+
role: PROJECT_ROLES.PROJECT_EDITOR,
|
|
350
|
+
resourceType: 'project',
|
|
351
|
+
resourceId: 'project_789',
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## API Reference
|
|
358
|
+
|
|
359
|
+
### Role Assignment Functions
|
|
360
|
+
|
|
361
|
+
#### `assignRole(assignment: RoleAssignment): Promise<UserRole>`
|
|
362
|
+
|
|
363
|
+
Assign a role to a user for a specific resource.
|
|
364
|
+
|
|
365
|
+
**Parameters:**
|
|
366
|
+
- `assignment.userId` - User ID
|
|
367
|
+
- `assignment.role` - Role to assign (from `TEAM_ROLES` or `PROJECT_ROLES`)
|
|
368
|
+
- `assignment.resourceType` - Resource type (`'team'` or `'project'`)
|
|
369
|
+
- `assignment.resourceId` - Resource ID
|
|
370
|
+
|
|
371
|
+
**Returns:** Created user role record
|
|
372
|
+
|
|
373
|
+
**Throws:** Error if role is invalid or doesn't match resource type
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
#### `assignRoles(assignments: RoleAssignment[]): Promise<UserRole[]>`
|
|
378
|
+
|
|
379
|
+
Assign multiple roles to a user at once (transactional).
|
|
380
|
+
|
|
381
|
+
**Parameters:**
|
|
382
|
+
- `assignments` - Array of role assignments
|
|
383
|
+
|
|
384
|
+
**Returns:** Array of created user role records
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
#### `removeRole(assignment: RoleAssignment): Promise<boolean>`
|
|
389
|
+
|
|
390
|
+
Remove a role from a user.
|
|
391
|
+
|
|
392
|
+
**Parameters:**
|
|
393
|
+
- `assignment` - Role assignment to remove
|
|
394
|
+
|
|
395
|
+
**Returns:** `true` if role was removed, `false` if it didn't exist
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
#### `updateRole(userId, oldRole, newRole, resourceType, resourceId): Promise<void>`
|
|
400
|
+
|
|
401
|
+
Update a user's role for a resource (atomic operation).
|
|
402
|
+
|
|
403
|
+
**Parameters:**
|
|
404
|
+
- `userId` - User ID
|
|
405
|
+
- `oldRole` - Current role to remove
|
|
406
|
+
- `newRole` - New role to assign
|
|
407
|
+
- `resourceType` - Resource type
|
|
408
|
+
- `resourceId` - Resource ID
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
### Role Query Functions
|
|
413
|
+
|
|
414
|
+
#### `getUserRoles(userId: string): Promise<UserRole[]>`
|
|
415
|
+
|
|
416
|
+
Get all roles for a user across all resources.
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
#### `getUserTeamRoles(userId: string, teamId: string): Promise<UserRole[]>`
|
|
421
|
+
|
|
422
|
+
Get all team roles for a user in a specific team.
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
#### `getUserProjectRoles(userId: string, projectId: string): Promise<UserRole[]>`
|
|
427
|
+
|
|
428
|
+
Get all project roles for a user in a specific project.
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
#### `getHighestTeamRole(userId: string, teamId: string): Promise<TeamRole | null>`
|
|
433
|
+
|
|
434
|
+
Get the highest team role for a user in a team (based on hierarchy).
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
#### `getHighestProjectRole(userId: string, projectId: string): Promise<ProjectRole | null>`
|
|
439
|
+
|
|
440
|
+
Get the highest project role for a user in a project (based on hierarchy).
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
#### `hasRole(userId, role, resourceType, resourceId): Promise<boolean>`
|
|
445
|
+
|
|
446
|
+
Check if a user has a specific role.
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
### Permission Check Functions
|
|
451
|
+
|
|
452
|
+
#### `hasPermission(check: PermissionCheck): Promise<boolean>`
|
|
453
|
+
|
|
454
|
+
Check if a user has a specific permission for a resource.
|
|
455
|
+
|
|
456
|
+
**Parameters:**
|
|
457
|
+
- `check.userId` - User ID
|
|
458
|
+
- `check.permission` - Permission to check (from `PERMISSIONS`)
|
|
459
|
+
- `check.resourceId` - Resource ID
|
|
460
|
+
- `check.resourceType` - Resource type (optional)
|
|
461
|
+
|
|
462
|
+
**Returns:** `true` if user has the permission
|
|
463
|
+
|
|
464
|
+
**Implementation Details:**
|
|
465
|
+
- For team resources, this function checks **both** the `user_roles` table and the `team_members` table
|
|
466
|
+
- This ensures team roles stored in `team_members` (e.g., from team creation) are correctly recognized
|
|
467
|
+
- The function checks both management roles (`TEAM_OWNER`, `TEAM_ADMIN`) and operational roles (`TEAM_EDITOR`, `TEAM_MEMBER`, `TEAM_VIEWER`)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
#### `hasAnyPermission(userId, permissions, resourceId, resourceType?): Promise<boolean>`
|
|
473
|
+
|
|
474
|
+
Check if a user has any of the specified permissions.
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
#### `hasAllPermissions(userId, permissions, resourceId, resourceType?): Promise<boolean>`
|
|
479
|
+
|
|
480
|
+
Check if a user has all of the specified permissions.
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
#### `getUserPermissions(userId, resourceId, resourceType?): Promise<Permission[]>`
|
|
485
|
+
|
|
486
|
+
Get all permissions for a user in a resource (deduplicated).
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
### Authorization Guard Functions
|
|
491
|
+
|
|
492
|
+
#### `requirePermission(check: PermissionCheck): Promise<void>`
|
|
493
|
+
|
|
494
|
+
Require a user to have a specific permission, or throw an error.
|
|
495
|
+
|
|
496
|
+
**Throws:** Error if user doesn't have the permission
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
#### `requireRole(userId, role, resourceType, resourceId): Promise<void>`
|
|
501
|
+
|
|
502
|
+
Require a user to have a specific role, or throw an error.
|
|
503
|
+
|
|
504
|
+
**Throws:** Error if user doesn't have the role
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
### Project Ownership Functions
|
|
511
|
+
|
|
512
|
+
#### `getOwnedProjects(userId: string): Promise<string[]>`
|
|
513
|
+
|
|
514
|
+
Get all projects where a user is PROJECT_OWNER.
|
|
515
|
+
|
|
516
|
+
**Returns:** Array of project IDs
|
|
517
|
+
|
|
518
|
+
**Use case:** Check before demoting a user from TEAM_EDITOR
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
#### `demoteWithOwnershipTransfer(userId, teamId, newOperationalRole, projectOwnershipTransfers): Promise<void>`
|
|
523
|
+
|
|
524
|
+
Safely demote a user from TEAM_EDITOR while transferring their project ownerships.
|
|
525
|
+
|
|
526
|
+
**Parameters:**
|
|
527
|
+
- `userId` - User ID to demote
|
|
528
|
+
- `teamId` - Team ID
|
|
529
|
+
- `newOperationalRole` - New role (`'TEAM_VIEWER'` or `'TEAM_MEMBER'`)
|
|
530
|
+
- `projectOwnershipTransfers` - Map of `projectId -> newOwnerId`
|
|
531
|
+
|
|
532
|
+
**Throws:** Error if any owned project is missing a transfer target
|
|
533
|
+
|
|
534
|
+
**Example:**
|
|
535
|
+
```typescript
|
|
536
|
+
await demoteWithOwnershipTransfer(
|
|
537
|
+
'user_123',
|
|
538
|
+
'team_456',
|
|
539
|
+
'TEAM_VIEWER',
|
|
540
|
+
{
|
|
541
|
+
'project_1': 'user_789',
|
|
542
|
+
'project_2': 'user_789',
|
|
543
|
+
}
|
|
544
|
+
);
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
## Best Practices
|
|
550
|
+
|
|
551
|
+
### 1. Always Use Server-Side Checks
|
|
552
|
+
|
|
553
|
+
**❌ Don't rely on client-side role checks for security:**
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
// BAD: Client-side only
|
|
557
|
+
'use client';
|
|
558
|
+
export function DeleteButton() {
|
|
559
|
+
const { user } = useSession();
|
|
560
|
+
if (user.role !== 'TEAM_OWNER') return null;
|
|
561
|
+
return <button onClick={deleteTeam}>Delete</button>;
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**✅ Always enforce permissions on the server:**
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
// GOOD: Server-side enforcement
|
|
569
|
+
export async function DELETE(request: Request) {
|
|
570
|
+
const session = await getSession();
|
|
571
|
+
|
|
572
|
+
await requirePermission({
|
|
573
|
+
userId: session.userId,
|
|
574
|
+
permission: PERMISSIONS.TEAM_DELETE,
|
|
575
|
+
resourceId: teamId,
|
|
576
|
+
resourceType: 'team',
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// Proceed with deletion
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### 2. Use Permission Checks, Not Role Checks
|
|
584
|
+
|
|
585
|
+
**❌ Don't check roles directly in business logic:**
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
// BAD: Tightly coupled to roles
|
|
589
|
+
if (userRole === 'TEAM_OWNER' || userRole === 'TEAM_ADMIN') {
|
|
590
|
+
// Allow action
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**✅ Check permissions instead:**
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
// GOOD: Flexible and maintainable
|
|
598
|
+
const canManage = await hasPermission({
|
|
599
|
+
userId,
|
|
600
|
+
permission: PERMISSIONS.TEAM_MANAGE_MEMBERS,
|
|
601
|
+
resourceId: teamId,
|
|
602
|
+
resourceType: 'team',
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
if (canManage) {
|
|
606
|
+
// Allow action
|
|
607
|
+
}
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### 3. Use Guards for API Routes
|
|
611
|
+
|
|
612
|
+
**✅ Use `requirePermission` for cleaner API routes:**
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
export async function POST(request: Request) {
|
|
616
|
+
const session = await getSession();
|
|
617
|
+
const { projectId, title, description } = await request.json();
|
|
618
|
+
|
|
619
|
+
// Guard: throws error if permission denied
|
|
620
|
+
await requirePermission({
|
|
621
|
+
userId: session.userId,
|
|
622
|
+
permission: PERMISSIONS.ISSUE_CREATE,
|
|
623
|
+
resourceId: projectId,
|
|
624
|
+
resourceType: 'project',
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// Continue with issue creation
|
|
628
|
+
const issue = await createIssue({ projectId, title, description });
|
|
629
|
+
return Response.json(issue);
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### 4. Batch Role Assignments
|
|
634
|
+
|
|
635
|
+
**✅ Use `assignRoles` for multiple assignments:**
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
// GOOD: Single transaction
|
|
639
|
+
await assignRoles([
|
|
640
|
+
{ userId, role: TEAM_ROLES.TEAM_OWNER, resourceType: 'team', resourceId: teamId },
|
|
641
|
+
{ userId, role: PROJECT_ROLES.PROJECT_OWNER, resourceType: 'project', resourceId: projectId },
|
|
642
|
+
]);
|
|
643
|
+
|
|
644
|
+
// BAD: Multiple transactions
|
|
645
|
+
await assignRole({ userId, role: TEAM_ROLES.TEAM_OWNER, resourceType: 'team', resourceId: teamId });
|
|
646
|
+
await assignRole({ userId, role: PROJECT_ROLES.PROJECT_OWNER, resourceType: 'project', resourceId: projectId });
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
### 6. Log Permission Denials
|
|
652
|
+
|
|
653
|
+
**✅ The RBAC utilities automatically log permission denials:**
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
// Logs are automatically created when permissions are denied
|
|
657
|
+
await requirePermission({
|
|
658
|
+
userId: session.userId,
|
|
659
|
+
permission: PERMISSIONS.TEAM_DELETE,
|
|
660
|
+
resourceId: teamId,
|
|
661
|
+
resourceType: 'team',
|
|
662
|
+
});
|
|
663
|
+
// If denied, logs: rbac.permission_denied with context
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### 7. Handle PROJECT_OWNER Demotion Edge Case
|
|
667
|
+
|
|
668
|
+
**✅ Always check for project ownership before demoting:**
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
// BAD: Direct demotion without checking
|
|
672
|
+
await updateRole(userId, 'TEAM_EDITOR', 'TEAM_VIEWER', 'team', teamId);
|
|
673
|
+
// May fail if user is PROJECT_OWNER
|
|
674
|
+
|
|
675
|
+
// GOOD: Check first, then handle appropriately
|
|
676
|
+
const ownedProjects = await getOwnedProjects(userId);
|
|
677
|
+
|
|
678
|
+
if (ownedProjects.length > 0) {
|
|
679
|
+
// Show UI to transfer ownership
|
|
680
|
+
return {
|
|
681
|
+
error: 'OWNERSHIP_TRANSFER_REQUIRED',
|
|
682
|
+
projects: ownedProjects,
|
|
683
|
+
message: `User owns ${ownedProjects.length} project(s). Transfer ownership first.`,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Safe to demote
|
|
688
|
+
await updateRole(userId, 'TEAM_EDITOR', 'TEAM_VIEWER', 'team', teamId);
|
|
689
|
+
|
|
690
|
+
// Or use the safe helper
|
|
691
|
+
await demoteWithOwnershipTransfer(userId, teamId, 'TEAM_VIEWER', {
|
|
692
|
+
'project_1': newOwnerId,
|
|
693
|
+
'project_2': newOwnerId,
|
|
694
|
+
});
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
## Security Considerations
|
|
700
|
+
|
|
701
|
+
### 1. Never Trust Client Input
|
|
702
|
+
|
|
703
|
+
Always validate user permissions on the server, even if the client UI hides certain actions.
|
|
704
|
+
|
|
705
|
+
### 2. Use Transactions for Role Changes
|
|
706
|
+
|
|
707
|
+
When changing roles that affect quotas, use transactions to ensure consistency:
|
|
708
|
+
|
|
709
|
+
```typescript
|
|
710
|
+
await db.transaction(async (tx) => {
|
|
711
|
+
await removeRole({ userId, role: oldRole, resourceType, resourceId });
|
|
712
|
+
await assignRole({ userId, role: newRole, resourceType, resourceId });
|
|
713
|
+
});
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### 3. Audit Role Changes
|
|
717
|
+
|
|
718
|
+
All role assignments and removals are automatically logged with structured logging:
|
|
719
|
+
|
|
720
|
+
```typescript
|
|
721
|
+
// Logs include:
|
|
722
|
+
// - eventType: 'rbac.assign_role.success' | 'rbac.remove_role.success'
|
|
723
|
+
// - userId, role, resourceType, resourceId
|
|
724
|
+
// - timestamp, requestId
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### 4. Validate Resource Ownership
|
|
728
|
+
|
|
729
|
+
Before checking permissions, verify that the resource exists and belongs to the team:
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
// Verify project belongs to team
|
|
733
|
+
const project = await getProject(projectId);
|
|
734
|
+
if (project.teamId !== teamId) {
|
|
735
|
+
throw new Error('Project not found');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Then check permissions
|
|
739
|
+
await requirePermission({
|
|
740
|
+
userId: session.userId,
|
|
741
|
+
permission: PERMISSIONS.PROJECT_UPDATE,
|
|
742
|
+
resourceId: projectId,
|
|
743
|
+
resourceType: 'project',
|
|
744
|
+
});
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
### 5. Rate Limit Role Changes
|
|
748
|
+
|
|
749
|
+
Implement rate limiting for role assignment endpoints to prevent abuse:
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
// Limit role changes to 10 per minute per team
|
|
753
|
+
await checkRateLimit(`role-changes:${teamId}`, 10, 60000);
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
---
|
|
757
|
+
|
|
758
|
+
## Integration with Authentication
|
|
759
|
+
|
|
760
|
+
The RBAC system integrates seamlessly with the authentication system:
|
|
761
|
+
|
|
762
|
+
### 1. Default Roles on Verification
|
|
763
|
+
|
|
764
|
+
When a user verifies their email, default roles are assigned:
|
|
765
|
+
|
|
766
|
+
```typescript
|
|
767
|
+
// In email verification handler
|
|
768
|
+
await verifyEmail(token);
|
|
769
|
+
|
|
770
|
+
// Assign default roles (currently none, user must create/join team)
|
|
771
|
+
// This will be updated when team creation is implemented
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### 2. Session Data Includes Roles
|
|
775
|
+
|
|
776
|
+
The session object includes user roles for quick access:
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
const session = await getSession();
|
|
780
|
+
// session.user.roles = [
|
|
781
|
+
// { role: 'TEAM_OWNER', resourceType: 'team', resourceId: 'team_123' },
|
|
782
|
+
// { role: 'PROJECT_EDITOR', resourceType: 'project', resourceId: 'project_456' },
|
|
783
|
+
// ]
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
### 3. Client-Side Role Checks
|
|
787
|
+
|
|
788
|
+
Use the `useSession` hook to access roles in client components:
|
|
789
|
+
|
|
790
|
+
```typescript
|
|
791
|
+
'use client';
|
|
792
|
+
import { useSession } from '@/features/auth/hooks/use-session';
|
|
793
|
+
|
|
794
|
+
export function TeamSettings() {
|
|
795
|
+
const { data: session } = useSession();
|
|
796
|
+
|
|
797
|
+
const isTeamOwner = session?.user.roles.some(
|
|
798
|
+
r => r.role === 'TEAM_OWNER' && r.resourceId === teamId
|
|
799
|
+
);
|
|
800
|
+
|
|
801
|
+
return (
|
|
802
|
+
<div>
|
|
803
|
+
{isTeamOwner && <DangerZone />}
|
|
804
|
+
</div>
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
---
|
|
810
|
+
|
|
811
|
+
## Testing RBAC
|
|
812
|
+
|
|
813
|
+
### Unit Tests
|
|
814
|
+
|
|
815
|
+
Test individual RBAC functions with mock data:
|
|
816
|
+
|
|
817
|
+
```typescript
|
|
818
|
+
import { assignRole, hasPermission, TEAM_ROLES, PERMISSIONS } from '@/server/auth/rbac';
|
|
819
|
+
|
|
820
|
+
test('team owner has all permissions', async () => {
|
|
821
|
+
await assignRole({
|
|
822
|
+
userId: 'user_123',
|
|
823
|
+
role: TEAM_ROLES.TEAM_OWNER,
|
|
824
|
+
resourceType: 'team',
|
|
825
|
+
resourceId: 'team_456',
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
const canDelete = await hasPermission({
|
|
829
|
+
userId: 'user_123',
|
|
830
|
+
permission: PERMISSIONS.TEAM_DELETE,
|
|
831
|
+
resourceId: 'team_456',
|
|
832
|
+
resourceType: 'team',
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
expect(canDelete).toBe(true);
|
|
836
|
+
});
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
### Integration Tests
|
|
840
|
+
|
|
841
|
+
Test RBAC with real database and API routes:
|
|
842
|
+
|
|
843
|
+
```typescript
|
|
844
|
+
test('POST /api/issues requires issue:create permission', async () => {
|
|
845
|
+
// Create user with viewer role (no create permission)
|
|
846
|
+
await assignRole({
|
|
847
|
+
userId: 'user_123',
|
|
848
|
+
role: PROJECT_ROLES.PROJECT_VIEWER,
|
|
849
|
+
resourceType: 'project',
|
|
850
|
+
resourceId: 'project_456',
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
// Attempt to create issue
|
|
854
|
+
const response = await fetch('/api/issues', {
|
|
855
|
+
method: 'POST',
|
|
856
|
+
body: JSON.stringify({ projectId: 'project_456', title: 'Test' }),
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
expect(response.status).toBe(403);
|
|
860
|
+
});
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
### Property-Based Tests
|
|
864
|
+
|
|
865
|
+
Test RBAC properties with random inputs:
|
|
866
|
+
|
|
867
|
+
```typescript
|
|
868
|
+
import fc from 'fast-check';
|
|
869
|
+
|
|
870
|
+
test('Property: Resource usage is tracked correctly', () => {
|
|
871
|
+
fc.assert(
|
|
872
|
+
fc.asyncProperty(
|
|
873
|
+
fc.uuid(),
|
|
874
|
+
fc.uuid(),
|
|
875
|
+
async (userId, teamId) => {
|
|
876
|
+
const usageBefore = await getTeamUsage(teamId);
|
|
877
|
+
|
|
878
|
+
// Check quotas
|
|
879
|
+
const result = await checkResourceLimit(teamId, 'members');
|
|
880
|
+
|
|
881
|
+
expect(result).toBeDefined();
|
|
882
|
+
expect(result.allowed).toBeDefined();
|
|
883
|
+
}
|
|
884
|
+
)
|
|
885
|
+
);
|
|
886
|
+
});
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
---
|
|
890
|
+
|
|
891
|
+
## Troubleshooting
|
|
892
|
+
|
|
893
|
+
### Common Issues
|
|
894
|
+
|
|
895
|
+
#### 1. Permission Denied Errors
|
|
896
|
+
|
|
897
|
+
**Problem:** User gets permission denied even though they should have access.
|
|
898
|
+
|
|
899
|
+
**Solution:**
|
|
900
|
+
- Check if user has the correct role assigned
|
|
901
|
+
- Verify the resource ID matches
|
|
902
|
+
- Check if the role has the required permission in `ROLE_PERMISSIONS`
|
|
903
|
+
- Review logs for `rbac.permission_denied` events
|
|
904
|
+
|
|
905
|
+
```typescript
|
|
906
|
+
// Debug: Check user's roles and permissions
|
|
907
|
+
const roles = await getUserRoles(userId);
|
|
908
|
+
console.log('User roles:', roles);
|
|
909
|
+
|
|
910
|
+
const permissions = await getUserPermissions(userId, resourceId, 'project');
|
|
911
|
+
console.log('User permissions:', permissions);
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
#### 2. Resource Quota Exceeded
|
|
915
|
+
|
|
916
|
+
**Problem:** Cannot assign role because team has reached resource quotas.
|
|
917
|
+
|
|
918
|
+
**Solution:**
|
|
919
|
+
- Check current usage: `await getTeamUsage(teamId)`
|
|
920
|
+
- Verify team's quotas in environment variables
|
|
921
|
+
- Increase instance limits if valid
|
|
922
|
+
- Remove unused members or resources
|
|
923
|
+
|
|
924
|
+
#### 3. Role Assignment Fails
|
|
925
|
+
|
|
926
|
+
**Problem:** `assignRole` throws an error.
|
|
927
|
+
|
|
928
|
+
**Solution:**
|
|
929
|
+
- Verify role exists in `ALL_ROLES`
|
|
930
|
+
- Check resource type matches role type (team role for team resource, project role for project resource)
|
|
931
|
+
- Ensure resource exists in database
|
|
932
|
+
- Check database constraints (foreign keys, unique constraints)
|
|
933
|
+
|
|
934
|
+
---
|
|
935
|
+
|
|
936
|
+
## Migration Guide
|
|
937
|
+
|
|
938
|
+
### From Mock Roles to Real RBAC
|
|
939
|
+
|
|
940
|
+
If you're migrating from mock role checks to the real RBAC system:
|
|
941
|
+
|
|
942
|
+
1. **Replace hardcoded role checks:**
|
|
943
|
+
|
|
944
|
+
```typescript
|
|
945
|
+
// Before
|
|
946
|
+
if (user.role === 'admin') { ... }
|
|
947
|
+
|
|
948
|
+
// After
|
|
949
|
+
const canManage = await hasPermission({
|
|
950
|
+
userId: user.id,
|
|
951
|
+
permission: PERMISSIONS.TEAM_MANAGE_MEMBERS,
|
|
952
|
+
resourceId: teamId,
|
|
953
|
+
resourceType: 'team',
|
|
954
|
+
});
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
2. **Update session data structure:**
|
|
958
|
+
|
|
959
|
+
```typescript
|
|
960
|
+
// Before
|
|
961
|
+
session.user.role = 'admin';
|
|
962
|
+
|
|
963
|
+
// After
|
|
964
|
+
session.user.roles = [
|
|
965
|
+
{ role: 'TEAM_ADMIN', resourceType: 'team', resourceId: 'team_123' }
|
|
966
|
+
];
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
3. **Add permission checks to API routes:**
|
|
970
|
+
|
|
971
|
+
```typescript
|
|
972
|
+
// Add to all protected API routes
|
|
973
|
+
await requirePermission({
|
|
974
|
+
userId: session.userId,
|
|
975
|
+
permission: PERMISSIONS.ISSUE_CREATE,
|
|
976
|
+
resourceId: projectId,
|
|
977
|
+
resourceType: 'project',
|
|
978
|
+
});
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
---
|
|
982
|
+
|
|
983
|
+
## Two-Tier Role System Examples
|
|
984
|
+
|
|
985
|
+
### Creating Team Owners
|
|
986
|
+
|
|
987
|
+
```typescript
|
|
988
|
+
// Owner who creates content
|
|
989
|
+
await assignRoles([
|
|
990
|
+
{ userId, role: 'TEAM_OWNER', resourceType: 'team', resourceId: teamId },
|
|
991
|
+
{ userId, role: 'TEAM_EDITOR', resourceType: 'team', resourceId: teamId },
|
|
992
|
+
]);
|
|
993
|
+
|
|
994
|
+
// Owner who only manages
|
|
995
|
+
await assignRoles([
|
|
996
|
+
{ userId, role: 'TEAM_OWNER', resourceType: 'team', resourceId: teamId },
|
|
997
|
+
{ userId, role: 'TEAM_VIEWER', resourceType: 'team', resourceId: teamId },
|
|
998
|
+
]);
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
### Creating Team Admins
|
|
1002
|
+
|
|
1003
|
+
```typescript
|
|
1004
|
+
// Admin who creates content
|
|
1005
|
+
await assignRoles([
|
|
1006
|
+
{ userId, role: 'TEAM_ADMIN', resourceType: 'team', resourceId: teamId },
|
|
1007
|
+
{ userId, role: 'TEAM_EDITOR', resourceType: 'team', resourceId: teamId },
|
|
1008
|
+
]);
|
|
1009
|
+
|
|
1010
|
+
// Admin who only manages (free)
|
|
1011
|
+
await assignRoles([
|
|
1012
|
+
{ userId, role: 'TEAM_ADMIN', resourceType: 'team', resourceId: teamId },
|
|
1013
|
+
{ userId, role: 'TEAM_MEMBER', resourceType: 'team', resourceId: teamId },
|
|
1014
|
+
]);
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
### Creating Regular Team Members
|
|
1018
|
+
|
|
1019
|
+
```typescript
|
|
1020
|
+
// Designer/QA
|
|
1021
|
+
await assignRole({
|
|
1022
|
+
userId,
|
|
1023
|
+
role: 'TEAM_EDITOR',
|
|
1024
|
+
resourceType: 'team',
|
|
1025
|
+
resourceId: teamId,
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
// Developer (free)
|
|
1029
|
+
await assignRole({
|
|
1030
|
+
userId,
|
|
1031
|
+
role: 'TEAM_MEMBER',
|
|
1032
|
+
resourceType: 'team',
|
|
1033
|
+
resourceId: teamId,
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
// Stakeholder (free)
|
|
1037
|
+
await assignRole({
|
|
1038
|
+
userId,
|
|
1039
|
+
role: 'TEAM_VIEWER',
|
|
1040
|
+
resourceType: 'team',
|
|
1041
|
+
resourceId: teamId,
|
|
1042
|
+
});
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
### Querying Two-Tier Roles
|
|
1046
|
+
|
|
1047
|
+
```typescript
|
|
1048
|
+
// Get management role
|
|
1049
|
+
const managementRole = await getManagementRole(userId, teamId);
|
|
1050
|
+
// Returns: 'TEAM_OWNER' | 'TEAM_ADMIN' | null
|
|
1051
|
+
|
|
1052
|
+
// Get operational role
|
|
1053
|
+
const operationalRole = await getOperationalRole(userId, teamId);
|
|
1054
|
+
// Returns: 'TEAM_EDITOR' | 'TEAM_MEMBER' | 'TEAM_VIEWER' | null
|
|
1055
|
+
|
|
1056
|
+
// Check if user is owner
|
|
1057
|
+
const isOwner = managementRole === 'TEAM_OWNER';
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
### Auto-Promotion to TEAM_EDITOR
|
|
1063
|
+
|
|
1064
|
+
```typescript
|
|
1065
|
+
// When assigning PROJECT_OWNER or PROJECT_EDITOR
|
|
1066
|
+
await assignRole({
|
|
1067
|
+
userId,
|
|
1068
|
+
role: 'PROJECT_OWNER',
|
|
1069
|
+
resourceType: 'project',
|
|
1070
|
+
resourceId: projectId,
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
// Auto-promote to TEAM_EDITOR
|
|
1074
|
+
await autoPromoteToEditor(userId, teamId);
|
|
1075
|
+
|
|
1076
|
+
// Or use ensureOperationalRole for more control
|
|
1077
|
+
await ensureOperationalRole(userId, teamId, 'TEAM_EDITOR');
|
|
1078
|
+
// Only upgrades if current role is lower, never downgrades
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
---
|
|
1082
|
+
|
|
1083
|
+
## FAQ
|
|
1084
|
+
|
|
1085
|
+
### Q: Can a user have only a management role without an operational role?
|
|
1086
|
+
**A:** Technically yes (database allows it), but the UI should always assign both. A user with only TEAM_OWNER would have no content access permissions.
|
|
1087
|
+
|
|
1088
|
+
### Q: Can a user have multiple operational roles?
|
|
1089
|
+
**A:** No. Users should have exactly one operational role (EDITOR, MEMBER, or VIEWER) per team.
|
|
1090
|
+
|
|
1091
|
+
### Q: What happens if I assign PROJECT_OWNER to a user with TEAM_VIEWER?
|
|
1092
|
+
**A:** The `autoPromoteToEditor()` function will upgrade them to TEAM_EDITOR.
|
|
1093
|
+
|
|
1094
|
+
### Q: Can I downgrade a TEAM_EDITOR to TEAM_MEMBER?
|
|
1095
|
+
**A:** Yes, but you must do it explicitly using `updateRole()`. Auto-promotion only upgrades, never downgrades. **Important:** If the user is a PROJECT_OWNER on any projects, the demotion will be blocked. You must transfer project ownership first using `demoteWithOwnershipTransfer()`.
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
**A:** Assign them TEAM_OWNER (management) + TEAM_VIEWER or TEAM_MEMBER (operational, both free).
|
|
1099
|
+
|
|
1100
|
+
### Q: Why separate management and operational roles?
|
|
1101
|
+
**A:** This allows flexible team management where owners/admins can manage settings without paying for a seat if they don't create content.
|
|
1102
|
+
|
|
1103
|
+
### Q: What happens if I try to demote a PROJECT_OWNER from TEAM_EDITOR?
|
|
1104
|
+
**A:** The system will block the demotion and throw an error listing all projects where the user is owner. You must use `demoteWithOwnershipTransfer()` to transfer ownership to other users first. This prevents orphaned projects without owners.
|
|
1105
|
+
|
|
1106
|
+
### Q: How do I check if a user can be safely demoted?
|
|
1107
|
+
**A:** Use `getOwnedProjects(userId)` to check if they own any projects. If the array is empty, demotion is safe. If not, you need to transfer ownership first.
|
|
1108
|
+
|
|
1109
|
+
---
|
|
1110
|
+
|
|
1111
|
+
## Related Documentation
|
|
1112
|
+
|
|
1113
|
+
- [Authentication System](./SECURITY.md) - User authentication and session management
|
|
1114
|
+
- [Resource Limits & Quotas](./RESOURCE_LIMITS.md) - Resource usage and limits
|
|
1115
|
+
- [Product Overview](./.ai/steering/product.md) - Product features and user roles
|
|
1116
|
+
- [API Documentation](./API.md) - API endpoints and authentication
|
|
1117
|
+
|
|
1118
|
+
---
|
|
1119
|
+
|
|
1120
|
+
## Support
|
|
1121
|
+
|
|
1122
|
+
For questions or issues with RBAC:
|
|
1123
|
+
|
|
1124
|
+
1. Check this documentation first
|
|
1125
|
+
2. Review the [Product Overview](./.ai/steering/product.md) for role definitions
|
|
1126
|
+
3. Check logs for `rbac.*` events
|
|
1127
|
+
4. Review the source code in `src/config/roles.ts` and `src/server/auth/rbac.ts`
|
|
1128
|
+
|
|
1129
|
+
---
|
|
1130
|
+
|
|
1131
|
+
**Last Updated:** November 19, 2025
|
|
1132
|
+
**Version:** 2.0.0 (Two-Tier Role System)
|