ui-syncup 0.3.13 → 0.4.0-beta.1
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 +131 -0
- package/.nvmrc +1 -0
- package/.releaserc.json +18 -0
- package/.vercelignore +73 -0
- package/AGENTS.md +544 -0
- package/CHANGELOG.md +37 -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 +117 -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,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Property-based tests for password hashing utilities
|
|
3
|
+
*
|
|
4
|
+
* These tests use fast-check to verify correctness properties across
|
|
5
|
+
* randomly generated inputs, ensuring the password hashing implementation
|
|
6
|
+
* is secure and correct for all valid inputs.
|
|
7
|
+
*
|
|
8
|
+
* @module server/auth/__tests__/password
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, test, expect } from 'vitest';
|
|
12
|
+
import * as fc from 'fast-check';
|
|
13
|
+
import { hashPassword, verifyPassword } from '../password';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Property test configuration
|
|
17
|
+
* Run each property 100+ times with different random inputs
|
|
18
|
+
*
|
|
19
|
+
* Note: Argon2 hashing is computationally expensive, so we use a lower
|
|
20
|
+
* number of runs for property tests to keep test execution time reasonable.
|
|
21
|
+
* The security properties still hold with fewer iterations.
|
|
22
|
+
*/
|
|
23
|
+
const PROPERTY_CONFIG = {
|
|
24
|
+
numRuns: 20, // Reduced from 100 due to Argon2 computational cost
|
|
25
|
+
timeout: 30000, // 30 second timeout per property
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
describe('Password Hashing - Property-Based Tests', () => {
|
|
29
|
+
/**
|
|
30
|
+
* Feature: authentication-system, Property 23: Passwords are hashed securely
|
|
31
|
+
* Validates: Requirements 8.1
|
|
32
|
+
*
|
|
33
|
+
* For any valid password string:
|
|
34
|
+
* 1. The hash should not equal the plaintext password
|
|
35
|
+
* 2. The hash should be non-empty
|
|
36
|
+
* 3. The hash should be verifiable against the original password
|
|
37
|
+
* 4. The hash should start with $argon2id$ (correct algorithm)
|
|
38
|
+
* 5. Different passwords should produce different hashes
|
|
39
|
+
*/
|
|
40
|
+
test('Property 23: Passwords are hashed securely', async () => {
|
|
41
|
+
await fc.assert(
|
|
42
|
+
fc.asyncProperty(
|
|
43
|
+
// Generate random passwords (1-128 characters, printable ASCII)
|
|
44
|
+
fc.string({ minLength: 1, maxLength: 128 }),
|
|
45
|
+
async (password) => {
|
|
46
|
+
// Hash the password
|
|
47
|
+
const hash = await hashPassword(password);
|
|
48
|
+
|
|
49
|
+
// Property 1: Hash should not equal plaintext
|
|
50
|
+
expect(hash).not.toBe(password);
|
|
51
|
+
|
|
52
|
+
// Property 2: Hash should be non-empty
|
|
53
|
+
expect(hash.length).toBeGreaterThan(0);
|
|
54
|
+
|
|
55
|
+
// Property 3: Hash should be verifiable
|
|
56
|
+
const isValid = await verifyPassword(password, hash);
|
|
57
|
+
expect(isValid).toBe(true);
|
|
58
|
+
|
|
59
|
+
// Property 4: Hash should use Argon2id algorithm
|
|
60
|
+
expect(hash).toMatch(/^\$argon2id\$/);
|
|
61
|
+
|
|
62
|
+
// Property 5: Wrong password should not verify
|
|
63
|
+
const wrongPassword = password + 'X'; // Append character to make it different
|
|
64
|
+
const isInvalid = await verifyPassword(wrongPassword, hash);
|
|
65
|
+
expect(isInvalid).toBe(false);
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
PROPERTY_CONFIG
|
|
69
|
+
);
|
|
70
|
+
}, 60000); // 60 second timeout
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Additional property: Hash uniqueness
|
|
74
|
+
*
|
|
75
|
+
* For any two different passwords, their hashes should be different.
|
|
76
|
+
* This ensures the hash function is deterministic but produces unique outputs.
|
|
77
|
+
*/
|
|
78
|
+
test('Property: Different passwords produce different hashes', async () => {
|
|
79
|
+
await fc.assert(
|
|
80
|
+
fc.asyncProperty(
|
|
81
|
+
fc.string({ minLength: 1, maxLength: 128 }),
|
|
82
|
+
fc.string({ minLength: 1, maxLength: 128 }),
|
|
83
|
+
async (password1, password2) => {
|
|
84
|
+
// Skip if passwords are the same
|
|
85
|
+
fc.pre(password1 !== password2);
|
|
86
|
+
|
|
87
|
+
const hash1 = await hashPassword(password1);
|
|
88
|
+
const hash2 = await hashPassword(password2);
|
|
89
|
+
|
|
90
|
+
// Different passwords should produce different hashes
|
|
91
|
+
expect(hash1).not.toBe(hash2);
|
|
92
|
+
}
|
|
93
|
+
),
|
|
94
|
+
PROPERTY_CONFIG
|
|
95
|
+
);
|
|
96
|
+
}, 60000); // 60 second timeout
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Additional property: Hash consistency
|
|
100
|
+
*
|
|
101
|
+
* For any password, hashing it multiple times should produce different hashes
|
|
102
|
+
* (due to random salt), but all hashes should verify against the original password.
|
|
103
|
+
*/
|
|
104
|
+
test('Property: Multiple hashes of same password all verify correctly', async () => {
|
|
105
|
+
await fc.assert(
|
|
106
|
+
fc.asyncProperty(
|
|
107
|
+
fc.string({ minLength: 1, maxLength: 128 }),
|
|
108
|
+
async (password) => {
|
|
109
|
+
// Generate multiple hashes of the same password
|
|
110
|
+
const hash1 = await hashPassword(password);
|
|
111
|
+
const hash2 = await hashPassword(password);
|
|
112
|
+
const hash3 = await hashPassword(password);
|
|
113
|
+
|
|
114
|
+
// Hashes should be different (due to random salt)
|
|
115
|
+
expect(hash1).not.toBe(hash2);
|
|
116
|
+
expect(hash2).not.toBe(hash3);
|
|
117
|
+
expect(hash1).not.toBe(hash3);
|
|
118
|
+
|
|
119
|
+
// All hashes should verify against the original password
|
|
120
|
+
expect(await verifyPassword(password, hash1)).toBe(true);
|
|
121
|
+
expect(await verifyPassword(password, hash2)).toBe(true);
|
|
122
|
+
expect(await verifyPassword(password, hash3)).toBe(true);
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
PROPERTY_CONFIG
|
|
126
|
+
);
|
|
127
|
+
}, 90000); // 90 second timeout (3 hashes per iteration)
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Edge case: Empty password handling
|
|
131
|
+
*
|
|
132
|
+
* Empty passwords should be rejected by hashPassword
|
|
133
|
+
*/
|
|
134
|
+
test('Edge case: Empty password is rejected', async () => {
|
|
135
|
+
await expect(hashPassword('')).rejects.toThrow('Password cannot be empty');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Edge case: Invalid hash handling
|
|
140
|
+
*
|
|
141
|
+
* verifyPassword should return false for invalid hashes instead of throwing
|
|
142
|
+
*/
|
|
143
|
+
test('Edge case: Invalid hash returns false', async () => {
|
|
144
|
+
const result = await verifyPassword('password', 'invalid-hash');
|
|
145
|
+
expect(result).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Edge case: Empty hash handling
|
|
150
|
+
*
|
|
151
|
+
* verifyPassword should return false for empty hashes
|
|
152
|
+
*/
|
|
153
|
+
test('Edge case: Empty hash returns false', async () => {
|
|
154
|
+
const result = await verifyPassword('password', '');
|
|
155
|
+
expect(result).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Edge case: Empty password in verification
|
|
160
|
+
*
|
|
161
|
+
* verifyPassword should return false for empty passwords
|
|
162
|
+
*/
|
|
163
|
+
test('Edge case: Empty password in verification returns false', async () => {
|
|
164
|
+
const hash = await hashPassword('ValidPassword123!');
|
|
165
|
+
const result = await verifyPassword('', hash);
|
|
166
|
+
expect(result).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Security property: Timing attack resistance
|
|
171
|
+
*
|
|
172
|
+
* Verification should take similar time regardless of password correctness.
|
|
173
|
+
* This is a basic check - true timing attack resistance requires more sophisticated testing.
|
|
174
|
+
*/
|
|
175
|
+
test('Property: Verification time is consistent', async () => {
|
|
176
|
+
const password = 'TestPassword123!';
|
|
177
|
+
const hash = await hashPassword(password);
|
|
178
|
+
|
|
179
|
+
// Measure time for correct password
|
|
180
|
+
const start1 = Date.now();
|
|
181
|
+
await verifyPassword(password, hash);
|
|
182
|
+
const time1 = Date.now() - start1;
|
|
183
|
+
|
|
184
|
+
// Measure time for incorrect password
|
|
185
|
+
const start2 = Date.now();
|
|
186
|
+
await verifyPassword('WrongPassword123!', hash);
|
|
187
|
+
const time2 = Date.now() - start2;
|
|
188
|
+
|
|
189
|
+
// Times should be within reasonable range (within 100ms of each other)
|
|
190
|
+
// This is a loose check since we can't guarantee exact timing
|
|
191
|
+
const timeDiff = Math.abs(time1 - time2);
|
|
192
|
+
expect(timeDiff).toBeLessThan(100);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Property-based tests for rate limiting utilities
|
|
3
|
+
*
|
|
4
|
+
* These tests use fast-check to verify correctness properties across
|
|
5
|
+
* randomly generated inputs, ensuring the rate limiting implementation
|
|
6
|
+
* correctly enforces limits and handles edge cases.
|
|
7
|
+
*
|
|
8
|
+
* @module server/auth/__tests__/rate-limiter
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, test, expect, beforeEach } from 'vitest';
|
|
12
|
+
import * as fc from 'fast-check';
|
|
13
|
+
import {
|
|
14
|
+
checkLimit,
|
|
15
|
+
getRemainingAttempts,
|
|
16
|
+
getResetTime,
|
|
17
|
+
resetLimit,
|
|
18
|
+
clearAllLimits,
|
|
19
|
+
createRateLimitKey,
|
|
20
|
+
RATE_LIMITS,
|
|
21
|
+
} from '../rate-limiter';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Property test configuration
|
|
25
|
+
* Run each property 100+ times with different random inputs
|
|
26
|
+
*/
|
|
27
|
+
const PROPERTY_CONFIG = {
|
|
28
|
+
numRuns: 100,
|
|
29
|
+
timeout: 10000, // 10 second timeout per property
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Clean up rate limits before each test
|
|
34
|
+
*/
|
|
35
|
+
beforeEach(async () => {
|
|
36
|
+
await clearAllLimits();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('Rate Limiter - Property-Based Tests', () => {
|
|
40
|
+
/**
|
|
41
|
+
* Feature: authentication-system, Property 33: IP-based rate limiting enforces limits
|
|
42
|
+
* Validates: Requirements 11.1
|
|
43
|
+
*
|
|
44
|
+
* For any IP address, when sign-in requests exceed 5 attempts per minute,
|
|
45
|
+
* the rate limiter should block subsequent requests until the window resets.
|
|
46
|
+
*/
|
|
47
|
+
test('Property 33: IP-based rate limiting enforces limits', async () => {
|
|
48
|
+
await fc.assert(
|
|
49
|
+
fc.asyncProperty(
|
|
50
|
+
// Generate random IP addresses
|
|
51
|
+
fc.ipV4(),
|
|
52
|
+
async (ip) => {
|
|
53
|
+
const key = createRateLimitKey.signInIp(ip);
|
|
54
|
+
const { limit, windowMs } = RATE_LIMITS.SIGNIN_IP;
|
|
55
|
+
|
|
56
|
+
// First N requests should be allowed
|
|
57
|
+
for (let i = 0; i < limit; i++) {
|
|
58
|
+
const allowed = await checkLimit(key, limit, windowMs);
|
|
59
|
+
expect(allowed).toBe(true);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Next request should be blocked
|
|
63
|
+
const blocked = await checkLimit(key, limit, windowMs);
|
|
64
|
+
expect(blocked).toBe(false);
|
|
65
|
+
|
|
66
|
+
// Remaining attempts should be 0
|
|
67
|
+
const remaining = await getRemainingAttempts(key, limit);
|
|
68
|
+
expect(remaining).toBe(0);
|
|
69
|
+
|
|
70
|
+
// Reset time should be positive
|
|
71
|
+
const resetTime = await getResetTime(key);
|
|
72
|
+
expect(resetTime).toBeGreaterThan(0);
|
|
73
|
+
|
|
74
|
+
// Clean up
|
|
75
|
+
await resetLimit(key);
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
PROPERTY_CONFIG
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Feature: authentication-system, Property 34: Email-based rate limiting enforces limits
|
|
84
|
+
* Validates: Requirements 11.2
|
|
85
|
+
*
|
|
86
|
+
* For any email address, when sign-in requests exceed 3 attempts per 15 minutes,
|
|
87
|
+
* the rate limiter should block subsequent requests until the window resets.
|
|
88
|
+
*/
|
|
89
|
+
test('Property 34: Email-based rate limiting enforces limits', async () => {
|
|
90
|
+
await fc.assert(
|
|
91
|
+
fc.asyncProperty(
|
|
92
|
+
// Generate random email addresses
|
|
93
|
+
fc.emailAddress(),
|
|
94
|
+
async (email) => {
|
|
95
|
+
const key = createRateLimitKey.signInEmail(email);
|
|
96
|
+
const { limit, windowMs } = RATE_LIMITS.SIGNIN_EMAIL;
|
|
97
|
+
|
|
98
|
+
// First N requests should be allowed
|
|
99
|
+
for (let i = 0; i < limit; i++) {
|
|
100
|
+
const allowed = await checkLimit(key, limit, windowMs);
|
|
101
|
+
expect(allowed).toBe(true);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Next request should be blocked
|
|
105
|
+
const blocked = await checkLimit(key, limit, windowMs);
|
|
106
|
+
expect(blocked).toBe(false);
|
|
107
|
+
|
|
108
|
+
// Remaining attempts should be 0
|
|
109
|
+
const remaining = await getRemainingAttempts(key, limit);
|
|
110
|
+
expect(remaining).toBe(0);
|
|
111
|
+
|
|
112
|
+
// Reset time should be positive
|
|
113
|
+
const resetTime = await getResetTime(key);
|
|
114
|
+
expect(resetTime).toBeGreaterThan(0);
|
|
115
|
+
|
|
116
|
+
// Clean up
|
|
117
|
+
await resetLimit(key);
|
|
118
|
+
}
|
|
119
|
+
),
|
|
120
|
+
PROPERTY_CONFIG
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Feature: authentication-system, Property 36: Rate limit responses include retry-after header
|
|
126
|
+
* Validates: Requirements 11.4
|
|
127
|
+
*
|
|
128
|
+
* For any rate-limited request, the system should provide information about
|
|
129
|
+
* when the limit will reset (retry-after time).
|
|
130
|
+
*/
|
|
131
|
+
test('Property 36: Rate limit responses include retry-after information', async () => {
|
|
132
|
+
await fc.assert(
|
|
133
|
+
fc.asyncProperty(
|
|
134
|
+
// Generate random keys
|
|
135
|
+
fc.string({ minLength: 1, maxLength: 50 }),
|
|
136
|
+
fc.integer({ min: 1, max: 10 }), // limit
|
|
137
|
+
fc.integer({ min: 1000, max: 60000 }), // windowMs (1s to 60s)
|
|
138
|
+
async (keyBase, limit, windowMs) => {
|
|
139
|
+
const key = `test:${keyBase}`;
|
|
140
|
+
|
|
141
|
+
// Exhaust the limit
|
|
142
|
+
for (let i = 0; i < limit; i++) {
|
|
143
|
+
await checkLimit(key, limit, windowMs);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Check that we're rate limited
|
|
147
|
+
const allowed = await checkLimit(key, limit, windowMs);
|
|
148
|
+
expect(allowed).toBe(false);
|
|
149
|
+
|
|
150
|
+
// Get reset time (should be positive and within window)
|
|
151
|
+
const resetTime = await getResetTime(key);
|
|
152
|
+
expect(resetTime).toBeGreaterThan(0);
|
|
153
|
+
expect(resetTime).toBeLessThanOrEqual(Math.ceil(windowMs / 1000));
|
|
154
|
+
|
|
155
|
+
// Clean up
|
|
156
|
+
await resetLimit(key);
|
|
157
|
+
}
|
|
158
|
+
),
|
|
159
|
+
PROPERTY_CONFIG
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Additional property: Rate limit resets after window expires
|
|
165
|
+
*
|
|
166
|
+
* For any rate limit, after the window expires, new requests should be allowed.
|
|
167
|
+
*/
|
|
168
|
+
test('Property: Rate limit resets after window expires', async () => {
|
|
169
|
+
const key = 'test:reset';
|
|
170
|
+
const limit = 3;
|
|
171
|
+
const windowMs = 100; // 100ms window for fast testing
|
|
172
|
+
|
|
173
|
+
// Exhaust the limit
|
|
174
|
+
for (let i = 0; i < limit; i++) {
|
|
175
|
+
const allowed = await checkLimit(key, limit, windowMs);
|
|
176
|
+
expect(allowed).toBe(true);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Should be blocked
|
|
180
|
+
const blocked = await checkLimit(key, limit, windowMs);
|
|
181
|
+
expect(blocked).toBe(false);
|
|
182
|
+
|
|
183
|
+
// Wait for window to expire
|
|
184
|
+
await new Promise((resolve) => setTimeout(resolve, windowMs + 50));
|
|
185
|
+
|
|
186
|
+
// Should be allowed again
|
|
187
|
+
const allowedAfterReset = await checkLimit(key, limit, windowMs);
|
|
188
|
+
expect(allowedAfterReset).toBe(true);
|
|
189
|
+
|
|
190
|
+
// Clean up
|
|
191
|
+
await resetLimit(key);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Additional property: Remaining attempts decrease correctly
|
|
196
|
+
*
|
|
197
|
+
* For any rate limit, remaining attempts should decrease by 1 with each request.
|
|
198
|
+
*/
|
|
199
|
+
test('Property: Remaining attempts decrease correctly', async () => {
|
|
200
|
+
await fc.assert(
|
|
201
|
+
fc.asyncProperty(
|
|
202
|
+
fc.string({ minLength: 1, maxLength: 50 }),
|
|
203
|
+
fc.integer({ min: 1, max: 20 }),
|
|
204
|
+
async (keyBase, limit) => {
|
|
205
|
+
const key = `test:${keyBase}`;
|
|
206
|
+
const windowMs = 60000;
|
|
207
|
+
|
|
208
|
+
// Check remaining attempts decrease correctly
|
|
209
|
+
for (let i = 0; i < limit; i++) {
|
|
210
|
+
const remaining = await getRemainingAttempts(key, limit);
|
|
211
|
+
expect(remaining).toBe(limit - i);
|
|
212
|
+
|
|
213
|
+
await checkLimit(key, limit, windowMs);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// After exhausting limit, remaining should be 0
|
|
217
|
+
const finalRemaining = await getRemainingAttempts(key, limit);
|
|
218
|
+
expect(finalRemaining).toBe(0);
|
|
219
|
+
|
|
220
|
+
// Clean up
|
|
221
|
+
await resetLimit(key);
|
|
222
|
+
}
|
|
223
|
+
),
|
|
224
|
+
PROPERTY_CONFIG
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Additional property: Different keys are independent
|
|
230
|
+
*
|
|
231
|
+
* Rate limits for different keys should not affect each other.
|
|
232
|
+
*/
|
|
233
|
+
test('Property: Different keys are independent', async () => {
|
|
234
|
+
await fc.assert(
|
|
235
|
+
fc.asyncProperty(
|
|
236
|
+
fc.string({ minLength: 1, maxLength: 50 }),
|
|
237
|
+
fc.string({ minLength: 1, maxLength: 50 }),
|
|
238
|
+
fc.integer({ min: 1, max: 10 }),
|
|
239
|
+
async (key1Base, key2Base, limit) => {
|
|
240
|
+
// Skip if keys are the same
|
|
241
|
+
fc.pre(key1Base !== key2Base);
|
|
242
|
+
|
|
243
|
+
const key1 = `test:${key1Base}`;
|
|
244
|
+
const key2 = `test:${key2Base}`;
|
|
245
|
+
const windowMs = 60000;
|
|
246
|
+
|
|
247
|
+
// Exhaust limit for key1
|
|
248
|
+
for (let i = 0; i < limit; i++) {
|
|
249
|
+
await checkLimit(key1, limit, windowMs);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// key1 should be blocked
|
|
253
|
+
const key1Blocked = await checkLimit(key1, limit, windowMs);
|
|
254
|
+
expect(key1Blocked).toBe(false);
|
|
255
|
+
|
|
256
|
+
// key2 should still be allowed
|
|
257
|
+
const key2Allowed = await checkLimit(key2, limit, windowMs);
|
|
258
|
+
expect(key2Allowed).toBe(true);
|
|
259
|
+
|
|
260
|
+
// Clean up
|
|
261
|
+
await resetLimit(key1);
|
|
262
|
+
await resetLimit(key2);
|
|
263
|
+
}
|
|
264
|
+
),
|
|
265
|
+
PROPERTY_CONFIG
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Additional property: Email keys are case-insensitive
|
|
271
|
+
*
|
|
272
|
+
* Email-based rate limits should treat emails case-insensitively.
|
|
273
|
+
*/
|
|
274
|
+
test('Property: Email keys are case-insensitive', async () => {
|
|
275
|
+
await fc.assert(
|
|
276
|
+
fc.asyncProperty(
|
|
277
|
+
fc.emailAddress(),
|
|
278
|
+
async (email) => {
|
|
279
|
+
const limit = 3;
|
|
280
|
+
const windowMs = 60000;
|
|
281
|
+
|
|
282
|
+
// Create keys with different cases
|
|
283
|
+
const key1 = createRateLimitKey.signInEmail(email.toLowerCase());
|
|
284
|
+
const key2 = createRateLimitKey.signInEmail(email.toUpperCase());
|
|
285
|
+
|
|
286
|
+
// Keys should be the same
|
|
287
|
+
expect(key1).toBe(key2);
|
|
288
|
+
|
|
289
|
+
// Exhaust limit with lowercase
|
|
290
|
+
for (let i = 0; i < limit; i++) {
|
|
291
|
+
await checkLimit(key1, limit, windowMs);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Uppercase should also be blocked (same key)
|
|
295
|
+
const blocked = await checkLimit(key2, limit, windowMs);
|
|
296
|
+
expect(blocked).toBe(false);
|
|
297
|
+
|
|
298
|
+
// Clean up
|
|
299
|
+
await resetLimit(key1);
|
|
300
|
+
}
|
|
301
|
+
),
|
|
302
|
+
PROPERTY_CONFIG
|
|
303
|
+
);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Edge case: Zero limit
|
|
308
|
+
*
|
|
309
|
+
* A limit of 0 should block all requests.
|
|
310
|
+
*/
|
|
311
|
+
test('Edge case: Zero limit blocks all requests', async () => {
|
|
312
|
+
const key = 'test:zero-limit';
|
|
313
|
+
const limit = 0;
|
|
314
|
+
const windowMs = 60000;
|
|
315
|
+
|
|
316
|
+
const allowed = await checkLimit(key, limit, windowMs);
|
|
317
|
+
expect(allowed).toBe(false);
|
|
318
|
+
|
|
319
|
+
const remaining = await getRemainingAttempts(key, limit);
|
|
320
|
+
expect(remaining).toBe(0);
|
|
321
|
+
|
|
322
|
+
await resetLimit(key);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Edge case: Very large limit
|
|
327
|
+
*
|
|
328
|
+
* A very large limit should allow many requests.
|
|
329
|
+
*/
|
|
330
|
+
test('Edge case: Very large limit allows many requests', async () => {
|
|
331
|
+
const key = 'test:large-limit';
|
|
332
|
+
const limit = 1000;
|
|
333
|
+
const windowMs = 60000;
|
|
334
|
+
|
|
335
|
+
// Make 100 requests (less than limit)
|
|
336
|
+
for (let i = 0; i < 100; i++) {
|
|
337
|
+
const allowed = await checkLimit(key, limit, windowMs);
|
|
338
|
+
expect(allowed).toBe(true);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const remaining = await getRemainingAttempts(key, limit);
|
|
342
|
+
expect(remaining).toBe(900);
|
|
343
|
+
|
|
344
|
+
await resetLimit(key);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Edge case: Very short window
|
|
349
|
+
*
|
|
350
|
+
* A very short window should reset quickly.
|
|
351
|
+
*/
|
|
352
|
+
test('Edge case: Very short window resets quickly', async () => {
|
|
353
|
+
const key = 'test:short-window';
|
|
354
|
+
const limit = 2;
|
|
355
|
+
const windowMs = 50; // 50ms
|
|
356
|
+
|
|
357
|
+
// Exhaust limit
|
|
358
|
+
await checkLimit(key, limit, windowMs);
|
|
359
|
+
await checkLimit(key, limit, windowMs);
|
|
360
|
+
|
|
361
|
+
// Should be blocked
|
|
362
|
+
const blocked = await checkLimit(key, limit, windowMs);
|
|
363
|
+
expect(blocked).toBe(false);
|
|
364
|
+
|
|
365
|
+
// Wait for window to expire
|
|
366
|
+
await new Promise((resolve) => setTimeout(resolve, windowMs + 20));
|
|
367
|
+
|
|
368
|
+
// Should be allowed again
|
|
369
|
+
const allowed = await checkLimit(key, limit, windowMs);
|
|
370
|
+
expect(allowed).toBe(true);
|
|
371
|
+
|
|
372
|
+
await resetLimit(key);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Edge case: Reset time for non-existent key
|
|
377
|
+
*
|
|
378
|
+
* Getting reset time for a key that doesn't exist should return 0.
|
|
379
|
+
*/
|
|
380
|
+
test('Edge case: Reset time for non-existent key returns 0', async () => {
|
|
381
|
+
const resetTime = await getResetTime('non-existent-key');
|
|
382
|
+
expect(resetTime).toBe(0);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Edge case: Remaining attempts for non-existent key
|
|
387
|
+
*
|
|
388
|
+
* Getting remaining attempts for a key that doesn't exist should return the limit.
|
|
389
|
+
*/
|
|
390
|
+
test('Edge case: Remaining attempts for non-existent key returns limit', async () => {
|
|
391
|
+
const limit = 10;
|
|
392
|
+
const remaining = await getRemainingAttempts('non-existent-key', limit);
|
|
393
|
+
expect(remaining).toBe(limit);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Integration test: Password reset rate limiting
|
|
398
|
+
*
|
|
399
|
+
* Verify that password reset rate limiting works as specified.
|
|
400
|
+
*/
|
|
401
|
+
test('Integration: Password reset rate limiting (3 per hour)', async () => {
|
|
402
|
+
const email = 'test@example.com';
|
|
403
|
+
const key = createRateLimitKey.passwordReset(email);
|
|
404
|
+
const { limit, windowMs } = RATE_LIMITS.PASSWORD_RESET;
|
|
405
|
+
|
|
406
|
+
// First 3 requests should be allowed
|
|
407
|
+
for (let i = 0; i < limit; i++) {
|
|
408
|
+
const allowed = await checkLimit(key, limit, windowMs);
|
|
409
|
+
expect(allowed).toBe(true);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// 4th request should be blocked
|
|
413
|
+
const blocked = await checkLimit(key, limit, windowMs);
|
|
414
|
+
expect(blocked).toBe(false);
|
|
415
|
+
|
|
416
|
+
// Verify remaining attempts is 0
|
|
417
|
+
const remaining = await getRemainingAttempts(key, limit);
|
|
418
|
+
expect(remaining).toBe(0);
|
|
419
|
+
|
|
420
|
+
// Verify reset time is within the window
|
|
421
|
+
const resetTime = await getResetTime(key);
|
|
422
|
+
expect(resetTime).toBeGreaterThan(0);
|
|
423
|
+
expect(resetTime).toBeLessThanOrEqual(Math.ceil(windowMs / 1000));
|
|
424
|
+
|
|
425
|
+
await resetLimit(key);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Integration test: Signup IP rate limiting
|
|
430
|
+
*
|
|
431
|
+
* Verify that signup IP rate limiting works as specified.
|
|
432
|
+
*/
|
|
433
|
+
test('Integration: Signup IP rate limiting (10 per hour)', async () => {
|
|
434
|
+
const ip = '192.168.1.1';
|
|
435
|
+
const key = createRateLimitKey.signupIp(ip);
|
|
436
|
+
const { limit, windowMs } = RATE_LIMITS.SIGNUP_IP;
|
|
437
|
+
|
|
438
|
+
// First 10 requests should be allowed
|
|
439
|
+
for (let i = 0; i < limit; i++) {
|
|
440
|
+
const allowed = await checkLimit(key, limit, windowMs);
|
|
441
|
+
expect(allowed).toBe(true);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// 11th request should be blocked
|
|
445
|
+
const blocked = await checkLimit(key, limit, windowMs);
|
|
446
|
+
expect(blocked).toBe(false);
|
|
447
|
+
|
|
448
|
+
await resetLimit(key);
|
|
449
|
+
});
|
|
450
|
+
});
|