@iqauth/sdk 2.6.4 → 2.8.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.
Files changed (117) 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 +212 -46
  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 +293 -34
  9. package/dist/browser.mjs +5 -5
  10. package/dist/{chunk-BVV54LPI.mjs → chunk-25SSYDIP.mjs} +10 -4
  11. package/dist/{chunk-XAWYUPMO.mjs → chunk-4V7FKOTG.mjs} +242 -22
  12. package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
  13. package/dist/{chunk-SL3KRS4W.mjs → chunk-CIJORODR.mjs} +23 -1
  14. package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
  15. package/dist/chunk-GLXSIGVS.mjs +66 -0
  16. package/dist/{chunk-DJIBN2N7.mjs → chunk-GN37E64I.mjs} +29 -7
  17. package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
  18. package/dist/chunk-JRDVUWAL.mjs +46 -0
  19. package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
  20. package/dist/{chunk-5T7GHBX6.mjs → chunk-TLET552H.mjs} +36 -0
  21. package/dist/chunk-VYQ3ETCK.mjs +244 -0
  22. package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
  23. package/dist/chunk-WHT6WKTY.mjs +3180 -0
  24. package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
  25. package/dist/chunk-WSH4SW7F.mjs +490 -0
  26. package/dist/{chunk-W3F4JYGP.mjs → chunk-ZLJPABB7.mjs} +139 -23
  27. package/dist/cli/index.js +2 -2
  28. package/dist/cli/index.mjs +2 -2
  29. package/dist/{client-BNQe3AgF.d.ts → client-D8L-PaWr.d.mts} +59 -6
  30. package/dist/{client-kYlJFgPv.d.mts → client-DkPL0EPZ.d.ts} +59 -6
  31. package/dist/{doctor-YYNHNMLD.mjs → doctor-JAFXWU3X.mjs} +2 -2
  32. package/dist/errors-Jl1Jtm-6.d.mts +107 -0
  33. package/dist/errors-Jl1Jtm-6.d.ts +107 -0
  34. package/dist/{express-CHpfa7D_.d.ts → express-Budysq4h.d.ts} +2 -2
  35. package/dist/{express-B6_1vBYZ.d.mts → express-DDTA3qV1.d.mts} +2 -2
  36. package/dist/express.d.mts +7 -6
  37. package/dist/express.d.ts +7 -6
  38. package/dist/express.js +563 -85
  39. package/dist/express.mjs +73 -34
  40. package/dist/fastify.d.mts +10 -0
  41. package/dist/fastify.d.ts +10 -0
  42. package/dist/fastify.js +589 -65
  43. package/dist/fastify.mjs +101 -11
  44. package/dist/hono.d.mts +10 -0
  45. package/dist/hono.d.ts +10 -0
  46. package/dist/hono.js +566 -65
  47. package/dist/hono.mjs +78 -11
  48. package/dist/index-Cko-d5po.d.mts +1848 -0
  49. package/dist/index-RNqwEcmY.d.ts +1848 -0
  50. package/dist/index.d.mts +56 -8
  51. package/dist/index.d.ts +56 -8
  52. package/dist/index.js +694 -75
  53. package/dist/index.mjs +30 -10
  54. package/dist/{keys-NLWFAOEM.mjs → keys-6Y776TG2.mjs} +2 -2
  55. package/dist/locales.d.mts +1 -1
  56. package/dist/locales.d.ts +1 -1
  57. package/dist/locales.js +36 -0
  58. package/dist/locales.mjs +1 -1
  59. package/dist/mobile.d.mts +77 -7
  60. package/dist/mobile.d.ts +77 -7
  61. package/dist/mobile.js +307 -46
  62. package/dist/mobile.mjs +98 -3
  63. package/dist/next.d.mts +10 -1
  64. package/dist/next.d.ts +10 -1
  65. package/dist/next.js +596 -205
  66. package/dist/next.mjs +83 -10
  67. package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-BXPMZCLe.d.ts} +30 -2
  68. package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-IEycmsgb.d.mts} +30 -2
  69. package/dist/{publishableKey-BaR0HoAH.d.ts → publishableKey-f2kq-rKw.d.mts} +1 -1
  70. package/dist/{publishableKey-BaR0HoAH.d.mts → publishableKey-f2kq-rKw.d.ts} +1 -1
  71. package/dist/react-permissions.d.mts +52 -0
  72. package/dist/react-permissions.d.ts +52 -0
  73. package/dist/react-permissions.js +239 -0
  74. package/dist/react-permissions.mjs +98 -0
  75. package/dist/react.d.mts +9 -1624
  76. package/dist/react.d.ts +9 -1624
  77. package/dist/react.js +882 -73
  78. package/dist/react.mjs +71 -2631
  79. package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
  80. package/dist/server/handlers.d.mts +200 -4
  81. package/dist/server/handlers.d.ts +200 -4
  82. package/dist/server/handlers.js +530 -16
  83. package/dist/server/handlers.mjs +14 -3
  84. package/dist/server.d.mts +171 -8
  85. package/dist/server.d.ts +171 -8
  86. package/dist/server.js +579 -61
  87. package/dist/server.mjs +99 -12
  88. package/dist/service.d.mts +4 -4
  89. package/dist/service.d.ts +4 -4
  90. package/dist/service.js +212 -46
  91. package/dist/service.mjs +3 -3
  92. package/dist/{signIn-CiIBTJIh.d.mts → signIn-CReqfXsh.d.mts} +95 -3
  93. package/dist/{signIn-OCr88Zf8.d.ts → signIn-Cfa1GTpO.d.ts} +95 -3
  94. package/dist/{signIn-4OKLDEIH.mjs → signIn-SHBW6Z4T.mjs} +1 -1
  95. package/dist/test.mjs +3 -3
  96. package/dist/{tokens-DCyzzn8L.d.mts → tokens-9F6ETrzk.d.ts} +9 -2
  97. package/dist/{tokens-aHiGFr_E.d.ts → tokens-B06VtvUi.d.mts} +9 -2
  98. package/dist/{types-DZAflmmq.d.mts → types-Bn8O-OEd.d.mts} +164 -11
  99. package/dist/{types-DZAflmmq.d.ts → types-Bn8O-OEd.d.ts} +164 -11
  100. package/dist/{types-6bNdxesb.d.ts → types-DnU2LhXR.d.mts} +7 -1
  101. package/dist/{types-6bNdxesb.d.mts → types-DnU2LhXR.d.ts} +7 -1
  102. package/dist/webhooks.d.mts +113 -17
  103. package/dist/webhooks.d.ts +113 -17
  104. package/dist/webhooks.js +179 -15
  105. package/dist/webhooks.mjs +7 -1
  106. package/dist/ws.d.mts +2 -2
  107. package/dist/ws.d.ts +2 -2
  108. package/dist/ws.js +80 -30
  109. package/dist/ws.mjs +4 -4
  110. package/docs/error-handling.md +101 -0
  111. package/docs/guides/effective-permissions.md +171 -0
  112. package/docs/guides/invitations.md +65 -0
  113. package/package.json +19 -4
  114. package/dist/chunk-6TDJJER7.mjs +0 -217
  115. package/dist/chunk-UKZLOHZG.mjs +0 -83
  116. package/dist/errors-CDdl24MP.d.mts +0 -52
  117. package/dist/errors-CDdl24MP.d.ts +0 -52
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  encodePublishableKey
3
- } from "./chunk-WQWBJSSS.mjs";
3
+ } from "./chunk-HVHNYPDC.mjs";
4
4
 
