create-ailk 0.23.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -33
- package/component-catalog.json +3 -3
- package/component-catalog.md +3 -3
- package/dist/add-module.js +10 -3
- package/dist/add-route.d.ts +12 -1
- package/dist/add-route.js +38 -4
- package/dist/cli.js +12 -7
- package/dist/derive-architecture.d.ts +3 -4
- package/dist/derive-architecture.js +75 -22
- package/dist/lib/design-checks.d.ts +9 -4
- package/dist/lib/design-checks.js +13 -7
- package/dist/lib/extract-module-bundle.js +29 -2
- package/dist/lib/fetch-template-snapshot.d.ts +1 -1
- package/dist/lib/fetch-template-snapshot.js +7 -3
- package/dist/lib/import-graph.d.ts +1 -1
- package/dist/lib/import-graph.js +4 -4
- package/dist/lib/module-license-gate.d.ts +12 -1
- package/dist/lib/module-license-gate.js +34 -12
- package/dist/lib/scaffold-render-sweep.d.ts +33 -0
- package/dist/lib/scaffold-render-sweep.js +45 -0
- package/dist/module-architecture.d.ts +20 -3
- package/dist/module-architecture.js +122 -538
- package/dist/paid-sections.d.ts +25 -1
- package/dist/paid-sections.js +88 -3
- package/dist/surfaces.js +10 -2
- package/dist/sync-routes.d.ts +30 -4
- package/dist/sync-routes.js +271 -18
- package/dist/template-record.d.ts +14 -4
- package/dist/template-record.js +23 -5
- package/dist/topology.d.ts +16 -3
- package/dist/topology.js +31 -19
- package/dist/update.d.ts +10 -1
- package/dist/update.js +540 -16
- package/package.json +3 -3
- package/release-scope.json +1 -1
- package/templates/.claude/cloudparity-repo-hooks.json +5 -0
- package/templates/.claude/hooks/template-guard.sh +137 -0
- package/templates/.claude/rules/extending.md +106 -0
- package/templates/.claude/rules/loop-contract.md +2 -2
- package/templates/.claude/settings.json +11 -0
- package/templates/.env.example +9 -5
- package/templates/.github/workflows/ci.yml +7 -4
- package/templates/CLAUDE.md +15 -3
- package/templates/CONVENTIONS.md +8 -10
- package/templates/README.md +14 -13
- package/templates/apps/api/.env.example +9 -2
- package/templates/apps/api/CLAUDE.md +122 -82
- package/templates/apps/api/jest.config.cjs +15 -5
- package/templates/apps/api/package.json +16 -20
- package/templates/apps/api/src/__tests__/trusted-origins.server.test.ts +2 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +4 -4
- package/templates/apps/api/src/index.ts +1 -1
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +7 -4
- package/templates/apps/api/src/modules/commerce.ts +10 -0
- package/templates/apps/api/src/modules/content-write.ts +10 -0
- package/templates/apps/api/src/modules/multi-tenant.ts +10 -0
- package/templates/apps/api/src/modules/scheduling.ts +10 -0
- package/templates/apps/api/src/modules/signal-collectors.ts +10 -0
- package/templates/apps/api/src/modules/workspace.ts +10 -0
- package/templates/apps/api/src/server.ts +117 -1857
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/CLAUDE.md +77 -24
- package/templates/apps/mcp/__tests__/app-entry.test.ts +4 -254
- package/templates/apps/mcp/__tests__/boot-file.test.ts +92 -0
- package/templates/apps/mcp/jest.config.cjs +6 -1
- package/templates/apps/mcp/package.json +5 -12
- package/templates/apps/mcp/src/app.ts +8 -7
- package/templates/apps/mcp/src/index.ts +6 -34
- package/templates/apps/mcp/src/lib/paid-tools.generated.ts +10 -0
- package/templates/apps/mcp/src/lib/topology.generated.ts +2 -2
- package/templates/apps/mcp/src/site.ts +12 -0
- package/templates/apps/web/.env.example +1 -1
- package/templates/apps/web/CLAUDE.md +21 -5
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +14 -4
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +22 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +28 -7
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +7 -3
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +222 -0
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +10 -3
- package/templates/apps/web/app/api/revalidate/route.ts +3 -3
- package/templates/apps/web/auth.ts +2 -2
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +7 -0
- package/templates/apps/web/lib/auth-email.ts +1 -1
- package/templates/apps/web/lib/magnet-routes.ts +3 -1
- package/templates/apps/web/lib/newsletter.actions.ts +9 -6
- package/templates/apps/web/lib/relay.ts +2 -2
- package/templates/apps/web/lib/release-scope.ts +47 -0
- package/templates/apps/web/lib/server-api.ts +1 -1
- package/templates/apps/web/src/lib/commerce-claim.ts +1 -1
- package/templates/customizations.yaml +37 -0
- package/templates/database/prisma.config.ts +10 -14
- package/templates/database/seed-test.ts +2 -2
- package/templates/database/seed.ts +32 -5
- package/templates/package.json +12 -6
- package/templates/pnpm-workspace.yaml +0 -1
- package/templates/project.yaml +16 -8
- package/templates/tools/jest/reporters.cjs +65 -0
- package/templates/tools/lint-architecture.ts +757 -7
- package/templates/tsconfig.json +1 -2
- package/versions.json +48 -35
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +0 -409
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +0 -102
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +0 -138
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +0 -441
- package/templates/apps/api/src/__tests__/factories/index.ts +0 -70
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +0 -82
- package/templates/apps/api/src/__tests__/leads-forwarded-rate-limit.test.ts +0 -135
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +0 -126
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +0 -132
- package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +0 -362
- package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +0 -184
- package/templates/apps/api/src/bin/abandon-sweep.ts +0 -42
- package/templates/apps/api/src/bin/rate-limit-probe.ts +0 -53
- package/templates/apps/api/src/bin/spokes.ts +0 -198
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +0 -45
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +0 -196
- package/templates/apps/api/src/config/__tests__/index.test.ts +0 -233
- package/templates/apps/api/src/config/__tests__/modules.test.ts +0 -76
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +0 -185
- package/templates/apps/api/src/config/__tests__/trusted-origins.test.ts +0 -238
- package/templates/apps/api/src/config/index.ts +0 -596
- package/templates/apps/api/src/config/modules.ts +0 -83
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +0 -581
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +0 -143
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +0 -237
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +0 -353
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +0 -201
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +0 -224
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +0 -120
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +0 -94
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +0 -144
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +0 -8
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +0 -149
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +0 -116
- package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +0 -209
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +0 -257
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +0 -96
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +0 -125
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +0 -165
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +0 -317
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +0 -129
- package/templates/apps/api/src/lib/__tests__/site.test.ts +0 -292
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +0 -89
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +0 -202
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +0 -217
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -82
- package/templates/apps/api/src/lib/agent-store.ts +0 -613
- package/templates/apps/api/src/lib/auth-email-budget.ts +0 -161
- package/templates/apps/api/src/lib/consent-store.ts +0 -363
- package/templates/apps/api/src/lib/content-adapter.ts +0 -112
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +0 -96
- package/templates/apps/api/src/lib/error-serializer.ts +0 -164
- package/templates/apps/api/src/lib/errors.ts +0 -95
- package/templates/apps/api/src/lib/file-fallback.ts +0 -87
- package/templates/apps/api/src/lib/flow-completion-store.ts +0 -136
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +0 -455
- package/templates/apps/api/src/lib/lead-capture-store.ts +0 -122
- package/templates/apps/api/src/lib/lead-delivery.ts +0 -52
- package/templates/apps/api/src/lib/license-key-store.ts +0 -45
- package/templates/apps/api/src/lib/locales.ts +0 -201
- package/templates/apps/api/src/lib/outbound-http.ts +0 -127
- package/templates/apps/api/src/lib/prisma.ts +0 -21
- package/templates/apps/api/src/lib/proposal-paid-store.ts +0 -66
- package/templates/apps/api/src/lib/rate-limit-probe.ts +0 -165
- package/templates/apps/api/src/lib/rate-limit-store.ts +0 -142
- package/templates/apps/api/src/lib/require-commerce-claim.ts +0 -250
- package/templates/apps/api/src/lib/require-paid-tier.ts +0 -53
- package/templates/apps/api/src/lib/site.ts +0 -254
- package/templates/apps/api/src/lib/slice-load.ts +0 -90
- package/templates/apps/api/src/lib/spoke-store.ts +0 -180
- package/templates/apps/api/src/lib/stripe.ts +0 -318
- package/templates/apps/api/src/lib/webhook-idempotency.ts +0 -156
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +0 -111
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +0 -172
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +0 -119
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +0 -138
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-trusted-origins.test.ts +0 -142
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +0 -155
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +0 -139
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +0 -562
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +0 -76
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +0 -97
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +0 -557
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +0 -72
- package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +0 -140
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +0 -721
- package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +0 -430
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +0 -1409
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +0 -174
- package/templates/apps/api/src/middleware/auth.ts +0 -864
- package/templates/apps/api/src/middleware/default-tenant.ts +0 -166
- package/templates/apps/api/src/middleware/rate-limit.ts +0 -983
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +0 -264
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +0 -182
- package/templates/apps/api/src/openapi/index.ts +0 -18
- package/templates/apps/api/src/openapi/spec.ts +0 -1815
- package/templates/apps/api/src/openapi/surface-drift.ts +0 -195
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +0 -175
- package/templates/apps/api/src/routes/aeo/index.ts +0 -107
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +0 -196
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +0 -249
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +0 -532
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +0 -313
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +0 -413
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +0 -609
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +0 -242
- package/templates/apps/api/src/routes/auth/tokens.ts +0 -211
- package/templates/apps/api/src/routes/auth/transactional-email.ts +0 -301
- package/templates/apps/api/src/routes/auth/user.ts +0 -305
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +0 -325
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +0 -178
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +0 -1300
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +0 -448
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -1742
- package/templates/apps/api/src/routes/checkout/index.ts +0 -34
- package/templates/apps/api/src/routes/checkout/session.ts +0 -171
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -540
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +0 -386
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +0 -160
- package/templates/apps/api/src/routes/consent/account.ts +0 -268
- package/templates/apps/api/src/routes/consent/cookie.ts +0 -90
- package/templates/apps/api/src/routes/consent/index.ts +0 -26
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +0 -127
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +0 -853
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +0 -230
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +0 -44
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +0 -118
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +0 -731
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +0 -753
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +0 -51
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +0 -450
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +0 -762
- package/templates/apps/api/src/routes/content/get-page.ts +0 -268
- package/templates/apps/api/src/routes/content/index.ts +0 -49
- package/templates/apps/api/src/routes/content/list-pages.ts +0 -95
- package/templates/apps/api/src/routes/content/list-types.ts +0 -19
- package/templates/apps/api/src/routes/content/offerings.ts +0 -184
- package/templates/apps/api/src/routes/content/pricing.ts +0 -176
- package/templates/apps/api/src/routes/content/rate-limit.ts +0 -22
- package/templates/apps/api/src/routes/content/search.ts +0 -129
- package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
- package/templates/apps/api/src/routes/content/slug.ts +0 -9
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +0 -88
- package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +0 -171
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +0 -515
- package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +0 -392
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +0 -165
- package/templates/apps/api/src/routes/leads/index.ts +0 -573
- package/templates/apps/api/src/routes/leads/list.ts +0 -104
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +0 -626
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +0 -1268
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +0 -847
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +0 -480
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -465
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +0 -872
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +0 -1022
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +0 -484
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +0 -535
- package/templates/apps/api/src/routes/project-listings/index.ts +0 -164
- package/templates/apps/api/src/routes/project-listings/me.ts +0 -87
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +0 -224
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +0 -102
- package/templates/apps/api/src/routes/project-listings/public.ts +0 -98
- package/templates/apps/api/src/routes/project-listings/respond.ts +0 -48
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +0 -27
- package/templates/apps/api/src/routes/project-listings/start.ts +0 -140
- package/templates/apps/api/src/routes/project-listings/submit.ts +0 -130
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +0 -150
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +0 -686
- package/templates/apps/api/src/routes/requests/confirm.ts +0 -601
- package/templates/apps/api/src/routes/requests/index.ts +0 -54
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +0 -306
- package/templates/apps/api/src/routes/test/auth/session.ts +0 -143
- package/templates/apps/api/src/routes/webhooks/README.md +0 -398
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +0 -649
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +0 -749
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -448
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -264
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +0 -159
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +0 -158
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +0 -814
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +0 -369
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -215
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +0 -808
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -1155
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -141
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +0 -384
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -2026
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -528
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +0 -132
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +0 -1028
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +0 -313
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +0 -503
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +0 -65
- package/templates/apps/api/src/services/__tests__/consent.test.ts +0 -299
- package/templates/apps/api/src/services/__tests__/default-tenant.test.ts +0 -147
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +0 -867
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +0 -368
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +0 -142
- package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +0 -854
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +0 -234
- package/templates/apps/api/src/services/__tests__/lead-accept-user.test.ts +0 -251
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +0 -2660
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +0 -166
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +0 -389
- package/templates/apps/api/src/services/__tests__/person.test.ts +0 -1176
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -444
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -154
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +0 -137
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +0 -531
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +0 -372
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +0 -348
- package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +0 -660
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +0 -941
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +0 -588
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +0 -78
- package/templates/apps/api/src/services/abandon-email.ts +0 -313
- package/templates/apps/api/src/services/aeo-score.ts +0 -325
- package/templates/apps/api/src/services/answer-distributions.ts +0 -138
- package/templates/apps/api/src/services/audit-log.ts +0 -961
- package/templates/apps/api/src/services/checkout-buyer.ts +0 -168
- package/templates/apps/api/src/services/consent.ts +0 -834
- package/templates/apps/api/src/services/default-tenant.ts +0 -416
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +0 -755
- package/templates/apps/api/src/services/dunning-policy.ts +0 -294
- package/templates/apps/api/src/services/enrichment-shim.ts +0 -53
- package/templates/apps/api/src/services/entitlements.ts +0 -338
- package/templates/apps/api/src/services/hub-sign-in.ts +0 -472
- package/templates/apps/api/src/services/lead-accept-user.ts +0 -126
- package/templates/apps/api/src/services/lead-promotion.ts +0 -160
- package/templates/apps/api/src/services/lead-routing.ts +0 -1372
- package/templates/apps/api/src/services/leads-inbox.ts +0 -312
- package/templates/apps/api/src/services/listing-config.ts +0 -178
- package/templates/apps/api/src/services/magnet-completion.ts +0 -242
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +0 -295
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +0 -268
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +0 -868
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +0 -416
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +0 -396
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -681
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
- package/templates/apps/api/src/services/offerings/catalog.ts +0 -308
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -385
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
- package/templates/apps/api/src/services/offerings/connection.ts +0 -1003
- package/templates/apps/api/src/services/offerings/index.ts +0 -614
- package/templates/apps/api/src/services/offerings/source.ts +0 -140
- package/templates/apps/api/src/services/offerings/store.ts +0 -150
- package/templates/apps/api/src/services/owner-workspaces.ts +0 -132
- package/templates/apps/api/src/services/person.ts +0 -1188
- package/templates/apps/api/src/services/plan-offerings.ts +0 -125
- package/templates/apps/api/src/services/project-listing-resume-email.ts +0 -326
- package/templates/apps/api/src/services/project-listings.ts +0 -2073
- package/templates/apps/api/src/services/public-site-resolver.ts +0 -396
- package/templates/apps/api/src/services/request-confirm.ts +0 -698
- package/templates/apps/api/src/services/revalidation.ts +0 -235
- package/templates/apps/api/src/services/slack-notify.ts +0 -291
- package/templates/apps/api/src/services/usage-aggregation.ts +0 -516
- package/templates/apps/api/src/services/usage-credits.ts +0 -340
- package/templates/apps/api/src/services/verified-email.ts +0 -71
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +0 -52
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +0 -46
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +0 -198
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +0 -36
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +0 -54
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +0 -116
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +0 -299
- package/templates/apps/mcp/__tests__/boot.test.ts +0 -280
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +0 -359
- package/templates/apps/mcp/__tests__/config/modules.test.ts +0 -78
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +0 -82
- package/templates/apps/mcp/__tests__/get_faq.test.ts +0 -80
- package/templates/apps/mcp/__tests__/get_locations.test.ts +0 -65
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +0 -557
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +0 -524
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +0 -81
- package/templates/apps/mcp/__tests__/http-transport.test.ts +0 -1151
- package/templates/apps/mcp/__tests__/index.test.ts +0 -195
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +0 -191
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +0 -531
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +0 -65
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -187
- package/templates/apps/mcp/__tests__/lib/content-root.test.ts +0 -93
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +0 -75
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +0 -165
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
- package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -228
- package/templates/apps/mcp/__tests__/locale.test.ts +0 -135
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +0 -175
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +0 -101
- package/templates/apps/mcp/__tests__/read_content.test.ts +0 -474
- package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -225
- package/templates/apps/mcp/__tests__/search_content.test.ts +0 -256
- package/templates/apps/mcp/__tests__/server.test.ts +0 -388
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +0 -448
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +0 -307
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -223
- package/templates/apps/mcp/__tests__/tools/create_project.test.ts +0 -243
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +0 -451
- package/templates/apps/mcp/src/abuse/daily-cap.ts +0 -47
- package/templates/apps/mcp/src/abuse/email-validator.ts +0 -25
- package/templates/apps/mcp/src/abuse/honeypot.ts +0 -21
- package/templates/apps/mcp/src/abuse/index.ts +0 -342
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +0 -58
- package/templates/apps/mcp/src/boot.ts +0 -167
- package/templates/apps/mcp/src/config/modules.ts +0 -43
- package/templates/apps/mcp/src/http-transport.ts +0 -526
- package/templates/apps/mcp/src/lib/audit-single-page.ts +0 -308
- package/templates/apps/mcp/src/lib/commerce-claim.ts +0 -263
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -142
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -139
- package/templates/apps/mcp/src/lib/errors.ts +0 -61
- package/templates/apps/mcp/src/lib/license-key-store.ts +0 -45
- package/templates/apps/mcp/src/lib/locales.ts +0 -171
- package/templates/apps/mcp/src/lib/prisma.ts +0 -39
- package/templates/apps/mcp/src/lib/public-workspace.ts +0 -133
- package/templates/apps/mcp/src/lib/resolve-locale.ts +0 -30
- package/templates/apps/mcp/src/lib/shown-offerings.ts +0 -105
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +0 -59
- package/templates/apps/mcp/src/request-context.ts +0 -52
- package/templates/apps/mcp/src/server.ts +0 -343
- package/templates/apps/mcp/src/serverless-app.ts +0 -88
- package/templates/apps/mcp/src/tools/audit.ts +0 -127
- package/templates/apps/mcp/src/tools/capture_lead.ts +0 -64
- package/templates/apps/mcp/src/tools/create_project.ts +0 -134
- package/templates/apps/mcp/src/tools/get_business_hours.ts +0 -68
- package/templates/apps/mcp/src/tools/get_faq.ts +0 -71
- package/templates/apps/mcp/src/tools/get_locations.ts +0 -88
- package/templates/apps/mcp/src/tools/get_offerings.ts +0 -96
- package/templates/apps/mcp/src/tools/get_pricing.ts +0 -94
- package/templates/apps/mcp/src/tools/get_site_info.ts +0 -86
- package/templates/apps/mcp/src/tools/index.ts +0 -137
- package/templates/apps/mcp/src/tools/list_pages.ts +0 -74
- package/templates/apps/mcp/src/tools/person_notes.ts +0 -25
- package/templates/apps/mcp/src/tools/proposals.ts +0 -27
- package/templates/apps/mcp/src/tools/purchase.ts +0 -205
- package/templates/apps/mcp/src/tools/read_content.ts +0 -75
- package/templates/apps/mcp/src/tools/request_callback.ts +0 -59
- package/templates/apps/mcp/src/tools/search_content.ts +0 -67
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -57
- package/templates/apps/mcp/src/workspace-server.ts +0 -224
- package/templates/database/CHANGELOG.md +0 -961
- package/templates/database/LICENSE +0 -202
- package/templates/database/NOTICE +0 -2
- package/templates/database/README.md +0 -243
- package/templates/database/__tests__/agents.db.test.ts +0 -320
- package/templates/database/__tests__/appointment.db.test.ts +0 -106
- package/templates/database/__tests__/artifact-content-store.db.test.ts +0 -316
- package/templates/database/__tests__/composite-fence.db.test.ts +0 -206
- package/templates/database/__tests__/consent-ledger.db.test.ts +0 -345
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +0 -79
- package/templates/database/__tests__/fence-fan-out.db.test.ts +0 -715
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +0 -79
- package/templates/database/__tests__/fixtures/person-backfill.json +0 -264
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +0 -182
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +0 -100
- package/templates/database/__tests__/flow-completion-migration.test.ts +0 -115
- package/templates/database/__tests__/flow-completion.db.test.ts +0 -136
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +0 -263
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +0 -392
- package/templates/database/__tests__/flows-presets.test.ts +0 -173
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +0 -161
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +0 -92
- package/templates/database/__tests__/member-area.db.test.ts +0 -257
- package/templates/database/__tests__/member-contract-migration.test.ts +0 -85
- package/templates/database/__tests__/member-contract.db.test.ts +0 -413
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +0 -270
- package/templates/database/__tests__/offerings-migration.test.ts +0 -197
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +0 -86
- package/templates/database/__tests__/offerings.db.test.ts +0 -303
- package/templates/database/__tests__/outbound-message-action.db.test.ts +0 -80
- package/templates/database/__tests__/person-contact.db.test.ts +0 -563
- package/templates/database/__tests__/person-notes.db.test.ts +0 -249
- package/templates/database/__tests__/person.db.test.ts +0 -586
- package/templates/database/__tests__/piece-kind-migration.test.ts +0 -67
- package/templates/database/__tests__/piece-kind.db.test.ts +0 -208
- package/templates/database/__tests__/prefixed-ids.db.test.ts +0 -222
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +0 -111
- package/templates/database/__tests__/seed.test.ts +0 -240
- package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
- package/templates/database/__tests__/tenancy.test.ts +0 -141
- package/templates/database/__tests__/user-cascade.test.ts +0 -140
- package/templates/database/_gitignore +0 -8
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/auth/schema.prisma +0 -641
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/config/schema.prisma +0 -441
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/content/schema.prisma +0 -339
- package/templates/database/eslint.config.mjs +0 -69
- package/templates/database/flows/listing-diagnostic.ts +0 -182
- package/templates/database/flows/presets.ts +0 -315
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +0 -3
- package/templates/database/inbox/__tests__/schema.test.ts +0 -44
- package/templates/database/inbox/schema.prisma +0 -1447
- package/templates/database/jest.config.cjs +0 -36
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +0 -328
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +0 -100
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +0 -28
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +0 -25
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +0 -42
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +0 -67
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +0 -21
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +0 -201
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +0 -67
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +0 -46
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +0 -25
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +0 -49
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +0 -197
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +0 -32
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +0 -22
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +0 -151
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +0 -223
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +0 -22
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +0 -32
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +0 -40
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +0 -4
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +0 -2
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +0 -2
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +0 -29
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +0 -6
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +0 -39
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +0 -24
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +0 -3
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +0 -33
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +0 -30
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +0 -47
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +0 -16
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +0 -47
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +0 -10
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +0 -26
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +0 -90
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +0 -174
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +0 -14
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +0 -25
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +0 -32
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +0 -71
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +0 -30
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +0 -49
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +0 -123
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +0 -15
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +0 -158
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +0 -40
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +0 -67
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +0 -70
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +0 -181
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +0 -172
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +0 -41
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +0 -185
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +0 -70
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +0 -57
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +0 -68
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +0 -1067
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +0 -210
- package/templates/database/migrations/20261003120000_spoke_registry/migration.sql +0 -80
- package/templates/database/migrations/20261003130000_scheduling_connection/migration.sql +0 -49
- package/templates/database/migrations/20261005120000_agents/migration.sql +0 -84
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +0 -35
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +0 -20
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +0 -33
- package/templates/database/migrations/20261010120000_person/migration.sql +0 -106
- package/templates/database/migrations/20261010121000_person_backfill/migration.sql +0 -132
- package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +0 -20
- package/templates/database/migrations/20261011120000_member_area/migration.sql +0 -158
- package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +0 -21
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +0 -192
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +0 -20
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +0 -122
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +0 -231
- package/templates/database/migrations/20261015120000_person_notes/migration.sql +0 -77
- package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +0 -43
- package/templates/database/migrations/20261016120000_piece_kind/migration.sql +0 -95
- package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +0 -30
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +0 -52
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +0 -27
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +0 -146
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +0 -77
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +0 -45
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +0 -47
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +0 -16
- package/templates/database/migrations/migration_lock.toml +0 -3
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/ops/schema.prisma +0 -43
- package/templates/database/package.json +0 -89
- package/templates/database/schema.prisma +0 -71
- package/templates/database/scripts/db-generate-locked.sh +0 -80
- package/templates/database/scripts/person-backfill.sql +0 -132
- package/templates/database/tenancy.ts +0 -183
- package/templates/database/tsconfig.build.json +0 -16
- package/templates/database/tsconfig.flows.json +0 -15
- package/templates/database/tsconfig.json +0 -16
- package/templates/database/tsconfig.tenancy.json +0 -14
|
@@ -1,864 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dual-mode authentication preHandler for apps/api (#587).
|
|
3
|
-
*
|
|
4
|
-
* Decorates `request.userId` on authenticated requests, or returns 401.
|
|
5
|
-
* Two modes, evaluated in order:
|
|
6
|
-
*
|
|
7
|
-
* 1. Cookie path — Better Auth session cookie present and valid.
|
|
8
|
-
* Looked up directly against the `Session` table (the row Better Auth's
|
|
9
|
-
* database adapter creates on sign-in). Cookie wins when both modes are
|
|
10
|
-
* present (locked by brief decision OQ-D / spec §3 "Both present").
|
|
11
|
-
* Non-GET requests with a cookie are required to carry an Origin header
|
|
12
|
-
* that is exactly one of the configured trusted origins (#7395) — the
|
|
13
|
-
* lightweight CSRF mechanism agreed in OQ-D. Bearer-authenticated callers
|
|
14
|
-
* skip the origin check (non-browser flow).
|
|
15
|
-
*
|
|
16
|
-
* 2. Bearer path — Authorization: Bearer <token>. The token is sha256-hashed
|
|
17
|
-
* and matched against `ApiToken.token` (stored as hash). Revoked / expired
|
|
18
|
-
* tokens fail closed; lastUsedAt is bumped on a successful hit.
|
|
19
|
-
*
|
|
20
|
-
* 3. Agent path (#3591) — a bearer starting `ailk_agt_` is an agent's key
|
|
21
|
-
* (`lib/agent-store.ts`). Its digest must match an enabled `Agent` whose
|
|
22
|
-
* key-holder still holds a membership; anything else is the same 401.
|
|
23
|
-
* The request acts as the agent's person (`request.userId`) and carries
|
|
24
|
-
* `request.agent` — never `request.sessionTenant`, which stays the
|
|
25
|
-
* cookie-only signal. An agent reaches ONLY routes a tenant guard tagged
|
|
26
|
-
* (`config.tenantGuarded`, set by both guards' `onRoute` hook) and is 403
|
|
27
|
-
* everywhere else; inside them the guard clamps its role. Each agent is
|
|
28
|
-
* limited to 60 requests a minute, at most 10 of them writes (any
|
|
29
|
-
* method but GET and HEAD).
|
|
30
|
-
*
|
|
31
|
-
* 4. Hub-credential path (#7894) — a bearer starting `ailk_hst_` is a
|
|
32
|
-
* credential a spoke holds for a person the hub signed in on its behalf
|
|
33
|
-
* (`lib/hub-sign-in-store.ts`). It is admitted only beside the SAME
|
|
34
|
-
* spoke's key (`x-ailk-spoke-key`), only while its grant is unrevoked and
|
|
35
|
-
* unexpired, its client enabled and its person's email verified, and only
|
|
36
|
-
* on `HUB_SESSION_ROUTES` — an exact list of METHOD + registered URL
|
|
37
|
-
* pairs — the credential's own `me`, the member routes and the
|
|
38
|
-
* account-agreements pair, 26 in all (#8220, #8515, #8765); any other
|
|
39
|
-
* route is 404 (never 403, so the account area's `read()` loads
|
|
40
|
-
* "nothing for this caller"). It acts as its person
|
|
41
|
-
* (`request.userId`) and carries `request.hubSession` — never
|
|
42
|
-
* `request.sessionTenant` — and its request logger names the spoke
|
|
43
|
-
* (`spokeId`). Each person is limited, at each spoke, to 60 requests and
|
|
44
|
-
* 10 writes a minute and 30 writes an hour — a fixed hour window from
|
|
45
|
-
* the bucket's first admitted write (#8220 D2, SEC-8169-4): the bucket is
|
|
46
|
-
* keyed on the VERIFIED spoke and person, never a header, and charged
|
|
47
|
-
* only after every check above passes, so a refused credential or an
|
|
48
|
-
* off-list route never gets one. The write limits refuse only writes,
|
|
49
|
-
* and a request the budget refuses is not counted.
|
|
50
|
-
*
|
|
51
|
-
* Every authenticated request carries `request.actor` — the person, or the
|
|
52
|
-
* agent and the person it acts for — and its request/reply loggers are
|
|
53
|
-
* rebound to name it, so Fastify's "request completed" line says who acted.
|
|
54
|
-
* No log line carries a credential.
|
|
55
|
-
*
|
|
56
|
-
* Decision matrix (per spec §3):
|
|
57
|
-
* - Cookie valid → cookie path wins. Bearer ignored.
|
|
58
|
-
* - Cookie absent, bearer valid → bearer path; CSRF skipped.
|
|
59
|
-
* - Both modes invalid → 401 unauthorized.
|
|
60
|
-
* - Cookie expired + bearer valid → 401 (no silent fallback).
|
|
61
|
-
* - Cookie absent, bearer malformed → 401 (no silent fallback).
|
|
62
|
-
* - DATABASE_URL unset → 503 database_not_configured (#6482).
|
|
63
|
-
*
|
|
64
|
-
* Public routes (health, content, leads, webhooks, openapi) are NOT registered
|
|
65
|
-
* with this preHandler — server.ts narrows scope via a sub-plugin.
|
|
66
|
-
*/
|
|
67
|
-
import { createHash } from "node:crypto";
|
|
68
|
-
|
|
69
|
-
import { getClock } from "@working-theory/common";
|
|
70
|
-
import type { Id } from "@working-theory/ids";
|
|
71
|
-
import type {
|
|
72
|
-
FastifyPluginCallback,
|
|
73
|
-
FastifyReply,
|
|
74
|
-
FastifyRequest,
|
|
75
|
-
} from "fastify";
|
|
76
|
-
|
|
77
|
-
import {
|
|
78
|
-
AGENT_KEY_PREFIX,
|
|
79
|
-
findAgentByKeyHash,
|
|
80
|
-
hashAgentKey,
|
|
81
|
-
touchAgentLastActive,
|
|
82
|
-
type AgentGrant,
|
|
83
|
-
} from "../lib/agent-store.js";
|
|
84
|
-
import {
|
|
85
|
-
findAuthorizationByCredentialHash,
|
|
86
|
-
HUB_SESSION_PREFIX,
|
|
87
|
-
} from "../lib/hub-sign-in-store.js";
|
|
88
|
-
import { prisma } from "../lib/prisma.js";
|
|
89
|
-
import { findSpokeByKeyHash, hashSpokeKey } from "../lib/spoke-store.js";
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Better Auth's default cookie names. Both are tried in order so a deployment
|
|
93
|
-
* running over HTTP locally and HTTPS in production reads the same code path.
|
|
94
|
-
*/
|
|
95
|
-
const COOKIE_NAMES = [
|
|
96
|
-
"better-auth.session_token",
|
|
97
|
-
"__Secure-better-auth.session_token",
|
|
98
|
-
] as const;
|
|
99
|
-
|
|
100
|
-
const UNAUTHORIZED = { error: "unauthorized" } as const;
|
|
101
|
-
const DATABASE_NOT_CONFIGURED = { error: "database_not_configured" } as const;
|
|
102
|
-
const NOT_FOUND = {
|
|
103
|
-
ok: false,
|
|
104
|
-
error: { code: "not_found", message: "Not found" },
|
|
105
|
-
} as const;
|
|
106
|
-
const SPOKE_KEY_HEADER = "x-ailk-spoke-key";
|
|
107
|
-
const FORBIDDEN = {
|
|
108
|
-
ok: false,
|
|
109
|
-
error: { code: "forbidden", message: "Forbidden" },
|
|
110
|
-
} as const;
|
|
111
|
-
/**
|
|
112
|
-
* How stale `Agent.lastActiveAt` may be before an admitted request writes it
|
|
113
|
-
* again: the column is "active within the last minute", not a request log,
|
|
114
|
-
* so a busy agent costs one UPDATE a minute rather than one per request.
|
|
115
|
-
*/
|
|
116
|
-
const AGENT_ACTIVITY_WRITE_INTERVAL_MS = 60_000;
|
|
117
|
-
|
|
118
|
-
const RATE_LIMITED = {
|
|
119
|
-
ok: false,
|
|
120
|
-
error: { code: "rate_limited", message: "Too many requests" },
|
|
121
|
-
} as const;
|
|
122
|
-
|
|
123
|
-
declare module "fastify" {
|
|
124
|
-
interface FastifyRequest {
|
|
125
|
-
userId?: Id<"user">;
|
|
126
|
-
sessionTenant?: SessionTenant;
|
|
127
|
-
/** Set ONLY on the agent path (#3591 D4). */
|
|
128
|
-
agent?: AgentGrant;
|
|
129
|
-
/** Who made the request — set on every authenticated request (#3591 D9). */
|
|
130
|
-
actor?: RequestActor;
|
|
131
|
-
/**
|
|
132
|
-
* Set ONLY on the hub-credential path (#7894 D6): the spoke the credential
|
|
133
|
-
* and the request's spoke key both name, and that spoke's workspace.
|
|
134
|
-
*/
|
|
135
|
-
hubSession?: HubSession;
|
|
136
|
-
}
|
|
137
|
-
interface FastifyContextConfig {
|
|
138
|
-
/**
|
|
139
|
-
* Set by both tenant guards' `onRoute` hook on every route registered
|
|
140
|
-
* after them in their scope (#3591 D6). An agent is refused on any route
|
|
141
|
-
* without it.
|
|
142
|
-
*/
|
|
143
|
-
tenantGuarded?: boolean;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/** The spoke a hub-issued credential was admitted for (#7894 D6). */
|
|
148
|
-
export type HubSession = {
|
|
149
|
-
spokeId: Id<"spoke">;
|
|
150
|
-
workspaceId: Id<"workspace">;
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* The only routes a hub-issued credential reaches (#7894 D6, #8220 D1): an
|
|
155
|
-
* EXACT list of method + registered URL, never a prefix and never computed,
|
|
156
|
-
* so a route added later joins only by a reviewed edit of this list
|
|
157
|
-
* (SEC-7894-3). Six groups, in this order: the credential's own `me` and
|
|
158
|
-
* #7877's three member reads; #8169's eight member reads; #8170's six member
|
|
159
|
-
* writes (roadmap §7.2 row order); #8438's member consents read and write;
|
|
160
|
-
* #8765's account-agreements read and write; #8515's onboarding read and
|
|
161
|
-
* write — 26 in all.
|
|
162
|
-
* `GET /v1/consent/platform` is not admitted — it is the Admin owner's own
|
|
163
|
-
* consent to Working Theory, on the hub's cookie session. Nor is the
|
|
164
|
-
* anonymous `GET /v1/consent/sign-up` (#8765 D2): it takes no credential. The member profile write IS admitted — it
|
|
165
|
-
* is the person's own account, which roadmap D9 saves through it. Not
|
|
166
|
-
* admitted, deliberately: `GET /v1/waitlist-signups/me`,
|
|
167
|
-
* `GET /v1/flow-checkouts/me`, `GET /v1/subscription/me` and
|
|
168
|
-
* `POST /v1/subscription/portal-session` (they answer for the HUB's site and
|
|
169
|
-
* the person's platform billing, SEC-7894-1, -2), the token routes and the
|
|
170
|
-
* hub's own-account `/v1/user/*` routes, `PATCH /v1/user/profile` among them.
|
|
171
|
-
*/
|
|
172
|
-
export const HUB_SESSION_ROUTES: readonly string[] = Object.freeze([
|
|
173
|
-
"GET /v1/hub-sign-in/me",
|
|
174
|
-
"GET /v1/member/pieces",
|
|
175
|
-
"GET /v1/member/messages",
|
|
176
|
-
"GET /v1/member/messages/:id/file",
|
|
177
|
-
"GET /v1/member/purchases",
|
|
178
|
-
"GET /v1/member/appointments",
|
|
179
|
-
"GET /v1/member/artifacts",
|
|
180
|
-
"GET /v1/member/artifacts/:pieceId/versions/:version/file",
|
|
181
|
-
"GET /v1/member/meeting-types",
|
|
182
|
-
"GET /v1/member/availability",
|
|
183
|
-
"GET /v1/member/request-form",
|
|
184
|
-
"GET /v1/member/billing",
|
|
185
|
-
"POST /v1/member/artifacts/:pieceId/review",
|
|
186
|
-
"POST /v1/member/bookings",
|
|
187
|
-
"POST /v1/member/requests",
|
|
188
|
-
"POST /v1/member/messages",
|
|
189
|
-
"PATCH /v1/member/profile",
|
|
190
|
-
"GET /v1/member/profile/contact",
|
|
191
|
-
"PATCH /v1/member/profile/contact",
|
|
192
|
-
"POST /v1/member/deletion-request",
|
|
193
|
-
// #8438 D6 — the member's own consents. `GET /v1/consent/platform` is NOT
|
|
194
|
-
// admitted: those are the Admin owner's, on the hub's own cookie session.
|
|
195
|
-
"GET /v1/member/consents",
|
|
196
|
-
"POST /v1/member/consents",
|
|
197
|
-
// #8765 D5 — the caller's account-level sign-up agreements, so a spoke's
|
|
198
|
-
// account layout reads and accepts the one grant every site shares.
|
|
199
|
-
"GET /v1/consent/account",
|
|
200
|
-
"POST /v1/consent/account",
|
|
201
|
-
// #8515 D13 — the client's onboarding read and write.
|
|
202
|
-
"GET /v1/member/onboarding",
|
|
203
|
-
"POST /v1/member/onboarding",
|
|
204
|
-
]);
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Who made a request (#3591 D9). A cookie session and a personal `ApiToken`
|
|
208
|
-
* act as their person; an agent acts on behalf of its responsible person.
|
|
209
|
-
*/
|
|
210
|
-
export type RequestActor =
|
|
211
|
-
| { type: "user"; userId: Id<"user"> }
|
|
212
|
-
| { type: "agent"; agentId: Id<"agent">; onBehalfOfUserId: Id<"user"> };
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Active tenant context carried by a cookie session (multi-tenant module).
|
|
216
|
-
* Set ONLY on the cookie path — a personal `ApiToken` carries no workspace
|
|
217
|
-
* binding, and an agent carries its own (`request.agent`, #3591), so neither
|
|
218
|
-
* ever sets this. Code that needs a cookie session (the billing portal, the
|
|
219
|
-
* workspace switch) can rely on its presence. `sessionRowId` is the Session
|
|
220
|
-
* PK, used by the workspace-switch service to update the active tenant
|
|
221
|
-
* server-side.
|
|
222
|
-
*/
|
|
223
|
-
export type SessionTenant = {
|
|
224
|
-
sessionRowId: Id<"session">;
|
|
225
|
-
activeOrganizationId: Id<"organization"> | null;
|
|
226
|
-
activeWorkspaceId: Id<"workspace"> | null;
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Hash an opaque bearer-token string the same way `tokens.ts` does at create
|
|
231
|
-
* time. Centralised here so the lookup contract stays identical.
|
|
232
|
-
*/
|
|
233
|
-
function hashBearer(token: string): string {
|
|
234
|
-
return createHash("sha256").update(token).digest("hex");
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Parse the request's Cookie header for the first Better Auth session-token
|
|
239
|
-
* variant present. Returns the cookie value (decoded) or undefined.
|
|
240
|
-
*
|
|
241
|
-
* Cookie parsing intentionally avoids decoding `+` as space — session tokens
|
|
242
|
-
* are URL-safe and don't contain `+`. Use decodeURIComponent only on the value
|
|
243
|
-
* to handle the rare case Better Auth percent-encodes the token.
|
|
244
|
-
*/
|
|
245
|
-
function readSessionCookie(request: FastifyRequest): string | undefined {
|
|
246
|
-
const raw = request.headers.cookie;
|
|
247
|
-
if (!raw) return undefined;
|
|
248
|
-
for (const part of raw.split(";")) {
|
|
249
|
-
const eq = part.indexOf("=");
|
|
250
|
-
if (eq < 0) continue;
|
|
251
|
-
const name = part.slice(0, eq).trim();
|
|
252
|
-
if (!COOKIE_NAMES.includes(name as (typeof COOKIE_NAMES)[number])) continue;
|
|
253
|
-
const value = part.slice(eq + 1).trim();
|
|
254
|
-
if (!value) continue;
|
|
255
|
-
try {
|
|
256
|
-
// Better Auth signs the cookie as <token>.<signature>; the dual-mode
|
|
257
|
-
// preHandler only needs the token portion to look up the Session row.
|
|
258
|
-
// Strip the signature if present.
|
|
259
|
-
const decoded = decodeURIComponent(value);
|
|
260
|
-
const dot = decoded.indexOf(".");
|
|
261
|
-
return dot > 0 ? decoded.slice(0, dot) : decoded;
|
|
262
|
-
} catch {
|
|
263
|
-
return undefined;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return undefined;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function readBearer(request: FastifyRequest): string | undefined {
|
|
270
|
-
const auth = request.headers.authorization;
|
|
271
|
-
if (!auth) return undefined;
|
|
272
|
-
const match = /^Bearer\s+(\S+)$/i.exec(auth);
|
|
273
|
-
return match ? match[1] : undefined;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* CSRF check for cookie-authenticated mutating requests (#7395).
|
|
278
|
-
* Returns true if the request is safe (GET/HEAD/OPTIONS) or the Origin header
|
|
279
|
-
* is exactly one of the trusted origins. False otherwise — including for any
|
|
280
|
-
* write when the list is empty, in every environment.
|
|
281
|
-
*/
|
|
282
|
-
function isOriginAllowed(
|
|
283
|
-
request: FastifyRequest,
|
|
284
|
-
trustedOrigins: readonly string[],
|
|
285
|
-
): boolean {
|
|
286
|
-
const method = request.method.toUpperCase();
|
|
287
|
-
if (method === "GET" || method === "HEAD" || method === "OPTIONS")
|
|
288
|
-
return true;
|
|
289
|
-
const origin = request.headers.origin;
|
|
290
|
-
if (!origin) return false;
|
|
291
|
-
return trustedOrigins.includes(origin);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
type SessionRow = {
|
|
295
|
-
id: string;
|
|
296
|
-
userId: string;
|
|
297
|
-
expiresAt: Date;
|
|
298
|
-
activeOrganizationId: string | null;
|
|
299
|
-
activeWorkspaceId: string | null;
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
async function resolveCookieSession(
|
|
303
|
-
cookieToken: string,
|
|
304
|
-
): Promise<SessionRow | undefined> {
|
|
305
|
-
const row = await prisma.session.findUnique({
|
|
306
|
-
where: { token: cookieToken },
|
|
307
|
-
select: {
|
|
308
|
-
id: true,
|
|
309
|
-
userId: true,
|
|
310
|
-
expiresAt: true,
|
|
311
|
-
activeOrganizationId: true,
|
|
312
|
-
activeWorkspaceId: true,
|
|
313
|
-
},
|
|
314
|
-
});
|
|
315
|
-
if (!row) return undefined;
|
|
316
|
-
return row;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
type BearerRow = {
|
|
320
|
-
userId: string;
|
|
321
|
-
expiresAt: Date | null;
|
|
322
|
-
revokedAt: Date | null;
|
|
323
|
-
id: string;
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
async function resolveBearerToken(
|
|
327
|
-
bearer: string,
|
|
328
|
-
): Promise<BearerRow | undefined> {
|
|
329
|
-
const hash = hashBearer(bearer);
|
|
330
|
-
const row = await prisma.apiToken.findUnique({
|
|
331
|
-
where: { token: hash },
|
|
332
|
-
select: { id: true, userId: true, expiresAt: true, revokedAt: true },
|
|
333
|
-
});
|
|
334
|
-
return row ?? undefined;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Decorate `request.actor` and rebind the request and reply loggers to name
|
|
339
|
-
* it (#3591 D9). Fastify writes its "request completed" line through
|
|
340
|
-
* `reply.log`, so that line carries the actor too.
|
|
341
|
-
*/
|
|
342
|
-
function bindActor(
|
|
343
|
-
request: FastifyRequest,
|
|
344
|
-
reply: FastifyReply,
|
|
345
|
-
actor: RequestActor,
|
|
346
|
-
): void {
|
|
347
|
-
request.actor = actor;
|
|
348
|
-
request.log = request.log.child(
|
|
349
|
-
actor.type === "agent"
|
|
350
|
-
? {
|
|
351
|
-
actorType: "agent",
|
|
352
|
-
agentId: actor.agentId,
|
|
353
|
-
onBehalfOfUserId: actor.onBehalfOfUserId,
|
|
354
|
-
}
|
|
355
|
-
: { actorType: "user" },
|
|
356
|
-
);
|
|
357
|
-
reply.log = request.log;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// ── The per-caller limits (#3591 D4 step 5, SEC-3591-04/-14; #8220 D2) ──────
|
|
361
|
-
//
|
|
362
|
-
// In the handler, not `onRequest`: the shared limiter runs before any
|
|
363
|
-
// identity exists, and a bucket must only ever be keyed on a VERIFIED id, so
|
|
364
|
-
// an invented key or credential never gets one. The shape of
|
|
365
|
-
// `routes/subscription/portal.ts`'s per-user budget. An agent and a person
|
|
366
|
-
// never share a map.
|
|
367
|
-
|
|
368
|
-
const AGENT_WINDOW_MS = 60_000;
|
|
369
|
-
const AGENT_REQUESTS_PER_WINDOW = 60;
|
|
370
|
-
const AGENT_WRITES_PER_WINDOW = 10;
|
|
371
|
-
/** Cap on distinct buckets — the oldest is evicted at capacity (DEF-011's shape). */
|
|
372
|
-
const MAX_AGENT_BUCKETS = 5_000;
|
|
373
|
-
|
|
374
|
-
const HUB_SESSION_WINDOW_MS = 60_000;
|
|
375
|
-
const HUB_SESSION_REQUESTS_PER_WINDOW = 60;
|
|
376
|
-
const HUB_SESSION_WRITES_PER_WINDOW = 10;
|
|
377
|
-
const HUB_SESSION_WRITE_HOUR_MS = 3_600_000;
|
|
378
|
-
const HUB_SESSION_WRITES_PER_HOUR = 30;
|
|
379
|
-
const MAX_HUB_SESSION_BUCKETS = 5_000;
|
|
380
|
-
|
|
381
|
-
type CallerBucket = {
|
|
382
|
-
windowStartMs: number;
|
|
383
|
-
requests: number;
|
|
384
|
-
writes: number;
|
|
385
|
-
/** The hourly write window — set by the bucket's first admitted write. */
|
|
386
|
-
hourStartMs: number | null;
|
|
387
|
-
hourWrites: number;
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
type CallerLimits = {
|
|
391
|
-
windowMs: number;
|
|
392
|
-
maxBuckets: number;
|
|
393
|
-
requests: number;
|
|
394
|
-
writes: number;
|
|
395
|
-
/** An hourly write ceiling — the hub-credential path's alone. */
|
|
396
|
-
writesPerHour?: { limit: number; windowMs: number };
|
|
397
|
-
/** When true, the write limits refuse only writes; a read meets only `requests`. */
|
|
398
|
-
refuseOnlyWrites: boolean;
|
|
399
|
-
/** When true, a refused request still counts toward every limit. */
|
|
400
|
-
countRefused: boolean;
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
const AGENT_LIMITS: CallerLimits = {
|
|
404
|
-
windowMs: AGENT_WINDOW_MS,
|
|
405
|
-
maxBuckets: MAX_AGENT_BUCKETS,
|
|
406
|
-
requests: AGENT_REQUESTS_PER_WINDOW,
|
|
407
|
-
writes: AGENT_WRITES_PER_WINDOW,
|
|
408
|
-
refuseOnlyWrites: false,
|
|
409
|
-
countRefused: true,
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
const HUB_SESSION_LIMITS: CallerLimits = {
|
|
413
|
-
windowMs: HUB_SESSION_WINDOW_MS,
|
|
414
|
-
maxBuckets: MAX_HUB_SESSION_BUCKETS,
|
|
415
|
-
requests: HUB_SESSION_REQUESTS_PER_WINDOW,
|
|
416
|
-
writes: HUB_SESSION_WRITES_PER_WINDOW,
|
|
417
|
-
writesPerHour: {
|
|
418
|
-
limit: HUB_SESSION_WRITES_PER_HOUR,
|
|
419
|
-
windowMs: HUB_SESSION_WRITE_HOUR_MS,
|
|
420
|
-
},
|
|
421
|
-
refuseOnlyWrites: true,
|
|
422
|
-
countRefused: false,
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
const agentBuckets = new Map<string, CallerBucket>();
|
|
426
|
-
const hubSessionBuckets = new Map<string, CallerBucket>();
|
|
427
|
-
|
|
428
|
-
/** Test-only reset. Not intended for production use. */
|
|
429
|
-
export function __resetAgentRateBuckets(): void {
|
|
430
|
-
agentBuckets.clear();
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/** Test-only reset. Not intended for production use. */
|
|
434
|
-
export function __resetHubSessionRateBuckets(): void {
|
|
435
|
-
hubSessionBuckets.clear();
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function isWrite(method: string): boolean {
|
|
439
|
-
const m = method.toUpperCase();
|
|
440
|
-
return m !== "GET" && m !== "HEAD";
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/** A bucket no window still holds — the eviction pass may drop it. */
|
|
444
|
-
function isStale(
|
|
445
|
-
bucket: CallerBucket,
|
|
446
|
-
now: number,
|
|
447
|
-
limits: CallerLimits,
|
|
448
|
-
): boolean {
|
|
449
|
-
if (now - bucket.windowStartMs < limits.windowMs) return false;
|
|
450
|
-
return (
|
|
451
|
-
bucket.hourStartMs === null ||
|
|
452
|
-
limits.writesPerHour === undefined ||
|
|
453
|
-
now - bucket.hourStartMs >= limits.writesPerHour.windowMs
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Charge one request to `key`'s bucket. `undefined` when admitted; the
|
|
459
|
-
* seconds to wait — to the end of the latest window that refused, at least
|
|
460
|
-
* 1 — when over budget.
|
|
461
|
-
*/
|
|
462
|
-
function chargeCallerBucket(
|
|
463
|
-
buckets: Map<string, CallerBucket>,
|
|
464
|
-
key: string,
|
|
465
|
-
method: string,
|
|
466
|
-
now: number,
|
|
467
|
-
limits: CallerLimits,
|
|
468
|
-
): number | undefined {
|
|
469
|
-
let bucket = buckets.get(key);
|
|
470
|
-
if (!bucket || now - bucket.windowStartMs >= limits.windowMs) {
|
|
471
|
-
if (!bucket && buckets.size >= limits.maxBuckets) {
|
|
472
|
-
for (const [k, b] of buckets) {
|
|
473
|
-
if (isStale(b, now, limits)) buckets.delete(k);
|
|
474
|
-
}
|
|
475
|
-
if (buckets.size >= limits.maxBuckets) {
|
|
476
|
-
const oldest = buckets.keys().next().value;
|
|
477
|
-
if (oldest !== undefined) buckets.delete(oldest);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
// Re-insert, so Map order stays oldest-window-first for the eviction
|
|
481
|
-
// above. A new minute keeps the hour's start and count.
|
|
482
|
-
buckets.delete(key);
|
|
483
|
-
bucket = {
|
|
484
|
-
windowStartMs: now,
|
|
485
|
-
requests: 0,
|
|
486
|
-
writes: 0,
|
|
487
|
-
hourStartMs: bucket?.hourStartMs ?? null,
|
|
488
|
-
hourWrites: bucket?.hourWrites ?? 0,
|
|
489
|
-
};
|
|
490
|
-
buckets.set(key, bucket);
|
|
491
|
-
}
|
|
492
|
-
const hour = limits.writesPerHour;
|
|
493
|
-
if (
|
|
494
|
-
hour !== undefined &&
|
|
495
|
-
bucket.hourStartMs !== null &&
|
|
496
|
-
now - bucket.hourStartMs >= hour.windowMs
|
|
497
|
-
) {
|
|
498
|
-
bucket.hourStartMs = null;
|
|
499
|
-
bucket.hourWrites = 0;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
const write = isWrite(method);
|
|
503
|
-
const requests = bucket.requests + 1;
|
|
504
|
-
const writes = bucket.writes + (write ? 1 : 0);
|
|
505
|
-
const hourWrites = bucket.hourWrites + (write ? 1 : 0);
|
|
506
|
-
const minuteEnd = bucket.windowStartMs + limits.windowMs;
|
|
507
|
-
const refusedUntil: number[] = [];
|
|
508
|
-
if (requests > limits.requests) refusedUntil.push(minuteEnd);
|
|
509
|
-
if (write || !limits.refuseOnlyWrites) {
|
|
510
|
-
if (writes > limits.writes) refusedUntil.push(minuteEnd);
|
|
511
|
-
if (write && hour !== undefined && hourWrites > hour.limit) {
|
|
512
|
-
refusedUntil.push((bucket.hourStartMs ?? now) + hour.windowMs);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
const refused = refusedUntil.length > 0;
|
|
517
|
-
if (!refused || limits.countRefused) {
|
|
518
|
-
bucket.requests = requests;
|
|
519
|
-
bucket.writes = writes;
|
|
520
|
-
if (write && hour !== undefined) {
|
|
521
|
-
bucket.hourWrites = hourWrites;
|
|
522
|
-
bucket.hourStartMs ??= now;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
if (!refused) return undefined;
|
|
526
|
-
return Math.max(1, Math.ceil((Math.max(...refusedUntil) - now) / 1_000));
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* The agent path (#3591 D4, D6). The key is never logged and never echoed:
|
|
531
|
-
* every refusal is a fixed body.
|
|
532
|
-
*/
|
|
533
|
-
async function authenticateAgent(
|
|
534
|
-
request: FastifyRequest,
|
|
535
|
-
reply: FastifyReply,
|
|
536
|
-
key: string,
|
|
537
|
-
now: number,
|
|
538
|
-
): Promise<void> {
|
|
539
|
-
const row = await findAgentByKeyHash(hashAgentKey(key)).catch(() => null);
|
|
540
|
-
// Unknown, switched off, or its key-holder has left the organization (the
|
|
541
|
-
// FK set the column null) — one body for each, so none is an oracle.
|
|
542
|
-
if (!row || !row.enabled || !row.keyIssuedToMembershipId) {
|
|
543
|
-
reply.code(401).send(UNAUTHORIZED);
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// Brand assertion at the Prisma read edge (ADR-0031 rule 5): the agents
|
|
548
|
-
// table's format CHECKs guarantee these columns hold well-formed ids.
|
|
549
|
-
const agentId = row.id as Id<"agent">;
|
|
550
|
-
const personId = row.responsible.userId as Id<"user">;
|
|
551
|
-
request.userId = personId;
|
|
552
|
-
request.agent = {
|
|
553
|
-
agentId,
|
|
554
|
-
workspaceId: row.workspaceId as Id<"workspace">,
|
|
555
|
-
organizationId: row.organizationId as Id<"organization">,
|
|
556
|
-
responsibleMembershipId: row.responsible.id as Id<"membership">,
|
|
557
|
-
permissionLevel: row.permissionLevel,
|
|
558
|
-
// #7864 D3 — the one place a missing value becomes none; the tenant
|
|
559
|
-
// guards keep only the grants its person's role also holds.
|
|
560
|
-
grants: row.grants ?? [],
|
|
561
|
-
};
|
|
562
|
-
bindActor(request, reply, {
|
|
563
|
-
type: "agent",
|
|
564
|
-
agentId,
|
|
565
|
-
onBehalfOfUserId: personId,
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
const retryAfter = chargeCallerBucket(
|
|
569
|
-
agentBuckets,
|
|
570
|
-
agentId,
|
|
571
|
-
request.method,
|
|
572
|
-
now,
|
|
573
|
-
AGENT_LIMITS,
|
|
574
|
-
);
|
|
575
|
-
if (retryAfter !== undefined) {
|
|
576
|
-
reply.code(429).header("Retry-After", String(retryAfter)).send(RATE_LIMITED);
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// D6 — an agent reaches only routes a tenant guard runs on. Every other
|
|
581
|
-
// route in the auth scope (tokens, user data, `/me` reads, the leads union,
|
|
582
|
-
// the workspace switcher), and any route added outside a tenant scope
|
|
583
|
-
// later, is closed to it by default.
|
|
584
|
-
if (request.routeOptions.config?.tenantGuarded !== true) {
|
|
585
|
-
reply.code(403).send(FORBIDDEN);
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Best-effort, as the token path bumps lastUsedAt: a failure never 401s.
|
|
590
|
-
// Only an admitted request writes, and only when the stamp is a minute old.
|
|
591
|
-
if (
|
|
592
|
-
!row.lastActiveAt ||
|
|
593
|
-
now - row.lastActiveAt.getTime() >= AGENT_ACTIVITY_WRITE_INTERVAL_MS
|
|
594
|
-
) {
|
|
595
|
-
touchAgentLastActive(agentId, new Date(now)).catch(() => undefined);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* The hub-credential path (#7894 D6). The credential is never logged and
|
|
601
|
-
* never echoed: every refusal is a fixed body. (1)-(3) failing is the one 401;
|
|
602
|
-
* (4), the route not being on `HUB_SESSION_ROUTES`, is 404 with no handler run;
|
|
603
|
-
* (5), the person over budget at this spoke (#8220 D2), is 429 with no
|
|
604
|
-
* handler run.
|
|
605
|
-
*/
|
|
606
|
-
async function authenticateHubSession(
|
|
607
|
-
request: FastifyRequest,
|
|
608
|
-
reply: FastifyReply,
|
|
609
|
-
credential: string,
|
|
610
|
-
now: number,
|
|
611
|
-
): Promise<void> {
|
|
612
|
-
// (1) The spoke key resolves to an unrevoked spoke.
|
|
613
|
-
const rawKey = request.headers[SPOKE_KEY_HEADER];
|
|
614
|
-
const spokeKey = typeof rawKey === "string" ? rawKey.trim() : "";
|
|
615
|
-
const spoke = spokeKey
|
|
616
|
-
? await findSpokeByKeyHash(hashSpokeKey(spokeKey)).catch(() => null)
|
|
617
|
-
: null;
|
|
618
|
-
if (!spoke || spoke.revokedAt) {
|
|
619
|
-
reply.code(401).send(UNAUTHORIZED);
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
// (2) The credential's grant is live, and its client IS this spoke's.
|
|
624
|
-
// (3) Its person's email is verified (read in the same query).
|
|
625
|
-
const grant = await findAuthorizationByCredentialHash(
|
|
626
|
-
hashBearer(credential),
|
|
627
|
-
).catch(() => null);
|
|
628
|
-
if (
|
|
629
|
-
!grant ||
|
|
630
|
-
grant.revokedAt ||
|
|
631
|
-
grant.expiresAt.getTime() <= now ||
|
|
632
|
-
!grant.client.enabled ||
|
|
633
|
-
grant.client.clientId !== spoke.id ||
|
|
634
|
-
grant.user.emailVerified !== true
|
|
635
|
-
) {
|
|
636
|
-
reply.code(401).send(UNAUTHORIZED);
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
// (4) Method and registered URL are on the list.
|
|
641
|
-
const route = `${request.method.toUpperCase()} ${request.routeOptions.url ?? ""}`;
|
|
642
|
-
if (!HUB_SESSION_ROUTES.includes(route)) {
|
|
643
|
-
reply.code(404).send(NOT_FOUND);
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// Brand assertion at the Prisma read edge (ADR-0031 rule 5), as above.
|
|
648
|
-
request.userId = grant.userId as Id<"user">;
|
|
649
|
-
request.hubSession = {
|
|
650
|
-
spokeId: spoke.id as Id<"spoke">,
|
|
651
|
-
workspaceId: spoke.workspaceId as Id<"workspace">,
|
|
652
|
-
};
|
|
653
|
-
bindActor(request, reply, { type: "user", userId: request.userId });
|
|
654
|
-
// #8220 D10 — every line the request logger writes after admission names
|
|
655
|
-
// the spoke that acted: its id, never its key or the credential. (Lines a
|
|
656
|
-
// helper writes through the module-level `logger` carry no request binding.)
|
|
657
|
-
request.log = request.log.child({ spokeId: spoke.id });
|
|
658
|
-
reply.log = request.log;
|
|
659
|
-
|
|
660
|
-
// (5) The verified (spoke, person) is within its budget.
|
|
661
|
-
const retryAfter = chargeCallerBucket(
|
|
662
|
-
hubSessionBuckets,
|
|
663
|
-
`${spoke.id}:${grant.userId}`,
|
|
664
|
-
request.method,
|
|
665
|
-
now,
|
|
666
|
-
HUB_SESSION_LIMITS,
|
|
667
|
-
);
|
|
668
|
-
if (retryAfter !== undefined) {
|
|
669
|
-
reply.code(429).header("Retry-After", String(retryAfter)).send(RATE_LIMITED);
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
export type AuthPreHandlerOptions = {
|
|
675
|
-
/**
|
|
676
|
-
* The browser origins a cookie-authenticated write may come from — exact
|
|
677
|
-
* matches only. server.ts passes `config.TRUSTED_ORIGINS` (#7395).
|
|
678
|
-
*/
|
|
679
|
-
trustedOrigins: readonly string[];
|
|
680
|
-
};
|
|
681
|
-
|
|
682
|
-
/**
|
|
683
|
-
* The result of `resolveOptionalCookieUser` — a signed-in user, or the reason
|
|
684
|
-
* there is none (#6866 D2).
|
|
685
|
-
*/
|
|
686
|
-
export type OptionalCookieUser =
|
|
687
|
-
| { kind: "user"; userId: Id<"user"> }
|
|
688
|
-
| {
|
|
689
|
-
kind: "none";
|
|
690
|
-
reason:
|
|
691
|
-
| "no_cookie"
|
|
692
|
-
| "no_database"
|
|
693
|
-
| "unknown_session"
|
|
694
|
-
| "expired"
|
|
695
|
-
| "cross_origin"
|
|
696
|
-
| "lookup_failed";
|
|
697
|
-
};
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
* Non-rejecting cookie resolver for routes that stay open to anonymous callers
|
|
701
|
-
* but act on a signed-in user when there is one (#6866 D2 — the checkout
|
|
702
|
-
* route's buyer stamp).
|
|
703
|
-
*
|
|
704
|
-
* Applies the cookie path's own rules — the same private cookie reader,
|
|
705
|
-
* Session lookup, expiry comparison and trusted-origin check as
|
|
706
|
-
* `makeAuthPreHandler` — so the two paths cannot drift. Reads the COOKIE only:
|
|
707
|
-
* a bearer `ApiToken` never identifies a user here. Never writes a reply and
|
|
708
|
-
* never throws; every failure is a `none` with its reason. The cookie is
|
|
709
|
-
* checked first, so an anonymous call on a database-less boot is `no_cookie`.
|
|
710
|
-
*/
|
|
711
|
-
export async function resolveOptionalCookieUser(
|
|
712
|
-
request: FastifyRequest,
|
|
713
|
-
opts: AuthPreHandlerOptions,
|
|
714
|
-
): Promise<OptionalCookieUser> {
|
|
715
|
-
const cookieToken = readSessionCookie(request);
|
|
716
|
-
if (!cookieToken) return { kind: "none", reason: "no_cookie" };
|
|
717
|
-
if (!process.env.DATABASE_URL) return { kind: "none", reason: "no_database" };
|
|
718
|
-
|
|
719
|
-
let session: SessionRow | undefined;
|
|
720
|
-
try {
|
|
721
|
-
session = await resolveCookieSession(cookieToken);
|
|
722
|
-
} catch {
|
|
723
|
-
return { kind: "none", reason: "lookup_failed" };
|
|
724
|
-
}
|
|
725
|
-
if (!session) return { kind: "none", reason: "unknown_session" };
|
|
726
|
-
if (session.expiresAt.getTime() <= getClock().now()) {
|
|
727
|
-
return { kind: "none", reason: "expired" };
|
|
728
|
-
}
|
|
729
|
-
if (!isOriginAllowed(request, opts.trustedOrigins)) {
|
|
730
|
-
return { kind: "none", reason: "cross_origin" };
|
|
731
|
-
}
|
|
732
|
-
// Brand assertion at the Prisma read edge (ADR-0031 rule 5), as above.
|
|
733
|
-
return { kind: "user", userId: session.userId as Id<"user"> };
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* The person a member read answers for (#7877 D5): the hub's own cookie
|
|
738
|
-
* session or a hub-issued credential (#7894 D6) — never a personal ApiToken,
|
|
739
|
-
* never an agent. `null` → the route answers 401 with `UNAUTHORIZED`.
|
|
740
|
-
*
|
|
741
|
-
* This is the ONE place the member reads (`services/member-area.ts`) learn
|
|
742
|
-
* who is calling: a later credential kind joins them by an edit of this
|
|
743
|
-
* function and nothing in `routes/member/`. An agent never gets here — the
|
|
744
|
-
* member routes are not tenant-guarded, so this preHandler answers it 403.
|
|
745
|
-
*/
|
|
746
|
-
export function memberCaller(request: FastifyRequest): Id<"user"> | null {
|
|
747
|
-
if (request.userId === undefined) return null;
|
|
748
|
-
if (request.sessionTenant !== undefined || request.hubSession !== undefined) {
|
|
749
|
-
return request.userId;
|
|
750
|
-
}
|
|
751
|
-
return null;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
/**
|
|
755
|
-
* Fastify preHandler implementing the dual-mode contract.
|
|
756
|
-
*
|
|
757
|
-
* Exported standalone so route-test suites can register it directly without
|
|
758
|
-
* spinning up the full server module.
|
|
759
|
-
*/
|
|
760
|
-
export function makeAuthPreHandler(opts: AuthPreHandlerOptions) {
|
|
761
|
-
const { trustedOrigins } = opts;
|
|
762
|
-
return async function authPreHandler(
|
|
763
|
-
request: FastifyRequest,
|
|
764
|
-
reply: FastifyReply,
|
|
765
|
-
): Promise<void> {
|
|
766
|
-
// A database-less dev boot (#6482). First, and read per request: after
|
|
767
|
-
// the lookups below, their `.catch` would already have answered 401.
|
|
768
|
-
if (!process.env.DATABASE_URL) {
|
|
769
|
-
reply.code(503).send(DATABASE_NOT_CONFIGURED);
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
const now = getClock().now();
|
|
774
|
-
const cookieToken = readSessionCookie(request);
|
|
775
|
-
|
|
776
|
-
if (cookieToken) {
|
|
777
|
-
const session = await resolveCookieSession(cookieToken).catch(
|
|
778
|
-
() => undefined,
|
|
779
|
-
);
|
|
780
|
-
if (session && session.expiresAt.getTime() > now) {
|
|
781
|
-
if (!isOriginAllowed(request, trustedOrigins)) {
|
|
782
|
-
reply.code(401).send(UNAUTHORIZED);
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
|
-
// Brand assertion at the Prisma read edge (ADR-0031 rule 5): the DB
|
|
786
|
-
// CHECK constraints guarantee these columns hold well-formed
|
|
787
|
-
// prefixed ids, so the compile-time brand is asserted once here.
|
|
788
|
-
request.userId = session.userId as Id<"user">;
|
|
789
|
-
request.sessionTenant = {
|
|
790
|
-
sessionRowId: session.id as Id<"session">,
|
|
791
|
-
activeOrganizationId: session.activeOrganizationId as Id<"organization"> | null,
|
|
792
|
-
activeWorkspaceId: session.activeWorkspaceId as Id<"workspace"> | null,
|
|
793
|
-
};
|
|
794
|
-
bindActor(request, reply, { type: "user", userId: request.userId });
|
|
795
|
-
return;
|
|
796
|
-
}
|
|
797
|
-
// Cookie present but invalid / expired → 401 (no silent bearer fallback).
|
|
798
|
-
reply.code(401).send(UNAUTHORIZED);
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
const bearer = readBearer(request);
|
|
803
|
-
if (bearer?.startsWith(HUB_SESSION_PREFIX)) {
|
|
804
|
-
await authenticateHubSession(request, reply, bearer, now);
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
if (bearer?.startsWith(AGENT_KEY_PREFIX)) {
|
|
808
|
-
await authenticateAgent(request, reply, bearer, now);
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
811
|
-
if (bearer) {
|
|
812
|
-
const row = await resolveBearerToken(bearer).catch(() => undefined);
|
|
813
|
-
if (!row) {
|
|
814
|
-
reply.code(401).send(UNAUTHORIZED);
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
if (row.revokedAt) {
|
|
818
|
-
reply.code(401).send(UNAUTHORIZED);
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
if (row.expiresAt && row.expiresAt.getTime() <= now) {
|
|
822
|
-
reply.code(401).send(UNAUTHORIZED);
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
request.userId = row.userId as Id<"user">;
|
|
826
|
-
// A personal token acts as its person, as before (#3591 D9).
|
|
827
|
-
bindActor(request, reply, { type: "user", userId: request.userId });
|
|
828
|
-
// Best-effort: bump lastUsedAt. Failure here MUST NOT 401 — the caller
|
|
829
|
-
// is already authenticated. Swallow the error and continue.
|
|
830
|
-
prisma.apiToken
|
|
831
|
-
.update({ where: { id: row.id }, data: { lastUsedAt: new Date(now) } })
|
|
832
|
-
.catch(() => undefined);
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
// Neither present.
|
|
837
|
-
reply.code(401).send(UNAUTHORIZED);
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
/**
|
|
842
|
-
* Plugin form — exposes the preHandler via Fastify's `addHook`. The
|
|
843
|
-
* encapsulation override (Symbol.for("skip-override")) lets the hook
|
|
844
|
-
* propagate to sibling routes registered in the SAME parent scope after the
|
|
845
|
-
* plugin — matching the rate-limit.ts pattern. Without it, the hook would
|
|
846
|
-
* only fire for routes registered INSIDE the plugin, which is not the calling
|
|
847
|
-
* convention (server.ts registers the plugin first, then sibling routes).
|
|
848
|
-
*/
|
|
849
|
-
export const authPreHandlerPlugin: FastifyPluginCallback<
|
|
850
|
-
AuthPreHandlerOptions
|
|
851
|
-
> = (fastify, opts, done) => {
|
|
852
|
-
const handler = makeAuthPreHandler(opts);
|
|
853
|
-
fastify.decorateRequest("userId", undefined);
|
|
854
|
-
fastify.decorateRequest("sessionTenant", undefined);
|
|
855
|
-
fastify.decorateRequest("agent", undefined);
|
|
856
|
-
fastify.decorateRequest("actor", undefined);
|
|
857
|
-
fastify.decorateRequest("hubSession", undefined);
|
|
858
|
-
fastify.addHook("preHandler", handler);
|
|
859
|
-
done();
|
|
860
|
-
};
|
|
861
|
-
|
|
862
|
-
(authPreHandlerPlugin as FastifyPluginCallback & { [key: symbol]: boolean })[
|
|
863
|
-
Symbol.for("skip-override")
|
|
864
|
-
] = true;
|