@tokenoftrust/storefront-runner 2.0.20 → 2.0.22
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 +3 -1
- package/apps/storefront/src/lib/activity/uiActor.ts +2 -4
- package/apps/storefront/src/lib/auth/adminEntry.ts +2 -2
- package/apps/storefront/src/lib/auth/devAuth.ts +1 -1
- package/apps/storefront/src/lib/auth/identityToken.ts +13 -2
- package/apps/storefront/src/lib/shared/capability.ts +16 -2
- package/apps/storefront/src/middleware/controlPlane.ts +1 -0
- package/package.json +1 -1
package/apps/storefront/env.d.ts
CHANGED
|
@@ -197,11 +197,13 @@ declare global {
|
|
|
197
197
|
/**
|
|
198
198
|
* Signed-in viewer for a login-gated preview host (set by the demo login
|
|
199
199
|
* gate once a valid, resource-bound `tot_session` cookie is present). Carries
|
|
200
|
-
* the email +
|
|
200
|
+
* the opaque subject, email + role(s)/capability tot20 resolved on this tenant, so chrome
|
|
201
201
|
* can show "signed in as … — owner/developer/staff". Absent on open or
|
|
202
202
|
* password-gated hosts.
|
|
203
203
|
*/
|
|
204
204
|
viewer?: {
|
|
205
|
+
/** Opaque identity-provider subject from the verified session assertion. */
|
|
206
|
+
subject?: string;
|
|
205
207
|
email: string;
|
|
206
208
|
roles: string[];
|
|
207
209
|
capability: string;
|
|
@@ -51,11 +51,9 @@ import type { ActionKey } from "@tot/public-runtime";
|
|
|
51
51
|
import { readViewerSession } from "@/lib/auth/route";
|
|
52
52
|
import { isStaffSession } from "@/lib/dashboard/staffAdmission";
|
|
53
53
|
import { readEnv } from "@/lib/env";
|
|
54
|
+
import { isOwnerCapability } from "@/lib/shared/capability";
|
|
54
55
|
import { hashActorId, recordActivity } from "./recordActivity";
|
|
55
56
|
|
|
56
|
-
/** The host-bound capabilities that mark a viewer as the store OWNER (merchant). */
|
|
57
|
-
const OWNER_CAPABILITIES = new Set(["owner", "admin"]);
|
|
58
|
-
|
|
59
57
|
/** The minimal viewer chrome the middleware stamps on `locals.viewer`. */
|
|
60
58
|
interface ViewerLike {
|
|
61
59
|
email?: string;
|
|
@@ -81,7 +79,7 @@ export function classifyActorKind(
|
|
|
81
79
|
): ActorKind {
|
|
82
80
|
if (!viewer?.email) return "agent";
|
|
83
81
|
if (isStaffSession(session)) return "admin";
|
|
84
|
-
if (
|
|
82
|
+
if (isOwnerCapability(viewer.capability)) return "merchant";
|
|
85
83
|
return "dev";
|
|
86
84
|
}
|
|
87
85
|
|
|
@@ -32,8 +32,8 @@ import type { SessionRecord } from "./session.js";
|
|
|
32
32
|
import { sessionHostCapability } from "./loginGate.js";
|
|
33
33
|
import {
|
|
34
34
|
decideIsOwner,
|
|
35
|
+
isOwnerCapability,
|
|
35
36
|
resolveShipCapabilities,
|
|
36
|
-
OWNER_CAPABILITIES,
|
|
37
37
|
type Capability,
|
|
38
38
|
type ShipCapabilities,
|
|
39
39
|
} from "@/lib/shared/capability";
|
|
@@ -108,7 +108,7 @@ export function resolveAdminEntry(input: {
|
|
|
108
108
|
const principal: AdminEntryPrincipal = !record
|
|
109
109
|
? "anonymous"
|
|
110
110
|
: hostCapability
|
|
111
|
-
?
|
|
111
|
+
? isOwnerCapability(hostCapability)
|
|
112
112
|
? (hostCapability as "owner" | "admin")
|
|
113
113
|
: "member"
|
|
114
114
|
: staffScope
|
|
@@ -77,7 +77,7 @@ export async function devSessionFields(
|
|
|
77
77
|
const resource = (await readEnv("STOREFRONT_APP_DOMAIN")) || "storefront";
|
|
78
78
|
const tenants = devTenantResources().map((resource) => ({
|
|
79
79
|
resource,
|
|
80
|
-
role: "
|
|
80
|
+
role: "member" as const,
|
|
81
81
|
}));
|
|
82
82
|
return {
|
|
83
83
|
email: email.toLowerCase(),
|
|
@@ -27,6 +27,11 @@ import {
|
|
|
27
27
|
type JSONWebKeySet,
|
|
28
28
|
} from "jose";
|
|
29
29
|
import { readEnv } from "@/lib/env";
|
|
30
|
+
import {
|
|
31
|
+
TENANT_ROLES,
|
|
32
|
+
isTenantRole,
|
|
33
|
+
type TenantRole,
|
|
34
|
+
} from "@/lib/shared/capability";
|
|
30
35
|
|
|
31
36
|
/** tot's issuer — consumers pin/allowlist this (`auth.jwt.issuer`). */
|
|
32
37
|
export const TOT_ISSUER = "tokenoftrust-internal-ops";
|
|
@@ -45,7 +50,13 @@ export const JWKS_PATH = "/api/.well-known/jwks.json";
|
|
|
45
50
|
* (`developer`/`inviter`/none → `member`). Org standing only; finer granularity
|
|
46
51
|
* is the storefront's own job to derive locally (Phase-4 PDP work).
|
|
47
52
|
*/
|
|
48
|
-
export
|
|
53
|
+
export const COARSE_ROLES = TENANT_ROLES;
|
|
54
|
+
export type CoarseRole = TenantRole;
|
|
55
|
+
|
|
56
|
+
/** Runtime counterpart to the closed coarse-role type. */
|
|
57
|
+
export function isCoarseRole(value: unknown): value is CoarseRole {
|
|
58
|
+
return isTenantRole(value);
|
|
59
|
+
}
|
|
49
60
|
|
|
50
61
|
/** A single tenant membership carried in the assertion. */
|
|
51
62
|
export interface TenantMembership {
|
|
@@ -90,7 +101,7 @@ export interface IdentityClaims {
|
|
|
90
101
|
|
|
91
102
|
/** Coarse-role collapse guard: anything tot didn't send as owner/admin → member. */
|
|
92
103
|
function coerceRole(role: unknown): CoarseRole {
|
|
93
|
-
return role
|
|
104
|
+
return isCoarseRole(role) ? role : "member";
|
|
94
105
|
}
|
|
95
106
|
|
|
96
107
|
/**
|
|
@@ -16,8 +16,22 @@
|
|
|
16
16
|
/** Access level the acting operator holds. */
|
|
17
17
|
export type Capability = "preview-access" | "ship-on-behalf";
|
|
18
18
|
|
|
19
|
+
/** Complete tenant-role vocabulary emitted by verified identity assertions. */
|
|
20
|
+
export const TENANT_ROLES = ["owner", "admin", "member"] as const;
|
|
21
|
+
export type TenantRole = (typeof TENANT_ROLES)[number];
|
|
22
|
+
|
|
23
|
+
export function isTenantRole(value: unknown): value is TenantRole {
|
|
24
|
+
return typeof value === "string" && (TENANT_ROLES as readonly string[]).includes(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
/** Capabilities that carry tenant-owner standing (vs. a granted developer). */
|
|
20
|
-
export const
|
|
28
|
+
export const OWNER_CAPABILITY_VALUES = ["owner", "admin"] as const satisfies readonly TenantRole[];
|
|
29
|
+
export type OwnerCapability = (typeof OWNER_CAPABILITY_VALUES)[number];
|
|
30
|
+
export const OWNER_CAPABILITIES: ReadonlySet<string> = new Set(OWNER_CAPABILITY_VALUES);
|
|
31
|
+
|
|
32
|
+
export function isOwnerCapability(value: unknown): value is OwnerCapability {
|
|
33
|
+
return typeof value === "string" && OWNER_CAPABILITIES.has(value);
|
|
34
|
+
}
|
|
21
35
|
|
|
22
36
|
/** Pure inputs to the owner-principal decision (session only, no I/O). */
|
|
23
37
|
export interface OwnerPrincipalInput {
|
|
@@ -34,7 +48,7 @@ export interface OwnerPrincipalInput {
|
|
|
34
48
|
*/
|
|
35
49
|
export function decideIsOwner(input: OwnerPrincipalInput): boolean {
|
|
36
50
|
if (!input.authorized) return false;
|
|
37
|
-
return
|
|
51
|
+
return isOwnerCapability(input.viewerCapability);
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
/**
|
|
@@ -206,6 +206,7 @@ export async function serveControlPlane(input: ControlPlaneInput): Promise<Respo
|
|
|
206
206
|
: tenant.appDomain;
|
|
207
207
|
const hostCapability = sessionHostCapability(viewerRecord, capabilityHost, nowSeconds);
|
|
208
208
|
locals.viewer = {
|
|
209
|
+
...(viewerRecord.subject ? { subject: viewerRecord.subject } : {}),
|
|
209
210
|
email: viewerRecord.email,
|
|
210
211
|
roles: hostCapability ? [hostCapability] : viewerRecord.roles,
|
|
211
212
|
capability: hostCapability ?? viewerRecord.capability,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.22",
|
|
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",
|