@tokenoftrust/storefront-runner 4.11.4 → 4.11.6
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/src/lib/orders/admin/orderSource.ts +20 -7
- package/apps/storefront/src/lib/orders/vault/contactRoutes.ts +14 -4
- package/apps/storefront/src/pages/api/admin/fulfillment/orders/[id]/address.ts +3 -2
- package/apps/storefront/src/pages/api/admin/fulfillment/orders/contact-search.ts +2 -1
- package/apps/storefront/src/pages/api/admin/fulfillment/orders/contacts.ts +2 -1
- package/package.json +1 -1
- package/packages/public-runtime/src/image-dependencies.ts +1 -1
|
@@ -86,6 +86,22 @@ export function workerOrderSourceDeps(
|
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* The stage a tenant's orders are recorded under, shared by every reader of an order (the Orders
|
|
91
|
+
* tab, the order routes and the contact vault routes): a shared-store tenant's ledger and sealed
|
|
92
|
+
* contacts carry this Worker's `COMMERCE_STAGE`; an own-store tenant's carry the routed environment's.
|
|
93
|
+
*/
|
|
94
|
+
export async function resolveOrderStage(
|
|
95
|
+
input: { tenantId: string; customizationEnv: CustomizationEnv | undefined },
|
|
96
|
+
deps: Pick<OrderSourceDeps, "admitSharedStoreTenant" | "readEnv">,
|
|
97
|
+
): Promise<{ checkout: "shared-test-store" | "own-store"; stage: CommerceStage | null }> {
|
|
98
|
+
if ((await deps.admitSharedStoreTenant(input.tenantId)).ok) {
|
|
99
|
+
const stage = await deps.readEnv("COMMERCE_STAGE");
|
|
100
|
+
return { checkout: "shared-test-store", stage: stage === "preview" || stage === "publication" ? stage : null };
|
|
101
|
+
}
|
|
102
|
+
return { checkout: "own-store", stage: commerceStageForEnv(input.customizationEnv) };
|
|
103
|
+
}
|
|
104
|
+
|
|
89
105
|
export async function resolveOrderSource(
|
|
90
106
|
input: { tenantId: string; customizationEnv: CustomizationEnv | undefined },
|
|
91
107
|
deps: OrderSourceDeps,
|
|
@@ -93,19 +109,16 @@ export async function resolveOrderSource(
|
|
|
93
109
|
const tenantId = input.tenantId.trim();
|
|
94
110
|
if (!tenantId) return { kind: "not-connected", checkout: null, gap: "no-tenant" };
|
|
95
111
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
112
|
+
const { checkout, stage } = await resolveOrderStage({ tenantId, customizationEnv: input.customizationEnv }, deps);
|
|
113
|
+
if (!stage) return { kind: "not-connected", checkout, gap: "stage-unknown" };
|
|
114
|
+
|
|
115
|
+
if (checkout === "shared-test-store") {
|
|
100
116
|
const db = await deps.readLedgerDb();
|
|
101
117
|
if (!db) return { kind: "not-connected", checkout, gap: "ledger-unavailable" };
|
|
102
118
|
const book = ledgerOrderBook(new ScrappyOrderLedger(db), { tenantId, stage });
|
|
103
119
|
return { kind: "ledger", checkout, scope: { tenantId, stage }, client: book.client, status: book.status };
|
|
104
120
|
}
|
|
105
121
|
|
|
106
|
-
const checkout = "own-store";
|
|
107
|
-
const stage = commerceStageForEnv(input.customizationEnv);
|
|
108
|
-
if (!stage) return { kind: "not-connected", checkout, gap: "stage-unknown" };
|
|
109
122
|
const bearer = await deps.readEnv("FOXY_ORDER_API_KEY");
|
|
110
123
|
if (!bearer) return { kind: "not-connected", checkout, gap: "partner-key-missing" };
|
|
111
124
|
return {
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The admission every order-contact admin route runs before it touches the vault: the owner gate,
|
|
3
3
|
* then the owner-or-staff split (contactReaderAuthority), then — for staff — a reason, a ticket and a
|
|
4
|
-
* LIVE owner-consented customer-contact-read grant. The tenant comes from the routed host
|
|
5
|
-
*
|
|
4
|
+
* LIVE owner-consented customer-contact-read grant. The tenant comes from the routed host, never
|
|
5
|
+
* from the request. An order contact's stage is the one its order is recorded under
|
|
6
|
+
* (`resolveOrderStage`, shared with the Orders tab, with the shared-store admission its route
|
|
7
|
+
* supplies); a message's is the routed customization environment's.
|
|
6
8
|
*/
|
|
7
9
|
import type { APIContext } from "astro";
|
|
8
10
|
import { requireAdminApiEntry } from "@/lib/access/auth/adminApiGuard";
|
|
9
11
|
import { readViewerSession } from "@/lib/access/auth/route";
|
|
10
12
|
import { contactReaderAuthority, resolveContactReadGrant } from "@/lib/access/auth/contactReaderAuthority";
|
|
11
|
-
import { readD1 } from "@/lib/publication/shared/env";
|
|
13
|
+
import { readD1, readEnv } from "@/lib/publication/shared/env";
|
|
12
14
|
import { fromD1, type Queryable } from "@/lib/catalog/d1/catalog.js";
|
|
13
15
|
import { commerceStageForEnv } from "@/lib/fulfillment/fulfillmentService";
|
|
16
|
+
import { resolveOrderStage, type OrderSourceDeps } from "@/lib/orders/admin/orderSource";
|
|
14
17
|
import type { KmsClient } from "@/lib/customers/contactVault/kms.js";
|
|
15
18
|
import { loadVaultKms } from "./kmsEnv.js";
|
|
16
19
|
import { type ContactActor, type VaultStage } from "@/lib/customers/contactVault/contactAudit.js";
|
|
@@ -54,6 +57,7 @@ export type ContactRoute =
|
|
|
54
57
|
export async function openContactRoute(
|
|
55
58
|
context: APIContext,
|
|
56
59
|
expect: "owner" | "staff",
|
|
60
|
+
record: "message" | Pick<OrderSourceDeps, "admitSharedStoreTenant">,
|
|
57
61
|
body?: unknown,
|
|
58
62
|
): Promise<ContactRoute> {
|
|
59
63
|
const refuse = (error: string, status: number): ContactRoute => ({ ok: false, response: contactJson({ error }, status) });
|
|
@@ -79,7 +83,13 @@ export async function openContactRoute(
|
|
|
79
83
|
actor = { actorId: authority.actorId, role: "staff", ...reason };
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
const
|
|
86
|
+
const customizationEnv = context.locals.customizationEnv;
|
|
87
|
+
const stage = record === "message"
|
|
88
|
+
? commerceStageForEnv(customizationEnv)
|
|
89
|
+
: (await resolveOrderStage({ tenantId: host, customizationEnv }, {
|
|
90
|
+
admitSharedStoreTenant: record.admitSharedStoreTenant,
|
|
91
|
+
readEnv: async (name) => (await readEnv(name)) ?? undefined,
|
|
92
|
+
})).stage;
|
|
83
93
|
if (!stage) return refuse("stage-unknown", 409);
|
|
84
94
|
const d1 = await readD1("STOREFRONT_APPS_DB");
|
|
85
95
|
if (!d1) return refuse("unavailable", 503);
|
|
@@ -10,6 +10,7 @@ import { routedApiParam } from "@/lib/publication/shared/routedApiParam";
|
|
|
10
10
|
*/
|
|
11
11
|
import type { APIContext } from "astro";
|
|
12
12
|
import { isOpaqueId } from "@/lib/fulfillment/fulfillmentService";
|
|
13
|
+
import { admitSharedStoreTenant } from "@/lib/checkout/commerce/sharedStoreAdmission";
|
|
13
14
|
import { contactJson, openContactRoute } from "@/lib/orders/vault/contactRoutes";
|
|
14
15
|
import { eraseOrderContact, readEraseGroups, readOrderContact, type Group } from "@/lib/orders/vault/orderContactVault";
|
|
15
16
|
|
|
@@ -18,7 +19,7 @@ export const prerender = false;
|
|
|
18
19
|
export async function GET(context: APIContext): Promise<Response> {
|
|
19
20
|
const orderId = routedApiParam(context, "fulfillment-address");
|
|
20
21
|
if (!orderId || !isOpaqueId(orderId)) return contactJson({ error: "not-found" }, 404);
|
|
21
|
-
const route = await openContactRoute(context, "owner");
|
|
22
|
+
const route = await openContactRoute(context, "owner", { admitSharedStoreTenant });
|
|
22
23
|
if (!route.ok) return route.response;
|
|
23
24
|
const read = await readOrderContact(route.db, route.kms, {
|
|
24
25
|
tenantId: route.tenantId, stage: route.stage, orderId, actor: route.actor, auditId: crypto.randomUUID(), now: new Date(),
|
|
@@ -48,7 +49,7 @@ async function eraseGroups(request: Request): Promise<Group[] | undefined | null
|
|
|
48
49
|
export async function DELETE(context: APIContext): Promise<Response> {
|
|
49
50
|
const orderId = routedApiParam(context, "fulfillment-address");
|
|
50
51
|
if (!orderId || !isOpaqueId(orderId)) return contactJson({ error: "not-found" }, 404);
|
|
51
|
-
const route = await openContactRoute(context, "owner");
|
|
52
|
+
const route = await openContactRoute(context, "owner", { admitSharedStoreTenant });
|
|
52
53
|
if (!route.ok) return route.response;
|
|
53
54
|
const groups = await eraseGroups(context.request);
|
|
54
55
|
if (groups === null) return contactJson({ error: "malformed" }, 400);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Staff are refused 403 support-read-required. Nothing here logs the body.
|
|
7
7
|
*/
|
|
8
8
|
import type { APIContext } from "astro";
|
|
9
|
+
import { admitSharedStoreTenant } from "@/lib/checkout/commerce/sharedStoreAdmission";
|
|
9
10
|
import { contactJson, openContactRoute, readRequestJson } from "@/lib/orders/vault/contactRoutes";
|
|
10
11
|
import { readSearchQuery, searchOrderContacts } from "@/lib/orders/vault/orderContactVault";
|
|
11
12
|
|
|
@@ -13,7 +14,7 @@ export const prerender = false;
|
|
|
13
14
|
|
|
14
15
|
export async function POST(context: APIContext): Promise<Response> {
|
|
15
16
|
if (new URL(context.request.url).search !== "") return contactJson({ error: "malformed" }, 400);
|
|
16
|
-
const route = await openContactRoute(context, "owner");
|
|
17
|
+
const route = await openContactRoute(context, "owner", { admitSharedStoreTenant });
|
|
17
18
|
if (!route.ok) return route.response;
|
|
18
19
|
const query = readSearchQuery(await readRequestJson(context.request));
|
|
19
20
|
if (!query) return contactJson({ error: "malformed" }, 400);
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
* expiries and rule ids. Owner only; staff are refused 403 support-read-required.
|
|
6
6
|
*/
|
|
7
7
|
import type { APIContext } from "astro";
|
|
8
|
+
import { admitSharedStoreTenant } from "@/lib/checkout/commerce/sharedStoreAdmission";
|
|
8
9
|
import { contactJson, openContactRoute } from "@/lib/orders/vault/contactRoutes";
|
|
9
10
|
import { listOrderContacts } from "@/lib/orders/vault/orderContactVault";
|
|
10
11
|
|
|
11
12
|
export const prerender = false;
|
|
12
13
|
|
|
13
14
|
export async function GET(context: APIContext): Promise<Response> {
|
|
14
|
-
const route = await openContactRoute(context, "owner");
|
|
15
|
+
const route = await openContactRoute(context, "owner", { admitSharedStoreTenant });
|
|
15
16
|
if (!route.ok) return route.response;
|
|
16
17
|
const listed = await listOrderContacts(route.db, route.kms, {
|
|
17
18
|
tenantId: route.tenantId, stage: route.stage, actor: route.actor, auditId: crypto.randomUUID(), now: new Date(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "4.11.
|
|
3
|
+
"version": "4.11.6",
|
|
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",
|
|
@@ -184,7 +184,7 @@ const LOCATOR_IMAGE_KEY_RE = /^(?:icon|favicon|logo|logolight|logodark|avatar|og
|
|
|
184
184
|
/** An object under one of these keys is an image whose `src`/`url` is the locator (chrome `brand.logo`, JSON-LD `logo`). */
|
|
185
185
|
const LOCATOR_IMAGE_OBJECT_KEY_RE = /^logo$/i;
|
|
186
186
|
const LOCATOR_SHAPED_RE = /^(?:\/|https?:|data:image\/)/i;
|
|
187
|
-
const HTML_VALUE_KEYS = new Set(["contenthtml", "content_html", "descriptionhtml", "description_html", "html"]);
|
|
187
|
+
const HTML_VALUE_KEYS = new Set(["contenthtml", "content_html", "descriptionhtml", "description_html", "body_html", "bodyhtml", "html"]);
|
|
188
188
|
const NON_IMAGE_ASSET_RE = /\.(?:css|m?js|woff2?|ttf|otf|eot|map|json|xml|txt|pdf|mp4|webm|mov)$/i;
|
|
189
189
|
const CONTROL_RE = /[\u0000-\u001f\u007f]/;
|
|
190
190
|
const URL_TAB_OR_NEWLINE_RE = /[\t\n\r]/g;
|