@vantageos/cloud-identity 0.5.0 → 0.7.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 CHANGED
@@ -3,6 +3,121 @@
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.7.0]
7
+
8
+ **Additive — resolution of a PRESENTED, non-master bearer.** The published
9
+ 0.5.0 surface covered the master credential (`validateMasterBearer`) and a
10
+ decoder that verifies nothing and says so (`decodeUnverifiedBearer`). Between
11
+ them there was no function taking a presented non-master bearer, hashing it,
12
+ looking the row up and returning a resolved identity or a typed refusal — so a
13
+ consuming product wrote that primitive locally, against an untyped client, with
14
+ the lookup failure swallowed into `null`. That is a second authority answering
15
+ "who is calling". This release is the single one, so the local variant can be
16
+ deleted rather than duplicated.
17
+
18
+ **Version number.** This entry takes `0.7.0`, not `0.6.0`. An unmerged branch
19
+ already claims `0.6.0` for `resolveTenantIdOrAbsent` (the non-throwing sibling
20
+ of `requireTenantId`); leaving `0.6.0` to it means these two can land in either
21
+ order without either having to be renumbered after review.
22
+
23
+ ### Added
24
+
25
+ - `validatePresentedBearer(authHeader, deps)` (`src/presented-bearer.ts`) —
26
+ parses the header, hashes the token to a SHA-256 hex digest, obtains the row
27
+ through the caller-supplied `deps.lookupBySecretHash(digest)`, re-compares the
28
+ row's stored digest with `timingSafeEqual`, then honours row shape, revocation
29
+ and expiry. The package cannot query a datastore, so the read is injected; every
30
+ decision stays in the package. `deps.now` is an injectable clock.
31
+ - **The callback receives the DIGEST, never the token**, keeping a usable
32
+ secret out of a consumer's query and out of its query logs.
33
+ - **An unknown digest, a revoked row, an expired row, a row of invalid shape
34
+ and a row whose stored digest does not match all return ONE shared frozen
35
+ value**, `{ ok: false, error: "mismatch" }` — one object rather than five
36
+ equal literals, so the branches cannot drift apart by an added field. A
37
+ prober cannot learn whether a token was ever real. `deps.onRefusal` reports
38
+ the internal distinction (`PresentedBearerInternalReason`) to the consumer's
39
+ own logs; a throwing sink cannot change the outcome.
40
+ - **A lookup that throws is a REFUSAL** (`error: "unavailable"`), deliberately
41
+ outside the collapsed bucket so a datastore outage can be answered 503
42
+ rather than reported to every caller as a bad credential. No branch grants
43
+ anything on a lookup failure.
44
+ - Header vocabulary is `validateMasterBearer`'s, unchanged: `"missing"`,
45
+ `"malformed"`, `"mismatch"`. A malformed header costs no datastore read.
46
+ - `requireAgentScopedIdentity(identity)` — the second, mandatory call for a
47
+ per-agent surface. An organization-wide token (a row with no agent identity)
48
+ resolves successfully and carries its agent as an EXPLICIT absence
49
+ (`AgentPresence`, a union that must be narrowed — not a nullable field a
50
+ caller can fail to check). The per-agent form `AgentScopedIdentity` carries a
51
+ module-private type brand, so it cannot be constructed outside the package: a
52
+ handler typed against it is reachable only through this function. Its refusal
53
+ is distinguishable from a bad credential on purpose — 403, not 401.
54
+ - `sha256Hex(input)` (`src/crypto.ts`) — lower-case hex SHA-256 of a string,
55
+ exported so the code WRITING a token row derives the digest exactly as the
56
+ resolver recomputes it.
57
+ - `storedBearerRowSchema` + `StoredBearerRow`, `AgentPresence`,
58
+ `ResolvedBearerIdentity`, `AgentScopedIdentity`,
59
+ `ValidatePresentedBearerResult`, `RequireAgentScopedResult`,
60
+ `PresentedBearerDeps`, `PresentedBearerInternalReason`.
61
+ - Subpath export `@vantageos/cloud-identity/presented-bearer`.
62
+
63
+ ### Changed
64
+
65
+ - Documentation only: `decodeUnverifiedBearer`'s security banner said the
66
+ production trust boundary was "not yet exported by this package". For an
67
+ opaque token it now is, and the banner names it. No behaviour change.
68
+
69
+ ### Unchanged
70
+
71
+ - **Every existing export keeps its exact behaviour and signature.** The new
72
+ module is a new file; the only edits to existing sources are one appended
73
+ function in `src/crypto.ts`, new export lines in `src/index.ts`, and the
74
+ docstring above. No existing test was edited.
75
+
76
+ ## [0.6.0]
77
+
78
+ **Additive — typed refusal for reads, and a role assertion.**
79
+
80
+ ### Added
81
+
82
+ - `resolveTenantIdOrAbsent(source)` (`src/org-guard.ts`): non-throwing sibling
83
+ of `requireTenantId`. Returns `{ present: true, tenantId }` or a typed
84
+ `{ present: false, absence: { code: "TENANT_ABSENT", reason } }`. A read that
85
+ throws crashes a mounted render; a read that returns a bare empty value is
86
+ byte-identical to "there is nothing". This returns neither.
87
+ - `requireHumanRole(session, required)` and
88
+ `resolveHumanRoleOrRefusal(session, required)` (`src/human-path.ts`): role
89
+ assertion on the verified session's `orgRole`, throwing and non-throwing
90
+ forms. Exact match, no hierarchy; an empty required list admits nobody.
91
+ - Types: `TenantAbsence`, `TenantAbsenceReason`, `TenantResolution`,
92
+ `RequiredHumanRole`, `RoleRefusal`, `RoleRefusalReason`, `RoleResolution`.
93
+
94
+ ### Fixed
95
+
96
+ - The Clerk org-role map is now prototype-less (`Object.create(null)`). As a
97
+ plain object literal it inherited `Object.prototype`, so a presented
98
+ `orgRole` of `toString`, `constructor`, `valueOf` or `__proto__` did not
99
+ MISS the lookup — it returned an inherited value, which is truthy, and the
100
+ resolver handed back a FUNCTION as the role instead of refusing. Any key
101
+ that is not one of the eight own keys is now `undefined`, so the existing
102
+ refusal path rejects it. Fixed at the data; no lookup expression and no
103
+ function body changed, and no list of forbidden names was introduced.
104
+
105
+ ### Unchanged
106
+
107
+ - `requireTenantId` and `normalizeVerifiedHumanSession` are byte-for-byte
108
+ unchanged and still throw on every refusal.
109
+ - The tenant key stays opaque: no export takes or returns a slug. That
110
+ opacity is now pinned on all four served paths (session, bearer,
111
+ self-host, `role.identity.tenant`), through BOTH the throwing and the
112
+ non-throwing sibling, with a fixture carrying edge whitespace so that
113
+ splicing a `.trim()` onto a served id goes RED instead of passing.
114
+
115
+ ### Removed
116
+
117
+ - `bun.lock`, committed by accident in 0.5.0. `package-lock.json` is the
118
+ lockfile of record; two lockfiles for two package managers guarantee a
119
+ silent divergence.
120
+
6
121
  ## [0.5.0]
