@iqauth/sdk 2.6.4 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +173 -1
  2. package/dist/browser-session.d.mts +4 -4
  3. package/dist/browser-session.d.ts +4 -4
  4. package/dist/browser-session.js +181 -41
  5. package/dist/browser-session.mjs +3 -3
  6. package/dist/browser.d.mts +5 -5
  7. package/dist/browser.d.ts +5 -5
  8. package/dist/browser.js +271 -32
  9. package/dist/browser.mjs +5 -5
  10. package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
  11. package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
  12. package/dist/chunk-GLXSIGVS.mjs +66 -0
  13. package/dist/{chunk-DJIBN2N7.mjs → chunk-GN37E64I.mjs} +29 -7
  14. package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
  15. package/dist/{chunk-W3F4JYGP.mjs → chunk-JXQI62A7.mjs} +108 -18
  16. package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
  17. package/dist/chunk-PMAFENVI.mjs +229 -0
  18. package/dist/chunk-RR2MGPTK.mjs +2724 -0
  19. package/dist/{chunk-XAWYUPMO.mjs → chunk-RTJAIBXY.mjs} +220 -20
  20. package/dist/{chunk-6TDJJER7.mjs → chunk-RUJXRTEW.mjs} +164 -5
  21. package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
  22. package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
  23. package/dist/{chunk-BVV54LPI.mjs → chunk-YVALAG3B.mjs} +10 -4
  24. package/dist/cli/index.js +2 -2
  25. package/dist/cli/index.mjs +2 -2
  26. package/dist/{client-kYlJFgPv.d.mts → client-BGFnBpfc.d.mts} +47 -4
  27. package/dist/{client-BNQe3AgF.d.ts → client-CDQ21LvW.d.ts} +47 -4
  28. package/dist/{doctor-YYNHNMLD.mjs → doctor-JAFXWU3X.mjs} +2 -2
  29. package/dist/errors-Jl1Jtm-6.d.mts +107 -0
  30. package/dist/errors-Jl1Jtm-6.d.ts +107 -0
  31. package/dist/{express-B6_1vBYZ.d.mts → express-CVNQEkOr.d.mts} +2 -2
  32. package/dist/{express-CHpfa7D_.d.ts → express-Piv2WhWM.d.ts} +2 -2
  33. package/dist/express.d.mts +7 -6
  34. package/dist/express.d.ts +7 -6
  35. package/dist/express.js +349 -52
  36. package/dist/express.mjs +39 -12
  37. package/dist/fastify.d.mts +2 -0
  38. package/dist/fastify.d.ts +2 -0
  39. package/dist/fastify.js +332 -52
  40. package/dist/fastify.mjs +23 -8
  41. package/dist/hono.d.mts +2 -0
  42. package/dist/hono.d.ts +2 -0
  43. package/dist/hono.js +329 -52
  44. package/dist/hono.mjs +20 -8
  45. package/dist/index-5KSZEnDe.d.ts +1626 -0
  46. package/dist/index-CKoZHAoc.d.mts +1626 -0
  47. package/dist/index.d.mts +56 -8
  48. package/dist/index.d.ts +56 -8
  49. package/dist/index.js +565 -69
  50. package/dist/index.mjs +29 -9
  51. package/dist/{keys-NLWFAOEM.mjs → keys-6Y776TG2.mjs} +2 -2
  52. package/dist/locales.d.mts +1 -1
  53. package/dist/locales.d.ts +1 -1
  54. package/dist/mobile.d.mts +77 -7
  55. package/dist/mobile.d.ts +77 -7
  56. package/dist/mobile.js +276 -41
  57. package/dist/mobile.mjs +98 -3
  58. package/dist/next.d.mts +2 -1
  59. package/dist/next.d.ts +2 -1
  60. package/dist/next.js +391 -201
  61. package/dist/next.mjs +22 -7
  62. package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-CGpMRie4.d.ts} +1 -1
  63. package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-M5G47LWO.d.mts} +1 -1
  64. package/dist/{publishableKey-BaR0HoAH.d.ts → publishableKey-f2kq-rKw.d.mts} +1 -1
  65. package/dist/{publishableKey-BaR0HoAH.d.mts → publishableKey-f2kq-rKw.d.ts} +1 -1
  66. package/dist/react-permissions.d.mts +52 -0
  67. package/dist/react-permissions.d.ts +52 -0
  68. package/dist/react-permissions.js +239 -0
  69. package/dist/react-permissions.mjs +97 -0
  70. package/dist/react.d.mts +9 -1624
  71. package/dist/react.d.ts +9 -1624
  72. package/dist/react.js +313 -33
  73. package/dist/react.mjs +58 -2632
  74. package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
  75. package/dist/server/handlers.d.mts +148 -3
  76. package/dist/server/handlers.d.ts +148 -3
  77. package/dist/server/handlers.js +410 -11
  78. package/dist/server/handlers.mjs +12 -3
  79. package/dist/server.d.mts +151 -8
  80. package/dist/server.d.ts +151 -8
  81. package/dist/server.js +406 -50
  82. package/dist/server.mjs +93 -11
  83. package/dist/service.d.mts +4 -4
  84. package/dist/service.d.ts +4 -4
  85. package/dist/service.js +181 -41
  86. package/dist/service.mjs +3 -3
  87. package/dist/{signIn-OCr88Zf8.d.ts → signIn-BLFnz8SV.d.ts} +78 -3
  88. package/dist/{signIn-4OKLDEIH.mjs → signIn-SHBW6Z4T.mjs} +1 -1
  89. package/dist/{signIn-CiIBTJIh.d.mts → signIn-T-CZ6t6r.d.mts} +78 -3
  90. package/dist/test.mjs +3 -3
  91. package/dist/{tokens-DCyzzn8L.d.mts → tokens-Bqhmqq_R.d.ts} +9 -2
  92. package/dist/{tokens-aHiGFr_E.d.ts → tokens-CITeoG6P.d.mts} +9 -2
  93. package/dist/{types-6bNdxesb.d.ts → types-BdQ2lqfT.d.mts} +1 -1
  94. package/dist/{types-6bNdxesb.d.mts → types-BdQ2lqfT.d.ts} +1 -1
  95. package/dist/{types-DZAflmmq.d.mts → types-XOV9XPVi.d.mts} +99 -10
  96. package/dist/{types-DZAflmmq.d.ts → types-XOV9XPVi.d.ts} +99 -10
  97. package/dist/webhooks.d.mts +100 -17
  98. package/dist/webhooks.d.ts +100 -17
  99. package/dist/webhooks.js +164 -15
  100. package/dist/webhooks.mjs +7 -1
  101. package/dist/ws.d.mts +2 -2
  102. package/dist/ws.d.ts +2 -2
  103. package/dist/ws.js +80 -30
  104. package/dist/ws.mjs +4 -4
  105. package/docs/error-handling.md +101 -0
  106. package/docs/guides/effective-permissions.md +171 -0
  107. package/package.json +13 -3
  108. package/dist/chunk-UKZLOHZG.mjs +0 -83
  109. package/dist/errors-CDdl24MP.d.mts +0 -52
  110. package/dist/errors-CDdl24MP.d.ts +0 -52
