@tokenoftrust/storefront-runner 2.0.11 → 2.0.12
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,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "Delegate ship-on-behalf" gate on /dashboard/<appDomain>/team, as a PURE
|
|
3
|
+
* decision — who sees the control, and what everyone else is told instead.
|
|
4
|
+
*
|
|
5
|
+
* The gate itself is unchanged and stays OWNER-ONLY: `/api/grants/principal`
|
|
6
|
+
* refuses anything but a real owner (mirroring tot20's `ownsTenant`), so showing
|
|
7
|
+
* the control to an admin would only ever produce a 403. What changes here is
|
|
8
|
+
* that every OTHER viewer now gets an explicit sentence naming their actual role,
|
|
9
|
+
* instead of the section vanishing with no explanation — a hidden control is
|
|
10
|
+
* indistinguishable from a broken one, and that ambiguity has already cost a
|
|
11
|
+
* live debugging session.
|
|
12
|
+
*
|
|
13
|
+
* Why staff get a REFUSAL and not a bypass: `ship-on-behalf` is a grant-required
|
|
14
|
+
* operation under `guidance://access-authority` — it is not required to keep the
|
|
15
|
+
* store's service running, so it runs on owner consent even when an admin or ToT
|
|
16
|
+
* staff performs it. Staff capture that consent and issue the grant with their
|
|
17
|
+
* own attestation on the record; they never self-serve it from a staff boolean.
|
|
18
|
+
* Do NOT "fix" the staff state by widening {@link resolveDelegateGate} to admit
|
|
19
|
+
* `mode: "staff"` — that is the confused-deputy hole the model exists to close.
|
|
20
|
+
*
|
|
21
|
+
* PURE (no I/O, no Astro) so all four states are unit-testable without rendering
|
|
22
|
+
* the page, matching `@/lib/dashboard/tenantSelection` and `@/lib/auth/loginGate`.
|
|
23
|
+
*/
|
|
24
|
+
import { membershipFor, staffAdmits } from "@/lib/dashboard/tenantSelection";
|
|
25
|
+
import type { StaffAdmission, TenantMembership } from "@/lib/dashboard/tenantSelection";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* What the delegate section renders for this viewer.
|
|
29
|
+
*
|
|
30
|
+
* - `owner` — a real `tenants[]` membership with coarse role `owner`. The only
|
|
31
|
+
* state that renders the control.
|
|
32
|
+
* - `member` — a real membership that is not owner (`admin`, `member`, …).
|
|
33
|
+
* - `staff` — no real membership; admitted by a LIVE ToT-staff vendor selection.
|
|
34
|
+
* `role` is the selection's capability, not a membership role.
|
|
35
|
+
* - `none` — neither. Unreachable from team.astro (its `membershipFor` guard
|
|
36
|
+
* redirects to /dashboard first), but represented so the function is total and
|
|
37
|
+
* no input can ever produce a silently blank section.
|
|
38
|
+
*/
|
|
39
|
+
export type DelegateGate =
|
|
40
|
+
| { mode: "owner" }
|
|
41
|
+
| { mode: "member"; role: string; explanation: string }
|
|
42
|
+
| { mode: "staff"; role: string; explanation: string }
|
|
43
|
+
| { mode: "none"; role: null; explanation: string };
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the delegate gate for a viewer.
|
|
47
|
+
*
|
|
48
|
+
* Order matters and mirrors {@link membershipFor}: a REAL membership always wins
|
|
49
|
+
* over a staff selection, so a ToT staffer who is also a genuine owner of this
|
|
50
|
+
* store is treated as the owner they are. Only a viewer with no membership at all
|
|
51
|
+
* falls through to the staff branch.
|
|
52
|
+
*
|
|
53
|
+
* The role string is surfaced verbatim in the explanation on purpose. The coarse
|
|
54
|
+
* role reaching `tenants[]` is the end of a five-hop chain (tot20 `appSpec.users`
|
|
55
|
+
* → `collapseCapability` → `coarseRole` → the signed id_token → the storefront's
|
|
56
|
+
* `coerceRole`, which defaults anything unrecognised to `member`). Naming the
|
|
57
|
+
* value the page actually received turns "why is this hidden?" into a one-glance
|
|
58
|
+
* answer instead of a trace through both repos.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveDelegateGate(
|
|
61
|
+
tenants: readonly TenantMembership[] | undefined,
|
|
62
|
+
resource: string,
|
|
63
|
+
staff?: StaffAdmission,
|
|
64
|
+
): DelegateGate {
|
|
65
|
+
const target = (resource ?? "").trim();
|
|
66
|
+
const membership = membershipFor(tenants, target);
|
|
67
|
+
if (membership) {
|
|
68
|
+
if (membership.role === "owner") return { mode: "owner" };
|
|
69
|
+
return {
|
|
70
|
+
mode: "member",
|
|
71
|
+
role: membership.role,
|
|
72
|
+
explanation:
|
|
73
|
+
`Only the store owner can delegate ship-on-behalf. Your role on ${target} is ` +
|
|
74
|
+
`“${membership.role}”. Ask an owner to set up the delegation, or to make you one.`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (staffAdmits(staff, target)) {
|
|
79
|
+
const role = staff!.selection!.capability;
|
|
80
|
+
return {
|
|
81
|
+
mode: "staff",
|
|
82
|
+
role,
|
|
83
|
+
explanation:
|
|
84
|
+
`You’re here through Token of Trust staff access (“${role}”), not as a member of ` +
|
|
85
|
+
`${target}. Delegating ship-on-behalf is the owner’s own control, so it isn’t ` +
|
|
86
|
+
`offered here — capture the owner’s consent and issue the grant on their behalf instead.`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
mode: "none",
|
|
92
|
+
role: null,
|
|
93
|
+
explanation:
|
|
94
|
+
`You don’t hold a role on ${target}, so ship-on-behalf delegation isn’t available here.`,
|
|
95
|
+
};
|
|
96
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.12",
|
|
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",
|