@tokenoftrust/storefront-runner 2.1.0 → 2.1.2

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.
@@ -273,6 +273,24 @@ export function isBypassPrivilegedPath(pathname: string): boolean {
273
273
  return false;
274
274
  }
275
275
 
276
+ /**
277
+ * Control surfaces a withheld visitor is sent straight to sign-in for, instead of the
278
+ * "Coming soon" holding page: admin and the cockpit (with its `/dev` alias), in both the
279
+ * bare and the tenant-prefixed form. The holding page's audience is a public browser of
280
+ * a gated STOREFRONT; a developer or merchant heading for a control surface needs the
281
+ * sign-in prompt, not an ambiguous 200 that reads as "this store isn't ready." `/api/`
282
+ * is excluded — API callers need a status, not a redirect.
283
+ */
284
+ export function isSignInRedirectPath(pathname: string): boolean {
285
+ // Bare `/api/...` would otherwise read as tenant `api` + `/dev`.
286
+ if (pathname === "/api" || pathname.startsWith("/api/")) return false;
287
+ if (isAdminPath(pathname)) return true;
288
+ if (pathname === "/cockpit" || pathname.startsWith("/cockpit/")) return true;
289
+ if (/^\/[^/]+\/cockpit(?:\/|$)/.test(pathname)) return true;
290
+ if (pathname === "/dev" || pathname.startsWith("/dev/")) return true;
291
+ return /^\/[^/]+\/dev(?:\/|$)/.test(pathname);
292
+ }
293
+
276
294
  export type LoginGateDecision =
277
295
  | { action: "pass" }
278
296
  | { action: "redirect"; location: string };
@@ -25,6 +25,7 @@ import {
25
25
  evaluateLoginGate,
26
26
  isAuthPath,
27
27
  isAdminPath,
28
+ isSignInRedirectPath,
28
29
  sessionAdmitsHost,
29
30
  sessionHostCapability,
30
31
  type GateMode,
@@ -171,15 +172,16 @@ export async function serveControlPlane(input: ControlPlaneInput): Promise<Respo
171
172
  previewTokenValid,
172
173
  });
173
174
  if (decision.action === "redirect") {
174
- // ADMIN (and any privileged surface) hard-redirects to sign-in rather than
175
- // rendering the merchant-facing "Coming soon" holding page: an unauthenticated
176
- // admin visitor must land on the sign-in prompt, never an ambiguous 200 that
175
+ // Admin and the cockpit hard-redirect to sign-in rather than rendering the
176
+ // merchant-facing "Coming soon" holding page: an unauthenticated visitor to a
177
+ // control surface must land on the sign-in prompt, never an ambiguous 200 that
177
178
  // reads as "this store isn't ready." The holding page's whole audience is a
178
- // public browser of a gated STOREFRONT; it is the wrong signal for /admin. The
179
- // gate already decided to withhold — this only changes HOW (303 vs holding
180
- // page); it never weakens the gate. `decision.location` already carries
181
- // `?next=` so the admin returns to exactly where they were headed.
182
- if (isAdminPath(url.pathname)) {
179
+ // public browser of a gated STOREFRONT; it is the wrong signal for /admin and
180
+ // `/<tenant>/cockpit`. The gate already decided to withhold — this only changes
181
+ // HOW (303 vs holding page); it never weakens the gate. `decision.location`
182
+ // already carries `?next=` so the visitor returns to exactly where they were
183
+ // headed.
184
+ if (isSignInRedirectPath(url.pathname)) {
183
185
  return context.redirect(decision.location, 303);
184
186
  }
185
187
  // Withhold content but offer sign-in (carrying the intended destination).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
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",