@tokenoftrust/storefront-runner 1.4.0 → 1.4.2-rc.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/apps/storefront/astro.config.mjs +17 -1
- package/apps/storefront/integrations/materialize-guard.mjs +56 -0
- package/apps/storefront/migrations-apps/0001_woozy_lyja.sql +22 -0
- package/apps/storefront/migrations-apps/0002_good_switch.sql +26 -0
- package/apps/storefront/migrations-apps/0003_mute_marauders.sql +12 -0
- package/apps/storefront/migrations-apps/meta/0001_snapshot.json +990 -0
- package/apps/storefront/migrations-apps/meta/0002_snapshot.json +1160 -0
- package/apps/storefront/migrations-apps/meta/0003_snapshot.json +1235 -0
- package/apps/storefront/migrations-apps/meta/_journal.json +21 -0
- package/apps/storefront/package.json +0 -1
- package/apps/storefront/public/js/dashboard-team.js +38 -2
- package/apps/storefront/src/components/Seo.astro +65 -1
- package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +878 -0
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +1432 -0
- package/apps/storefront/src/components/admin/AdminCustomersTab.astro +3 -7
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +1756 -1382
- package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +1 -3
- package/apps/storefront/src/components/blog/BlogPaywall.astro +87 -0
- package/apps/storefront/src/components/blog/PostAccessBadge.astro +43 -0
- package/apps/storefront/src/components/blog/PostGrid.astro +57 -0
- package/apps/storefront/src/components/blog/ReactionBar.astro +214 -0
- package/apps/storefront/src/layouts/Layout.astro +48 -3
- package/apps/storefront/src/lib/activity/alerts.ts +426 -0
- package/apps/storefront/src/lib/activity/changeActorAttribution.ts +85 -0
- package/apps/storefront/src/lib/activity/deployVersion.ts +120 -0
- package/apps/storefront/src/lib/activity/ingest.ts +188 -0
- package/apps/storefront/src/lib/activity/ingestAuth.ts +105 -0
- package/apps/storefront/src/lib/activity/killSwitch.ts +80 -0
- package/apps/storefront/src/lib/activity/query.ts +403 -0
- package/apps/storefront/src/lib/activity/recordActivity.ts +101 -0
- package/apps/storefront/src/lib/activity/store.ts +120 -0
- package/apps/storefront/src/lib/activity/uiActor.ts +150 -0
- package/apps/storefront/src/lib/activity/workerCommit.ts +70 -0
- package/apps/storefront/src/lib/admin/adminShell.ts +290 -0
- package/apps/storefront/src/lib/admin/ai/approvalMode.ts +2 -3
- package/apps/storefront/src/lib/admin/ai/audit.ts +3 -5
- package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +7 -7
- package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +3 -3
- package/apps/storefront/src/lib/admin/orderClient.ts +3 -5
- package/apps/storefront/src/lib/admin/services/orders.ts +17 -19
- package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +4 -6
- package/apps/storefront/src/lib/admin/viewmodels/settings.ts +1 -1
- package/apps/storefront/src/lib/apps/adminService.ts +4 -4
- package/apps/storefront/src/lib/apps/adminSession.ts +53 -44
- package/apps/storefront/src/lib/apps/apiAuth.ts +2 -2
- package/apps/storefront/src/lib/apps/apiRoute.ts +2 -2
- package/apps/storefront/src/lib/apps/catalogMapper.ts +1 -1
- package/apps/storefront/src/lib/apps/credentials.ts +2 -2
- package/apps/storefront/src/lib/apps/gatewayKeys.ts +4 -4
- package/apps/storefront/src/lib/apps/healthAggregate.ts +5 -5
- package/apps/storefront/src/lib/apps/orders/attributionService.ts +2 -2
- package/apps/storefront/src/lib/apps/orders/customerHash.ts +1 -1
- package/apps/storefront/src/lib/apps/orders/foxyOrderClient.ts +12 -12
- package/apps/storefront/src/lib/apps/orders/idempotency.ts +7 -7
- package/apps/storefront/src/lib/apps/orders/orderForwardReceiver.ts +1 -1
- package/apps/storefront/src/lib/apps/orders/orderMapper.ts +2 -2
- package/apps/storefront/src/lib/apps/orders/ordersStore.ts +6 -6
- package/apps/storefront/src/lib/apps/registryService.ts +6 -6
- package/apps/storefront/src/lib/apps/scopes.ts +2 -2
- package/apps/storefront/src/lib/apps/tokenIssuer.ts +4 -4
- package/apps/storefront/src/lib/apps/tokenVerifier.ts +6 -6
- package/apps/storefront/src/lib/apps/widgets/eligibility.ts +2 -2
- package/apps/storefront/src/lib/apps/widgets/frameProps.ts +3 -3
- package/apps/storefront/src/lib/apps/widgets/launchToken.ts +4 -4
- package/apps/storefront/src/lib/apps/widgets/placements.ts +2 -2
- package/apps/storefront/src/lib/apps/widgets/renderSlot.ts +1 -1
- package/apps/storefront/src/lib/auth/adminEntry.ts +9 -9
- package/apps/storefront/src/lib/auth/identityToken.ts +6 -14
- package/apps/storefront/src/lib/auth/loginGate.ts +75 -13
- package/apps/storefront/src/lib/auth/mcpClientAssertion.ts +6 -4
- package/apps/storefront/src/lib/auth/operatorSession.ts +113 -0
- package/apps/storefront/src/lib/auth/route.ts +44 -4
- package/apps/storefront/src/lib/auth/session.ts +7 -8
- package/apps/storefront/src/lib/auth/totAccessClient.ts +8 -18
- package/apps/storefront/src/lib/basePath.ts +9 -2
- package/apps/storefront/src/lib/blog/access.ts +29 -0
- package/apps/storefront/src/lib/blog/collection.ts +313 -0
- package/apps/storefront/src/lib/blog/markdown.ts +174 -0
- package/apps/storefront/src/lib/blog/provider.ts +5 -0
- package/apps/storefront/src/lib/blog/reactions.ts +87 -0
- package/apps/storefront/src/lib/blog/rss.ts +92 -0
- package/apps/storefront/src/lib/blog/teaser.ts +83 -0
- package/apps/storefront/src/lib/blog/types.ts +34 -0
- package/apps/storefront/src/lib/blog/viewCounts.ts +243 -0
- package/apps/storefront/src/lib/d1/catalog.ts +2 -2
- package/apps/storefront/src/lib/d1/schema-apps.ts +147 -14
- package/apps/storefront/src/lib/dev/activityIngestToken.ts +2 -4
- package/apps/storefront/src/lib/dev/activityStore.ts +3 -5
- package/apps/storefront/src/lib/dev/apiBase.ts +1 -4
- package/apps/storefront/src/lib/dev/cockpitStore.ts +1 -9
- package/apps/storefront/src/lib/dev/devDraft.ts +1 -1
- package/apps/storefront/src/lib/dev/previewStatus.ts +9 -21
- package/apps/storefront/src/lib/dev/rendezvousBroker.ts +9 -24
- package/apps/storefront/src/lib/edgeCache.ts +17 -0
- package/apps/storefront/src/lib/env.ts +51 -0
- package/apps/storefront/src/lib/http/fetchTransport.ts +63 -0
- package/apps/storefront/src/lib/i18n.ts +25 -0
- package/apps/storefront/src/lib/jsonld.ts +118 -0
- package/apps/storefront/src/lib/monitoring/manifest.ts +11 -11
- package/apps/storefront/src/lib/newsletter/confirmationEmail.ts +30 -0
- package/apps/storefront/src/lib/newsletter/consent.ts +23 -0
- package/apps/storefront/src/lib/newsletter/digest.ts +117 -0
- package/apps/storefront/src/lib/newsletter/resendClient.ts +79 -0
- package/apps/storefront/src/lib/newsletter/send.ts +265 -0
- package/apps/storefront/src/lib/newsletter/subscribers.ts +156 -0
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +3519 -0
- package/apps/storefront/src/lib/publish/apex-readiness.ts +41 -18
- package/apps/storefront/src/lib/publish/dispatchHealth.ts +5 -7
- package/apps/storefront/src/lib/publish/domainState.ts +22 -13
- package/apps/storefront/src/lib/publish/panelPolling.ts +78 -0
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +100 -42
- package/apps/storefront/src/lib/publish/stageBundleUpload.ts +215 -0
- package/apps/storefront/src/lib/rate/kvWindow.ts +53 -0
- package/apps/storefront/src/lib/search/index.ts +2 -2
- package/apps/storefront/src/lib/seo/alternates.ts +42 -0
- package/apps/storefront/src/lib/seo/meta.ts +65 -0
- package/apps/storefront/src/lib/social/golive.ts +212 -0
- package/apps/storefront/src/lib/social/notify.ts +32 -0
- package/apps/storefront/src/lib/social/tokenStore.ts +171 -0
- package/apps/storefront/src/lib/the-build/provider.ts +27 -0
- package/apps/storefront/src/lib/the-build/readingTime.ts +35 -0
- package/apps/storefront/src/lib/the-build/toc.ts +95 -0
- package/apps/storefront/src/lib/tot/ToTClient.ts +1 -1
- package/apps/storefront/src/lib/tot/d1Client.ts +2 -2
- package/apps/storefront/src/lib/tot/totClientInterface.ts +3 -4
- package/apps/storefront/src/lib/webhooks/deliveryStore.ts +16 -17
- package/apps/storefront/src/middleware/index.ts +130 -2
- package/apps/storefront/src/pages/admin/ops-timeline.astro +420 -0
- package/apps/storefront/src/pages/admin.astro +52 -1206
- package/apps/storefront/src/pages/api/activity.ts +137 -0
- package/apps/storefront/src/pages/api/admin/activity-alerts.ts +73 -0
- package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +27 -1
- package/apps/storefront/src/pages/api/apps/admin/install.ts +26 -1
- package/apps/storefront/src/pages/api/apps/admin/resume.ts +24 -1
- package/apps/storefront/src/pages/api/apps/admin/suspend.ts +24 -1
- package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +25 -1
- package/apps/storefront/src/pages/api/apps/admin/update.ts +24 -1
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +11 -0
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +11 -0
- package/apps/storefront/src/pages/api/apps/v1/attribution.ts +20 -0
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +12 -0
- package/apps/storefront/src/pages/api/blog/[collection]/[slug]/react.ts +104 -0
- package/apps/storefront/src/pages/api/cache-purge.ts +11 -0
- package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +30 -0
- package/apps/storefront/src/pages/api/newsletter/[collection]/subscribe.ts +146 -0
- package/apps/storefront/src/pages/api/newsletter/confirm.ts +75 -0
- package/apps/storefront/src/pages/api/newsletter/unsubscribe.ts +90 -0
- package/apps/storefront/src/pages/api/publish/stage-bundle.ts +70 -0
- package/apps/storefront/src/pages/api/rum/vitals.ts +21 -2
- package/apps/storefront/src/pages/auth/login.astro +5 -4
- package/apps/storefront/src/pages/blog/[slug].astro +96 -16
- package/apps/storefront/src/pages/blog/author/[author].astro +140 -0
- package/apps/storefront/src/pages/blog/category/[category].astro +67 -0
- package/apps/storefront/src/pages/blog/index.astro +2 -0
- package/apps/storefront/src/pages/blog/tag/[tag].astro +65 -0
- package/apps/storefront/src/pages/cockpit.astro +74 -12
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +15 -11
- package/apps/storefront/src/pages/search.astro +1 -1
- package/apps/storefront/src/pages/sitemap.xml.ts +20 -0
- package/apps/storefront/src/pages/the-build/[slug].astro +129 -0
- package/apps/storefront/src/pages/the-build/index.astro +146 -0
- package/apps/storefront/src/pages/the-build/rss.xml.ts +33 -0
- package/apps/storefront/src/styles/admin.css +1080 -0
- package/apps/storefront/tsconfig.json +5 -0
- package/package.json +1 -1
- package/packages/public-runtime/package.json +1 -0
- package/packages/public-runtime/src/activity/README.md +146 -0
- package/packages/public-runtime/src/activity/catalog.ts +553 -0
- package/packages/public-runtime/src/activity/event.ts +168 -0
- package/packages/public-runtime/src/activity/index.ts +16 -0
- package/packages/public-runtime/src/activity/redaction.ts +263 -0
- package/packages/public-runtime/src/candidate-index.ts +13 -0
- package/packages/public-runtime/src/catalog-d1.ts +1 -1
- package/packages/public-runtime/src/customization-preview.ts +25 -2
- package/packages/public-runtime/src/customization-runtime.ts +125 -3
- package/packages/public-runtime/src/customization-versioning.ts +10 -0
- package/packages/public-runtime/src/index.ts +7 -0
- package/packages/public-runtime/src/static-bundle.ts +357 -0
- package/packages/public-runtime/src/tenant.ts +36 -8
- package/scripts/build/copy-tenant-assets.mjs +14 -4
- package/scripts/dev/ai-edit.mjs +1 -1
- package/scripts/dev/checkout-watch.mjs +2 -2
- package/scripts/dev/file-browser.mjs +2 -4
- package/scripts/dev/file-writer.mjs +1 -1
- package/scripts/dev/git-status.mjs +2 -2
- package/scripts/dev/locate-handler.mjs +3 -3
- package/scripts/dev/port-check.mjs +3 -3
- package/scripts/dev/publish.mjs +5 -9
- package/scripts/dev/shot.mjs +1 -1
- package/scripts/dev/unified-diff.mjs +8 -3
- package/scripts/tot-dev.mjs +6 -9
- package/apps/storefront/src/lib/webhooks/signing.ts +0 -29
- package/apps/storefront/src/lib/webhooks/webhookSigningKey.ts +0 -146
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* REAL orders view model
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* unchanged. Read-only (scope-locks) — no write paths.
|
|
2
|
+
* REAL orders view model — the Orders tab's data source. Fetches the tenant's
|
|
3
|
+
* checkout orders via the order client and maps the frozen DTO
|
|
4
|
+
* (`@tot/public-runtime` checkout-orders) into the `OrdersViewModel` shape that
|
|
5
|
+
* `OrdersWorkspace.astro` renders. Read-only (scope-locks) — no write paths.
|
|
7
6
|
*
|
|
8
7
|
* The mapping is a pure function (`mapOrdersToViewModel`) for testability; the
|
|
9
8
|
* orchestration (`getOrdersViewModel`) lists orders, eagerly fetches each order's
|
|
10
9
|
* detail (OrdersWorkspace renders `detailsById[row.id]` for every row), and maps.
|
|
11
|
-
* On ANY upstream failure it returns a valid EMPTY view model so the tab renders
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* then this yields the empty model on qa.
|
|
10
|
+
* On ANY upstream failure it returns a valid EMPTY view model so the tab renders.
|
|
11
|
+
* The live "real qa data" path is gated on ToT-core exposing
|
|
12
|
+
* `GET /api/exciseTax/orders`; until that exists this yields the empty model on qa.
|
|
15
13
|
*
|
|
16
14
|
* Identity/age fields (riskLevel, readiness "identity_and_age", evidence) are
|
|
17
|
-
* derived provisionally from commerce facts here;
|
|
18
|
-
*
|
|
19
|
-
* `verificationDetail` slots
|
|
15
|
+
* derived provisionally from commerce facts here; the tot20 verification join
|
|
16
|
+
* fills them authoritatively via the DTO's null `verification` /
|
|
17
|
+
* `verificationDetail` slots.
|
|
20
18
|
*/
|
|
21
19
|
import type {
|
|
22
20
|
CheckoutOrderDetail,
|
|
@@ -45,7 +43,7 @@ function deriveNextStep(fulfillment: OrderShipState, blocker: string | null): st
|
|
|
45
43
|
return "Create label";
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
/** Provisional risk until
|
|
46
|
+
/** Provisional risk until the verification join fills `verification`. Prefer a real riskLevel when present. */
|
|
49
47
|
function deriveRisk(
|
|
50
48
|
verification: CheckoutOrderVerification | null,
|
|
51
49
|
blocker: string | null,
|
|
@@ -75,7 +73,7 @@ export function mapSummaryToRow(s: CheckoutOrderSummary): OrderRow {
|
|
|
75
73
|
};
|
|
76
74
|
}
|
|
77
75
|
|
|
78
|
-
/** Commerce-derived readiness checks (
|
|
76
|
+
/** Commerce-derived readiness checks (the verification join enriches the identity dimension). */
|
|
79
77
|
function deriveReadinessChecks(d: CheckoutOrderDetail): OrderReadinessCheck[] {
|
|
80
78
|
const ageHold = d.blocker === "Age verification hold";
|
|
81
79
|
return [
|
|
@@ -118,7 +116,7 @@ function deriveReadinessChecks(d: CheckoutOrderDetail): OrderReadinessCheck[] {
|
|
|
118
116
|
];
|
|
119
117
|
}
|
|
120
118
|
|
|
121
|
-
/** Base commerce evidence events (
|
|
119
|
+
/** Base commerce evidence events (the verification join replaces with the real verification timeline). */
|
|
122
120
|
function deriveEvidenceTimeline(d: CheckoutOrderDetail): OrderEvidenceEvent[] {
|
|
123
121
|
const events: OrderEvidenceEvent[] = [
|
|
124
122
|
{ title: "Order imported", detail: "Storefront order became available in the admin queue.", at: d.placedAt },
|
|
@@ -226,7 +224,7 @@ export interface GetOrdersViewModelOptions {
|
|
|
226
224
|
* the Orders tab always renders.
|
|
227
225
|
*/
|
|
228
226
|
/**
|
|
229
|
-
* Load outcome for the Orders tab
|
|
227
|
+
* Load outcome for the Orders tab — lets the UI pick the right state:
|
|
230
228
|
* `loaded` (rows), `empty` (fetch OK, 0 orders), `error` (upstream failed), and
|
|
231
229
|
* `not_configured` (produced by the caller when no bearer/client is available —
|
|
232
230
|
* the service itself never returns it). `page` carries the pagination cursor.
|
|
@@ -240,7 +238,7 @@ export interface OrdersLoadResult {
|
|
|
240
238
|
}
|
|
241
239
|
|
|
242
240
|
/**
|
|
243
|
-
* Parse server-backed list filters from a request's query string
|
|
241
|
+
* Parse server-backed list filters from a request's query string. `queue`
|
|
244
242
|
* of `"all"` (or absent) means no server filter. Non-numeric `limit` is ignored.
|
|
245
243
|
* `payment`/search are NOT here — they are client-side refinements (the DTO list
|
|
246
244
|
* API has no payment filter, per contract §9).
|
|
@@ -263,7 +261,7 @@ export function parseOrderFilters(params: URLSearchParams): CheckoutOrderListFil
|
|
|
263
261
|
}
|
|
264
262
|
|
|
265
263
|
/**
|
|
266
|
-
* Fetch + map the orders view model AND report the load outcome
|
|
264
|
+
* Fetch + map the orders view model AND report the load outcome. Lists,
|
|
267
265
|
* eagerly fetches per-order detail, maps. Distinguishes empty from error so the
|
|
268
266
|
* tab can degrade gracefully. Never throws.
|
|
269
267
|
*/
|
|
@@ -292,7 +290,7 @@ export async function getOrdersViewModelResult(
|
|
|
292
290
|
}
|
|
293
291
|
}
|
|
294
292
|
|
|
295
|
-
/**
|
|
293
|
+
/** Convenience wrapper: just the view model (see getOrdersViewModelResult). */
|
|
296
294
|
export async function getOrdersViewModel(
|
|
297
295
|
opts: GetOrdersViewModelOptions,
|
|
298
296
|
): Promise<OrdersViewModel> {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AI Workflows view model
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* now come from the ai-evidence-substrate (`lib/admin/ai`), which formalizes
|
|
7
|
-
* the approval/evidence models this view model originally stubbed.
|
|
2
|
+
* AI Workflows view model — the 8 named recurring jobs as a control-plane
|
|
3
|
+
* record. No execution: this defines only the read-oriented record shape; the
|
|
4
|
+
* tab + execution build on top of it. `ApprovalMode`/`AuditEvent`/`AiRecommendation`
|
|
5
|
+
* come from `lib/admin/ai`, which formalizes the approval/evidence models.
|
|
8
6
|
*/
|
|
9
7
|
|
|
10
8
|
import type { ApprovalMode } from "../ai/approvalMode";
|
|
@@ -54,7 +54,7 @@ export interface SettingsViewModel {
|
|
|
54
54
|
auditTrail: SettingsAuditEvent[];
|
|
55
55
|
/**
|
|
56
56
|
* The target tenant's current checkout styling — the editable descriptor the
|
|
57
|
-
* "Checkout styling" section
|
|
57
|
+
* "Checkout styling" section renders in its editor and saves back through
|
|
58
58
|
* the versioned publish spine (`POST /api/admin/checkout-style` →
|
|
59
59
|
* `publishToTest` on `tenants/<id>/checkout-style.json`). Always a complete
|
|
60
60
|
* `CheckoutStyle` (the resolved default merged with any published override), so
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Composition root + shared helpers for the `/api/apps/admin/*` routes
|
|
3
|
-
*
|
|
2
|
+
* Composition root + shared helpers for the `/api/apps/admin/*` routes —
|
|
3
|
+
* builds an `AppRegistryService` over the
|
|
4
4
|
* `STOREFRONT_APPS_DB` `Queryable` binding and a `D1CredentialStore`, the same
|
|
5
|
-
* construction `api/apps/oauth/token.ts`
|
|
5
|
+
* construction `api/apps/oauth/token.ts` uses, and mirrors
|
|
6
6
|
* `../grants/route.ts`'s `getService()` + no-store `json()` + 503-when-
|
|
7
7
|
* unconfigured pattern. Kept out of the pure service module so that stays
|
|
8
8
|
* transport-free and unit-testable against a fake `Queryable`.
|
|
@@ -72,7 +72,7 @@ export function parseInstallId(body: unknown): string | null {
|
|
|
72
72
|
* Resolve `installId` and verify it belongs to `tenantId` — every mutating
|
|
73
73
|
* admin route (update/suspend/resume/uninstall/rotate) MUST call this BEFORE
|
|
74
74
|
* invoking a service method, since those methods take a bare `installId` with
|
|
75
|
-
* no tenant check of their own
|
|
75
|
+
* no tenant check of their own. A foreign-tenant
|
|
76
76
|
* `installId` is reported IDENTICALLY to a truly-missing one — 404, same
|
|
77
77
|
* body shape as `RegistryError("not_found")` — so there is no cross-tenant
|
|
78
78
|
* existence oracle.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Operator-session resolution for the Private Apps admin API
|
|
3
|
-
* (`/api/apps/admin
|
|
3
|
+
* (`/api/apps/admin/*`). Mirrors
|
|
4
4
|
* `../grants/session.ts`'s owner-session resolution — same two paths, same
|
|
5
5
|
* fail-closed shape — but resolves `{ tenantId, actor }` instead of `{ owner }`:
|
|
6
6
|
*
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* Routes depend only on the `{ ok, tenantId, actor }` result. `authorizeAdmin`
|
|
39
39
|
* is the pure core of path 2 (unit-testable, no I/O).
|
|
40
40
|
*
|
|
41
|
-
*
|
|
41
|
+
* Console admin session. The two paths above are host-bound:
|
|
42
42
|
* they resolve exactly one tenant (`context.locals.tenant`). The
|
|
43
43
|
* `/api/apps/admin/*` surface is also reachable from the multi-tenant vendor
|
|
44
44
|
* console, where a signed-in viewer may hold memberships on several vendors —
|
|
@@ -61,6 +61,11 @@ import { sha256Hex } from "@tot/public-runtime";
|
|
|
61
61
|
import { readEnv } from "@/lib/env";
|
|
62
62
|
import { readViewerSession } from "@/lib/auth/route.js";
|
|
63
63
|
import { viewerCanAccess, membershipFor } from "@/lib/dashboard/tenantSelection.js";
|
|
64
|
+
import {
|
|
65
|
+
authorizeBearerOperator,
|
|
66
|
+
resolveHostBoundSession,
|
|
67
|
+
type SessionFailure,
|
|
68
|
+
} from "@/lib/auth/operatorSession.js";
|
|
64
69
|
|
|
65
70
|
/** Capabilities that make a signed-in viewer an app-registry operator for their tenant. */
|
|
66
71
|
const ADMIN_CAPABILITIES: ReadonlySet<string> = new Set(["owner", "admin"]);
|
|
@@ -88,33 +93,30 @@ export interface AdminAuthInput {
|
|
|
88
93
|
|
|
89
94
|
export type AdminAuthResult =
|
|
90
95
|
| { ok: true; tenantId: string; actor: string; role?: string }
|
|
91
|
-
|
|
|
96
|
+
| SessionFailure;
|
|
97
|
+
|
|
98
|
+
/** The one authorized-success shape both host-bound and Bearer admin paths return. */
|
|
99
|
+
type AdminAuthOk = Extract<AdminAuthResult, { ok: true }>;
|
|
92
100
|
|
|
93
101
|
/**
|
|
94
102
|
* Decide the authenticated tenant from env + request, with NO I/O —
|
|
95
103
|
* unit-testable. Fail-closed: unconfigured gate → 503, bad/absent Bearer →
|
|
96
|
-
* 401, no resolvable tenant → 400.
|
|
104
|
+
* 401, no resolvable tenant → 400. A thin adapter over the shared
|
|
105
|
+
* Bearer-operator core that maps its resolved `principal` to this surface's
|
|
106
|
+
* `tenantId` and stamps the fixed `BEARER_ACTOR` (no per-caller identity here).
|
|
97
107
|
*/
|
|
98
108
|
export function authorizeAdmin(input: AdminAuthInput): AdminAuthResult {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (!tenantId) {
|
|
111
|
-
return {
|
|
112
|
-
ok: false,
|
|
113
|
-
status: 400,
|
|
114
|
-
message: "tenant unresolved (set APPS_ADMIN_TENANT or X-Tot-Tenant)",
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
return { ok: true, tenantId, actor: BEARER_ACTOR };
|
|
109
|
+
const res = authorizeBearerOperator({
|
|
110
|
+
authHeader: input.authHeader,
|
|
111
|
+
gateSecret: input.gateSecret,
|
|
112
|
+
configuredPrincipal: input.configuredTenant,
|
|
113
|
+
requestedPrincipal: input.requestedTenant,
|
|
114
|
+
unconfiguredMessage: "apps admin operator secret not configured",
|
|
115
|
+
unresolvedMessage: "tenant unresolved (set APPS_ADMIN_TENANT or X-Tot-Tenant)",
|
|
116
|
+
});
|
|
117
|
+
return res.ok
|
|
118
|
+
? { ok: true, tenantId: res.principal, actor: BEARER_ACTOR }
|
|
119
|
+
: res;
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
/** One-way, stable pseudonym for an audit `actor` — never the email itself. */
|
|
@@ -128,30 +130,37 @@ async function actorFromEmail(email: string): Promise<string> {
|
|
|
128
130
|
* from the body.
|
|
129
131
|
*/
|
|
130
132
|
export async function resolveAdminSession(context: APIContext): Promise<AdminAuthResult> {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
return resolveHostBoundSession<AdminAuthOk>(context, {
|
|
134
|
+
capabilities: ADMIN_CAPABILITIES,
|
|
135
|
+
// A resolved-but-insufficient viewer fails closed with 403 and does NOT
|
|
136
|
+
// fall through to the Bearer gate — an authenticated-but-under-privileged
|
|
137
|
+
// caller must not be silently admitted by a secret it also presents.
|
|
138
|
+
onInsufficientCapability: {
|
|
139
|
+
mode: "failClosed",
|
|
140
|
+
failure: {
|
|
138
141
|
ok: false,
|
|
139
142
|
status: 403,
|
|
140
143
|
message: "not authorized to manage apps for this tenant",
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
// Path 1: the tenant IS the resolved host's appDomain — never the body.
|
|
147
|
+
resolveViewer: async (viewer, tenantId) => ({
|
|
148
|
+
ok: true,
|
|
149
|
+
tenantId,
|
|
150
|
+
actor: await actorFromEmail(viewer.email),
|
|
151
|
+
}),
|
|
152
|
+
// Path 2 (fallback): the headless-console Bearer operator secret. Dedicated
|
|
153
|
+
// secret only — no fallback to another deployment's secret.
|
|
154
|
+
resolveBearer: async (ctx) => {
|
|
155
|
+
const gateSecret = await readEnv("APPS_ADMIN_SECRET");
|
|
156
|
+
const configuredTenant = await readEnv("APPS_ADMIN_TENANT");
|
|
157
|
+
return authorizeAdmin({
|
|
158
|
+
authHeader: ctx.request.headers.get("authorization"),
|
|
159
|
+
gateSecret,
|
|
160
|
+
configuredTenant,
|
|
161
|
+
requestedTenant: ctx.request.headers.get("x-tot-tenant"),
|
|
162
|
+
});
|
|
163
|
+
},
|
|
155
164
|
});
|
|
156
165
|
}
|
|
157
166
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The scoped-API authorization boundary — every
|
|
3
3
|
* `/api/apps/v1/*` route calls `authorizeAppRequest` FIRST. Verifies the
|
|
4
|
-
* app-gateway bearer JWT (
|
|
4
|
+
* app-gateway bearer JWT (`AppTokenVerifier`), resolves the calling
|
|
5
5
|
* install from the registry, and enforces the single scope the route
|
|
6
6
|
* requires per `docs/private-apps/contract/openapi.yaml`'s `x-requiredScope`.
|
|
7
7
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Response helpers shared by every `/api/apps/v1/*` route
|
|
3
|
-
*
|
|
2
|
+
* Response helpers shared by every `/api/apps/v1/*` route — a no-store JSON
|
|
3
|
+
* responder (mirrors `../grants/route.ts` /
|
|
4
4
|
* `./adminService.ts`'s pattern) plus the `tot-request-id` / `traceparent`
|
|
5
5
|
* response headers `docs/private-apps/contract/openapi.yaml` documents on
|
|
6
6
|
* every operation.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Maps the internal `CatalogProduct` read model to the
|
|
2
|
+
* Maps the internal `CatalogProduct` read model to the scoped-API contract's
|
|
3
3
|
* `Product` shape (`docs/private-apps/contract/openapi.yaml`, schema
|
|
4
4
|
* `Product` — `additionalProperties: false`). Drops every internal field
|
|
5
5
|
* the contract doesn't declare (variants, images, metafields, source, …) so
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* D1-backed `CredentialPort` implementation
|
|
3
|
-
*
|
|
2
|
+
* D1-backed `CredentialPort` implementation — issues/rotates/clears the
|
|
3
|
+
* OAuth2 client-credentials for an app install, and
|
|
4
4
|
* verifies a presented `(clientId, secret)` pair for the token endpoint
|
|
5
5
|
* (`tokenIssuer.ts`).
|
|
6
6
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RS256 signing-key loader for the Storefront App Gateway
|
|
3
|
-
*
|
|
4
|
-
* JWTs a private app presents to the
|
|
2
|
+
* RS256 signing-key loader for the Storefront App Gateway — the key that mints
|
|
3
|
+
* and verifies the OAuth2 client-credentials
|
|
4
|
+
* JWTs a private app presents to the scoped APIs.
|
|
5
5
|
*
|
|
6
6
|
* Mirrors `../auth/brokerAssertion.ts`'s `loadSigningKey` caching + `readEnv`
|
|
7
7
|
* usage EXACTLY, but over a SEPARATE key (`JWT_APP_GATEWAY_PRIVATE_KEY_B64`):
|
|
@@ -34,7 +34,7 @@ export const DEFAULT_APP_GATEWAY_ISSUER = "storefront-app-gateway";
|
|
|
34
34
|
export const APP_TOKEN_TTL_SECONDS = 300;
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* Widget launch-token lifetime
|
|
37
|
+
* Widget launch-token lifetime — shorter than
|
|
38
38
|
* {@link APP_TOKEN_TTL_SECONDS}: this token only needs to survive the SSR
|
|
39
39
|
* render → iframe-load round trip, never a standing API session.
|
|
40
40
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Admin Health view aggregation
|
|
2
|
+
* Admin Health view aggregation. Folds
|
|
3
3
|
* one `AppInstall` (`registryService.ts`) and its `InstallHealth` rollup
|
|
4
|
-
* (`../webhooks/deliveryStore.ts`'s `getInstallHealth`,
|
|
4
|
+
* (`../webhooks/deliveryStore.ts`'s `getInstallHealth`, including
|
|
5
5
|
* `p95DeliveryMs`) into the single `AppInstallHealthView` shape the admin
|
|
6
6
|
* console renders. Deliberately transport-free — no D1/fetch here, so it's
|
|
7
7
|
* unit-testable with fakes; the caller (the admin health route) owns
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* a raw URL here, only as `hasWebhookEndpoint` presence — the console shows
|
|
12
12
|
* "configured or not", not the app's receiver address.
|
|
13
13
|
*
|
|
14
|
-
* `runtimeMode` is hardcoded to `"external"
|
|
15
|
-
* runtime-mode column
|
|
16
|
-
* an in-platform runtime mode is
|
|
14
|
+
* `runtimeMode` is hardcoded to `"external"`: there is no per-install
|
|
15
|
+
* runtime-mode column (every install IS an externally-hosted app;
|
|
16
|
+
* an in-platform runtime mode is out of scope).
|
|
17
17
|
*/
|
|
18
18
|
import { validateWebhookEndpoint } from "../webhooks/endpointGuard.js";
|
|
19
19
|
import type { InstallHealth } from "../webhooks/deliveryStore.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* App-owned attribution/commission write model
|
|
3
|
-
*
|
|
2
|
+
* App-owned attribution/commission write model. Owns the
|
|
3
|
+
* `app_attributions` rows `schema.ts` declares: the
|
|
4
4
|
* ledger `POST /attribution` (`docs/private-apps/contract/openapi.yaml`'s
|
|
5
5
|
* `writeAttribution`) writes into. Writes only into the app's own
|
|
6
6
|
* attribution namespace — this service never mutates `orders`, it only
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Opaque customer hash —
|
|
2
|
+
* Opaque customer hash — the `customerHash`
|
|
3
3
|
* every order/attribution surface exposes instead of a raw customer id (see
|
|
4
4
|
* `docs/private-apps/api-event-reference.md`'s PII-minimization matrix and
|
|
5
5
|
* `contract/openapi.yaml`'s `Order.customerHash`).
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* FoxyOrderClient — the boundary to tot-foxycart's order-read surface
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* FoxyOrderClient — the boundary to tot-foxycart's order-read surface.
|
|
3
|
+
* `ordersStore.ts` and the read routes / event receiver never call `fetch`
|
|
4
|
+
* directly; they go through this seam so a fake can stand in for tot-foxycart
|
|
5
|
+
* in tests.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
7
|
+
* The service is `tot-foxycart/routes/foxyOrderApi.js` +
|
|
8
|
+
* `modules/integrations/foxyOrderMapper.node.js`:
|
|
9
9
|
* a `GET {baseUrl}/commerce/orders?appDomain=&limit=&offset=&since=&until=&status=`
|
|
10
10
|
* list endpoint returning `{ orders, limit, offset, total, hasMore }` (limit/offset,
|
|
11
|
-
* NOT page/pageSize
|
|
11
|
+
* NOT page/pageSize), and a
|
|
12
12
|
* `GET {baseUrl}/commerce/orders/{externalOrderId}?appDomain=` detail endpoint
|
|
13
|
-
* returning `{ order }` (an envelope, not the bare order
|
|
13
|
+
* returning `{ order }` (an envelope, not the bare order),
|
|
14
14
|
* both Bearer-authenticated with the tenant's Foxy partner API key. `FoxyOrder`
|
|
15
|
-
* below
|
|
15
|
+
* below mirrors `buildOrderPayload()`'s actual normalized DTO — NOT the
|
|
16
16
|
* contract's minimized `Order` shape (`contract/openapi.yaml`); mapping one to
|
|
17
17
|
* the other is `orderMapper.ts`'s job.
|
|
18
18
|
*
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* factory shape and `../../webhooks/deliveryEngine.ts`'s bounded-timeout
|
|
21
21
|
* `fetch` (AbortController, injectable `fetchImpl`) — but adds a settable
|
|
22
22
|
* test seam (`setFoxyOrderClientForTesting`) since this client, unlike
|
|
23
|
-
* `messagesClient.ts`, is a shared singleton multiple
|
|
24
|
-
* routes, event receiver)
|
|
23
|
+
* `messagesClient.ts`, is a shared singleton that multiple callers (read
|
|
24
|
+
* routes, event receiver) each independently resolve.
|
|
25
25
|
*/
|
|
26
26
|
import { readEnv } from "@/lib/env";
|
|
27
27
|
|
|
@@ -174,7 +174,7 @@ export class HttpFoxyOrderClient implements FoxyOrderClient {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// --- Test seam ---------------------------------------------------------------
|
|
177
|
-
//
|
|
177
|
+
// Callers (read routes, event receiver) each resolve their own client
|
|
178
178
|
// instance via `getFoxyOrderClient()`; a module-level override lets a single
|
|
179
179
|
// `setFoxyOrderClientForTesting` call stand in for tot-foxycart across all of
|
|
180
180
|
// them in one test, without threading a fake through every call site.
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Idempotency-Key ledger
|
|
3
|
-
* `idempotency_keys` table `schema.ts` declares: the
|
|
2
|
+
* Idempotency-Key ledger. Backs the
|
|
3
|
+
* `idempotency_keys` table `schema.ts` declares: the one real (not
|
|
4
4
|
* presence-only) idempotency ledger in this codebase, and the one
|
|
5
5
|
* `POST /attribution` (`docs/private-apps/contract/openapi.yaml`'s
|
|
6
6
|
* `writeAttribution`) requires — a reused key with a body hash that doesn't
|
|
7
7
|
* match the one first stored is a 409, not a silent overwrite or a second
|
|
8
|
-
* row. Contrast
|
|
8
|
+
* row. Contrast the webhook replay endpoint (`webhooks/deliveries/{id}/replay.ts`),
|
|
9
9
|
* which only checks the header's presence because it has no request body to
|
|
10
|
-
* hash and no table backing it
|
|
10
|
+
* hash and no table backing it.
|
|
11
11
|
*
|
|
12
|
-
* Pure over the `Queryable` seam, same shape every other
|
|
13
|
-
* Route-agnostic by design: `attribution.ts` is
|
|
14
|
-
*
|
|
12
|
+
* Pure over the `Queryable` seam, same shape every other orders store uses.
|
|
13
|
+
* Route-agnostic by design: `attribution.ts` is one caller, but any
|
|
14
|
+
* idempotent write route can reuse this.
|
|
15
15
|
*/
|
|
16
16
|
import { sha256Hex } from "@tot/public-runtime";
|
|
17
17
|
import type { Queryable } from "../../d1/catalog.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Inbound order-forward receiver
|
|
2
|
+
* Inbound order-forward receiver. The
|
|
3
3
|
* transport-free core `pages/api/apps/internal/order-forward.ts` calls once
|
|
4
4
|
* it has verified the HMAC signature: given one reconciled order from
|
|
5
5
|
* tot-foxycart, upsert it into `ordersStore.ts` and decide whether that write
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure mapper: `FoxyOrderClient`'s `FoxyOrder` (tot-foxycart's normalized
|
|
3
|
-
* transaction DTO) -> the contract's minimized `Order` shape
|
|
4
|
-
*
|
|
3
|
+
* transaction DTO) -> the contract's minimized `Order` shape
|
|
4
|
+
* (`docs/private-apps/contract/openapi.yaml`'s `Order`).
|
|
5
5
|
*
|
|
6
6
|
* Drops everything the contract's `additionalProperties: false` Order
|
|
7
7
|
* doesn't declare — no raw checkout payload, no signed-cart material, no tax
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `orders` read-model store
|
|
3
|
-
* `orders` rows `schema.ts` declares: the diffing surface
|
|
4
|
-
*
|
|
2
|
+
* `orders` read-model store. Owns the
|
|
3
|
+
* `orders` rows `schema.ts` declares: the diffing surface the event emission
|
|
4
|
+
* reads (`upsertOrder`'s returned `previousStatus` is how it
|
|
5
5
|
* detects a created→fulfilled transition without a second query) and the
|
|
6
|
-
* lookup surface the attribution route
|
|
6
|
+
* lookup surface the attribution route uses for its cross-tenant
|
|
7
7
|
* ownership check.
|
|
8
8
|
*
|
|
9
9
|
* Pure over the `Queryable` seam (see `../../d1/catalog.ts`) — same shape
|
|
@@ -54,8 +54,8 @@ export interface UpsertOrderInput {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* `upsertOrder`'s return: the row's status BEFORE this write (`null` for a
|
|
57
|
-
* brand-new order) alongside the row as it now stands — the diffing signal
|
|
58
|
-
*
|
|
57
|
+
* brand-new order) alongside the row as it now stands — the diffing signal the
|
|
58
|
+
* event emission needs to tell "just created" apart from
|
|
59
59
|
* "created → fulfilled" apart from "no-op re-delivery of the same status",
|
|
60
60
|
* without a second round trip.
|
|
61
61
|
*/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* App registry service
|
|
2
|
+
* App registry service. Owns the
|
|
3
3
|
* `app_installs` / `app_audit_log` rows described in `schema.ts` and
|
|
4
4
|
* orchestrates credential lifecycle through the {@link CredentialPort} seam
|
|
5
|
-
* (
|
|
5
|
+
* (the credential store implements it; this service depends only on the interface, mirroring
|
|
6
6
|
* how `D1VersionStore` implements `@tot/private-controlplane`'s `VersionStore`
|
|
7
7
|
* contract without either side importing the other's internals).
|
|
8
8
|
*
|
|
@@ -131,7 +131,7 @@ export interface IssuedCredential {
|
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
133
|
* `IssuedCredential` plus the one-time plaintext secret. `issue`/`rotate` mint
|
|
134
|
-
* the secret, hash it for storage (
|
|
134
|
+
* the secret, hash it for storage (`sha256Hex`), and hand the
|
|
135
135
|
* plaintext back exactly once here — there is no "show secret again" path by
|
|
136
136
|
* design, so a caller that discards this return has lost it for good (see
|
|
137
137
|
* `AppRegistryService.install`'s `InstallResult`, which is why this seam
|
|
@@ -140,10 +140,10 @@ export interface IssuedCredential {
|
|
|
140
140
|
export type IssuedCredentialSecret = IssuedCredential & { secret: string };
|
|
141
141
|
|
|
142
142
|
/**
|
|
143
|
-
* Minimal seam for the credential lifecycle
|
|
143
|
+
* Minimal seam for the credential lifecycle the credential store
|
|
144
144
|
* implements against `app_credentials`. `AppRegistryService` orchestrates
|
|
145
145
|
* through this interface only — it never touches credential storage directly,
|
|
146
|
-
* so
|
|
146
|
+
* so it can be tested with a fake.
|
|
147
147
|
*/
|
|
148
148
|
export interface CredentialPort {
|
|
149
149
|
/** Issue a fresh client-credential set for a newly installed app. */
|
|
@@ -155,7 +155,7 @@ export interface CredentialPort {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
|
-
* The 3 platform-owned widget slots
|
|
158
|
+
* The 3 platform-owned widget slots. Mirrors the `widgets[].placement`
|
|
159
159
|
* enum in `docs/private-apps/contract/tot-app.schema.json` — widgets render in a
|
|
160
160
|
* platform-managed sandboxed iframe at one of these fixed slots, never as
|
|
161
161
|
* merchant-pasted script at an app-chosen location.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Scope validation — the compliance-floor gate for Storefront Private Apps
|
|
3
|
-
*
|
|
2
|
+
* Scope validation — the compliance-floor gate for Storefront Private Apps.
|
|
3
|
+
* See `docs/private-apps/feature-brief.md` §"The
|
|
4
4
|
* compliance floor" and the machine-readable contract:
|
|
5
5
|
* - `docs/private-apps/contract/scopes.json` — the 8-scope V1 catalog.
|
|
6
6
|
* - `docs/private-apps/contract/forbidden-scopes.json` — the 8 ids that map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OAuth2 client-credentials token issuer — the Storefront App Gateway's
|
|
3
|
-
* `POST /oauth/token` grant
|
|
4
|
-
* short-lived RS256 JWT a private app presents as a bearer token to the
|
|
3
|
+
* `POST /oauth/token` grant. Mints the
|
|
4
|
+
* short-lived RS256 JWT a private app presents as a bearer token to the
|
|
5
5
|
* scoped APIs.
|
|
6
6
|
*
|
|
7
7
|
* `tenant` and `env` are supplied by the CALLER (the route, from
|
|
@@ -97,8 +97,8 @@ export class AppTokenIssuer {
|
|
|
97
97
|
tenant: install.tenantId,
|
|
98
98
|
env: install.env,
|
|
99
99
|
scopes: install.scopes,
|
|
100
|
-
// Discriminates this bearer token from a widget launch token
|
|
101
|
-
//
|
|
100
|
+
// Discriminates this bearer token from a widget launch token so the two
|
|
101
|
+
// can never be replayed for each other even though both
|
|
102
102
|
// are RS256 JWTs signed by the same gateway key — see tokenVerifier.ts.
|
|
103
103
|
token_use: "access",
|
|
104
104
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Offline verification of the Storefront App Gateway's own OAuth2
|
|
3
|
-
* client-credentials JWT
|
|
4
|
-
* imports
|
|
3
|
+
* client-credentials JWT — this is what the scoped-API boundary
|
|
4
|
+
* imports to authorize an app's request.
|
|
5
5
|
*
|
|
6
6
|
* Modeled directly on `../auth/identityToken.ts`'s `IdentityAssertionVerifier`
|
|
7
7
|
* (same `jwtVerify` + JWKS-by-`kid` shape, `aud`/`iss` pinning, alg allowlist),
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "./gatewayKeys.js";
|
|
26
26
|
import { readEnv } from "@/lib/env";
|
|
27
27
|
|
|
28
|
-
/** The verified app-token claims (
|
|
28
|
+
/** The verified app-token claims (the scoped API's authorization input). */
|
|
29
29
|
export interface AppTokenClaims {
|
|
30
30
|
iss: string;
|
|
31
31
|
sub: string; // install id
|
|
@@ -40,8 +40,8 @@ export interface AppTokenClaims {
|
|
|
40
40
|
|
|
41
41
|
/** Normalize a jose JWT payload into typed {@link AppTokenClaims}, throwing on any missing claim. */
|
|
42
42
|
function toAppTokenClaims(payload: Record<string, unknown>): AppTokenClaims {
|
|
43
|
-
// The security-critical discriminator: a widget launch token
|
|
44
|
-
// `token_use: "widget_launch"`) is signed by this SAME gateway key, so
|
|
43
|
+
// The security-critical discriminator: a widget launch token
|
|
44
|
+
// (`token_use: "widget_launch"`) is signed by this SAME gateway key, so
|
|
45
45
|
// signature/aud/iss/exp checks alone can't tell it apart from a bearer
|
|
46
46
|
// token. Requiring `token_use === "access"` here is what stops a launch
|
|
47
47
|
// token — minted for a sandboxed iframe, short-TTL, no scopes checked —
|
|
@@ -128,7 +128,7 @@ export class AppTokenVerifier {
|
|
|
128
128
|
/**
|
|
129
129
|
* Get (or build) the verifier for one tenant, from the gateway's own
|
|
130
130
|
* locally-derived JWKS. Returns `null` when the signing key isn't configured
|
|
131
|
-
* — callers (
|
|
131
|
+
* — callers (the scoped-API routes) fail closed with 503, mirroring `getIdentityVerifier`.
|
|
132
132
|
* Not cached across calls (unlike `identityToken.ts`'s remote-fetch verifier):
|
|
133
133
|
* deriving the local JWKS is cheap (`gatewayKeys.ts` caches the export itself).
|
|
134
134
|
*/
|