create-ailk 0.24.0 → 0.26.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/README.md +53 -22
- package/component-catalog.json +3 -3
- package/component-catalog.md +3 -3
- package/dist/add-module.js +4 -2
- package/dist/add-route.d.ts +12 -1
- package/dist/add-route.js +31 -3
- package/dist/cli.js +12 -7
- package/dist/derive-architecture.d.ts +3 -4
- package/dist/derive-architecture.js +65 -29
- 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 +2 -1
- package/dist/lib/fetch-template-snapshot.js +8 -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 +36 -15
- package/dist/lib/module-tier.d.ts +2 -8
- package/dist/lib/module-tier.js +13 -12
- package/dist/module-architecture.d.ts +20 -3
- package/dist/module-architecture.js +129 -552
- package/dist/paid-sections.d.ts +25 -1
- package/dist/paid-sections.js +88 -3
- package/dist/starter-grid.d.ts +6 -6
- package/dist/starter-grid.js +12 -5
- package/dist/surfaces.js +39 -2
- package/dist/template-record.d.ts +21 -3
- package/dist/template-record.js +38 -7
- package/dist/topology.d.ts +16 -3
- package/dist/topology.js +54 -23
- package/dist/update.d.ts +10 -1
- package/dist/update.js +715 -21
- package/package.json +3 -3
- package/release-scope.json +2 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +8 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +2 -2
- package/site-templates/marketing-site/ailk-template.json +7 -1
- package/site-templates/marketing-site/provenance.json +7 -1
- package/site-templates/saas-product/ailk-template.json +2 -1
- package/site-templates/saas-product/provenance.json +1 -0
- 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 +20 -5
- package/templates/.github/workflows/ci.yml +7 -4
- package/templates/CLAUDE.md +11 -1
- package/templates/CONVENTIONS.md +8 -10
- package/templates/README.md +14 -13
- package/templates/apps/api/.env.example +23 -2
- package/templates/apps/api/CLAUDE.md +119 -83
- 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-client.js +16 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -3
- 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 -1882
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/CLAUDE.md +90 -26
- 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 -13
- 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 +31 -3
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +43 -4
- package/templates/apps/web/app/[locale]/(auth)/legal-footnote.tsx +35 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +117 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +14 -37
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +6 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +20 -35
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/page.test.tsx +108 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +111 -5
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +78 -16
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +132 -12
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +53 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +4 -2
- package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +47 -24
- package/templates/apps/web/app/[locale]/(authed)/signed-out-gate.generated.tsx +23 -4
- package/templates/apps/web/app/[locale]/contact/intake-analytics.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 +7 -3
- package/templates/apps/web/app/[locale]/layout.tsx +33 -13
- package/templates/apps/web/app/[locale]/meet/confirmed/BookingHandOff.tsx +128 -0
- package/templates/apps/web/app/[locale]/meet/confirmed/__tests__/BookingHandOff.test.tsx +126 -0
- 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 +10 -6
- package/templates/apps/web/i18n/request.ts +49 -1
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/__tests__/account-config.test.ts +7 -0
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +276 -0
- package/templates/apps/web/lib/auth-email.ts +103 -21
- package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +72 -23
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +94 -70
- package/templates/apps/web/lib/prisma-for-auth.ts +5 -2
- 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/lib/sign-up-agreements.ts +291 -28
- package/templates/apps/web/lib/site-contributions.ts +18 -0
- package/templates/apps/web/messages/en.json +186 -20
- package/templates/apps/web/messages/site/en.json +1 -0
- 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 +5 -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 +553 -7
- package/templates/tsconfig.json +1 -2
- package/versions.json +49 -36
- 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 -308
- 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 -257
- 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 -321
- 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 -391
- 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 -2148
- 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 -332
- 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 -1426
- 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 -183
- package/templates/apps/api/src/routes/content/index.ts +0 -49
- package/templates/apps/api/src/routes/content/list-pages.ts +0 -74
- 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 -100
- package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
- 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 -639
- 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 -491
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -476
- 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 -449
- 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 -809
- 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 -2028
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -645
- 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 -877
- 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 -1204
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -450
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +0 -202
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -151
- 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 -336
- 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 -750
- 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 -348
- 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 -975
- 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 -400
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -685
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
- package/templates/apps/api/src/services/offerings/catalog.ts +0 -318
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -403
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
- package/templates/apps/api/src/services/offerings/connection.ts +0 -1083
- 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 -1211
- 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 -2085
- 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 -1232
- 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 -70
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -162
- 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 -180
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
- package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -268
- package/templates/apps/mcp/__tests__/locale.test.ts +0 -120
- 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__/rate-limit-write.test.ts +0 -189
- package/templates/apps/mcp/__tests__/read_content.test.ts +0 -511
- package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -160
- package/templates/apps/mcp/__tests__/search_content.test.ts +0 -271
- 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 -222
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -152
- 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 -349
- 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 -564
- 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 -116
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -140
- 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/site-config.ts +0 -53
- 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 -66
- 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 -138
- package/templates/apps/mcp/src/tools/list_pages.ts +0 -68
- 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 -81
- package/templates/apps/mcp/src/tools/request_callback.ts +0 -52
- package/templates/apps/mcp/src/tools/search_content.ts +0 -63
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -54
- package/templates/apps/mcp/src/workspace-server.ts +0 -224
- package/templates/database/CHANGELOG.md +0 -981
- 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 -272
- package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
- package/templates/database/__tests__/tenancy.test.ts +0 -141
- package/templates/database/__tests__/trial-magnet-migration.test.ts +0 -186
- 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/20261025120000_trial_magnet/migration.sql +0 -28
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +0 -23
- 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
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
*
|
|
32
32
|
* Replay window (D7, reopened and closed by #5902): a captured delivery
|
|
33
33
|
* verifies for 5 minutes and no longer, the Slack receiver's tolerance
|
|
34
|
-
* (
|
|
34
|
+
* (packages-paid/api-signal-collectors/src/routes/slack/actions.ts). Within the window a replay is a
|
|
35
35
|
* second revalidateTag on an already-purged tag — a no-op — so the receiver
|
|
36
36
|
* still keeps no seen-set.
|
|
37
37
|
*
|
|
@@ -65,7 +65,7 @@ const SIGNATURE_TOLERANCE_SECONDS = 5 * 60;
|
|
|
65
65
|
const TIMESTAMP_SHAPE = /^[0-9]{1,12}$/;
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* The sender's bounds (
|
|
68
|
+
* The sender's bounds (packages/api/src/services/revalidation.ts), applied again
|
|
69
69
|
* here because this route is unauthenticated until the MAC verifies and the
|
|
70
70
|
* values become cache tags. `workspaceId` is logged, never tagged, so it stays
|
|
71
71
|
* a plain string.
|
|
@@ -90,7 +90,7 @@ type RevalidateBody = z.infer<typeof bodySchema>;
|
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* Line-for-line the repo's one shipped verifier
|
|
93
|
-
* (
|
|
93
|
+
* (packages-paid/api-signal-collectors/src/routes/slack/actions.ts:50-62), with Slack's `v0:<ts>:<body>`
|
|
94
94
|
* base string extended by the event (#5902). The length guard is required,
|
|
95
95
|
* not decorative: timingSafeEqual throws on unequal lengths. The `sha256=`
|
|
96
96
|
* prefix stays inside the compared material so a same-length `sha512=` header
|
|
@@ -459,7 +459,9 @@ export function createAuth(deps: CreateAuthDeps = {}) {
|
|
|
459
459
|
/**
|
|
460
460
|
* The sign-up grants (#8765 D4): the user hook decides whether the new
|
|
461
461
|
* user carries agreements, the account hook writes them on the captured
|
|
462
|
-
* transaction client.
|
|
462
|
+
* transaction client. A magic-link sign-up creates no Account, so the
|
|
463
|
+
* session hook writes its grants, in their own transaction, before the
|
|
464
|
+
* session exists (#9667 D5). No other database hook is installed.
|
|
463
465
|
*/
|
|
464
466
|
databaseHooks: signUpAgreementHooks(),
|
|
465
467
|
secret: resolveSecret(),
|
|
@@ -560,7 +562,7 @@ export function createAuth(deps: CreateAuthDeps = {}) {
|
|
|
560
562
|
*
|
|
561
563
|
* So: when the IP is resolvable the § D8a values apply as specified; when
|
|
562
564
|
* it is not, we fall through to the library's shorter-recovery defaults.
|
|
563
|
-
* This is the same reasoning `
|
|
565
|
+
* This is the same reasoning `packages/api/src/lib/auth-email-budget.ts`
|
|
564
566
|
* applies to the site-wide send cap it declines to implement — a
|
|
565
567
|
* site-wide cap an attacker can hold open is a denial of the control it
|
|
566
568
|
* implements — and the two now agree instead of contradicting each other.
|
|
@@ -568,7 +570,7 @@ export function createAuth(deps: CreateAuthDeps = {}) {
|
|
|
568
570
|
* STORAGE CAVEAT, load-bearing and NOT solved here: `"memory"` is a
|
|
569
571
|
* process-local Map, so on a multi-instance deployment the effective
|
|
570
572
|
* ceiling is 5 × N per 900 s — the identical caveat
|
|
571
|
-
* `
|
|
573
|
+
* `packages/api/src/middleware/rate-limit.ts` documents for itself. The
|
|
572
574
|
* durable alternative needs a rate-limit table the schema does not have,
|
|
573
575
|
* and this issue adds no migration. What does bound the consequence is
|
|
574
576
|
* the per-recipient send budget at the apps/api relay.
|
|
@@ -671,9 +673,11 @@ export function createAuth(deps: CreateAuthDeps = {}) {
|
|
|
671
673
|
/**
|
|
672
674
|
* `magicLink` only when its predicate holds (#7435 § D2): absent, its
|
|
673
675
|
* `/sign-in/magic-link` and `/magic-link/verify` endpoints do not exist.
|
|
674
|
-
* Sign-in only — the callback mails existing users
|
|
675
|
-
* `disableSignUp` refuses to create one even for a
|
|
676
|
-
* (lib/auth-email.ts).
|
|
676
|
+
* Sign-in only while sign-up is closed — the callback mails existing users
|
|
677
|
+
* and nobody else, and `disableSignUp` refuses to create one even for a
|
|
678
|
+
* live token (lib/auth-email.ts). While `isSignUpOpen()` a request
|
|
679
|
+
* carrying the agreements is a sign-up: its token is bound to them, and
|
|
680
|
+
* the verify creates the account only for a token so bound (#9667).
|
|
677
681
|
*
|
|
678
682
|
* `signUpAgreementsPlugin` always (#8765 D3): while sign-up is open,
|
|
679
683
|
* `/sign-up/email` without every sign-up agreement is refused before the
|
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
// English when no catalog exists (e.g. a locale is enabled in project.yaml
|
|
5
5
|
// but its messages/<locale>.json has not been translated yet).
|
|
6
6
|
//
|
|
7
|
+
// The site's own strings (#9401 D8) live in messages/site/<locale>.json, an
|
|
8
|
+
// overlay the template ships as `{}` and never changes, so `create-ailk
|
|
9
|
+
// update` keeps a site's edit of it. It falls back to the English overlay,
|
|
10
|
+
// then to nothing, and merges add-only: a key the template catalog already
|
|
11
|
+
// has keeps the template's value. The merge builds new objects, descends only
|
|
12
|
+
// into plain objects, takes only string leaves and skips prototype keys, so
|
|
13
|
+
// it can neither mutate the imported catalog nor pollute `Object.prototype`.
|
|
14
|
+
//
|
|
7
15
|
// Spec: docs/internal/specs/i18n-phase1-s2-next-intl-foundation-spec.md
|
|
8
16
|
|
|
9
17
|
import type { AbstractIntlMessages } from "next-intl";
|
|
@@ -11,6 +19,42 @@ import { getRequestConfig } from "next-intl/server";
|
|
|
11
19
|
|
|
12
20
|
const DEFAULT_LOCALE = "en";
|
|
13
21
|
|
|
22
|
+
const UNSAFE_KEYS: ReadonlySet<string> = new Set([
|
|
23
|
+
"__proto__",
|
|
24
|
+
"constructor",
|
|
25
|
+
"prototype",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
type Catalog = Record<string, unknown>;
|
|
29
|
+
|
|
30
|
+
function isCatalog(value: unknown): value is Catalog {
|
|
31
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** `base` with every key of `overlay` it lacks added — new objects, never a mutation. */
|
|
35
|
+
function mergeAddOnly(base: Catalog, overlay: Catalog): Catalog {
|
|
36
|
+
const out: Catalog = { ...base };
|
|
37
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
38
|
+
if (UNSAFE_KEYS.has(key)) continue;
|
|
39
|
+
const has = Object.prototype.hasOwnProperty.call(base, key);
|
|
40
|
+
if (typeof value === "string") {
|
|
41
|
+
if (!has) out[key] = value;
|
|
42
|
+
} else if (isCatalog(value)) {
|
|
43
|
+
if (!has) out[key] = mergeAddOnly({}, value);
|
|
44
|
+
else if (isCatalog(base[key])) out[key] = mergeAddOnly(base[key], value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function loadSiteOverlay(locale: string): Promise<Catalog> {
|
|
51
|
+
const mod =
|
|
52
|
+
(await import(`../messages/site/${locale}.json`).catch(() => null)) ??
|
|
53
|
+
(await import(`../messages/site/${DEFAULT_LOCALE}.json`).catch(() => null));
|
|
54
|
+
const overlay: unknown = mod ? (mod.default ?? mod) : null;
|
|
55
|
+
return isCatalog(overlay) ? overlay : {};
|
|
56
|
+
}
|
|
57
|
+
|
|
14
58
|
export default getRequestConfig(async ({ requestLocale }) => {
|
|
15
59
|
// requestLocale is the modern API (next-intl >= 3.22). It resolves the
|
|
16
60
|
// active locale from the request, validated by middleware.
|
|
@@ -32,5 +76,9 @@ export default getRequestConfig(async ({ requestLocale }) => {
|
|
|
32
76
|
messages = (fallback.default ?? fallback) as AbstractIntlMessages;
|
|
33
77
|
}
|
|
34
78
|
|
|
35
|
-
|
|
79
|
+
const overlay = await loadSiteOverlay(locale);
|
|
80
|
+
return {
|
|
81
|
+
locale,
|
|
82
|
+
messages: mergeAddOnly(messages, overlay) as AbstractIntlMessages,
|
|
83
|
+
};
|
|
36
84
|
});
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
* 401/403) is `false`, any other failure rejects. The popups are bound
|
|
27
27
|
* in the route (`account/new-popups.tsx`, tested beside it): this file
|
|
28
28
|
* imports nothing under `(authed)`.
|
|
29
|
+
* 8. #9401 AC-11 — the template's `site-contributions.ts` stub ships no
|
|
30
|
+
* Billing section either; a site fills it.
|
|
29
31
|
*/
|
|
30
32
|
|
|
31
33
|
const mockSignupMe = jest.fn();
|
|
@@ -68,6 +70,7 @@ import {
|
|
|
68
70
|
accountSettingsSections,
|
|
69
71
|
type AccountNewRead,
|
|
70
72
|
} from "../account-config";
|
|
73
|
+
import { siteBillingSections } from "../site-contributions";
|
|
71
74
|
|
|
72
75
|
const INIT = { headers: { cookie: "session=abc" } };
|
|
73
76
|
|
|
@@ -166,6 +169,10 @@ describe("account-config exports (AC-6)", () => {
|
|
|
166
169
|
expect(accountBillingSections).toEqual([]);
|
|
167
170
|
});
|
|
168
171
|
|
|
172
|
+
it("ships no site Billing section either: the stub is the site's to fill (#9401 AC-11, D7)", () => {
|
|
173
|
+
expect(siteBillingSections).toEqual([]);
|
|
174
|
+
});
|
|
175
|
+
|
|
169
176
|
it("ships the base kinds and the member kinds in their sections (#8231 D4–D6)", () => {
|
|
170
177
|
expect(accountInboxKinds.map((k) => [k.kind, k.section])).toEqual([
|
|
171
178
|
["result", "updates"],
|
|
@@ -451,3 +451,279 @@ describe("sendMagicLink — existing users only, deferred (#7435 § D3, AC-2)",
|
|
|
451
451
|
});
|
|
452
452
|
});
|
|
453
453
|
});
|
|
454
|
+
|
|
455
|
+
// ── #9667 — a magic link can create the account (D1, D4) ───────────────────
|
|
456
|
+
|
|
457
|
+
describe("sendMagicLink — the sign-up branch on an open site (#9667 D1, D4)", () => {
|
|
458
|
+
const AGREED = { agreements: ["account_terms"], locale: "en" };
|
|
459
|
+
|
|
460
|
+
it("disableSignUp follows AILK_AUTH_SIGNUP_OPEN", () => {
|
|
461
|
+
delete process.env.AILK_AUTH_SIGNUP_OPEN;
|
|
462
|
+
expect(makeMagicLinkOptions({ send: jest.fn() }).disableSignUp).toBe(true);
|
|
463
|
+
process.env.AILK_AUTH_SIGNUP_OPEN = "true";
|
|
464
|
+
expect(makeMagicLinkOptions({ send: jest.fn() }).disableSignUp).toBe(false);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("binds, then mails ONE sign-up email to an unknown address with userId \"sign-up\" (#9720 AC-1)", async () => {
|
|
468
|
+
process.env.AILK_AUTH_SIGNUP_OPEN = "true";
|
|
469
|
+
const send = jest.fn().mockResolvedValue({ ok: true });
|
|
470
|
+
const bind = jest.fn().mockResolvedValue(AGREED);
|
|
471
|
+
const { lines, log } = makeLog();
|
|
472
|
+
const { ctx, background } = makeCtx(null);
|
|
473
|
+
const { sendMagicLink } = makeMagicLinkOptions({ send, log, bind });
|
|
474
|
+
|
|
475
|
+
await sendMagicLink(
|
|
476
|
+
{ email: "new@example.com", url: LINK, token: "MLTOKEN", metadata: AGREED },
|
|
477
|
+
ctx,
|
|
478
|
+
);
|
|
479
|
+
await Promise.all(background);
|
|
480
|
+
|
|
481
|
+
expect(bind).toHaveBeenCalledWith(
|
|
482
|
+
ctx.context.internalAdapter,
|
|
483
|
+
"MLTOKEN",
|
|
484
|
+
AGREED,
|
|
485
|
+
MAGIC_LINK_TTL_SECONDS,
|
|
486
|
+
);
|
|
487
|
+
expect(send).toHaveBeenCalledTimes(1);
|
|
488
|
+
expect(JSON.parse(send.mock.calls[0]![0] as string)).toEqual({
|
|
489
|
+
kind: "magic-link-sign-up",
|
|
490
|
+
userId: "sign-up",
|
|
491
|
+
to: "new@example.com",
|
|
492
|
+
url: LINK,
|
|
493
|
+
agreements: ["account_terms"],
|
|
494
|
+
locale: "en",
|
|
495
|
+
});
|
|
496
|
+
const serialized = JSON.stringify(lines);
|
|
497
|
+
expect(serialized).not.toContain("new@example.com");
|
|
498
|
+
expect(serialized).not.toContain("MLTOKEN");
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it("binds, then mails the account, for a known address", async () => {
|
|
502
|
+
process.env.AILK_AUTH_SIGNUP_OPEN = "true";
|
|
503
|
+
const send = jest.fn().mockResolvedValue({ ok: true });
|
|
504
|
+
const bind = jest.fn().mockResolvedValue(AGREED);
|
|
505
|
+
const { log } = makeLog();
|
|
506
|
+
const { ctx, background } = makeCtx({ id: "user_1", email: "person@example.com" });
|
|
507
|
+
const { sendMagicLink } = makeMagicLinkOptions({ send, log, bind });
|
|
508
|
+
|
|
509
|
+
await sendMagicLink(
|
|
510
|
+
{ email: "Person@Example.com", url: LINK, token: "MLTOKEN", metadata: AGREED },
|
|
511
|
+
ctx,
|
|
512
|
+
);
|
|
513
|
+
await Promise.all(background);
|
|
514
|
+
|
|
515
|
+
expect(bind).toHaveBeenCalledTimes(1);
|
|
516
|
+
expect(JSON.parse(send.mock.calls[0]![0] as string)).toMatchObject({
|
|
517
|
+
userId: "user_1",
|
|
518
|
+
to: "person@example.com",
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("a failed binding sends nothing to an unknown address and logs bind_failed", async () => {
|
|
523
|
+
process.env.AILK_AUTH_SIGNUP_OPEN = "true";
|
|
524
|
+
const send = jest.fn();
|
|
525
|
+
const bind = jest.fn().mockRejectedValue(new Error("db down for new@example.com"));
|
|
526
|
+
const { lines, log } = makeLog();
|
|
527
|
+
const { ctx, runInBackgroundOrAwait } = makeCtx(null);
|
|
528
|
+
const { sendMagicLink } = makeMagicLinkOptions({ send, log, bind });
|
|
529
|
+
|
|
530
|
+
await expect(
|
|
531
|
+
sendMagicLink(
|
|
532
|
+
{ email: "new@example.com", url: LINK, token: "MLTOKEN", metadata: AGREED },
|
|
533
|
+
ctx,
|
|
534
|
+
),
|
|
535
|
+
).resolves.toBeUndefined();
|
|
536
|
+
|
|
537
|
+
expect(runInBackgroundOrAwait).not.toHaveBeenCalled();
|
|
538
|
+
expect(send).not.toHaveBeenCalled();
|
|
539
|
+
expect(lines[0]).toEqual({
|
|
540
|
+
level: "warn",
|
|
541
|
+
fields: { kind: "magic-link", reason: "send_failed", errorKind: "bind_failed" },
|
|
542
|
+
});
|
|
543
|
+
const serialized = JSON.stringify(lines);
|
|
544
|
+
expect(serialized).not.toContain("new@example.com");
|
|
545
|
+
expect(serialized).not.toContain("MLTOKEN");
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it("a sign-in request (no metadata) on an open site still sends nothing to an unknown address", async () => {
|
|
549
|
+
process.env.AILK_AUTH_SIGNUP_OPEN = "true";
|
|
550
|
+
const send = jest.fn();
|
|
551
|
+
const bind = jest.fn();
|
|
552
|
+
const { lines, log } = makeLog();
|
|
553
|
+
const { ctx, runInBackgroundOrAwait } = makeCtx(null);
|
|
554
|
+
const { sendMagicLink } = makeMagicLinkOptions({ send, log, bind });
|
|
555
|
+
|
|
556
|
+
await sendMagicLink({ email: "nobody@example.com", url: LINK, token: "MLTOKEN" }, ctx);
|
|
557
|
+
|
|
558
|
+
expect(bind).not.toHaveBeenCalled();
|
|
559
|
+
expect(runInBackgroundOrAwait).not.toHaveBeenCalled();
|
|
560
|
+
expect(send).not.toHaveBeenCalled();
|
|
561
|
+
expect(lines).toEqual([
|
|
562
|
+
{ level: "info", fields: { kind: "magic-link", reason: "no_account" } },
|
|
563
|
+
]);
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
// ── #9720 — the sign-up email names the agreements (D6) ────────────────────
|
|
568
|
+
|
|
569
|
+
describe("sendMagicLink — the sign-up kind (#9720 D6)", () => {
|
|
570
|
+
const AGREED = { agreements: ["account_terms"], locale: "en" };
|
|
571
|
+
|
|
572
|
+
it("takes the agreements and locale from the binding, not the request", async () => {
|
|
573
|
+
process.env.AILK_AUTH_SIGNUP_OPEN = "true";
|
|
574
|
+
const send = jest.fn().mockResolvedValue({ ok: true });
|
|
575
|
+
const bind = jest
|
|
576
|
+
.fn()
|
|
577
|
+
.mockResolvedValue({ agreements: ["account_terms"], locale: "fr" });
|
|
578
|
+
const { log } = makeLog();
|
|
579
|
+
const { ctx, background } = makeCtx(null);
|
|
580
|
+
const { sendMagicLink } = makeMagicLinkOptions({ send, log, bind });
|
|
581
|
+
|
|
582
|
+
await sendMagicLink(
|
|
583
|
+
{ email: "new@example.com", url: LINK, token: "MLTOKEN", metadata: AGREED },
|
|
584
|
+
ctx,
|
|
585
|
+
);
|
|
586
|
+
await Promise.all(background);
|
|
587
|
+
|
|
588
|
+
expect(JSON.parse(send.mock.calls[0]![0] as string)).toEqual({
|
|
589
|
+
kind: "magic-link-sign-up",
|
|
590
|
+
userId: "sign-up",
|
|
591
|
+
to: "new@example.com",
|
|
592
|
+
url: LINK,
|
|
593
|
+
agreements: ["account_terms"],
|
|
594
|
+
locale: "fr",
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it("a known address on a sign-up request gets exactly the sign-in body (AC-2)", async () => {
|
|
599
|
+
process.env.AILK_AUTH_SIGNUP_OPEN = "true";
|
|
600
|
+
const send = jest.fn().mockResolvedValue({ ok: true });
|
|
601
|
+
const bind = jest.fn().mockResolvedValue(AGREED);
|
|
602
|
+
const { log } = makeLog();
|
|
603
|
+
const { ctx, background } = makeCtx({ id: "user_1", email: "person@example.com" });
|
|
604
|
+
const { sendMagicLink } = makeMagicLinkOptions({ send, log, bind });
|
|
605
|
+
|
|
606
|
+
await sendMagicLink(
|
|
607
|
+
{ email: "Person@Example.com", url: LINK, token: "MLTOKEN", metadata: AGREED },
|
|
608
|
+
ctx,
|
|
609
|
+
);
|
|
610
|
+
await Promise.all(background);
|
|
611
|
+
|
|
612
|
+
expect(send).toHaveBeenCalledTimes(1);
|
|
613
|
+
expect(JSON.parse(send.mock.calls[0]![0] as string)).toEqual({
|
|
614
|
+
kind: "magic-link",
|
|
615
|
+
userId: "user_1",
|
|
616
|
+
to: "person@example.com",
|
|
617
|
+
url: LINK,
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it("on a closed site binds nothing: a known address gets the sign-in body, an unknown one nothing (AC-2)", async () => {
|
|
622
|
+
delete process.env.AILK_AUTH_SIGNUP_OPEN;
|
|
623
|
+
const bind = jest.fn().mockResolvedValue(AGREED);
|
|
624
|
+
|
|
625
|
+
const knownSend = jest.fn().mockResolvedValue({ ok: true });
|
|
626
|
+
const known = makeCtx({ id: "user_1", email: "person@example.com" });
|
|
627
|
+
await makeMagicLinkOptions({ send: knownSend, log: makeLog().log, bind }).sendMagicLink(
|
|
628
|
+
{ email: "person@example.com", url: LINK, token: "MLTOKEN", metadata: AGREED },
|
|
629
|
+
known.ctx,
|
|
630
|
+
);
|
|
631
|
+
await Promise.all(known.background);
|
|
632
|
+
|
|
633
|
+
const unknownSend = jest.fn();
|
|
634
|
+
const unknown = makeCtx(null);
|
|
635
|
+
await makeMagicLinkOptions({ send: unknownSend, log: makeLog().log, bind }).sendMagicLink(
|
|
636
|
+
{ email: "new@example.com", url: LINK, token: "MLTOKEN", metadata: AGREED },
|
|
637
|
+
unknown.ctx,
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
expect(bind).not.toHaveBeenCalled();
|
|
641
|
+
expect(JSON.parse(knownSend.mock.calls[0]![0] as string)).toEqual({
|
|
642
|
+
kind: "magic-link",
|
|
643
|
+
userId: "user_1",
|
|
644
|
+
to: "person@example.com",
|
|
645
|
+
url: LINK,
|
|
646
|
+
});
|
|
647
|
+
expect(unknownSend).not.toHaveBeenCalled();
|
|
648
|
+
expect(unknown.runInBackgroundOrAwait).not.toHaveBeenCalled();
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
it("a known and an unknown address answer the same, after the same work, while the send never settles (AC-2)", async () => {
|
|
652
|
+
process.env.AILK_AUTH_SIGNUP_OPEN = "true";
|
|
653
|
+
const never = () => new Promise(() => undefined);
|
|
654
|
+
|
|
655
|
+
for (const user of [{ id: "user_1", email: "person@example.com" }, null]) {
|
|
656
|
+
const send = jest.fn(never);
|
|
657
|
+
const bind = jest.fn().mockResolvedValue(AGREED);
|
|
658
|
+
const { ctx, findUserByEmail, runInBackgroundOrAwait } = makeCtx(user);
|
|
659
|
+
const { sendMagicLink } = makeMagicLinkOptions({ send, log: makeLog().log, bind });
|
|
660
|
+
|
|
661
|
+
await expect(
|
|
662
|
+
sendMagicLink(
|
|
663
|
+
{ email: "someone@example.com", url: LINK, token: "MLTOKEN", metadata: AGREED },
|
|
664
|
+
ctx,
|
|
665
|
+
),
|
|
666
|
+
).resolves.toBeUndefined();
|
|
667
|
+
|
|
668
|
+
expect(findUserByEmail).toHaveBeenCalledTimes(1);
|
|
669
|
+
expect(bind).toHaveBeenCalledTimes(1);
|
|
670
|
+
expect(runInBackgroundOrAwait).toHaveBeenCalledTimes(1);
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it("logs the sign-up send with no address, token or agreement (AC-3)", async () => {
|
|
675
|
+
process.env.AILK_AUTH_SIGNUP_OPEN = "true";
|
|
676
|
+
const send = jest.fn().mockResolvedValue({ ok: true });
|
|
677
|
+
const bind = jest.fn().mockResolvedValue(AGREED);
|
|
678
|
+
const { lines, log } = makeLog();
|
|
679
|
+
const { ctx, background } = makeCtx(null);
|
|
680
|
+
const { sendMagicLink } = makeMagicLinkOptions({ send, log, bind });
|
|
681
|
+
|
|
682
|
+
await sendMagicLink(
|
|
683
|
+
{ email: "new@example.com", url: LINK, token: "MLTOKEN", metadata: AGREED },
|
|
684
|
+
ctx,
|
|
685
|
+
);
|
|
686
|
+
await Promise.all(background);
|
|
687
|
+
|
|
688
|
+
expect(lines).toEqual([
|
|
689
|
+
{
|
|
690
|
+
level: "info",
|
|
691
|
+
fields: { userId: "sign-up", kind: "magic-link-sign-up", reason: "sent" },
|
|
692
|
+
},
|
|
693
|
+
]);
|
|
694
|
+
const serialized = JSON.stringify(lines);
|
|
695
|
+
expect(serialized).not.toContain("new@example.com");
|
|
696
|
+
expect(serialized).not.toContain("MLTOKEN");
|
|
697
|
+
expect(serialized).not.toContain("account_terms");
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
it("signs a sign-up body that carries the agreements and locale (AC-3)", () => {
|
|
701
|
+
const req = buildAuthEmailRequest(
|
|
702
|
+
{
|
|
703
|
+
kind: "magic-link-sign-up",
|
|
704
|
+
userId: "sign-up",
|
|
705
|
+
to: "new@example.com",
|
|
706
|
+
url: LINK,
|
|
707
|
+
agreements: ["account_terms"],
|
|
708
|
+
locale: "en",
|
|
709
|
+
},
|
|
710
|
+
"webhook-secret",
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
const { createHmac } = require("node:crypto") as typeof import("node:crypto");
|
|
714
|
+
const timestamp = req.headers["x-ailk-auth-email-timestamp"];
|
|
715
|
+
expect(req.headers["x-ailk-auth-email-signature"]).toBe(
|
|
716
|
+
`sha256=${createHmac("sha256", "webhook-secret")
|
|
717
|
+
.update(`${timestamp}.${req.rawBody}`, "utf8")
|
|
718
|
+
.digest("hex")}`,
|
|
719
|
+
);
|
|
720
|
+
expect(JSON.parse(req.rawBody)).toEqual({
|
|
721
|
+
kind: "magic-link-sign-up",
|
|
722
|
+
userId: "sign-up",
|
|
723
|
+
to: "new@example.com",
|
|
724
|
+
url: LINK,
|
|
725
|
+
agreements: ["account_terms"],
|
|
726
|
+
locale: "en",
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
});
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* ONE SERIALIZATION. `buildAuthEmailRequest` builds the body string ONCE,
|
|
20
20
|
* signs THAT string, and hands the same string to the transport — the
|
|
21
|
-
* invariant `
|
|
21
|
+
* invariant `packages/api/src/services/revalidation.ts` states for itself ("a
|
|
22
22
|
* key-order or whitespace drift between signer and sender is impossible by
|
|
23
23
|
* construction"). This is why the call cannot go through the client's
|
|
24
24
|
* `post()`, which stringifies its own payload argument.
|
|
@@ -37,12 +37,24 @@ import { createHmac } from "node:crypto";
|
|
|
37
37
|
|
|
38
38
|
import { authEmail } from "@working-theory/api-client";
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
import { isSignUpOpen } from "./auth-env";
|
|
41
|
+
import {
|
|
42
|
+
bindLinkAgreements,
|
|
43
|
+
signUpMetadata,
|
|
44
|
+
type LinkAgreements,
|
|
45
|
+
type LinkAgreementsStore,
|
|
46
|
+
} from "./sign-up-agreements";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The five fixed kinds the relay will send. A caller picks no copy: a sign-up
|
|
50
|
+
* link sends agreement ids and a locale, which the API names itself (#9720).
|
|
51
|
+
*/
|
|
41
52
|
export type AuthEmailKind =
|
|
42
53
|
| "password-reset"
|
|
43
54
|
| "email-verification"
|
|
44
55
|
| "password-changed"
|
|
45
|
-
| "magic-link"
|
|
56
|
+
| "magic-link"
|
|
57
|
+
| "magic-link-sign-up";
|
|
46
58
|
|
|
47
59
|
export interface AuthEmailPayload {
|
|
48
60
|
kind: AuthEmailKind;
|
|
@@ -50,6 +62,8 @@ export interface AuthEmailPayload {
|
|
|
50
62
|
to: string;
|
|
51
63
|
url?: string;
|
|
52
64
|
expiresInMinutes?: number;
|
|
65
|
+
agreements?: string[];
|
|
66
|
+
locale?: string;
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
export interface AuthEmailRequest {
|
|
@@ -116,6 +130,10 @@ export function buildAuthEmailRequest(
|
|
|
116
130
|
...(payload.expiresInMinutes !== undefined
|
|
117
131
|
? { expiresInMinutes: payload.expiresInMinutes }
|
|
118
132
|
: {}),
|
|
133
|
+
...(payload.agreements !== undefined
|
|
134
|
+
? { agreements: payload.agreements }
|
|
135
|
+
: {}),
|
|
136
|
+
...(payload.locale !== undefined ? { locale: payload.locale } : {}),
|
|
119
137
|
});
|
|
120
138
|
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
121
139
|
const signature = `sha256=${createHmac("sha256", secret)
|
|
@@ -365,7 +383,7 @@ export const MAGIC_LINK_TTL_SECONDS = 300;
|
|
|
365
383
|
*/
|
|
366
384
|
export interface MagicLinkEndpointContext {
|
|
367
385
|
context: {
|
|
368
|
-
internalAdapter: {
|
|
386
|
+
internalAdapter: LinkAgreementsStore & {
|
|
369
387
|
findUserByEmail(
|
|
370
388
|
email: string,
|
|
371
389
|
): Promise<{ user: { id: string; email: string } } | null>;
|
|
@@ -390,14 +408,32 @@ export interface MagicLinkEndpointContext {
|
|
|
390
408
|
* branches cost one indexed read; the send goes to `runInBackgroundOrAwait`,
|
|
391
409
|
* which `auth.ts`'s `backgroundTasks.handler` runs after the response.
|
|
392
410
|
*
|
|
411
|
+
* A SIGN-UP REQUEST (#9667 D4) — one whose `metadata` carries the agreements,
|
|
412
|
+
* which `lib/sign-up-agreements.ts`'s plugin has already checked — binds them
|
|
413
|
+
* to the token, then mails the address whether or not it has an account: a
|
|
414
|
+
* sign-in link to an existing account, a sign-up link to a new address (whose
|
|
415
|
+
* `userId` is the fixed sentinel `"sign-up"`; the relay only logs it). The
|
|
416
|
+
* sign-up link is its own kind, `magic-link-sign-up`, carrying the agreement
|
|
417
|
+
* ids and locale the binding holds, so the email says an account will be
|
|
418
|
+
* created and names what it records (#9720 D6). The kind is picked from two
|
|
419
|
+
* values already in hand, inside the same deferred send, so the answer and
|
|
420
|
+
* its timing do not depend on it. A failed binding mails only an existing
|
|
421
|
+
* account. A sign-in request is unchanged: an unknown address gets nothing.
|
|
422
|
+
*
|
|
393
423
|
* NOTHING HERE THROWS, so a known and an unknown address both reach the
|
|
394
424
|
* plugin's `{ status: true }`.
|
|
395
425
|
*/
|
|
396
|
-
function makeSendMagicLink(deps:
|
|
426
|
+
function makeSendMagicLink(deps: MagicLinkDeps) {
|
|
397
427
|
const { send } = deps;
|
|
398
428
|
const log = deps.log ?? defaultLog;
|
|
429
|
+
const bind = deps.bind ?? bindLinkAgreements;
|
|
399
430
|
return async function sendMagicLink(
|
|
400
|
-
data: {
|
|
431
|
+
data: {
|
|
432
|
+
email: string;
|
|
433
|
+
url: string;
|
|
434
|
+
token: string;
|
|
435
|
+
metadata?: Record<string, unknown>;
|
|
436
|
+
},
|
|
401
437
|
ctx?: MagicLinkEndpointContext,
|
|
402
438
|
): Promise<void> {
|
|
403
439
|
let found: { user: { id: string; email: string } } | null;
|
|
@@ -413,37 +449,83 @@ function makeSendMagicLink(deps: DispatchDeps) {
|
|
|
413
449
|
return;
|
|
414
450
|
}
|
|
415
451
|
|
|
416
|
-
|
|
452
|
+
// A sign-up request (#9667 D2, D4): `metadata` carries the agreements,
|
|
453
|
+
// which the sign-up-agreements plugin has already checked. The binding
|
|
454
|
+
// is written whether or not the address has an account, so the work
|
|
455
|
+
// before the response never depends on it.
|
|
456
|
+
let binding: LinkAgreements | null = null;
|
|
457
|
+
const signUp = isSignUpOpen() ? signUpMetadata(data) : null;
|
|
458
|
+
if (signUp) {
|
|
459
|
+
try {
|
|
460
|
+
binding = await bind(
|
|
461
|
+
ctx.context.internalAdapter,
|
|
462
|
+
data.token,
|
|
463
|
+
signUp,
|
|
464
|
+
MAGIC_LINK_TTL_SECONDS,
|
|
465
|
+
);
|
|
466
|
+
} catch {
|
|
467
|
+
log.warn({
|
|
468
|
+
kind: "magic-link",
|
|
469
|
+
reason: "send_failed",
|
|
470
|
+
errorKind: "bind_failed",
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// A new address has no user yet; the relay only logs `userId`, so a
|
|
476
|
+
// sign-up link carries the fixed sentinel and the binding's ids, never
|
|
477
|
+
// the request's (#9720 D6).
|
|
478
|
+
let payload: AuthEmailPayload;
|
|
479
|
+
if (found) {
|
|
480
|
+
payload = {
|
|
481
|
+
kind: "magic-link",
|
|
482
|
+
userId: found.user.id,
|
|
483
|
+
to: found.user.email,
|
|
484
|
+
url: data.url,
|
|
485
|
+
};
|
|
486
|
+
} else if (binding) {
|
|
487
|
+
payload = {
|
|
488
|
+
kind: "magic-link-sign-up",
|
|
489
|
+
userId: "sign-up",
|
|
490
|
+
to: data.email,
|
|
491
|
+
url: data.url,
|
|
492
|
+
agreements: binding.agreements,
|
|
493
|
+
locale: binding.locale,
|
|
494
|
+
};
|
|
495
|
+
} else {
|
|
417
496
|
// No address in the line — the PII boundary holds for unknown addresses
|
|
418
497
|
// too, and they are the ones an enumerator types.
|
|
419
498
|
log.info({ kind: "magic-link", reason: "no_account" });
|
|
420
499
|
return;
|
|
421
500
|
}
|
|
422
501
|
|
|
423
|
-
const { user } = found;
|
|
424
502
|
// Not awaited: `runInBackgroundOrAwait` never rejects, and `dispatch`
|
|
425
503
|
// swallows its own failures into a log line.
|
|
426
|
-
void ctx.context.runInBackgroundOrAwait(
|
|
427
|
-
dispatch(
|
|
428
|
-
{ kind: "magic-link", userId: user.id, to: user.email, url: data.url },
|
|
429
|
-
{ send, log },
|
|
430
|
-
),
|
|
431
|
-
);
|
|
504
|
+
void ctx.context.runInBackgroundOrAwait(dispatch(payload, { send, log }));
|
|
432
505
|
};
|
|
433
506
|
}
|
|
434
507
|
|
|
508
|
+
interface MagicLinkDeps extends DispatchDeps {
|
|
509
|
+
/** Binds a sign-up request's agreements to its token (#9667 D4). */
|
|
510
|
+
bind?: typeof bindLinkAgreements;
|
|
511
|
+
}
|
|
512
|
+
|
|
435
513
|
/**
|
|
436
514
|
* The options `auth.ts` hands Better Auth's `magicLink()` plugin (#7435 § D2).
|
|
437
515
|
*
|
|
438
|
-
* `disableSignUp
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
516
|
+
* `disableSignUp` follows `isSignUpOpen()`, read when `createAuth()` builds
|
|
517
|
+
* the plugin — the moment and the predicate `emailAndPassword.disableSignUp`
|
|
518
|
+
* uses, so the two methods never disagree (#9667 D1). Closed, it is defence
|
|
519
|
+
* in depth behind § D3: even a leaked token for an address with no account
|
|
520
|
+
* cannot create one. Open, the verify creates the account only for a token
|
|
521
|
+
* with an agreements binding (`lib/sign-up-agreements.ts`). `storeToken:
|
|
522
|
+
* "hashed"` keeps a database read from yielding a live sign-in token.
|
|
523
|
+
* `allowedAttempts` is left unset — the plugin consumes atomically regardless
|
|
524
|
+
* and warns on any value but 1.
|
|
443
525
|
*/
|
|
444
|
-
export function makeMagicLinkOptions(deps:
|
|
526
|
+
export function makeMagicLinkOptions(deps: MagicLinkDeps) {
|
|
445
527
|
return {
|
|
446
|
-
disableSignUp:
|
|
528
|
+
disableSignUp: !isSignUpOpen(),
|
|
447
529
|
expiresIn: MAGIC_LINK_TTL_SECONDS,
|
|
448
530
|
storeToken: "hashed" as const,
|
|
449
531
|
sendMagicLink: makeSendMagicLink(deps),
|