.github/9
ISSUE_TEMPLATE/3bug_report.md config.yml feature_request.md
workflows/3ci.yml clean-clone.yml release.yml
CODEOWNERS dependabot.yml pull_request_template.md
apps/630
api/250
src/243
common/30
__tests__/3authorize.test.ts bigint-serialization.interceptor.test.ts files-limits.test.ts
filters/1all-exceptions.filter.ts
guards/3auth.guard.ts internal-service.guard.ts rate-limit.guard.ts
interceptors/2actor-context.interceptor.ts bigint-serialization.interceptor.ts
middleware/2idempotency.test.ts idempotency.ts
pipes/1zod-validation.pipe.ts
actor-context.ts audit.service.ts cache.test.ts cache.ts cookies.ts core.module.ts deprecation.ts email.service.ts errors.ts event-bus.ts log-scrubber.test.ts log-scrubber.ts logger.ts permission-cache-bridge.ts prisma.service.ts redis.service.ts trace-metadata.ts types.ts
config/1env.ts
lib/11
__tests__/5crypto.test.ts filter-builder.test.ts jwt.test.ts pagination.test.ts positions.test.ts
authorize.ts crypto.ts filter-builder.ts jwt.ts pagination.ts positions.ts
modules/197
ai/28
__tests__/4ai-writing.test.ts database-ai.test.ts generate-executor.test.ts prompts.test.ts
generator/4executor.ts generate.controller.ts generate.dto.ts generate.service.ts
ai-chat.service.ts ai-rate-limit.ts ai-writing.controller.ts ai-writing.dto.ts ai-writing.service.ts ai.controller.ts ai.module.ts ai.service.ts budget-guard.service.ts database-ai.controller.ts database-ai.dto.ts database-ai.service.ts db-prompts.ts dto.ts gateway-provider.ts ledger.service.ts prompts.ts rag.controller.ts rag.dto.ts rag.service.ts
api-keys/3api-keys.controller.ts api-keys.module.ts api-keys.service.ts
audit/2audit-log.controller.ts audit.module.ts
auth/4auth.controller.ts auth.module.ts auth.service.ts dto.ts
automations/5automations.controller.ts automations.module.ts automations.service.ts dto.ts webhook-incoming.controller.ts
blocks/5
__tests__/1batch-version-cas.test.ts
block.service.ts blocks.controller.ts blocks.module.ts dto.ts
comments/4comments.controller.ts comments.module.ts comments.service.ts dto.ts
databases/9
__tests__/3normalize-select-options.test.ts option-id-canonicalization.test.ts record-open-query-shape.test.ts
database.service.ts databases.controller.ts databases.module.ts dto.ts property-cache-schema.ts query.service.ts
docs/2docs.controller.ts docs.module.ts
files/10
__tests__/1quota.test.ts
dto.ts files.controller.ts files.module.ts files.service.ts quota.ts record.ts s3.ts uploads.controller.ts uploads.service.ts
forms/18debug.controller.ts form.dto.ts forms.controller.ts forms.module.ts forms.service.ts markdown.test.ts markdown.ts optional-auth.guard.ts public-forms.controller.ts public-forms.service.ts question.dto.ts questions.service.ts rate-limit.ts recaptcha.ts slug.ts submissions.controller.ts submissions.service.ts submit-schema.ts
health/3health.controller.ts health.module.ts health.service.ts
integrations/3integrations.controller.ts integrations.module.ts integrations.service.ts
internal/12
__tests__/2flush-query-shape.test.ts internal.endpoints.test.ts
ai-test-seam.controller.ts ai-test-seam.dto.ts ai-test-seam.service.ts internal.controller.ts internal.dto.ts internal.module.ts internal.service.ts test-seam.controller.ts test-seam.dto.ts test-seam.service.ts
notifications/3notifications.controller.ts notifications.module.ts notifications.service.ts
oauth/6
__tests__/1oauth.scopes.test.ts
oauth.controller.ts oauth.module.ts oauth.service.ts scopes.ts token-keys.ts
org/15audit.service.ts embed-preview.controller.ts guards.ts members.service.ts org-admin.controller.ts org-policy.service.ts org.module.ts overview.service.ts policy-effective.controller.ts policy.schema.ts policy.test.ts retention.service.ts totp.test.ts totp.ts two-factor.service.ts
orgs/5dto.ts orgs.controller.ts orgs.module.ts orgs.service.ts util.ts
pages/4dto.ts page.service.ts pages.controller.ts pages.module.ts
scim/15
__tests__/3email-mapping.test.ts etag.test.ts filters.test.ts
etag.ts filters.ts groups.service.ts schemas.ts scim-admin.controller.ts scim-admin.service.ts scim-auth.guard.ts scim-auth.service.ts scim-errors.ts scim.controller.ts scim.module.ts users.service.ts
search/4
__tests__/1search.service.test.ts
search.controller.ts search.module.ts search.service.ts
sessions/3sessions.controller.ts sessions.module.ts sessions.service.ts
share/3share.controller.ts share.module.ts share.service.ts
sso/16
__tests__/4enforcement.test.ts oidc.verify.test.ts provisioning.test.ts saml.validate.test.ts
enforcement.ts oidc.controller.ts oidc.service.ts oidc.ts provisioning.service.ts saml.controller.ts saml.ts schemas.ts sso-admin.controller.ts sso-admin.service.ts sso-secrets.ts sso.module.ts
teams/4dto.ts teams.controller.ts teams.module.ts teams.service.ts
users/4dto.ts users.controller.ts users.module.ts users.service.ts
webhooks/3webhooks.controller.ts webhooks.module.ts webhooks.service.ts
workspaces/4dto.ts workspaces.controller.ts workspaces.module.ts workspaces.service.ts
types/1xmldom.d.ts
app.module.ts constants.ts main.ts
_probe_rawbody.cjs .env.example Dockerfile package.json README.md tsconfig.json vitest.config.ts
realtime/33
src/17auth.ts comments.ts config.ts event-source.ts http-store.ts index.ts metrics.ts notifications.ts page-updated-source.ts presence.ts prisma-store.ts protocol.ts rate-limit.ts room-key.ts rooms.ts server.ts update-log.ts
test/11auth.test.ts config.test.ts event-source.test.ts http-store.test.ts metrics-merge.test.ts page-updated-source.test.ts presence.test.ts protocol.test.ts rate-limit.test.ts rooms.test.ts task-0094-builders.test.ts
.env.example Dockerfile package.json README.md tsconfig.json
web/255
src/244
app/45
__tests__/1theme-tokens.test.ts
(app)/26
w/25
[workspace]/25
ask/1page.tsx
databases/1
[id]/1page.tsx
favorites/1page.tsx
files/1page.tsx
notifications/1page.tsx
page/4
[id]/4
[block]/1page.tsx
share/1page.tsx
layout.tsx page.tsx
search/1page.tsx
settings/10
appearance/1page.tsx
automations/3
[id]/1page.tsx
new/1page.tsx
page.tsx
billing/1page.tsx
developers/1page.tsx
members/1page.tsx
permissions/1page.tsx
teams/1page.tsx
page.tsx
shared/1page.tsx
templates/1page.tsx
trash/1page.tsx
layout.tsx page.tsx
layout.tsx
(auth)/4
login/1page.tsx
register/1page.tsx
reset-password/1page.tsx
layout.tsx
api/2
metrics/1route.ts
v1/1
health/1route.ts
f/1
[slug]/1page.tsx
health/1
ready/1route.ts
org/2
[orgId]/2
admin/2
integrations/1page.tsx
page.tsx
share/1
[id]/1page.tsx
error.tsx global-error.tsx globals.css layout.tsx not-found.tsx page.tsx providers.tsx
components/118
admin/10
org-admin/8ApiKeysTab.tsx AuditTab.tsx MembersTab.tsx OverviewTab.tsx PoliciesTab.tsx RetentionTab.tsx WebhooksTab.tsx WorkspacePicker.tsx
ScimCard.tsx SsoTabs.tsx
ai/6ai-panel.tsx ask-ai-submenu.tsx db-ai-dialog.tsx generate-page-dialog.tsx writing-actions-menu.tsx writing-diff.tsx
automations/5action-editor.tsx condition-editor.tsx lib.ts run-history.tsx trigger-editor.tsx
collaboration/6CursorLayer.tsx OfflineBanner.tsx PresenceBar.tsx README.md ReviewFailedDialog.tsx StatusChip.tsx
comments/3comment-panel.tsx comment-thread.tsx mention-input.tsx
database/27
__tests__/2cell-editor.test.tsx cell-renderer.test.tsx
AiExtractDialog.tsx AiFormulaDialog.tsx AiGenerateDialog.tsx board-view.tsx buttons-panel.tsx calendar-view.tsx cell-editor.tsx cell-renderer.tsx chart-view.tsx database-page-view.tsx database-toolbar.tsx database-view-renderer.tsx db-embedded.tsx filter-builder.tsx form-builder-view.tsx formula-editor.tsx gallery-view.tsx group-builder.tsx list-view.tsx property-editor.tsx record-header.tsx record-panel.tsx sort-builder.tsx table-view.tsx timeline-view.tsx
dialogs/7
__tests__/1move-to-dialog.test.tsx
create-dialog.tsx dialog.tsx file-permissions-dialog.tsx invite-dialog.tsx move-to-dialog.tsx share-dialog.tsx
editor/19
__tests__/3code-block-text.test.ts code-block.test.tsx expandable.test.tsx
block-card.tsx block-renderer.tsx block-save-queue.ts block-toolbar.tsx context-menu.tsx expandable.tsx history-popover.tsx inline-editor.tsx insert-dialog.tsx page-canvas.tsx page-spinner.tsx page-view.tsx selection-toolbar.tsx slash-menu.tsx template-dialog.tsx type-menu.tsx
files/4FileBlock.tsx FileBrowser.tsx FilePicker.tsx PreviewModal.tsx
form/4form-field.tsx form-renderer.tsx form-responses-view.tsx form-view.tsx
layout/8
__tests__/1page-tree-icon.test.tsx
command-palette.tsx factory-logo.tsx page-tree-node.tsx sidebar.tsx theme-provider.tsx topbar.tsx workspace-shell.tsx
mentions/3
__tests__/1mention-model.test.ts
mention-model.ts MentionPopup.tsx
search/3ask-ai-search.tsx results-list.tsx search-dialog.tsx
settings/10
api-keys/4ApiKeyCreateDialog.tsx ApiKeyRevealDialog.tsx ApiKeyTable.tsx ScopesBadge.tsx
webhooks/5CurlExamples.tsx DeliveryInspector.tsx EventExplorer.tsx OverviewTab.tsx WebhooksTab.tsx
settings-shell.tsx
ui/3confirm-dialog.tsx form.tsx toaster.tsx
hooks/21
__tests__/4realtime-frame-handler.test.ts use-blocks.test.ts use-database-options.test.ts use-workspace.test.tsx
realtime-frame-handler.ts use-ai-stream.ts use-auth.ts use-blocks.ts use-database.ts use-db-ai-stream.ts use-forms.ts use-members.ts use-mention-sources.ts use-notifications.ts use-offline-status.ts use-pages.ts use-presence-announcer.ts use-presence-toasts.ts use-presence.ts use-realtime.ts use-workspace.ts
lib/50
__tests__/21ai-database.test.ts ai-generate.test.ts ai-sse.test.ts ai-writing.test.ts api-keys.test.ts api-members.test.ts auth-refresh.test.ts auth-store.test.ts batch-queue-drop.test.ts block-content.test.ts clipboard-import.test.ts code-highlight.test.ts offline-status.test.ts presence-announcer.test.ts presence-color.test.ts presence-cursor.test.ts presence-emitter.test.ts realtime-url.test.ts search.test.ts utils.test.ts writing-diff.test.ts
files/7
__tests__/2picker.test.ts upload.test.ts
api.ts picker.ts upload-store.ts upload.ts use-file-uploads.ts
ai-database.ts ai.ts api-keys.ts api.ts auth-store.ts batch-queue.ts block-content.ts clipboard-import.ts code-highlight.ts constants.ts offline-status.ts org-admin.ts presence-announcer.ts presence-color.ts presence-cursor.ts presence-editor-views.ts presence-emitter.ts query-client.ts realtime.ts search.ts types.ts utils.ts
stores/8
__tests__/1block-patch-store.test.ts
block-patch-store.ts comment-pill-store.ts database-store.ts editor-store.ts offlineQueue.ts presence-store.ts ui-store.ts
instrumentation.ts middleware.ts
.env.example Dockerfile next-env.d.ts next.config.mjs package.json postcss.config.mjs README.md tailwind.config.ts tsconfig.json tsconfig.tsbuildinfo vitest.config.ts
worker/92
src/87
__tests__/13ai-embedding.test.ts ai-request.test.ts ai-usage-budget.test.ts audit-export.test.ts email-decision.test.ts encrypt.test.ts health-search-index-lag.test.ts mime.test.ts retention.test.ts s3-sign.test.ts search-index.test.ts smtp.test.ts webhook-sign.test.ts
health/1server.ts
lib/12
__tests__/1clamav.test.ts
clamav.ts encrypt.ts job-otel.ts mime.ts notify.ts prisma.ts redis-stream.ts redis.ts s3.ts sharp.ts smtp.ts
processors/56
automations/28
__tests__/3condition-evaluator.spec.ts executors.spec.ts run-pipeline.spec.ts
executors/17assign-user.ts call-integration.ts change-status.ts classify.ts create-notification.ts create-page.ts email-cap.ts helpers.ts integration-registry.ts permission.ts record-write.ts registry.ts run-ai-action.ts send-email.ts send-webhook.ts types.ts update-record.ts
condition-evaluator.ts dead-letter.ts log.ts matcher.ts pipeline.ts run-state.ts schedule.ts watchdog.ts
files/15
__tests__/4errors.test.ts scan-gate.test.ts status.test.ts temp.test.ts
steps/4finalize.ts metadata.ts scan.ts thumbnails.ts
cleanup.ts errors.ts generateThumbs.ts processor.ts status.ts temp.ts types.ts
search-index/4builders.ts grants.ts index.ts processor.ts
ai-embedding.ts ai.processor.ts audit-export.ts automations.processor.ts email-decision.processor.ts email.processor.ts maintenance.processor.ts retention.ts webhooks.processor.ts
config.ts events.ts index.ts logger.ts queues.ts
.env.example Dockerfile package.json README.md tsconfig.json
docs/73
api/17ai.md authentication.md automations.md blocks.md comments.md databases.md files.md forms.md openapi.yaml org-admin.md pages.md scim.md search.md sso.md versioning.md webhooks.md workspaces.md
architecture/10ai-architecture.md automation.md backend-architecture.md database-architecture.md frontend-architecture.md realtime-architecture.md search-architecture.md security-architecture.md storage.md system-architecture.md
database/2database-architecture.md redis-keys.md
development/8admin-guide.md coding-standards.md deployment.md development-guide.md files-pipeline.md perf-report.md performance.md testing.md
product/4functional-requirements.md non-functional-requirements.md product-requirements.md ux-requirements.md
requirements/1requirements-matrix.md
security/5audit-report.md files.md forms.md license-allowlist.md security-architecture.md
tasks/16phase-00-foundation.md phase-01-authentication.md phase-02-workspace.md phase-03-pages.md phase-04-editor.md phase-05-databases.md phase-06-relations.md phase-07-collaboration.md phase-08-search.md phase-09-files.md phase-10-forms.md phase-11-automation.md phase-12-ai.md phase-13-api.md phase-14-enterprise.md phase-15-production.md
ui/9ai.md automations.md design-system.md developers.md files.md forms.md org-admin.md scim.md sso.md
support-matrix.md
infrastructure/71
caddy/1Caddyfile
docker/6
init/101-extensions.sql
opensearch/1init-indices.json
.env.docker docker-compose.yml opensearch-init.sh README.md
kubernetes/40
observability/20
alertmanager/1alertmanager.yaml
grafana/8
dashboards/6ai.json api-overview.json infra.json realtime.json search.json workers-queues.json
provisioning/2
dashboards/1dashboards.yaml
datasources/1datasource.yaml
prometheus/3alerts.yaml recording-rules.yaml scrape.yaml
scripts/1sync-sources.mjs
alertmanager.yaml grafana.yaml kustomization.yaml namespace.yaml node-exporter.yaml otel-collector.yaml prometheus.yaml
postgres/7configmap.yaml cronjob-backup.yaml cronjob-opensearch-snapshot.yaml pgbouncer.yaml replica.yaml statefulset.yaml walg-iam-policy.yaml
api.yaml configmap.yaml ingress.yaml kustomization.yaml namespace.yaml networkpolicy.yaml pdb.yaml README.md realtime.yaml redis.yaml secret.example.yaml web.yaml worker.yaml
nginx/2nginx.conf README.md
observability/14
alertmanager/1alertmanager.yaml
grafana/8
dashboards/6ai.json api-overview.json infra.json realtime.json search.json workers-queues.json
provisioning/2
dashboards/1dashboards.yaml
datasources/1datasource.yaml
prometheus/3alerts.yaml recording-rules.yaml scrape.yaml
scripts/1dashboard-completeness.mjs
README.md
terraform/8compute.tf data.tf main.tf network.tf outputs.tf README.md security.tf variables.tf
packages/289
ai/24
src/20
providers/7anthropic.ts google.ts http.ts index.ts openai-compatible.ts rag.test.ts writing.test.ts
schemas/2database.ts generate.ts
adapters.test.ts config.test.ts config.ts gateway.test.ts gateway.ts index.ts tools.test.ts tools.ts types.ts usage.test.ts usage.ts
package.json README.md tsconfig.json vitest.config.ts
auth/11
src/7index.ts passwords.test.ts passwords.ts sessions.test.ts sessions.ts tokens.test.ts tokens.ts
package.json README.md tsconfig.json vitest.config.ts
collaboration/17
src/13awareness.ts codec.test.ts codec.ts index.ts lww.test.ts lww.ts mapping.ts offline.ts outbox.test.ts outbox.ts provider.test.ts provider.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
config/7
src/4eslint.base.js index.ts prettier.base.json tsconfig.base.json
package.json README.md tsconfig.json
database-client/5
src/2index.test.ts index.ts
package.json README.md tsconfig.json
database-engine/33
src/29
filter/2filter.test.ts index.ts
formula/8evaluator.ts formula.test.ts functions.ts index.ts lexer.ts parser.ts types.ts validate.ts
query/6group.ts index.ts paginate.ts pivot.ts query.test.ts sort.ts
relations/2index.ts relations.test.ts
rollups/2index.ts rollups.test.ts
values/5dates.ts index.ts registry.ts types.ts values.test.ts
views/3buttons.ts index.ts views.test.ts
index.ts
package.json README.md tsconfig.json vitest.config.ts
editor/25
src/21
marks/1highlight.ts
nodes/2equation.ts mention.ts
autosave.test.ts autosave.ts clipboard.test.ts clipboard.ts codec.test.ts codec.ts context-menu.test.ts context-menu.ts diff.test.ts diff.ts factory.ts history.test.ts history.ts index.ts positions.ts slash.test.ts slash.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
observability/20
src/16
__tests__/8db-redis-metrics.test.ts metrics.test.ts otel.test.ts pino-otel.test.ts prometheus.test.ts resource.test.ts scrubber.test.ts traceparent.test.ts
index.ts metrics.ts otel.ts pino-otel.ts prometheus.ts resource.ts scrubber.ts traceparent.ts
package.json README.md tsconfig.json vitest.config.ts
permissions/11
src/7index.ts levels.test.ts levels.ts resolve.test.ts resolve.ts rules.test.ts rules.ts
package.json README.md tsconfig.json vitest.config.ts
search/22
src/18chunking.test.ts chunking.ts client.test.ts client.ts embeddings.test.ts embeddings.ts hybrid.test.ts hybrid.ts index.ts indexer.test.ts indexer.ts mapping.test.ts mapping.ts queries.test.ts queries.ts rrf.test.ts rrf.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
shared/61
src/57
dto/17ai.dto.ts auth.dto.test.ts auth.dto.ts block.dto.ts browser-safety.test.ts comment.dto.ts database.dto.test.ts database.dto.ts file.dto.ts formula-ast.ts index.ts notification.dto.ts page.dto.ts search.dto.ts share.dto.ts webhook.dto.ts workspace.dto.ts
events/8audit-catalog.test.ts audit-catalog.ts audit-csv.test.ts audit-csv.ts audit-scope.test.ts audit-scope.ts catalog.test.ts catalog.ts
realtime/6comment-events.ts frames.test.ts frames.ts notification-policy.ts notification-prefs.ts task-0094.test.ts
automation.test.ts automation.ts constants.ts crypto.test.ts crypto.ts env.ts errors.ts events.test.ts events.ts forms.test.ts forms.ts index.ts markdown.test.ts markdown.ts mentions.test.ts mentions.ts net.test.ts net.ts positions.test.ts positions.ts presence.test.ts presence.ts security-headers.ts webhook-delivery.test.ts webhook-delivery.ts webhook-events.ts
package.json README.md tsconfig.json vitest.config.ts
storage/16
src/12client.test.ts client.ts index.ts keys.test.ts keys.ts mime.test.ts mime.ts presign.test.ts presign.ts thumbnails.test.ts thumbnails.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
ui/37
src/33
components/19avatar.tsx badge.tsx button.tsx checkbox.tsx command.tsx context-menu.tsx dialog.tsx dropdown-menu.tsx empty-state.tsx index.ts popover.tsx separator.tsx skeleton.tsx slider.tsx spinner.tsx switch.tsx tabs.tsx toast.tsx tooltip.tsx
color.ts command-filter.test.ts command-filter.ts focus.test.ts focus.ts formatters.test.ts formatters.ts index.ts theme.test.ts theme.ts toast-store.test.ts toast-store.ts tokens.test.ts tokens.ts
package.json README.md tsconfig.json vitest.config.ts
prisma/16
migrations/13
20260927000000_init/1migration.sql
20260928000000_search_documents_content_hash/1migration.sql
20260929000000_workspace_usage_storage_bytes/1migration.sql
20260930000000_form_submitted_notification_type/1migration.sql
20261001000000_ai_phase12/1migration.sql
20261002000000_webhooks_secret_encrypted_dlq/1migration.sql
20261002111436_webhook_delivery_latency/1migration.sql
20261002120000_org_admin_2fa_retention_unique/1migration.sql
20261002215124_audit_export/1migration.sql
20261003210000_perf_indexes_hot_paths/1migration.sql
20261004004500_perf_indexes_hot_paths_2/1migration.sql
migration_lock.toml README.md
schema.prisma seed.ts verify.ts
scripts/7backup-health.ts docs-completeness.mjs generate-openapi.mjs minio-init.ts opensearch-reindex.ts storage-orphans.ts verify-clean-clone.sh
tests/169
e2e/63
.auth/1user.json
fixtures/7
files/6clip.mp4 clip.wav clip.webm hero-1400.png note.docx note.txt
realtime.ts
mocks/2oidc-issuer.ts saml-idp.ts
playwright-report/3
data/2a6557bae9c2714a658968d4eb8534195c08ebda8.png e3092d07d00a16063e46385c02a559a9054fa847.webm
index.html
reports/1files-security.json
specs/34ai-database.spec.ts ai-generate.spec.ts ai-rag.spec.ts ai-security.spec.ts ai-writing.spec.ts ai.spec.ts api-keys.spec.ts appearance-theme.spec.ts auth.spec.ts automation.spec.ts collab-two-tab.spec.ts comments.spec.ts databases.spec.ts default-workspace-alias.spec.ts developers.spec.ts editor.spec.ts files.spec.ts final-flow.spec.ts forms.spec.ts oauth.spec.ts org-admin.spec.ts pages.spec.ts performance.spec.ts presence-cursors.spec.ts qa-paste-toggle.spec.ts realtime.spec.ts scim.spec.ts search-index-lag.spec.ts search.spec.ts sharing.spec.ts sso.spec.ts task-0094-comments-notifications.spec.ts webhooks.spec.ts workspace.spec.ts
test-results-final-flow/3
final-flow-Release-v1-0-fi-6c06b-w-state-carries-sequential--chromium-desktop/2test-failed-1.png trace.zip
.last-run.json
utils/4api.ts dnd.ts search-lag.ts wait.ts
.gitignore fixtures.ts global-setup.ts package.json playwright.config.ts playwright.final-flow.config.ts README.md tsconfig.json
fixtures/1
files/1make-sparse.ts
integration/60
ai-capability/2ai-app.capability.test.ts gateway.capability.test.ts
drill/2seed-drill.mjs verify-restore.mjs
fixtures/5
files/5clip.mp4 clip.wav doc.pdf hero-1400.png note.txt
helpers/3api.ts containers.ts seed.ts
realtime/11control-channel.test.ts convergence.test.ts flush-rebase-http.test.ts handshake.test.ts heartbeat-limits.test.ts lifecycle.test.ts presence.test.ts rebase.test.ts resume.test.ts rt-helpers.ts task-0094-fanout.test.ts
ai-chat.integration.test.ts ai-database.integration.test.ts ai-generate.integration.test.ts ai-rag.integration.test.ts ai-writing.integration.test.ts api-keys.integration.test.ts audit-completeness.integration.test.ts audit-export-s3.integration.test.ts auth.integration.test.ts automation-pipeline.integration.test.ts automations.integration.test.ts blocks.integration.test.ts cache-invalidation.integration.test.ts comments.integration.test.ts databases.integration.test.ts files-lifecycle.integration.test.ts files-pipeline.integration.test.ts files-security.integration.test.ts files.integration.test.ts forms-responses.integration.test.ts forms.integration.test.ts oauth.integration.test.ts openapi.contract.test.ts org-admin.integration.test.ts package.json pages.integration.test.ts permissions.integration.test.ts README.md replica-routing.integration.test.ts retention-engine.integration.test.ts scim.integration.test.ts search.integration.test.ts sso.integration.test.ts tsconfig.json vitest.config.ts webhooks.integration.test.ts workspace.integration.test.ts
performance/28
fixtures/1search-100k.ts
helpers/2seed-100k.mjs seed-1k-load.mjs
reports/7api-load-smoke.json database-load-smoke.json editor-local.json realtime-load.json search-full.json search-last.json search-reduced.json
api-load.js api.js archive.tar database-load.js database.js editor-local.mjs extract-100k-env.mjs package.json README.md realtime-1k.js realtime-load.js realtime.js run-realtime-load.sh search-search.k6.js search-summary.json seed-100k.mjs threshold-breach-demo.js verify-zero-op-loss.mjs
security/16
helpers/1app.ts
audit-report.test.ts auth-paths.test.ts deps.test.ts formula-fuzz.test.ts gitleaks-config.test.ts headers.test.ts images.test.ts oracle.ts package.json permission-fuzz.test.ts rate-limits.test.ts README.md tsconfig.json upload-abuse.test.ts vitest.config.ts
README.md
.dockerignore .env .env.bak-task0125 .env.example .eslintrc.json .gitignore .npmrc .prettierignore .prettierrc.json AGENTS.md ARCHITECTURE.md CHANGELOG.md CONTRIBUTING.md gitleaks.toml LICENSE package.json pnpm-lock.yaml pnpm-workspace.yaml PRODUCT-SPEC.md README.md ROADMAP.md SECURITY.md tsconfig.json turbo.json
phase-01-authentication.mddocs/tasks/phase-01-authentication.md← tasks41,222 B← Back to the app

