@authrim/server 0.1.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.
Files changed (60) hide show
  1. package/README.md +610 -0
  2. package/dist/adapters/express.cjs +3 -0
  3. package/dist/adapters/express.cjs.map +1 -0
  4. package/dist/adapters/express.d.cts +75 -0
  5. package/dist/adapters/express.d.ts +75 -0
  6. package/dist/adapters/express.js +3 -0
  7. package/dist/adapters/express.js.map +1 -0
  8. package/dist/adapters/fastify.cjs +3 -0
  9. package/dist/adapters/fastify.cjs.map +1 -0
  10. package/dist/adapters/fastify.d.cts +101 -0
  11. package/dist/adapters/fastify.d.ts +101 -0
  12. package/dist/adapters/fastify.js +3 -0
  13. package/dist/adapters/fastify.js.map +1 -0
  14. package/dist/adapters/hono.cjs +2 -0
  15. package/dist/adapters/hono.cjs.map +1 -0
  16. package/dist/adapters/hono.d.cts +85 -0
  17. package/dist/adapters/hono.d.ts +85 -0
  18. package/dist/adapters/hono.js +2 -0
  19. package/dist/adapters/hono.js.map +1 -0
  20. package/dist/adapters/koa.cjs +3 -0
  21. package/dist/adapters/koa.cjs.map +1 -0
  22. package/dist/adapters/koa.d.cts +75 -0
  23. package/dist/adapters/koa.d.ts +75 -0
  24. package/dist/adapters/koa.js +3 -0
  25. package/dist/adapters/koa.js.map +1 -0
  26. package/dist/adapters/nestjs.cjs +3 -0
  27. package/dist/adapters/nestjs.cjs.map +1 -0
  28. package/dist/adapters/nestjs.d.cts +126 -0
  29. package/dist/adapters/nestjs.d.ts +126 -0
  30. package/dist/adapters/nestjs.js +3 -0
  31. package/dist/adapters/nestjs.js.map +1 -0
  32. package/dist/chunk-7POGA5LZ.cjs +3 -0
  33. package/dist/chunk-7POGA5LZ.cjs.map +1 -0
  34. package/dist/chunk-N3ONRO35.js +2 -0
  35. package/dist/chunk-N3ONRO35.js.map +1 -0
  36. package/dist/chunk-O2ALCNXB.cjs +2 -0
  37. package/dist/chunk-O2ALCNXB.cjs.map +1 -0
  38. package/dist/chunk-OS567YCE.js +3 -0
  39. package/dist/chunk-OS567YCE.js.map +1 -0
  40. package/dist/chunk-TPROSFE7.cjs +2 -0
  41. package/dist/chunk-TPROSFE7.cjs.map +1 -0
  42. package/dist/chunk-XOFM2JHF.js +2 -0
  43. package/dist/chunk-XOFM2JHF.js.map +1 -0
  44. package/dist/config-I0GIVJA_.d.cts +364 -0
  45. package/dist/config-I0GIVJA_.d.ts +364 -0
  46. package/dist/index.cjs +3 -0
  47. package/dist/index.cjs.map +1 -0
  48. package/dist/index.d.cts +791 -0
  49. package/dist/index.d.ts +791 -0
  50. package/dist/index.js +3 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/providers/index.cjs +2 -0
  53. package/dist/providers/index.cjs.map +1 -0
  54. package/dist/providers/index.d.cts +79 -0
  55. package/dist/providers/index.d.ts +79 -0
  56. package/dist/providers/index.js +2 -0
  57. package/dist/providers/index.js.map +1 -0
  58. package/dist/types-CzpMdWFR.d.cts +435 -0
  59. package/dist/types-D7gjcvs9.d.ts +435 -0
  60. package/package.json +119 -0
