create-ailk 0.22.0 → 0.23.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/component-catalog.json +149 -5
- package/component-catalog.md +7 -6
- package/dist/add-module.js +11 -9
- package/dist/add-route.d.ts +55 -1
- package/dist/add-route.js +294 -12
- package/dist/cli.js +27 -10
- package/dist/derive-architecture.d.ts +7 -5
- package/dist/derive-architecture.js +9 -32
- package/dist/lib/design-checks.js +4 -2
- package/dist/lib/env-catalog.js +0 -4
- package/dist/lib/npm-auth.d.ts +9 -0
- package/dist/lib/npm-auth.js +48 -0
- package/dist/lib/scaffold-render-sweep.d.ts +3 -2
- package/dist/lib/scaffold-render-sweep.js +5 -8
- package/dist/module-architecture.js +98 -10
- package/dist/paid-sections.d.ts +21 -2
- package/dist/paid-sections.js +69 -7
- package/dist/seed-flows.d.ts +4 -1
- package/dist/seed-flows.js +50 -4
- package/dist/spoke-checkout.d.ts +38 -35
- package/dist/spoke-checkout.js +66 -229
- package/dist/starter-grid.d.ts +0 -3
- package/dist/starter-grid.js +24 -14
- package/dist/surfaces.js +33 -12
- package/dist/sync-routes.d.ts +38 -1
- package/dist/sync-routes.js +184 -11
- package/dist/template-record.d.ts +1 -1
- package/dist/template-record.js +3 -2
- package/dist/update.js +10 -0
- package/package.json +3 -3
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
- package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/templates/.env.example +9 -7
- package/templates/.github/workflows/ci.yml +13 -0
- package/templates/README.md +1 -0
- package/templates/_gitignore +4 -0
- package/templates/apps/api/.env.example +27 -0
- package/templates/apps/api/CLAUDE.md +77 -16
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
- package/templates/apps/api/src/config/index.ts +109 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
- package/templates/apps/api/src/lib/error-serializer.ts +74 -7
- package/templates/apps/api/src/lib/stripe.ts +74 -4
- package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
- package/templates/apps/api/src/openapi/spec.ts +10 -7
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +325 -0
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1300 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
- package/templates/apps/api/src/routes/checkout/session.ts +57 -25
- package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +5 -5
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
- package/templates/apps/api/src/routes/consent/account.ts +4 -2
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +2 -2
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
- package/templates/apps/api/src/routes/content/index.ts +16 -2
- package/templates/apps/api/src/routes/content/offerings.ts +184 -0
- package/templates/apps/api/src/routes/content/pricing.ts +176 -0
- package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
- package/templates/apps/api/src/routes/leads/index.ts +17 -14
- package/templates/apps/api/src/routes/webhooks/README.md +66 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +5 -5
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
- package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +91 -17
- package/templates/apps/api/src/server.ts +140 -51
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +51 -2
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +374 -44
- package/templates/apps/api/src/services/__tests__/person.test.ts +232 -1
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +444 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +13 -5
- package/templates/apps/api/src/services/entitlements.ts +157 -1
- package/templates/apps/api/src/services/lead-routing.ts +113 -58
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +295 -0
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +868 -0
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +396 -0
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +681 -0
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
- package/templates/apps/api/src/services/offerings/catalog.ts +308 -0
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +385 -0
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
- package/templates/apps/api/src/services/offerings/connection.ts +1003 -0
- package/templates/apps/api/src/services/offerings/index.ts +614 -0
- package/templates/apps/api/src/services/offerings/source.ts +140 -0
- package/templates/apps/api/src/services/offerings/store.ts +150 -0
- package/templates/apps/api/src/services/person.ts +124 -9
- package/templates/apps/api/src/services/plan-offerings.ts +96 -30
- package/templates/apps/api/src/services/public-site-resolver.ts +19 -14
- package/templates/apps/mcp/CLAUDE.md +22 -14
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
- package/templates/apps/mcp/__tests__/http-transport.test.ts +4 -4
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
- package/templates/apps/mcp/__tests__/server.test.ts +13 -11
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
- package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
- package/templates/apps/mcp/src/server.ts +5 -0
- package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
- package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
- package/templates/apps/mcp/src/tools/purchase.ts +30 -2
- package/templates/apps/mcp/src/workspace-server.ts +15 -0
- package/templates/apps/web/.env.example +10 -31
- package/templates/apps/web/CLAUDE.md +315 -42
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +4 -2
- package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +8 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +22 -144
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +9 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +22 -24
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +140 -66
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +57 -22
- package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +119 -11
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +28 -5
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +6 -2
- package/templates/apps/web/app/[locale]/contact/page.tsx +99 -92
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +237 -5
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +352 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
- package/templates/apps/web/app/[locale]/layout.tsx +55 -4
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
- package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
- package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
- package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
- package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +2 -1
- package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +57 -0
- package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
- package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +67 -8
- package/templates/apps/web/lib/auth-routes.ts +36 -2
- package/templates/apps/web/lib/checkout-config.ts +9 -20
- package/templates/apps/web/lib/deck-page.ts +1 -0
- package/templates/apps/web/lib/deliverable-grant.ts +18 -0
- package/templates/apps/web/lib/in-page-magnet.ts +416 -0
- package/templates/apps/web/lib/magnet-routes.ts +42 -0
- package/templates/apps/web/lib/modules.generated.ts +9 -0
- package/templates/apps/web/lib/newsletter.actions.ts +202 -0
- package/templates/apps/web/lib/offerings-pricing.ts +326 -0
- package/templates/apps/web/lib/posting-title.ts +17 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +4 -2
- package/templates/apps/web/messages/en.json +49 -15
- package/templates/apps/web/middleware.ts +12 -2
- package/templates/apps/web/security-headers.config.ts +17 -0
- package/templates/content/en/BlogList/blog.mdx +4 -71
- package/templates/content/en/BookCall/book-a-call.mdx +1 -41
- package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
- package/templates/content/en/BookCall/onsite-service.mdx +1 -17
- package/templates/content/en/BookCall/start-project.mdx +1 -17
- package/templates/content/en/ContactPage/contact.mdx +6 -63
- package/templates/content/en/HomePage/home.mdx +3 -21
- package/templates/database/CHANGELOG.md +31 -0
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
- package/templates/database/__tests__/offerings-migration.test.ts +197 -0
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
- package/templates/database/__tests__/offerings.db.test.ts +303 -0
- package/templates/database/__tests__/seed.test.ts +31 -1
- package/templates/database/auth/schema.prisma +4 -0
- package/templates/database/config/schema.prisma +91 -0
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +30 -2
- package/versions.json +35 -35
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
- package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
- package/templates/apps/web/app/api/checkout/[offering]/route.ts +0 -48
- package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +0 -120
- package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +0 -86
- package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +0 -92
- package/templates/apps/web/app/api/webhooks/stripe/route.ts +0 -31
- package/templates/apps/web/lib/__tests__/checkout.test.ts +0 -243
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
- package/templates/apps/web/lib/checkout.ts +0 -93
- package/templates/apps/web/lib/meeting-slugs.ts +0 -63
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file sign-up-rate-limit.test.ts
|
|
3
|
+
* @description #8805 — the sign-up agreements' evidence read is counted per
|
|
4
|
+
* site key, end to end: the real `build()`, the `signupevidence` scope's own
|
|
5
|
+
* limiter and the REAL `@fastify/rate-limit` global ceiling.
|
|
6
|
+
*
|
|
7
|
+
* Every sign-up reads `GET /v1/consent/sign-up` from the web's server, and
|
|
8
|
+
* every web instance behind one egress address used to share one per-IP
|
|
9
|
+
* bucket of 10 a minute — which an anonymous caller spoofing that address's
|
|
10
|
+
* `X-Forwarded-For` could fill. A request presenting a VALID site key (the
|
|
11
|
+
* `x-ailk-spoke-key` the web's api-client already sends) is now counted
|
|
12
|
+
* against that key's own bucket instead, in both limiters; an invalid or
|
|
13
|
+
* absent key keeps the per-IP 10.
|
|
14
|
+
*
|
|
15
|
+
* External dependency boundary mocked: `resolveCallerSite`, the registry
|
|
16
|
+
* lookup a key digests to (one key valid, every other refused), and
|
|
17
|
+
* `resolveSiteWorkspaceId`, the route's own workspace seam. `DATABASE_URL` is
|
|
18
|
+
* unset for the file's duration, so the limiter counts in the memory store.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const mockResolveCallerSite = jest.fn();
|
|
22
|
+
// eslint-disable-next-line no-restricted-syntax -- jest mock factory must be hoisted; the registry lookup is the database boundary
|
|
23
|
+
jest.mock("../../../services/public-site-resolver.js", () => ({
|
|
24
|
+
...jest.requireActual<object>("../../../services/public-site-resolver.js"),
|
|
25
|
+
resolveCallerSite: (...args: unknown[]) => mockResolveCallerSite(...args),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
const mockResolveSiteWorkspaceId = jest.fn();
|
|
29
|
+
// eslint-disable-next-line no-restricted-syntax -- jest mock factory must be hoisted
|
|
30
|
+
jest.mock("../../../lib/site.js", () => ({
|
|
31
|
+
...jest.requireActual<object>("../../../lib/site.js"),
|
|
32
|
+
resolveSiteWorkspaceId: () => mockResolveSiteWorkspaceId(),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
jest.mock("@fastify/cors", () => {
|
|
36
|
+
return jest.fn().mockResolvedValue(undefined);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
jest.mock("@working-theory/email", () => ({
|
|
40
|
+
sendLeadNotification: jest.fn().mockResolvedValue({ ok: true }),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
import type { FastifyRequest } from "fastify";
|
|
44
|
+
|
|
45
|
+
import { __resetBuckets } from "../../../middleware/rate-limit";
|
|
46
|
+
import { build } from "../../../server";
|
|
47
|
+
|
|
48
|
+
/** The deployment's own site workspace. */
|
|
49
|
+
const W = "wksp_01920000-0000-7000-8000-0000000000a1";
|
|
50
|
+
/** The spoke's own workspace — never the evidence route's answer. */
|
|
51
|
+
const SPOKE_WS = "wksp_01920000-0000-7000-8000-0000000000b1";
|
|
52
|
+
const SPOKE_ID = "spoke_01920000-0000-7000-8000-0000000000b1";
|
|
53
|
+
const VALID = "ailk_spk_valid-site-key";
|
|
54
|
+
const WEB_EGRESS = "198.51.100.20";
|
|
55
|
+
|
|
56
|
+
describe("GET /v1/consent/sign-up against the real limiters (#8805)", () => {
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- server is typed via Fastify generics; explicit any matches server.test.ts
|
|
58
|
+
let app: any;
|
|
59
|
+
const prior = {
|
|
60
|
+
databaseUrl: process.env.DATABASE_URL,
|
|
61
|
+
perKey: process.env.AILK_RATE_LIMIT_SIGNUPEVIDENCE_PER_KEY_PER_MINUTE,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
async function statuses(
|
|
65
|
+
count: number,
|
|
66
|
+
headers: Record<string, string>,
|
|
67
|
+
): Promise<number[]> {
|
|
68
|
+
const codes: number[] = [];
|
|
69
|
+
for (let i = 0; i < count; i++) {
|
|
70
|
+
const res = await app.inject({
|
|
71
|
+
method: "GET",
|
|
72
|
+
url: "/v1/consent/sign-up",
|
|
73
|
+
headers,
|
|
74
|
+
});
|
|
75
|
+
codes.push(res.statusCode);
|
|
76
|
+
}
|
|
77
|
+
return codes;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const keyed = (key: string = VALID) => ({
|
|
81
|
+
"x-forwarded-for": WEB_EGRESS,
|
|
82
|
+
"x-ailk-spoke-key": key,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
beforeEach(async () => {
|
|
86
|
+
__resetBuckets();
|
|
87
|
+
delete process.env.DATABASE_URL;
|
|
88
|
+
delete process.env.AILK_RATE_LIMIT_SIGNUPEVIDENCE_PER_KEY_PER_MINUTE;
|
|
89
|
+
mockResolveSiteWorkspaceId.mockResolvedValue(W);
|
|
90
|
+
mockResolveCallerSite.mockImplementation((req: FastifyRequest) =>
|
|
91
|
+
Promise.resolve(
|
|
92
|
+
req.headers["x-ailk-spoke-key"] === VALID
|
|
93
|
+
? { kind: "spoke", spokeId: SPOKE_ID, workspaceId: SPOKE_WS }
|
|
94
|
+
: { kind: "refused", reason: "key-unknown" },
|
|
95
|
+
),
|
|
96
|
+
);
|
|
97
|
+
app = await build();
|
|
98
|
+
await app.ready();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
afterEach(async () => {
|
|
102
|
+
if (app) await app.close();
|
|
103
|
+
app = undefined;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
afterAll(() => {
|
|
107
|
+
for (const [name, value] of [
|
|
108
|
+
["DATABASE_URL", prior.databaseUrl],
|
|
109
|
+
["AILK_RATE_LIMIT_SIGNUPEVIDENCE_PER_KEY_PER_MINUTE", prior.perKey],
|
|
110
|
+
] as const) {
|
|
111
|
+
if (value === undefined) delete process.env[name];
|
|
112
|
+
else process.env[name] = value;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("admits keyed requests from one IP past 10 a minute — and past the global 60 — up to the key ceiling of 120", async () => {
|
|
117
|
+
const codes = await statuses(121, keyed());
|
|
118
|
+
expect(codes.slice(0, 120).every((code) => code !== 429)).toBe(true);
|
|
119
|
+
expect(codes[120]).toBe(429);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("refuses the eleventh keyless request from one IP with 429", async () => {
|
|
123
|
+
const codes = await statuses(11, { "x-forwarded-for": WEB_EGRESS });
|
|
124
|
+
expect(codes.slice(0, 10).every((code) => code !== 429)).toBe(true);
|
|
125
|
+
expect(codes[10]).toBe(429);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("refuses the eleventh request with an invalid key from one IP with 429", async () => {
|
|
129
|
+
const codes = await statuses(11, keyed("ailk_spk_forged"));
|
|
130
|
+
expect(codes.slice(0, 10).every((code) => code !== 429)).toBe(true);
|
|
131
|
+
expect(codes[10]).toBe(429);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("a spoofer filling the web egress address's buckets leaves the keyed caller its budget", async () => {
|
|
135
|
+
const spoofed = await statuses(70, { "x-forwarded-for": WEB_EGRESS });
|
|
136
|
+
expect(spoofed[69]).toBe(429);
|
|
137
|
+
const codes = await statuses(20, keyed());
|
|
138
|
+
expect(codes.every((code) => code !== 429)).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("reads AILK_RATE_LIMIT_SIGNUPEVIDENCE_PER_KEY_PER_MINUTE over the default", async () => {
|
|
142
|
+
await app.close();
|
|
143
|
+
process.env.AILK_RATE_LIMIT_SIGNUPEVIDENCE_PER_KEY_PER_MINUTE = "15";
|
|
144
|
+
app = await build();
|
|
145
|
+
await app.ready();
|
|
146
|
+
const codes = await statuses(16, keyed());
|
|
147
|
+
expect(codes.slice(0, 15).every((code) => code !== 429)).toBe(true);
|
|
148
|
+
expect(codes[15]).toBe(429);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("answers a keyed request with the site workspace, never the key's", async () => {
|
|
152
|
+
const res = await app.inject({
|
|
153
|
+
method: "GET",
|
|
154
|
+
url: "/v1/consent/sign-up",
|
|
155
|
+
headers: keyed(),
|
|
156
|
+
});
|
|
157
|
+
expect(res.statusCode).toBe(200);
|
|
158
|
+
expect(res.json().data.workspaceId).toBe(W);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* (#8765, spec docs/specs/8765-sign-up-refuses-without-the-spec.md D2, D5).
|
|
4
4
|
*
|
|
5
5
|
* GET /v1/consent/sign-up?locale=
|
|
6
|
-
* Anonymous
|
|
6
|
+
* Anonymous. `{ workspaceId, agreements: [{ purposeId,
|
|
7
7
|
* policyVersion, locale, statement, statementHash }] }` — one entry per
|
|
8
8
|
* `signUp` agreement of the site registry, its statement resolved by
|
|
9
9
|
* `resolveConsentStatement` for `locale` (default `en`) and hashed by
|
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
* Callers. The evidence route: the web's own server, stamping the grant it
|
|
35
35
|
* writes inside Better Auth's sign-up transaction (`apps/web/lib/
|
|
36
36
|
* sign-up-agreements.ts`, D3, D4). Registered at the top level in its own
|
|
37
|
-
* rate-limited scope (`signupevidence`), outside every module gate
|
|
37
|
+
* rate-limited scope (`signupevidence`), outside every module gate; a call
|
|
38
|
+
* presenting a valid site key is counted per key there, any other per IP
|
|
39
|
+
* (#8805, `server.ts`) — the key never chooses the workspace. The
|
|
38
40
|
* account pair: `memberCaller` — the hub's own cookie session or a hub
|
|
39
41
|
* credential (the account layout's interstitial, D7); a personal `ApiToken`
|
|
40
42
|
* is `401 unauthorized`. There is no verified-email floor: the agreement is
|
|
@@ -222,7 +222,7 @@ describe("GET /api/content/:type/:slug", () => {
|
|
|
222
222
|
"# Content Strategist",
|
|
223
223
|
);
|
|
224
224
|
|
|
225
|
-
it("targets <baseUrl>/<locale>/careers/<slug
|
|
225
|
+
it("targets <baseUrl>/<locale>/careers/<slug>/apply", async () => {
|
|
226
226
|
writeSiteMdx(fixture.getContentRoot());
|
|
227
227
|
await writePosting();
|
|
228
228
|
const res = await app.inject({ method: "GET", url: "/v1/content/JobPosting/content-strategist" });
|
|
@@ -230,7 +230,7 @@ describe("GET /api/content/:type/:slug", () => {
|
|
|
230
230
|
const entity = JSON.parse(res.body).mainEntity as Record<string, unknown>;
|
|
231
231
|
expect(entity.potentialAction).toEqual({
|
|
232
232
|
"@type": "ApplyAction",
|
|
233
|
-
target: "https://example.com/en/careers/content-strategist
|
|
233
|
+
target: "https://example.com/en/careers/content-strategist/apply",
|
|
234
234
|
});
|
|
235
235
|
});
|
|
236
236
|
|