Phase 1 — Authentication: Task Specifications

Purpose: The complete auth surface — registration + email verification, login with rotating opaque refresh tokens, session management, password reset, the shared packages/auth core, auth UI, the global API guard with rate limiting, audit events, password policy, and the E2E proof. No workspace/page logic. Tasks run in order, TASK-0013 → TASK-0022. Phase exit criteria (verbatim from ROADMAP.md): full auth flow E2E-tested; refresh rotation proven; audit events for login/logout/ reset; rate limiting on auth endpoints.

Task Table

ID Title Est.
TASK-0013 Registration + email verification M
TASK-0014 Login (access JWT + rotating refresh + sessions) L
TASK-0015 Logout, session list, all-sessions revoke M
TASK-0016 Password reset (request → single-use token → confirm) M
TASK-0017 packages/auth core (token service + rotation test matrix) M
TASK-0018 Auth pages UI (login/register/forgot/reset/verify) L
TASK-0019 API auth guard (global JwtAuthGuard + rate limiting) M
TASK-0020 Auth audit events M
TASK-0021 Password policy (min 10, letter+number, bcrypt cost 12) S
TASK-0022 E2E auth flows (tests/e2e/auth.spec.ts) M

Phase Completion Checklist

TASK-0013

Title

Registration + email verification

