@tokenoftrust/storefront-runner 2.2.119 → 2.2.121

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.
@@ -331,6 +331,8 @@ export interface LoginGateInput {
331
331
  * cockpit, /api) still require a real session.
332
332
  */
333
333
  previewTokenValid?: boolean;
334
+ /** A verified, tenant-bound candidate content pin admits public content only. */
335
+ candidatePinValid?: boolean;
334
336
  }
335
337
 
336
338
  /**
@@ -355,10 +357,12 @@ export function evaluateLoginGate(input: LoginGateInput): LoginGateDecision {
355
357
  // public content only. NEVER for admin/cockpit/api — that guard lives here (not
356
358
  // just in middleware) so /admin stays gated even if the flag is set upstream.
357
359
  if (input.qaBypass && !isBypassPrivilegedPath(input.pathname)) return { action: "pass" };
358
- // Customization preview: a signed, tenant-bound preview token admits a viewer to
359
- // that tenant's public content on any tenant. Same privileged-path exclusion as
360
- // the QA bypass — admin/cockpit/api are never reachable on a token alone.
361
- if (input.previewTokenValid && !isBypassPrivilegedPath(input.pathname)) {
360
+ // Signed, tenant-bound preview grants admit public content only. Admin,
361
+ // cockpit, and API routes still require a session.
362
+ if (
363
+ (input.previewTokenValid || input.candidatePinValid) &&
364
+ !isBypassPrivilegedPath(input.pathname)
365
+ ) {
362
366
  return { action: "pass" };
363
367
  }
364
368
  if (input.hasValidSession) return { action: "pass" };
@@ -35,6 +35,7 @@ import { resolveAdminTenantMode } from "@/lib/adminTenantEnvelope";
35
35
  import { gatedHoldingResponse } from "@/lib/auth/gatePage";
36
36
  import { readSession, KvSessionStore, SESSION_COOKIE } from "@/lib/auth/session";
37
37
  import { runRenderChain } from "./renderChain.js";
38
+ import { resolveCandidateContentPin } from "./candidatePin.js";
38
39
  import type { InternalContentGrant } from "@/lib/auth/internalContentAuthority";
39
40
 
40
41
  export interface ControlPlaneInput {
@@ -63,6 +64,11 @@ export interface ControlPlaneInput {
63
64
  export async function serveControlPlane(input: ControlPlaneInput): Promise<Response> {
64
65
  const { context, next, host, url, tenant, basePath, renderPath, cache, kv } = input;
65
66
  const { request, locals } = context;
67
+ // A pinned route rewrite arrives here without the outer preview route's
68
+ // session. Verify its tenant-bound grant before the host login gate runs.
69
+ const candidatePin = input.hostLoginGated
70
+ ? await resolveCandidateContentPin(url, tenant.tenant_id)
71
+ : null;
66
72
 
67
73
  // Login-gate enforcement (DEMO_GATE_MODE="login"): runs before the render so a
68
74
  // gated page is never served to an unauthenticated viewer. A tenant that
@@ -170,6 +176,7 @@ export async function serveControlPlane(input: ControlPlaneInput): Promise<Respo
170
176
  tenantExempt,
171
177
  qaBypass,
172
178
  previewTokenValid,
179
+ candidatePinValid: !!candidatePin,
173
180
  });
174
181
  if (decision.action === "redirect") {
175
182
  // Admin and the cockpit hard-redirect to sign-in rather than rendering the
@@ -261,6 +268,7 @@ export async function serveControlPlane(input: ControlPlaneInput): Promise<Respo
261
268
  publicationRequestPath: input.publicationRequestPath,
262
269
  publicationRoute: input.publicationRoute,
263
270
  publicationBucket: input.publicationBucket,
271
+ verifiedCandidatePin: candidatePin ?? undefined,
264
272
  managedIntegrationDeclarations,
265
273
  readTenantConsentSnapshot: () => readSavedConsentSnapshot(tenant.tenant_id),
266
274
  // The D1 catalog serving projection, for a tenant that declares
@@ -14,7 +14,7 @@ import { mergeTheme, themeToCssVars } from "@/themes/schema";
14
14
  import { createToTClient } from "@/lib/tot/ToTClient";
15
15
  import { readEnv, readR2 } from "@/lib/runtimeEnv";
16
16
  import { tryServeStaticBundle } from "./staticBundle.js";
17
- import { resolveCandidateContentPin } from "./candidatePin.js";
17
+ import { resolveCandidateContentPin, type CandidateContentPin } from "./candidatePin.js";
18
18
  import { createContentProvider, DevDraftContentProvider } from "@/lib/storyblok/provider";
19
19
  import { bindPublicationSpaceContentProviderV1 } from "@/lib/publication/contentProvider.js";
20
20
  import { resolvePublicationRuntimeSourceV1 } from "@/lib/publication/runtimeSource.js";
@@ -144,6 +144,8 @@ export interface RenderChainInput {
144
144
  readCatalogD1: (() => Promise<Parameters<typeof createToTClient>[1]>) | null;
145
145
  /** Exact per-request grant for an F2-selected internal publication space. */
146
146
  internalContentGrant?: InternalContentGrant;
147
+ /** Pin already verified for this tenant by the control-plane login gate. */
148
+ verifiedCandidatePin?: CandidateContentPin;
147
149
  }
148
150
 
149
151
  /** Materialize-only response header: the tenant's declared origins, comma-separated. */
@@ -180,7 +182,8 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
180
182
  // pin can only ever name its own version; a mismatched/expired/absent token
181
183
  // falls through to the normal channel-resolved read below, same fail-closed
182
184
  // posture as the `__preview` token.
183
- const candidatePin = await resolveCandidateContentPin(url, tenantRecord.tenant_id);
185
+ const candidatePin = input.verifiedCandidatePin ??
186
+ await resolveCandidateContentPin(url, tenantRecord.tenant_id);
184
187
  // Per-developer AI-edit draft: the signed
185
188
  // tot_dev_draft cookie's mere PRESENCE opts the request out of the shared page
186
189
  // cache below (a draft view must never be cached publicly); it's verified —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.2.119",
3
+ "version": "2.2.121",
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",