@vantageos/cloud-identity 0.6.0 → 0.8.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 +121 -0
- package/README.md +147 -0
- package/dist/authorize-flow.d.ts +169 -0
- package/dist/authorize-flow.d.ts.map +1 -0
- package/dist/authorize-flow.js +262 -0
- package/dist/authorize-flow.js.map +1 -0
- package/dist/authorize-shared.d.ts +44 -0
- package/dist/authorize-shared.d.ts.map +1 -0
- package/dist/authorize-shared.js +134 -0
- package/dist/authorize-shared.js.map +1 -0
- package/dist/clerk-session.d.ts +52 -0
- package/dist/clerk-session.d.ts.map +1 -0
- package/dist/clerk-session.js +93 -0
- package/dist/clerk-session.js.map +1 -0
- package/dist/crypto.d.ts +12 -0
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +18 -0
- package/dist/crypto.js.map +1 -1
- package/dist/human-path.d.ts +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/oidc.d.ts +76 -0
- package/dist/oidc.d.ts.map +1 -0
- package/dist/oidc.js +83 -0
- package/dist/oidc.js.map +1 -0
- package/dist/presented-bearer.d.ts +205 -0
- package/dist/presented-bearer.d.ts.map +1 -0
- package/dist/presented-bearer.js +226 -0
- package/dist/presented-bearer.js.map +1 -0
- package/dist/tenancy-domain.d.ts +6 -3
- package/dist/tenancy-domain.d.ts.map +1 -1
- package/dist/tenancy-domain.js +6 -3
- package/dist/tenancy-domain.js.map +1 -1
- package/dist/token-exchange.d.ts +53 -0
- package/dist/token-exchange.d.ts.map +1 -0
- package/dist/token-exchange.js +60 -0
- package/dist/token-exchange.js.map +1 -0
- package/package.json +21 -1
|
@@ -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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"}
|
package/dist/tenancy-domain.d.ts
CHANGED
|
@@ -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
|
|
109
|
-
*
|
|
110
|
-
*
|
|
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
|
|
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"}
|
package/dist/tenancy-domain.js
CHANGED
|
@@ -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
|
|
119
|
-
*
|
|
120
|
-
*
|
|
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":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;AAGzE,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE;CACvD,CAAC,CAAC;AAGH,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,IAAI,EAAE,mBAAmB;IACzB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;CACrB,CAAC,CAAC;AAGH,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC;CACpC,CAAC,CAAC;AAaH;;;;;;GAMG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACnC,IAAI,GAAG,iBAA0B,CAAC;IAClC,OAAO,CAAwB;IAExC,YAAY,OAA8B;QACxC,KAAK,CACH,sCAAsC,OAAO,CAAC,iBAAiB,oCAAoC,OAAO,CAAC,eAAe,OAAO,OAAO,CAAC,MAAM,EAAE,CAClJ,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,kFAAkF;QAClF,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAED,8EAA8E;AAC9E,uBAAuB;AACvB,8EAA8E;AAE9E;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,oBAAoB,CAClC,GAAkB,EAClB,IAA6B;IAE7B,IAAI,IAAI,CAAC,WAAW,KAAK,GAAG,CAAC,WAAW,EAAE,CAAC;QACzC,MAAM,IAAI,mBAAmB,CAAC;YAC5B,iBAAiB,EAAE,IAAI,CAAC,WAAW;YACnC,eAAe,EAAE,GAAG,CAAC,WAAW;YAChC,MAAM,EAAE,qEAAqE;SAC9E,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC,WAAW,CAAC;AACzB,CAAC;AAED,8EAA8E;AAC9E,yBAAyB;AACzB,8EAA8E;AAE9E
|
|
1
|
+
{"version":3,"file":"tenancy-domain.js","sourceRoot":"","sources":["../src/tenancy-domain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AuthorizationCodeStore } from "./authorize-flow.js";
|
|
2
|
+
import { type AuthorizeRefusal } from "./authorize-shared.js";
|
|
3
|
+
/**
|
|
4
|
+
* The claims a token minted from an authorization code carries. They are
|
|
5
|
+
* copied from the code's own binding, which was written from the verified
|
|
6
|
+
* Clerk user and a membership that user holds. There is no `scopeProfile`
|
|
7
|
+
* here and no code path that reads one: an identity is never supplied by the
|
|
8
|
+
* client-registration row.
|
|
9
|
+
*/
|
|
10
|
+
export interface AuthorizedTokenClaims {
|
|
11
|
+
sub: string;
|
|
12
|
+
org_id: string;
|
|
13
|
+
org_slug: string | null;
|
|
14
|
+
org_role: string;
|
|
15
|
+
aud: string;
|
|
16
|
+
client_id: string;
|
|
17
|
+
scope: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ExchangeInput {
|
|
20
|
+
/** The authorization code the client presents. */
|
|
21
|
+
code: string;
|
|
22
|
+
/** PKCE verifier (RFC 7636). */
|
|
23
|
+
codeVerifier: string;
|
|
24
|
+
/** Must equal the redirect_uri of the authorize request. */
|
|
25
|
+
redirectUri: string;
|
|
26
|
+
/** The authenticated client's id. */
|
|
27
|
+
clientId: string;
|
|
28
|
+
/** Optional RFC 8707 resource; when present it must equal the bound one. */
|
|
29
|
+
resource?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ExchangeDeps {
|
|
32
|
+
codeStore: AuthorizationCodeStore;
|
|
33
|
+
/** Epoch milliseconds. */
|
|
34
|
+
now?: () => number;
|
|
35
|
+
}
|
|
36
|
+
export type ExchangeResult = {
|
|
37
|
+
ok: true;
|
|
38
|
+
claims: AuthorizedTokenClaims;
|
|
39
|
+
} | {
|
|
40
|
+
ok: false;
|
|
41
|
+
refusal: AuthorizeRefusal;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Redeems an authorization code. The code is CONSUMED first (atomically, by
|
|
45
|
+
* the store) so that every attempt, successful or not, burns it: a wrong
|
|
46
|
+
* verifier cannot be retried against the same code. A code presented twice is
|
|
47
|
+
* `code-reused`, distinct from `code-unknown`, so the consumer can revoke what
|
|
48
|
+
* the first redemption issued.
|
|
49
|
+
*
|
|
50
|
+
* Checks, in order: expiry, client, redirect_uri, resource, PKCE.
|
|
51
|
+
*/
|
|
52
|
+
export declare function exchangeAuthorizationCode(input: ExchangeInput, deps: ExchangeDeps): Promise<ExchangeResult>;
|
|
53
|
+
//# sourceMappingURL=token-exchange.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-exchange.d.ts","sourceRoot":"","sources":["../src/token-exchange.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EACL,KAAK,gBAAgB,EAKtB,MAAM,uBAAuB,CAAC;AAG/B;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,sBAAsB,CAAC;IAClC,0BAA0B;IAC1B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,qBAAqB,CAAA;CAAE,GAC3C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAO7C;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,cAAc,CAAC,CAuCzB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { isWellFormedVerifier, pkceChallengeFromVerifier, refusal, utf8, } from "./authorize-shared.js";
|
|
2
|
+
import { sha256Hex, timingSafeEqual } from "./crypto.js";
|
|
3
|
+
const fail = (reason) => ({
|
|
4
|
+
ok: false,
|
|
5
|
+
refusal: refusal(reason),
|
|
6
|
+
});
|
|
7
|
+
/**
|
|
8
|
+
* Redeems an authorization code. The code is CONSUMED first (atomically, by
|
|
9
|
+
* the store) so that every attempt, successful or not, burns it: a wrong
|
|
10
|
+
* verifier cannot be retried against the same code. A code presented twice is
|
|
11
|
+
* `code-reused`, distinct from `code-unknown`, so the consumer can revoke what
|
|
12
|
+
* the first redemption issued.
|
|
13
|
+
*
|
|
14
|
+
* Checks, in order: expiry, client, redirect_uri, resource, PKCE.
|
|
15
|
+
*/
|
|
16
|
+
export async function exchangeAuthorizationCode(input, deps) {
|
|
17
|
+
if (typeof input.code !== "string" || input.code.length === 0) {
|
|
18
|
+
return fail("code-unknown");
|
|
19
|
+
}
|
|
20
|
+
let consumed;
|
|
21
|
+
try {
|
|
22
|
+
consumed = await deps.codeStore.consume(await sha256Hex(input.code));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return fail("code-store-unavailable");
|
|
26
|
+
}
|
|
27
|
+
if (consumed.status === "unknown")
|
|
28
|
+
return fail("code-unknown");
|
|
29
|
+
if (consumed.status === "already-used")
|
|
30
|
+
return fail("code-reused");
|
|
31
|
+
const rec = consumed.record;
|
|
32
|
+
if (rec.expiresAt <= (deps.now ?? Date.now)())
|
|
33
|
+
return fail("code-expired");
|
|
34
|
+
if (rec.clientId !== input.clientId)
|
|
35
|
+
return fail("client-mismatch");
|
|
36
|
+
if (rec.redirectUri !== input.redirectUri)
|
|
37
|
+
return fail("redirect-uri-changed");
|
|
38
|
+
if (input.resource !== undefined && input.resource !== rec.resource) {
|
|
39
|
+
return fail("resource-mismatch");
|
|
40
|
+
}
|
|
41
|
+
if (!isWellFormedVerifier(input.codeVerifier))
|
|
42
|
+
return fail("verifier-malformed");
|
|
43
|
+
const derived = await pkceChallengeFromVerifier(input.codeVerifier);
|
|
44
|
+
if (!(await timingSafeEqual(utf8(derived), utf8(rec.codeChallenge)))) {
|
|
45
|
+
return fail("pkce-mismatch");
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
claims: {
|
|
50
|
+
sub: rec.clerkUserId,
|
|
51
|
+
org_id: rec.orgId,
|
|
52
|
+
org_slug: rec.orgSlug,
|
|
53
|
+
org_role: rec.orgRole,
|
|
54
|
+
aud: rec.resource,
|
|
55
|
+
client_id: rec.clientId,
|
|
56
|
+
scope: rec.scope,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=token-exchange.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-exchange.js","sourceRoot":"","sources":["../src/token-exchange.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,oBAAoB,EACpB,yBAAyB,EACzB,OAAO,EACP,IAAI,GACL,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA0CzD,MAAM,IAAI,GAAG,CAAC,MAAqC,EAAkB,EAAE,CAAC,CAAC;IACvE,EAAE,EAAE,KAAK;IACT,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC;CACzB,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,KAAoB,EACpB,IAAkB;IAElB,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,wBAAwB,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC;IAC/D,IAAI,QAAQ,CAAC,MAAM,KAAK,cAAc;QAAE,OAAO,IAAI,CAAC,aAAa,CAAC,CAAC;IACnE,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC;IAE5B,IAAI,GAAG,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC,cAAc,CAAC,CAAC;IAC3E,IAAI,GAAG,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACpE,IAAI,GAAG,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAC/E,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,GAAG,CAAC,QAAQ,EAAE,CAAC;QACpE,OAAO,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACjF,MAAM,OAAO,GAAG,MAAM,yBAAyB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACpE,IAAI,CAAC,CAAC,MAAM,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACrE,OAAO,IAAI,CAAC,eAAe,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI;QACR,MAAM,EAAE;YACN,GAAG,EAAE,GAAG,CAAC,WAAW;YACpB,MAAM,EAAE,GAAG,CAAC,KAAK;YACjB,QAAQ,EAAE,GAAG,CAAC,OAAO;YACrB,QAAQ,EAAE,GAAG,CAAC,OAAO;YACrB,GAAG,EAAE,GAAG,CAAC,QAAQ;YACjB,SAAS,EAAE,GAAG,CAAC,QAAQ;YACvB,KAAK,EAAE,GAAG,CAAC,KAAK;SACjB;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vantageos/cloud-identity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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,26 @@
|
|
|
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"
|
|
40
|
+
},
|
|
41
|
+
"./authorize": {
|
|
42
|
+
"types": "./dist/authorize-flow.d.ts",
|
|
43
|
+
"import": "./dist/authorize-flow.js"
|
|
44
|
+
},
|
|
45
|
+
"./token-exchange": {
|
|
46
|
+
"types": "./dist/token-exchange.d.ts",
|
|
47
|
+
"import": "./dist/token-exchange.js"
|
|
48
|
+
},
|
|
49
|
+
"./clerk-session": {
|
|
50
|
+
"types": "./dist/clerk-session.d.ts",
|
|
51
|
+
"import": "./dist/clerk-session.js"
|
|
52
|
+
},
|
|
53
|
+
"./oidc": {
|
|
54
|
+
"types": "./dist/oidc.d.ts",
|
|
55
|
+
"import": "./dist/oidc.js"
|
|
36
56
|
}
|
|
37
57
|
},
|
|
38
58
|
"files": [
|