7
122
 
8
123
  **Additive — grant-aware scope filter.** Closes the other pole of the
package/README.md CHANGED
@@ -105,9 +105,72 @@ 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
 
132
+ **Resolving a presented bearer token**
133
+
134
+ - `validatePresentedBearer(authHeader, deps)` — takes the raw
135
+ `Authorization` header a non-master caller presented, and returns either a
136
+ resolved identity or a coarse refusal. It hashes the token, asks your
137
+ `deps.lookupBySecretHash` callback for the row carrying that digest,
138
+ re-compares the stored digest in constant time, and then checks revocation
139
+ and expiry. Your callback does one indexed read; every decision stays in the
140
+ package, so two consumers cannot drift into two different answers to "who is
141
+ calling".
142
+ - **Your callback receives the digest, never the token.** A raw secret
143
+ therefore never reaches your query, your query log or your slow-query log.
144
+ Use `sha256Hex` to produce the same digest when you WRITE a token row, so
145
+ the two sides agree.
146
+ - **An unknown token, a revoked token and an expired token return one
147
+ identical value** (`{ ok: false, error: "mismatch" }`). Someone probing
148
+ your endpoint cannot learn from the reply whether a token was ever real.
149
+ Pass `deps.onRefusal` to receive the distinction
150
+ (`PresentedBearerInternalReason`) in your own logs, where it belongs.
151
+ - **A failing lookup refuses.** If your callback throws, the result is
152
+ `{ ok: false, error: "unavailable" }` — a refusal you can answer with 503
153
+ instead of 401. There is no branch that grants anything when the lookup
154
+ fails.
155
+ - `deps.now` is an injectable clock, so expiry is testable without waiting.
156
+ - Types: `PresentedBearerDeps`, `StoredBearerRow` (validated at runtime by
157
+ `storedBearerRowSchema`), `ResolvedBearerIdentity`,
158
+ `ValidatePresentedBearerResult`.
159
+ - `requireAgentScopedIdentity(identity)` — narrows a resolved identity to a
160
+ surface that acts as ONE agent, and refuses when the token has no agent.
161
+ A token can be organization-wide: it names a tenant and no agent. That is a
162
+ legitimate credential, and it must never be silently widened into whichever
163
+ agent the surface happened to be about. So the agent is not a nullable field
164
+ you have to remember to check — `ResolvedBearerIdentity.agent` is an
165
+ `AgentPresence`, a union you must narrow, and the per-agent form
166
+ (`AgentScopedIdentity`, with a plain `agentId: string`) can only be obtained
167
+ from this function. Returns `RequireAgentScopedResult`; its refusal is
168
+ distinct from a bad credential, because the right answer there is 403, not
169
+ 401.
170
+ - `sha256Hex(input)` — the lower-case hex SHA-256 digest of a string. Exported
171
+ so that the code storing a token row hashes it exactly the way the resolver
172
+ will recompute it. Hash once, store the digest, discard the token.
173
+
111
174
  **Row filtering**
112
175
 
113
176
  - `passesScopeFilter(ctx, row)` — true when this row is visible to this
@@ -228,6 +291,15 @@ Worth reading before you rely on it.
228
291
  - **It does not talk to your database.** `scopeFilterList` filters rows you
229
292
  have already fetched. If fetching them was itself expensive or unsafe, filter
230
293
  earlier, in your query.
294
+ - **It does not issue, rotate or revoke tokens.** `validatePresentedBearer`
295
+ reads a row you already store and decides whether it may be believed. Minting
296
+ a token, writing its row, marking it revoked and expiring it are yours; the
297
+ package only insists that a row it is handed be believed in constant time,
298
+ and refused identically however it fails.
299
+ - **It does not verify a signed JWT.** The presented-bearer path is for an
300
+ OPAQUE token — one whose only meaning is the row it matches. If your
301
+ credential carries its own signed claims, verify that signature with a JWT
302
+ library first; this package exports no verifier.
231
303
 