@@ -20,21 +20,42 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/server/handlers.ts
21
21
  var handlers_exports = {};
22
22
  __export(handlers_exports, {
23
+ __resetSignoutMarkersForTests: () => __resetSignoutMarkersForTests,
24
+ buildUserinfoResponse: () => buildUserinfoResponse,
25
+ createInMemorySignoutRegistry: () => createInMemorySignoutRegistry,
23
26
  handleCallback: () => handleCallback,
24
27
  handleRefresh: () => handleRefresh,
25
28
  handleSignout: () => handleSignout,
29
+ handleUserinfo: () => handleUserinfo,
26
30
  serializeCookie: () => serializeCookie
27
31
  });
28
32
  module.exports = __toCommonJS(handlers_exports);
29
33
 
30
34
  // src/errors.ts
31
- var IQAuthError = class extends Error {
32
- constructor(code, message, status, raw) {
35
+ var IQAuthError = class _IQAuthError extends Error {
36
+ constructor(code, message, status, cause) {
33
37
  super(message);
34
38
  this.name = "IQAuthError";
35
39
  this.code = code;
36
40
  this.status = status;
37
- this.raw = raw;
41
+ this.cause = cause;
42
+ this.raw = cause;
43
+ }
44
+ /**
45
+ * Type guard: true when `value` is an `IQAuthError`. Useful for adapters
46
+ * that round-trip errors through `unknown` (e.g. fastify's `setErrorHandler`).
47
+ */
48
+ static isIQAuthError(value) {
49
+ return value instanceof _IQAuthError || typeof value === "object" && value !== null && value.name === "IQAuthError" && typeof value.code === "string";
50
+ }
51
+ /**
52
+ * Type-narrowed code check. Lets callers write
53
+ * `if (err.is("token_expired")) …` with full IntelliSense for the typed
54
+ * taxonomy without losing the ability to handle server codes via
55
+ * `err.code === "TOKEN_REVOKED"`.
56
+ */
57
+ is(code) {
58
+ return this.code === code;
38
59
  }
39
60
  };
40
61
 
@@ -67,14 +88,14 @@ function assertPublishableKey(raw, opts) {
67
88
  const ctx = opts?.context ? `${opts.context}: ` : "";
68
89
  if (typeof raw !== "string" || raw.length === 0) {
69
90
  throw new IQAuthError(
70
- "CONFIG_INVALID",
91
+ "config_invalid",
71
92
  `${ctx}IQAuth publishable key is missing. Set IQAUTH_PUBLISHABLE_KEY (or pass publishableKey) to a pk_test_\u2026 or pk_live_\u2026 value from the IQAuth admin console.`
72
93
  );
73
94
  }
74
95
  const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
75
96
  if (!shapeMatch) {
76
97
  throw new IQAuthError(
77
- "CONFIG_INVALID",
98
+ "config_invalid",
78
99
  `${ctx}IQAuth publishable key is malformed (got ${raw.slice(0, 12)}\u2026). Expected pk_test_\u2026 or pk_live_\u2026; regenerate the key from the IQAuth admin console.`
79
100
  );
80
101
  }
@@ -83,19 +104,19 @@ function assertPublishableKey(raw, opts) {
83
104
  decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
84
105
  } catch {
85
106
  throw new IQAuthError(
86
- "CONFIG_INVALID",
107
+ "config_invalid",
87
108
  `${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
88
109
  );
89
110
  }
90
111
  if (!isPublishableKeyPayload(decoded)) {
91
112
  throw new IQAuthError(
92
- "CONFIG_INVALID",
113
+ "config_invalid",
93
114
  `${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
94
115
  );
95
116
  }
96
117
  if (!isValidIssuerUrl(decoded.iss)) {
97
118
  throw new IQAuthError(
98
- "CONFIG_INVALID",
119
+ "config_invalid",
99
120
  `${ctx}IQAuth publishable key encodes an invalid issuer (iss=${JSON.stringify(decoded.iss)}). Expected a fully-qualified URL like "https://auth.example.com" (scheme required). Regenerate the key from the IQAuth admin console \u2014 the new key will encode a valid issuer URL.`
100
121
  );
101
122
  }
@@ -107,7 +128,267 @@ function isPublishableKeyPayload(value) {
107
128
  return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
108
129
  }
109
130
 
131
+ // src/modules/tokens.ts
132
+ var import_jose = require("jose");
133
+ var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
134
+ var DEFAULT_TOKEN_ISSUER = [
135
+ "https://auth.dispositioniq.com",
136
+ "auth.dispositioniq.com"
137
+ ];
138
+ var DEFAULT_TOKEN_AUDIENCE = [
139
+ "dispositioniq",
140
+ "iqcapture",
141
+ "iqreuse",
142
+ "iqvalidate"
143
+ ];
144
+ var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
145
+ function classifyJoseError(err) {
146
+ if (err instanceof import_jose.errors.JWTExpired) {
147
+ return { code: "token_expired", message: "Token has expired" };
148
+ }
149
+ if (err instanceof import_jose.errors.JOSEError) {
150
+ return { code: "token_invalid", message: err.message };
151
+ }
152
+ if (err instanceof Error) {
153
+ return { code: "token_invalid", message: err.message };
154
+ }
155
+ return { code: "token_invalid", message: "Token verification failed" };
156
+ }
157
+ function decodeProtectedHeader(token) {
158
+ const parts = token.split(".");
159
+ if (parts.length < 2) return null;
160
+ try {
161
+ const padded = parts[0] + "=".repeat((4 - parts[0].length % 4) % 4);
162
+ const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
163
+ let json;
164
+ if (typeof atob === "function") {
165
+ json = atob(b64);
166
+ } else {
167
+ const { Buffer: Buffer2 } = require("buffer");
168
+ json = Buffer2.from(b64, "base64").toString("utf8");
169
+ }
170
+ return JSON.parse(json);
171
+ } catch {
172
+ return null;
173
+ }
174
+ }
175
+ var TokensModule = class {
176
+ constructor(baseUrl, options = {}) {
177
+ this.jwksCache = null;
178
+ this.inFlightRefresh = null;
179
+ this.baseUrl = baseUrl;
180
+ this.defaultIssuer = options.issuer ?? DEFAULT_TOKEN_ISSUER;
181
+ this.defaultAudience = options.audience ?? DEFAULT_TOKEN_AUDIENCE;
182
+ this.defaultClockTolerance = options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
183
+ }
184
+ /**
185
+ * Verify a JWT access token using RS256/ES256 via JWKS from
186
+ * `/.well-known/jwks.json`. Backed by `jose` (Web Crypto) so it runs on
187
+ * Node, browser, and edge runtimes alike — no `node:crypto` dependency.
188
+ * Caches JWKS for 1 hour and refetches once on unknown `kid`.
189
+ */
190
+ async verify(token, options = {}) {
191
+ const header = decodeProtectedHeader(token);
192
+ if (!header) {
193
+ throw new IQAuthError("token_invalid", "Unable to decode token");
194
+ }
195
+ const kid = header.kid;
196
+ if (!kid) {
197
+ throw new IQAuthError("token_invalid", "Token missing kid header");
198
+ }
199
+ let cache = await this.ensureCache();
200
+ if (!cache.byKid.has(kid)) {
201
+ this.jwksCache = null;
202
+ cache = await this.ensureCache();
203
+ }
204
+ if (!cache.byKid.has(kid)) {
205
+ throw new IQAuthError("token_invalid", `Unknown key ID: ${kid}`);
206
+ }
207
+ const issuer = options.issuer ?? this.defaultIssuer;
208
+ const audience = options.audience ?? this.defaultAudience;
209
+ const clockTolerance = options.clockTolerance ?? this.defaultClockTolerance;
210
+ const algorithms = options.algorithms ?? ["RS256", "ES256"];
211
+ const verifyOptions = {
212
+ algorithms,
213
+ clockTolerance,
214
+ issuer,
215
+ audience
216
+ };
217
+ try {
218
+ const { payload } = await (0, import_jose.jwtVerify)(token, cache.verifier, verifyOptions);
219
+ return payload;
220
+ } catch (err) {
221
+ const classified = classifyJoseError(err);
222
+ throw new IQAuthError(classified.code, classified.message, void 0, err);
223
+ }
224
+ }
225
+ /**
226
+ * Decode a JWT without verification. Returns null if malformed.
227
+ */
228
+ decode(token) {
229
+ try {
230
+ const parts = token.split(".");
231
+ if (parts.length < 2) return null;
232
+ const payload = parts[1];
233
+ const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
234
+ const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
235
+ let json;
236
+ if (typeof atob === "function") {
237
+ json = atob(b64);
238
+ } else {
239
+ const { Buffer: Buffer2 } = require("buffer");
240
+ json = Buffer2.from(b64, "base64").toString("utf8");
241
+ }
242
+ try {
243
+ json = decodeURIComponent(escape(json));
244
+ } catch {
245
+ }
246
+ const claims = JSON.parse(json);
247
+ if (!claims || typeof claims !== "object") return null;
248
+ return claims;
249
+ } catch {
250
+ return null;
251
+ }
252
+ }
253
+ /** Check if a token is expired based on the `exp` claim. */
254
+ isExpired(token) {
255
+ const claims = this.decode(token);
256
+ if (!claims?.exp) return true;
257
+ const now = Math.floor(Date.now() / 1e3);
258
+ return claims.exp <= now;
259
+ }
260
+ /** Get the claims from a token without verification. */
261
+ getClaims(token) {
262
+ const claims = this.decode(token);
263
+ if (!claims) {
264
+ throw new IQAuthError("token_invalid", "Unable to decode token claims");
265
+ }
266
+ return claims;
267
+ }
268
+ async ensureCache() {
269
+ if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) {
270
+ return this.jwksCache;
271
+ }
272
+ await this.refreshJwks();
273
+ if (!this.jwksCache) {
274
+ throw new IQAuthError("jwks_unavailable", "JWKS cache unavailable after refresh");
275
+ }
276
+ return this.jwksCache;
277
+ }
278
+ async refreshJwks() {
279
+ if (this.inFlightRefresh) {
280
+ return this.inFlightRefresh;
281
+ }
282
+ this.inFlightRefresh = (async () => {
283
+ try {
284
+ let res;
285
+ try {
286
+ res = await fetch(`${this.baseUrl}/.well-known/jwks.json`);
287
+ } catch (err) {
288
+ throw new IQAuthError(
289
+ "network",
290
+ err instanceof Error ? err.message : "JWKS fetch network error",
291
+ void 0,
292
+ err
293
+ );
294
+ }
295
+ if (!res.ok) {
296
+ throw new IQAuthError(
297
+ "jwks_fetch_failed",
298
+ `Failed to fetch JWKS: ${res.status}`,
299
+ res.status
300
+ );
301
+ }
302
+ let jwks;
303
+ try {
304
+ jwks = await res.json();
305
+ } catch (err) {
306
+ throw new IQAuthError(
307
+ "jwks_fetch_failed",
308
+ "Malformed JWKS response: invalid JSON",
309
+ res.status,
310
+ err
311
+ );
312
+ }
313
+ if (!jwks || !Array.isArray(jwks.keys)) {
314
+ throw new IQAuthError(
315
+ "jwks_fetch_failed",
316
+ "Malformed JWKS response: expected { keys: [...] }"
317
+ );
318
+ }
319
+ const byKid = /* @__PURE__ */ new Set();
320
+ for (const key of jwks.keys) {
321
+ if (!key || typeof key.kid !== "string" || typeof key.n !== "string" && typeof key.x !== "string" || key.kty === "RSA" && (typeof key.n !== "string" || typeof key.e !== "string")) {
322
+ throw new IQAuthError(
323
+ "jwks_fetch_failed",
324
+ "Malformed JWKS response: key missing required fields"
325
+ );
326
+ }
327
+ byKid.add(key.kid);
328
+ }
329
+ const verifier = (0, import_jose.createLocalJWKSet)({ keys: jwks.keys });
330
+ this.jwksCache = { raw: jwks.keys, byKid, verifier, fetchedAt: Date.now() };
331
+ } finally {
332
+ this.inFlightRefresh = null;
333
+ }
334
+ })();
335
+ return this.inFlightRefresh;
336
+ }
337
+ /** @internal Exposed for testing — clears JWKS cache */
338
+ clearCache() {
339
+ this.jwksCache = null;
340
+ }
341
+ /**
342
+ * Task #126: Eagerly populate the JWKS cache so the first verify() call
343
+ * doesn't pay a network round-trip. Safe to call repeatedly — single-flight
344
+ * behavior is shared with the lazy refresh path. Errors are swallowed so
345
+ * callers (e.g. `attachHelpers` auto-prewarm) can fire-and-forget.
346
+ */
347
+ async prewarm() {
348
+ if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) return;
349
+ try {
350
+ await this.refreshJwks();
351
+ } catch {
352
+ }
353
+ }
354
+ };
355
+
110
356
  // src/server/handlers.ts
357
+ async function buildUserinfoResponse(claims, opts = {}) {
358
+ const baseUser = {
359
+ sub: claims.sub,
360
+ email: claims.email,
361
+ name: claims.name,
362
+ tenantId: claims.tenantId,
363
+ vendorId: claims.vendorId,
364
+ roles: claims.roles ?? [],
365
+ entitlements: claims.entitlements ?? []
366
+ };
367
+ const enriched = opts.enrich ? await opts.enrich(claims) : null;
368
+ const user = enriched ? { ...baseUser, ...enriched } : baseUser;
369
+ return {
370
+ success: true,
371
+ data: {
372
+ user,
373
+ claims,
374
+ tenantId: claims.tenantId ?? null
375
+ }
376
+ };
377
+ }
378
+ function emitTiming(cfg, event) {
379
+ if (cfg.debug) {
380
+ try {
381
+ console.debug("[iqauth_helper]", event);
382
+ } catch {
383
+ }
384
+ }
385
+ if (cfg.onTimingEvent) {
386
+ try {
387
+ cfg.onTimingEvent(event);
388
+ } catch {
389
+ }
390
+ }
391
+ }
111
392
  var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
112
393
  "TOKEN_REVOKED",
113
394
  "SESSION_REVOKED",
@@ -147,7 +428,11 @@ function resolve(config) {
147
428
  })),