Objective

POST /api/v1/auth/register creates a user (hashed password, unique email) and starts 6-digit email verification (code in email; printed to the log in dev mode); POST /api/v1/auth/verify-email flips the user to verified.

Background

Phase 0 already provides the env layer (SMTP optional — unset = dev mail mode: logged, not sent), seeded demo users, and the User/EmailVerification models; register/verify are @Public until the guard lands (TASK-0019). The register service leaves a documented onUserRegistered(user) hook — the personal workspace is Phase 2 (TASK-0024).

Requirements

  1. apps/api/src/modules/auth/{auth.module.ts, auth.controller.ts, auth.service.ts, mail.service.ts}: POST /register { email, password, name } (zod: valid lowercased email, password ≥ 10 until TASK-0021, name 1–100) → 201 { user: { id, email, name, emailVerifiedAt: null } } (never the hash); duplicate → 409 email_taken; password bcrypt cost BCRYPT_ROUNDS (12); user created UNVERIFIED, status: ACTIVE.
  2. Verification: 6-digit code, EmailVerification.tokenHash = sha256(code) (code never stored), 24 h expiry, one active row per user (new request delete+insert in a transaction); MailService = nodemailer from SMTP env, dev mode = logger.info the code with devMode: true.
  3. POST /verify-email { email, code } → 200 { verified: true } (sets VERIFIED + emailVerifiedAt); wrong/expired → 400 invalid_verification_code; unknown email → 404 user_not_found; POST /resend-verification { email } → 202 (rate-limited by TASK-0019).