232
304
  ## Upgrading to 0.4.0
233
305
 
package/dist/crypto.d.ts CHANGED
@@ -15,4 +15,16 @@
15
15
  * Deno, edge runtimes) and parity with the original Convex implementation.
16
16
  */
17
17
  export declare function timingSafeEqual(a: Uint8Array, b: Uint8Array): Promise<boolean>;
18
+ /**
19
+ * SHA-256 of a UTF-8 string as a lower-case hex digest.
20
+ *
21
+ * Exported because a consumer that STORES a bearer row must produce the digest
22
+ * the same way the resolver will recompute it. Without this, each consumer
23
+ * hashes its own way and the mismatch surfaces as "correct token refused".
24
+ *
25
+ * @security Hash the token once, store the digest, discard the token. This
26
+ * function is one-way; it is not a substitute for a comparison — compare
27
+ * digests with `timingSafeEqual`, never with `===`.
28
+ */
29
+ export declare function sha256Hex(input: string): Promise<string>;
18
30
  //# sourceMappingURL=crypto.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CACnC,CAAC,EAAE,UAAU,EACb,CAAC,EAAE,UAAU,GACZ,OAAO,CAAC,OAAO,CAAC,CAoBlB"}
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CACnC,CAAC,EAAE,UAAU,EACb,CAAC,EAAE,UAAU,GACZ,OAAO,CAAC,OAAO,CAAC,CAoBlB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9D"}
package/dist/crypto.js CHANGED
@@ -29,4 +29,22 @@ export async function timingSafeEqual(a, b) {
29
29
  }
30
30
  return diff === 0;
31
31
  }
32
+ /**
33
+ * SHA-256 of a UTF-8 string as a lower-case hex digest.
34
+ *
35
+ * Exported because a consumer that STORES a bearer row must produce the digest
36
+ * the same way the resolver will recompute it. Without this, each consumer
37
+ * hashes its own way and the mismatch surfaces as "correct token refused".
38
+ *
39
+ * @security Hash the token once, store the digest, discard the token. This
40
+ * function is one-way; it is not a substitute for a comparison — compare
41
+ * digests with `timingSafeEqual`, never with `===`.
42
+ */
43
+ export async function sha256Hex(input) {
44
+ const data = new TextEncoder().encode(input);
45
+ const digest = await crypto.subtle.digest("SHA-256", data);
46
+ return Array.from(new Uint8Array(digest))
47
+ .map((byte) => byte.toString(16).padStart(2, "0"))
48
+ .join("");
49
+ }
32
50
  //# sourceMappingURL=crypto.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,CAAa,EACb,CAAa;IAEb,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,6EAA6E;QAC7E,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CACxC,KAAK,EACL,WAAsC,EACtC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAC;QACF,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAgC,CAAC,CAAC;QACzE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACxB,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC"}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,CAAa,EACb,CAAa;IAEb,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,6EAA6E;QAC7E,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CACxC,KAAK,EACL,WAAsC,EACtC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAC;QACF,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAgC,CAAC,CAAC;QACzE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACxB,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAa;IAC3C,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;SACtC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SACjD,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC"}
@@ -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
@@ -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;AAoBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAC3C,qBAAqB,CAiCvB"}
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"}
@@ -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
@@ -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;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"}
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
@@ -5,14 +5,16 @@
5
5
  * tenant resolution, membership requirement, row filtering and bearer
6
6
  * validation.
7
7
  */
8
- export { timingSafeEqual } from "./crypto.js";
8
+ export { timingSafeEqual, sha256Hex } from "./crypto.js";
9
9
  export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWildcardScope, LEGACY_WILDCARD_CTX, type ScopeFilterable, } from "./scope-filter.js";
10
10
  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
+ export { validatePresentedBearer, requireAgentScopedIdentity, storedBearerRowSchema, } from "./presented-bearer.js";
19
+ export type { StoredBearerRow, AgentPresence, ResolvedBearerIdentity, AgentScopedIdentity, ValidatePresentedBearerResult, RequireAgentScopedResult, PresentedBearerDeps, PresentedBearerInternalReason, } from "./presented-bearer.js";
18
20
  //# sourceMappingURL=index.d.ts.map
@@ -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,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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzD,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;AAKzB,OAAO,EACL,uBAAuB,EACvB,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,eAAe,EACf,aAAa,EACb,sBAAsB,EACtB,mBAAmB,EACnB,6BAA6B,EAC7B,wBAAwB,EACxB,mBAAmB,EACnB,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -5,13 +5,17 @@
5
5
  * tenant resolution, membership requirement, row filtering and bearer
6
6
  * validation.
7
7
  */
8
- export { timingSafeEqual } from "./crypto.js";
8
+ export { timingSafeEqual, sha256Hex } from "./crypto.js";
9
9
  export { passesScopeFilter, scopeFilterList, scopeFilterGet, isMasterScope, isWildcardScope, LEGACY_WILDCARD_CTX, } from "./scope-filter.js";
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
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
+ // Presented-bearer resolution — the opaque-token trust boundary for a
18
+ // NON-MASTER credential. The package's single answer to "who is calling"
19
+ // for a presented bearer; consumers must not keep a local variant.
20
+ export { validatePresentedBearer, requireAgentScopedIdentity, storedBearerRowSchema, } from "./presented-bearer.js";
17
21
  //# 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;AAOjD,kFAAkF;AAClF,OAAO,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzD,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;AAWzB,sEAAsE;AACtE,yEAAyE;AACzE,mEAAmE;AACnE,OAAO,EACL,uBAAuB,EACvB,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,uBAAuB,CAAC"}
