@vantageos/cloud-identity 0.3.0 → 0.5.0
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/CHANGELOG.md +65 -0
- package/README.md +101 -1
- package/dist/human-path.d.ts +95 -0
- package/dist/human-path.d.ts.map +1 -0
- package/dist/human-path.js +117 -0
- package/dist/human-path.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/org-guard.d.ts +16 -0
- package/dist/org-guard.d.ts.map +1 -1
- package/dist/org-guard.js +7 -0
- package/dist/org-guard.js.map +1 -1
- package/dist/scope-filter.d.ts +54 -3
- package/dist/scope-filter.d.ts.map +1 -1
- package/dist/scope-filter.js +58 -5
- package/dist/scope-filter.js.map +1 -1
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,71 @@
|
|
|
3
3
|
All notable changes to `@vantageos/cloud-identity` are documented here.
|
|
4
4
|
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
5
|
|
|
6
|
+
## [0.5.0]
|
|
7
|
+
|
|
8
|
+
**Additive — grant-aware scope filter.** Closes the other pole of the
|
|
9
|
+
fail-closed defect class: 0.3.0/0.4.0 closed "a right granted by ABSENCE"
|
|
10
|
+
(missing `oauthCtx` silently passing everything); this closes "a right
|
|
11
|
+
REFUSED by absence" — a per-row grant (a named mission agent/pilot, a
|
|
12
|
+
mandate's `fulfilledBy`) that IS present on the row but that
|
|
13
|
+
`passesScopeFilter`/`scopeFilterList`/`scopeFilterGet` were structurally
|
|
14
|
+
blind to, because they only ever consulted `createdBy`/`namespace`.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `passesScopeFilter`, `scopeFilterList`, `scopeFilterGet` (`src/scope-filter.ts`)
|
|
19
|
+
now accept an optional `grantFields: readonly string[]` parameter, defaulting
|
|
20
|
+
to `[]`. Each entry names a property on the row that the CALLER declares as
|
|
21
|
+
a per-row grant — the field's runtime value is treated as a single identity
|
|
22
|
+
(`string`, equality match) or multiple identities (`string[]`, any-element
|
|
23
|
+
match) against `oauthCtx.fromAllowList`. The package never hardcodes which
|
|
24
|
+
fields are grants for which table; that declaration is caller-supplied data.
|
|
25
|
+
- **Fail-closed regression preserved byte-for-byte:** omitting `grantFields`
|
|
26
|
+
(or passing `[]`) reproduces 0.4.0 behaviour exactly — this is a pure
|
|
27
|
+
widening, no existing caller's behaviour changes without an explicit
|
|
28
|
+
opt-in declaration at its own call site.
|
|
29
|
+
|
|
30
|
+
## [0.4.0]
|
|
31
|
+
|
|
32
|
+
**Additive.** Two new primitives so one identity layer covers both deployment
|
|
33
|
+
shapes and the human sign-in path — no separate self-host layer, no change to
|
|
34
|
+
the Cloud/machine path. Serves Tau's `vantage-starter` self-host install and
|
|
35
|
+
the Hephaistos human-path onboarding, both stalled on this surface.
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- **Named deployment mode** — `DeploymentMode` (`"cloud" | "self-host"`) and a
|
|
40
|
+
`{ kind: "self-host"; tenantId }` branch on `TenantSource` / `requireTenantId`
|
|
41
|
+
(`src/org-guard.ts`). `cloud` keeps the existing fail-closed behaviour
|
|
42
|
+
unchanged (org/workspace required on every request — invariant #1123 holds,
|
|
43
|
+
0.4.0 reopens nothing). `self-host` is single-tenant: `requireTenantId`
|
|
44
|
+
returns the configured `tenantId` only when it is a non-empty string, and
|
|
45
|
+
**throws** otherwise — the tenant is presented explicitly by configuration,
|
|
46
|
+
never inferred from the absence of an organization.
|
|
47
|
+
- **Human-path resolver** — `normalizeVerifiedHumanSession()` (`src/human-path.ts`,
|
|
48
|
+
new `./human-path` subpath export) maps an already-*verified*, framework-agnostic
|
|
49
|
+
Clerk-shaped session (`{ orgId, userId, orgRole }`) to
|
|
50
|
+
`{ tenant, subject, role }`. `role` is a new union `HumanAccountRole`
|
|
51
|
+
(`owner | admin | member | client`), distinct from `workspaceRoleSchema`
|
|
52
|
+
(`Admin | Editor | Viewer`) and never conflated. No Clerk SDK is imported.
|
|
53
|
+
Refuses (throws) on no session, no org, no user id, or an unrecognized/absent
|
|
54
|
+
org role — presented, never inferred. **Naming fix, still pre-publish:** this
|
|
55
|
+
function was originally named `resolveHumanIdentity`, which invited passing
|
|
56
|
+
in an unverified, client-supplied object (e.g. `req.body`) and would make
|
|
57
|
+
that object the trusted source of tenant/subject/role — a
|
|
58
|
+
privilege-escalation risk. Renamed to `normalizeVerifiedHumanSession` before
|
|
59
|
+
0.4.0 reaches npm (current published latest is 0.3.0) to make the
|
|
60
|
+
already-verified precondition explicit at the call-site, mirroring
|
|
61
|
+
`decodeUnverifiedBearer`'s rename from `resolveBearer` in 0.2.0. Not a
|
|
62
|
+
breaking change — 0.4.0 has no npm consumer yet.
|
|
63
|
+
|
|
64
|
+
### Notes
|
|
65
|
+
|
|
66
|
+
- Purely additive: every 0.3.0 export (`passesScopeFilter`, `scopeFilterList`,
|
|
67
|
+
`scopeFilterGet`, `validateMasterBearer`, `getEffectiveTenantId`,
|
|
68
|
+
`decodeUnverifiedBearer`, `requireTenantId`'s `session`/`bearer` branches, …)
|
|
69
|
+
is behaviorally unchanged. Full suite 87/87, `tsc --noEmit` 0.
|
|
70
|
+
|
|
6
71
|
## [Unreleased] - 0.3.0
|
|
7
72
|
|
|
8
73
|
**BREAKING.** Closes two measured 0.2.0 defects — a right must never be
|
package/README.md
CHANGED
|
@@ -153,7 +153,64 @@ want to catch by type, and `BearerPayload`, the type returned by
|
|
|
153
153
|
- `SessionIdentity` — the human-path shape `requireTenantId` accepts under
|
|
154
154
|
`{ kind: "session", identity }` (see "Both caller paths, one contract").
|
|
155
155
|
- `TenantSource` — the discriminated union `requireTenantId` accepts:
|
|
156
|
-
`{ kind: "session", identity }
|
|
156
|
+
`{ kind: "session", identity }`, `{ kind: "bearer", context }`, or
|
|
157
|
+
`{ kind: "self-host", tenantId }`.
|
|
158
|
+
- `DeploymentMode` — the named `"cloud" | "self-host"` literal union (0.4.0).
|
|
159
|
+
|
|
160
|
+
**Deployment mode and the human path (0.4.0, additive)**
|
|
161
|
+
|
|
162
|
+
- `DeploymentMode` (`"cloud" | "self-host"`) names the two ways this package
|
|
163
|
+
can be run. `cloud` keeps `requireTenantId`'s existing session/bearer
|
|
164
|
+
behaviour exactly as-is — organization/workspace REQUIRED, fail-closed.
|
|
165
|
+
`self-host` is single-tenant: pass `{ kind: "self-host", tenantId }` to
|
|
166
|
+
`requireTenantId`, and it returns `tenantId` when it is a non-empty string.
|
|
167
|
+
The self-host tenant id must be DECLARED via configuration — if you select
|
|
168
|
+
self-host mode without configuring a tenant id, `requireTenantId` throws
|
|
169
|
+
rather than returning a default. A right is never granted by absence,
|
|
170
|
+
whichever mode you are in.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { requireTenantId } from "@vantageos/cloud-identity";
|
|
174
|
+
|
|
175
|
+
// self-host: the tenant id is configured explicitly, once, at startup.
|
|
176
|
+
requireTenantId({ kind: "self-host", tenantId: "the-one-tenant" });
|
|
177
|
+
// -> "the-one-tenant"
|
|
178
|
+
|
|
179
|
+
requireTenantId({ kind: "self-host", tenantId: undefined });
|
|
180
|
+
// -> throws: "No tenant id configured for self-host mode..."
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
- `normalizeVerifiedHumanSession(session)` — **normalizes, does not
|
|
184
|
+
authenticate.** Maps an already-*verified* (not merely well-shaped),
|
|
185
|
+
framework-agnostic Clerk session object (`{ orgId, userId, orgRole }`) to
|
|
186
|
+
`{ tenant, subject, role }`. This package never imports a Clerk SDK; the
|
|
187
|
+
caller MUST verify the session upstream (e.g. Clerk's server-side `auth()`)
|
|
188
|
+
and pass in the already-verified object — never an unverified,
|
|
189
|
+
client-supplied object such as `req.body`, which would make the caller's
|
|
190
|
+
input the trusted source of tenant/subject/role. Refuses (throws) when the
|
|
191
|
+
session has no organization, no user id, or an org role this package does
|
|
192
|
+
not recognize — never defaults silently; these are shape checks, not a
|
|
193
|
+
verification step.
|
|
194
|
+
- `humanAccountRoleSchema` (type `HumanAccountRole`) — the new
|
|
195
|
+
`"owner" | "admin" | "member" | "client"` role union returned by
|
|
196
|
+
`normalizeVerifiedHumanSession`. Distinct from `workspaceRoleSchema`
|
|
197
|
+
(`Admin | Editor | Viewer`) — one is an organization-account role, the
|
|
198
|
+
other is workspace membership; they are never conflated.
|
|
199
|
+
- `ClerkSessionLike` — the minimal input shape `normalizeVerifiedHumanSession` accepts:
|
|
200
|
+
`{ orgId?, userId?, orgRole? }`.
|
|
201
|
+
- `ResolvedHumanIdentity` — the `{ tenant, subject, role }` return type of
|
|
202
|
+
`normalizeVerifiedHumanSession`.
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { normalizeVerifiedHumanSession } from "@vantageos/cloud-identity";
|
|
206
|
+
|
|
207
|
+
normalizeVerifiedHumanSession({
|
|
208
|
+
orgId: "org_abc",
|
|
209
|
+
userId: "user_123",
|
|
210
|
+
orgRole: "org:admin",
|
|
211
|
+
});
|
|
212
|
+
// -> { tenant: "org_abc", subject: "user_123", role: "admin" }
|
|
213
|
+
```
|
|
157
214
|
|
|
158
215
|
## What this package does not do
|
|
159
216
|
|
|
@@ -172,6 +229,49 @@ Worth reading before you rely on it.
|
|
|
172
229
|
have already fetched. If fetching them was itself expensive or unsafe, filter
|
|
173
230
|
earlier, in your query.
|
|
174
231
|
|
|
232
|
+
## Upgrading to 0.4.0
|
|
233
|
+
|
|
234
|
+
**This release is purely additive — nothing you already call changes.** Every
|
|
235
|
+
0.3.0 export keeps its exact behaviour; 0.4.0 only adds two primitives, so an
|
|
236
|
+
upgrade from 0.3.0 compiles and runs unchanged until you choose to adopt them.
|
|
237
|
+
|
|
238
|
+
What is new:
|
|
239
|
+
|
|
240
|
+
- **A named deployment mode.** `requireTenantId` gains a `self-host` source
|
|
241
|
+
alongside `session` and `bearer`. `cloud` is unchanged — an org-less
|
|
242
|
+
identity is still refused. `self-host` is single-tenant and returns the
|
|
243
|
+
tenant you **declare**; it throws when none is configured, never inferring
|
|
244
|
+
one from absence.
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
import { requireTenantId } from "@vantageos/cloud-identity";
|
|
248
|
+
|
|
249
|
+
// cloud (unchanged): refuses when the session has no org
|
|
250
|
+
requireTenantId({ kind: "session", identity });
|
|
251
|
+
// self-host: returns the DECLARED tenant, throws if it is empty
|
|
252
|
+
requireTenantId({ kind: "self-host", tenantId: process.env.TENANT_ID });
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
- **A human-path normalizer.** `normalizeVerifiedHumanSession` maps an
|
|
256
|
+
**already-verified** Clerk-shaped session into `{ tenant, subject, role }`
|
|
257
|
+
(`role` ∈ `owner | admin | member | client`).
|
|
258
|
+
|
|
259
|
+
```js
|
|
260
|
+
import { normalizeVerifiedHumanSession } from "@vantageos/cloud-identity";
|
|
261
|
+
|
|
262
|
+
// session MUST already be verified upstream, e.g. Clerk auth() server-side:
|
|
263
|
+
const { tenant, subject, role } = normalizeVerifiedHumanSession(session);
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
⚠️ It **normalizes, it does not authenticate.** Passing an unverified,
|
|
267
|
+
client-controlled object (a `req.body`, a query payload) makes that object
|
|
268
|
+
the trusted source of tenant, subject and role — a privilege escalation.
|
|
269
|
+
Verify the session upstream first; only pass the object your provider
|
|
270
|
+
already verified. (Same discipline as `decodeUnverifiedBearer`.)
|
|
271
|
+
|
|
272
|
+
Nothing to change on upgrade, nothing removed. Adopt the new primitives when
|
|
273
|
+
you need them.
|
|
274
|
+
|
|
175
275
|
## Upgrading to 0.3.0
|
|
176
276
|
|
|
177
277
|
**This release changes a default, and it breaks compilation on purpose.**
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vantageos/cloud-identity 0.4.0 — Human-path resolver (Clerk → { tenant,
|
|
3
|
+
* subject, role }).
|
|
4
|
+
*
|
|
5
|
+
* Additive-only: does not import any Clerk SDK, and does not modify any
|
|
6
|
+
* existing export. The caller resolves a Clerk session (or any session
|
|
7
|
+
* provider with an equivalent shape) *outside* this package and passes in a
|
|
8
|
+
* plain, already-resolved object; this module only normalizes it into the
|
|
9
|
+
* package's contract.
|
|
10
|
+
*
|
|
11
|
+
* `role` is a NEW literal union (`owner | admin | member | client`),
|
|
12
|
+
* distinct from `workspaceRoleSchema` (`Admin | Editor | Viewer`) in
|
|
13
|
+
* `./tenancy-domain.ts` — the two are unrelated vocabularies (workspace
|
|
14
|
+
* membership role vs. organization-account role) and are never conflated.
|
|
15
|
+
*
|
|
16
|
+
* Consistent with the package's "a right is presented, never inferred from
|
|
17
|
+
* an absence" contract: a session with no organization refuses (throws),
|
|
18
|
+
* and an unrecognized/absent Clerk org role refuses (throws) rather than
|
|
19
|
+
* silently defaulting to a role.
|
|
20
|
+
*/
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
export declare const humanAccountRoleSchema: z.ZodEnum<{
|
|
23
|
+
owner: "owner";
|
|
24
|
+
admin: "admin";
|
|
25
|
+
member: "member";
|
|
26
|
+
client: "client";
|
|
27
|
+
}>;
|
|
28
|
+
export type HumanAccountRole = z.infer<typeof humanAccountRoleSchema>;
|
|
29
|
+
/**
|
|
30
|
+
* The minimal shape this package needs out of an already-resolved Clerk
|
|
31
|
+
* session (e.g. the object returned by Clerk's own `auth()` in a Next.js
|
|
32
|
+
* server context). No Clerk SDK type is imported — this is a structural
|
|
33
|
+
* shape the caller supplies directly.
|
|
34
|
+
*/
|
|
35
|
+
export type ClerkSessionLike = {
|
|
36
|
+
orgId?: string | null;
|
|
37
|
+
userId?: string | null;
|
|
38
|
+
orgRole?: string | null;
|
|
39
|
+
};
|
|
40
|
+
export type ResolvedHumanIdentity = {
|
|
41
|
+
tenant: string;
|
|
42
|
+
subject: string;
|
|
43
|
+
role: HumanAccountRole;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* @security ⚠️ DECODE/NORMALIZE-ONLY. NOT AUTHENTICATION. NOT VERIFICATION.
|
|
47
|
+
*
|
|
48
|
+
* This function does NOT authenticate a request and does NOT verify
|
|
49
|
+
* anything. It NORMALIZES a session that the caller has ALREADY verified
|
|
50
|
+
* upstream — e.g. via Clerk's own server-side `auth()` in a Next.js/Convex
|
|
51
|
+
* server context — into this package's `{ tenant, subject, role }` contract.
|
|
52
|
+
*
|
|
53
|
+
* Passing an unverified, client-supplied object here (for example
|
|
54
|
+
* `req.body`, a query-string payload, or anything else an attacker
|
|
55
|
+
* controls) makes THAT object the trusted source of `tenant`, `subject`,
|
|
56
|
+
* AND `role` — a privilege-escalation bug. The shape checks below (missing
|
|
57
|
+
* orgId/userId/orgRole) only guard against malformed input; they perform
|
|
58
|
+
* NO signature check, NO issuer check, NO session-validity check.
|
|
59
|
+
*
|
|
60
|
+
* Callers MUST verify the session upstream (Clerk `auth()` server-side, or
|
|
61
|
+
* an equivalent signed-session verifier) BEFORE calling this function. Only
|
|
62
|
+
* pass in the object Clerk itself already verified — never pass through an
|
|
63
|
+
* unverified request body.
|
|
64
|
+
*
|
|
65
|
+
* Resolves an already-verified, framework-agnostic Clerk session object into
|
|
66
|
+
* the package's `{ tenant, subject, role }` contract.
|
|
67
|
+
*
|
|
68
|
+
* - throws when `session` is null/undefined ("Unauthenticated: no
|
|
69
|
+
* session.") — same refusal message class as `requireTenantId`'s
|
|
70
|
+
* session branch, for consistent error handling across both resolvers.
|
|
71
|
+
* - throws when `session.orgId` is missing or empty ("No active
|
|
72
|
+
* organization on this session...") — human path is cloud fail-closed,
|
|
73
|
+
* same invariant as `requireTenantId`.
|
|
74
|
+
* - throws when `session.userId` is missing or empty.
|
|
75
|
+
* - throws when `session.orgRole` is missing, empty, or not a recognized
|
|
76
|
+
* Clerk org role (unknown roles are refused, never defaulted).
|
|
77
|
+
*
|
|
78
|
+
* Named to make the already-verified precondition explicit at the
|
|
79
|
+
* call-site — see CHANGELOG.md 0.4.0 for the naming history — mirroring
|
|
80
|
+
* `decodeUnverifiedBearer`'s equivalent rename in `./tenancy-domain.ts`.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* // session MUST already be verified upstream, e.g.:
|
|
85
|
+
* // const { orgId, userId, orgRole } = await auth(); // Clerk server-side
|
|
86
|
+
* const { tenant, subject, role } = normalizeVerifiedHumanSession({
|
|
87
|
+
* orgId: "org_abc",
|
|
88
|
+
* userId: "user_123",
|
|
89
|
+
* orgRole: "org:admin",
|
|
90
|
+
* });
|
|
91
|
+
* // -> { tenant: "org_abc", subject: "user_123", role: "admin" }
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function normalizeVerifiedHumanSession(session: ClerkSessionLike | null | undefined): ResolvedHumanIdentity;
|
|
95
|
+
//# sourceMappingURL=human-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"human-path.d.ts","sourceRoot":"","sources":["../src/human-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,sBAAsB;;;;;EAKjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAMtE;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAMF,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,gBAAgB,CAAC;CACxB,CAAC;AAoBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAC3C,qBAAqB,CAiCvB"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vantageos/cloud-identity 0.4.0 — Human-path resolver (Clerk → { tenant,
|
|
3
|
+
* subject, role }).
|
|
4
|
+
*
|
|
5
|
+
* Additive-only: does not import any Clerk SDK, and does not modify any
|
|
6
|
+
* existing export. The caller resolves a Clerk session (or any session
|
|
7
|
+
* provider with an equivalent shape) *outside* this package and passes in a
|
|
8
|
+
* plain, already-resolved object; this module only normalizes it into the
|
|
9
|
+
* package's contract.
|
|
10
|
+
*
|
|
11
|
+
* `role` is a NEW literal union (`owner | admin | member | client`),
|
|
12
|
+
* distinct from `workspaceRoleSchema` (`Admin | Editor | Viewer`) in
|
|
13
|
+
* `./tenancy-domain.ts` — the two are unrelated vocabularies (workspace
|
|
14
|
+
* membership role vs. organization-account role) and are never conflated.
|
|
15
|
+
*
|
|
16
|
+
* Consistent with the package's "a right is presented, never inferred from
|
|
17
|
+
* an absence" contract: a session with no organization refuses (throws),
|
|
18
|
+
* and an unrecognized/absent Clerk org role refuses (throws) rather than
|
|
19
|
+
* silently defaulting to a role.
|
|
20
|
+
*/
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// HumanAccountRole
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
export const humanAccountRoleSchema = z.enum([
|
|
26
|
+
"owner",
|
|
27
|
+
"admin",
|
|
28
|
+
"member",
|
|
29
|
+
"client",
|
|
30
|
+
]);
|
|
31
|
+
/**
|
|
32
|
+
* Maps Clerk's own org-role strings (`org:admin`, `org:member`, etc. — and
|
|
33
|
+
* their bare forms `admin`, `member`) onto the package's `HumanAccountRole`
|
|
34
|
+
* union. Deliberately explicit and exhaustive-by-throw: any string not
|
|
35
|
+
* listed here is refused rather than silently coerced to a default, per
|
|
36
|
+
* "presented, never inferred from an absence."
|
|
37
|
+
*/
|
|
38
|
+
const CLERK_ORG_ROLE_MAP = {
|
|
39
|
+
"org:owner": "owner",
|
|
40
|
+
owner: "owner",
|
|
41
|
+
"org:admin": "admin",
|
|
42
|
+
admin: "admin",
|
|
43
|
+
"org:member": "member",
|
|
44
|
+
member: "member",
|
|
45
|
+
"org:client": "client",
|
|
46
|
+
client: "client",
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* @security ⚠️ DECODE/NORMALIZE-ONLY. NOT AUTHENTICATION. NOT VERIFICATION.
|
|
50
|
+
*
|
|
51
|
+
* This function does NOT authenticate a request and does NOT verify
|
|
52
|
+
* anything. It NORMALIZES a session that the caller has ALREADY verified
|
|
53
|
+
* upstream — e.g. via Clerk's own server-side `auth()` in a Next.js/Convex
|
|
54
|
+
* server context — into this package's `{ tenant, subject, role }` contract.
|
|
55
|
+
*
|
|
56
|
+
* Passing an unverified, client-supplied object here (for example
|
|
57
|
+
* `req.body`, a query-string payload, or anything else an attacker
|
|
58
|
+
* controls) makes THAT object the trusted source of `tenant`, `subject`,
|
|
59
|
+
* AND `role` — a privilege-escalation bug. The shape checks below (missing
|
|
60
|
+
* orgId/userId/orgRole) only guard against malformed input; they perform
|
|
61
|
+
* NO signature check, NO issuer check, NO session-validity check.
|
|
62
|
+
*
|
|
63
|
+
* Callers MUST verify the session upstream (Clerk `auth()` server-side, or
|
|
64
|
+
* an equivalent signed-session verifier) BEFORE calling this function. Only
|
|
65
|
+
* pass in the object Clerk itself already verified — never pass through an
|
|
66
|
+
* unverified request body.
|
|
67
|
+
*
|
|
68
|
+
* Resolves an already-verified, framework-agnostic Clerk session object into
|
|
69
|
+
* the package's `{ tenant, subject, role }` contract.
|
|
70
|
+
*
|
|
71
|
+
* - throws when `session` is null/undefined ("Unauthenticated: no
|
|
72
|
+
* session.") — same refusal message class as `requireTenantId`'s
|
|
73
|
+
* session branch, for consistent error handling across both resolvers.
|
|
74
|
+
* - throws when `session.orgId` is missing or empty ("No active
|
|
75
|
+
* organization on this session...") — human path is cloud fail-closed,
|
|
76
|
+
* same invariant as `requireTenantId`.
|
|
77
|
+
* - throws when `session.userId` is missing or empty.
|
|
78
|
+
* - throws when `session.orgRole` is missing, empty, or not a recognized
|
|
79
|
+
* Clerk org role (unknown roles are refused, never defaulted).
|
|
80
|
+
*
|
|
81
|
+
* Named to make the already-verified precondition explicit at the
|
|
82
|
+
* call-site — see CHANGELOG.md 0.4.0 for the naming history — mirroring
|
|
83
|
+
* `decodeUnverifiedBearer`'s equivalent rename in `./tenancy-domain.ts`.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* // session MUST already be verified upstream, e.g.:
|
|
88
|
+
* // const { orgId, userId, orgRole } = await auth(); // Clerk server-side
|
|
89
|
+
* const { tenant, subject, role } = normalizeVerifiedHumanSession({
|
|
90
|
+
* orgId: "org_abc",
|
|
91
|
+
* userId: "user_123",
|
|
92
|
+
* orgRole: "org:admin",
|
|
93
|
+
* });
|
|
94
|
+
* // -> { tenant: "org_abc", subject: "user_123", role: "admin" }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function normalizeVerifiedHumanSession(session) {
|
|
98
|
+
if (!session) {
|
|
99
|
+
throw new Error("Unauthenticated: no session.");
|
|
100
|
+
}
|
|
101
|
+
const { orgId, userId, orgRole } = session;
|
|
102
|
+
if (typeof orgId !== "string" || orgId.length === 0) {
|
|
103
|
+
throw new Error("No active organization on this session: select an organization before accessing agents.");
|
|
104
|
+
}
|
|
105
|
+
if (typeof userId !== "string" || userId.length === 0) {
|
|
106
|
+
throw new Error("No user id on this session: cannot resolve subject.");
|
|
107
|
+
}
|
|
108
|
+
if (typeof orgRole !== "string" || orgRole.length === 0) {
|
|
109
|
+
throw new Error("No organization role on this session: role must be presented explicitly, never inferred.");
|
|
110
|
+
}
|
|
111
|
+
const mapped = CLERK_ORG_ROLE_MAP[orgRole];
|
|
112
|
+
if (!mapped) {
|
|
113
|
+
throw new Error(`Unrecognized organization role "${orgRole}": expected one of ${Object.keys(CLERK_ORG_ROLE_MAP).join(", ")}.`);
|
|
114
|
+
}
|
|
115
|
+
return { tenant: orgId, subject: userId, role: mapped };
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=human-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"human-path.js","sourceRoot":"","sources":["../src/human-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC;IAC3C,OAAO;IACP,OAAO;IACP,QAAQ;IACR,QAAQ;CACT,CAAC,CAAC;AA6BH;;;;;;GAMG;AACH,MAAM,kBAAkB,GAAqC;IAC3D,WAAW,EAAE,OAAO;IACpB,KAAK,EAAE,OAAO;IACd,WAAW,EAAE,OAAO;IACpB,KAAK,EAAE,OAAO;IACd,YAAY,EAAE,QAAQ;IACtB,MAAM,EAAE,QAAQ;IAChB,YAAY,EAAE,QAAQ;IACtB,MAAM,EAAE,QAAQ;CACjB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,UAAU,6BAA6B,CAC3C,OAA4C;IAE5C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAE3C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CACb,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,mCAAmC,OAAO,sBAAsB,MAAM,CAAC,IAAI,CACzE,kBAAkB,CACnB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAChB,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1D,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,5 +12,7 @@ export type { OAuthCtx, ScopeProfile, NamespacePrefix, FromAllowListEntry, Valid
|
|
|
12
12
|
export { workspaceSchema, workspaceMemberSchema, workspaceRoleSchema, tenantContextSchema, ScopeViolationError, getEffectiveTenantId, decodeUnverifiedBearer, } from "./tenancy-domain.js";
|
|
13
13
|
export type { Workspace, WorkspaceMember, WorkspaceRole, TenantContext, ScopeViolationPayload, BearerPayload, } from "./tenancy-domain.js";
|
|
14
14
|
export { requireTenantId } from "./org-guard.js";
|
|
15
|
-
export type { SessionIdentity, TenantSource } from "./org-guard.js";
|
|
15
|
+
export type { SessionIdentity, TenantSource, DeploymentMode, } from "./org-guard.js";
|
|
16
|
+
export { humanAccountRoleSchema, normalizeVerifiedHumanSession } from "./human-path.js";
|
|
17
|
+
export type { HumanAccountRole, ClerkSessionLike, ResolvedHumanIdentity, } from "./human-path.js";
|
|
16
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,SAAS,EACT,eAAe,EACf,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,SAAS,EACT,eAAe,EACf,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EACV,eAAe,EACf,YAAY,EACZ,cAAc,GACf,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AACxF,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,8 @@ export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWi
|
|
|
10
10
|
export { validateMasterBearer } from "./bearer-validation.js";
|
|
11
11
|
// 0.2.0 — Domain-tenancy layer (additive)
|
|
12
12
|
export { workspaceSchema, workspaceMemberSchema, workspaceRoleSchema, tenantContextSchema, ScopeViolationError, getEffectiveTenantId, decodeUnverifiedBearer, } from "./tenancy-domain.js";
|
|
13
|
-
// Organization-membership guard (unified session + bearer contract)
|
|
13
|
+
// Organization-membership guard (unified session + bearer + self-host contract)
|
|
14
14
|
export { requireTenantId } from "./org-guard.js";
|
|
15
|
+
// 0.4.0 — Human-path resolver (Clerk-shaped session -> { tenant, subject, role })
|
|
16
|
+
export { humanAccountRoleSchema, normalizeVerifiedHumanSession } from "./human-path.js";
|
|
15
17
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,mBAAmB,GAEpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAS9D,0CAA0C;AAC1C,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAU7B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,aAAa,EACb,eAAe,EACf,mBAAmB,GAEpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAS9D,0CAA0C;AAC1C,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAU7B,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAOjD,kFAAkF;AAClF,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/org-guard.d.ts
CHANGED
|
@@ -44,6 +44,15 @@ import type { TenantContext } from "./tenancy-domain.js";
|
|
|
44
44
|
export type SessionIdentity = {
|
|
45
45
|
orgId?: string | null;
|
|
46
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Named deployment mode — 0.4.0. `cloud` requires an organization/workspace
|
|
49
|
+
* on every request (the existing, unchanged `session`/`bearer` behaviour
|
|
50
|
+
* below). `self-host` is single-tenant: the tenant id is DECLARED via
|
|
51
|
+
* explicit configuration, never inferred from the absence of an
|
|
52
|
+
* organization. See the `self-host` branch of `TenantSource` and
|
|
53
|
+
* `requireTenantId` for the refuse-on-missing-config semantics.
|
|
54
|
+
*/
|
|
55
|
+
export type DeploymentMode = "cloud" | "self-host";
|
|
47
56
|
/**
|
|
48
57
|
* Discriminated union covering both VantagePeers Cloud entry paths behind
|
|
49
58
|
* one contract:
|
|
@@ -51,6 +60,10 @@ export type SessionIdentity = {
|
|
|
51
60
|
* - `bearer`: machine path, via a resolved, ALREADY-VERIFIED token
|
|
52
61
|
* (`TenantContext` — see `decodeUnverifiedBearer` / the planned signed-JWT
|
|
53
62
|
* verifier for how a `TenantContext` gets produced).
|
|
63
|
+
* - `self-host`: single-tenant path (0.4.0, additive). The tenant id is
|
|
64
|
+
* presented explicitly by configuration, not inferred: `requireTenantId`
|
|
65
|
+
* returns `tenantId` only when it is a non-empty string, and throws
|
|
66
|
+
* otherwise — a right is never granted by absence, here either.
|
|
54
67
|
*/
|
|
55
68
|
export type TenantSource = {
|
|
56
69
|
kind: "session";
|
|
@@ -58,6 +71,9 @@ export type TenantSource = {
|
|
|
58
71
|
} | {
|
|
59
72
|
kind: "bearer";
|
|
60
73
|
context: TenantContext;
|
|
74
|
+
} | {
|
|
75
|
+
kind: "self-host";
|
|
76
|
+
tenantId: string | null | undefined;
|
|
61
77
|
};
|
|
62
78
|
/**
|
|
63
79
|
* Resolve the caller's tenant id, refusing when no organization/workspace is
|
package/dist/org-guard.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"org-guard.d.ts","sourceRoot":"","sources":["../src/org-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,CAAC;AAE/
|
|
1
|
+
{"version":3,"file":"org-guard.d.ts","sourceRoot":"","sources":["../src/org-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,WAAW,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,CAAC;AAE/D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CA+B5D"}
|
package/dist/org-guard.js
CHANGED
|
@@ -61,6 +61,13 @@ export function requireTenantId(source) {
|
|
|
61
61
|
}
|
|
62
62
|
return orgId;
|
|
63
63
|
}
|
|
64
|
+
if (source.kind === "self-host") {
|
|
65
|
+
const tenantId = source.tenantId;
|
|
66
|
+
if (typeof tenantId !== "string" || tenantId.length === 0) {
|
|
67
|
+
throw new Error("No tenant id configured for self-host mode: declare a tenantId explicitly — it is never inferred from absence.");
|
|
68
|
+
}
|
|
69
|
+
return tenantId;
|
|
70
|
+
}
|
|
64
71
|
const workspaceId = source.context?.workspaceId;
|
|
65
72
|
if (typeof workspaceId !== "string" || workspaceId.length === 0) {
|
|
66
73
|
throw new Error("No workspace on this bearer-resolved tenant context: the bearer must resolve to a non-empty workspaceId.");
|
package/dist/org-guard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"org-guard.js","sourceRoot":"","sources":["../src/org-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;
|
|
1
|
+
{"version":3,"file":"org-guard.js","sourceRoot":"","sources":["../src/org-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAyCH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAAC,MAAoB;IAClD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAClD,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QACjC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CACb,gHAAgH,CACjH,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC;IAChD,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CACb,0GAA0G,CAC3G,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
package/dist/scope-filter.d.ts
CHANGED
|
@@ -5,6 +5,31 @@
|
|
|
5
5
|
* implementation; the only change was replacing a framework-specific context
|
|
6
6
|
* type with the framework-agnostic `OAuthCtx` from `./types`.
|
|
7
7
|
*
|
|
8
|
+
* Contract (0.5.0 — additive, widening only, vs. 0.4.0):
|
|
9
|
+
* - Every exported predicate/filter now accepts an OPTIONAL `grantFields`
|
|
10
|
+
* parameter: `readonly string[]`, defaulting to `[]`. Each entry names a
|
|
11
|
+
* property on the row that the CALLER declares as a per-row grant (e.g.
|
|
12
|
+
* "agents", "pilot", "fulfilledBy"). The package never hardcodes which
|
|
13
|
+
* fields are grants for which table — that declaration is data supplied
|
|
14
|
+
* by the caller at the call site, not business knowledge baked into this
|
|
15
|
+
* shared module (no-hardcoded-business-knowledge).
|
|
16
|
+
* - A row passes for a non-master caller if createdBy/namespace already
|
|
17
|
+
* passed (0.4.0 behaviour, unchanged) OR if ANY declared grant field's
|
|
18
|
+
* value names an identity in `oauthCtx.fromAllowList` — the field may be
|
|
19
|
+
* a single string (e.g. "pilot") or a string[] (e.g. "agents"): a string
|
|
20
|
+
* value matches by equality, an array value matches if any element is in
|
|
21
|
+
* `fromAllowList`.
|
|
22
|
+
* - `grantFields` defaulting to `[]` means a caller that declares NO grants
|
|
23
|
+
* gets EXACTLY the 0.4.0 behaviour, byte-for-byte — this is a pure
|
|
24
|
+
* widening, no existing caller's behaviour changes without an explicit
|
|
25
|
+
* opt-in declaration at its own call site.
|
|
26
|
+
* - This closes the OTHER pole of the fail-closed defect: 0.3.0/0.4.0
|
|
27
|
+
* closed "a right granted by ABSENCE" (missing oauthCtx silently passing
|
|
28
|
+
* everything); this closes "a right REFUSED by absence" — a grant that
|
|
29
|
+
* IS present on the row (a named agent, pilot, or fulfiller) but that the
|
|
30
|
+
* filter was structurally blind to because it only ever consulted
|
|
31
|
+
* createdBy/namespace.
|
|
32
|
+
*
|
|
8
33
|
* Contract (0.3.0 — BREAKING vs. 0.2.0):
|
|
9
34
|
* - `oauthCtx` is now MANDATORY. A right is never granted by absence: the
|
|
10
35
|
* 0.2.0 behaviour of `oauthCtx === undefined` silently passing everything
|
|
@@ -50,6 +75,18 @@ export type ScopeFilterable = {
|
|
|
50
75
|
* `fromAllowList` contains the `"*"` sentinel.
|
|
51
76
|
*/
|
|
52
77
|
export declare function isMasterScope(ctx: OAuthCtx): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Names of row properties the CALLER declares as per-row grants. Each named
|
|
80
|
+
* property's runtime value must be a `string` (single identity) or
|
|
81
|
+
* `string[]` (multiple identities) to be consulted — any other runtime shape
|
|
82
|
+
* is silently skipped (not a match), never thrown, since these are duck-typed
|
|
83
|
+
* against arbitrary Convex documents.
|
|
84
|
+
*
|
|
85
|
+
* This is DATA supplied by the call site, never a list hardcoded inside this
|
|
86
|
+
* package: the package does not and must not know that "agents" means
|
|
87
|
+
* missions or "fulfilledBy" means mandates.
|
|
88
|
+
*/
|
|
89
|
+
export type GrantFieldDeclaration = readonly string[];
|
|
53
90
|
/**
|
|
54
91
|
* Core predicate. Returns true when the row is visible to the caller.
|
|
55
92
|
*
|
|
@@ -57,6 +94,9 @@ export declare function isMasterScope(ctx: OAuthCtx): boolean;
|
|
|
57
94
|
* - row.createdBy ∈ oauthCtx.fromAllowList → true
|
|
58
95
|
* - row.namespace === prefix OR startsWith prefix+'/'
|
|
59
96
|
* for any prefix ∈ oauthCtx.namespaceReadPrefixes → true
|
|
97
|
+
* - row[grantField] (for any declared grantField) names an identity in
|
|
98
|
+
* oauthCtx.fromAllowList, where the field is a string (equality) or a
|
|
99
|
+
* string[] (any-element membership) → true
|
|
60
100
|
* - otherwise → false
|
|
61
101
|
*
|
|
62
102
|
* `oauthCtx` is mandatory (see module-level contract note above): an omitted
|
|
@@ -64,21 +104,32 @@ export declare function isMasterScope(ctx: OAuthCtx): boolean;
|
|
|
64
104
|
* need the old legacy-bearer wildcard behaviour must pass `LEGACY_WILDCARD_CTX`
|
|
65
105
|
* explicitly.
|
|
66
106
|
*
|
|
107
|
+
* `grantFields` (0.5.0, optional, defaults to `[]`) lets a caller widen the
|
|
108
|
+
* predicate for its own table without this package hardcoding which fields
|
|
109
|
+
* are grants — see the module-level 0.5.0 contract note above. Omitting it
|
|
110
|
+
* (or passing `[]`) reproduces 0.4.0 behaviour exactly.
|
|
111
|
+
*
|
|
67
112
|
* Substring matches that don't fall on a '/' boundary are explicitly rejected
|
|
68
113
|
* (e.g. namespace="orchestrator/alphabet" does NOT match prefix
|
|
69
114
|
* "orchestrator/alpha"). This avoids the classic prefix-isolation bypass.
|
|
70
115
|
*/
|
|
71
|
-
export declare function passesScopeFilter(oauthCtx: OAuthCtx, row:
|
|
116
|
+
export declare function passesScopeFilter<T extends ScopeFilterable>(oauthCtx: OAuthCtx, row: T, grantFields?: GrantFieldDeclaration): boolean;
|
|
72
117
|
/**
|
|
73
118
|
* Filter a list of rows (post-query). Used by list_* tools.
|
|
119
|
+
*
|
|
120
|
+
* `grantFields` — see `passesScopeFilter`. Defaults to `[]` (0.4.0-identical
|
|
121
|
+
* behaviour).
|
|
74
122
|
*/
|
|
75
|
-
export declare function scopeFilterList<T extends ScopeFilterable>(oauthCtx: OAuthCtx, rows: T[]): T[];
|
|
123
|
+
export declare function scopeFilterList<T extends ScopeFilterable>(oauthCtx: OAuthCtx, rows: T[], grantFields?: GrantFieldDeclaration): T[];
|
|
76
124
|
/**
|
|
77
125
|
* Assert a single row passes (get_* tools). Returns the row when allowed, null
|
|
78
126
|
* otherwise — callers translate null to a 404-equivalent "not found" MCP error
|
|
79
127
|
* to avoid leaking the difference between "absent" and "filtered out".
|
|
128
|
+
*
|
|
129
|
+
* `grantFields` — see `passesScopeFilter`. Defaults to `[]` (0.4.0-identical
|
|
130
|
+
* behaviour).
|
|
80
131
|
*/
|
|
81
|
-
export declare function scopeFilterGet<T extends ScopeFilterable>(oauthCtx: OAuthCtx, row: T | null | undefined): T | null;
|
|
132
|
+
export declare function scopeFilterGet<T extends ScopeFilterable>(oauthCtx: OAuthCtx, row: T | null | undefined, grantFields?: GrantFieldDeclaration): T | null;
|
|
82
133
|
/**
|
|
83
134
|
* Helper for callers that want a single "is master / legacy" predicate.
|
|
84
135
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-filter.d.ts","sourceRoot":"","sources":["../src/scope-filter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"scope-filter.d.ts","sourceRoot":"","sources":["../src/scope-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAKjC,CAAC;AAWF;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAIpD;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,qBAAqB,GAAG,SAAS,MAAM,EAAE,CAAC;AActD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,eAAe,EACzD,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,CAAC,EACN,WAAW,GAAE,qBAAuC,GACnD,OAAO,CAgBT;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,eAAe,EACvD,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,CAAC,EAAE,EACT,WAAW,GAAE,qBAAuC,GACnD,CAAC,EAAE,CAGL;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,eAAe,EACtD,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EACzB,WAAW,GAAE,qBAAuC,GACnD,CAAC,GAAG,IAAI,CAIV;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAGtD"}
|
package/dist/scope-filter.js
CHANGED
|
@@ -5,6 +5,31 @@
|
|
|
5
5
|
* implementation; the only change was replacing a framework-specific context
|
|
6
6
|
* type with the framework-agnostic `OAuthCtx` from `./types`.
|
|
7
7
|
*
|
|
8
|
+
* Contract (0.5.0 — additive, widening only, vs. 0.4.0):
|
|
9
|
+
* - Every exported predicate/filter now accepts an OPTIONAL `grantFields`
|
|
10
|
+
* parameter: `readonly string[]`, defaulting to `[]`. Each entry names a
|
|
11
|
+
* property on the row that the CALLER declares as a per-row grant (e.g.
|
|
12
|
+
* "agents", "pilot", "fulfilledBy"). The package never hardcodes which
|
|
13
|
+
* fields are grants for which table — that declaration is data supplied
|
|
14
|
+
* by the caller at the call site, not business knowledge baked into this
|
|
15
|
+
* shared module (no-hardcoded-business-knowledge).
|
|
16
|
+
* - A row passes for a non-master caller if createdBy/namespace already
|
|
17
|
+
* passed (0.4.0 behaviour, unchanged) OR if ANY declared grant field's
|
|
18
|
+
* value names an identity in `oauthCtx.fromAllowList` — the field may be
|
|
19
|
+
* a single string (e.g. "pilot") or a string[] (e.g. "agents"): a string
|
|
20
|
+
* value matches by equality, an array value matches if any element is in
|
|
21
|
+
* `fromAllowList`.
|
|
22
|
+
* - `grantFields` defaulting to `[]` means a caller that declares NO grants
|
|
23
|
+
* gets EXACTLY the 0.4.0 behaviour, byte-for-byte — this is a pure
|
|
24
|
+
* widening, no existing caller's behaviour changes without an explicit
|
|
25
|
+
* opt-in declaration at its own call site.
|
|
26
|
+
* - This closes the OTHER pole of the fail-closed defect: 0.3.0/0.4.0
|
|
27
|
+
* closed "a right granted by ABSENCE" (missing oauthCtx silently passing
|
|
28
|
+
* everything); this closes "a right REFUSED by absence" — a grant that
|
|
29
|
+
* IS present on the row (a named agent, pilot, or fulfiller) but that the
|
|
30
|
+
* filter was structurally blind to because it only ever consulted
|
|
31
|
+
* createdBy/namespace.
|
|
32
|
+
*
|
|
8
33
|
* Contract (0.3.0 — BREAKING vs. 0.2.0):
|
|
9
34
|
* - `oauthCtx` is now MANDATORY. A right is never granted by absence: the
|
|
10
35
|
* 0.2.0 behaviour of `oauthCtx === undefined` silently passing everything
|
|
@@ -57,6 +82,15 @@ export function isMasterScope(ctx) {
|
|
|
57
82
|
return true;
|
|
58
83
|
return ctx.fromAllowList.includes("*");
|
|
59
84
|
}
|
|
85
|
+
const NO_GRANT_FIELDS = [];
|
|
86
|
+
function grantFieldMatches(value, fromAllowList) {
|
|
87
|
+
if (typeof value === "string")
|
|
88
|
+
return fromAllowList.includes(value);
|
|
89
|
+
if (Array.isArray(value)) {
|
|
90
|
+
return value.some((entry) => typeof entry === "string" && fromAllowList.includes(entry));
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
60
94
|
/**
|
|
61
95
|
* Core predicate. Returns true when the row is visible to the caller.
|
|
62
96
|
*
|
|
@@ -64,6 +98,9 @@ export function isMasterScope(ctx) {
|
|
|
64
98
|
* - row.createdBy ∈ oauthCtx.fromAllowList → true
|
|
65
99
|
* - row.namespace === prefix OR startsWith prefix+'/'
|
|
66
100
|
* for any prefix ∈ oauthCtx.namespaceReadPrefixes → true
|
|
101
|
+
* - row[grantField] (for any declared grantField) names an identity in
|
|
102
|
+
* oauthCtx.fromAllowList, where the field is a string (equality) or a
|
|
103
|
+
* string[] (any-element membership) → true
|
|
67
104
|
* - otherwise → false
|
|
68
105
|
*
|
|
69
106
|
* `oauthCtx` is mandatory (see module-level contract note above): an omitted
|
|
@@ -71,11 +108,16 @@ export function isMasterScope(ctx) {
|
|
|
71
108
|
* need the old legacy-bearer wildcard behaviour must pass `LEGACY_WILDCARD_CTX`
|
|
72
109
|
* explicitly.
|
|
73
110
|
*
|
|
111
|
+
* `grantFields` (0.5.0, optional, defaults to `[]`) lets a caller widen the
|
|
112
|
+
* predicate for its own table without this package hardcoding which fields
|
|
113
|
+
* are grants — see the module-level 0.5.0 contract note above. Omitting it
|
|
114
|
+
* (or passing `[]`) reproduces 0.4.0 behaviour exactly.
|
|
115
|
+
*
|
|
74
116
|
* Substring matches that don't fall on a '/' boundary are explicitly rejected
|
|
75
117
|
* (e.g. namespace="orchestrator/alphabet" does NOT match prefix
|
|
76
118
|
* "orchestrator/alpha"). This avoids the classic prefix-isolation bypass.
|
|
77
119
|
*/
|
|
78
|
-
export function passesScopeFilter(oauthCtx, row) {
|
|
120
|
+
export function passesScopeFilter(oauthCtx, row, grantFields = NO_GRANT_FIELDS) {
|
|
79
121
|
assertOauthCtx(oauthCtx);
|
|
80
122
|
if (isMasterScope(oauthCtx))
|
|
81
123
|
return true;
|
|
@@ -90,25 +132,36 @@ export function passesScopeFilter(oauthCtx, row) {
|
|
|
90
132
|
return true;
|
|
91
133
|
}
|
|
92
134
|
}
|
|
135
|
+
for (const field of grantFields) {
|
|
136
|
+
const value = row[field];
|
|
137
|
+
if (grantFieldMatches(value, oauthCtx.fromAllowList))
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
93
140
|
return false;
|
|
94
141
|
}
|
|
95
142
|
/**
|
|
96
143
|
* Filter a list of rows (post-query). Used by list_* tools.
|
|
144
|
+
*
|
|
145
|
+
* `grantFields` — see `passesScopeFilter`. Defaults to `[]` (0.4.0-identical
|
|
146
|
+
* behaviour).
|
|
97
147
|
*/
|
|
98
|
-
export function scopeFilterList(oauthCtx, rows) {
|
|
148
|
+
export function scopeFilterList(oauthCtx, rows, grantFields = NO_GRANT_FIELDS) {
|
|
99
149
|
assertOauthCtx(oauthCtx);
|
|
100
|
-
return rows.filter((r) => passesScopeFilter(oauthCtx, r));
|
|
150
|
+
return rows.filter((r) => passesScopeFilter(oauthCtx, r, grantFields));
|
|
101
151
|
}
|
|
102
152
|
/**
|
|
103
153
|
* Assert a single row passes (get_* tools). Returns the row when allowed, null
|
|
104
154
|
* otherwise — callers translate null to a 404-equivalent "not found" MCP error
|
|
105
155
|
* to avoid leaking the difference between "absent" and "filtered out".
|
|
156
|
+
*
|
|
157
|
+
* `grantFields` — see `passesScopeFilter`. Defaults to `[]` (0.4.0-identical
|
|
158
|
+
* behaviour).
|
|
106
159
|
*/
|
|
107
|
-
export function scopeFilterGet(oauthCtx, row) {
|
|
160
|
+
export function scopeFilterGet(oauthCtx, row, grantFields = NO_GRANT_FIELDS) {
|
|
108
161
|
assertOauthCtx(oauthCtx);
|
|
109
162
|
if (row == null)
|
|
110
163
|
return null;
|
|
111
|
-
return passesScopeFilter(oauthCtx, row) ? row : null;
|
|
164
|
+
return passesScopeFilter(oauthCtx, row, grantFields) ? row : null;
|
|
112
165
|
}
|
|
113
166
|
/**
|
|
114
167
|
* Helper for callers that want a single "is master / legacy" predicate.
|
package/dist/scope-filter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-filter.js","sourceRoot":"","sources":["../src/scope-filter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"scope-filter.js","sourceRoot":"","sources":["../src/scope-filter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vantageos/cloud-identity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Framework-agnostic identity + scope-filter primitives for VantagePeers Cloud (multi-tenant MCP).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
"./tenancy-domain": {
|
|
30
30
|
"types": "./dist/tenancy-domain.d.ts",
|
|
31
31
|
"import": "./dist/tenancy-domain.js"
|
|
32
|
+
},
|
|
33
|
+
"./human-path": {
|
|
34
|
+
"types": "./dist/human-path.d.ts",
|
|
35
|
+
"import": "./dist/human-path.js"
|
|
32
36
|
}
|
|
33
37
|
},
|
|
34
38
|
"files": [
|