Acceptance Criteria

  1. Valid register → 201; the users row has a $2b$12$ hash, UNVERIFIED.
  2. Duplicate email → 409 email_taken; short password → 400 with field errors.
  3. With SMTP unset the code is in a structured log line (devMode: true) and tokenHash equals sha256 of the logged code (integration test re-derives it).
  4. Verify with the logged code → 200 + VERIFIED + emailVerifiedAt set; wrong code → 400.
  5. A second request supersedes the old code (old code stops working).

Backend Changes

Frontend Changes

None (UI is TASK-0018).

Database Changes

None (uses users, email_verifications from the Phase 0 migration).

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done

Endpoints + mail service live; unit + integration green; API docs written; dev-mode code logging verified.

TASK-0014

Title

Login (access JWT + rotating opaque refresh + sessions)

Objective

POST /api/v1/auth/login issues a 15-min HS256 access JWT + a 30-day opaque rotating refresh token (hashed at rest, one Session row per login); POST /refresh rotates with reuse detection that revokes the token family; GET /me returns the current user.

Background

Session and RefreshToken models exist; there is no familyId column — the family is the replacedBy chain (replaying a rotated token revokes everything from that point, walking the chain to the tip). Logic lives in token.service.ts (pure, repo-injected); TASK-0017 extracts it into packages/auth without duplication.