@@ -0,0 +1,364 @@
1
+ /**
2
+ * HTTP Provider Interface
3
+ *
4
+ * Platform-agnostic HTTP client abstraction.
5
+ * This interface uses the standard fetch API signature for maximum compatibility.
6
+ */
7
+ /**
8
+ * HTTP Provider interface
9
+ *
10
+ * Implementations should:
11
+ * - Use the platform's native fetch or equivalent
12
+ * - Handle timeout via AbortSignal
13
+ * - Propagate network errors appropriately
14
+ *
15
+ * This interface follows the standard fetch API signature to ensure
16
+ * compatibility across all runtime environments (Node.js, Bun, Deno,
17
+ * Cloudflare Workers, Vercel Edge Functions).
18
+ */
19
+ interface HttpProvider {
20
+ /**
21
+ * Make an HTTP request
22
+ *
23
+ * @param input - URL or Request object
24
+ * @param init - Request options
25
+ * @returns Promise resolving to a Response
26
+ * @throws Error on network failure or timeout
27
+ */
28
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
29
+ }
30
+
31
+ /**
32
+ * Crypto Provider Interface
33
+ *
34
+ * Platform-agnostic cryptographic operations abstraction for server-side JWT operations.
35
+ * Implementations must be injected - @authrim/server does not use crypto.subtle directly.
36
+ */
37
+ /**
38
+ * Crypto Provider interface
39
+ *
40
+ * Implementations should:
41
+ * - Use cryptographically secure operations
42
+ * - Support RSA and ECDSA signature verification
43
+ * - Implement JWK import for public keys
44
+ * - Calculate JWK thumbprints per RFC 7638
45
+ */
46
+ interface CryptoProvider {
47
+ /**
48
+ * Verify a digital signature
49
+ *
50
+ * @param alg - JWA algorithm (e.g., 'RS256', 'ES256')
51
+ * @param key - CryptoKey for verification
52
+ * @param signature - Signature bytes
53
+ * @param data - Data that was signed
54
+ * @returns Promise resolving to true if signature is valid
55
+ */
56
+ verifySignature(alg: string, key: CryptoKey, signature: Uint8Array, data: Uint8Array): Promise<boolean>;
57
+ /**
58
+ * Import a JWK as a CryptoKey
59
+ *
60
+ * @param jwk - JSON Web Key
61
+ * @param alg - Algorithm to use with the key
62
+ * @returns Promise resolving to a CryptoKey
63
+ */
64
+ importJwk(jwk: JsonWebKey, alg: string): Promise<CryptoKey>;
65
+ /**
66
+ * Compute SHA-256 hash
67
+ *
68
+ * @param data - String or bytes to hash
69
+ * @returns Promise resolving to hash bytes
70
+ */
71
+ sha256(data: string | Uint8Array): Promise<Uint8Array>;
72
+ /**
73
+ * Calculate JWK Thumbprint (RFC 7638)
74
+ *
75
+ * @param jwk - JSON Web Key (public key)
76
+ * @returns Promise resolving to base64url-encoded thumbprint
77
+ */
78
+ calculateThumbprint(jwk: JsonWebKey): Promise<string>;
79
+ }
80
+
81
+ /**
82
+ * Clock Provider Interface
83
+ *
84
+ * Abstraction for time operations to enable testing and custom time sources.
85
+ */
86
+ /**
87
+ * Clock Provider interface
88
+ *
89
+ * Implementations should:
90
+ * - Return consistent, monotonic time values
91
+ * - Use the system clock in production
92
+ * - Allow time manipulation in tests
93
+ */
94
+ interface ClockProvider {
95
+ /**
96
+ * Get current Unix timestamp in seconds
97
+ *
98
+ * @returns Current time as Unix timestamp (seconds since epoch)
99
+ */
100
+ nowSeconds(): number;
101
+ /**
102
+ * Get current time in milliseconds
103
+ *
104
+ * @returns Current time in milliseconds since epoch
105
+ */
106
+ nowMs(): number;
107
+ }
108
+
109
+ /**
110
+ * Cache Provider Interface
111
+ *
112
+ * Generic caching abstraction for JWKS and other cacheable data.
113
+ */
114
+ /**
115
+ * Cache Provider interface
116
+ *
117
+ * Implementations should:
118
+ * - Support TTL-based expiration
119
+ * - Be thread-safe in concurrent environments
120
+ * - Handle memory limits gracefully
121
+ *
122
+ * @template T - Type of cached values
123
+ */
124
+ interface CacheProvider<T> {
125
+ /**
126
+ * Get a cached value
127
+ *
128
+ * @param key - Cache key
129
+ * @returns Cached value or undefined if not found/expired
130
+ */
131
+ get(key: string): T | undefined;
132
+ /**
133
+ * Set a cached value
134
+ *
135
+ * @param key - Cache key
136
+ * @param value - Value to cache
137
+ * @param ttlMs - Time-to-live in milliseconds (optional, uses default if not provided)
138
+ */
139
+ set(key: string, value: T, ttlMs?: number): void;
140
+ /**
141
+ * Delete a cached value
142
+ *
143
+ * @param key - Cache key
144
+ */
145
+ delete(key: string): void;
146
+ /**
147
+ * Clear all cached values (optional)
148
+ *
149
+ * May be a no-op in some implementations (e.g., distributed caches).
150
+ */
151
+ clear?(): void;
152
+ }
153
+
154
+ /**
155
+ * JWK (JSON Web Key) Type Definitions
156
+ *
157
+ * Based on RFC 7517 (JSON Web Key) and RFC 7518 (JSON Web Algorithms)
158
+ */
159
+ /**
160
+ * Key type (RFC 7517 Section 6.1)
161
+ */
162
+ type JwkKeyType = 'EC' | 'RSA' | 'oct' | 'OKP';
163
+ /**
164
+ * Key use (RFC 7517 Section 4.2)
165
+ */
166
+ type JwkKeyUse = 'sig' | 'enc';
167
+ /**
168
+ * Key operations (RFC 7517 Section 4.3)
169
+ */
170
+ type JwkKeyOps = 'sign' | 'verify' | 'encrypt' | 'decrypt' | 'wrapKey' | 'unwrapKey' | 'deriveKey' | 'deriveBits';
171
+ /**
172
+ * Supported signing algorithms (RFC 7518)
173
+ */
174
+ type JwkSigningAlgorithm = 'RS256' | 'RS384' | 'RS512' | 'PS256' | 'PS384' | 'PS512' | 'ES256' | 'ES384' | 'ES512' | 'EdDSA';
175
+ /**
176
+ * EC curve names (RFC 7518 Section 6.2.1.1)
177
+ */
178
+ type EcCurve = 'P-256' | 'P-384' | 'P-521';
179
+ /**
180
+ * OKP curve names (RFC 8037)
181
+ */
182
+ type OkpCurve = 'Ed25519' | 'Ed448' | 'X25519' | 'X448';
183
+ /**
184
+ * Base JWK properties (RFC 7517 Section 4)
185
+ */
186
+ interface JwkBase {
187
+ /** Key Type (required) */
188
+ kty: JwkKeyType;
189
+ /** Public Key Use */
190
+ use?: JwkKeyUse;
191
+ /** Key Operations */
192
+ key_ops?: JwkKeyOps[];
193
+ /** Algorithm */
194
+ alg?: JwkSigningAlgorithm;
195
+ /** Key ID */
196
+ kid?: string;
197
+ /** X.509 URL */
198
+ x5u?: string;
199
+ /** X.509 Certificate Chain */
200
+ x5c?: string[];
201
+ /** X.509 Certificate SHA-1 Thumbprint */
202
+ x5t?: string;
203
+ /** X.509 Certificate SHA-256 Thumbprint */
204
+ 'x5t#S256'?: string;
205
+ }
206
+ /**
207
+ * RSA Public Key (RFC 7518 Section 6.3)
208
+ */
209
+ interface RsaPublicJwk extends JwkBase {
210
+ kty: 'RSA';
211
+ /** Modulus */
212
+ n: string;
213
+ /** Exponent */
214
+ e: string;
215
+ }
216
+ /**
217
+ * EC Public Key (RFC 7518 Section 6.2)
218
+ */
219
+ interface EcPublicJwk extends JwkBase {
220
+ kty: 'EC';
221
+ /** Curve */
222
+ crv: EcCurve;
223
+ /** X Coordinate */
224
+ x: string;
225
+ /** Y Coordinate */
226
+ y: string;
227
+ }
228
+ /**
229
+ * OKP (Octet Key Pair) Public Key (RFC 8037)
230
+ */
231
+ interface OkpPublicJwk extends JwkBase {
232
+ kty: 'OKP';
233
+ /** Curve */
234
+ crv: OkpCurve;
235
+ /** Public Key */
236
+ x: string;
237
+ }
238
+ /**
239
+ * Public JWK (union type)
240
+ */
241
+ type PublicJwk = RsaPublicJwk | EcPublicJwk | OkpPublicJwk;
242
+ /**
243
+ * JWK Set (RFC 7517 Section 5)
244
+ */
245
+ interface JwkSet {
246
+ keys: PublicJwk[];
247
+ }
248
+ /**
249
+ * Internal representation of a cached JWK with metadata
250
+ */
251
+ interface CachedJwk {
252
+ jwk: PublicJwk;
253
+ cryptoKey: CryptoKey;
254
+ }
255
+
256
+ /**
257
+ * Configuration Type Definitions
258
+ */
259
+
260
+ /**
261
+ * AuthrimServer configuration options
262
+ */
263
+ interface AuthrimServerConfig {
264
+ /**
265
+ * Expected token issuer(s)
266
+ * This is compared against the `iss` claim in tokens
267
+ */
268
+ issuer: string | string[];
269
+ /**
270
+ * Expected audience(s) for this resource server
271
+ * This is compared against the `aud` claim in tokens
272
+ */
273
+ audience: string | string[];
274
+ /**
275
+ * JWKS endpoint URL
276
+ * If not provided, will be discovered from `{issuer}/.well-known/openid-configuration`
277
+ */
278
+ jwksUri?: string;
279
+ /**
280
+ * Clock tolerance in seconds for exp, nbf, iat validation
281
+ * @default 60
282
+ */
283
+ clockToleranceSeconds?: number;
284
+ /**
285
+ * JWKS cache TTL in milliseconds
286
+ * @default 3600000 (1 hour)
287
+ */
288
+ jwksRefreshIntervalMs?: number;
289
+ /**
290
+ * Token introspection endpoint
291
+ * Required for introspection operations
292
+ */
293
+ introspectionEndpoint?: string;
294
+ /**
295
+ * Token revocation endpoint
296
+ * Required for revocation operations
297
+ */
298
+ revocationEndpoint?: string;
299
+ /**
300
+ * Client credentials for introspection/revocation
301
+ */
302
+ clientCredentials?: {
303
+ clientId: string;
304
+ clientSecret: string;
305
+ };
306
+ /**
307
+ * HTTP provider for network requests
308
+ * @default fetchHttpProvider()
309
+ */
310
+ http?: HttpProvider;
311
+ /**
312
+ * Crypto provider for cryptographic operations
313
+ * @default webCryptoProvider()
314
+ */
315
+ crypto?: CryptoProvider;
316
+ /**
317
+ * Clock provider for time operations
318
+ * @default systemClock()
319
+ */
320
+ clock?: ClockProvider;
321
+ /**
322
+ * JWKS cache provider
323
+ * @default memoryCache()
324
+ */
325
+ jwksCache?: CacheProvider<CachedJwk[]>;
326
+ /**
327
+ * Require HTTPS for issuer and JWKS URI
328
+ * Set to false to allow HTTP in development environments
329
+ * @default true
330
+ */
331
+ requireHttps?: boolean;
332
+ }
333
+ /**
334
+ * Resolved configuration with all defaults applied
335
+ */
336
+ interface ResolvedAuthrimServerConfig {
337
+ issuer: string[];
338
+ audience: string[];
339
+ jwksUri?: string;
340
+ clockToleranceSeconds: number;
341
+ jwksRefreshIntervalMs: number;
342
+ introspectionEndpoint?: string;
343
+ revocationEndpoint?: string;
344
+ clientCredentials?: {
345
+ clientId: string;
346
+ clientSecret: string;
347
+ };
348
+ http: HttpProvider;
349
+ crypto: CryptoProvider;
350
+ clock: ClockProvider;
351
+ jwksCache: CacheProvider<CachedJwk[]>;
352
+ requireHttps: boolean;
353
+ }
354
+ /**
355
+ * Memory cache options
356
+ */
357
+ interface MemoryCacheOptions {
358
+ /** Default TTL in milliseconds */
359
+ ttlMs?: number;
360
+ /** Maximum number of entries */
361
+ maxSize?: number;
362
+ }
363
+
364
+ export type { AuthrimServerConfig as A, CachedJwk as C, EcPublicJwk as E, HttpProvider as H, JwkKeyType as J, MemoryCacheOptions as M, OkpPublicJwk as O, PublicJwk as P, ResolvedAuthrimServerConfig as R, CryptoProvider as a, ClockProvider as b, CacheProvider as c, JwkKeyUse as d, JwkSet as e, JwkSigningAlgorithm as f, RsaPublicJwk as g };
package/dist/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict';var chunk7POGA5LZ_cjs=require('./chunk-7POGA5LZ.cjs'),chunkO2ALCNXB_cjs=require('./chunk-O2ALCNXB.cjs'),chunkTPROSFE7_cjs=require('./chunk-TPROSFE7.cjs');function q(i){return i.use===void 0||i.use==="sig"}function B(i,e){return i.alg===void 0?true:i.alg===e}function W(i,e,r){let o=i.find(t=>t.jwk.kid===e&&q(t.jwk)&&B(t.jwk,r));return o?{key:o,error:null,needsRefresh:false}:{key:null,error:new chunkTPROSFE7_cjs.a("jwks_key_not_found",`No key found with kid: ${e}`),needsRefresh:true}}function N(i,e){let r=i.filter(o=>q(o.jwk)&&B(o.jwk,e));return r.length===1?{key:r[0],error:null,needsRefresh:false}:r.length===0?{key:null,error:new chunkTPROSFE7_cjs.a("jwks_key_ambiguous",`No keys found matching algorithm: ${e}`),needsRefresh:false}:{key:null,error:new chunkTPROSFE7_cjs.a("jwks_key_ambiguous",`Multiple keys (${r.length}) match algorithm: ${e}. Token must include 'kid' header.`),needsRefresh:false}}function S(i,e){return e.kid?W(i,e.kid,e.alg):N(i,e.alg)}var x=class x{constructor(e){this.inFlight=null;this.config=e,this.cacheKey=`jwks:${e.jwksUri}`;}async getKey(e){let r=await this.getKeys(),o=S(r,e);return o.error&&o.needsRefresh&&(r=await this.fetchAndCacheKeys(),o=S(r,e)),o}async getKeys(){let e=this.config.cache.get(this.cacheKey);return e||this.fetchAndCacheKeys()}async fetchAndCacheKeys(){if(this.inFlight&&this.config.clock.nowMs()-this.inFlight.startedAt<3e4)return this.inFlight.promise;let e=this.doFetchAndCache();this.inFlight={promise:e,startedAt:this.config.clock.nowMs()};try{return await e}finally{this.inFlight=null;}}async doFetchAndCache(){try{let e=await this.config.http.fetch(this.config.jwksUri,{headers:{Accept:"application/json"}});if(!this.config.allowCrossOriginRedirect&&e.url){let n=new URL(this.config.jwksUri),a=new URL(e.url);if(n.host!==a.host)throw new chunkTPROSFE7_cjs.a("jwks_fetch_error",`JWKS fetch was redirected to a different host: ${a.host} (from ${n.host}). This is blocked for security. Set allowCrossOriginRedirect: true if this is intentional.`)}if(!e.ok)throw await e.text().catch(()=>{}),new chunkTPROSFE7_cjs.a("jwks_fetch_error",`Failed to fetch JWKS: ${e.status} ${e.statusText}`);let r=await e.json();if(!r.keys||!Array.isArray(r.keys))throw new chunkTPROSFE7_cjs.a("jwks_fetch_error","Invalid JWKS response: missing keys array");let o=[];for(let n of r.keys)try{let a=this.determineAlgorithm(n);if(!a){this.config.onKeyImportWarning?.({kid:n.kid,kty:n.kty,alg:n.alg,reason:n.kty==="RSA"||n.kty==="EC"||n.kty==="OKP"?"unsupported_algorithm":"unknown_key_type",message:`Cannot determine algorithm for key: kty=${n.kty}, alg=${n.alg??"none"}`});continue}let s=await this.config.crypto.importJwk(n,a);o.push({jwk:n,cryptoKey:s});}catch(a){this.config.onKeyImportWarning?.({kid:n.kid,kty:n.kty,alg:n.alg,reason:"import_failed",message:`Failed to import key: ${a instanceof Error?a.message:"Unknown error"}`});continue}let t=this.parseCacheControl(e.headers.get("Cache-Control"));return this.config.cache.set(this.cacheKey,o,t),o}catch(e){throw e instanceof chunkTPROSFE7_cjs.a?e:new chunkTPROSFE7_cjs.a("jwks_fetch_error",`Failed to fetch JWKS: ${e instanceof Error?e.message:"Unknown error"}`,{cause:e instanceof Error?e:void 0})}}determineAlgorithm(e){return e.alg?e.alg:e.kty==="RSA"?"RS256":e.kty==="EC"?{"P-256":"ES256","P-384":"ES384","P-521":"ES512"}[e.crv]??null:e.kty==="OKP"&&e.crv==="Ed25519"?"EdDSA":null}parseCacheControl(e){if(!e)return this.config.cacheTtlMs;let r=e.match(/max-age=(\d+)/);if(r&&r[1]){let o=parseInt(r[1],10);if(!isNaN(o)&&o>0)return Math.min(o*1e3,x.MAX_CACHE_TTL_MS)}return this.config.cacheTtlMs}invalidate(){this.config.cache.delete(this.cacheKey);}};x.MAX_CACHE_TTL_MS=1440*60*1e3;var v=x;var F=8192,ie=new Set(["RS256","RS384","RS512","PS256","PS384","PS512","ES256","ES384","ES512","EdDSA"]),h=class extends Error{constructor(){super('Algorithm "none" is not allowed');this.code="insecure_algorithm";this.name="InsecureAlgorithmError";}};function m(i){if(i.length>F)throw new Error(`JWT exceeds maximum size of ${F} bytes`);let e=i.split(".");if(e.length!==3)throw new Error("Invalid JWT format: expected 3 parts");let[r,o,t]=e;if(!r||!o||!t)throw new Error("Invalid JWT format: empty parts");try{let n=JSON.parse(chunk7POGA5LZ_cjs.e(r)),a=JSON.parse(chunk7POGA5LZ_cjs.e(o));if(!n||typeof n!="object")throw new Error("Invalid JWT header: must be an object");if(typeof n.alg!="string")throw new Error("Invalid JWT header: missing or invalid alg claim");if(n.alg.toLowerCase()==="none")throw new h;return {header:n,payload:a,signature:t}}catch(n){throw n instanceof h?n:new Error("Invalid JWT format")}}async function I(i,e,r){let o=m(i);if(!ie.has(o.header.alg))return null;let t=i.lastIndexOf("."),n=i.substring(0,t),a=chunk7POGA5LZ_cjs.c(o.signature),s=new TextEncoder().encode(n);return await r.verifySignature(o.header.alg,e,a,s)?o:null}function p(i,e){if(typeof globalThis.crypto<"u"&&"timingSafeEqual"in globalThis.crypto){let s=new TextEncoder,l=s.encode(i),d=s.encode(e);return l.length!==d.length?(globalThis.crypto.timingSafeEqual(l,l),false):globalThis.crypto.timingSafeEqual(l,d)}let r=new TextEncoder,o=r.encode(i),t=r.encode(e),n=Math.max(o.length,t.length),a=o.length===t.length?0:1;for(let s=0;s<n;s++){let l=o[s]??0,d=t[s]??0;a|=l^d;}return a===0}function J(i,e){let{issuer:r,audience:o,clockToleranceSeconds:t,now:n,requireExp:a,requireIat:s}=e,l=oe(i.iss,r);if(!l.valid)return l;let d=ne(i.aud,o);if(!d.valid)return d;if(i.exp!==void 0){let u=ce(i.exp,n,t);if(!u.valid)return u}else if(a)return {valid:false,error:{code:"missing_exp",message:"Missing required exp claim"}};if(i.nbf!==void 0){let u=le(i.nbf,n,t);if(!u.valid)return u}if(i.iat!==void 0){let u=de(i.iat,n,t);if(!u.valid)return u}else if(s)return {valid:false,error:{code:"missing_iat",message:"Missing required iat claim"}};return {valid:true}}function oe(i,e){if(i===void 0||i==="")return {valid:false,error:{code:"invalid_issuer",message:"Missing or empty issuer claim"}};let o=(Array.isArray(e)?e:[e]).filter(n=>n!=="");return o.length===0?{valid:false,error:{code:"invalid_issuer",message:"No valid expected issuers configured"}}:o.some(n=>p(i,n))?{valid:true}:{valid:false,error:{code:"invalid_issuer",message:`Invalid issuer: ${i}`}}}function ne(i,e){if(i===void 0)return {valid:false,error:{code:"invalid_audience",message:"Missing audience claim"}};let r=Array.isArray(e)?e:[e],o=Array.isArray(i)?i:[i],t=r.filter(s=>s!==""),n=o.filter(s=>s!=="");return t.length===0?{valid:false,error:{code:"invalid_audience",message:"No valid expected audiences configured"}}:n.length===0?{valid:false,error:{code:"invalid_audience",message:"Token has no valid audience claims"}}:n.some(s=>t.some(l=>p(s,l)))?{valid:true}:{valid:false,error:{code:"invalid_audience",message:`Invalid audience: ${n.join(", ")}`}}}var se=3250368e4,ae=0;function M(i){return typeof i!="number"||!Number.isFinite(i)?false:i>=ae&&i<=se}function ce(i,e,r){return M(i)?i+r<e?{valid:false,error:{code:"token_expired",message:`Token expired at ${new Date(i*1e3).toISOString()}`}}:{valid:true}:{valid:false,error:{code:"invalid_exp",message:"Invalid exp claim: must be a number"}}}function le(i,e,r){return M(i)?i-r>e?{valid:false,error:{code:"token_not_yet_valid",message:`Token not valid until ${new Date(i*1e3).toISOString()}`}}:{valid:true}:{valid:false,error:{code:"invalid_nbf",message:"Invalid nbf claim: must be a number"}}}function de(i,e,r){return M(i)?i-r>e?{valid:false,error:{code:"iat_in_future",message:`Token issued in the future: ${new Date(i*1e3).toISOString()}`}}:{valid:true}:{valid:false,error:{code:"invalid_iat",message:"Invalid iat claim: must be a number"}}}function V(i,e){if(i!==void 0)return Math.max(0,i-e)}var k=class{constructor(e){this.config=e;}async validate(e){try{let r;try{r=m(e);}catch(d){return {data:null,error:{code:"token_malformed",message:d instanceof Error?d.message:"Invalid JWT format"}}}let o=await this.config.jwksManager.getKey(r.header);if(o.error)return {data:null,error:{code:o.error.code,message:o.error.message}};if(!o.key)return {data:null,error:{code:"jwks_key_not_found",message:"No suitable key found in JWKS"}};let t=await I(e,o.key.cryptoKey,this.config.crypto);if(!t)return {data:null,error:{code:"signature_invalid",message:"JWT signature verification failed"}};let n=this.config.clock.nowSeconds(),a=J(t.payload,{issuer:this.config.options.issuer,audience:this.config.options.audience,clockToleranceSeconds:this.config.options.clockToleranceSeconds??60,now:n});if(!a.valid&&a.error)return {data:null,error:a.error};if(this.config.options.requiredScopes?.length){let d=this.validateScopes(t.payload.scope,this.config.options.requiredScopes);if(!d.valid&&d.error)return {data:null,error:d.error}}let s=t.payload.cnf?.jkt?"DPoP":"Bearer";return {data:{claims:t.payload,token:e,tokenType:s,expiresIn:V(t.payload.exp,n)},error:null}}catch(r){return r instanceof chunkTPROSFE7_cjs.a?{data:null,error:{code:r.code,message:r.message}}:{data:null,error:{code:"invalid_token",message:r instanceof Error?r.message:"Token validation failed"}}}}validateScopes(e,r){if(!e)return {valid:false,error:{code:"insufficient_scope",message:`Missing required scopes: ${r.join(" ")}`}};let o=e.split(" "),t=r.filter(n=>!o.includes(n));return t.length>0?{valid:false,error:{code:"insufficient_scope",message:`Missing required scopes: ${t.join(" ")}`}}:{valid:true}}};function b(i,e){let r=encodeURIComponent(i),o=encodeURIComponent(e),t=`${r}:${o}`,a=new TextEncoder().encode(t),s="";for(let l of a)s+=String.fromCharCode(l);return btoa(s)}var w=class{constructor(e){this.config=e;}async introspect(e){let r=new URLSearchParams;r.set("token",e.token),e.token_type_hint&&r.set("token_type_hint",e.token_type_hint);let o=b(this.config.clientId,this.config.clientSecret);try{let t=await this.config.http.fetch(this.config.endpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:`Basic ${o}`,Accept:"application/json"},body:r.toString()});if(!t.ok)throw await t.text().catch(()=>{}),new chunkTPROSFE7_cjs.a("introspection_error",`Introspection request failed: ${t.status} ${t.statusText}`);return await t.json()}catch(t){throw t instanceof chunkTPROSFE7_cjs.a?t:new chunkTPROSFE7_cjs.a("introspection_error",`Introspection request failed: ${t instanceof Error?t.message:"Unknown error"}`,{cause:t instanceof Error?t:void 0})}}};var C=class{constructor(e){this.config=e;}async revoke(e){let r=new URLSearchParams;r.set("token",e.token),e.token_type_hint&&r.set("token_type_hint",e.token_type_hint);let o=b(this.config.clientId,this.config.clientSecret);try{let t=await this.config.http.fetch(this.config.endpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:`Basic ${o}`},body:r.toString()});if(!t.ok)throw await t.text().catch(()=>{}),new chunkTPROSFE7_cjs.a("revocation_error",`Revocation request failed: ${t.status} ${t.statusText}`)}catch(t){throw t instanceof chunkTPROSFE7_cjs.a?t:new chunkTPROSFE7_cjs.a("revocation_error",`Revocation request failed: ${t instanceof Error?t.message:"Unknown error"}`,{cause:t instanceof Error?t:void 0})}}};async function A(i,e){return e.calculateThumbprint(i)}async function ue(i,e,r){return await A(i,r)===e}var pe=new Set(["RS256","RS384","RS512","PS256","PS384","PS512","ES256","ES384","ES512","EdDSA"]),fe=new Set(["d","p","q","dp","dq","qi","k"]);function me(i){let e=i.split(".");if(e.length!==3)throw new Error("Invalid DPoP proof format: expected 3 parts");let[r,o,t]=e;if(!r||!o||!t)throw new Error("Invalid DPoP proof format: empty parts");let n=JSON.parse(chunk7POGA5LZ_cjs.e(r)),a=JSON.parse(chunk7POGA5LZ_cjs.e(o));return {header:n,payload:a,signature:t}}function z(i){try{let e=new URL(i);return `${e.protocol}//${e.host}${e.pathname}`}catch{return null}}var P=class{constructor(e,r){this.crypto=e;this.clock=r;}async validate(e,r){try{let{header:o,payload:t}=me(e),n=this.validateHeader(o);if(!n.valid)return n;let a=this.validatePayloadStructure(t);if(!a.valid)return a;let s=await this.verifySignature(e,o);if(!s.valid)return s;if(t.htm.toUpperCase()!==r.method.toUpperCase())return {valid:!1,errorCode:"dpop_method_mismatch",errorMessage:`Method mismatch: expected ${r.method}, got ${t.htm}`};let l=z(r.uri),d=z(t.htu);if(l===null)return {valid:!1,errorCode:"dpop_proof_invalid",errorMessage:`Invalid expected URI: ${r.uri}`};if(d===null)return {valid:!1,errorCode:"dpop_uri_mismatch",errorMessage:`Invalid htu claim: ${t.htu}`};if(l!==d)return {valid:!1,errorCode:"dpop_uri_mismatch",errorMessage:`URI mismatch: expected ${l}, got ${d}`};let u=r.maxAge??60,f=r.clockTolerance??60,U=this.clock.nowSeconds(),G=U-u-f,X=U+f;if(t.iat<G||t.iat>X)return {valid:!1,errorCode:"dpop_iat_expired",errorMessage:"Proof iat is outside acceptable window"};if(r.expectedNonce!==void 0&&t.nonce!==r.expectedNonce)return {valid:!1,errorCode:"dpop_nonce_required",errorMessage:"Invalid or missing DPoP nonce"};let K=await A(o.jwk,this.crypto);if(r.accessToken){let D=await this.validateAccessTokenHash(t.ath,r.accessToken);if(!D.valid)return D}return r.expectedThumbprint&&!p(K,r.expectedThumbprint)?{valid:!1,errorCode:"dpop_binding_mismatch",errorMessage:"DPoP proof key does not match token binding"}:{valid:!0,thumbprint:K}}catch(o){return {valid:false,errorCode:"dpop_proof_invalid",errorMessage:o instanceof Error?o.message:"Invalid DPoP proof"}}}validateHeader(e){if(e.typ!=="dpop+jwt")return {valid:false,errorCode:"dpop_proof_invalid",errorMessage:"Invalid DPoP proof type: must be dpop+jwt"};if(!pe.has(e.alg))return {valid:false,errorCode:"dpop_proof_invalid",errorMessage:`Unsupported algorithm: ${e.alg}`};if(!e.jwk)return {valid:false,errorCode:"dpop_proof_invalid",errorMessage:"Missing JWK in DPoP proof header"};let r=e.jwk;for(let o of fe)if(o in r&&r[o]!==void 0)return {valid:false,errorCode:"dpop_proof_invalid",errorMessage:"DPoP proof JWK contains private key parameters"};return {valid:true}}validatePayloadStructure(e){return e.jti?e.htm?e.htu?typeof e.iat!="number"?{valid:false,errorCode:"dpop_proof_invalid",errorMessage:"Missing or invalid iat claim"}:{valid:true}:{valid:false,errorCode:"dpop_proof_invalid",errorMessage:"Missing htu claim"}:{valid:false,errorCode:"dpop_proof_invalid",errorMessage:"Missing htm claim"}:{valid:false,errorCode:"dpop_proof_invalid",errorMessage:"Missing jti claim"}}async verifySignature(e,r){try{let o=await this.crypto.importJwk(r.jwk,r.alg),t=e.lastIndexOf("."),n=e.substring(0,t),a=e.substring(t+1),s=new TextEncoder().encode(n),l=chunk7POGA5LZ_cjs.c(a);return await this.crypto.verifySignature(r.alg,o,l,s)?{valid:!0}:{valid:!1,errorCode:"dpop_proof_signature_invalid",errorMessage:"DPoP proof signature verification failed"}}catch(o){return {valid:false,errorCode:"dpop_proof_signature_invalid",errorMessage:`Failed to verify DPoP proof: ${o instanceof Error?o.message:"Unknown error"}`}}}async validateAccessTokenHash(e,r){if(!e)return {valid:false,errorCode:"dpop_ath_missing",errorMessage:"Missing ath claim when access token is present"};let o=new TextEncoder().encode(r),t=await this.crypto.sha256(o),n=chunk7POGA5LZ_cjs.b(t);return p(e,n)?{valid:true}:{valid:false,errorCode:"dpop_ath_mismatch",errorMessage:"Access token hash mismatch"}}};function _(i,e,r){if(!(!r||!i))try{if(new URL(i).protocol!=="https:")throw new chunkTPROSFE7_cjs.a("configuration_error",`${e} must use HTTPS: ${i}. Set requireHttps: false to allow HTTP in development.`)}catch(o){throw o instanceof chunkTPROSFE7_cjs.a?o:new chunkTPROSFE7_cjs.a("configuration_error",`Invalid ${e} URL: ${i}`)}}function ge(i){let e=Array.isArray(i.issuer)?i.issuer:[i.issuer],r=Array.isArray(i.audience)?i.audience:[i.audience],o=i.requireHttps??true;for(let t of e)_(t,"issuer",o);return _(i.jwksUri,"jwksUri",o),_(i.introspectionEndpoint,"introspectionEndpoint",o),_(i.revocationEndpoint,"revocationEndpoint",o),{issuer:e,audience:r,jwksUri:i.jwksUri,clockToleranceSeconds:i.clockToleranceSeconds??60,jwksRefreshIntervalMs:i.jwksRefreshIntervalMs??36e5,introspectionEndpoint:i.introspectionEndpoint,revocationEndpoint:i.revocationEndpoint,clientCredentials:i.clientCredentials,http:i.http??chunk7POGA5LZ_cjs.a(),crypto:i.crypto??chunk7POGA5LZ_cjs.f(),clock:i.clock??chunk7POGA5LZ_cjs.g(),jwksCache:i.jwksCache??chunk7POGA5LZ_cjs.h({ttlMs:i.jwksRefreshIntervalMs??36e5}),requireHttps:o}}var T=class{constructor(e){this.jwksManager=null;this.tokenValidator=null;this.dpopValidator=null;this.introspectionClient=null;this.revocationClient=null;this.initPromise=null;this.initialized=false;this.config=ge(e);}async init(){if(!this.initialized){if(this.initPromise)return this.initPromise;this.initPromise=this.doInit();try{await this.initPromise,this.initialized=!0;}catch(e){throw this.initPromise=null,e}}}async doInit(){let e=this.config.jwksUri;e||(e=await this.discoverJwksUri()),this.jwksManager=new v({jwksUri:e,cacheTtlMs:this.config.jwksRefreshIntervalMs,http:this.config.http,crypto:this.config.crypto,clock:this.config.clock,cache:this.config.jwksCache}),this.tokenValidator=new k({jwksManager:this.jwksManager,crypto:this.config.crypto,clock:this.config.clock,options:{issuer:this.config.issuer,audience:this.config.audience,clockToleranceSeconds:this.config.clockToleranceSeconds}}),this.dpopValidator=new P(this.config.crypto,this.config.clock),this.config.introspectionEndpoint&&this.config.clientCredentials&&(this.introspectionClient=new w({endpoint:this.config.introspectionEndpoint,clientId:this.config.clientCredentials.clientId,clientSecret:this.config.clientCredentials.clientSecret,http:this.config.http})),this.config.revocationEndpoint&&this.config.clientCredentials&&(this.revocationClient=new C({endpoint:this.config.revocationEndpoint,clientId:this.config.clientCredentials.clientId,clientSecret:this.config.clientCredentials.clientSecret,http:this.config.http}));}async discoverJwksUri(){let e=this.config.issuer[0];if(!e)throw new chunkTPROSFE7_cjs.a("configuration_error","No issuer configured");let r=`${e.replace(/\/$/,"")}/.well-known/openid-configuration`;try{let o=await this.config.http.fetch(r,{headers:{Accept:"application/json"}});if(!o.ok)throw await o.text().catch(()=>{}),new chunkTPROSFE7_cjs.a("configuration_error",`Failed to fetch OpenID Configuration: ${o.status}`);let t=await o.json();if(!t.jwks_uri)throw new chunkTPROSFE7_cjs.a("configuration_error","OpenID Configuration missing jwks_uri");return _(t.jwks_uri,"discovered jwks_uri",this.config.requireHttps),t.jwks_uri}catch(o){throw o instanceof chunkTPROSFE7_cjs.a?o:new chunkTPROSFE7_cjs.a("configuration_error",`Failed to discover JWKS URI: ${o instanceof Error?o.message:"Unknown error"}`,{cause:o instanceof Error?o:void 0})}}async validateToken(e){return await this.init(),this.tokenValidator?this.tokenValidator.validate(e):{data:null,error:{code:"configuration_error",message:"Token validator not initialized"}}}async validateDPoP(e,r){return await this.init(),this.dpopValidator?this.dpopValidator.validate(e,r):{valid:false,errorCode:"configuration_error",errorMessage:"DPoP validator not initialized"}}async introspect(e,r){if(await this.init(),!this.introspectionClient)throw new chunkTPROSFE7_cjs.a("configuration_error","Introspection endpoint not configured");return this.introspectionClient.introspect({token:e,token_type_hint:r})}async revoke(e,r){if(await this.init(),!this.revocationClient)throw new chunkTPROSFE7_cjs.a("configuration_error","Revocation endpoint not configured");return this.revocationClient.revoke({token:e,token_type_hint:r})}getConfig(){return this.config}invalidateJwksCache(){this.jwksManager?.invalidate();}};function he(i){return new T(i)}var R="http://schemas.openid.net/event/backchannel-logout",E=class{validate(e,r){let o,t;try{let f=m(e);o=f.header,t=f.payload;}catch(f){return f instanceof h?{valid:false,error:'Algorithm "none" is not allowed for logout tokens',errorCode:"insecure_algorithm"}:{valid:false,error:"Invalid JWT format",errorCode:"invalid_format"}}if(!t.iss||!p(t.iss,r.issuer))return {valid:false,error:"Issuer validation failed",errorCode:"invalid_issuer"};if(!(Array.isArray(t.aud)?t.aud:[t.aud]).some(f=>p(f,r.audience)))return {valid:false,error:"Audience validation failed",errorCode:"invalid_audience"};if(!t.events||typeof t.events!="object")return {valid:false,error:"Missing or invalid events claim",errorCode:"invalid_events"};let s=t.events[R];if(s===void 0)return {valid:false,error:`Missing ${R} in events claim`,errorCode:"invalid_events"};if(typeof s!="object"||s===null||Array.isArray(s)||Object.keys(s).length!==0)return {valid:false,error:"Back-channel logout event must be an empty object",errorCode:"invalid_events"};if(!t.jti)return {valid:false,error:"Missing jti claim",errorCode:"missing_jti"};if(!t.sub&&!t.sid)return {valid:false,error:"Logout token must contain either sub or sid (or both)",errorCode:"missing_sub_and_sid"};if("nonce"in t&&t.nonce!==void 0)return {valid:false,error:"Logout token must not contain nonce claim",errorCode:"nonce_present"};let l=Math.floor(Date.now()/1e3),d=r.maxAge??60,u=r.clockSkew??30;return t.exp?l>t.exp+u?{valid:false,error:`Token expired at ${t.exp}`,errorCode:"token_expired"}:t.iat&&l-t.iat>d+u?{valid:false,error:`Token too old: iat ${t.iat} is more than ${d} seconds ago`,errorCode:"iat_too_old"}:t.iat&&t.iat>l+u?{valid:false,error:`Token iat is in the future: ${t.iat}`,errorCode:"not_yet_valid"}:r.expectedSub&&t.sub&&!p(t.sub,r.expectedSub)?{valid:false,error:"Subject validation failed",errorCode:"sub_mismatch"}:r.expectedSid&&t.sid&&!p(t.sid,r.expectedSid)?{valid:false,error:"Session ID validation failed",errorCode:"sid_mismatch"}:{valid:true,claims:t,header:o}:{valid:false,error:"Missing exp claim",errorCode:"token_expired"}}extractClaims(e){try{return m(e).payload}catch{return null}}extractHeader(e){try{return m(e).header}catch{return null}}};
2
+ Object.defineProperty(exports,"base64UrlDecode",{enumerable:true,get:function(){return chunk7POGA5LZ_cjs.c}});Object.defineProperty(exports,"base64UrlDecodeString",{enumerable:true,get:function(){return chunk7POGA5LZ_cjs.e}});Object.defineProperty(exports,"base64UrlEncode",{enumerable:true,get:function(){return chunk7POGA5LZ_cjs.b}});Object.defineProperty(exports,"base64UrlEncodeString",{enumerable:true,get:function(){return chunk7POGA5LZ_cjs.d}});Object.defineProperty(exports,"buildErrorHeaders",{enumerable:true,get:function(){return chunkO2ALCNXB_cjs.c}});Object.defineProperty(exports,"buildErrorResponse",{enumerable:true,get:function(){return chunkO2ALCNXB_cjs.a}});Object.defineProperty(exports,"buildWwwAuthenticateHeader",{enumerable:true,get:function(){return chunkO2ALCNXB_cjs.b}});Object.defineProperty(exports,"AuthrimServerError",{enumerable:true,get:function(){return chunkTPROSFE7_cjs.a}});Object.defineProperty(exports,"authenticateRequest",{enumerable:true,get:function(){return chunkTPROSFE7_cjs.c}});Object.defineProperty(exports,"getServerErrorMeta",{enumerable:true,get:function(){return chunkTPROSFE7_cjs.b}});exports.AuthrimServer=T;exports.BACKCHANNEL_LOGOUT_EVENT=R;exports.BackChannelLogoutValidator=E;exports.DPoPValidator=P;exports.IntrospectionClient=w;exports.JwksManager=v;exports.RevocationClient=C;exports.TokenValidator=k;exports.calculateJwkThumbprint=A;exports.createAuthrimServer=he;exports.getExpiresIn=V;exports.parseJwt=m;exports.selectKey=S;exports.selectKeyByAlgorithm=N;exports.selectKeyByKid=W;exports.validateClaims=J;exports.verifyJwkThumbprint=ue;exports.verifyJwtSignature=I;//# sourceMappingURL=index.cjs.map
3
+ //# sourceMappingURL=index.cjs.map