148
429
  appId: parsed.appId,
149
430
  tenantId: parsed.tenantId,
150
- clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
431
+ clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only",
432
+ debug: config.debug,
433
+ onTimingEvent: config.onTimingEvent,
434
+ signoutRegistry: config.signoutRegistry ?? defaultSignoutRegistry,
435
+ signoutMarkerTtlMs: config.signoutMarkerTtlMs ?? DEFAULT_SIGNOUT_TTL_MS
151
436
  };
152
437
  }
153
438
  function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
@@ -164,15 +449,64 @@ function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
164
449
  }
165
450
  function clearCookies(cfg) {
166
451
  return [
167
- makeCookie(cfg, cfg.accessCookieName, "", 0),
168
- makeCookie(cfg, cfg.refreshCookieName, "", 0)
452
+ { ...makeCookie(cfg, cfg.accessCookieName, "", 0), clear: true },
453
+ { ...makeCookie(cfg, cfg.refreshCookieName, "", 0), clear: true }
169
454
  ];
170
455
  }
456
+ var DEFAULT_SIGNOUT_TTL_MS = 6e4;
457
+ var inMemorySignoutMarkers = /* @__PURE__ */ new Map();
458
+ function pruneInMemoryMarkers(now) {
459
+ if (inMemorySignoutMarkers.size === 0) return;
460
+ for (const [k, exp] of inMemorySignoutMarkers) {
461
+ if (exp <= now) inMemorySignoutMarkers.delete(k);
462
+ }
463
+ }
464
+ var defaultSignoutRegistry = {
465
+ mark(token, ttlMs) {
466
+ const now = Date.now();
467
+ pruneInMemoryMarkers(now);
468
+ inMemorySignoutMarkers.set(token, now + ttlMs);
469
+ },
470
+ has(token) {
471
+ const now = Date.now();
472
+ const exp = inMemorySignoutMarkers.get(token);
473
+ if (!exp) return false;
474
+ if (exp <= now) {
475
+ inMemorySignoutMarkers.delete(token);
476
+ return false;
477
+ }
478
+ return true;
479
+ }
480
+ };
481
+ function __resetSignoutMarkersForTests() {
482
+ inMemorySignoutMarkers.clear();
483
+ }
484
+ function createInMemorySignoutRegistry() {
485
+ const store = /* @__PURE__ */ new Map();
486
+ return {
487
+ mark(token, ttlMs) {
488
+ const now = Date.now();
489
+ for (const [k, exp] of store) if (exp <= now) store.delete(k);
490
+ store.set(token, now + ttlMs);
491
+ },
492
+ has(token) {
493
+ const now = Date.now();
494
+ const exp = store.get(token);
495
+ if (!exp) return false;
496
+ if (exp <= now) {
497
+ store.delete(token);
498
+ return false;
499
+ }
500
+ return true;
501
+ }
502
+ };
503
+ }
171
504
  function serializeCookie(d) {
172
505
  const parts = [`${d.name}=${encodeURIComponent(d.value)}`];
173
506
  parts.push(`Path=${d.path}`);
174
507
  if (d.domain) parts.push(`Domain=${d.domain}`);
175
508
  parts.push(`Max-Age=${d.maxAge}`);
509
+ if (d.clear) parts.push("Expires=Thu, 01 Jan 1970 00:00:00 GMT");
176
510
  if (d.secure) parts.push("Secure");
177
511
  if (d.httpOnly) parts.push("HttpOnly");
178
512
  parts.push(`SameSite=${d.sameSite}`);
@@ -180,7 +514,9 @@ function serializeCookie(d) {
180
514
  }
181
515
  async function handleCallback(config, input) {
182
516
  const cfg = resolve(config);
517
+ const t0 = Date.now();
183
518
  if (!input.code || !input.redirectUri) {
519
+ emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "VALIDATION_ERROR" });
184
520
  return {
185
521
  status: 400,
186
522
  body: { success: false, error: { code: "VALIDATION_ERROR", message: "code and redirectUri are required" } },
@@ -188,6 +524,7 @@ async function handleCallback(config, input) {
188
524
  };
189
525
  }
190
526
  if (!cfg.secretKey) {
527
+ emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "INTERNAL_ERROR" });
191
528
  return {
192
529
  status: 500,
193
530
  body: { success: false, error: { code: "INTERNAL_ERROR", message: "secretKey is required for the callback handler" } },
@@ -211,6 +548,7 @@ async function handleCallback(config, input) {
211
548
  });