5
5
  // src/test.ts
6
6
  import { createServer } from "http";
@@ -0,0 +1,490 @@
1
+ import {
2
+ assertPublishableKey
3
+ } from "./chunk-HVHNYPDC.mjs";
4
+ import {
5
+ TokensModule
6
+ } from "./chunk-NUO2I65G.mjs";
7
+ import {
8
+ IQAuthError
9
+ } from "./chunk-6PJRLRB4.mjs";
10
+
11
+ // src/server/handlers.ts
12
+ async function buildUserinfoResponse(claims, opts = {}) {
13
+ const baseUser = {
14
+ sub: claims.sub,
15
+ email: claims.email,
16
+ name: claims.name,
17
+ tenantId: claims.tenantId,
18
+ vendorId: claims.vendorId,
19
+ roles: claims.roles ?? [],
20
+ entitlements: claims.entitlements ?? [],
21
+ // Task #171 — project the active source/client scope onto the userinfo
22
+ // payload so server handlers (`getSessionUser`, `/api/iqauth/userinfo`)
23
+ // expose it without consumers having to re-decode the JWT.
24
+ ...claims.scopeContext !== void 0 ? { scopeContext: claims.scopeContext } : {}
25
+ };
26
+ const enriched = opts.enrich ? await opts.enrich(claims) : null;
27
+ const user = enriched ? { ...baseUser, ...enriched } : baseUser;
28
+ return {
29
+ success: true,
30
+ data: {
31
+ user,
32
+ claims,
33
+ tenantId: claims.tenantId ?? null
34
+ }
35
+ };
36
+ }
37
+ function emitTiming(cfg, event) {
38
+ if (cfg.debug) {
39
+ try {
40
+ console.debug("[iqauth_helper]", event);
41
+ } catch {
42
+ }
43
+ }
44
+ if (cfg.onTimingEvent) {
45
+ try {
46
+ cfg.onTimingEvent(event);
47
+ } catch {
48
+ }
49
+ }
50
+ }
51
+ var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
52
+ "TOKEN_REVOKED",
53
+ "SESSION_REVOKED",
54
+ "INVALID_GRANT",
55
+ "invalid_grant",
56
+ "USER_DEACTIVATED",
57
+ "USER_DISABLED",
58
+ "TENANT_SUSPENDED"
59
+ ]);
60
+ function shouldClearCookiesOnFailure(policy, status, errorCode) {
61
+ if (policy === "always") return true;
62
+ if (policy === "never") return false;
63
+ if (status === 410) return true;
64
+ if (errorCode && TERMINAL_REFRESH_ERROR_CODES.has(errorCode)) return true;
65
+ return false;
66
+ }
67
+ var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
68
+ var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
69
+ function assertCookiePrefixInvariants(name, secure, path, domain) {
70
+ if (name.startsWith("__Host-")) {
71
+ if (!secure) {
72
+ throw new IQAuthError(
73
+ "config_invalid",
74
+ `Cookie "${name}" uses the __Host- prefix, which browsers only accept on a Secure cookie. Set secure:true (and serve over HTTPS).`
75
+ );
76
+ }
77
+ if (path !== "/") {
78
+ throw new IQAuthError(
79
+ "config_invalid",
80
+ `Cookie "${name}" uses the __Host- prefix, which requires Path=/ (got "${path}"). Remove cookiePath or set it to "/".`
81
+ );
82
+ }
83
+ if (domain) {
84
+ throw new IQAuthError(
85
+ "config_invalid",
86
+ `Cookie "${name}" uses the __Host- prefix, which forbids a Domain attribute (the cookie is host-locked). Remove cookieDomain.`
87
+ );
88
+ }
89
+ } else if (name.startsWith("__Secure-") && !secure) {
90
+ throw new IQAuthError(
91
+ "config_invalid",
92
+ `Cookie "${name}" uses the __Secure- prefix, which browsers only accept on a Secure cookie. Set secure:true (and serve over HTTPS).`
93
+ );
94
+ }
95
+ }
96
+ function resolve(config) {
97
+ const parsed = assertPublishableKey(config.publishableKey, { context: "@iqauth/sdk helpers" });
98
+ const inferredIssuer = parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`;
99
+ maybeWarnDefaultSignoutRegistry(config);
100
+ const secure = config.secure ?? true;
101
+ if (config.secure === false && config.allowInsecureCookies !== true) {
102
+ throw new IQAuthError(
103
+ "config_invalid",
104
+ "Refusing to issue auth cookies with secure:false \u2014 this exposes session cookies over plaintext HTTP. For local HTTP development, set allowInsecureCookies:true to acknowledge the risk. Production MUST use HTTPS with secure cookies."
105
+ );
106
+ }
107
+ const accessCookieName = config.accessCookieName ?? config.cookieNames?.access ?? "iqauth_at";
108
+ const refreshCookieName = config.refreshCookieName ?? config.cookieNames?.refresh ?? "iqauth_rt";
109
+ const stateCookieName = config.stateCookieName ?? "iqauth_state";
110
+ const cookiePath = config.cookiePath ?? "/";
111
+ const cookieDomain = config.cookieDomain;
112
+ for (const name of [accessCookieName, refreshCookieName, stateCookieName]) {
113
+ assertCookiePrefixInvariants(name, secure, cookiePath, cookieDomain);
114
+ }
115
+ return {
116
+ publishableKey: config.publishableKey,
117
+ secretKey: config.secretKey,
118
+ issuer: (config.issuer ?? inferredIssuer).replace(/\/+$/, ""),
119
+ accessCookieName,
120
+ refreshCookieName,
121
+ cookieDomain,
122
+ sameSite: config.sameSite ?? "lax",
123
+ secure,
124
+ cookiePath,
125
+ tokenPath: config.tokenPath ?? "/oidc/token",
126
+ refreshPath: config.refreshPath ?? "/api/v1/auth/refresh",
127
+ logoutPath: config.logoutPath ?? "/api/v1/auth/logout",
128
+ fetchImpl: config.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
129
+ throw new Error("global fetch is unavailable; pass fetchImpl");
130
+ })),
131
+ appId: parsed.appId,
132
+ tenantId: parsed.tenantId,
133
+ clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only",
134
+ debug: config.debug,
135
+ onTimingEvent: config.onTimingEvent,
136
+ signoutRegistry: config.signoutRegistry ?? defaultSignoutRegistry,
137
+ signoutMarkerTtlMs: config.signoutMarkerTtlMs ?? DEFAULT_SIGNOUT_TTL_MS,
138
+ requireOAuthState: config.requireOAuthState ?? true,
139
+ stateCookieName: config.stateCookieName ?? "iqauth_state"
140
+ };
141
+ }
142
+ function timingSafeEqualStr(a, b) {
143
+ const len = Math.max(a.length, b.length);
144
+ let diff = a.length ^ b.length;
145
+ for (let i = 0; i < len; i++) {
146
+ diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
147
+ }
148
+ return diff === 0;
149
+ }
150
+ function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
151
+ return {
152
+ name,
153
+ value,
154
+ maxAge,
155
+ httpOnly,
156
+ secure: cfg.secure,
157
+ sameSite: cfg.sameSite,
158
+ path: cfg.cookiePath,
159
+ domain: cfg.cookieDomain
160
+ };
161
+ }
162
+ function clearCookies(cfg) {
163
+ return [
164
+ { ...makeCookie(cfg, cfg.accessCookieName, "", 0), clear: true },
165
+ { ...makeCookie(cfg, cfg.refreshCookieName, "", 0), clear: true }
166
+ ];
167
+ }
168
+ function clearStateCookie(cfg) {
169
+ return { ...makeCookie(cfg, cfg.stateCookieName, "", 0, false), clear: true };
170
+ }
171
+ var DEFAULT_SIGNOUT_TTL_MS = 6e4;
172
+ var inMemorySignoutMarkers = /* @__PURE__ */ new Map();
173
+ function pruneInMemoryMarkers(now) {
174
+ if (inMemorySignoutMarkers.size === 0) return;
175
+ for (const [k, exp] of inMemorySignoutMarkers) {
176
+ if (exp <= now) inMemorySignoutMarkers.delete(k);
177
+ }
178
+ }
179
+ var defaultSignoutRegistry = {
180
+ mark(token, ttlMs) {
181
+ const now = Date.now();
182
+ pruneInMemoryMarkers(now);
183
+ inMemorySignoutMarkers.set(token, now + ttlMs);
184
+ },
185
+ has(token) {
186
+ const now = Date.now();
187
+ const exp = inMemorySignoutMarkers.get(token);
188
+ if (!exp) return false;
189
+ if (exp <= now) {
190
+ inMemorySignoutMarkers.delete(token);
191
+ return false;
192
+ }
193
+ return true;
194
+ }
195
+ };
196
+ var warnedDefaultSignoutRegistry = false;
197
+ function maybeWarnDefaultSignoutRegistry(config) {
198
+ if (warnedDefaultSignoutRegistry) return;
199
+ if (config.signoutRegistry) return;
200
+ warnedDefaultSignoutRegistry = true;
201
+ console.warn(
202
+ "[IQAuth] Using the in-memory signout registry (process-local). Signout idempotency is NOT shared across instances \u2014 in a multi-replica deployment a /refresh racing a /signout on another replica can reissue cookies after sign-out. Plug a shared backend (e.g. Redis) into IQAuthHelperConfig.signoutRegistry to fix this and silence this warning."
203
+ );
204
+ }
205
+ function __resetSignoutMarkersForTests() {
206
+ inMemorySignoutMarkers.clear();
207
+ }
208
+ function __resetSignoutRegistryWarningForTests() {
209
+ warnedDefaultSignoutRegistry = false;
210
+ }
211
+ function createInMemorySignoutRegistry() {
212
+ const store = /* @__PURE__ */ new Map();
213
+ return {
214
+ mark(token, ttlMs) {
215
+ const now = Date.now();
216
+ for (const [k, exp] of store) if (exp <= now) store.delete(k);
217
+ store.set(token, now + ttlMs);
218
+ },
219
+ has(token) {
220
+ const now = Date.now();
221
+ const exp = store.get(token);
222
+ if (!exp) return false;
223
+ if (exp <= now) {
224
+ store.delete(token);
225
+ return false;
226
+ }
227
+ return true;
228
+ }
229
+ };
230
+ }
231
+ function serializeCookie(d) {
232
+ const parts = [`${d.name}=${encodeURIComponent(d.value)}`];
233
+ parts.push(`Path=${d.path}`);
234
+ if (d.domain) parts.push(`Domain=${d.domain}`);
235
+ parts.push(`Max-Age=${d.maxAge}`);
236
+ if (d.clear) parts.push("Expires=Thu, 01 Jan 1970 00:00:00 GMT");
237
+ if (d.secure) parts.push("Secure");
238
+ if (d.httpOnly) parts.push("HttpOnly");
239
+ parts.push(`SameSite=${d.sameSite}`);
240
+ return parts.join("; ");
241
+ }
242
+ async function handleCallback(config, input) {
243
+ const cfg = resolve(config);
244
+ const t0 = Date.now();
245
+ if (!input.code || !input.redirectUri) {
246
+ emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "VALIDATION_ERROR" });
247
+ return {
248
+ status: 400,
249
+ body: { success: false, error: { code: "VALIDATION_ERROR", message: "code and redirectUri are required" } },
250
+ cookies: []
251
+ };
252
+ }
253
+ const provided = input.state;
254
+ const expected = input.expectedState;
255
+ const stateOk = cfg.requireOAuthState ? !!expected && !!provided && timingSafeEqualStr(provided, expected) : !expected || !!provided && timingSafeEqualStr(provided, expected);
256
+ if (!stateOk) {
257
+ emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "STATE_MISMATCH" });
258
+ return {
259
+ status: 400,
260
+ body: {
261
+ success: false,
262
+ error: {
263
+ code: "STATE_MISMATCH",
264
+ message: "OAuth state validation failed; the sign-in could not be verified as originating from this browser."
265
+ }
266
+ },
267
+ cookies: [clearStateCookie(cfg)]
268
+ };
269
+ }
270
+ if (!cfg.secretKey) {
271
+ emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "INTERNAL_ERROR" });
272
+ return {
273
+ status: 500,
274
+ body: { success: false, error: { code: "INTERNAL_ERROR", message: "secretKey is required for the callback handler" } },
275
+ cookies: []
276
+ };
277
+ }
278
+ const body = new URLSearchParams({
279
+ grant_type: "authorization_code",
280
+ code: input.code,
281
+ redirect_uri: input.redirectUri,
282
+ client_id: cfg.appId
283
+ });
284
+ if (input.codeVerifier) body.set("code_verifier", input.codeVerifier);
285
+ const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.tokenPath}`, {
286
+ method: "POST",
287
+ headers: {
288
+ "Content-Type": "application/x-www-form-urlencoded",
289
+ Authorization: `Basic ${typeof btoa === "function" ? btoa(`${cfg.appId}:${cfg.secretKey}`) : Buffer.from(`${cfg.appId}:${cfg.secretKey}`).toString("base64")}`
290
+ },
291
+ body: body.toString()
292
+ });
293
+ const json = await res.json().catch(() => ({}));
294
+ if (!res.ok || !json.access_token) {
295
+ emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: json.error || "OIDC_EXCHANGE_FAILED" });
296
+ return {
297
+ status: res.status || 502,
298
+ body: {
299
+ success: false,
300
+ error: {
301
+ code: json.error || "OIDC_EXCHANGE_FAILED",
302
+ message: json.error_description || "Authorization code exchange failed"
303
+ }
304
+ },
305
+ cookies: []
306
+ };
307
+ }
308
+ try {
309
+ await getTokensFor(cfg.issuer).verify(json.access_token, {
310
+ issuer: cfg.issuer,
311
+ ...config.verify
312
+ });
313
+ } catch (err) {
314
+ const code = err instanceof IQAuthError ? err.code : err.code || "TOKEN_INVALID";
315
+ emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code });
316
+ return {
317
+ status: 502,
318
+ body: {
319
+ success: false,
320
+ error: {
321
+ code: "ACCESS_TOKEN_VERIFICATION_FAILED",
322
+ message: "The issuer returned an access token that failed verification; no session was established."
323
+ }
324
+ },
325
+ cookies: []
326
+ };
327
+ }
328
+ const cookies = [];
329
+ cookies.push(
330
+ makeCookie(cfg, cfg.accessCookieName, json.access_token, json.expires_in ?? ACCESS_TOKEN_TTL_SECONDS)
331
+ );
332
+ if (json.refresh_token) {
333
+ cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.refresh_token, REFRESH_TOKEN_TTL_SECONDS));
334
+ }
335
+ cookies.push(clearStateCookie(cfg));
336
+ emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: true });
337
+ return {
338
+ status: 200,
339
+ body: { success: true, data: { authenticated: true } },
340
+ cookies
341
+ };
342
+ }
343
+ async function handleRefresh(config, input) {
344
+ const cfg = resolve(config);
345
+ const t0 = Date.now();
346
+ const refreshToken = input.refreshToken;
347
+ const idemKey = input.idempotencyToken;
348
+ if (idemKey && await Promise.resolve(cfg.signoutRegistry.has(idemKey))) {
349
+ return {
350
+ status: 401,
351
+ body: { success: false, error: { code: "SESSION_REVOKED", message: "Session was signed out" } },
352
+ cookies: clearCookies(cfg)
353
+ };
354
+ }
355
+ if (!refreshToken) {
356
+ emitTiming(cfg, { phase: "refresh", durationMs: Date.now() - t0, ok: false, code: "TOKEN_INVALID" });
357
+ return {
358
+ status: 401,
359
+ body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
360
+ cookies: cfg.clearCookiesOnRefreshFailure === "always" ? clearCookies(cfg) : []
361
+ };
362
+ }
363
+ const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
364
+ method: "POST",
365
+ headers: { "Content-Type": "application/json" },
366
+ body: JSON.stringify({ refreshToken })
367
+ });
368
+ const json = await res.json().catch(() => ({}));
369
+ if (!res.ok || !json.success || !json.data?.accessToken) {
370
+ const status = res.status || 401;
371
+ const errorCode = json.error?.code || "TOKEN_INVALID";
372
+ emitTiming(cfg, { phase: "refresh", durationMs: Date.now() - t0, ok: false, code: errorCode });
373
+ const shouldClear = shouldClearCookiesOnFailure(
374
+ cfg.clearCookiesOnRefreshFailure,
375
+ status,
376
+ errorCode
377
+ );
378
+ return {
379
+ status,
380
+ body: {
381
+ success: false,
382
+ error: {
383
+ code: errorCode,
384
+ message: json.error?.message || "Refresh failed"
385
+ }
386
+ },
387
+ cookies: shouldClear ? clearCookies(cfg) : []
388
+ };
389
+ }
390
+ const cookies = [
391
+ makeCookie(cfg, cfg.accessCookieName, json.data.accessToken, ACCESS_TOKEN_TTL_SECONDS)
392
+ ];
393
+ if (json.data.refreshToken) {
394
+ cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.data.refreshToken, REFRESH_TOKEN_TTL_SECONDS));
395
+ }
396
+ emitTiming(cfg, { phase: "refresh", durationMs: Date.now() - t0, ok: true });
397
+ return {
398
+ status: 200,
399
+ body: { success: true, data: { accessToken: json.data.accessToken } },
400
+ cookies
401
+ };
402
+ }
403
+ async function handleSignout(config, input) {
404
+ const cfg = resolve(config);
405
+ const t0 = Date.now();
406
+ if (input.idempotencyToken) {
407
+ await Promise.resolve(cfg.signoutRegistry.mark(input.idempotencyToken, cfg.signoutMarkerTtlMs));
408
+ }
409
+ if (input.accessToken) {
410
+ try {
411
+ await cfg.fetchImpl(`${cfg.issuer}${cfg.logoutPath}`, {
412
+ method: "POST",
413
+ headers: {
414
+ "Content-Type": "application/json",
415
+ Authorization: `Bearer ${input.accessToken}`
416
+ }
417
+ });
418
+ } catch {
419
+ }
420
+ }
421
+ if (input.endSsoSession !== false && input.ssoCookieHeader) {
422
+ try {
423
+ await cfg.fetchImpl(`${cfg.issuer}/oidc/sso-logout`, {
424
+ method: "POST",
425
+ headers: { Cookie: input.ssoCookieHeader }
426
+ });
427
+ } catch {
428
+ }
429
+ }
430
+ emitTiming(cfg, { phase: "signout", durationMs: Date.now() - t0, ok: true });
431
+ return {
432
+ status: 200,
433
+ body: { success: true, data: { signedOut: true } },
434
+ cookies: clearCookies(cfg)
435
+ };
436
+ }
437
+ var TOKENS_CACHE = /* @__PURE__ */ new Map();
438
+ function getTokensFor(issuer) {
439
+ let m = TOKENS_CACHE.get(issuer);
440
+ if (!m) {
441
+ m = new TokensModule(issuer);
442
+ TOKENS_CACHE.set(issuer, m);
443
+ }
444
+ return m;
445
+ }
446
+ async function handleUserinfo(config, input) {
447
+ const cfg = resolve(config);
448
+ if (!input.accessToken) {
449
+ return {
450
+ status: 401,
451
+ body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing access token" } },
452
+ cookies: []
453
+ };
454
+ }
455
+ let claims;
456
+ try {
457
+ claims = await getTokensFor(cfg.issuer).verify(input.accessToken, {
458
+ issuer: cfg.issuer,
459
+ ...config.verify
460
+ });
461
+ } catch (err) {
462
+ const code = err instanceof IQAuthError ? err.code : err.code || "TOKEN_INVALID";
463
+ const message = err instanceof Error ? err.message : "Access token verification failed";
464
+ return {
465
+ status: 401,
466
+ body: { success: false, error: { code, message } },
467
+ cookies: []
468
+ };
469
+ }
470
+ const envelope = await buildUserinfoResponse(claims, {
471
+ enrich: config.userinfoEnricher ? (c) => config.userinfoEnricher(c, input.req) : void 0
472
+ });
473
+ return {
474
+ status: 200,
475
+ body: envelope,
476
+ cookies: []
477
+ };
478
+ }
479
+
480
+ export {
481
+ buildUserinfoResponse,
482
+ __resetSignoutMarkersForTests,
483
+ __resetSignoutRegistryWarningForTests,
484
+ createInMemorySignoutRegistry,
485
+ serializeCookie,
486
+ handleCallback,
487
+ handleRefresh,
488
+ handleSignout,
489
+ handleUserinfo
490
+ };