@vantageos/cloud-identity 0.4.0 → 0.6.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 +69 -0
- package/README.md +21 -0
- package/dist/human-path.d.ts +46 -0
- package/dist/human-path.d.ts.map +1 -1
- package/dist/human-path.js +63 -2
- package/dist/human-path.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/org-guard.d.ts +48 -0
- package/dist/org-guard.d.ts.map +1 -1
- package/dist/org-guard.js +48 -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 +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,75 @@
|
|
|
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.6.0]
|
|
7
|
+
|
|
8
|
+
**Additive — typed refusal for reads, and a role assertion.**
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `resolveTenantIdOrAbsent(source)` (`src/org-guard.ts`): non-throwing sibling
|
|
13
|
+
of `requireTenantId`. Returns `{ present: true, tenantId }` or a typed
|
|
14
|
+
`{ present: false, absence: { code: "TENANT_ABSENT", reason } }`. A read that
|
|
15
|
+
throws crashes a mounted render; a read that returns a bare empty value is
|
|
16
|
+
byte-identical to "there is nothing". This returns neither.
|
|
17
|
+
- `requireHumanRole(session, required)` and
|
|
18
|
+
`resolveHumanRoleOrRefusal(session, required)` (`src/human-path.ts`): role
|
|
19
|
+
assertion on the verified session's `orgRole`, throwing and non-throwing
|
|
20
|
+
forms. Exact match, no hierarchy; an empty required list admits nobody.
|
|
21
|
+
- Types: `TenantAbsence`, `TenantAbsenceReason`, `TenantResolution`,
|
|
22
|
+
`RequiredHumanRole`, `RoleRefusal`, `RoleRefusalReason`, `RoleResolution`.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- The Clerk org-role map is now prototype-less (`Object.create(null)`). As a
|
|
27
|
+
plain object literal it inherited `Object.prototype`, so a presented
|
|
28
|
+
`orgRole` of `toString`, `constructor`, `valueOf` or `__proto__` did not
|
|
29
|
+
MISS the lookup — it returned an inherited value, which is truthy, and the
|
|
30
|
+
resolver handed back a FUNCTION as the role instead of refusing. Any key
|
|
31
|
+
that is not one of the eight own keys is now `undefined`, so the existing
|
|
32
|
+
refusal path rejects it. Fixed at the data; no lookup expression and no
|
|
33
|
+
function body changed, and no list of forbidden names was introduced.
|
|
34
|
+
|
|
35
|
+
### Unchanged
|
|
36
|
+
|
|
37
|
+
- `requireTenantId` and `normalizeVerifiedHumanSession` are byte-for-byte
|
|
38
|
+
unchanged and still throw on every refusal.
|
|
39
|
+
- The tenant key stays opaque: no export takes or returns a slug. That
|
|
40
|
+
opacity is now pinned on all four served paths (session, bearer,
|
|
41
|
+
self-host, `role.identity.tenant`), through BOTH the throwing and the
|
|
42
|
+
non-throwing sibling, with a fixture carrying edge whitespace so that
|
|
43
|
+
splicing a `.trim()` onto a served id goes RED instead of passing.
|
|
44
|
+
|
|
45
|
+
### Removed
|
|
46
|
+
|
|
47
|
+
- `bun.lock`, committed by accident in 0.5.0. `package-lock.json` is the
|
|
48
|
+
lockfile of record; two lockfiles for two package managers guarantee a
|
|
49
|
+
silent divergence.
|
|
50
|
+
|
|
51
|
+
## [0.5.0]
|
|
52
|
+
|
|
53
|
+
**Additive — grant-aware scope filter.** Closes the other pole of the
|
|
54
|
+
fail-closed defect class: 0.3.0/0.4.0 closed "a right granted by ABSENCE"
|
|
55
|
+
(missing `oauthCtx` silently passing everything); this closes "a right
|
|
56
|
+
REFUSED by absence" — a per-row grant (a named mission agent/pilot, a
|
|
57
|
+
mandate's `fulfilledBy`) that IS present on the row but that
|
|
58
|
+
`passesScopeFilter`/`scopeFilterList`/`scopeFilterGet` were structurally
|
|
59
|
+
blind to, because they only ever consulted `createdBy`/`namespace`.
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- `passesScopeFilter`, `scopeFilterList`, `scopeFilterGet` (`src/scope-filter.ts`)
|
|
64
|
+
now accept an optional `grantFields: readonly string[]` parameter, defaulting
|
|
65
|
+
to `[]`. Each entry names a property on the row that the CALLER declares as
|
|
66
|
+
a per-row grant — the field's runtime value is treated as a single identity
|
|
67
|
+
(`string`, equality match) or multiple identities (`string[]`, any-element
|
|
68
|
+
match) against `oauthCtx.fromAllowList`. The package never hardcodes which
|
|
69
|
+
fields are grants for which table; that declaration is caller-supplied data.
|
|
70
|
+
- **Fail-closed regression preserved byte-for-byte:** omitting `grantFields`
|
|
71
|
+
(or passing `[]`) reproduces 0.4.0 behaviour exactly — this is a pure
|
|
72
|
+
widening, no existing caller's behaviour changes without an explicit
|
|
73
|
+
opt-in declaration at its own call site.
|
|
74
|
+
|
|
6
75
|
## [0.4.0]
|
|
7
76
|
|
|
8
77
|
**Additive.** Two new primitives so one identity layer covers both deployment
|
package/README.md
CHANGED
|
@@ -105,6 +105,27 @@ await resolveTenant({ session: null }); // throws Error("Unauthenticated: no ses
|
|
|
105
105
|
`{ kind: "bearer", context }` for a machine caller. A missing session, a
|
|
106
106
|
missing organization and an empty organization id are all refusals, never a
|
|
107
107
|
default.
|
|
108
|
+
- `resolveTenantIdOrAbsent(source)` — the NON-throwing sibling of
|
|
109
|
+
`requireTenantId`, for a public READ. Same `TenantSource` union, same
|
|
110
|
+
refusal conditions; instead of throwing it returns
|
|
111
|
+
`{ present: true, tenantId }` or
|
|
112
|
+
`{ present: false, absence: { code: "TENANT_ABSENT", reason } }`
|
|
113
|
+
(`TenantAbsence`, `TenantAbsenceReason`, `TenantResolution`). The absence
|
|
114
|
+
is a non-empty object: it is never `[]`, `{}`, `null` or a zeroed count, so
|
|
115
|
+
"no organization" and "an organization with nothing in it" cannot collapse
|
|
116
|
+
into one value. Use `requireTenantId` at a WRITE boundary and
|
|
117
|
+
`resolveTenantIdOrAbsent` where a throw would crash a mounted render. The
|
|
118
|
+
tenant id is OPAQUE — returned byte-for-byte, never a slug or display name.
|
|
119
|
+
- `requireHumanRole(session, required)` — asserts the VERIFIED session
|
|
120
|
+
carries `required` (a `HumanAccountRole` or a list of them, exact match, no
|
|
121
|
+
hierarchy) and returns the resolved identity; throws otherwise. The role is
|
|
122
|
+
read from `session.orgRole`, never from an argument. `RequiredHumanRole`
|
|
123
|
+
is its `required` type.
|
|
124
|
+
- `resolveHumanRoleOrRefusal(session, required)` — the non-throwing form:
|
|
125
|
+
`{ admitted: true, identity }` or
|
|
126
|
+
`{ admitted: false, refusal: { code: "ROLE_REFUSED", reason } }`
|
|
127
|
+
(`RoleResolution`, `RoleRefusal`, `RoleRefusalReason`). `reason` is
|
|
128
|
+
`role-not-held` for a verified member lacking the role.
|
|
108
129
|
- `getEffectiveTenantId(ctx, args)` — resolves which tenant a request should
|
|
109
130
|
act on when a caller may legitimately act on more than one.
|
|
110
131
|
|
package/dist/human-path.d.ts
CHANGED
|
@@ -92,4 +92,50 @@ export type ResolvedHumanIdentity = {
|
|
|
92
92
|
* ```
|
|
93
93
|
*/
|
|
94
94
|
export declare function normalizeVerifiedHumanSession(session: ClerkSessionLike | null | undefined): ResolvedHumanIdentity;
|
|
95
|
+
/**
|
|
96
|
+
* Why a caller was not admitted. `role-not-held` is the one that matters: a
|
|
97
|
+
* verified member of the organization who does not carry the required role.
|
|
98
|
+
*/
|
|
99
|
+
export type RoleRefusalReason = "no-session" | "no-organization" | "no-verified-role" | "role-not-held";
|
|
100
|
+
/** The typed refusal. A non-empty object carrying its own code. */
|
|
101
|
+
export type RoleRefusal = {
|
|
102
|
+
code: "ROLE_REFUSED";
|
|
103
|
+
reason: RoleRefusalReason;
|
|
104
|
+
};
|
|
105
|
+
/** Result of `resolveHumanRoleOrRefusal`. Branch on `admitted`. */
|
|
106
|
+
export type RoleResolution = {
|
|
107
|
+
admitted: true;
|
|
108
|
+
identity: ResolvedHumanIdentity;
|
|
109
|
+
} | {
|
|
110
|
+
admitted: false;
|
|
111
|
+
refusal: RoleRefusal;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* `required` is the POLICY the call site declares; the caller's role is the
|
|
115
|
+
* CLAIM, and it is read only from `session.orgRole` (which the caller has
|
|
116
|
+
* already verified upstream — see the @security note on
|
|
117
|
+
* `normalizeVerifiedHumanSession`). There is no parameter through which a
|
|
118
|
+
* caller can supply its own role. Matching is EXACT, with no hierarchy:
|
|
119
|
+
* `owner` does not satisfy `"admin"`; pass `["owner", "admin"]` to admit
|
|
120
|
+
* either. An empty list admits nobody.
|
|
121
|
+
*/
|
|
122
|
+
export type RequiredHumanRole = HumanAccountRole | readonly HumanAccountRole[];
|
|
123
|
+
/**
|
|
124
|
+
* THROWING form, for a write boundary. Returns the resolved identity when
|
|
125
|
+
* the verified session carries one of the `required` roles; throws
|
|
126
|
+
* otherwise. Malformed sessions throw exactly what
|
|
127
|
+
* `normalizeVerifiedHumanSession` throws; a well-formed session lacking the
|
|
128
|
+
* role throws "Role refused: ...".
|
|
129
|
+
*
|
|
130
|
+
* Same `@security` precondition as `normalizeVerifiedHumanSession`: this
|
|
131
|
+
* asserts on a session that is ALREADY verified. It verifies nothing.
|
|
132
|
+
*/
|
|
133
|
+
export declare function requireHumanRole(session: ClerkSessionLike | null | undefined, required: RequiredHumanRole): ResolvedHumanIdentity;
|
|
134
|
+
/**
|
|
135
|
+
* NON-THROWING form, for a public read. Returns `{ admitted: true, identity }`
|
|
136
|
+
* or `{ admitted: false, refusal }` where the refusal carries `ROLE_REFUSED`
|
|
137
|
+
* and a reason. Admits exactly the sessions `requireHumanRole` returns for
|
|
138
|
+
* and refuses exactly the ones it throws for.
|
|
139
|
+
*/
|
|
140
|
+
export declare function resolveHumanRoleOrRefusal(session: ClerkSessionLike | null | undefined, required: RequiredHumanRole): RoleResolution;
|
|
95
141
|
//# sourceMappingURL=human-path.d.ts.map
|
package/dist/human-path.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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;AAgCF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAC3C,qBAAqB,CAiCvB;AAMD;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GACzB,YAAY,GACZ,iBAAiB,GACjB,kBAAkB,GAClB,eAAe,CAAC;AAEpB,mEAAmE;AACnE,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AAEF,mEAAmE;AACnE,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,qBAAqB,CAAA;CAAE,GACnD;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAE9C;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG,SAAS,gBAAgB,EAAE,CAAC;AAM/E;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,EAC5C,QAAQ,EAAE,iBAAiB,GAC1B,qBAAqB,CAWvB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,EAC5C,QAAQ,EAAE,iBAAiB,GAC1B,cAAc,CAqBhB"}
|
package/dist/human-path.js
CHANGED
|
@@ -35,7 +35,17 @@ export const humanAccountRoleSchema = z.enum([
|
|
|
35
35
|
* listed here is refused rather than silently coerced to a default, per
|
|
36
36
|
* "presented, never inferred from an absence."
|
|
37
37
|
*/
|
|
38
|
-
const CLERK_ORG_ROLE_MAP =
|
|
38
|
+
const CLERK_ORG_ROLE_MAP = Object.assign(
|
|
39
|
+
// PROTOTYPE-LESS on purpose. As a plain object literal this map INHERITED
|
|
40
|
+
// `Object.prototype`, so a lookup of a presented role like `toString`,
|
|
41
|
+
// `constructor` or `__proto__` did not MISS — it returned an inherited
|
|
42
|
+
// value, which is truthy, and the resolver handed back a FUNCTION as the
|
|
43
|
+
// role instead of refusing. With a null prototype, any key that is not one
|
|
44
|
+
// of the eight own keys below is `undefined`, so the existing
|
|
45
|
+
// `if (!mapped) throw` refuses it. The lookup expressions in
|
|
46
|
+
// `normalizeVerifiedHumanSession` and `resolveHumanRoleOrRefusal` are
|
|
47
|
+
// unchanged — this is a fix at the DATA, not a list of forbidden names.
|
|
48
|
+
Object.create(null), {
|
|
39
49
|
"org:owner": "owner",
|
|
40
50
|
owner: "owner",
|
|
41
51
|
"org:admin": "admin",
|
|
@@ -44,7 +54,7 @@ const CLERK_ORG_ROLE_MAP = {
|
|
|
44
54
|
member: "member",
|
|
45
55
|
"org:client": "client",
|
|
46
56
|
client: "client",
|
|
47
|
-
};
|
|
57
|
+
});
|
|
48
58
|
/**
|
|
49
59
|
* @security ⚠️ DECODE/NORMALIZE-ONLY. NOT AUTHENTICATION. NOT VERIFICATION.
|
|
50
60
|
*
|
|
@@ -114,4 +124,55 @@ export function normalizeVerifiedHumanSession(session) {
|
|
|
114
124
|
}
|
|
115
125
|
return { tenant: orgId, subject: userId, role: mapped };
|
|
116
126
|
}
|
|
127
|
+
function requiredList(required) {
|
|
128
|
+
return typeof required === "string" ? [required] : required;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* THROWING form, for a write boundary. Returns the resolved identity when
|
|
132
|
+
* the verified session carries one of the `required` roles; throws
|
|
133
|
+
* otherwise. Malformed sessions throw exactly what
|
|
134
|
+
* `normalizeVerifiedHumanSession` throws; a well-formed session lacking the
|
|
135
|
+
* role throws "Role refused: ...".
|
|
136
|
+
*
|
|
137
|
+
* Same `@security` precondition as `normalizeVerifiedHumanSession`: this
|
|
138
|
+
* asserts on a session that is ALREADY verified. It verifies nothing.
|
|
139
|
+
*/
|
|
140
|
+
export function requireHumanRole(session, required) {
|
|
141
|
+
const identity = normalizeVerifiedHumanSession(session);
|
|
142
|
+
const allowed = requiredList(required);
|
|
143
|
+
if (!allowed.includes(identity.role)) {
|
|
144
|
+
throw new Error(`Role refused: this session is a verified member but does not carry ${allowed.length === 0 ? "any admitted role" : `role ${allowed.join(" or ")}`}.`);
|
|
145
|
+
}
|
|
146
|
+
return identity;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* NON-THROWING form, for a public read. Returns `{ admitted: true, identity }`
|
|
150
|
+
* or `{ admitted: false, refusal }` where the refusal carries `ROLE_REFUSED`
|
|
151
|
+
* and a reason. Admits exactly the sessions `requireHumanRole` returns for
|
|
152
|
+
* and refuses exactly the ones it throws for.
|
|
153
|
+
*/
|
|
154
|
+
export function resolveHumanRoleOrRefusal(session, required) {
|
|
155
|
+
const refuse = (reason) => ({
|
|
156
|
+
admitted: false,
|
|
157
|
+
refusal: { code: "ROLE_REFUSED", reason },
|
|
158
|
+
});
|
|
159
|
+
if (!session)
|
|
160
|
+
return refuse("no-session");
|
|
161
|
+
const { orgId, userId, orgRole } = session;
|
|
162
|
+
if (typeof orgId !== "string" || orgId.length === 0) {
|
|
163
|
+
return refuse("no-organization");
|
|
164
|
+
}
|
|
165
|
+
if (typeof userId !== "string" || userId.length === 0) {
|
|
166
|
+
return refuse("no-verified-role");
|
|
167
|
+
}
|
|
168
|
+
if (typeof orgRole !== "string" || orgRole.length === 0) {
|
|
169
|
+
return refuse("no-verified-role");
|
|
170
|
+
}
|
|
171
|
+
const role = CLERK_ORG_ROLE_MAP[orgRole];
|
|
172
|
+
if (!role)
|
|
173
|
+
return refuse("no-verified-role");
|
|
174
|
+
if (!requiredList(required).includes(role))
|
|
175
|
+
return refuse("role-not-held");
|
|
176
|
+
return { admitted: true, identity: { tenant: orgId, subject: userId, role } };
|
|
177
|
+
}
|
|
117
178
|
//# sourceMappingURL=human-path.js.map
|
package/dist/human-path.js.map
CHANGED
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"human-path.js","sourceRoot":"","sources":["../src/human-path.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,8 +11,8 @@ export { validateMasterBearer } from "./bearer-validation.js";
|
|
|
11
11
|
export type { OAuthCtx, ScopeProfile, NamespacePrefix, FromAllowListEntry, ValidateMasterBearerResult, } from "./types.js";
|
|
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
|
-
export { requireTenantId } 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";
|
|
14
|
+
export { requireTenantId, resolveTenantIdOrAbsent } from "./org-guard.js";
|
|
15
|
+
export type { TenantAbsence, TenantAbsenceReason, TenantResolution, SessionIdentity, TenantSource, DeploymentMode, } from "./org-guard.js";
|
|
16
|
+
export { humanAccountRoleSchema, normalizeVerifiedHumanSession, requireHumanRole, resolveHumanRoleOrRefusal, } from "./human-path.js";
|
|
17
|
+
export type { RequiredHumanRole, RoleRefusal, RoleRefusalReason, RoleResolution, HumanAccountRole, ClerkSessionLike, ResolvedHumanIdentity, } from "./human-path.js";
|
|
18
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;
|
|
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,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAC1E,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,cAAc,GACf,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,sBAAsB,EACtB,6BAA6B,EAC7B,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ 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
13
|
// Organization-membership guard (unified session + bearer + self-host contract)
|
|
14
|
-
export { requireTenantId } from "./org-guard.js";
|
|
14
|
+
export { requireTenantId, resolveTenantIdOrAbsent } from "./org-guard.js";
|
|
15
15
|
// 0.4.0 — Human-path resolver (Clerk-shaped session -> { tenant, subject, role })
|
|
16
|
-
export { humanAccountRoleSchema, normalizeVerifiedHumanSession } from "./human-path.js";
|
|
16
|
+
export { humanAccountRoleSchema, normalizeVerifiedHumanSession, requireHumanRole, resolveHumanRoleOrRefusal, } from "./human-path.js";
|
|
17
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,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;
|
|
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,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAU1E,kFAAkF;AAClF,OAAO,EACL,sBAAsB,EACtB,6BAA6B,EAC7B,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC"}
|
package/dist/org-guard.d.ts
CHANGED
|
@@ -92,4 +92,52 @@ export type TenantSource = {
|
|
|
92
92
|
* organization/workspace information is missing.
|
|
93
93
|
*/
|
|
94
94
|
export declare function requireTenantId(source: TenantSource): string;
|
|
95
|
+
/**
|
|
96
|
+
* Why a caller resolved to no tenant. A closed set, so a consumer can branch
|
|
97
|
+
* on content.
|
|
98
|
+
*/
|
|
99
|
+
export type TenantAbsenceReason = "no-session" | "no-organization" | "no-workspace" | "no-tenant-configured";
|
|
100
|
+
/**
|
|
101
|
+
* The typed absence. It is a NON-empty object carrying its own code: it can
|
|
102
|
+
* never be mistaken for `[]`, `{}`, `null`, `0` or a zeroed aggregate, all of
|
|
103
|
+
* which are what "an organization with nothing in it" looks like.
|
|
104
|
+
*/
|
|
105
|
+
export type TenantAbsence = {
|
|
106
|
+
code: "TENANT_ABSENT";
|
|
107
|
+
reason: TenantAbsenceReason;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Result of `resolveTenantIdOrAbsent`. Branch on `present`; the two arms
|
|
111
|
+
* share no field, so a consumer that forgets to branch gets a type error
|
|
112
|
+
* rather than a silently-empty read.
|
|
113
|
+
*/
|
|
114
|
+
export type TenantResolution = {
|
|
115
|
+
present: true;
|
|
116
|
+
tenantId: string;
|
|
117
|
+
} | {
|
|
118
|
+
present: false;
|
|
119
|
+
absence: TenantAbsence;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* The tenant id is OPAQUE. It is returned byte-for-byte as presented
|
|
123
|
+
* (`orgId`, `workspaceId` or the self-host `tenantId`), never trimmed,
|
|
124
|
+
* lower-cased or otherwise interpreted. This package never receives or
|
|
125
|
+
* derives a slug or any human-facing name; each product maps its own notion
|
|
126
|
+
* (an organization id, a workspace id) onto that string.
|
|
127
|
+
*
|
|
128
|
+
* NON-THROWING sibling of `requireTenantId`, for a public READ.
|
|
129
|
+
*
|
|
130
|
+
* `requireTenantId` throws on every branch, which is right for a WRITE
|
|
131
|
+
* boundary and wrong for a subscribed read: a throw at a caller with no
|
|
132
|
+
* organization crashes a mounted render, while a bare empty value is
|
|
133
|
+
* byte-identical to "there is nothing". This function does neither: it
|
|
134
|
+
* returns the tenant, or a typed absence that cannot be confused with an
|
|
135
|
+
* empty result.
|
|
136
|
+
*
|
|
137
|
+
* Same `TenantSource` union, same refusal conditions as `requireTenantId` —
|
|
138
|
+
* every input that makes it throw makes this return `{ present: false }`,
|
|
139
|
+
* and every input it serves is served identically. Grants nothing that
|
|
140
|
+
* `requireTenantId` refuses.
|
|
141
|
+
*/
|
|
142
|
+
export declare function resolveTenantIdOrAbsent(source: TenantSource): TenantResolution;
|
|
95
143
|
//# sourceMappingURL=org-guard.d.ts.map
|
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,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"}
|
|
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;AAMD;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAC3B,YAAY,GACZ,iBAAiB,GACjB,cAAc,GACd,sBAAsB,CAAC;AAE3B;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,mBAAmB,CAAC;CAC7B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,GAAG,gBAAgB,CA4B9E"}
|
package/dist/org-guard.js
CHANGED
|
@@ -74,4 +74,52 @@ export function requireTenantId(source) {
|
|
|
74
74
|
}
|
|
75
75
|
return workspaceId;
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* The tenant id is OPAQUE. It is returned byte-for-byte as presented
|
|
79
|
+
* (`orgId`, `workspaceId` or the self-host `tenantId`), never trimmed,
|
|
80
|
+
* lower-cased or otherwise interpreted. This package never receives or
|
|
81
|
+
* derives a slug or any human-facing name; each product maps its own notion
|
|
82
|
+
* (an organization id, a workspace id) onto that string.
|
|
83
|
+
*
|
|
84
|
+
* NON-THROWING sibling of `requireTenantId`, for a public READ.
|
|
85
|
+
*
|
|
86
|
+
* `requireTenantId` throws on every branch, which is right for a WRITE
|
|
87
|
+
* boundary and wrong for a subscribed read: a throw at a caller with no
|
|
88
|
+
* organization crashes a mounted render, while a bare empty value is
|
|
89
|
+
* byte-identical to "there is nothing". This function does neither: it
|
|
90
|
+
* returns the tenant, or a typed absence that cannot be confused with an
|
|
91
|
+
* empty result.
|
|
92
|
+
*
|
|
93
|
+
* Same `TenantSource` union, same refusal conditions as `requireTenantId` —
|
|
94
|
+
* every input that makes it throw makes this return `{ present: false }`,
|
|
95
|
+
* and every input it serves is served identically. Grants nothing that
|
|
96
|
+
* `requireTenantId` refuses.
|
|
97
|
+
*/
|
|
98
|
+
export function resolveTenantIdOrAbsent(source) {
|
|
99
|
+
const absent = (reason) => ({
|
|
100
|
+
present: false,
|
|
101
|
+
absence: { code: "TENANT_ABSENT", reason },
|
|
102
|
+
});
|
|
103
|
+
if (source.kind === "session") {
|
|
104
|
+
if (!source.identity)
|
|
105
|
+
return absent("no-session");
|
|
106
|
+
const orgId = source.identity.orgId;
|
|
107
|
+
if (typeof orgId !== "string" || orgId.length === 0) {
|
|
108
|
+
return absent("no-organization");
|
|
109
|
+
}
|
|
110
|
+
return { present: true, tenantId: orgId };
|
|
111
|
+
}
|
|
112
|
+
if (source.kind === "self-host") {
|
|
113
|
+
const tenantId = source.tenantId;
|
|
114
|
+
if (typeof tenantId !== "string" || tenantId.length === 0) {
|
|
115
|
+
return absent("no-tenant-configured");
|
|
116
|
+
}
|
|
117
|
+
return { present: true, tenantId };
|
|
118
|
+
}
|
|
119
|
+
const workspaceId = source.context?.workspaceId;
|
|
120
|
+
if (typeof workspaceId !== "string" || workspaceId.length === 0) {
|
|
121
|
+
return absent("no-workspace");
|
|
122
|
+
}
|
|
123
|
+
return { present: true, tenantId: workspaceId };
|
|
124
|
+
}
|
|
77
125
|
//# sourceMappingURL=org-guard.js.map
|
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;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"}
|
|
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;AAmCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAoB;IAC1D,MAAM,MAAM,GAAG,CAAC,MAA2B,EAAoB,EAAE,CAAC,CAAC;QACjE,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE;KAC3C,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ;YAAE,OAAO,MAAM,CAAC,YAAY,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpD,OAAO,MAAM,CAAC,iBAAiB,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC5C,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,OAAO,MAAM,CAAC,sBAAsB,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACrC,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,OAAO,MAAM,CAAC,cAAc,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AAClD,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