@dnsid-ai/oidc 0.20.1

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/dist/index.cjs ADDED
@@ -0,0 +1,911 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ OAuthError: () => OAuthError,
34
+ OIDCProfile: () => OIDCProfile,
35
+ OIDCTokenMinter: () => OIDCTokenMinter,
36
+ createOIDCKeyProviderFromJWK: () => createOIDCKeyProviderFromJWK,
37
+ createOIDCProfile: () => createOIDCProfile,
38
+ createOIDCTokenMinter: () => createOIDCTokenMinter,
39
+ decodeOIDCClaims: () => decodeOIDCClaims,
40
+ mintOIDCToken: () => mintOIDCToken,
41
+ validateExactOIDCIssuer: () => validateExactOIDCIssuer
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+ var dnsPromises = __toESM(require("dns/promises"), 1);
45
+ var net = __toESM(require("net"), 1);
46
+ var import_transport = require("@dnsid-ai/transport");
47
+ var import_jose = require("jose");
48
+ var import_protocol = require("@dnsid-ai/protocol");
49
+ var import_protocol2 = require("@dnsid-ai/protocol");
50
+ var OAuthError = class extends Error {
51
+ /** The RFC 6749 error code, when the endpoint provided one. */
52
+ error;
53
+ /** Human-readable error description, when the endpoint provided one. */
54
+ errorDescription;
55
+ constructor(error, errorDescription) {
56
+ super(errorDescription ? `${error}: ${errorDescription}` : error ?? "OIDC token exchange failed");
57
+ this.name = "OAuthError";
58
+ this.error = error;
59
+ this.errorDescription = errorDescription;
60
+ }
61
+ };
62
+ function validateOidcTimes(config) {
63
+ const lifetime = config.assertionLifetime === void 0 ? 300 : config.assertionLifetime;
64
+ const maximum = config.maxAssertionLifetime === void 0 ? 900 : config.maxAssertionLifetime;
65
+ const skew = config.clockSkew === void 0 ? 30 : config.clockSkew;
66
+ if (!Number.isFinite(lifetime) || lifetime <= 0 || !Number.isFinite(maximum) || maximum <= 0 || !Number.isFinite(skew) || skew < 0) throw new import_protocol2.ArgumentError("invalid OIDC lifetime or clock skew");
67
+ }
68
+ function validateTokenTimes(claims, skew) {
69
+ const now = Date.now() / 1e3;
70
+ const fail = (message) => {
71
+ throw new import_protocol2.VerificationError(message, { code: import_protocol2.VerificationCode.RecordInvalid });
72
+ };
73
+ if (typeof claims.exp !== "number" || !Number.isFinite(claims.exp)) fail("OIDC token missing exp");
74
+ if (typeof claims.iat !== "number" || !Number.isFinite(claims.iat)) fail("OIDC token missing iat");
75
+ if (claims.nbf !== void 0 && (typeof claims.nbf !== "number" || !Number.isFinite(claims.nbf))) fail("OIDC token invalid nbf");
76
+ if (claims.exp <= claims.iat) fail("OIDC token exp must be after iat");
77
+ if (now >= claims.exp) fail("OIDC token is expired");
78
+ if (claims.iat > now + skew || claims.nbf !== void 0 && claims.nbf > now + skew) fail("OIDC token not yet valid");
79
+ }
80
+ var DEFAULT_SCOPE = "openid";
81
+ var DEFAULT_ASSERTION_LIFETIME_SECONDS = 300;
82
+ var DEFAULT_MAX_ASSERTION_LIFETIME_SECONDS = 900;
83
+ var DEFAULT_CLOCK_SKEW_SECONDS = 30;
84
+ var DEFAULT_TOKEN_ALGS = ["RS256"];
85
+ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
86
+ var MAX_OIDC_JSON_BYTES = 1048576;
87
+ var RESERVED_ASSERTION_CLAIMS = /* @__PURE__ */ new Set(["iss", "sub", "aud", "iat", "exp", "jti", "fqdn"]);
88
+ var OIDCTokenMinter = class {
89
+ domain;
90
+ keyProvider;
91
+ fetch;
92
+ defaultEndpointOptions;
93
+ defaultScope;
94
+ assertionLifetime;
95
+ maxAssertionLifetime;
96
+ timeoutMs;
97
+ allowHttpLoopbackIssuer;
98
+ /**
99
+ * @throws ArgumentError if domain is not a valid agent FQDN, the endpoint
100
+ * options mix modes (see {@link OIDCTokenEndpointOptions}), or timeoutMs
101
+ * is not a positive number.
102
+ */
103
+ constructor(opts) {
104
+ try {
105
+ this.domain = (0, import_protocol2.normalizeFQDN)(opts.domain, true);
106
+ } catch (e) {
107
+ throw new import_protocol2.ArgumentError(`domain is not a valid agent FQDN: ${e.message}`);
108
+ }
109
+ this.keyProvider = opts.keyProvider;
110
+ this.fetch = opts.fetch ?? (0, import_transport.createSsrfSafeFetch)();
111
+ this.defaultEndpointOptions = validateEndpointMode({
112
+ issuer: opts.issuer,
113
+ serverUrl: opts.serverUrl,
114
+ discoveryUrl: opts.discoveryUrl,
115
+ tokenEndpoint: opts.tokenEndpoint
116
+ });
117
+ this.defaultScope = opts.defaultScope;
118
+ validateOidcTimes(opts);
119
+ this.assertionLifetime = opts.assertionLifetime;
120
+ this.maxAssertionLifetime = opts.maxAssertionLifetime;
121
+ this.timeoutMs = validateTimeoutMs(opts.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS);
122
+ this.allowHttpLoopbackIssuer = opts.allowHttpLoopbackIssuer ?? false;
123
+ }
124
+ /**
125
+ * Mints a signed JWT bearer client assertion for the given issuer
126
+ * (iss/sub/fqdn = agent domain, aud = issuer, fresh jti).
127
+ *
128
+ * @throws ArgumentError if the issuer URL is invalid, the expiry is not
129
+ * positive or exceeds the maximum lifetime, or additionalClaims override
130
+ * a reserved claim.
131
+ * @throws ValidationError if the operational signing key is unsupported for JWS.
132
+ * @throws VerificationError (SignatureInvalid) if the produced signature does
133
+ * not verify against the active operational key.
134
+ */
135
+ async createAssertion(opts) {
136
+ return createOIDCAssertionJWT(this.domain, this.keyProvider, opts, {
137
+ assertionLifetime: this.assertionLifetime,
138
+ maxAssertionLifetime: this.maxAssertionLifetime,
139
+ allowHttpLoopbackIssuer: this.allowHttpLoopbackIssuer
140
+ });
141
+ }
142
+ /**
143
+ * Resolves the token endpoint (per the configured or per-call endpoint mode),
144
+ * mints a fresh assertion, and performs the JWT bearer token exchange.
145
+ *
146
+ * @returns The normalized token response.
147
+ * @throws ArgumentError if audience is missing or the options are inconsistent.
148
+ * @throws VerificationError if discovery or transport fails, the target
149
+ * resolves to an unsafe address, or the response is malformed.
150
+ * @throws OAuthError if the token endpoint returns an OAuth error response.
151
+ */
152
+ async mintToken(opts) {
153
+ if (!opts.audience) throw new import_protocol2.ArgumentError("audience is required");
154
+ const endpoint = await resolveOIDCTokenEndpoint(this.fetch, endpointOptionsForCall(this.defaultEndpointOptions, opts), {
155
+ allowHttpLoopbackIssuer: this.allowHttpLoopbackIssuer,
156
+ timeoutMs: this.timeoutMs
157
+ });
158
+ const assertion = await this.createAssertion({
159
+ issuer: endpoint.issuer,
160
+ additionalClaims: opts.additionalAssertionClaims
161
+ });
162
+ return exchangeOIDCTokenAt(this.fetch, {
163
+ issuer: endpoint.issuer,
164
+ tokenEndpoint: endpoint.tokenEndpoint,
165
+ assertion,
166
+ audience: opts.audience,
167
+ scope: resolveScope(opts.scope, opts.scopes, this.defaultScope),
168
+ allowHttpLoopbackIssuer: this.allowHttpLoopbackIssuer,
169
+ timeoutMs: this.timeoutMs
170
+ });
171
+ }
172
+ };
173
+ async function createOIDCTokenMinter(opts) {
174
+ return new OIDCTokenMinter({
175
+ ...opts,
176
+ keyProvider: await resolveOIDCKeyProvider(opts)
177
+ });
178
+ }
179
+ async function mintOIDCToken(opts) {
180
+ const minter = await createOIDCTokenMinter(opts);
181
+ return minter.mintToken(opts);
182
+ }
183
+ async function createOIDCKeyProviderFromJWK(privateJwk) {
184
+ return PrivateJWKKeyProvider.fromJWK(privateJwk);
185
+ }
186
+ var OIDCProfile = class _OIDCProfile {
187
+ /** Builds a profile sharing an identity manager's domain, operational key provider, and identity resolver. */
188
+ static fromIdentityManager(identityManager, oidc) {
189
+ return new _OIDCProfile({
190
+ domain: (0, import_protocol.requireLocalDomain)(identityManager),
191
+ keyProvider: identityManager.getKeyProvider(),
192
+ identityResolver: identityManager,
193
+ oidc
194
+ });
195
+ }
196
+ domain;
197
+ keyProvider;
198
+ identityResolver;
199
+ fetch;
200
+ config;
201
+ /** @throws ArgumentError if domain is not a valid agent FQDN. */
202
+ constructor(opts) {
203
+ try {
204
+ this.domain = (0, import_protocol2.normalizeFQDN)(opts.domain, true);
205
+ } catch (e) {
206
+ throw new import_protocol2.ArgumentError(`domain is not a valid agent FQDN: ${e.message}`);
207
+ }
208
+ this.keyProvider = opts.keyProvider ?? null;
209
+ this.identityResolver = opts.identityResolver;
210
+ this.fetch = opts.fetch ?? (0, import_transport.createSsrfSafeFetch)();
211
+ this.config = Object.freeze({ ...opts.oidc });
212
+ validateOidcTimes(this.config);
213
+ }
214
+ /**
215
+ * Mints a signed JWT bearer client assertion for the given issuer
216
+ * (iss/sub/fqdn = agent domain, aud = issuer, fresh jti).
217
+ *
218
+ * @throws ArgumentError if the issuer URL is invalid, the expiry is not
219
+ * positive or exceeds the maximum lifetime, or additionalClaims override
220
+ * a reserved claim.
221
+ * @throws ValidationError if the operational signing key is unsupported for JWS.
222
+ * @throws VerificationError (SignatureInvalid) if the produced signature does
223
+ * not verify against the active operational key.
224
+ */
225
+ async createOIDCAssertion(opts) {
226
+ if (!this.keyProvider) throw new import_protocol2.ArgumentError("OIDC assertion signing requires a keyProvider");
227
+ return createOIDCAssertionJWT(this.domain, this.keyProvider, opts, {
228
+ assertionLifetime: this.config.assertionLifetime,
229
+ maxAssertionLifetime: this.config.maxAssertionLifetime,
230
+ allowHttpLoopbackIssuer: this.config.allowHttpLoopbackIssuer
231
+ });
232
+ }
233
+ /**
234
+ * Fetches and validates the issuer's discovery document. The document's
235
+ * issuer must match exactly, and token_endpoint/jwks_uri must share the
236
+ * issuer's origin.
237
+ *
238
+ * @throws ArgumentError if the issuer is not an exact HTTPS URL.
239
+ * @throws VerificationError if the fetch fails, redirects, or the document is invalid.
240
+ */
241
+ async discoverOIDCIssuer(issuer, options = {}) {
242
+ return (0, import_protocol2.withVerificationBudget)((signal) => discoverExplicitOIDCIssuer(this.fetch, issuer, void 0, {
243
+ allowHttpLoopbackIssuer: this.config.allowHttpLoopbackIssuer,
244
+ timeoutMs: this.config.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS,
245
+ signal
246
+ }), options);
247
+ }
248
+ /**
249
+ * Discovers the issuer and performs the RFC 7523 JWT bearer exchange. When
250
+ * `assertion` is supplied it is presented as-is (its aud must exactly match
251
+ * the discovered issuer); otherwise a fresh assertion is minted.
252
+ *
253
+ * @returns The normalized token response.
254
+ * @throws ArgumentError if audience is missing or a supplied assertion is
255
+ * not a valid JWT addressed to the issuer.
256
+ * @throws VerificationError if discovery or transport fails, or the response is malformed.
257
+ * @throws OAuthError if the token endpoint returns an OAuth error response.
258
+ */
259
+ async exchangeOIDCToken(opts) {
260
+ if (!opts.assertion && !this.keyProvider) {
261
+ throw new import_protocol2.ArgumentError("OIDC assertion signing requires a keyProvider");
262
+ }
263
+ if (!opts.audience) throw new import_protocol2.ArgumentError("audience is required");
264
+ const doc = await this.discoverOIDCIssuer(opts.issuer);
265
+ const assertion = opts.assertion ?? await this.createOIDCAssertion({ issuer: doc.issuer });
266
+ if (opts.assertion) {
267
+ let assertionClaims;
268
+ try {
269
+ assertionClaims = (0, import_jose.decodeJwt)(opts.assertion);
270
+ } catch {
271
+ throw new import_protocol2.ArgumentError("OIDC assertion must be a valid JWT");
272
+ }
273
+ if (!audienceExactlyMatches(assertionClaims.aud, doc.issuer)) {
274
+ throw new import_protocol2.ArgumentError("OIDC assertion audience must exactly match issuer");
275
+ }
276
+ }
277
+ return exchangeOIDCTokenAt(this.fetch, {
278
+ issuer: doc.issuer,
279
+ tokenEndpoint: doc.token_endpoint,
280
+ assertion,
281
+ audience: opts.audience,
282
+ scope: resolveScope(opts.scope, void 0, this.config.defaultScope),
283
+ allowHttpLoopbackIssuer: this.config.allowHttpLoopbackIssuer,
284
+ timeoutMs: this.config.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS
285
+ });
286
+ }
287
+ /** Like {@link OIDCProfile.exchangeOIDCToken} but always mints a fresh assertion, ignoring any supplied one. */
288
+ getOIDCToken(opts) {
289
+ const { assertion: _assertion, ...fresh } = opts;
290
+ return this.exchangeOIDCToken(fresh);
291
+ }
292
+ /**
293
+ * Verifies an OIDC token end-to-end: issuer allow-list, exact audience
294
+ * match, header and claim hygiene, signature against the issuer's published
295
+ * JWKS (restricted to allowedTokenAlgorithms), and timestamp checks with the
296
+ * configured clock skew. Unless `verifyDnsidSubject` is false, the token
297
+ * subject is then verified as a DNSid identity record via the profile's
298
+ * identityResolver.
299
+ *
300
+ * @returns The verified subject, claims, and (unless skipped) the DNSid
301
+ * verification result for the subject domain.
302
+ * @throws ArgumentError if the issuer URL is invalid or audience is missing.
303
+ * @throws VerificationError with a VerificationCode (RecordInvalid,
304
+ * SignatureInvalid, or TLSError) identifying the first check that failed —
305
+ * including when the issuer is not in allowedIssuers, or when subject
306
+ * verification is requested without an identityResolver.
307
+ */
308
+ async verifyOIDCToken(token, opts) {
309
+ return (0, import_protocol2.withVerificationBudget)((signal) => this.verifyTokenWithinBudget(token, { ...opts, signal }), opts);
310
+ }
311
+ async verifyTokenWithinBudget(token, opts) {
312
+ const issuer = validateExactOIDCIssuer(opts.issuer, this.config.allowHttpLoopbackIssuer);
313
+ if (!(this.config.allowedIssuers ?? []).includes(issuer)) {
314
+ throw new import_protocol2.VerificationError("OIDC issuer is not allowed", { code: import_protocol2.VerificationCode.RecordInvalid });
315
+ }
316
+ if (typeof opts.audience !== "string" || !opts.audience) throw new import_protocol2.ArgumentError("audience is required");
317
+ let header;
318
+ let claims;
319
+ try {
320
+ const compact = (0, import_protocol2.parseCompactJose)(token);
321
+ header = compact.header;
322
+ claims = (0, import_protocol2.parseJoseObject)(compact.payload);
323
+ } catch {
324
+ throw new import_protocol2.VerificationError("malformed JWT", { code: import_protocol2.VerificationCode.RecordInvalid });
325
+ }
326
+ if (claims.iss !== issuer) throw new import_protocol2.VerificationError("OIDC issuer mismatch", { code: import_protocol2.VerificationCode.RecordInvalid });
327
+ if (Object.keys(header).some((k) => !["alg", "kid", "typ"].includes(k))) {
328
+ throw new import_protocol2.VerificationError("unsupported OIDC token header", { code: import_protocol2.VerificationCode.RecordInvalid });
329
+ }
330
+ if (!audienceExactlyMatches(claims.aud, opts.audience)) {
331
+ throw new import_protocol2.VerificationError("OIDC audience mismatch", { code: import_protocol2.VerificationCode.RecordInvalid });
332
+ }
333
+ const allowedAlgs = validateAllowedTokenAlgorithms(this.config.allowedTokenAlgorithms ?? DEFAULT_TOKEN_ALGS);
334
+ if (!allowedAlgs.includes(header.alg)) throw new import_protocol2.VerificationError("OIDC token algorithm is not allowed", { code: import_protocol2.VerificationCode.SignatureInvalid });
335
+ validateTokenTimes(claims, this.config.clockSkew ?? DEFAULT_CLOCK_SKEW_SECONDS);
336
+ const doc = await this.discoverOIDCIssuer(issuer, { signal: opts.signal });
337
+ const jwksResponse = await fetchOIDC(this.fetch, doc.jwks_uri, { redirect: "manual" }, {
338
+ signal: opts.signal,
339
+ allowHttpLoopbackIssuer: this.config.allowHttpLoopbackIssuer,
340
+ timeoutMs: this.config.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS
341
+ });
342
+ if (jwksResponse.status !== 200) throw new import_protocol2.VerificationError("OIDC JWKS fetch failed", { code: import_protocol2.VerificationCode.TLSError });
343
+ const jwks = requireRecord(await (0, import_protocol2.waitForVerification)(() => readLimitedJson(jwksResponse), opts.signal), "OIDC JWKS must be a JSON object");
344
+ if (!Array.isArray(jwks.keys)) {
345
+ throw new import_protocol2.VerificationError("OIDC JWKS keys must be an array", { code: import_protocol2.VerificationCode.RecordInvalid });
346
+ }
347
+ if (typeof header.alg !== "string") {
348
+ throw new import_protocol2.VerificationError("OIDC token missing signing algorithm", { code: import_protocol2.VerificationCode.SignatureInvalid });
349
+ }
350
+ if (!allowedAlgs.includes(header.alg)) {
351
+ throw new import_protocol2.VerificationError(`OIDC token algorithm is not allowed: ${header.alg}`, { code: import_protocol2.VerificationCode.SignatureInvalid });
352
+ }
353
+ const signingKey = jwks.keys.find((k) => typeof k === "object" && k !== null && k.kid === header.kid);
354
+ if (!signingKey || !oidcKeySupportsAlg(signingKey, header.alg)) {
355
+ throw new import_protocol2.VerificationError("OIDC signing key mismatch", { code: import_protocol2.VerificationCode.SignatureInvalid });
356
+ }
357
+ const clockSkew = this.config.clockSkew ?? DEFAULT_CLOCK_SKEW_SECONDS;
358
+ validateTokenTimes(claims, clockSkew);
359
+ let verifiedClaims;
360
+ try {
361
+ await (0, import_jose.compactVerify)(token, await (0, import_jose.importJWK)(signingKey, header.alg), { algorithms: allowedAlgs });
362
+ verifiedClaims = claims;
363
+ } catch (e) {
364
+ throw new import_protocol2.VerificationError(`OIDC token verification failed: ${e.message}`, { code: import_protocol2.VerificationCode.SignatureInvalid });
365
+ }
366
+ if (typeof verifiedClaims.sub !== "string" || !verifiedClaims.sub) {
367
+ throw new import_protocol2.VerificationError("OIDC token missing sub", { code: import_protocol2.VerificationCode.RecordInvalid });
368
+ }
369
+ if (opts.verifyDnsidSubject !== false && !this.identityResolver) {
370
+ throw new import_protocol2.VerificationError("OIDC token subject verification requires an identityResolver", { code: import_protocol2.VerificationCode.RecordInvalid });
371
+ }
372
+ const verifiedDomain = opts.verifyDnsidSubject === false ? void 0 : await (0, import_protocol2.waitForVerification)(() => this.identityResolver.verifyDomain(verifiedClaims.sub, opts.peerCert, { signal: opts.signal }), opts.signal);
373
+ validateTokenTimes(verifiedClaims, clockSkew);
374
+ return { issuer, subject: verifiedClaims.sub, audience: opts.audience, verifiedDomain, claims: verifiedClaims };
375
+ }
376
+ };
377
+ function createOIDCProfile(opts) {
378
+ return new OIDCProfile(opts);
379
+ }
380
+ async function resolveOIDCKeyProvider(opts) {
381
+ if (opts.keyProvider && opts.privateJwk) {
382
+ throw new import_protocol2.ArgumentError("provide either keyProvider or privateJwk, not both");
383
+ }
384
+ if (opts.keyProvider) return opts.keyProvider;
385
+ if (opts.privateJwk) return createOIDCKeyProviderFromJWK(opts.privateJwk);
386
+ throw new import_protocol2.ArgumentError("keyProvider or privateJwk is required");
387
+ }
388
+ var PrivateJWKKeyProvider = class _PrivateJWKKeyProvider {
389
+ constructor(publicJwk, privateKey) {
390
+ this.publicJwk = publicJwk;
391
+ this.privateKey = privateKey;
392
+ }
393
+ publicJwk;
394
+ privateKey;
395
+ static async fromJWK(privateJwk) {
396
+ if (!privateJwk || typeof privateJwk !== "object" || typeof privateJwk.d !== "string" || privateJwk.d === "") {
397
+ throw new import_protocol2.ArgumentError("privateJwk must be a private JWK with a d member");
398
+ }
399
+ const publicJwk = publicJwkFromPrivateJWK(privateJwk);
400
+ if (!publicJwk.kid) publicJwk.kid = await (0, import_protocol2.jwkThumbprint)(publicJwk);
401
+ if (!publicJwk.alg) publicJwk.alg = (0, import_protocol2.jwkSignatureAlg)(publicJwk);
402
+ if (!publicJwk.use) publicJwk.use = "sig";
403
+ const alg = (0, import_protocol2.jwkSignatureAlg)(publicJwk);
404
+ const privateKey = await crypto.subtle.importKey(
405
+ "jwk",
406
+ privateJwk,
407
+ importAlgorithmForSigningAlg(alg, publicJwk),
408
+ false,
409
+ ["sign"]
410
+ );
411
+ return new _PrivateJWKKeyProvider(publicJwk, privateKey);
412
+ }
413
+ async signingKey() {
414
+ return { ...this.publicJwk };
415
+ }
416
+ async jwk(kid) {
417
+ if (kid !== this.publicJwk.kid) throw new import_protocol2.ArgumentError(`key not found: ${kid}`);
418
+ return this.signingKey();
419
+ }
420
+ async listKeyIds() {
421
+ return [this.publicJwk.kid];
422
+ }
423
+ async sign(payload) {
424
+ const sig = await crypto.subtle.sign(
425
+ signAlgorithmForSigningAlg((0, import_protocol2.jwkSignatureAlg)(this.publicJwk)),
426
+ this.privateKey,
427
+ (0, import_protocol2.toArrayBuffer)(payload)
428
+ );
429
+ return new Uint8Array(sig);
430
+ }
431
+ async signKey(kid, payload) {
432
+ if (kid !== this.publicJwk.kid) throw new import_protocol2.ArgumentError(`key not found: ${kid}`);
433
+ return this.sign(payload);
434
+ }
435
+ async generateKey() {
436
+ throw new import_protocol2.ArgumentError("private JWK key provider does not support key generation");
437
+ }
438
+ async activate(_kid) {
439
+ throw new import_protocol2.ArgumentError("private JWK key provider does not support key activation");
440
+ }
441
+ async supersede(_kid) {
442
+ throw new import_protocol2.ArgumentError("private JWK key provider does not support key purging");
443
+ }
444
+ /** @deprecated Use supersede(). */
445
+ async purge(kid) {
446
+ await this.supersede(kid);
447
+ }
448
+ };
449
+ function publicJwkFromPrivateJWK(privateJwk) {
450
+ const publicJwk = { kty: privateJwk.kty, kid: privateJwk.kid ?? "" };
451
+ copyStringMember(privateJwk, publicJwk, "alg");
452
+ copyStringMember(privateJwk, publicJwk, "use");
453
+ copyStringMember(privateJwk, publicJwk, "crv");
454
+ copyStringMember(privateJwk, publicJwk, "x");
455
+ copyStringMember(privateJwk, publicJwk, "y");
456
+ copyStringMember(privateJwk, publicJwk, "n");
457
+ copyStringMember(privateJwk, publicJwk, "e");
458
+ return publicJwk;
459
+ }
460
+ function copyStringMember(source, target, key) {
461
+ if (typeof source[key] === "string") target[key] = source[key];
462
+ }
463
+ async function createOIDCAssertionJWT(domain, keyProvider, opts, config) {
464
+ const issuer = validateExactOIDCIssuer(opts.issuer, config.allowHttpLoopbackIssuer);
465
+ validateOidcTimes(config);
466
+ const expiry = opts.expiry === void 0 ? config.assertionLifetime ?? DEFAULT_ASSERTION_LIFETIME_SECONDS : opts.expiry;
467
+ const maxLifetime = config.maxAssertionLifetime ?? DEFAULT_MAX_ASSERTION_LIFETIME_SECONDS;
468
+ if (!Number.isFinite(expiry) || expiry <= 0) throw new import_protocol2.ArgumentError("OIDC assertion expiry must be positive");
469
+ if (expiry > maxLifetime) throw new import_protocol2.ArgumentError("OIDC assertion expiry exceeds maximum lifetime");
470
+ const now = Math.floor(Date.now() / 1e3);
471
+ if (now + expiry <= now) throw new import_protocol2.ArgumentError("OIDC assertion expiry is below timestamp resolution");
472
+ const claims = {
473
+ iss: domain,
474
+ sub: domain,
475
+ aud: [issuer],
476
+ iat: now,
477
+ exp: now + expiry,
478
+ jti: crypto.randomUUID(),
479
+ fqdn: domain
480
+ };
481
+ for (const [k, v] of Object.entries(opts.additionalClaims ?? {})) {
482
+ if (RESERVED_ASSERTION_CLAIMS.has(k)) {
483
+ throw new import_protocol2.ArgumentError(`additionalClaims must not override reserved claim: ${k}`);
484
+ }
485
+ claims[k] = v;
486
+ }
487
+ const signingKey = await keyProvider.signingKey();
488
+ const alg = (0, import_protocol2.jwkSignatureAlg)(signingKey);
489
+ return signCompact({ alg, kid: signingKey.kid, typ: "JWT" }, claims, keyProvider, signingKey, alg);
490
+ }
491
+ function endpointOptionsForCall(defaults, overrides) {
492
+ if (overrides.tokenEndpoint !== void 0) {
493
+ return validateEndpointMode({
494
+ issuer: overrides.issuer ?? (overrides.serverUrl === void 0 ? defaults.issuer : void 0),
495
+ serverUrl: overrides.serverUrl,
496
+ discoveryUrl: overrides.discoveryUrl,
497
+ tokenEndpoint: overrides.tokenEndpoint
498
+ });
499
+ }
500
+ if (overrides.issuer !== void 0 || overrides.serverUrl !== void 0) {
501
+ return validateEndpointMode({
502
+ issuer: overrides.issuer,
503
+ serverUrl: overrides.serverUrl,
504
+ discoveryUrl: overrides.discoveryUrl
505
+ });
506
+ }
507
+ return validateEndpointMode({ ...defaults, discoveryUrl: overrides.discoveryUrl ?? defaults.discoveryUrl });
508
+ }
509
+ function validateEndpointMode(opts) {
510
+ if (opts.tokenEndpoint !== void 0) {
511
+ if (opts.tokenEndpoint === "") throw new import_protocol2.ArgumentError("tokenEndpoint must not be empty");
512
+ if (opts.issuer === void 0) throw new import_protocol2.ArgumentError("issuer is required when tokenEndpoint is configured");
513
+ if (opts.issuer === "") throw new import_protocol2.ArgumentError("issuer must not be empty when tokenEndpoint is configured");
514
+ if (opts.serverUrl !== void 0 || opts.discoveryUrl !== void 0) {
515
+ throw new import_protocol2.ArgumentError("tokenEndpoint mode supports issuer and tokenEndpoint only");
516
+ }
517
+ } else if (opts.issuer !== void 0) {
518
+ if (opts.issuer === "") throw new import_protocol2.ArgumentError("issuer must not be empty");
519
+ if (opts.serverUrl !== void 0) throw new import_protocol2.ArgumentError("provide either issuer or serverUrl, not both");
520
+ } else if (opts.serverUrl !== void 0) {
521
+ if (opts.serverUrl === "") throw new import_protocol2.ArgumentError("serverUrl must not be empty");
522
+ } else if (opts.discoveryUrl !== void 0) {
523
+ throw new import_protocol2.ArgumentError("discoveryUrl requires issuer or serverUrl");
524
+ }
525
+ return opts;
526
+ }
527
+ async function resolveOIDCTokenEndpoint(fetch, opts, fetchOptions) {
528
+ if (opts.tokenEndpoint) {
529
+ if (!opts.issuer) throw new import_protocol2.ArgumentError("issuer is required when tokenEndpoint is configured");
530
+ const issuer = validateOIDCMintIssuerRoot(opts.issuer, fetchOptions.allowHttpLoopbackIssuer);
531
+ validateSameOriginEndpoint(opts.tokenEndpoint, issuer, "token_endpoint");
532
+ return { issuer, tokenEndpoint: opts.tokenEndpoint };
533
+ }
534
+ if (opts.issuer) {
535
+ const doc = await discoverMintOIDCIssuer(fetch, opts.issuer, opts.discoveryUrl, fetchOptions);
536
+ return { issuer: doc.issuer, tokenEndpoint: doc.token_endpoint, discoveryDocument: doc };
537
+ }
538
+ if (opts.serverUrl) {
539
+ const serverUrl = validateBaseUrl(opts.serverUrl, "serverUrl", fetchOptions.allowHttpLoopbackIssuer);
540
+ const doc = await discoverServerOIDCIssuer(fetch, serverUrl, opts.discoveryUrl, fetchOptions);
541
+ return { issuer: doc.issuer, tokenEndpoint: `${serverUrl}/token`, discoveryDocument: doc };
542
+ }
543
+ throw new import_protocol2.ArgumentError("issuer, serverUrl, or tokenEndpoint is required");
544
+ }
545
+ async function discoverExplicitOIDCIssuer(fetch, issuer, discoveryUrl, fetchOptions) {
546
+ issuer = validateExactOIDCIssuer(issuer, fetchOptions.allowHttpLoopbackIssuer);
547
+ const doc = await fetchOIDCDiscovery(fetch, discoveryUrl ?? `${issuer}/.well-known/openid-configuration`, fetchOptions);
548
+ if (doc.issuer !== issuer) throw new import_protocol2.VerificationError("OIDC discovery issuer mismatch", { code: import_protocol2.VerificationCode.RecordInvalid });
549
+ validateSameOriginEndpoint(doc.token_endpoint, issuer, "token_endpoint");
550
+ validateSameOriginEndpoint(doc.jwks_uri, issuer, "jwks_uri");
551
+ return doc;
552
+ }
553
+ async function discoverMintOIDCIssuer(fetch, issuer, discoveryUrl, fetchOptions) {
554
+ issuer = validateOIDCMintIssuerRoot(issuer, fetchOptions.allowHttpLoopbackIssuer);
555
+ const doc = await fetchOIDCDiscovery(fetch, discoveryUrl ?? `${issuer}/.well-known/openid-configuration`, fetchOptions);
556
+ if (doc.issuer !== issuer) throw new import_protocol2.VerificationError("OIDC discovery issuer mismatch", { code: import_protocol2.VerificationCode.RecordInvalid });
557
+ validateSameOriginEndpoint(doc.token_endpoint, issuer, "token_endpoint");
558
+ return doc;
559
+ }
560
+ async function discoverServerOIDCIssuer(fetch, serverUrl, discoveryUrl, fetchOptions) {
561
+ const doc = await fetchOIDCDiscovery(fetch, discoveryUrl ?? `${serverUrl}/.well-known/openid-configuration`, fetchOptions);
562
+ if (typeof doc.issuer !== "string" || doc.issuer === "") {
563
+ throw new import_protocol2.VerificationError("OIDC discovery document missing issuer", { code: import_protocol2.VerificationCode.RecordInvalid });
564
+ }
565
+ const issuer = validateExactOIDCIssuer(doc.issuer, fetchOptions.allowHttpLoopbackIssuer);
566
+ return { ...doc, issuer };
567
+ }
568
+ async function fetchOIDCDiscovery(fetch, discoveryUrl, fetchOptions) {
569
+ validateAbsoluteUrl(discoveryUrl, "discoveryUrl", fetchOptions.allowHttpLoopbackIssuer);
570
+ const response = await fetchOIDC(fetch, discoveryUrl, { redirect: "manual" }, fetchOptions);
571
+ if (response.status >= 300 && response.status < 400) throw new import_protocol2.VerificationError("OIDC discovery redirects are not allowed", { code: import_protocol2.VerificationCode.TLSError });
572
+ if (response.status !== 200) throw new import_protocol2.VerificationError("OIDC discovery failed", { code: import_protocol2.VerificationCode.TLSError });
573
+ const doc = requireRecord(await readLimitedJson(response), "OIDC discovery document must be a JSON object");
574
+ if (typeof doc.issuer !== "string" || doc.issuer === "") {
575
+ throw new import_protocol2.VerificationError("OIDC discovery document missing issuer", { code: import_protocol2.VerificationCode.RecordInvalid });
576
+ }
577
+ return doc;
578
+ }
579
+ async function exchangeOIDCTokenAt(fetch, opts) {
580
+ const form = new URLSearchParams({
581
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
582
+ assertion: opts.assertion,
583
+ audience: opts.audience
584
+ });
585
+ if (opts.scope !== void 0) form.set("scope", opts.scope);
586
+ const response = await fetchOIDC(fetch, opts.tokenEndpoint, {
587
+ method: "POST",
588
+ redirect: "manual",
589
+ headers: { "content-type": "application/x-www-form-urlencoded" },
590
+ body: form
591
+ }, {
592
+ allowHttpLoopbackIssuer: opts.allowHttpLoopbackIssuer,
593
+ timeoutMs: opts.timeoutMs
594
+ });
595
+ if (response.status >= 300 && response.status < 400) throw new import_protocol2.VerificationError("OIDC token endpoint redirects are not allowed", { code: import_protocol2.VerificationCode.TLSError });
596
+ if (response.status !== 200) {
597
+ const body = await readLimitedJson(response).catch(() => ({}));
598
+ if (response.status >= 200 && response.status < 300) {
599
+ throw new import_protocol2.VerificationError(`OIDC token endpoint returned unexpected successful status ${response.status}`, { code: import_protocol2.VerificationCode.RecordInvalid });
600
+ }
601
+ const raw2 = requireRecord(body, "OIDC token response must be a JSON object");
602
+ throw new OAuthError(asString(raw2.error), asString(raw2.error_description));
603
+ }
604
+ const raw = requireRecord(await readLimitedJson(response), "OIDC token response must be a JSON object");
605
+ if (typeof raw.access_token !== "string" || !raw.access_token) {
606
+ throw new import_protocol2.VerificationError("OIDC token response missing access_token", { code: import_protocol2.VerificationCode.RecordInvalid });
607
+ }
608
+ if (typeof raw.token_type !== "string" || raw.token_type.toLowerCase() !== "bearer") {
609
+ throw new import_protocol2.VerificationError("OIDC token response token_type must be Bearer", { code: import_protocol2.VerificationCode.RecordInvalid });
610
+ }
611
+ return {
612
+ accessToken: raw.access_token,
613
+ idToken: asString(raw.id_token),
614
+ tokenType: raw.token_type,
615
+ expiresIn: typeof raw.expires_in === "number" ? raw.expires_in : void 0,
616
+ scope: asString(raw.scope),
617
+ issuer: opts.issuer,
618
+ tokenEndpoint: opts.tokenEndpoint,
619
+ raw
620
+ };
621
+ }
622
+ function resolveScope(scope, scopes, defaultScope) {
623
+ if (scope !== void 0 && scopes !== void 0) {
624
+ throw new import_protocol2.ArgumentError("provide either scope or scopes, not both");
625
+ }
626
+ const value = scopes !== void 0 ? scopes.join(" ") : scope ?? defaultScope ?? DEFAULT_SCOPE;
627
+ return value === "" ? void 0 : value;
628
+ }
629
+ function validateExactOIDCIssuer(issuer, allowHttpLoopbackIssuer = false) {
630
+ let url;
631
+ try {
632
+ url = new URL(issuer);
633
+ } catch {
634
+ throw new import_protocol2.ArgumentError("invalid OIDC issuer URL");
635
+ }
636
+ const path = url.pathname === "/" ? "" : url.pathname;
637
+ if (issuer !== `${url.protocol}//${url.host}${path}` || issuer.endsWith("/")) {
638
+ throw new import_protocol2.ArgumentError("OIDC issuer must be an exact URL without query, fragment, or trailing slash");
639
+ }
640
+ if (url.protocol !== "https:" && !(allowHttpLoopbackIssuer && url.protocol === "http:" && isLocalhost(url.hostname))) {
641
+ throw new import_protocol2.ArgumentError("OIDC issuer must use HTTPS");
642
+ }
643
+ return issuer;
644
+ }
645
+ function validateOIDCMintIssuerRoot(issuer, allowHttpLoopbackIssuer = false) {
646
+ let url;
647
+ try {
648
+ url = new URL(issuer);
649
+ } catch {
650
+ throw new import_protocol2.ArgumentError("invalid OIDC issuer URL");
651
+ }
652
+ if (issuer !== `${url.protocol}//${url.host}`) {
653
+ throw new import_protocol2.ArgumentError("OIDC issuer must be an exact issuer root with no path, query, fragment, or trailing slash");
654
+ }
655
+ const host = unbracketHost(url.hostname).toLowerCase().replace(/\.$/, "");
656
+ if (host === "api.dnsid.dev" || host === "api.dnsid.ai") {
657
+ throw new import_protocol2.ArgumentError(`API host ${host} is not a valid OIDC issuer`);
658
+ }
659
+ if (url.protocol !== "https:" && !(allowHttpLoopbackIssuer && url.protocol === "http:" && isLocalhost(url.hostname))) {
660
+ throw new import_protocol2.ArgumentError("OIDC issuer must use HTTPS");
661
+ }
662
+ return issuer;
663
+ }
664
+ function validateBaseUrl(raw, name, allowHttpLoopbackIssuer = false) {
665
+ const url = validateAbsoluteUrl(raw, name, allowHttpLoopbackIssuer);
666
+ if (url.search || url.hash) throw new import_protocol2.ArgumentError(`${name} must not include query or fragment`);
667
+ const path = url.pathname === "/" ? "" : url.pathname.replace(/\/+$/, "");
668
+ return `${url.protocol}//${url.host}${path}`;
669
+ }
670
+ function validateAbsoluteUrl(raw, name, allowHttpLoopbackIssuer = false) {
671
+ let url;
672
+ try {
673
+ url = new URL(raw);
674
+ } catch {
675
+ throw new import_protocol2.ArgumentError(`invalid OIDC ${name} URL`);
676
+ }
677
+ const loopbackHttp = allowHttpLoopbackIssuer && url.protocol === "http:" && isLocalhost(url.hostname);
678
+ if (url.protocol !== "https:" && !loopbackHttp) {
679
+ throw new import_protocol2.ArgumentError(`OIDC ${name} must use HTTPS`);
680
+ }
681
+ if (url.hash) throw new import_protocol2.ArgumentError(`OIDC ${name} must not include a fragment`);
682
+ return url;
683
+ }
684
+ function validateSameOriginEndpoint(endpoint, issuer, name) {
685
+ if (typeof endpoint !== "string") throw new import_protocol2.VerificationError(`OIDC ${name} is required`, { code: import_protocol2.VerificationCode.RecordInvalid });
686
+ let url;
687
+ try {
688
+ url = new URL(endpoint);
689
+ } catch {
690
+ throw new import_protocol2.VerificationError(`invalid OIDC ${name}`, { code: import_protocol2.VerificationCode.RecordInvalid });
691
+ }
692
+ const issuerURL = new URL(issuer);
693
+ if (url.protocol !== issuerURL.protocol || url.host !== issuerURL.host) {
694
+ throw new import_protocol2.VerificationError(`OIDC ${name} must use the issuer origin`, { code: import_protocol2.VerificationCode.RecordInvalid });
695
+ }
696
+ if (!url.pathname || url.pathname === "/" || url.search || url.hash) {
697
+ throw new import_protocol2.VerificationError(`invalid OIDC ${name}`, { code: import_protocol2.VerificationCode.RecordInvalid });
698
+ }
699
+ }
700
+ function validateTimeoutMs(timeoutMs) {
701
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
702
+ throw new import_protocol2.ArgumentError("OIDC fetch timeout must be a positive number of milliseconds");
703
+ }
704
+ return timeoutMs;
705
+ }
706
+ function isLocalhost(hostname) {
707
+ const host = unbracketHost(hostname);
708
+ const family = net.isIP(host);
709
+ return host === "localhost" || family === 4 && host.split(".")[0] === "127" || family === 6 && host === "::1";
710
+ }
711
+ function audienceExactlyMatches(value, expected) {
712
+ return value === expected || Array.isArray(value) && value.length === 1 && value[0] === expected;
713
+ }
714
+ function asString(value) {
715
+ return typeof value === "string" ? value : void 0;
716
+ }
717
+ function oidcKeySupportsAlg(key, alg) {
718
+ if (key.alg !== void 0 && key.alg !== alg) return false;
719
+ if (key.kty === "RSA") return /^RS(256|384|512)$/.test(alg) || /^PS(256|384|512)$/.test(alg);
720
+ try {
721
+ return (0, import_protocol2.jwkSignatureAlg)({ ...key, alg }) === alg;
722
+ } catch {
723
+ return false;
724
+ }
725
+ }
726
+ function validateAllowedTokenAlgorithms(algs) {
727
+ for (const alg of algs) {
728
+ if (!import_protocol2.SIGNING_ALGS.has(alg)) {
729
+ throw new import_protocol2.VerificationError(`unsupported OIDC token algorithm: ${alg}`, { code: import_protocol2.VerificationCode.SignatureInvalid });
730
+ }
731
+ }
732
+ return algs;
733
+ }
734
+ async function fetchOIDC(fetch, input, init, options = {}) {
735
+ const url = typeof input === "string" ? new URL(input) : input instanceof URL ? input : new URL(input.url);
736
+ const timeoutMs = validateTimeoutMs(options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS);
737
+ const allowHttpLoopbackIssuer = options.allowHttpLoopbackIssuer ?? false;
738
+ const loopbackHttp = allowHttpLoopbackIssuer && url.protocol === "http:" && isLocalhost(url.hostname);
739
+ if (url.protocol !== "https:" && !loopbackHttp) {
740
+ throw new import_protocol2.VerificationError("OIDC request target is not allowed", { code: import_protocol2.VerificationCode.TLSError });
741
+ }
742
+ const signal = options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs);
743
+ if (!loopbackHttp) await (0, import_protocol2.waitForVerification)(() => rejectUnsafeOIDCTarget(url.hostname, timeoutMs), signal);
744
+ try {
745
+ return await (0, import_protocol2.waitForVerification)(() => fetch(input, { ...init, signal }), signal);
746
+ } catch (e) {
747
+ throw new import_protocol2.VerificationError(`OIDC request failed: ${e.message}`, { code: import_protocol2.VerificationCode.TLSError });
748
+ }
749
+ }
750
+ async function rejectUnsafeOIDCTarget(hostname, timeoutMs) {
751
+ try {
752
+ const host = unbracketHost(hostname);
753
+ const literalFamily = net.isIP(host);
754
+ const addresses = literalFamily ? [{ address: host, family: literalFamily }] : await withTimeout(
755
+ dnsPromises.lookup(host, { all: true }),
756
+ timeoutMs,
757
+ `OIDC issuer DNS lookup timed out after ${timeoutMs}ms`
758
+ );
759
+ rejectUnsafeOIDCAddresses(addresses);
760
+ } catch (e) {
761
+ if (e instanceof import_protocol2.VerificationError) throw e;
762
+ throw new import_protocol2.VerificationError(`OIDC issuer DNS lookup failed: ${e.message}`, { code: import_protocol2.VerificationCode.TLSError });
763
+ }
764
+ }
765
+ async function withTimeout(operation, timeoutMs, message) {
766
+ let timeout;
767
+ const timeoutPromise = new Promise((_, reject) => {
768
+ timeout = setTimeout(() => reject(new import_protocol2.VerificationError(message, { code: import_protocol2.VerificationCode.TLSError })), timeoutMs);
769
+ timeout.unref?.();
770
+ });
771
+ try {
772
+ return await Promise.race([operation, timeoutPromise]);
773
+ } finally {
774
+ if (timeout) clearTimeout(timeout);
775
+ }
776
+ }
777
+ function rejectUnsafeOIDCAddresses(addresses) {
778
+ const unsafe = addresses.find(({ address }) => (0, import_transport.isUnsafeIp)(address));
779
+ if (unsafe) {
780
+ throw new import_protocol2.VerificationError(`OIDC request target resolves to unsafe IP address: ${unsafe.address}`, { code: import_protocol2.VerificationCode.TLSError });
781
+ }
782
+ }
783
+ function unbracketHost(hostname) {
784
+ return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
785
+ }
786
+ function requireRecord(value, message) {
787
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
788
+ throw new import_protocol2.VerificationError(message, { code: import_protocol2.VerificationCode.RecordInvalid });
789
+ }
790
+ return value;
791
+ }
792
+ async function readLimitedJson(response) {
793
+ const body = response.body;
794
+ if (!body) return null;
795
+ const reader = body.getReader();
796
+ try {
797
+ const chunks = [];
798
+ let size = 0;
799
+ for (; ; ) {
800
+ const { done, value } = await reader.read();
801
+ if (done) break;
802
+ size += value.byteLength;
803
+ if (size > MAX_OIDC_JSON_BYTES) {
804
+ await reader.cancel();
805
+ throw new import_protocol2.VerificationError("OIDC response exceeds maximum size", { code: import_protocol2.VerificationCode.TLSError });
806
+ }
807
+ chunks.push(value);
808
+ }
809
+ const bytes = new Uint8Array(size);
810
+ let offset = 0;
811
+ for (const chunk of chunks) {
812
+ bytes.set(chunk, offset);
813
+ offset += chunk.byteLength;
814
+ }
815
+ try {
816
+ return JSON.parse(new TextDecoder().decode(bytes));
817
+ } catch {
818
+ throw new import_protocol2.VerificationError("OIDC response is not valid JSON", { code: import_protocol2.VerificationCode.RecordInvalid });
819
+ }
820
+ } finally {
821
+ reader.releaseLock();
822
+ }
823
+ }
824
+ async function signCompact(header, payload, keyProvider, signingKey, alg) {
825
+ const headerB64 = (0, import_protocol2.toBase64Url)(new TextEncoder().encode(JSON.stringify(header)));
826
+ const payloadB64 = (0, import_protocol2.toBase64Url)(new TextEncoder().encode(JSON.stringify(payload)));
827
+ const signingInput = `${headerB64}.${payloadB64}`;
828
+ const sig = await keyProvider.sign(new TextEncoder().encode(signingInput));
829
+ if (!await (0, import_protocol2.verifyWithKey)(signingInput, sig, signingKey, alg)) {
830
+ throw new import_protocol2.VerificationError("OIDC assertion signature does not match active signing key", { code: import_protocol2.VerificationCode.SignatureInvalid });
831
+ }
832
+ return `${signingInput}.${(0, import_protocol2.toBase64Url)(sig)}`;
833
+ }
834
+ function importAlgorithmForSigningAlg(alg, key) {
835
+ switch (alg) {
836
+ case "EdDSA":
837
+ return { name: "Ed25519" };
838
+ case "ES256":
839
+ return { name: "ECDSA", namedCurve: "P-256" };
840
+ case "ES384":
841
+ return { name: "ECDSA", namedCurve: "P-384" };
842
+ case "ES512":
843
+ return { name: "ECDSA", namedCurve: "P-521" };
844
+ case "RS256":
845
+ case "RS384":
846
+ case "RS512":
847
+ return { name: "RSASSA-PKCS1-v1_5", hash: rsaHashForAlg(alg) };
848
+ case "PS256":
849
+ case "PS384":
850
+ case "PS512":
851
+ return { name: "RSA-PSS", hash: rsaHashForAlg(alg) };
852
+ default:
853
+ throw new import_protocol2.VerificationError(`unsupported JWK signature algorithm: ${key.kid ? `${key.kid} ` : ""}${alg}`, {
854
+ code: import_protocol2.VerificationCode.SignatureInvalid
855
+ });
856
+ }
857
+ }
858
+ function signAlgorithmForSigningAlg(alg) {
859
+ switch (alg) {
860
+ case "EdDSA":
861
+ return { name: "Ed25519" };
862
+ case "ES256":
863
+ return { name: "ECDSA", hash: "SHA-256" };
864
+ case "ES384":
865
+ return { name: "ECDSA", hash: "SHA-384" };
866
+ case "ES512":
867
+ return { name: "ECDSA", hash: "SHA-512" };
868
+ case "RS256":
869
+ case "RS384":
870
+ case "RS512":
871
+ return { name: "RSASSA-PKCS1-v1_5" };
872
+ case "PS256":
873
+ return { name: "RSA-PSS", saltLength: 32 };
874
+ case "PS384":
875
+ return { name: "RSA-PSS", saltLength: 48 };
876
+ case "PS512":
877
+ return { name: "RSA-PSS", saltLength: 64 };
878
+ default:
879
+ throw new import_protocol2.VerificationError(`unsupported JWK signature algorithm: ${alg}`, { code: import_protocol2.VerificationCode.SignatureInvalid });
880
+ }
881
+ }
882
+ function rsaHashForAlg(alg) {
883
+ if (alg.endsWith("256")) return "SHA-256";
884
+ if (alg.endsWith("384")) return "SHA-384";
885
+ return "SHA-512";
886
+ }
887
+ function decodeOIDCClaims(token) {
888
+ const parts = token.split(".");
889
+ if (parts.length < 2) throw new import_protocol2.VerificationError("malformed JWT", { code: import_protocol2.VerificationCode.RecordInvalid });
890
+ try {
891
+ const claims = JSON.parse(new TextDecoder().decode((0, import_protocol2.fromBase64Url)(parts[1])));
892
+ if (typeof claims !== "object" || claims === null || Array.isArray(claims)) {
893
+ throw new Error("invalid claims");
894
+ }
895
+ return claims;
896
+ } catch {
897
+ throw new import_protocol2.VerificationError("malformed JWT", { code: import_protocol2.VerificationCode.RecordInvalid });
898
+ }
899
+ }
900
+ // Annotate the CommonJS export names for ESM import in node:
901
+ 0 && (module.exports = {
902
+ OAuthError,
903
+ OIDCProfile,
904
+ OIDCTokenMinter,
905
+ createOIDCKeyProviderFromJWK,
906
+ createOIDCProfile,
907
+ createOIDCTokenMinter,
908
+ decodeOIDCClaims,
909
+ mintOIDCToken,
910
+ validateExactOIDCIssuer
911
+ });