Requirements

  1. POST /login { email, password }: unknown email or wrong password → 401 { code: "invalid_credentials" } (identical body + timing — bcrypt always verifies against a dummy hash when the user is missing); success → 200 { accessToken, refreshToken, user }; access = JWT HS256 { sub, email, iat, exp: now+900 } with JWT_ACCESS_SECRET; refresh = randomBytes(48).toString("hex") (96 chars, clear, returned once).
  2. On login create Session (tokenHash = sha256(refresh), UA, IP — first-hop X-Forwarded-For only) + RefreshToken (expiresAt = now + REFRESH_TTL_DAYS, replacedBy: null); set User.lastLoginAt.
  3. POST /refresh { refreshToken }: (a) unknown hash → 401 invalid_refresh_token; (b) replaying a rotated token → reuse: revoke the token + every successor via the replacedBy chain + their Session rows → 401 refresh_reuse_detected; (c) valid → mark revokedAt, issue a new RefreshToken (old replacedBy = new id; the same Session survives via its tokenHash update), return the new pair.
  4. GET /me (Bearer) → 200 { id, email, name, avatarUrl, theme, locale, timezone, emailVerifiedAt, createdAt }; expired/invalid → 401 unauthorized; sub must match an existing ACTIVE user.

Acceptance Criteria

  1. Login with seeded demo credentials → 200, decodable 15-min token + 96-char refresh; sessions + refresh_tokens rows store sha256 only (DB scan: no plaintext).
  2. Wrong password → 401, no session row; body identical to unknown email.
  3. Refresh rotates: the old token immediately 401s (refresh_reuse_detected + family revoked); the new token works.
  4. login → refresh(t1→t2) → refresh(t2→t3) → replay t1 → t3 refresh 401 and all 3 Session rows REVOKED.
  5. Expired access token → 401 on /me; expired refresh → 401 (test clock or backdated expiresAt).

Backend Changes

Frontend Changes

None (UI is TASK-0018).

Database Changes

None (sessions + refresh_tokens suffice; family = replacedBy chain — design note in docs/security/auth-token-design.md, TASK-0019).

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done

Login/refresh/me with rotation + reuse detection; unit + integration prove criteria 1–5; docs written. The rotation logic is extraction-ready (pure, repo-injected).

TASK-0015

Title

Logout, session list, all-sessions revoke

Objective

Log out (revoke the current session), list active sessions, revoke any one, revoke all others — with the matching session manager in the app shell header.

Background

Session rows survive refresh rotations (only tokenHash changes), so a session is the stable "this login" identity; revoking a session must also kill its current refresh token.

