@tokenoftrust/storefront-runner 4.7.8 → 4.7.9

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.
@@ -0,0 +1,134 @@
1
+ /**
2
+ * A ToT-staff principal ENTERING a store: the one audited act behind every staff
3
+ * cross-store admission (#8425 staff access).
4
+ *
5
+ * Shared by the staff console (`POST /api/dashboard/enter-vendor`) and the preview
6
+ * token mint (`POST /api/preview/token`, what `tot preview token` calls), so a staff
7
+ * CLI principal records exactly the selection the console does:
8
+ *
9
+ * 1. prove the staff actor to tot20 with a fresh storefront-signed broker assertion
10
+ * minted from the VERIFIED session email (no plaintext email on the wire);
11
+ * 2. tot20 re-checks staff scope, scopes the capability, and writes the AUDIT row —
12
+ * it is the authority, and nothing is admitted without its ok;
13
+ * 3. stamp the short-lived {@link StaffSelection} on the session (never a `tenants[]`
14
+ * entry), so re-entering after it lapses re-audits;
15
+ * 4. hand every exit's outcome to the caller's `emit`, which records it as
16
+ * `dashboard.vendor_entered` against its opaque admin actor id (the email is
17
+ * never stored).
18
+ */
19
+ import type { SessionRecord, SessionStore, StaffSelection } from "@/lib/access/auth/session";
20
+ import { updateSession } from "@/lib/access/auth/session";
21
+ import { isStaffSession, buildStaffSelection } from "./staffAdmission";
22
+ import {
23
+ createStaffVendorAccessClient,
24
+ staffAccessHttpStatus,
25
+ StaffVendorAccessClientError,
26
+ } from "./staffVendorAccess";
27
+ import { mintBrokerAssertion, BrokerAssertionError } from "@/lib/access/auth/brokerAssertion";
28
+ /** One `dashboard.vendor_entered` outcome, for the caller to record against its actor. */
29
+ export interface StaffEntryEvent {
30
+ outcome: { status: "succeeded" | "failed" | "refused"; errorClass?: string };
31
+ scope: { tenantId: string };
32
+ payload: { args: { resource: string; capability?: string } };
33
+ }
34
+
35
+ export interface StaffStoreEntryInput {
36
+ /** The verified viewer session. Must carry ToT-staff scope. */
37
+ session: SessionRecord;
38
+ /** The store to enter, by appDomain. */
39
+ vendorAppDomain: string;
40
+ /** Records each entry outcome; the route binds it to its opaque admin actor. */
41
+ emit: (event: StaffEntryEvent) => void;
42
+ /** The persisted session to stamp the selection on; null when there is no store. */
43
+ sessionHandle: () => Promise<{ store: SessionStore; id: string } | null>;
44
+ }
45
+
46
+ export type StaffStoreEntryResult =
47
+ | { ok: true; selection: StaffSelection; capability: string }
48
+ | {
49
+ ok: false;
50
+ status: number;
51
+ body: { error: string; reason?: string; code?: string };
52
+ };
53
+
54
+ /** Enter `vendorAppDomain` as ToT staff: audit with tot20, then persist the selection. */
55
+ export async function enterStoreAsStaff(input: StaffStoreEntryInput): Promise<StaffStoreEntryResult> {
56
+ const { session, emit } = input;
57
+ const vendorAppDomain = input.vendorAppDomain.trim();
58
+ if (!isStaffSession(session)) {
59
+ return { ok: false, status: 403, body: { error: "staff access required", reason: "notStaff" } };
60
+ }
61
+
62
+ let client;
63
+ try {
64
+ client = await createStaffVendorAccessClient();
65
+ } catch (err) {
66
+ if (err instanceof StaffVendorAccessClientError) {
67
+ return { ok: false, status: 503, body: { error: "staff-access integration not configured" } };
68
+ }
69
+ throw err;
70
+ }
71
+
72
+ let brokerAssertion: string;
73
+ try {
74
+ brokerAssertion = await mintBrokerAssertion(session.email);
75
+ } catch (err) {
76
+ if (err instanceof BrokerAssertionError) {
77
+ return { ok: false, status: 503, body: { error: "staff-proof signing not configured" } };
78
+ }
79
+ throw err;
80
+ }
81
+
82
+ const result = await client.grantStaffAccess(brokerAssertion, vendorAppDomain);
83
+ if (!result.ok) {
84
+ emit({
85
+ outcome: { status: "refused", errorClass: result.reason ?? `http_${result.status}` },
86
+ scope: { tenantId: vendorAppDomain },
87
+ payload: { args: { resource: vendorAppDomain } },
88
+ });
89
+ return {
90
+ ok: false,
91
+ status: staffAccessHttpStatus(result.reason, result.status),
92
+ body: { error: result.message, reason: result.reason, code: result.code },
93
+ };
94
+ }
95
+
96
+ const nowSeconds = Math.floor(Date.now() / 1000);
97
+ const selection = buildStaffSelection({
98
+ resource: vendorAppDomain,
99
+ capability: result.capability,
100
+ nowSeconds,
101
+ });
102
+ const handle = await input.sessionHandle();
103
+ if (!handle) {
104
+ // The audit row is written but the marker cannot persist (no SESSION KV / no
105
+ // cookie): fail closed so the caller is never told it is admitted.
106
+ emit({
107
+ outcome: { status: "failed", errorClass: "session_store_unavailable" },
108
+ scope: { tenantId: vendorAppDomain },
109
+ payload: { args: { resource: vendorAppDomain, capability: result.capability } },
110
+ });
111
+ return { ok: false, status: 503, body: { error: "session store unavailable; cannot persist admission" } };
112
+ }
113
+ const updated = await updateSession(
114
+ handle.store,
115
+ handle.id,
116
+ (rec) => ({ ...rec, staffSelection: selection }),
117
+ nowSeconds,
118
+ );
119
+ if (!updated) {
120
+ emit({
121
+ outcome: { status: "failed", errorClass: "session_expired" },
122
+ scope: { tenantId: vendorAppDomain },
123
+ payload: { args: { resource: vendorAppDomain, capability: result.capability } },
124
+ });
125
+ return { ok: false, status: 401, body: { error: "session expired; sign in again" } };
126
+ }
127
+
128
+ emit({
129
+ outcome: { status: "succeeded" },
130
+ scope: { tenantId: vendorAppDomain },
131
+ payload: { args: { resource: vendorAppDomain, capability: result.capability } },
132
+ });
133
+ return { ok: true, selection, capability: result.capability };
134
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "4.7.8",
3
+ "version": "4.7.9",
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",