@@ -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
@@ -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
@@ -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"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Presented-bearer resolution — the opaque-token trust boundary.
3
+ * (See package.json "version" for the release this shipped in.)
4
+ *
5
+ * Closes the gap between the two credential paths the package already had:
6
+ *
7
+ * - `validateMasterBearer` covers the MASTER credential only (one configured
8
+ * secret, no datastore, no identity beyond "it was the master").
9
+ * - `decodeUnverifiedBearer` says in its own name that it verifies nothing;
10
+ * its payload is attacker-controlled and is not a trust boundary.
11
+ *
12
+ * Between them there was no function taking a PRESENTED, NON-MASTER bearer,
13
+ * hashing it, looking the row up and returning a resolved identity or a typed
14
+ * refusal. Consumers therefore wrote that primitive locally — a second
15
+ * authority answering "who is calling", which is exactly what must not exist.
16
+ * This module is the single one.
17
+ *
18
+ * DIVISION OF LABOUR. The package cannot query a datastore, so the row lookup
19
+ * arrives as a caller-supplied callback. Everything that can be got wrong
20
+ * stays here: the header parse, the hashing, the constant-time digest compare,
21
+ * revocation, expiry, row-shape validation, and the SHAPE of the refusal. The
22
+ * caller supplies one indexed read and nothing else.
23
+ *
24
+ * Errors are coarse-grained on purpose — callers should not surface the exact
25
+ * reason to remote clients beyond a generic 401/403. The vocabulary is
26
+ * `validateMasterBearer`'s, unchanged: "missing", "malformed", "mismatch".
27
+ *
28
+ * @security The callback is invoked with the SHA-256 HEX DIGEST of the
29
+ * presented token, never the token. A consumer's datastore query, query log
30
+ * and slow-query log therefore never see a usable secret. The raw token does
31
+ * not appear in any returned value, any refusal, or any logging callback.
32
+ */
33
+ import { z } from "zod";
34
+ /**
35
+ * Shape of the row a consumer's datastore returns for a given digest.
36
+ *
37
+ * `agentId` ABSENT (undefined or null) means an ORGANIZATION-WIDE token: it
38
+ * carries a tenant but no agent identity. Such a row is valid and resolves
39
+ * successfully; what it may NOT do is stand in for an agent. See
40
+ * `requireAgentScopedIdentity`.
41
+ *
42
+ * `secretHash` is the digest as stored by the consumer. It is re-compared here
43
+ * against the digest computed from the presented token, in constant time, so a
44
+ * lookup that performs a loose match (prefix, LIKE, case-folded index) or that
45
+ * returns the wrong row cannot become an authentication.
46
+ */
47
+ export declare const storedBearerRowSchema: z.ZodObject<{
48
+ tenantId: z.ZodString;
49
+ secretHash: z.ZodString;
50
+ agentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
51
+ revoked: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
52
+ expiresAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
53
+ scopes: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
54
+ }, z.core.$strip>;
55
+ export type StoredBearerRow = z.infer<typeof storedBearerRowSchema>;
56
+ /**
57
+ * The agent, present or explicitly absent. A DISCRIMINATED UNION rather than a
58
+ * nullable `agentId?: string`, because a nullable field is a duty to remember
59
+ * and this one was being forgotten: `identity.agent.agentId` does not type-check
60
+ * until the caller has narrowed on `present`.
61
+ */
62
+ export type AgentPresence = {
63
+ present: true;
64
+ agentId: string;
65
+ } | {
66
+ present: false;
67
+ code: "AGENT_ABSENT";
68
+ reason: "organization-wide-token";
69
+ };
70
+ /**
71
+ * A resolved, verified caller. Carries the tenant always, and the agent as a
72
+ * presence-or-absence. Carries NO secret and no digest.
73
+ */
74
+ export type ResolvedBearerIdentity = {
75
+ tenantId: string;
76
+ agent: AgentPresence;
77
+ scopes: readonly string[];
78
+ };
79
+ /**
80
+ * Brand for `AgentScopedIdentity`. Declared and NOT exported, so the branded
81
+ * type cannot be constructed anywhere outside this module: a per-agent surface
82
+ * that types its parameter as `AgentScopedIdentity` can only be reached through
83
+ * `requireAgentScopedIdentity`. That is what makes the organization-wide-token
84
+ * refusal structural rather than a convention a caller may skip.
85
+ */
86
+ declare const agentScopedBrand: unique symbol;
87
+ /**
88
+ * A resolved caller that HAS an agent. `agentId` is a plain non-optional
89
+ * string, so per-agent code needs no narrowing and has nothing to forget.
90
+ */
91
+ export type AgentScopedIdentity = {
92
+ readonly [agentScopedBrand]: true;
93
+ tenantId: string;
94
+ agentId: string;
95
+ scopes: readonly string[];
96
+ };
97
+ /**
98
+ * Result of `validatePresentedBearer`.
99
+ *
100
+ * - `ok: true` → the row was found, its digest matched in constant time, it
101
+ * is neither revoked nor expired, and its shape is valid.
102
+ * - `error: "missing"` → no Authorization header.
103
+ * - `error: "malformed"` → header present but not parseable as
104
+ * `Bearer <non-empty-token>`.
105
+ * - `error: "mismatch"` → the single, COLLAPSED refusal. An unknown digest,
106
+ * a revoked row, an expired row, a row of invalid shape and a row whose
107
+ * stored digest does not match all produce this value and nothing else, so
108
+ * a prober cannot learn from the response whether a token was ever real.
109
+ * - `error: "unavailable"` → the lookup callback itself threw or rejected.
110
+ * Deliberately NOT in the collapsed bucket: a caller must be able to
111
+ * answer 503 rather than 401 when its datastore is down, and an attacker
112
+ * cannot induce this state for one chosen token rather than another, so it
113
+ * discloses nothing about any token. It is a REFUSAL like the others — a
114
+ * lookup failure never falls through to a grant.
115
+ */
116
+ export type ValidatePresentedBearerResult = {
117
+ ok: true;
118
+ identity: ResolvedBearerIdentity;
119
+ error?: undefined;
120
+ } | {
121
+ ok: false;
122
+ error: "missing" | "malformed" | "mismatch" | "unavailable";
123
+ identity?: undefined;
124
+ };
125
+ /**
126
+ * Result of `requireAgentScopedIdentity`. The refusal is distinguishable from
127
+ * `validatePresentedBearer`'s refusals on purpose: the credential here is
128
+ * known-good, and the caller owes the remote client a 403 (wrong surface for
129
+ * this token), not a 401 (bad token).
130
+ */
131
+ export type RequireAgentScopedResult = {
132
+ ok: true;
133
+ identity: AgentScopedIdentity;
134
+ error?: undefined;
135
+ } | {
136
+ ok: false;
137
+ error: "no-agent-on-token";
138
+ identity?: undefined;
139
+ };
140
+ /**
141
+ * Why a refusal happened, for the consumer's own observability. Reported
142
+ * through `deps.onRefusal`; NEVER present in a returned value, because a
143
+ * returned distinction is a returned disclosure.
144
+ *
145
+ * @security Carries no token, no digest and no row. Only a reason word.
146
+ */
147
+ export type PresentedBearerInternalReason = "no-header" | "malformed-header" | "unknown-token" | "revoked-token" | "expired-token" | "row-shape-invalid" | "stored-digest-mismatch" | "lookup-failed";
148
+ export type PresentedBearerDeps = {
149
+ /**
150
+ * Indexed read of one row by the SHA-256 HEX DIGEST of the presented token.
151
+ * Returns the row, or null/undefined when no row carries that digest.
152
+ *
153
+ * @security The argument is a digest, never the token. Do not log it, and do
154
+ * not widen this to accept a raw secret.
155
+ */
156
+ lookupBySecretHash: (sha256HexDigest: string) => Promise<StoredBearerRow | null | undefined>;
157
+ /** Injectable clock. Never `Date.now()` inline, so expiry is testable. */
158
+ now?: () => number;
159
+ /** Observability sink for the collapsed refusals. Must not throw; if it does, the throw is swallowed and the refusal stands. */
160
+ onRefusal?: (reason: PresentedBearerInternalReason) => void;
161
+ };
162
+ /**
163
+ * Resolve an `Authorization: Bearer <token>` header presented by a NON-MASTER
164
+ * caller into a verified identity, or into a typed refusal.
165
+ *
166
+ * Order of operations, and why:
167
+ * 1. Parse the header. A missing or malformed header is refused WITHOUT
168
+ * calling the lookup, so a garbage request costs no datastore read.
169
+ * 2. Hash the token to a SHA-256 hex digest. The raw token goes no further.
170
+ * 3. Look the row up BY THAT DIGEST.
171
+ * 4. Re-compare the row's stored digest against the computed one with
172
+ * `timingSafeEqual` — never `!==` on a secret or a digest.
173
+ * 5. Validate the row's shape, then revocation, then expiry.
174
+ * 6. Resolve the tenant, and the agent as a presence or an explicit absence.
175
+ *
176
+ * Steps 3–5 all refuse with the SAME frozen value. A right is never granted by
177
+ * absence or by failure: there is no branch that returns `ok: true` without
178
+ * having passed every check above.
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * const result = await validatePresentedBearer(req.headers.get("authorization"), {
183
+ * lookupBySecretHash: (digest) => db.tokens.byDigest(digest),
184
+ * });
185
+ * if (!result.ok) return respond(result.error === "unavailable" ? 503 : 401);
186
+ * const tenantId = result.identity.tenantId;
187
+ * ```
188
+ */
189
+ export declare function validatePresentedBearer(authHeader: string | null | undefined, deps: PresentedBearerDeps): Promise<ValidatePresentedBearerResult>;
190
+ /**
191
+ * Narrow a resolved identity to a PER-AGENT surface, refusing when the token
192
+ * carries no agent.
193
+ *
194
+ * An organization-wide token is a legitimate credential that is simply not an
195
+ * agent; treating it as one would silently widen it into whichever agent the
196
+ * surface happened to be about. So this is a second, mandatory call, and its
197
+ * output type is branded: a per-agent handler declares `AgentScopedIdentity`
198
+ * and the only way to obtain one is here.
199
+ *
200
+ * Grants nothing on its own — it can only ever narrow an identity
201
+ * `validatePresentedBearer` already resolved.
202
+ */
203
+ export declare function requireAgentScopedIdentity(identity: ResolvedBearerIdentity): RequireAgentScopedResult;
204
+ export {};
205
+ //# sourceMappingURL=presented-bearer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presented-bearer.d.ts","sourceRoot":"","sources":["../src/presented-bearer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB;;;;;;;iBAOhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAMpE;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,CAAC;AAEhF;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF;;;;;;GAMG;AACH,OAAO,CAAC,MAAM,gBAAgB,EAAE,OAAO,MAAM,CAAC;AAE9C;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B,CAAC;AAMF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,6BAA6B,GACrC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,sBAAsB,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GACjE;IACE,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,aAAa,CAAC;IAC5D,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEN;;;;;GAKG;AACH,MAAM,MAAM,wBAAwB,GAChC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,mBAAmB,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GAC9D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC;AAMpE;;;;;;GAMG;AACH,MAAM,MAAM,6BAA6B,GACrC,WAAW,GACX,kBAAkB,GAClB,eAAe,GACf,eAAe,GACf,eAAe,GACf,mBAAmB,GACnB,wBAAwB,GACxB,eAAe,CAAC;AAEpB,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;;;;OAMG;IACH,kBAAkB,EAAE,CAClB,eAAe,EAAE,MAAM,KACpB,OAAO,CAAC,eAAe,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACjD,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,gIAAgI;IAChI,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,6BAA6B,KAAK,IAAI,CAAC;CAC7D,CAAC;AAyDF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACrC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,6BAA6B,CAAC,CAkFxC;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,sBAAsB,GAC/B,wBAAwB,CAY1B"}
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Presented-bearer resolution — the opaque-token trust boundary.
3
+ * (See package.json "version" for the release this shipped in.)
4
+ *
5
+ * Closes the gap between the two credential paths the package already had:
6
+ *
7
+ * - `validateMasterBearer` covers the MASTER credential only (one configured
8
+ * secret, no datastore, no identity beyond "it was the master").
9
+ * - `decodeUnverifiedBearer` says in its own name that it verifies nothing;
10
+ * its payload is attacker-controlled and is not a trust boundary.
11
+ *
12
+ * Between them there was no function taking a PRESENTED, NON-MASTER bearer,
13
+ * hashing it, looking the row up and returning a resolved identity or a typed
14
+ * refusal. Consumers therefore wrote that primitive locally — a second
15
+ * authority answering "who is calling", which is exactly what must not exist.
16
+ * This module is the single one.
17
+ *
18
+ * DIVISION OF LABOUR. The package cannot query a datastore, so the row lookup
19
+ * arrives as a caller-supplied callback. Everything that can be got wrong
20
+ * stays here: the header parse, the hashing, the constant-time digest compare,
21
+ * revocation, expiry, row-shape validation, and the SHAPE of the refusal. The
22
+ * caller supplies one indexed read and nothing else.
23
+ *
24
+ * Errors are coarse-grained on purpose — callers should not surface the exact
25
+ * reason to remote clients beyond a generic 401/403. The vocabulary is
26
+ * `validateMasterBearer`'s, unchanged: "missing", "malformed", "mismatch".
27
+ *
28
+ * @security The callback is invoked with the SHA-256 HEX DIGEST of the
29
+ * presented token, never the token. A consumer's datastore query, query log
30
+ * and slow-query log therefore never see a usable secret. The raw token does
31
+ * not appear in any returned value, any refusal, or any logging callback.
32
+ */
33
+ import { z } from "zod";
34
+ import { sha256Hex, timingSafeEqual } from "./crypto.js";
35
+ const BEARER_PREFIX_LOWER = "bearer ";
36
+ // ---------------------------------------------------------------------------
37
+ // The stored row
38
+ // ---------------------------------------------------------------------------
39
+ /**
40
+ * Shape of the row a consumer's datastore returns for a given digest.
41
+ *
42
+ * `agentId` ABSENT (undefined or null) means an ORGANIZATION-WIDE token: it
43
+ * carries a tenant but no agent identity. Such a row is valid and resolves
44
+ * successfully; what it may NOT do is stand in for an agent. See
45
+ * `requireAgentScopedIdentity`.
46
+ *
47
+ * `secretHash` is the digest as stored by the consumer. It is re-compared here
48
+ * against the digest computed from the presented token, in constant time, so a
49
+ * lookup that performs a loose match (prefix, LIKE, case-folded index) or that
50
+ * returns the wrong row cannot become an authentication.
51
+ */
52
+ export const storedBearerRowSchema = z.object({
53
+ tenantId: z.string().min(1),
54
+ secretHash: z.string().min(1),
55
+ agentId: z.string().min(1).nullish(),
56
+ revoked: z.boolean().nullish(),
57
+ expiresAt: z.number().nullish(),
58
+ scopes: z.array(z.string()).nullish(),
59
+ });
60
+ // ---------------------------------------------------------------------------
61
+ // The refusals, as shared frozen singletons
62
+ // ---------------------------------------------------------------------------
63
+ const MISSING = Object.freeze({
64
+ ok: false,
65
+ error: "missing",
66
+ });
67
+ const MALFORMED = Object.freeze({
68
+ ok: false,
69
+ error: "malformed",
70
+ });
71
+ const UNAVAILABLE = Object.freeze({
72
+ ok: false,
73
+ error: "unavailable",
74
+ });
75
+ /**
76
+ * THE collapsed refusal. One frozen value, shared by every branch that must be
77
+ * externally indistinguishable — unknown digest, revoked, expired, invalid row
78
+ * shape, stored-digest mismatch. Not five literals that happen to be equal
79
+ * today: one object, so the branches cannot drift apart by an added field.
80
+ */
81
+ const COLLAPSED_REFUSAL = Object.freeze({
82
+ ok: false,
83
+ error: "mismatch",
84
+ });
85
+ function report(deps, reason) {
86
+ const sink = deps.onRefusal;
87
+ if (typeof sink !== "function")
88
+ return;
89
+ try {
90
+ sink(reason);
91
+ }
92
+ catch {
93
+ // A failing logger must never change an authorization outcome.
94
+ }
95
+ }
96
+ function collapse(deps, reason) {
97
+ report(deps, reason);
98
+ return COLLAPSED_REFUSAL;
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // validatePresentedBearer
102
+ // ---------------------------------------------------------------------------
103
+ /**
104
+ * Resolve an `Authorization: Bearer <token>` header presented by a NON-MASTER
105
+ * caller into a verified identity, or into a typed refusal.
106
+ *
107
+ * Order of operations, and why:
108
+ * 1. Parse the header. A missing or malformed header is refused WITHOUT
109
+ * calling the lookup, so a garbage request costs no datastore read.
110
+ * 2. Hash the token to a SHA-256 hex digest. The raw token goes no further.
111
+ * 3. Look the row up BY THAT DIGEST.
112
+ * 4. Re-compare the row's stored digest against the computed one with
113
+ * `timingSafeEqual` — never `!==` on a secret or a digest.
114
+ * 5. Validate the row's shape, then revocation, then expiry.
115
+ * 6. Resolve the tenant, and the agent as a presence or an explicit absence.
116
+ *
117
+ * Steps 3–5 all refuse with the SAME frozen value. A right is never granted by
118
+ * absence or by failure: there is no branch that returns `ok: true` without
119
+ * having passed every check above.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * const result = await validatePresentedBearer(req.headers.get("authorization"), {
124
+ * lookupBySecretHash: (digest) => db.tokens.byDigest(digest),
125
+ * });
126
+ * if (!result.ok) return respond(result.error === "unavailable" ? 503 : 401);
127
+ * const tenantId = result.identity.tenantId;
128
+ * ```
129
+ */
130
+ export async function validatePresentedBearer(authHeader, deps) {
131
+ if (typeof deps?.lookupBySecretHash !== "function") {
132
+ // A programming error in the consumer, not a claim about any caller: it
133
+ // must not be reported as a credential refusal.
134
+ throw new TypeError("validatePresentedBearer: deps.lookupBySecretHash must be a function.");
135
+ }
136
+ if (authHeader === undefined || authHeader === null || authHeader === "") {
137
+ report(deps, "no-header");
138
+ return MISSING;
139
+ }
140
+ // Case-insensitive scheme match without lower-casing the token bytes
141
+ // (RFC 7235 §2.1), identical to `validateMasterBearer`.
142
+ const lower = authHeader.toLowerCase();
143
+ if (!lower.startsWith(BEARER_PREFIX_LOWER)) {
144
+ report(deps, "malformed-header");
145
+ return MALFORMED;
146
+ }
147
+ const token = authHeader.slice(BEARER_PREFIX_LOWER.length).trim();
148
+ if (token.length === 0) {
149
+ report(deps, "malformed-header");
150
+ return MALFORMED;
151
+ }
152
+ const presentedDigest = await sha256Hex(token);
153
+ let row;
154
+ try {
155
+ row = await deps.lookupBySecretHash(presentedDigest);
156
+ }
157
+ catch {
158
+ report(deps, "lookup-failed");
159
+ return UNAVAILABLE;
160
+ }
161
+ if (row === null || row === undefined) {
162
+ return collapse(deps, "unknown-token");
163
+ }
164
+ const parsed = storedBearerRowSchema.safeParse(row);
165
+ if (!parsed.success) {
166
+ return collapse(deps, "row-shape-invalid");
167
+ }
168
+ const stored = parsed.data;
169
+ const encoder = new TextEncoder();
170
+ const digestsMatch = await timingSafeEqual(encoder.encode(stored.secretHash.toLowerCase()), encoder.encode(presentedDigest));
171
+ if (!digestsMatch) {
172
+ return collapse(deps, "stored-digest-mismatch");
173
+ }
174
+ if (stored.revoked === true) {
175
+ return collapse(deps, "revoked-token");
176
+ }
177
+ const nowMs = deps.now ? deps.now() : Date.now();
178
+ if (typeof stored.expiresAt === "number" && stored.expiresAt <= nowMs) {
179
+ return collapse(deps, "expired-token");
180
+ }
181
+ const agent = typeof stored.agentId === "string" && stored.agentId.length > 0
182
+ ? { present: true, agentId: stored.agentId }
183
+ : {
184
+ present: false,
185
+ code: "AGENT_ABSENT",
186
+ reason: "organization-wide-token",
187
+ };
188
+ return {
189
+ ok: true,
190
+ identity: {
191
+ tenantId: stored.tenantId,
192
+ agent,
193
+ scopes: stored.scopes ?? [],
194
+ },
195
+ };
196
+ }
197
+ // ---------------------------------------------------------------------------
198
+ // requireAgentScopedIdentity
199
+ // ---------------------------------------------------------------------------
200
+ /**
201
+ * Narrow a resolved identity to a PER-AGENT surface, refusing when the token
202
+ * carries no agent.
203
+ *
204
+ * An organization-wide token is a legitimate credential that is simply not an
205
+ * agent; treating it as one would silently widen it into whichever agent the
206
+ * surface happened to be about. So this is a second, mandatory call, and its
207
+ * output type is branded: a per-agent handler declares `AgentScopedIdentity`
208
+ * and the only way to obtain one is here.
209
+ *
210
+ * Grants nothing on its own — it can only ever narrow an identity
211
+ * `validatePresentedBearer` already resolved.
212
+ */
213
+ export function requireAgentScopedIdentity(identity) {
214
+ if (!identity.agent.present) {
215
+ return { ok: false, error: "no-agent-on-token" };
216
+ }
217
+ const scoped = {
218
+ tenantId: identity.tenantId,
219
+ agentId: identity.agent.agentId,
220
+ scopes: identity.scopes,
221
+ // The brand exists only in the type system; there is no runtime field to
222
+ // forge, and no value to construct outside this module.
223
+ };
224
+ return { ok: true, identity: scoped };
225
+ }
226
+ //# sourceMappingURL=presented-bearer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presented-bearer.js","sourceRoot":"","sources":["../src/presented-bearer.ts"],"names":[],"mappings":"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"}
@@ -105,9 +105,12 @@ export declare function getEffectiveTenantId(ctx: TenantContext, args: {
105
105
  * - decoder for a bearer that was ALREADY verified by an upstream signed-JWT
106
106
  * middleware (the production trust boundary)
107
107
  *
108
- * For the production trust boundary, callers MUST substitute a signed JWT
109
- * verifier (not yet exported by this package) or an opaque-token lookup
110
- * against a Convex tenancy table.
108
+ * For the production trust boundary on an OPAQUE token, callers MUST use
109
+ * `validatePresentedBearer` (see `./presented-bearer.ts`), which hashes the
110
+ * presented token, looks the row up by digest through a caller-supplied read,
111
+ * re-compares the stored digest in constant time, and honours revocation and
112
+ * expiry. For a SIGNED JWT, callers must substitute a signature verifier; this
113
+ * package does not export one.
111
114
  *
112
115
  * Renamed from `resolveBearer` (0.2.0 pre-release) to make the lack of
113
116
  * verification impossible to miss at the call-site.
@@ -1 +1 @@
1
- {"version":3,"file":"tenancy-domain.d.ts","sourceRoot":"","sources":["../src/tenancy-domain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,mBAAmB;;;;EAAwC,CAAC;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAMhE,eAAO,MAAM,eAAe;;;;;iBAK1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAMxD,eAAO,MAAM,qBAAqB;;;;;;;;;iBAKhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAMpE,eAAO,MAAM,mBAAmB;;;;;;;;iBAI9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAMhE,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,EAAG,iBAAiB,CAAU;IAC3C,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;gBAE5B,OAAO,EAAE,qBAAqB;CAS3C;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GAC5B,MAAM,CASR;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,QAAA,MAAM,mBAAmB;;;;;;;;iBAIvB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,aAAa,CAAC,CAmBxB"}
1
+ {"version":3,"file":"tenancy-domain.d.ts","sourceRoot":"","sources":["../src/tenancy-domain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,mBAAmB;;;;EAAwC,CAAC;AACzE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAMhE,eAAO,MAAM,eAAe;;;;;iBAK1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAMxD,eAAO,MAAM,qBAAqB;;;;;;;;;iBAKhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAMpE,eAAO,MAAM,mBAAmB;;;;;;;;iBAI9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAMhE,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,EAAG,iBAAiB,CAAU;IAC3C,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;gBAE5B,OAAO,EAAE,qBAAqB;CAS3C;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GAC5B,MAAM,CASR;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,QAAA,MAAM,mBAAmB;;;;;;;;iBAIvB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,aAAa,CAAC,CAmBxB"}
@@ -115,9 +115,12 @@ export function getEffectiveTenantId(ctx, args) {
115
115
  * - decoder for a bearer that was ALREADY verified by an upstream signed-JWT
116
116
  * middleware (the production trust boundary)
117
117
  *
118
- * For the production trust boundary, callers MUST substitute a signed JWT
119
- * verifier (not yet exported by this package) or an opaque-token lookup
120
- * against a Convex tenancy table.
118
+ * For the production trust boundary on an OPAQUE token, callers MUST use
119
+ * `validatePresentedBearer` (see `./presented-bearer.ts`), which hashes the
120
+ * presented token, looks the row up by digest through a caller-supplied read,
121
+ * re-compares the stored digest in constant time, and honours revocation and
122
+ * expiry. For a SIGNED JWT, callers must substitute a signature verifier; this
123
+ * package does not export one.
121
124
  *
122
125
  * Renamed from `resolveBearer` (0.2.0 pre-release) to make the lack of
123
126
  * verification impossible to miss at the call-site.
@@ -1 +1 @@
1
- {"version":3,"file":"tenancy-domain.js","sourceRoot":"","sources":["../src/tenancy-domain.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tenancy-domain.js","sourceRoot":"","sources":["../src/tenancy-domain.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.5.0",
3
+ "version": "0.7.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",
@@ -33,6 +33,10 @@
33
33
  "./human-path": {
34
34
  "types": "./dist/human-path.d.ts",
35
35
  "import": "./dist/human-path.js"
36
+ },
37
+ "./presented-bearer": {
38
+ "types": "./dist/presented-bearer.d.ts",
39
+ "import": "./dist/presented-bearer.js"
36
40
  }
37
41
  },
38
42
  "files": [