Requirements

  1. POST /logout { refreshToken } → RefreshToken.revokedAt = now, Session REVOKED; 200 { loggedOut: true }; unknown token → same 200 (idempotent, no leak).
  2. GET /sessions (Bearer) → 200 { sessions: [{ id, userAgent, ipAddress, createdAt, expiresAt, isCurrent }] } — active only, newest first, max 50; isCurrent from the presented token's session.
  3. DELETE /sessions/:id (Bearer; caller's only → foreign id 404 session_not_found); POST /sessions/revoke-all → all but current → 200 { revokedCount }.
  4. A revoked session's access token stays valid until its 15-min expiry (documented trade-off — access TTL is the blast radius); its refresh 401s.
  5. UI: apps/web/src/components/auth/session-manager.tsx — gear menu in the shell header: "Log out" (current), "Other sessions" (device string + IP + current badge) with per-row revoke + "Log out of all other sessions"; TanStack Query invalidation per action; 401 → redirect /login.

Acceptance Criteria

  1. Logout → 200; that token then 401s on refresh; Session REVOKED; repeat logout → 200.
  2. List shows exactly the caller's active sessions with correct isCurrent; a second user's sessions never appear (two-user integration test).
  3. DELETE /sessions/:id revokes the target (its refresh dies) and 404s a foreign id; revoke-all revokes all but current (count asserted).
  4. UI: the manager renders the current session; revoking a row removes it; "all other" empties the list except current (asserted in TASK-0022).
  5. Post-revoke: refresh 401, but the unexpired access token still reads /me (asserted, documented).

Backend Changes

Frontend Changes

Database Changes

None (existing rows; revokedAt already settable).

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done

4 endpoints + session manager; unit + integration prove criteria 1–3 + 5; criterion 4 ready for TASK-0022; docs written.

TASK-0016

Title

Password reset (request → single-use token → confirm)

Objective

POST /api/v1/auth/password-reset/request emails a 1-hour single-use token (always 202, enumeration-proof); confirm sets the new password exactly once and revokes all of the user's sessions.

Background

PasswordReset (tokenHash, status PENDING/USED, expiresAt, usedAt) exists; User has no passwordChangedAt, so session revocation is the "logged out everywhere" mechanism; mail via MailService (dev mode prints the token).

Requirements

  1. request { email } → always 202 { status: "if_account_exists_token_sent" } (same body + timing for unknown email — dummy-verify path like login); if the user exists: create PasswordReset (PENDING, tokenHash = sha256(token), token = 32 random bytes hex, expiresAt = now + 1h, supersede prior PENDING rows), email a link ${FRONTEND_URL}/reset-password?token=….
  2. confirm { token, newPassword }: by sha256(token) — missing/expired → 400 reset_token_invalid; USED → 400 reset_token_used; else in a transaction: set User.passwordHash (bcrypt 12, policy per TASK-0021), row → USED + usedAt, revoke all the user's sessions + refresh tokens (TASK-0015 internal), audit password.reset.confirmed.
  3. Verified and unverified users both get it (reset doubles as "find my account" — documented); the link is single-use + 1-hour bounded even if clicked late.
  4. UI: apps/web/src/app/(auth)/reset-password/page.tsx reads ?token=, RHF + zod (password + confirm match, API policy errors), success state → "All sessions signed out — log in with your new password" + link to /login; expired/used → inline error banner (no reload).
  5. Request is rate-limited 10/min per IP (TASK-0019) + 5/hour per lowercased email (in-memory throttle, documented).

Acceptance Criteria

  1. Unknown email → 202, zero rows; real email → 202 + one PENDING row + (dev) token in the log.
  2. Confirm → 200, passwordHash changes (old password fails login, new works), row USED + usedAt.
  3. Same token again → 400 reset_token_used; backdated expiresAt → 400 reset_token_invalid.
  4. After confirm, every pre-reset session/refresh token for that user is revoked (DB assertion).
  5. Browser flow (request → follow logged token → new password → login with it) works; the expired-token page shows the banner (asserted in TASK-0022).

Backend Changes

Frontend Changes

Database Changes

None (existing rows; status/usedAt modeled).

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done

Both endpoints + reset page; unit + integration prove criteria 1–4; UI flow demonstrated (screenshot in PR); docs written.

TASK-0017

Title

packages/auth core (token service + rotation test matrix)

Objective

Extract the auth core into packages/auth: a framework-agnostic token service (JWT issue/verify + opaque refresh generate/hash) and the refresh-rotation state machine (rotate, expiry, reuse, family revoke) with a full unit test matrix — so apps/api calls it and Phase 7 realtime reuses it.

Background

Per AGENTS.md (reuse, no duplication, domain logic independent of DB/UI) the api service becomes a thin Prisma adapter.

Requirements

  1. packages/auth/package.json (name @factory/auth) + src/index.ts exports: createTokenService({ signSecret, accessTtlSeconds, refreshTtlDays, now? }), hashToken(value) (sha256 hex), generateOpaqueToken(bytes = 48), verifyAccessToken(token, signSecret) → { sub, email } | null.
  2. Rotation: issueSession(userId, { userAgent, ipAddress }); rotate(refreshToken, repo) → { ok: true, next } | { ok: false, reason: "invalid" | "expired" | "reuse", revokedTokenHashes } — repo is a package-defined interface (findByHash, revoke, create, revokeChain); the chain walk lives here, not in the api.
  3. Unit matrix (packages/auth/src/*.test.ts, ≥ 30 cases): rotation happy path; session identity preserved; double rotation (t1→t2→t3); reuse of t1 after t3 (chain revoked, t3 rejected next); reuse of the currently-valid token; access expiry (injected clock); refresh expiry; unknown token; 5-length replacedBy chain revoke-from-reuse-point; JWT tamper (sig flip → null); wrong sub; clock-injection determinism (seeded RNG option for tests).
  4. apps/api/src/modules/auth/token.service.ts deleted; api imports @factory/auth (a grep test asserts no token logic remains in the api).
  5. packages/auth coverage ≥ 80% lines/branches (TASK-0008 config already sets it — this task makes it true).

Acceptance Criteria

  1. pnpm test runs the matrix green (≥ 30 cases; invalid|expired|reuse all covered).
  2. For a 3-step chain, replaying step 1 revokes steps 2+3 (their hashes in revokedTokenHashes) and step 3's next refresh is rejected (fake repo records all revokes).
  3. The api contains no jsonwebtoken/crypto.randomBytes token logic (grep test); TASK-0014's integration tests pass unchanged (behavior-preserving).
  4. Coverage report shows packages/auth ≥ 80%.
  5. verifyAccessToken rejects bad signature, expired, malformed, missing sub — one test per reason.

Backend Changes

Frontend Changes

None

Database Changes

None

API Changes

None (behavior-preserving refactor).

Tests

Security

Documentation

Dependencies

Definition of Done

Package shipped with the full matrix; api a thin adapter; behavior proven unchanged; README + design doc updated.

TASK-0018

Title

Auth pages UI (login/register/forgot/reset/verify)

Objective

The five auth pages in apps/web — /login, /register, /forgot-password, /reset-password, /verify-email — with React Hook Form + Zod from @factory/shared/dto/auth, proper error states, theme-awareness, responsive layout, plus the minimal authenticated landing they redirect into.

Background

A logged-in Phase 1 user has no workspace yet (auto-creation is Phase 2, TASK-0024), so / is a minimal landing: greeting + the session manager (TASK-0015) + "Workspaces arrive in Phase 2". The shared zod DTOs live in packages/shared/src/dto/auth.ts (TASK-0013/0016) so form and API validate with the same schemas.

Requirements

  1. Routes apps/web/src/app/(auth)/{login, register,forgot-password, reset-password, verify-email}/page.tsx — server-rendered shell + client form components (apps/web/src/components/auth/*.tsx).
  2. Forms: RHF + zodResolver(sharedSchema); client messages + server code → field mapping (email_taken → email field, invalid_credentials → banner, invalid_verification_code → code field, reset_token_* → banner); 429 → "Too many attempts, try again in X min" banner.
  3. Login: store accessToken in memory (Zustand useAuthStore) + refreshToken in localStorage key factory.refresh (trade-off documented in docs/security/auth-token-design.md); on any API 401 → one automatic refresh then redirect /login?expired=1; success → /.
  4. Register success → "Check your email" (dev mode: show the code inline when NEXT_PUBLIC_DEV_MAIL_HINT=true — server flag, never in production builds) + link to /login; verify-email: email prefilled from query, 6-digit segmented code input, success → "Verified — sign in".
  5. Theme-aware via @factory/ui tokens (light/dark/system E2E-asserted); responsive (single column < 768 px, card ≥ 768 px); keyboard-accessible (label↔input, focus- visible, error text via aria-describedby).
  6. Guard: /login redirects authenticated users to /; / requires auth (apps/web/src/middleware.ts token-presence check only — real authz is server-side, TASK-0019).
  7. data-testid conventions for TASK-0022: login-form/-email/-password/-submit/- error, register-form/-email/-password/-name/-submit, forgot-form, verify-email-code/-submit, reset-password-form, session-menu, session-row, logout-button (full table in docs/ui/auth-pages.md).

Acceptance Criteria

  1. All five pages render in light and dark with no unthemed elements (E2E prefers-color-scheme emulation in TASK-0022).
  2. Client validation blocks empty/invalid email + short password before any network call; server email_taken lands as a field error (not an alert).
  3. Login with demo credentials stores tokens, redirects to /, the session menu shows the current session; logout returns to /login.
  4. An expired access token triggers exactly one silent refresh (network: 401 → POST /refresh → retried request 200).
  5. 375 px: all forms usable, no horizontal scroll, labels unclipped; Tab order logical (PR checklist).
  6. Unauthenticated / → /login; authenticated /login → /.

Backend Changes

None, except: resend-verification response gains an optional devCode field in development only (documented in docs/api/auth.md; absent in production) so the register "check your email" state can show it in dev.

Frontend Changes

Database Changes

None

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done

Five pages + landing + middleware + api wrapper; unit tests green; theme/responsive checklist passed; testid conventions documented + in use; docs written.

TASK-0019

Title

API auth guard (global JwtAuthGuard + rate limiting)

Objective

Make authentication mandatory on every api route except explicit @Public() routes; add 10/min-per-IP rate limiting on login/register/reset; document the cookie-free Bearer design.

Background

verifyAccessToken comes from packages/auth (TASK-0017); the guard is registered once via APP_GUARD in app.module.ts.

Requirements

  1. apps/api/src/common/guards/jwt-auth.guard.ts (APP_GUARD): reads Authorization: Bearer <token>; missing/malformed → 401 { code: "unauthorized" } (TASK-0010 envelope + requestId); verifies via @factory/auth; loads the user (exists + ACTIVE), attaches req.user = { id, email }; failures → 401 unauthorized / 401 account_disabled.
  2. @Public() (apps/api/src/common/decorators/public.decorator.ts via SetMetadata) — the guard skips it. Public list (explicit, documented): /api/v1/health, /health/ready, and POST /api/v1/auth/ register, login, refresh, verify-email, resend-verification, password-reset/request, password-reset/confirm. No other public routes — a route-map test enumerates every route and asserts each is @Public() or guarded (prevents silent public endpoints).
  3. Rate limiting (NestJS throttler or a small custom guard): POST /login, /register, /password-reset/request → 10 req / 60 s per IP → 429 { code: "rate_limited", retryAfterSeconds } + Retry-After header; resend-verification → 5/60 s per IP and 5/hour per email; per-IP store in-memory (single instance) with a Redis-adapter interface for multi-instance (documented; Phase 15 wires it).
  4. The guard reads no cookies and the api sets none — the cookie-free Bearer design (stateless scaling, no CSRF surface, explicit header) is written up with the client storage trade-off in one document.
  5. 401s do not leak which check failed beyond the two codes (bad signature vs expired → same unauthorized).

Acceptance Criteria

  1. Every non-@Public() route without a Bearer token → 401 unauthorized with the envelope (route-map test proves it per route).
  2. A valid token passes; an expired one → 401 (no distinction from bad signature in the body).
  3. 11 consecutive POST /login from one IP in 60 s → the 11th is 429 with Retry-After (the first 10 are not 429).
  4. Two IPs are limited independently (A exhausted, B still 200).
  5. The route-map test fails when a new non-public route is added without a token (deliberate violation demo, throwaway branch).
  6. No Set-Cookie on any endpoint (shared integration hook).

Backend Changes

Frontend Changes

None (the client already speaks Bearer; 429 handling is built, TASK-0018).

Database Changes

None (in-memory throttle; the Redis adapter is an interface only).

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done

Guard + public decorator + throttler live; route-map test proves no silent public routes; 429 proven per IP; no-Set-Cookie proven; design doc canonical.

TASK-0020

Title

Auth audit events

Objective

Write audit_logs rows for user.login, user.register, user.logout, password.reset.requested, password.reset.confirmed with actor/ip/ua captured — the foundation every later phase extends.

Background

AuditLog + AuditActorType exist and the request context (TASK-0010) carries requestId; audit failure must never break the request (log + degrade, never 500 the auth response — documented resilience rule, auditable in the log).

Requirements

  1. apps/api/src/modules/audit/{audit.module.ts, audit.service.ts}: record(event) where AuditEvent = { action, actorType, actorId, workspaceId?, pageId?, resourceType, resourceId, metadata? }; the service fills ipAddress (first-hop XFF, TASK-0014 rule) + userAgent from context; fire-and-forget insert with catch → logger.error (the request never fails because of audit).
  2. Emission points (exactly these five in Phase 1): user.register (on success, metadata { emailVerified: false }); user.login (metadata { sessionId }); user.logout (metadata { sessionId, revokeAll }); password.reset.requested (internal only — the API never reveals it); password.reset.confirmed (metadata { revokedSessionCount }).
  3. Reuse-detection revokes (TASK-0014) write a sixth event session.revoked_reuse (actorType: SYSTEM, actorId: "system", metadata { sessionId, replayedTokenHash } — the hash, never the token).
  4. Event names centralized in packages/shared/src/audit-events.ts (const map + AuditAction type) — the single source of truth (AGENTS.md).
  5. No PII beyond the model allows: email only in user.register metadata (documented exception); passwords/tokens never in metadata (source-scan test on the audit service).

Acceptance Criteria

  1. Each of the 6 events produces exactly one row with correct action, actorType, actorId, ipAddress, userAgent (integration test matches ip/ua to the request it sent).
  2. user.register metadata has the email; no row anywhere contains a password or token value (DB-wide metadata::text scan for the known strings → zero hits).
  3. Failing the audit insert (fault injection) leaves the auth response 200 + an error log line.
  4. audit-events.ts exports all six names; an emission using a string literal not in the map fails typecheck (the AuditAction type).
  5. session.revoked_reuse appears after a proven reuse (replay the TASK-0014 chain) with actorType: SYSTEM.

Backend Changes

Frontend Changes

None (the viewer is Phase 14; this task is write-side).

Database Changes

API Changes

None (no endpoint exposes audit in Phase 1).

Tests

Security

Documentation

Dependencies

Definition of Done

Audit module + 6 emission points live; all 5 criteria proven (incl. the no-secret scan); first Phase 1 migration applied; docs written.

TASK-0021

Title

Password policy (min 10, letter+number, bcrypt cost 12)

Objective

Make the password policy one tested, enforced rule — minimum 10 chars with ≥ 1 letter and ≥ 1 number, bcrypt cost 12 — applied at registration + reset confirm, with the policy itself in packages/auth.

Background

The policy defines character rules, the env (BCRYPT_ROUNDS, TASK-0005) defines the cost; web forms and the api share one definition.

Requirements

  1. packages/auth/src/password-policy.ts: PASSWORD_POLICY = { minLength: 10, requireLetter: true, requireNumber: true } (exported const), validatePassword(pw) → { valid, violations } ("too_short" | "missing_letter" | "missing_number"), and a passwordSchema (zod .refine mapping violations to stable codes); packages/shared/src/dto/auth.ts re-exports passwordSchema (forms validate with the same rule).
  2. hashPassword(pw, cost) in packages/auth (bcrypt, default 12; the api passes env.BCRYPT_ROUNDS); api boot assertion: cost outside 10–15 → exit 64.
  3. Wire into POST /register + POST /password-reset/confirm → 400 with details: { password: ["too_short", …] } (exact violation codes, not prose); login is not policy-checked (pre-existing hashes stay usable — documented).
  4. The seeded passwords (DemoPassw0rd!, AdminPassw0rd!) satisfy the policy — a test proves it (guards against a future tightening that breaks the TASK-0011 seed contract).
  5. Single-pass validation (collect all violations; no short-circuit timing that reveals which rule failed first — coarse timing bounds in a unit test).

Acceptance Criteria

  1. validatePassword matrix (≥ 15 cases): 9 chars → too_short; 10 letters → missing_number; 10 numbers → missing_letter; 10 mixed → valid; 50 chars → all violations reported; "Äbcdef1234" valid (unicode letter counts); spaces-only → all violations.
  2. Register short1a → 400 ["too_short"]; abcdefghij → ["missing_number"]; 1234567890 → ["missing_letter"]; ValidPass1 → 201.
  3. Reset-confirm with a violating password → 400 with codes; the old password still works (no partial state).
  4. Both seeded passwords pass validatePassword (seed-contract guard).
  5. Default cost 12: a unit-test hash yields a $2b$12$ prefix; api boot with BCRYPT_ROUNDS=8 exits 64.

Backend Changes

Frontend Changes

Database Changes

None

API Changes

Tests

Security

Documentation

Dependencies

Definition of Done

Policy in packages/auth with the full matrix; api + forms wired to the shared schema; cost assertion proven; seed guard green; docs written.

TASK-0022

Title

E2E auth flows (tests/e2e/auth.spec.ts)

Objective

Prove the Phase 1 exit criteria in Playwright (tests/e2e/auth.spec.ts): the full register → login → me → refresh → logout → re-login flow, the wrong-password error path, and rate limiting at the 11th attempt.

Background

The E2E api runs with SMTP unset → dev mail mode, so verification/reset codes are logged by the api container; the helper readDevCodeFromLogs() tails the saved compose log for the code line (dev-only; in CI the E2E job saves the compose logs).

Requirements

  1. tests/e2e/auth.spec.ts, test.describe.serial, unique email per run (e2e-<timestamp>@factory.local): - Flow 1 (happy path): /register (name/email/E2EPassw0rd1) → "Check your email" → read dev code → /verify-email?email=… → code → verified banner → /login with the new user's credentials → land on / (greeting shows the name) → GET /me via helper returns the user → refresh via helper → logout via data-testid=logout-button → back on /login → re-login → / again. - Flow 2 (wrong password): /login with the new email + wrong password → login-error banner "Invalid email or password", no redirect, form values preserved except password. - Flow 3 (rotation + reuse, API-level via helper): login → refresh (t1→t2) → replay t1 → 401 refresh_reuse_detected → t2 refresh 401 → GET /sessions (fresh login) shows 0 sessions. - Flow 4 (rate limit — LAST): from the same IP, send login attempts until the 11th in the 60 s window → the 11th is 429 with Retry-After; the /login UI then shows the "Too many attempts" banner (one browser submit after the helper loop). Budget: flows 1–3 make ≤ 9 throttled calls; assert the first 429 arrives no earlier than attempt 11 and no later than attempt 16 (window-start tolerance — deterministic enough for CI).
  2. Theme + responsive: emulate dark + light on /login — form card background colors differ (computed style); at 375 px the login form has no horizontal scroll.
  3. DB assertions via helpers/db.ts: after Flow 1, audit_logs has user.register + user.login (≥ 2) + user.logout for the new user; after Flow 3, session.revoked_reuse exists.
  4. Cleanup: unique emails only (no teardown delete — the E2E DB resets per CI run via a fresh volume); the spec passes twice in a row locally (a beforeAll waits for the throttle window to clear if the previous run was < 60 s ago).

Acceptance Criteria

  1. pnpm test:e2e (auth.spec.ts) green on the dev stack (compose --profile apps + migrated + seeded DB), clean machine — run recorded in the PR.
  2. Flow 1 completes end-to-end (verify via dev code) + re-login; the greeting shows the registered name.
  3. Flow 2 shows the exact banner and preserves form state.
  4. Flow 3 proves reuse detection through the real api (two 401s + empty session list) + the session.revoked_reuse row.
  5. Flow 4: first 429 within the 11–16 budget, Retry-After present, UI banner shown; the spec passes twice consecutively.
  6. Theme/responsive assertions pass in both schemes + at 375 px.

Backend Changes

None (a gap found is a bug in an earlier Phase 1 task — fix it in that module in the same PR, recorded).

Frontend Changes

Database Changes

None

API Changes

None

Tests

Security

Documentation

Dependencies

Definition of Done

tests/e2e/auth.spec.ts green twice locally + in CI; all six criteria proven; exit→test mapping recorded; docs updated. Phase 1 exit criteria fully met — Phase 2 may start.