wood-fired-tasks 1.12.0
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.md +112 -0
- package/CHANGELOG.md +271 -0
- package/CLAUDE.md +21 -0
- package/LICENSE +21 -0
- package/README.md +687 -0
- package/SECURITY.md +299 -0
- package/dist/api/hooks/error-handler.d.ts +6 -0
- package/dist/api/hooks/error-handler.js +128 -0
- package/dist/api/hooks/error-handler.js.map +1 -0
- package/dist/api/plugins/auth/index.d.ts +78 -0
- package/dist/api/plugins/auth/index.js +300 -0
- package/dist/api/plugins/auth/index.js.map +1 -0
- package/dist/api/plugins/auth/keys.d.ts +23 -0
- package/dist/api/plugins/auth/keys.js +100 -0
- package/dist/api/plugins/auth/keys.js.map +1 -0
- package/dist/api/plugins/auth/strategies/legacy.d.ts +46 -0
- package/dist/api/plugins/auth/strategies/legacy.js +87 -0
- package/dist/api/plugins/auth/strategies/legacy.js.map +1 -0
- package/dist/api/plugins/auth/strategies/pat.d.ts +37 -0
- package/dist/api/plugins/auth/strategies/pat.js +99 -0
- package/dist/api/plugins/auth/strategies/pat.js.map +1 -0
- package/dist/api/plugins/auth/strategies/session.d.ts +37 -0
- package/dist/api/plugins/auth/strategies/session.js +30 -0
- package/dist/api/plugins/auth/strategies/session.js.map +1 -0
- package/dist/api/plugins/auth/strategies/types.d.ts +12 -0
- package/dist/api/plugins/auth/strategies/types.js +2 -0
- package/dist/api/plugins/auth/strategies/types.js.map +1 -0
- package/dist/api/plugins/auth.d.ts +29 -0
- package/dist/api/plugins/auth.js +30 -0
- package/dist/api/plugins/auth.js.map +1 -0
- package/dist/api/plugins/swagger.d.ts +31 -0
- package/dist/api/plugins/swagger.js +83 -0
- package/dist/api/plugins/swagger.js.map +1 -0
- package/dist/api/routes/auth/auth-error.d.ts +23 -0
- package/dist/api/routes/auth/auth-error.js +68 -0
- package/dist/api/routes/auth/auth-error.js.map +1 -0
- package/dist/api/routes/auth/callback.d.ts +44 -0
- package/dist/api/routes/auth/callback.js +175 -0
- package/dist/api/routes/auth/callback.js.map +1 -0
- package/dist/api/routes/auth/csrf.d.ts +24 -0
- package/dist/api/routes/auth/csrf.js +75 -0
- package/dist/api/routes/auth/csrf.js.map +1 -0
- package/dist/api/routes/auth/device-code.d.ts +41 -0
- package/dist/api/routes/auth/device-code.js +51 -0
- package/dist/api/routes/auth/device-code.js.map +1 -0
- package/dist/api/routes/auth/device-disabled-stub.d.ts +33 -0
- package/dist/api/routes/auth/device-disabled-stub.js +16 -0
- package/dist/api/routes/auth/device-disabled-stub.js.map +1 -0
- package/dist/api/routes/auth/device-html.d.ts +41 -0
- package/dist/api/routes/auth/device-html.js +243 -0
- package/dist/api/routes/auth/device-html.js.map +1 -0
- package/dist/api/routes/auth/device-token.d.ts +38 -0
- package/dist/api/routes/auth/device-token.js +153 -0
- package/dist/api/routes/auth/device-token.js.map +1 -0
- package/dist/api/routes/auth/disabled-stub.d.ts +31 -0
- package/dist/api/routes/auth/disabled-stub.js +21 -0
- package/dist/api/routes/auth/disabled-stub.js.map +1 -0
- package/dist/api/routes/auth/index.d.ts +65 -0
- package/dist/api/routes/auth/index.js +47 -0
- package/dist/api/routes/auth/index.js.map +1 -0
- package/dist/api/routes/auth/login.d.ts +27 -0
- package/dist/api/routes/auth/login.js +91 -0
- package/dist/api/routes/auth/login.js.map +1 -0
- package/dist/api/routes/auth/logout.d.ts +29 -0
- package/dist/api/routes/auth/logout.js +43 -0
- package/dist/api/routes/auth/logout.js.map +1 -0
- package/dist/api/routes/comments/index.d.ts +3 -0
- package/dist/api/routes/comments/index.js +74 -0
- package/dist/api/routes/comments/index.js.map +1 -0
- package/dist/api/routes/comments/schemas.d.ts +37 -0
- package/dist/api/routes/comments/schemas.js +24 -0
- package/dist/api/routes/comments/schemas.js.map +1 -0
- package/dist/api/routes/dependencies/index.d.ts +3 -0
- package/dist/api/routes/dependencies/index.js +60 -0
- package/dist/api/routes/dependencies/index.js.map +1 -0
- package/dist/api/routes/dependencies/schemas.d.ts +24 -0
- package/dist/api/routes/dependencies/schemas.js +15 -0
- package/dist/api/routes/dependencies/schemas.js.map +1 -0
- package/dist/api/routes/events.d.ts +3 -0
- package/dist/api/routes/events.js +157 -0
- package/dist/api/routes/events.js.map +1 -0
- package/dist/api/routes/health.d.ts +27 -0
- package/dist/api/routes/health.js +231 -0
- package/dist/api/routes/health.js.map +1 -0
- package/dist/api/routes/me/index.d.ts +3 -0
- package/dist/api/routes/me/index.js +8 -0
- package/dist/api/routes/me/index.js.map +1 -0
- package/dist/api/routes/me/profile.d.ts +3 -0
- package/dist/api/routes/me/profile.js +58 -0
- package/dist/api/routes/me/profile.js.map +1 -0
- package/dist/api/routes/me/tokens.d.ts +3 -0
- package/dist/api/routes/me/tokens.js +410 -0
- package/dist/api/routes/me/tokens.js.map +1 -0
- package/dist/api/routes/projects/dependency-graph.d.ts +26 -0
- package/dist/api/routes/projects/dependency-graph.js +56 -0
- package/dist/api/routes/projects/dependency-graph.js.map +1 -0
- package/dist/api/routes/projects/index.d.ts +3 -0
- package/dist/api/routes/projects/index.js +96 -0
- package/dist/api/routes/projects/index.js.map +1 -0
- package/dist/api/routes/projects/schemas.d.ts +37 -0
- package/dist/api/routes/projects/schemas.js +27 -0
- package/dist/api/routes/projects/schemas.js.map +1 -0
- package/dist/api/routes/projects/topology.d.ts +27 -0
- package/dist/api/routes/projects/topology.js +51 -0
- package/dist/api/routes/projects/topology.js.map +1 -0
- package/dist/api/routes/tasks/index.d.ts +3 -0
- package/dist/api/routes/tasks/index.js +330 -0
- package/dist/api/routes/tasks/index.js.map +1 -0
- package/dist/api/routes/tasks/schemas.d.ts +316 -0
- package/dist/api/routes/tasks/schemas.js +129 -0
- package/dist/api/routes/tasks/schemas.js.map +1 -0
- package/dist/api/routes/web/index.d.ts +23 -0
- package/dist/api/routes/web/index.js +30 -0
- package/dist/api/routes/web/index.js.map +1 -0
- package/dist/api/routes/web/login.d.ts +20 -0
- package/dist/api/routes/web/login.js +20 -0
- package/dist/api/routes/web/login.js.map +1 -0
- package/dist/api/routes/web/me.d.ts +23 -0
- package/dist/api/routes/web/me.js +31 -0
- package/dist/api/routes/web/me.js.map +1 -0
- package/dist/api/routes/web/tokens.d.ts +29 -0
- package/dist/api/routes/web/tokens.js +65 -0
- package/dist/api/routes/web/tokens.js.map +1 -0
- package/dist/api/server.d.ts +44 -0
- package/dist/api/server.js +521 -0
- package/dist/api/server.js.map +1 -0
- package/dist/api/start.d.ts +1 -0
- package/dist/api/start.js +79 -0
- package/dist/api/start.js.map +1 -0
- package/dist/cli/api/client.d.ts +126 -0
- package/dist/cli/api/client.js +408 -0
- package/dist/cli/api/client.js.map +1 -0
- package/dist/cli/api/errors.d.ts +16 -0
- package/dist/cli/api/errors.js +20 -0
- package/dist/cli/api/errors.js.map +1 -0
- package/dist/cli/api/types.d.ts +205 -0
- package/dist/cli/api/types.js +15 -0
- package/dist/cli/api/types.js.map +1 -0
- package/dist/cli/auth/browser-open.d.ts +1 -0
- package/dist/cli/auth/browser-open.js +116 -0
- package/dist/cli/auth/browser-open.js.map +1 -0
- package/dist/cli/auth/credentials.d.ts +27 -0
- package/dist/cli/auth/credentials.js +179 -0
- package/dist/cli/auth/credentials.js.map +1 -0
- package/dist/cli/auth/device-flow.d.ts +75 -0
- package/dist/cli/auth/device-flow.js +149 -0
- package/dist/cli/auth/device-flow.js.map +1 -0
- package/dist/cli/bin/tasks-client.d.ts +2 -0
- package/dist/cli/bin/tasks-client.js +86 -0
- package/dist/cli/bin/tasks-client.js.map +1 -0
- package/dist/cli/bin/tasks.d.ts +3 -0
- package/dist/cli/bin/tasks.js +127 -0
- package/dist/cli/bin/tasks.js.map +1 -0
- package/dist/cli/commands/backup.d.ts +3 -0
- package/dist/cli/commands/backup.js +65 -0
- package/dist/cli/commands/backup.js.map +1 -0
- package/dist/cli/commands/claim.d.ts +2 -0
- package/dist/cli/commands/claim.js +37 -0
- package/dist/cli/commands/claim.js.map +1 -0
- package/dist/cli/commands/comment-add.d.ts +2 -0
- package/dist/cli/commands/comment-add.js +45 -0
- package/dist/cli/commands/comment-add.js.map +1 -0
- package/dist/cli/commands/comment-delete.d.ts +2 -0
- package/dist/cli/commands/comment-delete.js +56 -0
- package/dist/cli/commands/comment-delete.js.map +1 -0
- package/dist/cli/commands/comment-list.d.ts +2 -0
- package/dist/cli/commands/comment-list.js +56 -0
- package/dist/cli/commands/comment-list.js.map +1 -0
- package/dist/cli/commands/completed.d.ts +10 -0
- package/dist/cli/commands/completed.js +168 -0
- package/dist/cli/commands/completed.js.map +1 -0
- package/dist/cli/commands/completions.d.ts +10 -0
- package/dist/cli/commands/completions.js +179 -0
- package/dist/cli/commands/completions.js.map +1 -0
- package/dist/cli/commands/create.d.ts +2 -0
- package/dist/cli/commands/create.js +99 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/db-check.d.ts +3 -0
- package/dist/cli/commands/db-check.js +63 -0
- package/dist/cli/commands/db-check.js.map +1 -0
- package/dist/cli/commands/db-migrate-identities.d.ts +37 -0
- package/dist/cli/commands/db-migrate-identities.js +352 -0
- package/dist/cli/commands/db-migrate-identities.js.map +1 -0
- package/dist/cli/commands/db-mint-token.d.ts +33 -0
- package/dist/cli/commands/db-mint-token.js +150 -0
- package/dist/cli/commands/db-mint-token.js.map +1 -0
- package/dist/cli/commands/db.d.ts +10 -0
- package/dist/cli/commands/db.js +16 -0
- package/dist/cli/commands/db.js.map +1 -0
- package/dist/cli/commands/delete.d.ts +2 -0
- package/dist/cli/commands/delete.js +54 -0
- package/dist/cli/commands/delete.js.map +1 -0
- package/dist/cli/commands/dep-add.d.ts +2 -0
- package/dist/cli/commands/dep-add.js +43 -0
- package/dist/cli/commands/dep-add.js.map +1 -0
- package/dist/cli/commands/dep-list.d.ts +2 -0
- package/dist/cli/commands/dep-list.js +36 -0
- package/dist/cli/commands/dep-list.js.map +1 -0
- package/dist/cli/commands/dep-remove.d.ts +2 -0
- package/dist/cli/commands/dep-remove.js +55 -0
- package/dist/cli/commands/dep-remove.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.js +139 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/health.d.ts +2 -0
- package/dist/cli/commands/health.js +28 -0
- package/dist/cli/commands/health.js.map +1 -0
- package/dist/cli/commands/list.d.ts +2 -0
- package/dist/cli/commands/list.js +103 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/login.d.ts +2 -0
- package/dist/cli/commands/login.js +210 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/logout.d.ts +31 -0
- package/dist/cli/commands/logout.js +184 -0
- package/dist/cli/commands/logout.js.map +1 -0
- package/dist/cli/commands/project-create.d.ts +2 -0
- package/dist/cli/commands/project-create.js +44 -0
- package/dist/cli/commands/project-create.js.map +1 -0
- package/dist/cli/commands/project-delete.d.ts +2 -0
- package/dist/cli/commands/project-delete.js +54 -0
- package/dist/cli/commands/project-delete.js.map +1 -0
- package/dist/cli/commands/project-list.d.ts +2 -0
- package/dist/cli/commands/project-list.js +55 -0
- package/dist/cli/commands/project-list.js.map +1 -0
- package/dist/cli/commands/project-show.d.ts +2 -0
- package/dist/cli/commands/project-show.js +38 -0
- package/dist/cli/commands/project-show.js.map +1 -0
- package/dist/cli/commands/project-update.d.ts +2 -0
- package/dist/cli/commands/project-update.js +56 -0
- package/dist/cli/commands/project-update.js.map +1 -0
- package/dist/cli/commands/show.d.ts +2 -0
- package/dist/cli/commands/show.js +38 -0
- package/dist/cli/commands/show.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +3 -0
- package/dist/cli/commands/stats.js +81 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/subtask-create.d.ts +2 -0
- package/dist/cli/commands/subtask-create.js +85 -0
- package/dist/cli/commands/subtask-create.js.map +1 -0
- package/dist/cli/commands/subtask-list.d.ts +2 -0
- package/dist/cli/commands/subtask-list.js +61 -0
- package/dist/cli/commands/subtask-list.js.map +1 -0
- package/dist/cli/commands/topology.d.ts +16 -0
- package/dist/cli/commands/topology.js +52 -0
- package/dist/cli/commands/topology.js.map +1 -0
- package/dist/cli/commands/update.d.ts +2 -0
- package/dist/cli/commands/update.js +90 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/commands/whoami.d.ts +30 -0
- package/dist/cli/commands/whoami.js +201 -0
- package/dist/cli/commands/whoami.js.map +1 -0
- package/dist/cli/config/env.d.ts +4 -0
- package/dist/cli/config/env.js +25 -0
- package/dist/cli/config/env.js.map +1 -0
- package/dist/cli/output/error-handler.d.ts +5 -0
- package/dist/cli/output/error-handler.js +41 -0
- package/dist/cli/output/error-handler.js.map +1 -0
- package/dist/cli/output/formatters.d.ts +86 -0
- package/dist/cli/output/formatters.js +352 -0
- package/dist/cli/output/formatters.js.map +1 -0
- package/dist/cli/output/json-output.d.ts +39 -0
- package/dist/cli/output/json-output.js +50 -0
- package/dist/cli/output/json-output.js.map +1 -0
- package/dist/cli/output/spinner.d.ts +14 -0
- package/dist/cli/output/spinner.js +48 -0
- package/dist/cli/output/spinner.js.map +1 -0
- package/dist/cli/prompts/interactive.d.ts +25 -0
- package/dist/cli/prompts/interactive.js +80 -0
- package/dist/cli/prompts/interactive.js.map +1 -0
- package/dist/config/env.d.ts +182 -0
- package/dist/config/env.js +311 -0
- package/dist/config/env.js.map +1 -0
- package/dist/db/database.d.ts +11 -0
- package/dist/db/database.js +25 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/migrate.d.ts +10 -0
- package/dist/db/migrate.js +137 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/migrations/001-initial-schema.d.ts +3 -0
- package/dist/db/migrations/001-initial-schema.js +100 -0
- package/dist/db/migrations/001-initial-schema.js.map +1 -0
- package/dist/db/migrations/002-task-hierarchy-and-dependencies.d.ts +3 -0
- package/dist/db/migrations/002-task-hierarchy-and-dependencies.js +42 -0
- package/dist/db/migrations/002-task-hierarchy-and-dependencies.js.map +1 -0
- package/dist/db/migrations/003-comments-and-estimates.d.ts +3 -0
- package/dist/db/migrations/003-comments-and-estimates.js +36 -0
- package/dist/db/migrations/003-comments-and-estimates.js.map +1 -0
- package/dist/db/migrations/004-claim-protocol.d.ts +3 -0
- package/dist/db/migrations/004-claim-protocol.js +41 -0
- package/dist/db/migrations/004-claim-protocol.js.map +1 -0
- package/dist/db/migrations/005-backlogged-status.d.ts +3 -0
- package/dist/db/migrations/005-backlogged-status.js +156 -0
- package/dist/db/migrations/005-backlogged-status.js.map +1 -0
- package/dist/db/migrations/006-slack-channel-subscriptions.d.ts +3 -0
- package/dist/db/migrations/006-slack-channel-subscriptions.js +23 -0
- package/dist/db/migrations/006-slack-channel-subscriptions.js.map +1 -0
- package/dist/db/migrations/007-completed-at.d.ts +19 -0
- package/dist/db/migrations/007-completed-at.js +36 -0
- package/dist/db/migrations/007-completed-at.js.map +1 -0
- package/dist/db/migrations/008-identity-tables.d.ts +21 -0
- package/dist/db/migrations/008-identity-tables.js +84 -0
- package/dist/db/migrations/008-identity-tables.js.map +1 -0
- package/dist/db/migrations/009-parallel-fk-columns.d.ts +33 -0
- package/dist/db/migrations/009-parallel-fk-columns.js +62 -0
- package/dist/db/migrations/009-parallel-fk-columns.js.map +1 -0
- package/dist/db/migrations/010-identity-uniqueness-indexes.d.ts +47 -0
- package/dist/db/migrations/010-identity-uniqueness-indexes.js +65 -0
- package/dist/db/migrations/010-identity-uniqueness-indexes.js.map +1 -0
- package/dist/db/migrations/011-acceptance-criteria.d.ts +30 -0
- package/dist/db/migrations/011-acceptance-criteria.js +41 -0
- package/dist/db/migrations/011-acceptance-criteria.js.map +1 -0
- package/dist/db/migrations/012-verification-evidence.d.ts +41 -0
- package/dist/db/migrations/012-verification-evidence.js +49 -0
- package/dist/db/migrations/012-verification-evidence.js.map +1 -0
- package/dist/events/event-bus.d.ts +101 -0
- package/dist/events/event-bus.js +184 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/events/sse-manager.d.ts +79 -0
- package/dist/events/sse-manager.js +220 -0
- package/dist/events/sse-manager.js.map +1 -0
- package/dist/events/types.d.ts +43 -0
- package/dist/events/types.js +22 -0
- package/dist/events/types.js.map +1 -0
- package/dist/index.d.ts +110 -0
- package/dist/index.js +209 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/audit/schema.d.ts +201 -0
- package/dist/lib/audit/schema.js +141 -0
- package/dist/lib/audit/schema.js.map +1 -0
- package/dist/lib/decompose/schema.d.ts +157 -0
- package/dist/lib/decompose/schema.js +138 -0
- package/dist/lib/decompose/schema.js.map +1 -0
- package/dist/lib/loop-run/integration-audit-schema.d.ts +78 -0
- package/dist/lib/loop-run/integration-audit-schema.js +68 -0
- package/dist/lib/loop-run/integration-audit-schema.js.map +1 -0
- package/dist/lib/loop-run/schema.d.ts +49 -0
- package/dist/lib/loop-run/schema.js +67 -0
- package/dist/lib/loop-run/schema.js.map +1 -0
- package/dist/mcp/errors.d.ts +11 -0
- package/dist/mcp/errors.js +29 -0
- package/dist/mcp/errors.js.map +1 -0
- package/dist/mcp/identity-resolution.d.ts +76 -0
- package/dist/mcp/identity-resolution.js +189 -0
- package/dist/mcp/identity-resolution.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +126 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/remote/index.d.ts +21 -0
- package/dist/mcp/remote/index.js +95 -0
- package/dist/mcp/remote/index.js.map +1 -0
- package/dist/mcp/remote/register-tools.d.ts +26 -0
- package/dist/mcp/remote/register-tools.js +751 -0
- package/dist/mcp/remote/register-tools.js.map +1 -0
- package/dist/mcp/remote/rest-client.d.ts +66 -0
- package/dist/mcp/remote/rest-client.js +300 -0
- package/dist/mcp/remote/rest-client.js.map +1 -0
- package/dist/mcp/resources/events.d.ts +28 -0
- package/dist/mcp/resources/events.js +98 -0
- package/dist/mcp/resources/events.js.map +1 -0
- package/dist/mcp/server.d.ts +59 -0
- package/dist/mcp/server.js +72 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/comment-tools.d.ts +12 -0
- package/dist/mcp/tools/comment-tools.js +115 -0
- package/dist/mcp/tools/comment-tools.js.map +1 -0
- package/dist/mcp/tools/dependency-tools.d.ts +3 -0
- package/dist/mcp/tools/dependency-tools.js +91 -0
- package/dist/mcp/tools/dependency-tools.js.map +1 -0
- package/dist/mcp/tools/health-tools.d.ts +9 -0
- package/dist/mcp/tools/health-tools.js +82 -0
- package/dist/mcp/tools/health-tools.js.map +1 -0
- package/dist/mcp/tools/project-tools.d.ts +13 -0
- package/dist/mcp/tools/project-tools.js +167 -0
- package/dist/mcp/tools/project-tools.js.map +1 -0
- package/dist/mcp/tools/task-tools.d.ts +41 -0
- package/dist/mcp/tools/task-tools.js +434 -0
- package/dist/mcp/tools/task-tools.js.map +1 -0
- package/dist/mcp/tools/topology-tools.d.ts +14 -0
- package/dist/mcp/tools/topology-tools.js +46 -0
- package/dist/mcp/tools/topology-tools.js.map +1 -0
- package/dist/repositories/api-token.repository.d.ts +40 -0
- package/dist/repositories/api-token.repository.js +63 -0
- package/dist/repositories/api-token.repository.js.map +1 -0
- package/dist/repositories/comment.repository.d.ts +17 -0
- package/dist/repositories/comment.repository.js +73 -0
- package/dist/repositories/comment.repository.js.map +1 -0
- package/dist/repositories/dependency.repository.d.ts +19 -0
- package/dist/repositories/dependency.repository.js +55 -0
- package/dist/repositories/dependency.repository.js.map +1 -0
- package/dist/repositories/errors.d.ts +29 -0
- package/dist/repositories/errors.js +48 -0
- package/dist/repositories/errors.js.map +1 -0
- package/dist/repositories/interfaces.d.ts +200 -0
- package/dist/repositories/interfaces.js +2 -0
- package/dist/repositories/interfaces.js.map +1 -0
- package/dist/repositories/project.repository.d.ts +21 -0
- package/dist/repositories/project.repository.js +97 -0
- package/dist/repositories/project.repository.js.map +1 -0
- package/dist/repositories/row-mapper.d.ts +40 -0
- package/dist/repositories/row-mapper.js +38 -0
- package/dist/repositories/row-mapper.js.map +1 -0
- package/dist/repositories/task.repository.d.ts +45 -0
- package/dist/repositories/task.repository.js +612 -0
- package/dist/repositories/task.repository.js.map +1 -0
- package/dist/repositories/types.d.ts +20 -0
- package/dist/repositories/types.js +11 -0
- package/dist/repositories/types.js.map +1 -0
- package/dist/repositories/user.repository.d.ts +121 -0
- package/dist/repositories/user.repository.js +209 -0
- package/dist/repositories/user.repository.js.map +1 -0
- package/dist/schemas/comment.schema.d.ts +24 -0
- package/dist/schemas/comment.schema.js +27 -0
- package/dist/schemas/comment.schema.js.map +1 -0
- package/dist/schemas/dependency-graph.schema.d.ts +181 -0
- package/dist/schemas/dependency-graph.schema.js +98 -0
- package/dist/schemas/dependency-graph.schema.js.map +1 -0
- package/dist/schemas/dependency.schema.d.ts +9 -0
- package/dist/schemas/dependency.schema.js +16 -0
- package/dist/schemas/dependency.schema.js.map +1 -0
- package/dist/schemas/idempotency.schema.d.ts +18 -0
- package/dist/schemas/idempotency.schema.js +22 -0
- package/dist/schemas/idempotency.schema.js.map +1 -0
- package/dist/schemas/task.schema.d.ts +369 -0
- package/dist/schemas/task.schema.js +276 -0
- package/dist/schemas/task.schema.js.map +1 -0
- package/dist/schemas/topology.schema.d.ts +56 -0
- package/dist/schemas/topology.schema.js +48 -0
- package/dist/schemas/topology.schema.js.map +1 -0
- package/dist/services/auth-audit.d.ts +46 -0
- package/dist/services/auth-audit.js +28 -0
- package/dist/services/auth-audit.js.map +1 -0
- package/dist/services/claim-release.service.d.ts +42 -0
- package/dist/services/claim-release.service.js +90 -0
- package/dist/services/claim-release.service.js.map +1 -0
- package/dist/services/comment.service.d.ts +44 -0
- package/dist/services/comment.service.js +96 -0
- package/dist/services/comment.service.js.map +1 -0
- package/dist/services/dependency-graph.service.d.ts +33 -0
- package/dist/services/dependency-graph.service.js +453 -0
- package/dist/services/dependency-graph.service.js.map +1 -0
- package/dist/services/dependency.service.d.ts +32 -0
- package/dist/services/dependency.service.js +79 -0
- package/dist/services/dependency.service.js.map +1 -0
- package/dist/services/device-flow-store.d.ts +155 -0
- package/dist/services/device-flow-store.js +323 -0
- package/dist/services/device-flow-store.js.map +1 -0
- package/dist/services/errors.d.ts +28 -0
- package/dist/services/errors.js +44 -0
- package/dist/services/errors.js.map +1 -0
- package/dist/services/idempotency.service.d.ts +37 -0
- package/dist/services/idempotency.service.js +54 -0
- package/dist/services/idempotency.service.js.map +1 -0
- package/dist/services/identity-seeder.d.ts +56 -0
- package/dist/services/identity-seeder.js +131 -0
- package/dist/services/identity-seeder.js.map +1 -0
- package/dist/services/oidc-boot.d.ts +73 -0
- package/dist/services/oidc-boot.js +66 -0
- package/dist/services/oidc-boot.js.map +1 -0
- package/dist/services/oidc-client.d.ts +99 -0
- package/dist/services/oidc-client.js +108 -0
- package/dist/services/oidc-client.js.map +1 -0
- package/dist/services/pat-hash.d.ts +23 -0
- package/dist/services/pat-hash.js +73 -0
- package/dist/services/pat-hash.js.map +1 -0
- package/dist/services/pat-touch-debounce.d.ts +65 -0
- package/dist/services/pat-touch-debounce.js +82 -0
- package/dist/services/pat-touch-debounce.js.map +1 -0
- package/dist/services/project.service.d.ts +41 -0
- package/dist/services/project.service.js +133 -0
- package/dist/services/project.service.js.map +1 -0
- package/dist/services/slack.service.d.ts +31 -0
- package/dist/services/slack.service.js +52 -0
- package/dist/services/slack.service.js.map +1 -0
- package/dist/services/task.service.d.ts +151 -0
- package/dist/services/task.service.js +425 -0
- package/dist/services/task.service.js.map +1 -0
- package/dist/services/topology.service.d.ts +65 -0
- package/dist/services/topology.service.js +170 -0
- package/dist/services/topology.service.js.map +1 -0
- package/dist/services/user-upsert.d.ts +43 -0
- package/dist/services/user-upsert.js +53 -0
- package/dist/services/user-upsert.js.map +1 -0
- package/dist/services/workflow-engine.d.ts +93 -0
- package/dist/services/workflow-engine.js +250 -0
- package/dist/services/workflow-engine.js.map +1 -0
- package/dist/slack/commands/tasks-command.d.ts +88 -0
- package/dist/slack/commands/tasks-command.js +920 -0
- package/dist/slack/commands/tasks-command.js.map +1 -0
- package/dist/slack/formatters/project-formatter.d.ts +19 -0
- package/dist/slack/formatters/project-formatter.js +94 -0
- package/dist/slack/formatters/project-formatter.js.map +1 -0
- package/dist/slack/notifier.d.ts +41 -0
- package/dist/slack/notifier.js +111 -0
- package/dist/slack/notifier.js.map +1 -0
- package/dist/slack/repositories/channel-subscription.repository.d.ts +25 -0
- package/dist/slack/repositories/channel-subscription.repository.js +51 -0
- package/dist/slack/repositories/channel-subscription.repository.js.map +1 -0
- package/dist/slack/task-formatter.d.ts +31 -0
- package/dist/slack/task-formatter.js +151 -0
- package/dist/slack/task-formatter.js.map +1 -0
- package/dist/slack/user-identity.d.ts +37 -0
- package/dist/slack/user-identity.js +70 -0
- package/dist/slack/user-identity.js.map +1 -0
- package/dist/types/identity.d.ts +84 -0
- package/dist/types/identity.js +6 -0
- package/dist/types/identity.js.map +1 -0
- package/dist/types/task.d.ts +202 -0
- package/dist/types/task.js +17 -0
- package/dist/types/task.js.map +1 -0
- package/dist/utils/cycle-detector.d.ts +25 -0
- package/dist/utils/cycle-detector.js +86 -0
- package/dist/utils/cycle-detector.js.map +1 -0
- package/dist/utils/exit-codes.d.ts +64 -0
- package/dist/utils/exit-codes.js +57 -0
- package/dist/utils/exit-codes.js.map +1 -0
- package/dist/utils/is-main.d.ts +12 -0
- package/dist/utils/is-main.js +24 -0
- package/dist/utils/is-main.js.map +1 -0
- package/dist/utils/version.d.ts +1 -0
- package/dist/utils/version.js +22 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/web/html.d.ts +73 -0
- package/dist/web/html.js +154 -0
- package/dist/web/html.js.map +1 -0
- package/dist/web/pages/device.d.ts +19 -0
- package/dist/web/pages/device.js +76 -0
- package/dist/web/pages/device.js.map +1 -0
- package/dist/web/pages/error.d.ts +6 -0
- package/dist/web/pages/error.js +23 -0
- package/dist/web/pages/error.js.map +1 -0
- package/dist/web/pages/login.d.ts +5 -0
- package/dist/web/pages/login.js +22 -0
- package/dist/web/pages/login.js.map +1 -0
- package/dist/web/pages/me.d.ts +9 -0
- package/dist/web/pages/me.js +37 -0
- package/dist/web/pages/me.js.map +1 -0
- package/dist/web/pages/tokens.d.ts +20 -0
- package/dist/web/pages/tokens.js +89 -0
- package/dist/web/pages/tokens.js.map +1 -0
- package/dist/web/session-constants.d.ts +21 -0
- package/dist/web/session-constants.js +22 -0
- package/dist/web/session-constants.js.map +1 -0
- package/dist/web/session-flash.d.ts +15 -0
- package/dist/web/session-flash.js +11 -0
- package/dist/web/session-flash.js.map +1 -0
- package/dist/web/session-user.d.ts +59 -0
- package/dist/web/session-user.js +44 -0
- package/dist/web/session-user.js.map +1 -0
- package/docs/AGENT_CONTEXT.md +280 -0
- package/docs/README.md +49 -0
- package/llms.txt +33 -0
- package/package.json +129 -0
package/SECURITY.md
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
For repository structure and agent-context entry, see [`AGENTS.md`](AGENTS.md).
|
|
4
|
+
|
|
5
|
+
We take the security of wood-fired-tasks seriously. This document explains
|
|
6
|
+
which versions receive security fixes, how to report a vulnerability, and
|
|
7
|
+
what is in scope.
|
|
8
|
+
|
|
9
|
+
## Supported Versions
|
|
10
|
+
|
|
11
|
+
Only the current `main` branch and the most recent tagged release receive
|
|
12
|
+
security updates. Older tags are provided as-is.
|
|
13
|
+
|
|
14
|
+
| Version | Supported |
|
|
15
|
+
| ----------------- | ------------------ |
|
|
16
|
+
| `main` (HEAD) | :white_check_mark: |
|
|
17
|
+
| `v1.12` (latest) | :white_check_mark: |
|
|
18
|
+
| `v1.0` – `v1.11` | :x: |
|
|
19
|
+
|
|
20
|
+
"Latest" tracks whichever tag is most recent on GitHub; at the time of
|
|
21
|
+
writing that is `v1.12`. If you are reading this on an older checkout,
|
|
22
|
+
verify the current latest release via
|
|
23
|
+
`git tag --sort=-creatordate | head -1` or the GitHub Releases page.
|
|
24
|
+
|
|
25
|
+
## Reporting a Vulnerability
|
|
26
|
+
|
|
27
|
+
**Preferred:** open a private report via GitHub Security Advisories:
|
|
28
|
+
|
|
29
|
+
https://github.com/Wood-Fired-Games/wood-fired-tasks/security/advisories/new
|
|
30
|
+
|
|
31
|
+
**Fallback:** email `security@woodfiredgames.com` with steps to reproduce,
|
|
32
|
+
affected version/commit, and the impact you observed. Please do not file
|
|
33
|
+
public GitHub issues for suspected vulnerabilities.
|
|
34
|
+
|
|
35
|
+
We will:
|
|
36
|
+
|
|
37
|
+
- Acknowledge your report within **5 business days**.
|
|
38
|
+
- Aim to ship a fix or documented workaround within **30 days** for issues
|
|
39
|
+
rated high or critical. Lower-severity issues are batched into the next
|
|
40
|
+
routine release.
|
|
41
|
+
- Credit reporters in the release notes unless you ask us not to.
|
|
42
|
+
|
|
43
|
+
## Scope
|
|
44
|
+
|
|
45
|
+
**In scope:**
|
|
46
|
+
|
|
47
|
+
- The Fastify REST API (TypeScript, Node ≥22) under `src/api/` — routes,
|
|
48
|
+
plugins (auth, rate-limit, SSE), and request/response validation.
|
|
49
|
+
- The MCP server under `src/mcp/` — both transports: the **stdio** server
|
|
50
|
+
(`npm run mcp:start` / `npm run mcp:dev` / installed Claude Code stdio
|
|
51
|
+
target) and the **remote HTTP** server (`npm run mcp:remote`), including
|
|
52
|
+
its tool implementations and prompt/resource handlers.
|
|
53
|
+
- The `tasks` CLI under `src/cli/` — command parsers, HTTP client, and the
|
|
54
|
+
small set of offline subcommands that touch SQLite directly
|
|
55
|
+
(`backup`, `doctor`, `stats`, `db-check`, `completed`).
|
|
56
|
+
- The Slack integration under `src/slack/` — Bolt subprocess, slash-command
|
|
57
|
+
handlers, signed-request verification, and the EventBus → Slack notifier
|
|
58
|
+
path.
|
|
59
|
+
- The shared service / repository / workflow layer under `src/services/`,
|
|
60
|
+
`src/repositories/`, and `src/events/` that all four entry points sit on
|
|
61
|
+
top of.
|
|
62
|
+
|
|
63
|
+
**Out of scope:**
|
|
64
|
+
|
|
65
|
+
- Third-party dependencies — please report those directly upstream
|
|
66
|
+
(e.g. Fastify, `@slack/bolt`, `@modelcontextprotocol/sdk`,
|
|
67
|
+
`better-sqlite3`, `commander`, `zod`).
|
|
68
|
+
- User-side customizations layered on top of the project, including
|
|
69
|
+
custom auth proxies in front of the API, self-hosted reverse proxies,
|
|
70
|
+
or forked deployments with modified middleware.
|
|
71
|
+
- Findings from automated scanners (SAST/DAST/dependency CVE noise)
|
|
72
|
+
submitted without a working proof-of-concept against this codebase.
|
|
73
|
+
|
|
74
|
+
## What We Consider Security-Relevant
|
|
75
|
+
|
|
76
|
+
Issues we will prioritize include, but are not limited to:
|
|
77
|
+
|
|
78
|
+
- Authentication bypass on any endpoint — reaching a `/api/v1` route
|
|
79
|
+
without a valid PAT, session, or `X-API-Key` credential, or bypassing
|
|
80
|
+
the SSE auth path. (Note: there is no separate authorization layer to
|
|
81
|
+
bypass — see "Authentication Is Not Authorization" below. Any valid
|
|
82
|
+
credential is already full-access.)
|
|
83
|
+
- Secrets exposure (API keys, Slack tokens, `.env` leakage, log
|
|
84
|
+
scrubbing gaps in pino redaction, Slack signing-secret disclosure).
|
|
85
|
+
- SQL injection or FTS5 injection in task/comment/project queries
|
|
86
|
+
(better-sqlite3 prepared statements, search filters, sort/order
|
|
87
|
+
parameters).
|
|
88
|
+
- Server-Side Request Forgery (SSRF) in any outbound HTTP call.
|
|
89
|
+
- Prompt-injection vectors via MCP tool descriptions, task fields,
|
|
90
|
+
comment bodies, or resource contents that cause an MCP client to
|
|
91
|
+
take unintended action.
|
|
92
|
+
- Signature-verification bypass on the Slack webhook / events endpoint,
|
|
93
|
+
or replay of signed Slack requests.
|
|
94
|
+
- Anything that allows **unauthenticated** mutation of tasks, projects,
|
|
95
|
+
comments, dependencies, or Slack channel subscriptions — i.e. mutating
|
|
96
|
+
state without presenting any valid credential, or escalating
|
|
97
|
+
read-only access to write access on either MCP transport. (Mutation by
|
|
98
|
+
an *authenticated* identity is by design — every credential is
|
|
99
|
+
full-access; see "Authentication Is Not Authorization".)
|
|
100
|
+
|
|
101
|
+
Thank you for helping keep wood-fired-tasks and its users safe.
|
|
102
|
+
|
|
103
|
+
## Authentication Architecture
|
|
104
|
+
|
|
105
|
+
As of v1.6, the REST API supports three authentication strategies, tried
|
|
106
|
+
in order by a Fastify chain plugin (`src/api/plugins/auth/index.ts`). The
|
|
107
|
+
first strategy that produces a valid `request.user` wins; the request
|
|
108
|
+
proceeds with that user's id stamped onto every write (`created_by_user_id`,
|
|
109
|
+
`assignee_user_id`, `author_user_id`) and surfaced in the per-request audit
|
|
110
|
+
log (`user_id`, `token_id`, `auth_method`).
|
|
111
|
+
|
|
112
|
+
| Order | Strategy | Credential | Wire format |
|
|
113
|
+
|-------|----------|------------|-------------|
|
|
114
|
+
| 1 | **PAT (Personal Access Token)** | A token row in `api_tokens` | `Authorization: Bearer wft_pat_<…>` |
|
|
115
|
+
| 2 | **Session** | An OIDC-derived sealed-box session cookie | `Cookie: wft_session=<…>` |
|
|
116
|
+
| 3 | **Legacy** | An entry in the `API_KEYS` env list | `X-API-Key: <…>` |
|
|
117
|
+
|
|
118
|
+
The three strategies coexist intentionally — legacy keeps existing
|
|
119
|
+
deployments running while operators migrate; PAT is the recommended
|
|
120
|
+
machine credential; session is the recommended user credential.
|
|
121
|
+
|
|
122
|
+
### PAT lifecycle
|
|
123
|
+
|
|
124
|
+
PATs are minted from a logged-in `/me` web session **or** offline via the
|
|
125
|
+
CLI (`tasks db mint-token --user <id|email|displayName> --name <label>`,
|
|
126
|
+
see [`docs/CLI.md`](docs/CLI.md)). The raw token value is shown **once at
|
|
127
|
+
mint time** — the database only stores a SHA-256 hash, so a lost PAT
|
|
128
|
+
cannot be recovered (only re-minted).
|
|
129
|
+
|
|
130
|
+
**PATs have no default expiry.** The `api_tokens.expires_at` column is
|
|
131
|
+
nullable and is left `NULL` unless you explicitly pass
|
|
132
|
+
`--expires-at <ISO-8601>` at mint time (e.g.
|
|
133
|
+
`tasks db mint-token --user alice@example.com --name ci-runner --expires-at 2027-05-22T00:00:00Z`).
|
|
134
|
+
A token with a NULL `expires_at` is valid until it is revoked. Because a
|
|
135
|
+
non-expiring credential never rotates itself, operators are responsible
|
|
136
|
+
for hygiene:
|
|
137
|
+
|
|
138
|
+
- **Rotate** by minting a replacement PAT (with a fresh `--expires-at`),
|
|
139
|
+
deploying it, then revoking the old one — one PAT per machine/agent so a
|
|
140
|
+
rotation never disturbs unrelated clients.
|
|
141
|
+
- **Revoke** explicitly via the `/me` UI, the `DELETE /me/tokens/:id`
|
|
142
|
+
endpoint, or `tasks logout` (revokes the active PAT and removes the
|
|
143
|
+
local credentials file). Revoked PATs are rejected immediately on the
|
|
144
|
+
next request — there is no cache.
|
|
145
|
+
- **Set an expiry** on every new PAT (`--expires-at`) so credentials age
|
|
146
|
+
out even if a manual revocation is forgotten. The expiry is enforced by
|
|
147
|
+
the PAT auth strategy: once `expires_at` is in the past the token fails
|
|
148
|
+
with `reasonCode: expired`.
|
|
149
|
+
|
|
150
|
+
The PAT prefix (`wft_pat_`) is part of the wire format: the remote MCP
|
|
151
|
+
server and the CLI HTTP client switch their auth header based on the
|
|
152
|
+
prefix, so the same env var (`WFT_API_KEY` for MCP, `API_KEY` for CLI)
|
|
153
|
+
transparently accepts a PAT or a legacy key.
|
|
154
|
+
|
|
155
|
+
### Session lifecycle
|
|
156
|
+
|
|
157
|
+
OIDC sign-in (`/auth/login` → Google → `/auth/callback`) creates a
|
|
158
|
+
sealed-box-encrypted cookie containing the user id and a small set of
|
|
159
|
+
claims. The cookie:
|
|
160
|
+
|
|
161
|
+
- Uses `SESSION_COOKIE_SECRET` (32 bytes, generated via
|
|
162
|
+
`openssl rand -base64 32`) as the sodium sealed-box key.
|
|
163
|
+
- Has `maxAge=8h`, `httpOnly=true`, and `sameSite=lax`.
|
|
164
|
+
- Sets the `secure` attribute **only when `NODE_ENV=production`**
|
|
165
|
+
(`src/api/server.ts` — `secure: config.NODE_ENV === 'production'`).
|
|
166
|
+
- Has **no DB-side sessions table** — the cookie is self-contained.
|
|
167
|
+
Rotating `SESSION_COOKIE_SECRET` invalidates every active session
|
|
168
|
+
immediately because the existing cookies can no longer be decrypted.
|
|
169
|
+
|
|
170
|
+
> **Run production behind HTTPS — even on a LAN.** Because the cookie is
|
|
171
|
+
> flagged `secure` whenever `NODE_ENV=production`, a production server
|
|
172
|
+
> reached over plain `http://` will have its `Set-Cookie` dropped by the
|
|
173
|
+
> browser, silently breaking the OIDC login flow (the session never
|
|
174
|
+
> persists, so the callback loops back to `/auth/login`). This applies to
|
|
175
|
+
> internal / LAN deployments too: terminate TLS in front of the service
|
|
176
|
+
> (reverse proxy or a self-signed cert the clients trust) before exposing
|
|
177
|
+
> the browser login. The matching `secure=false` in non-production exists
|
|
178
|
+
> only so local `http://localhost` development works — do not run a
|
|
179
|
+
> public or shared instance with `NODE_ENV` unset.
|
|
180
|
+
|
|
181
|
+
The OIDC flow itself uses **PKCE + state** to prevent CSRF / replay
|
|
182
|
+
against the callback endpoint, and validates the issuer + audience
|
|
183
|
+
against `OIDC_ISSUER_URL` + `OIDC_CLIENT_ID` before binding the local
|
|
184
|
+
session.
|
|
185
|
+
|
|
186
|
+
### Per-request audit
|
|
187
|
+
|
|
188
|
+
Every authenticated request emits a structured pino log line carrying:
|
|
189
|
+
|
|
190
|
+
- `user_id` — the local `users.id` (NULL for service accounts like
|
|
191
|
+
`mcp-bot` / `slack-bot` only when the bot row is missing; the seed
|
|
192
|
+
guarantees they exist).
|
|
193
|
+
- `token_id` — the `api_tokens.id` when strategy=PAT; NULL
|
|
194
|
+
otherwise.
|
|
195
|
+
- `auth_method` — one of `pat`, `session`, `legacy`.
|
|
196
|
+
- `apiKeyLabel` — the human-friendly label for legacy keys, e.g.
|
|
197
|
+
`key_alice-laptop`. Absent for PAT / session.
|
|
198
|
+
|
|
199
|
+
Failures emit a counterpart `tag: auth.failure` line with a coarse
|
|
200
|
+
`reasonCode` (`missing_credential`, `unknown_token`, `revoked_token`, …)
|
|
201
|
+
so secret values never appear in logs. The `auth-audit` helper enforces
|
|
202
|
+
this — it is the **only** sanctioned way for the auth plugin to
|
|
203
|
+
log into the request.
|
|
204
|
+
|
|
205
|
+
## Legacy `X-API-Key` Status
|
|
206
|
+
|
|
207
|
+
The legacy `X-API-Key` strategy is **deprecated but still fully
|
|
208
|
+
supported as of v1.11.** It remains the third link in the auth chain
|
|
209
|
+
(`src/api/plugins/auth/index.ts` walks PAT → session → legacy), so a
|
|
210
|
+
request carrying a valid `API_KEYS` entry still authenticates and
|
|
211
|
+
mutates data. PAT and OIDC session are the preferred credentials; legacy
|
|
212
|
+
keys exist to keep older deployments running while operators migrate.
|
|
213
|
+
|
|
214
|
+
There is **no scheduled removal version.** Earlier drafts of this
|
|
215
|
+
document described a "v1.7 sunset" that would drop `API_KEYS` support —
|
|
216
|
+
that never happened. v1.7 through v1.11 shipped with the legacy strategy
|
|
217
|
+
intact, and no removal date is currently committed.
|
|
218
|
+
|
|
219
|
+
Legacy authentication is surfaced so operators can track migration
|
|
220
|
+
progress, not blocked:
|
|
221
|
+
|
|
222
|
+
- Every legacy-authed REST response carries two RFC 8594 headers:
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
Deprecation: true
|
|
226
|
+
Sunset: 2026-12-31
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The `Sunset` value comes from the `LEGACY_AUTH_SUNSET_DATE` env var
|
|
230
|
+
(default `2026-12-31`, must be `YYYY-MM-DD`). It is an advisory
|
|
231
|
+
migration target, **not** an enforced cutoff — the strategy keeps
|
|
232
|
+
working past that date. PAT-authed and session-authed requests carry
|
|
233
|
+
**neither** header.
|
|
234
|
+
|
|
235
|
+
- Every legacy-authed request also emits a `warn`-level log line:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"level": 40,
|
|
240
|
+
"event": "legacy_auth_used",
|
|
241
|
+
"userId": 1,
|
|
242
|
+
"apiKeyLabel": "key_alice-laptop",
|
|
243
|
+
"requestId": "…",
|
|
244
|
+
"requestUrl": "/api/v1/tasks",
|
|
245
|
+
"sunset": "2026-12-31"
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Aggregate `legacy_auth_used` over a rolling window to gauge migration
|
|
250
|
+
readiness — a steady decline to zero means clients have all moved to
|
|
251
|
+
PAT or session. New deployments should issue PATs (one per
|
|
252
|
+
machine/agent) or use OIDC sessions rather than `API_KEYS`.
|
|
253
|
+
|
|
254
|
+
If a future release does remove the legacy strategy, the
|
|
255
|
+
`tasks db migrate-identities` tool (idempotent; backfills identity FKs
|
|
256
|
+
for historical rows that carry only the legacy TEXT identity columns)
|
|
257
|
+
is the supported pre-upgrade step. It is safe to run today.
|
|
258
|
+
|
|
259
|
+
## CORS
|
|
260
|
+
|
|
261
|
+
The REST API **does not register a CORS plugin** — there is no
|
|
262
|
+
`@fastify/cors` (or equivalent) registration anywhere in `src/api/`, and
|
|
263
|
+
`cors` is not a project dependency. This is intentional: the API is built
|
|
264
|
+
for server-to-server and agent traffic (PAT / `X-API-Key` in headers),
|
|
265
|
+
plus a same-origin browser surface (`/auth/*`, `/me`, `/login`) that does
|
|
266
|
+
not need cross-origin access. With no `Access-Control-Allow-Origin`
|
|
267
|
+
header emitted, browsers block cross-origin reads of API responses by
|
|
268
|
+
default.
|
|
269
|
+
|
|
270
|
+
> **Never add `origin: true` (reflect-any-origin) CORS.** The OIDC
|
|
271
|
+
> session is a **credentialed cookie** (`Cookie: wft_session=…`).
|
|
272
|
+
> Combining a reflect-any-origin CORS policy
|
|
273
|
+
> (`origin: true` / `Access-Control-Allow-Origin: <reflected>`) with
|
|
274
|
+
> `Access-Control-Allow-Credentials: true` would let any website the
|
|
275
|
+
> victim visits make authenticated, cookie-bearing requests to the API on
|
|
276
|
+
> the victim's behalf — a cross-site request forgery / data-exfiltration
|
|
277
|
+
> hole. If you must enable CORS, set an explicit, hard-coded allow-list of
|
|
278
|
+
> trusted origins; do not reflect the request origin while credentials are
|
|
279
|
+
> allowed.
|
|
280
|
+
|
|
281
|
+
## Authentication Is Not Authorization
|
|
282
|
+
|
|
283
|
+
Authentication identifies the caller; it does **not** scope what the
|
|
284
|
+
caller may do. Wood Fired Tasks has **no RBAC, no ACL, and no tenant /
|
|
285
|
+
project isolation.** Every authenticated identity — whether it arrived
|
|
286
|
+
via PAT, OIDC session, or a legacy `X-API-Key` — is effectively an
|
|
287
|
+
admin: it can read, write, and delete **every** task, project, comment,
|
|
288
|
+
dependency, and Slack subscription across **every** project in the
|
|
289
|
+
database. The `--scopes` minted onto a PAT are advisory metadata only and
|
|
290
|
+
are **not enforced** by any endpoint.
|
|
291
|
+
|
|
292
|
+
The consequence: any valid credential is a full-access credential. If you
|
|
293
|
+
need per-user, per-team, or per-tenant isolation, you must enforce it
|
|
294
|
+
**outside** this service — front it with an authenticating reverse proxy
|
|
295
|
+
that performs its own per-tenant authorization. Treat the loss or leak of
|
|
296
|
+
any single PAT or API key as a full-database compromise and revoke/rotate
|
|
297
|
+
accordingly. Scoped, role-based permissions are tracked as future work;
|
|
298
|
+
until they land, the model above is the whole authorization story.
|
|
299
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
|
|
2
|
+
/**
|
|
3
|
+
* Custom error handler that maps Phase 1 service errors to structured HTTP responses
|
|
4
|
+
* with machine-readable error codes.
|
|
5
|
+
*/
|
|
6
|
+
export declare function errorHandler(error: FastifyError | Error, request: FastifyRequest, reply: FastifyReply): void;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ValidationError, BusinessError, NotFoundError } from '../../services/errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generic, status-appropriate messages used when an error's raw `message` is
|
|
4
|
+
* NOT trusted for verbatim forwarding to the client. These never echo
|
|
5
|
+
* third-party / upstream detail.
|
|
6
|
+
*/
|
|
7
|
+
const GENERIC_STATUS_MESSAGES = {
|
|
8
|
+
400: 'Bad request',
|
|
9
|
+
401: 'Unauthorized',
|
|
10
|
+
403: 'Forbidden',
|
|
11
|
+
404: 'Resource not found',
|
|
12
|
+
405: 'Method not allowed',
|
|
13
|
+
406: 'Not acceptable',
|
|
14
|
+
408: 'Request timeout',
|
|
15
|
+
409: 'Conflict',
|
|
16
|
+
413: 'Payload too large',
|
|
17
|
+
414: 'URI too long',
|
|
18
|
+
415: 'Unsupported media type',
|
|
19
|
+
422: 'Unprocessable entity',
|
|
20
|
+
429: 'Too many requests',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Allowlist of Fastify/framework error code prefixes whose `message` is
|
|
24
|
+
* framework-generated (not third-party/upstream content) and therefore safe to
|
|
25
|
+
* forward verbatim. These describe how the *client's own request* was malformed
|
|
26
|
+
* (validation, body parsing, content-type, payload size) and never carry
|
|
27
|
+
* internal/upstream secrets.
|
|
28
|
+
*/
|
|
29
|
+
const ALLOWLISTED_CODE_PREFIXES = [
|
|
30
|
+
'FST_ERR_VALIDATION', // Zod / JSON-schema request validation failures
|
|
31
|
+
'FST_ERR_CTP_', // content-type parser errors (e.g. invalid/empty JSON body)
|
|
32
|
+
'FST_ERR_RTE_', // routing errors
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Project-authored error codes whose `message` is intentionally client-facing
|
|
36
|
+
* and safe to forward verbatim. Unlike the FST_ERR_* framework codes above,
|
|
37
|
+
* these are constructed by this codebase (not Fastify), so they are matched
|
|
38
|
+
* exactly rather than by prefix. Keep this set minimal — only codes whose
|
|
39
|
+
* message is guaranteed not to carry internal/upstream detail.
|
|
40
|
+
* - TOO_MANY_REQUESTS: @fastify/rate-limit errorResponseBuilder in server.ts
|
|
41
|
+
* emits "Rate limit exceeded, retry in <after>" — a documented, safe
|
|
42
|
+
* client-facing message (see server.ts errorResponseBuilder contract).
|
|
43
|
+
*/
|
|
44
|
+
const ALLOWLISTED_EXACT_CODES = new Set(['TOO_MANY_REQUESTS']);
|
|
45
|
+
/**
|
|
46
|
+
* Decide whether an error's raw `message` may be surfaced verbatim to the
|
|
47
|
+
* client. Only errors the project explicitly trusts qualify:
|
|
48
|
+
* - Fastify request-validation errors (carry a `validation` array),
|
|
49
|
+
* - errors whose Fastify `code` matches a known-safe framework prefix, and
|
|
50
|
+
* - project-authored error codes whose message is intentionally client-facing
|
|
51
|
+
* (ALLOWLISTED_EXACT_CODES, e.g. TOO_MANY_REQUESTS).
|
|
52
|
+
* Everything else with a `statusCode` gets a generic status message instead,
|
|
53
|
+
* so third-party / upstream error detail is never leaked.
|
|
54
|
+
*
|
|
55
|
+
* The project's own error classes (ValidationError / BusinessError /
|
|
56
|
+
* NotFoundError) are handled by dedicated branches earlier and do not pass
|
|
57
|
+
* through here.
|
|
58
|
+
*/
|
|
59
|
+
function isMessageAllowlisted(error) {
|
|
60
|
+
// Fastify attaches a `validation` array to request-schema validation errors.
|
|
61
|
+
if (Array.isArray(error.validation)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
const code = error.code;
|
|
65
|
+
if (typeof code === 'string' && code.length > 0) {
|
|
66
|
+
if (ALLOWLISTED_EXACT_CODES.has(code)) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return ALLOWLISTED_CODE_PREFIXES.some((prefix) => code.startsWith(prefix));
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Custom error handler that maps Phase 1 service errors to structured HTTP responses
|
|
75
|
+
* with machine-readable error codes.
|
|
76
|
+
*/
|
|
77
|
+
export function errorHandler(error, request, reply) {
|
|
78
|
+
// Log the FULL error server-side for debugging. This is the only place the
|
|
79
|
+
// raw message/stack of a non-allowlisted error is ever exposed.
|
|
80
|
+
request.log.error(error);
|
|
81
|
+
// Map Phase 1 custom errors FIRST (before checking Fastify-specific properties)
|
|
82
|
+
if (error instanceof ValidationError) {
|
|
83
|
+
reply.code(400).send({
|
|
84
|
+
error: 'VALIDATION_ERROR',
|
|
85
|
+
message: 'Validation failed',
|
|
86
|
+
details: error.fieldErrors,
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (error instanceof NotFoundError) {
|
|
91
|
+
reply.code(404).send({
|
|
92
|
+
error: 'NOT_FOUND',
|
|
93
|
+
message: error.message,
|
|
94
|
+
details: { entity: error.entity, id: error.id },
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (error instanceof BusinessError) {
|
|
99
|
+
reply.code(422).send({
|
|
100
|
+
error: 'BUSINESS_RULE_VIOLATION',
|
|
101
|
+
message: error.message,
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// statusCode-bearing errors (Fastify validation, body parsing, and arbitrary
|
|
106
|
+
// third-party errors that happen to carry a statusCode).
|
|
107
|
+
if ('statusCode' in error) {
|
|
108
|
+
const fastifyError = error;
|
|
109
|
+
const statusCode = fastifyError.statusCode || 400;
|
|
110
|
+
// Only surface the raw message for errors the project explicitly trusts
|
|
111
|
+
// (audit C7). Everything else gets a generic, status-appropriate message so
|
|
112
|
+
// internal/upstream detail is never forwarded verbatim.
|
|
113
|
+
const message = isMessageAllowlisted(fastifyError)
|
|
114
|
+
? error.message
|
|
115
|
+
: GENERIC_STATUS_MESSAGES[statusCode] ?? 'An unexpected error occurred';
|
|
116
|
+
reply.code(statusCode).send({
|
|
117
|
+
error: fastifyError.code || 'REQUEST_ERROR',
|
|
118
|
+
message,
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Fallback for unexpected errors - do NOT leak stack traces
|
|
123
|
+
reply.code(500).send({
|
|
124
|
+
error: 'INTERNAL_ERROR',
|
|
125
|
+
message: 'An unexpected error occurred',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=error-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../../src/api/hooks/error-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 28 (Plan 28-04) — unified auth chain plugin.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the legacy single-strategy plugin at `src/api/plugins/auth.ts`
|
|
5
|
+
* with a three-strategy chain (PAT → session-stub → legacy API_KEYS). The
|
|
6
|
+
* file at `src/api/plugins/auth.ts` survives as a thin re-export shim so
|
|
7
|
+
* existing `import authPlugin from './plugins/auth.js'` callers (server.ts,
|
|
8
|
+
* auth-logging.test.ts) keep working without churn.
|
|
9
|
+
*
|
|
10
|
+
* Plugin responsibilities:
|
|
11
|
+
* 1. Decorate `FastifyRequest` with `user`, `authMethod`, `tokenId`,
|
|
12
|
+
* `apiKeyLabel` at plugin load (Fastify requires decoration before any
|
|
13
|
+
* route registers).
|
|
14
|
+
* 2. Validate `process.env.API_KEYS` in production (throws synchronously,
|
|
15
|
+
* which Fastify bubbles up to createServer → exits with non-zero).
|
|
16
|
+
* 3. Pre-compute SHA-256 hashes of every configured API_KEYS entry once
|
|
17
|
+
* at register time; feed the result into the legacy strategy on every
|
|
18
|
+
* request so it never re-hashes.
|
|
19
|
+
* 4. Register a `preHandler` hook that:
|
|
20
|
+
* a. Short-circuits when `request.routeOptions.config.skipAuth === true`.
|
|
21
|
+
* b. Walks PAT → session-stub → legacy. First match wins. PAT failure
|
|
22
|
+
* does NOT fall through to legacy — see `enforceSessionOnly` /
|
|
23
|
+
* strategy-fail short-circuit below.
|
|
24
|
+
* c. On a successful match, populates `request.user`, `request.authMethod`,
|
|
25
|
+
* `request.tokenId` (and `request.apiKeyLabel` for legacy), re-childs
|
|
26
|
+
* the request logger with `{ user_id, token_id, auth_method,
|
|
27
|
+
* apiKeyLabel }` so every downstream log line carries audit fields,
|
|
28
|
+
* and enforces `config.sessionOnly` post-auth.
|
|
29
|
+
* d. On a strategy `fail` outcome, emits one `auth.failure` warn log via
|
|
30
|
+
* the Phase 27 `logAuthFailure` helper and returns a uniform 401
|
|
31
|
+
* (the distinct `reasonCode` lives ONLY in the audit log — never in
|
|
32
|
+
* the response body).
|
|
33
|
+
* e. On total fall-through (every strategy returned `skip`), emits a
|
|
34
|
+
* catch-all `auth.failure` log tagged `strategy: 'legacy'`,
|
|
35
|
+
* `reasonCode: 'missing_credential'` (per Plan-04 Decision Q6).
|
|
36
|
+
*
|
|
37
|
+
* Side-effect contracts:
|
|
38
|
+
* - PAT match schedules `setImmediate(() => apiTokenRepository.touchLastUsed(
|
|
39
|
+
* tokenId))`. The 10-minute Map debounce required by REQUIREMENTS PAT-03
|
|
40
|
+
* lands in Plan 28-06; this plan ships the naive every-request write.
|
|
41
|
+
* - The chain NEVER logs successful auth (no `auth.success` line); the
|
|
42
|
+
* re-childed request logger is the canonical audit trail.
|
|
43
|
+
*
|
|
44
|
+
* fp() wrap with `{ name: 'wft-auth', fastify: '5.x' }` is non-negotiable —
|
|
45
|
+
* without it, sibling routes registered in the same parent scope (the
|
|
46
|
+
* `/api/v1` block in server.ts) bypass the hook entirely. Existing
|
|
47
|
+
* rate-limit.test.ts would catch a regression, but the comment block at the
|
|
48
|
+
* bottom of this file (and at the bottom of the legacy auth.ts) is the
|
|
49
|
+
* primary defence against a reflexive refactor.
|
|
50
|
+
*/
|
|
51
|
+
import type { FastifyPluginAsync, FastifyRequest } from 'fastify';
|
|
52
|
+
import type { AuthenticatedUser } from '../../../types/identity.js';
|
|
53
|
+
/**
|
|
54
|
+
* Throws if `preHandler` has not run yet (or if `skipAuth` was set). Use in
|
|
55
|
+
* route handlers / tool helpers that need a non-null `request.user`. The
|
|
56
|
+
* type narrowing is the entire point — once `requireUser` returns, the
|
|
57
|
+
* caller has an `AuthenticatedUser` without further null checks.
|
|
58
|
+
*
|
|
59
|
+
* CR-01 (Phase 30 review) — belt-and-suspenders: check for BOTH `null`
|
|
60
|
+
* (the initialized default via `decorateRequest('user', null)`) AND
|
|
61
|
+
* `undefined` (the value the slot holds when the route was registered
|
|
62
|
+
* OUTSIDE any scope that ran the auth-chain plugin — e.g. a top-level
|
|
63
|
+
* device-flow route mounted as a sibling of the `/api/v1` scope). The
|
|
64
|
+
* scope-wiring fix in server.ts addresses the production wiring, but
|
|
65
|
+
* leaving the guard narrow would silently re-open the bug if a future
|
|
66
|
+
* refactor moved a sessionOnly route outside the chain again.
|
|
67
|
+
*/
|
|
68
|
+
export declare function requireUser(request: FastifyRequest): AuthenticatedUser;
|
|
69
|
+
/**
|
|
70
|
+
* Wrap with fastify-plugin to escape the encapsulated scope. Without `fp()`
|
|
71
|
+
* the preHandler hook only fires for routes registered INSIDE this plugin —
|
|
72
|
+
* every sibling under `/api/v1/*` would bypass auth entirely. The
|
|
73
|
+
* `{ name: 'wft-auth', fastify: '5.x' }` options match the pre-split
|
|
74
|
+
* plugin's identity so any downstream `fastify.hasPlugin('wft-auth')`
|
|
75
|
+
* checks keep working.
|
|
76
|
+
*/
|
|
77
|
+
declare const authChain: FastifyPluginAsync;
|
|
78
|
+
export default authChain;
|