@tokenoftrust/storefront-runner 2.0.16 → 2.0.18
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/apps/storefront/env.d.ts +18 -0
- package/apps/storefront/migrations-apps/0012_test_destinations.sql +29 -0
- package/apps/storefront/src/components/blog/ReactionBar.astro +1 -1
- package/apps/storefront/src/components/islands/CheckoutComplianceGate.tsx +365 -49
- package/apps/storefront/src/components/islands/checkoutVerificationFlow.ts +251 -0
- package/apps/storefront/src/config/compliance/rulesets.ts +18 -7
- package/apps/storefront/src/config/devTenantSeed.ts +24 -1
- package/apps/storefront/src/config/evidenceTenantRoster.ts +25 -0
- package/apps/storefront/src/config/publicationSpaceConfig.ts +25 -0
- package/apps/storefront/src/config/resolver.ts +5 -0
- package/apps/storefront/src/config/tenantAnalyticsFiles.ts +43 -0
- package/apps/storefront/src/config/tenants.ts +11 -0
- package/apps/storefront/src/layouts/Layout.astro +5 -1
- package/apps/storefront/src/lib/activity/killSwitch.ts +1 -1
- package/apps/storefront/src/lib/admin/consentSettingsClient.ts +113 -0
- package/apps/storefront/src/lib/apps/consentConfigRead.ts +24 -0
- package/apps/storefront/src/lib/apps/gtmMigrationDraft.ts +103 -0
- package/apps/storefront/src/lib/apps/routeParams.ts +45 -0
- package/apps/storefront/src/lib/auth/loginGate.ts +25 -0
- package/apps/storefront/src/lib/checkoutCommerce.ts +1 -1
- package/apps/storefront/src/lib/compliance/enforcement.ts +25 -3
- package/apps/storefront/src/lib/d1/schema-apps.ts +44 -0
- package/apps/storefront/src/lib/env.ts +13 -111
- package/apps/storefront/src/lib/fixtures/regulated-commerce-truth/index.ts +540 -0
- package/apps/storefront/src/lib/fixtures/ws3-readonly-clone-preflight/index.ts +495 -0
- package/apps/storefront/src/lib/integration/tenantAnalyticsDeclarations.ts +198 -0
- package/apps/storefront/src/lib/monitoring/manifest.ts +1 -1
- package/apps/storefront/src/lib/rawMarketingHtml.ts +9 -1
- package/apps/storefront/src/lib/runtimeEnv.ts +124 -0
- package/apps/storefront/src/lib/verification/totVerificationClient.ts +485 -0
- package/apps/storefront/src/middleware/candidatePin.ts +35 -0
- package/apps/storefront/src/middleware/controlPlane.ts +270 -0
- package/apps/storefront/src/middleware/index.ts +100 -796
- package/apps/storefront/src/middleware/publicRender.ts +43 -0
- package/apps/storefront/src/middleware/renderChain.ts +685 -0
- package/apps/storefront/src/middleware/routeBranch.ts +160 -0
- package/apps/storefront/src/middleware/staticBundle.ts +109 -0
- package/apps/storefront/src/pages/[...slug].astro +1 -0
- package/apps/storefront/src/pages/api/compliance/preflight.ts +179 -2
- package/apps/storefront/src/pages/index.astro +1 -0
- package/apps/storefront/vitest.config.ts +5 -0
- package/package.json +1 -1
- package/packages/public-runtime/package.json +1 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/deliver-test-event-request.schema.json +122 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/get-test-destination-delivery-request.schema.json +79 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/register-test-destination-request.schema.json +93 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/test-destination-delivery.schema.json +106 -0
- package/packages/public-runtime/schemas/managed-analytics/v1/test-destination.schema.json +77 -0
- package/packages/public-runtime/schemas/publication-space/v1/manifest.schema.json +195 -0
- package/packages/public-runtime/schemas/publication-space/v1/normalized-mount.schema.json +80 -0
- package/packages/public-runtime/schemas/publication-space/v1/ref.schema.json +20 -0
- package/packages/public-runtime/schemas/publication-version/v1/pointer.schema.json +28 -0
- package/packages/public-runtime/schemas/publication-version/v1/transition-request.schema.json +44 -0
- package/packages/public-runtime/schemas/publication-version/v1/transition.schema.json +47 -0
- package/packages/public-runtime/schemas/publication-version/v1/version.schema.json +58 -0
- package/packages/public-runtime/src/activity/catalog.ts +1 -1
- package/packages/public-runtime/src/candidate-index.ts +6 -0
- package/packages/public-runtime/src/customization-reconcile.ts +8 -0
- package/packages/public-runtime/src/html-cache-policy.ts +57 -0
- package/packages/public-runtime/src/index.ts +7 -0
- package/packages/public-runtime/src/integration/consent-broker.ts +115 -0
- package/packages/public-runtime/src/integration/interim-bridge.ts +111 -27
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.schemas.ts +65 -0
- package/packages/public-runtime/src/integration/managed-analytics-api-v1.ts +81 -0
- package/packages/public-runtime/src/integration/render-phases.ts +8 -3
- package/packages/public-runtime/src/publication-artifact.ts +203 -0
- package/packages/public-runtime/src/publication-space.ts +853 -0
- package/packages/public-runtime/src/publication-version.ts +587 -0
- package/packages/public-runtime/src/route-tier.ts +257 -0
- package/packages/public-runtime/src/static-bundle.ts +18 -5
- package/packages/public-runtime/src/tenant-assets.ts +85 -17
- package/packages/public-runtime/src/tenant.ts +70 -0
- package/packages/public-runtime/tests/conformance/fake-destination.ts +402 -0
- package/packages/public-runtime/tests/fixtures/publication-spaces/internal.json +32 -0
- package/packages/public-runtime/tests/fixtures/publication-spaces/mounted-public.json +38 -0
- package/packages/public-runtime/tests/fixtures/publication-spaces/normalized-mounts.json +39 -0
- package/packages/public-runtime/tests/fixtures/publication-spaces/ref.json +5 -0
- package/packages/public-runtime/tests/fixtures/publication-spaces/root-public.json +31 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/articles-v1.json +20 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/articles-v2.json +20 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/internal-v1.json +20 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/live-pointer.json +11 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/live-transition-request.json +14 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/live-transition.json +17 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/root-v1.json +20 -0
- package/packages/public-runtime/tests/fixtures/publication-versions/root-v2.json +20 -0
- package/scripts/dev/ai-edit.mjs +16 -3
- package/scripts/dev/port-check.mjs +18 -3
- package/scripts/dev/publish.mjs +26 -3
- package/scripts/tenant/validate.mjs +14 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which of the three middleware branches serves this request.
|
|
3
|
+
*
|
|
4
|
+
* The TIER answer is F2's — `resolveRouteTierV1` (packages/public-runtime) — and
|
|
5
|
+
* is never re-derived here. This module only maps that one answer onto the three
|
|
6
|
+
* serving shapes the Worker actually has, and applies the literal path tables
|
|
7
|
+
* that correct the resolver's deliberate fail-closed over-restriction of
|
|
8
|
+
* platform-reserved paths.
|
|
9
|
+
*
|
|
10
|
+
* Those tables are NOT a second classification. Every reserved path already has
|
|
11
|
+
* one answer (Tier 3, authorization required) and these do not change it: they
|
|
12
|
+
* only say HOW three fixed sets of reserved paths are served, because "run the
|
|
13
|
+
* control plane" is the wrong shape for all three.
|
|
14
|
+
*
|
|
15
|
+
* - {@link PLATFORM_STATIC_ASSET_PREFIXES} — built and public/ bytes. Identical
|
|
16
|
+
* for every visitor, no tenant state at all. Serving them through origin
|
|
17
|
+
* compute is exactly what ADR 0013 exists to prevent.
|
|
18
|
+
* - {@link PLATFORM_PUBLIC_DISCOVERY_PATHS} — robots/sitemap/llms. Anonymous-
|
|
19
|
+
* identical, but rendered from the tenant's own catalog + content, so they
|
|
20
|
+
* are not raw bytes; they are ordinary Tier 0 public output that happens to
|
|
21
|
+
* live on a reserved mount.
|
|
22
|
+
* - {@link PLATFORM_PUBLIC_APP_ROUTE_PREFIXES} — the app's own catalog browse
|
|
23
|
+
* pages. Reserved so a tenant mount cannot claim them, which is a different
|
|
24
|
+
* question from who may read them: they are the storefront's most public
|
|
25
|
+
* surface, and `isCacheablePath` already whitelists the same set.
|
|
26
|
+
*
|
|
27
|
+
* Contract: docs/architecture/decisions/0013-public-serving-contract.md
|
|
28
|
+
*/
|
|
29
|
+
import { resolveRouteTierV1, type RouteTierIndexV1, type ResolvedRouteTierV1 } from "@tot/public-runtime";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The three serving shapes.
|
|
33
|
+
*
|
|
34
|
+
* static-passthrough — platform bytes; `next()` and baseline headers, nothing else.
|
|
35
|
+
* public-render — anonymous tenant output; no D1 read at request time.
|
|
36
|
+
* control-plane — authorization-gated; admin, API, preview, the dev loop.
|
|
37
|
+
*/
|
|
38
|
+
export type MiddlewareBranchV1 = "static-passthrough" | "public-render" | "control-plane";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Built output and `public/` roots. Reached by the Worker only where the
|
|
42
|
+
* platform's static handler did not already answer them (plain `astro dev`, the
|
|
43
|
+
* local runner); serving them from the render chain would put origin compute in
|
|
44
|
+
* front of bytes that never vary.
|
|
45
|
+
*/
|
|
46
|
+
export const PLATFORM_STATIC_ASSET_PREFIXES = [
|
|
47
|
+
"/_astro",
|
|
48
|
+
"/apps",
|
|
49
|
+
"/assets",
|
|
50
|
+
"/brand",
|
|
51
|
+
"/favicon.svg",
|
|
52
|
+
"/js",
|
|
53
|
+
"/platform",
|
|
54
|
+
"/shared",
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Per-tenant discovery documents. Every one is generated from `locals.tot` +
|
|
59
|
+
* `locals.content` (see src/pages/robots.txt.ts, sitemap.xml.ts, llms.txt.ts),
|
|
60
|
+
* so they need the public render chain — they are not static bytes and cannot be
|
|
61
|
+
* passed through.
|
|
62
|
+
*/
|
|
63
|
+
export const PLATFORM_PUBLIC_DISCOVERY_PATHS = [
|
|
64
|
+
"/llms.txt",
|
|
65
|
+
"/robots.txt",
|
|
66
|
+
"/sitemap.xml",
|
|
67
|
+
] as const;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The storefront app's own catalog browse routes (src/pages/products,
|
|
71
|
+
* collections, search.astro, saved.astro). They are reserved mounts because a
|
|
72
|
+
* tenant publication space may not claim them — not because reading one needs
|
|
73
|
+
* authorization. `isCacheablePath` names the same set as the core public
|
|
74
|
+
* cacheable surface, and routing them through the control plane would put origin
|
|
75
|
+
* compute plus its D1 reads in front of a tenant's highest-traffic public pages.
|
|
76
|
+
*
|
|
77
|
+
* Dynamic Astro pages, not bytes, so `public-render` — never passthrough.
|
|
78
|
+
*/
|
|
79
|
+
export const PLATFORM_PUBLIC_APP_ROUTE_PREFIXES = [
|
|
80
|
+
"/collections",
|
|
81
|
+
"/products",
|
|
82
|
+
"/saved",
|
|
83
|
+
"/search",
|
|
84
|
+
] as const;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Case-folded, matching `isPlatformReserved`'s own folding in the tier resolver:
|
|
88
|
+
* a path it recognizes as reserved (`/Robots.txt`, `/ASSETS/app.js`) must reach
|
|
89
|
+
* the table that says HOW to serve it, or it falls through to the control plane.
|
|
90
|
+
* Every entry below is lowercase ASCII.
|
|
91
|
+
*/
|
|
92
|
+
function matchesPathOrPrefix(path: string, entry: string): boolean {
|
|
93
|
+
const caseFolded = path.toLowerCase();
|
|
94
|
+
return caseFolded === entry || caseFolded.startsWith(`${entry}/`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function isPlatformStaticAssetPath(renderPath: string): boolean {
|
|
98
|
+
return PLATFORM_STATIC_ASSET_PREFIXES.some((entry) => matchesPathOrPrefix(renderPath, entry));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function isPlatformPublicDiscoveryPath(renderPath: string): boolean {
|
|
102
|
+
return PLATFORM_PUBLIC_DISCOVERY_PATHS.some((entry) => matchesPathOrPrefix(renderPath, entry));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function isPlatformPublicAppRoutePath(renderPath: string): boolean {
|
|
106
|
+
return PLATFORM_PUBLIC_APP_ROUTE_PREFIXES.some((entry) =>
|
|
107
|
+
matchesPathOrPrefix(renderPath, entry),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface MiddlewareBranchInputV1 {
|
|
112
|
+
index: RouteTierIndexV1;
|
|
113
|
+
/**
|
|
114
|
+
* The TENANT-RELATIVE path — `renderPath` from `resolveTenantRouting`, with any
|
|
115
|
+
* `/<domain>` selection prefix already stripped. Never `url.pathname`: on a
|
|
116
|
+
* path-routed deployment `/<tenant>/admin` arrives with the prefix attached, and
|
|
117
|
+
* feeding that to the resolver misses the reserved-mount check entirely, which
|
|
118
|
+
* resolves the control plane as public Tier 0.
|
|
119
|
+
*/
|
|
120
|
+
renderPath: string;
|
|
121
|
+
/**
|
|
122
|
+
* True when this HOST is withheld behind sign-in (`DEMO_GATE_MODE=login` and the
|
|
123
|
+
* host is in `DEMO_GATE_HOSTS`). A host property, resolved before and
|
|
124
|
+
* independently of any route: under ADR 0013 the Worker keeps "preview, draft
|
|
125
|
+
* overlay, admin, the dev loop, and gated member areas", so a login-gated host
|
|
126
|
+
* is not serving a public path at all and every route on it is control plane.
|
|
127
|
+
*/
|
|
128
|
+
hostLoginGated: boolean;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface ResolvedMiddlewareBranchV1 {
|
|
132
|
+
branch: MiddlewareBranchV1;
|
|
133
|
+
/** F2's answer for this request, surfaced on `locals.routeTier`. */
|
|
134
|
+
route: ResolvedRouteTierV1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Resolve the route once and pick its branch. Fail-closed throughout: F2 answers
|
|
139
|
+
* Tier 3 with authorization required for anything it cannot resolve, and that
|
|
140
|
+
* lands on the control plane.
|
|
141
|
+
*/
|
|
142
|
+
export function resolveMiddlewareBranchV1(
|
|
143
|
+
input: MiddlewareBranchInputV1,
|
|
144
|
+
): ResolvedMiddlewareBranchV1 {
|
|
145
|
+
const route = resolveRouteTierV1(input.index, input.renderPath);
|
|
146
|
+
if (input.hostLoginGated) return { branch: "control-plane", route };
|
|
147
|
+
if (isPlatformStaticAssetPath(input.renderPath)) {
|
|
148
|
+
return { branch: "static-passthrough", route };
|
|
149
|
+
}
|
|
150
|
+
if (
|
|
151
|
+
isPlatformPublicDiscoveryPath(input.renderPath) ||
|
|
152
|
+
isPlatformPublicAppRoutePath(input.renderPath)
|
|
153
|
+
) {
|
|
154
|
+
return { branch: "public-render", route };
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
branch: route.requiresAuthorization ? "control-plane" : "public-render",
|
|
158
|
+
route,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Digest-addressed static-bundle serving (ADR 0012/0013) — CDN → R2, no render.
|
|
3
|
+
*
|
|
4
|
+
* This is the public path's real substrate, so BOTH branches that can be asked
|
|
5
|
+
* for public bytes reach it: the render chain (route documents) and the static
|
|
6
|
+
* passthrough (`/_astro`, `/assets` and the other platform roots, whose per-version
|
|
7
|
+
* hashed names exist only inside a tenant's bundle, never in the Worker's own
|
|
8
|
+
* build output). It reads R2 and KV and never D1.
|
|
9
|
+
*/
|
|
10
|
+
import { readR2 } from "@/lib/runtimeEnv";
|
|
11
|
+
import { getCachedPage, putCachedPage, staticBundleCacheKey } from "@/lib/edgeCache";
|
|
12
|
+
import { resolveChannelDigest, serveStaticBundle } from "@tot/public-runtime";
|
|
13
|
+
import type { StaticBundleR2BucketLike, TenantConfig } from "@tot/public-runtime";
|
|
14
|
+
|
|
15
|
+
export interface StaticBundleAttemptInput {
|
|
16
|
+
request: Request;
|
|
17
|
+
url: URL;
|
|
18
|
+
tenant: TenantConfig;
|
|
19
|
+
basePath: string;
|
|
20
|
+
cache: Cache | undefined;
|
|
21
|
+
kv: KVNamespace | undefined;
|
|
22
|
+
isPreviewHost: boolean;
|
|
23
|
+
/** `?__preview=` or the `tot_preview` cookie. */
|
|
24
|
+
hasPreviewToken: boolean;
|
|
25
|
+
hasCandidatePin: boolean;
|
|
26
|
+
hasDraftCookie: boolean;
|
|
27
|
+
/** Managed installs inject document markup, so a bundled DOCUMENT would be stale. */
|
|
28
|
+
hasManagedInstalls: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Serve this request from the tenant's live bundle, or return null to let the
|
|
33
|
+
* caller own it.
|
|
34
|
+
*
|
|
35
|
+
* For a static-publish tenant's PRODUCTION reads only (never preview/test — the
|
|
36
|
+
* "Worker serves interactive preview, R2 serves prod" split). A missing digest or
|
|
37
|
+
* object fails closed inside `serveStaticBundle`, which is why this must fully own
|
|
38
|
+
* the response once eligible rather than degrading into a render.
|
|
39
|
+
*
|
|
40
|
+
* Host-resolved reads only (`basePath === ""`): bundle bytes are served VERBATIM
|
|
41
|
+
* with root-absolute links, so a path-prefixed view (/<domain>/… on the shared
|
|
42
|
+
* .store host) would escape its prefix on every href — those views keep SSR, which
|
|
43
|
+
* rewrites hrefs through basePath.
|
|
44
|
+
*/
|
|
45
|
+
export async function tryServeStaticBundle(
|
|
46
|
+
input: StaticBundleAttemptInput,
|
|
47
|
+
): Promise<Response | null> {
|
|
48
|
+
const { request, url, tenant, cache, kv } = input;
|
|
49
|
+
const eligible =
|
|
50
|
+
tenant.staticPublish != null &&
|
|
51
|
+
tenant.staticPublish.enabled !== false &&
|
|
52
|
+
input.basePath === "" &&
|
|
53
|
+
request.method.toUpperCase() === "GET" &&
|
|
54
|
+
!input.isPreviewHost &&
|
|
55
|
+
!input.hasPreviewToken &&
|
|
56
|
+
!input.hasCandidatePin &&
|
|
57
|
+
!input.hasDraftCookie &&
|
|
58
|
+
!input.hasManagedInstalls &&
|
|
59
|
+
!url.pathname.startsWith("/api/");
|
|
60
|
+
if (!eligible) return null;
|
|
61
|
+
|
|
62
|
+
const bundleBucket = await readR2("STATIC_BUNDLES");
|
|
63
|
+
// No STATIC_BUNDLES binding (plain `astro dev`, or unconfigured) — degrading to
|
|
64
|
+
// the caller rather than a hard failure keeps `astro dev`/tests usable without
|
|
65
|
+
// provisioning R2.
|
|
66
|
+
if (!bundleBucket) return null;
|
|
67
|
+
|
|
68
|
+
const kvGet = kv ? (key: string) => kv.get(key) : async () => null;
|
|
69
|
+
// The R2 bundle key is the `treeDigest`, NOT the `live` channel versionId (the
|
|
70
|
+
// Gitea sha). Read the reconciled digest companion (`channelDigests.live`) —
|
|
71
|
+
// `gatedPromote`/`rollbackCandidateAtomic` write it in the same CAS flip as the
|
|
72
|
+
// versionId. No digest = this tenant has never staged a bundle — the caller
|
|
73
|
+
// serves it (a static-publish tenant must be able to bootstrap, and its app
|
|
74
|
+
// surfaces must never be downed by an unstaged static plane).
|
|
75
|
+
const digest = await resolveChannelDigest(kvGet, tenant.tenant_id, "live");
|
|
76
|
+
if (!digest) {
|
|
77
|
+
console.error(
|
|
78
|
+
`[static-bundle] no live digest for ${tenant.tenant_id} — SSR until a bundle is staged`,
|
|
79
|
+
);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const bundleCacheKey = cache
|
|
84
|
+
? staticBundleCacheKey(tenant.tenant_id, digest, url.pathname, url.search)
|
|
85
|
+
: null;
|
|
86
|
+
if (bundleCacheKey && cache) {
|
|
87
|
+
const hit = await getCachedPage(cache, bundleCacheKey);
|
|
88
|
+
if (hit) return hit;
|
|
89
|
+
}
|
|
90
|
+
const bundleResponse = await serveStaticBundle(
|
|
91
|
+
{
|
|
92
|
+
bucket: bundleBucket as unknown as StaticBundleR2BucketLike,
|
|
93
|
+
digest,
|
|
94
|
+
onFailClosed: (detail) => {
|
|
95
|
+
console.error(
|
|
96
|
+
`[static-bundle] fail-closed for ${tenant.tenant_id} ${url.pathname}: ${detail}`,
|
|
97
|
+
);
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
url.pathname,
|
|
101
|
+
);
|
|
102
|
+
// `null` = the manifest proves this path is outside the bundle's static surface
|
|
103
|
+
// (an app surface like /admin) — the caller owns it.
|
|
104
|
+
if (!bundleResponse) return null;
|
|
105
|
+
if (bundleCacheKey && cache) {
|
|
106
|
+
await putCachedPage(cache, bundleCacheKey, bundleResponse);
|
|
107
|
+
}
|
|
108
|
+
return bundleResponse;
|
|
109
|
+
}
|
|
@@ -22,11 +22,18 @@ import {
|
|
|
22
22
|
type VerificationState,
|
|
23
23
|
type CartComplianceDecision,
|
|
24
24
|
} from "@tot/public-runtime";
|
|
25
|
+
import type { ComplianceRuleset } from "@tot/public-runtime";
|
|
25
26
|
import {
|
|
27
|
+
isLiveTotVerificationProvider,
|
|
26
28
|
isRegulatedTenant,
|
|
27
29
|
isSandboxVerificationProvider,
|
|
28
30
|
resolveTenantRuleset,
|
|
29
31
|
} from "@/lib/compliance/enforcement";
|
|
32
|
+
import {
|
|
33
|
+
createTotVerificationClient,
|
|
34
|
+
type TotVerificationClient,
|
|
35
|
+
type TotVerificationStatus,
|
|
36
|
+
} from "@/lib/verification/totVerificationClient";
|
|
30
37
|
|
|
31
38
|
export const prerender = false;
|
|
32
39
|
|
|
@@ -46,6 +53,31 @@ interface PreflightBody {
|
|
|
46
53
|
evidenceRef?: string;
|
|
47
54
|
} | null;
|
|
48
55
|
ageAffirmed?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* LIVE provider only: the `appTransactionId` correlating this checkout attempt
|
|
58
|
+
* to a verification already started with ToT. Present on the resume leg of the
|
|
59
|
+
* shopper redirect trip. It is a CORRELATION HANDLE, not a claim — the server
|
|
60
|
+
* re-reads the real status for it on every call.
|
|
61
|
+
*/
|
|
62
|
+
verificationRef?: string;
|
|
63
|
+
/** LIVE provider only: the shopper details a new verification is started from. */
|
|
64
|
+
shopper?: {
|
|
65
|
+
email?: string;
|
|
66
|
+
givenName?: string;
|
|
67
|
+
familyName?: string;
|
|
68
|
+
dateOfBirth?: string;
|
|
69
|
+
} | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* What the live provider leg resolved, echoed back so the (downstream) checkout
|
|
74
|
+
* verification flow can send the shopper on and resume with the same ref. Purely
|
|
75
|
+
* informational — the DECISION is `decision`, computed server-side.
|
|
76
|
+
*/
|
|
77
|
+
interface LiveVerificationMeta {
|
|
78
|
+
ref?: string;
|
|
79
|
+
status?: TotVerificationStatus | "conditions_met" | "interactive";
|
|
80
|
+
continuationUrl?: string;
|
|
49
81
|
}
|
|
50
82
|
|
|
51
83
|
function json(body: unknown, status = 200): Response {
|
|
@@ -107,6 +139,134 @@ function normalizeDestination(
|
|
|
107
139
|
};
|
|
108
140
|
}
|
|
109
141
|
|
|
142
|
+
// --- Live ToT verification provider -----------------------------------------
|
|
143
|
+
|
|
144
|
+
let clientOverride: TotVerificationClient | null | undefined;
|
|
145
|
+
|
|
146
|
+
/** Test seam: inject a client (or `null` to simulate an unconfigured env). */
|
|
147
|
+
export function __setTotVerificationClientForTests(
|
|
148
|
+
client: TotVerificationClient | null | undefined,
|
|
149
|
+
): void {
|
|
150
|
+
clientOverride = client;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function resolveClient(): Promise<TotVerificationClient | null> {
|
|
154
|
+
if (clientOverride !== undefined) return clientOverride;
|
|
155
|
+
return await createTotVerificationClient();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Map a live provider status onto the pure engine's verification state. */
|
|
159
|
+
function stateForStatus(
|
|
160
|
+
status: TotVerificationStatus,
|
|
161
|
+
ref: string,
|
|
162
|
+
): VerificationState {
|
|
163
|
+
switch (status) {
|
|
164
|
+
case "cleared":
|
|
165
|
+
// The provider cleared its age/identity workflow for this app. It does not
|
|
166
|
+
// report a numeric age, so `verifiedAge` stays undefined — the engine then
|
|
167
|
+
// treats "verified" as satisfying the tenant's minAge, which is exactly the
|
|
168
|
+
// provider's own contract (the app's workflow encodes the age threshold).
|
|
169
|
+
return { outcome: "verified", evidenceRef: ref };
|
|
170
|
+
case "rejected":
|
|
171
|
+
return { outcome: "failed", evidenceRef: ref };
|
|
172
|
+
case "pending":
|
|
173
|
+
return { outcome: "pending", evidenceRef: ref };
|
|
174
|
+
case "not_started":
|
|
175
|
+
return { outcome: "unknown" };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
interface LiveVerificationResolution {
|
|
180
|
+
state: VerificationState;
|
|
181
|
+
meta: LiveVerificationMeta;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Resolve the shopper's verification from the LIVE provider, server-side.
|
|
186
|
+
*
|
|
187
|
+
* Returns `null` on ANY adapter failure (unconfigured env, transport error,
|
|
188
|
+
* non-2xx, malformed payload) — the caller turns that into the same fail-closed
|
|
189
|
+
* BLOCK as "no provider configured". Never throws.
|
|
190
|
+
*/
|
|
191
|
+
async function resolveLiveVerification(
|
|
192
|
+
body: PreflightBody,
|
|
193
|
+
): Promise<LiveVerificationResolution | null> {
|
|
194
|
+
let client: TotVerificationClient | null;
|
|
195
|
+
try {
|
|
196
|
+
client = await resolveClient();
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
if (!client) return null;
|
|
201
|
+
|
|
202
|
+
const ref =
|
|
203
|
+
typeof body.verificationRef === "string" && body.verificationRef.trim()
|
|
204
|
+
? body.verificationRef.trim()
|
|
205
|
+
: null;
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
if (ref) {
|
|
209
|
+
const read = await client.readVerification({ appTransactionId: ref });
|
|
210
|
+
if (!read.ok) return null;
|
|
211
|
+
if (read.status !== "not_started") {
|
|
212
|
+
return { state: stateForStatus(read.status, ref), meta: { ref, status: read.status } };
|
|
213
|
+
}
|
|
214
|
+
// "not_started" for a known ref falls through to a create with the same ref.
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const email =
|
|
218
|
+
typeof body.shopper?.email === "string" ? body.shopper.email.trim() : "";
|
|
219
|
+
if (!email) {
|
|
220
|
+
// Nothing started and nothing to start from: the shopper still has to
|
|
221
|
+
// verify. NOT an adapter failure — "unknown" is the engine's "do the step".
|
|
222
|
+
return {
|
|
223
|
+
state: { outcome: "unknown" },
|
|
224
|
+
meta: ref ? { ref, status: "not_started" } : {},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const appTransactionId = ref ?? crypto.randomUUID();
|
|
229
|
+
const created = await client.createVerification({
|
|
230
|
+
appTransactionId,
|
|
231
|
+
person: {
|
|
232
|
+
primaryEmail: email,
|
|
233
|
+
...(body.shopper?.givenName ? { givenName: body.shopper.givenName } : {}),
|
|
234
|
+
...(body.shopper?.familyName ? { familyName: body.shopper.familyName } : {}),
|
|
235
|
+
...(body.shopper?.dateOfBirth ? { dateOfBirth: body.shopper.dateOfBirth } : {}),
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
if (!created.ok) return null;
|
|
239
|
+
const meta: LiveVerificationMeta = {
|
|
240
|
+
ref: appTransactionId,
|
|
241
|
+
status: created.nextStep === "conditions_met" ? "conditions_met" : "interactive",
|
|
242
|
+
};
|
|
243
|
+
if (created.continuationUrl) meta.continuationUrl = created.continuationUrl;
|
|
244
|
+
return {
|
|
245
|
+
state:
|
|
246
|
+
created.nextStep === "conditions_met"
|
|
247
|
+
? { outcome: "verified", evidenceRef: appTransactionId }
|
|
248
|
+
: { outcome: "pending", evidenceRef: appTransactionId },
|
|
249
|
+
meta,
|
|
250
|
+
};
|
|
251
|
+
} catch {
|
|
252
|
+
// Belt-and-braces: the client is typed never-throws, but an unexpected
|
|
253
|
+
// exception must still fail closed rather than escape this route.
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Strip the provider from a ruleset so the pure engine emits its existing
|
|
260
|
+
* "no verification provider configured" outcome — BLOCK + `failedClosed: true`.
|
|
261
|
+
* This is how an adapter failure fails closed WITHOUT touching the engine.
|
|
262
|
+
*/
|
|
263
|
+
function withProviderCleared(ruleset: ComplianceRuleset): ComplianceRuleset {
|
|
264
|
+
return {
|
|
265
|
+
...ruleset,
|
|
266
|
+
verification: { ...ruleset.verification, provider: undefined },
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
110
270
|
export async function POST(context: APIContext): Promise<Response> {
|
|
111
271
|
const { tenant, tot } = context.locals;
|
|
112
272
|
const regulated = isRegulatedTenant(tenant);
|
|
@@ -121,9 +281,26 @@ export async function POST(context: APIContext): Promise<Response> {
|
|
|
121
281
|
: json({ decision: evaluateEmpty() });
|
|
122
282
|
}
|
|
123
283
|
|
|
124
|
-
|
|
284
|
+
let ruleset = await resolveTenantRuleset(tenant);
|
|
125
285
|
let verification = normalizeVerification(body.verification);
|
|
126
286
|
const destination = normalizeDestination(body.destination);
|
|
287
|
+
let liveVerification: LiveVerificationMeta | undefined;
|
|
288
|
+
|
|
289
|
+
// LIVE provider: the decision is made here, from a real server-side API call.
|
|
290
|
+
// The client-supplied `verification` field is DISCARDED (it is a shopper input,
|
|
291
|
+
// not evidence) and the affirmation shortcut below is unreachable — "tot" is
|
|
292
|
+
// never in SANDBOX_VERIFICATION_PROVIDERS. Any adapter failure clears the
|
|
293
|
+
// provider so the engine blocks fail-closed, exactly as when none is wired.
|
|
294
|
+
if (ruleset && isLiveTotVerificationProvider(ruleset.verification.provider)) {
|
|
295
|
+
const resolved = await resolveLiveVerification(body);
|
|
296
|
+
if (!resolved) {
|
|
297
|
+
ruleset = withProviderCleared(ruleset);
|
|
298
|
+
verification = { outcome: "unknown" };
|
|
299
|
+
} else {
|
|
300
|
+
verification = resolved.state;
|
|
301
|
+
liveVerification = resolved.meta;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
127
304
|
|
|
128
305
|
// Test/sandbox verification profile (the mode the 8501 demo/preview runs in):
|
|
129
306
|
// when the resolved provider is an explicit sandbox value and no live provider
|
|
@@ -187,7 +364,7 @@ export async function POST(context: APIContext): Promise<Response> {
|
|
|
187
364
|
ageAffirmed: body.ageAffirmed === true,
|
|
188
365
|
});
|
|
189
366
|
|
|
190
|
-
return json({ decision });
|
|
367
|
+
return json(liveVerification ? { decision, verification: liveVerification } : { decision });
|
|
191
368
|
}
|
|
192
369
|
|
|
193
370
|
/** The trivial "nothing to enforce" decision for an unregulated empty request. */
|
|
@@ -13,6 +13,11 @@ export default defineConfig({
|
|
|
13
13
|
resolve: {
|
|
14
14
|
alias: {
|
|
15
15
|
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
|
16
|
+
// `astro:middleware` is a virtual id Astro's own Vite plugin maps onto this
|
|
17
|
+
// real module. Mapping it the same way lets src/middleware/index.ts — the
|
|
18
|
+
// branch-to-handler dispatch — be invoked in a unit test instead of only
|
|
19
|
+
// read as text.
|
|
20
|
+
"astro:middleware": "astro/virtual-modules/middleware.js",
|
|
16
21
|
},
|
|
17
22
|
},
|
|
18
23
|
test: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.18",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
package/packages/public-runtime/schemas/managed-analytics/v1/deliver-test-event-request.schema.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://tokenoftrust.com/schemas/managed-analytics/v1/deliver-test-event-request.schema.json",
|
|
4
|
+
"title": "DeliverTestEventRequest",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"apiVersion",
|
|
9
|
+
"idempotencyKey",
|
|
10
|
+
"expectedRevision",
|
|
11
|
+
"body"
|
|
12
|
+
],
|
|
13
|
+
"properties": {
|
|
14
|
+
"apiVersion": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"const": "v1"
|
|
17
|
+
},
|
|
18
|
+
"idempotencyKey": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"not": {
|
|
21
|
+
"anyOf": [
|
|
22
|
+
{
|
|
23
|
+
"type": "string",
|
|
24
|
+
"pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"type": "string",
|
|
28
|
+
"pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "string",
|
|
32
|
+
"pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"type": "string",
|
|
36
|
+
"pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"minLength": 8,
|
|
41
|
+
"maxLength": 128,
|
|
42
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
|
|
43
|
+
},
|
|
44
|
+
"expectedRevision": {
|
|
45
|
+
"type": "integer",
|
|
46
|
+
"minimum": 0
|
|
47
|
+
},
|
|
48
|
+
"body": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"additionalProperties": false,
|
|
51
|
+
"required": [
|
|
52
|
+
"destinationId",
|
|
53
|
+
"eventId",
|
|
54
|
+
"actionName"
|
|
55
|
+
],
|
|
56
|
+
"properties": {
|
|
57
|
+
"destinationId": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"not": {
|
|
60
|
+
"anyOf": [
|
|
61
|
+
{
|
|
62
|
+
"type": "string",
|
|
63
|
+
"pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"type": "string",
|
|
67
|
+
"pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"type": "string",
|
|
71
|
+
"pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"type": "string",
|
|
75
|
+
"pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
"minLength": 1,
|
|
80
|
+
"maxLength": 128,
|
|
81
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
|
|
82
|
+
},
|
|
83
|
+
"eventId": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"not": {
|
|
86
|
+
"anyOf": [
|
|
87
|
+
{
|
|
88
|
+
"type": "string",
|
|
89
|
+
"pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"type": "string",
|
|
93
|
+
"pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"type": "string",
|
|
97
|
+
"pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"type": "string",
|
|
101
|
+
"pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
"minLength": 1,
|
|
106
|
+
"maxLength": 128,
|
|
107
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
|
|
108
|
+
},
|
|
109
|
+
"actionName": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"enum": [
|
|
112
|
+
"user_clicked_contact_sales_mktg",
|
|
113
|
+
"user_clicked_create_account_mktg",
|
|
114
|
+
"user_clicked_login_mktg",
|
|
115
|
+
"user_clicked_contact_support_consumer_mktg",
|
|
116
|
+
"user_clicked_contact_support_partner_mktg"
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|