212
549
  const json = await res.json().catch(() => ({}));
213
550
  if (!res.ok || !json.access_token) {
551
+ emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: json.error || "OIDC_EXCHANGE_FAILED" });
214
552
  return {
215
553
  status: res.status || 502,
216
554
  body: {
@@ -230,6 +568,7 @@ async function handleCallback(config, input) {
230
568
  if (json.refresh_token) {
231
569
  cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.refresh_token, REFRESH_TOKEN_TTL_SECONDS));
232
570
  }
571
+ emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: true });
233
572
  return {
234
573
  status: 200,
235
574
  body: { success: true, data: { authenticated: true } },
@@ -238,8 +577,18 @@ async function handleCallback(config, input) {
238
577
  }
239
578
  async function handleRefresh(config, input) {
240
579
  const cfg = resolve(config);
580
+ const t0 = Date.now();
241
581
  const refreshToken = input.refreshToken;
582
+ const idemKey = input.idempotencyToken;
583
+ if (idemKey && await Promise.resolve(cfg.signoutRegistry.has(idemKey))) {
584
+ return {
585
+ status: 401,
586
+ body: { success: false, error: { code: "SESSION_REVOKED", message: "Session was signed out" } },
587
+ cookies: clearCookies(cfg)
588
+ };
589
+ }
242
590
  if (!refreshToken) {
591
+ emitTiming(cfg, { phase: "refresh", durationMs: Date.now() - t0, ok: false, code: "TOKEN_INVALID" });
243
592
  return {
244
593
  status: 401,
245
594
  body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
@@ -255,6 +604,7 @@ async function handleRefresh(config, input) {
255
604
  if (!res.ok || !json.success || !json.data?.accessToken) {
256
605
  const status = res.status || 401;
257
606
  const errorCode = json.error?.code || "TOKEN_INVALID";
607
+ emitTiming(cfg, { phase: "refresh", durationMs: Date.now() - t0, ok: false, code: errorCode });
258
608
  const shouldClear = shouldClearCookiesOnFailure(
259
609
  cfg.clearCookiesOnRefreshFailure,
260
610
  status,
@@ -278,6 +628,7 @@ async function handleRefresh(config, input) {
278
628
  if (json.data.refreshToken) {
279
629
  cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.data.refreshToken, REFRESH_TOKEN_TTL_SECONDS));
280
630
  }
631
+ emitTiming(cfg, { phase: "refresh", durationMs: Date.now() - t0, ok: true });
281
632
  return {
282
633
  status: 200,
283
634
  body: { success: true, data: { accessToken: json.data.accessToken } },
@@ -286,6 +637,10 @@ async function handleRefresh(config, input) {
286
637
  }
287
638
  async function handleSignout(config, input) {
288
639
  const cfg = resolve(config);
640
+ const t0 = Date.now();
641
+ if (input.idempotencyToken) {
642
+ await Promise.resolve(cfg.signoutRegistry.mark(input.idempotencyToken, cfg.signoutMarkerTtlMs));
643
+ }
289
644
  if (input.accessToken) {
290
645
  try {
291
646
  await cfg.fetchImpl(`${cfg.issuer}${cfg.logoutPath}`, {
@@ -307,16 +662,60 @@ async function handleSignout(config, input) {
307
662
  } catch {
308
663
  }
309
664
  }
665
+ emitTiming(cfg, { phase: "signout", durationMs: Date.now() - t0, ok: true });
310
666
  return {
311
667
  status: 200,
312
668
  body: { success: true, data: { signedOut: true } },
313
669
  cookies: clearCookies(cfg)
314
670
  };
315
671
  }
672
+ var TOKENS_CACHE = /* @__PURE__ */ new Map();
673
+ function getTokensFor(issuer) {
674
+ let m = TOKENS_CACHE.get(issuer);
675
+ if (!m) {
676
+ m = new TokensModule(issuer);
677
+ TOKENS_CACHE.set(issuer, m);
678
+ }
679
+ return m;
680
+ }
681
+ async function handleUserinfo(config, input) {
682
+ const cfg = resolve(config);
683
+ if (!input.accessToken) {
684
+ return {
685
+ status: 401,
686
+ body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing access token" } },
687
+ cookies: []
688
+ };
689
+ }
690
+ let claims;
691
+ try {
692
+ claims = await getTokensFor(cfg.issuer).verify(input.accessToken, config.verify);
693
+ } catch (err) {
694
+ const code = err instanceof IQAuthError ? err.code : err.code || "TOKEN_INVALID";
695
+ const message = err instanceof Error ? err.message : "Access token verification failed";
696
+ return {
697
+ status: 401,
698
+ body: { success: false, error: { code, message } },
699
+ cookies: []
700
+ };
701
+ }
702
+ const envelope = await buildUserinfoResponse(claims, {
703
+ enrich: config.userinfoEnricher ? (c) => config.userinfoEnricher(c, input.req) : void 0
704
+ });
705
+ return {
706
+ status: 200,
707
+ body: envelope,
708
+ cookies: []
709
+ };
710
+ }
316
711
  // Annotate the CommonJS export names for ESM import in node:
317
712
  0 && (module.exports = {
713
+ __resetSignoutMarkersForTests,
714
+ buildUserinfoResponse,
715
+ createInMemorySignoutRegistry,
318
716
  handleCallback,
319
717
  handleRefresh,
320
718
  handleSignout,
719
+ handleUserinfo,
321
720
  serializeCookie
322
721
  });
@@ -1,15 +1,24 @@
1
1
  import {
2
+ __resetSignoutMarkersForTests,
3
+ buildUserinfoResponse,
4
+ createInMemorySignoutRegistry,
2
5
  handleCallback,
3
6
  handleRefresh,
4
7
  handleSignout,
8
+ handleUserinfo,
5
9
  serializeCookie
6
- } from "../chunk-6TDJJER7.mjs";
7
- import "../chunk-WQWBJSSS.mjs";
8
- import "../chunk-6I6RM4MN.mjs";
10
+ } from "../chunk-RUJXRTEW.mjs";
11
+ import "../chunk-HVHNYPDC.mjs";
12
+ import "../chunk-NUO2I65G.mjs";
13
+ import "../chunk-6PJRLRB4.mjs";
9
14
  import "../chunk-Y6FXYEAI.mjs";
10
15
  export {
16
+ __resetSignoutMarkersForTests,
17
+ buildUserinfoResponse,
18
+ createInMemorySignoutRegistry,
11
19
  handleCallback,
12
20
  handleRefresh,
13
21
  handleSignout,
22
+ handleUserinfo,
14
23
  serializeCookie
15
24
  };