@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
@@ -4,8 +4,8 @@ import {
4
4
  exitImpersonation,
5
5
  reverify,
6
6
  withReverification
7
- } from "./chunk-LIZYFXH7.mjs";
8
- import "./chunk-6I6RM4MN.mjs";
7
+ } from "./chunk-DFWHSDYQ.mjs";
8
+ import "./chunk-6PJRLRB4.mjs";
9
9
  import "./chunk-Y6FXYEAI.mjs";
10
10
  export {
11
11
  PRIOR_SESSION_STORAGE_KEY,
@@ -1,3 +1,6 @@
1
+ import { c as TokenVerifyOptions } from '../tokens-CITeoG6P.mjs';
2
+ import { J as JwtClaims, S as SessionUser } from '../types-XOV9XPVi.mjs';
3
+
1
4
  /**
2
5
  * Framework-neutral helper handlers for the auto-mounted routes added by the
3
6
  * @iqauth/sdk framework adapters. Each handler takes the parsed request
@@ -17,6 +20,35 @@
17
20
  * framework's cookie API. This keeps adapters trivially thin and makes the
18
21
  * handlers pure-functionally testable.
19
22
  */
23
+
24
+ /**
25
+ * Shape returned by the auto-mounted `GET /api/iqauth/me` route.
26
+ *
27
+ * Mirrors the envelope the browser SDK's `SessionManager.bootstrap()` already
28
+ * accepts: `data.user` is preferred when present (so an integrator can supply
29
+ * extra app-specific fields via `userinfoEnricher`), otherwise the SDK falls
30
+ * back to deriving a `SessionUser` from `data.claims` directly.
31
+ */
32
+ interface UserinfoResponse {
33
+ success: true;
34
+ data: {
35
+ user: SessionUser;
36
+ claims: JwtClaims;
37
+ tenantId: string | null;
38
+ };
39
+ }
40
+ /**
41
+ * Build the documented `/api/iqauth/me` envelope from a verified set of JWT
42
+ * claims. Pure / framework-neutral — exported so integrators replacing the
43
+ * route entirely can still emit the canonical shape.
44
+ *
45
+ * @param claims Verified JWT claims (output of `tokens.verify(token)`).
46
+ * @param opts.enrich Optional sync/async hook returning a partial
47
+ * `SessionUser` to merge over the claim-derived defaults.
48
+ */
49
+ declare function buildUserinfoResponse(claims: JwtClaims, opts?: {
50
+ enrich?: (claims: JwtClaims) => Partial<SessionUser> | Promise<Partial<SessionUser>>;
51
+ }): Promise<UserinfoResponse>;
20
52
  interface SetCookieDirective {
21
53
  name: string;
22
54
  value: string;
@@ -27,10 +59,21 @@ interface SetCookieDirective {
27
59
  sameSite: "lax" | "strict" | "none";
28
60
  path: string;
29
61
  domain?: string;
62
+ /**
63
+ * Marker for "this directive is clearing the cookie." When true, the
64
+ * serializer emits `Expires=Thu, 01 Jan 1970 00:00:00 GMT` in addition to
65
+ * `Max-Age=0` and an empty value. Some browsers (older Safari, certain
66
+ * embedded WebViews) ignore `Max-Age=0` without an explicit `Expires` in
67
+ * the past, which is the root cause of "ghost signed-in" sessions after
68
+ * Sign Out. Adapters with a typed cookie API (Express `res.clearCookie`,
69
+ * Fastify `reply.clearCookie`) should use the framework helper for the
70
+ * same belt-and-suspenders behavior.
71
+ */
72
+ clear?: boolean;
30
73
  }
31
74
  interface HandlerResponse {
32
75
  status: number;
33
- body: Record<string, unknown>;
76
+ body: Record<string, unknown> | UserinfoResponse;
34
77
  cookies: SetCookieDirective[];
35
78
  }
36
79
  interface IQAuthHelperConfig {
@@ -86,8 +129,80 @@ interface IQAuthHelperConfig {
86
129
  * apps that manage cookie lifecycle entirely outside the SDK helpers.
87
130
  */
88
131
  clearCookiesOnRefreshFailure?: "terminal-only" | "always" | "never";
132
+ /**
133
+ * Mount the auto-mounted `GET ${mountPath}/me` userinfo route. Off by default
134
+ * in adapters until the integrator opts in. When true, the handler accepts
135
+ * either an `Authorization: Bearer …` header OR the `iqauth_at` cookie,
136
+ * verifies the token against the issuer, and returns the documented
137
+ * {@link UserinfoResponse} envelope (`data.user` + `data.claims` +
138
+ * `data.tenantId`).
139
+ *
140
+ * Independent of `mountHelperRoutes` so apps can opt into `/me` without
141
+ * also auto-mounting `/callback`/`/refresh`/`/signout` (e.g. if they
142
+ * already manage those routes themselves).
143
+ */
144
+ mountUserinfo?: boolean;
145
+ /**
146
+ * Optional hook to add app-specific fields to the `data.user` returned by
147
+ * the auto-mounted userinfo route. Receives the verified claims and the
148
+ * raw framework request object (typed `unknown` because adapters differ).
149
+ * Returned partial is shallow-merged over the claim-derived defaults so
150
+ * integrators can override e.g. `name`, or add fields not present on
151
+ * `SessionUser` itself (the response shape stays stable).
152
+ */
153
+ userinfoEnricher?: (claims: JwtClaims, req: unknown) => Partial<SessionUser> | Promise<Partial<SessionUser>>;
154
+ /**
155
+ * Token verification overrides forwarded to the cached `TokensModule` used
156
+ * by the userinfo handler. Mirrors the per-call shape on `tokens.verify`.
157
+ */
158
+ verify?: TokenVerifyOptions;
159
+ /**
160
+ * Task #126: Optional debug + timing-event hook. When `debug` is true, the
161
+ * helper handlers emit `console.debug("[iqauth_helper]", evt)` for each
162
+ * phase (`callback`, `refresh`, `signout`). When `onTimingEvent` is set,
163
+ * the same event is also passed to the callback, with `{ phase, durationMs,
164
+ * ok, code? }` shape. Use to push timings into your APM (Datadog, OTEL).
165
+ */
166
+ debug?: boolean;
167
+ onTimingEvent?: (event: IQAuthTimingEvent) => void;
168
+ /**
169
+ * Pluggable registry that tracks recently-signed-out idempotency tokens
170
+ * (synthetic per-session opaque strings; see {@link SignoutRegistry}).
171
+ * Defaults to a module-scoped in-memory `Map` shared by every adapter
172
+ * instance in this process. Multi-instance deployments (Railway replicas,
173
+ * autoscaled containers, blue/green) MUST plug in a shared store
174
+ * (Redis is the obvious fit) so a refresh routed to instance B can see
175
+ * the signout that landed on instance A. The interface is intentionally
176
+ * tiny — `mark` records the token with a TTL, `has` queries it.
177
+ */
178
+ signoutRegistry?: SignoutRegistry;
179
+ /**
180
+ * TTL applied when {@link handleSignout} marks an idempotency token.
181
+ * Defaults to 60 seconds — long enough to swallow any reasonable
182
+ * refresh-then-signout race, short enough that it can never delay a
183
+ * legitimate fresh sign-in.
184
+ */
185
+ signoutMarkerTtlMs?: number;
186
+ }
187
+ interface IQAuthTimingEvent {
188
+ phase: "callback" | "refresh" | "signout" | "bootstrap" | "signIn";
189
+ durationMs: number;
190
+ ok: boolean;
191
+ code?: string;
192
+ }
193
+ /**
194
+ * Pluggable backing store for the refresh/signout collapse cache. The
195
+ * default in-memory implementation is process-local and works fine for
196
+ * single-process deployments; multi-instance deployments should provide a
197
+ * shared backend (Redis is the canonical choice — `mark` ⇒ `SETEX`,
198
+ * `has` ⇒ `EXISTS`). Both methods may be sync or async; handlers always
199
+ * `await` the result.
200
+ */
201
+ interface SignoutRegistry {
202
+ mark(idempotencyToken: string, ttlMs: number): void | Promise<void>;
203
+ has(idempotencyToken: string): boolean | Promise<boolean>;
89
204
  }
90
- interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure" | "cookieNames">> {
205
+ interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure" | "cookieNames" | "mountUserinfo" | "userinfoEnricher" | "verify" | "debug" | "onTimingEvent" | "signoutRegistry" | "signoutMarkerTtlMs">> {
91
206
  cookieNames?: {
92
207
  access?: string;
93
208
  refresh?: string;
@@ -99,7 +214,23 @@ interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" |
99
214
  appId: string;
100
215
  tenantId: string;
101
216
  clearCookiesOnRefreshFailure: "terminal-only" | "always" | "never";
217
+ debug?: boolean;
218
+ onTimingEvent?: (event: IQAuthTimingEvent) => void;
219
+ signoutRegistry: SignoutRegistry;
220
+ signoutMarkerTtlMs: number;
102
221
  }
222
+ /**
223
+ * Test-only hook for clearing the default in-memory signout marker cache
224
+ * between test runs. Custom registries are unaffected. Not part of the
225
+ * public surface.
226
+ */
227
+ declare function __resetSignoutMarkersForTests(): void;
228
+ /**
229
+ * Public factory for a fresh in-memory {@link SignoutRegistry}. Useful for
230
+ * tests that want isolated state per case, and as a reference implementation
231
+ * for apps building Redis-backed (or other shared-store) registries.
232
+ */
233
+ declare function createInMemorySignoutRegistry(): SignoutRegistry;
103
234
  /**
104
235
  * Serialize a cookie directive to a Set-Cookie header value. Adapters that
105
236
  * lack a typed cookie API (Hono, raw Node) use this; Express / Fastify /
@@ -123,12 +254,26 @@ declare function handleCallback(config: IQAuthHelperConfig, input: {
123
254
  */
124
255
  declare function handleRefresh(config: IQAuthHelperConfig, input: {
125
256
  refreshToken?: string;
257
+ idempotencyToken?: string;
126
258
  }): Promise<HandlerResponse>;
127
259
  /** POST /api/iqauth/signout — clear cookies and best-effort revoke at issuer. */
128
260
  declare function handleSignout(config: IQAuthHelperConfig, input: {
129
261
  accessToken?: string;
262
+ refreshToken?: string;
263
+ idempotencyToken?: string;
130
264
  ssoCookieHeader?: string;
131
265
  endSsoSession?: boolean;
132
266
  }): Promise<HandlerResponse>;
267
+ /**
268
+ * GET /api/iqauth/me — verify the access token and return the documented
269
+ * userinfo envelope. Accepts the token from `Authorization: Bearer …` OR
270
+ * the access cookie (default `iqauth_at`). Adapters call this directly; the
271
+ * `req` value is forwarded verbatim to `userinfoEnricher` so integrators can
272
+ * read additional context (headers, sub-paths, etc).
273
+ */
274
+ declare function handleUserinfo(config: IQAuthHelperConfig, input: {
275
+ accessToken?: string;
276
+ req?: unknown;
277
+ }): Promise<HandlerResponse>;
133
278
 
134
- export { type HandlerResponse, type IQAuthHelperConfig, type ResolvedConfig as ResolvedIQAuthHelperConfig, type SetCookieDirective, handleCallback, handleRefresh, handleSignout, serializeCookie };
279
+ export { type HandlerResponse, type IQAuthHelperConfig, type IQAuthTimingEvent, type ResolvedConfig as ResolvedIQAuthHelperConfig, type SetCookieDirective, type SignoutRegistry, type UserinfoResponse, __resetSignoutMarkersForTests, buildUserinfoResponse, createInMemorySignoutRegistry, handleCallback, handleRefresh, handleSignout, handleUserinfo, serializeCookie };
@@ -1,3 +1,6 @@
1
+ import { c as TokenVerifyOptions } from '../tokens-Bqhmqq_R.js';
2
+ import { J as JwtClaims, S as SessionUser } from '../types-XOV9XPVi.js';
3
+
1
4
  /**
2
5
  * Framework-neutral helper handlers for the auto-mounted routes added by the
3
6
  * @iqauth/sdk framework adapters. Each handler takes the parsed request
@@ -17,6 +20,35 @@
17
20
  * framework's cookie API. This keeps adapters trivially thin and makes the
18
21
  * handlers pure-functionally testable.
19
22
  */
23
+
24
+ /**
25
+ * Shape returned by the auto-mounted `GET /api/iqauth/me` route.
26
+ *
27
+ * Mirrors the envelope the browser SDK's `SessionManager.bootstrap()` already
28
+ * accepts: `data.user` is preferred when present (so an integrator can supply
29
+ * extra app-specific fields via `userinfoEnricher`), otherwise the SDK falls
30
+ * back to deriving a `SessionUser` from `data.claims` directly.
31
+ */
32
+ interface UserinfoResponse {
33
+ success: true;
34
+ data: {
35
+ user: SessionUser;
36
+ claims: JwtClaims;
37
+ tenantId: string | null;
38
+ };
39
+ }
40
+ /**
41
+ * Build the documented `/api/iqauth/me` envelope from a verified set of JWT
42
+ * claims. Pure / framework-neutral — exported so integrators replacing the
43
+ * route entirely can still emit the canonical shape.
44
+ *
45
+ * @param claims Verified JWT claims (output of `tokens.verify(token)`).
46
+ * @param opts.enrich Optional sync/async hook returning a partial
47
+ * `SessionUser` to merge over the claim-derived defaults.
48
+ */
49
+ declare function buildUserinfoResponse(claims: JwtClaims, opts?: {
50
+ enrich?: (claims: JwtClaims) => Partial<SessionUser> | Promise<Partial<SessionUser>>;
51
+ }): Promise<UserinfoResponse>;
20
52
  interface SetCookieDirective {
21
53
  name: string;
22
54
  value: string;
@@ -27,10 +59,21 @@ interface SetCookieDirective {
27
59
  sameSite: "lax" | "strict" | "none";
28
60
  path: string;
29
61
  domain?: string;
62
+ /**
63
+ * Marker for "this directive is clearing the cookie." When true, the
64
+ * serializer emits `Expires=Thu, 01 Jan 1970 00:00:00 GMT` in addition to
65
+ * `Max-Age=0` and an empty value. Some browsers (older Safari, certain
66
+ * embedded WebViews) ignore `Max-Age=0` without an explicit `Expires` in
67
+ * the past, which is the root cause of "ghost signed-in" sessions after
68
+ * Sign Out. Adapters with a typed cookie API (Express `res.clearCookie`,
69
+ * Fastify `reply.clearCookie`) should use the framework helper for the
70
+ * same belt-and-suspenders behavior.
71
+ */
72
+ clear?: boolean;
30
73
  }
31
74
  interface HandlerResponse {
32
75
  status: number;
33
- body: Record<string, unknown>;
76
+ body: Record<string, unknown> | UserinfoResponse;
34
77
  cookies: SetCookieDirective[];
35
78
  }
36
79
  interface IQAuthHelperConfig {
@@ -86,8 +129,80 @@ interface IQAuthHelperConfig {
86
129
  * apps that manage cookie lifecycle entirely outside the SDK helpers.
87
130
  */
88
131
  clearCookiesOnRefreshFailure?: "terminal-only" | "always" | "never";
132
+ /**
133
+ * Mount the auto-mounted `GET ${mountPath}/me` userinfo route. Off by default
134
+ * in adapters until the integrator opts in. When true, the handler accepts
135
+ * either an `Authorization: Bearer …` header OR the `iqauth_at` cookie,
136
+ * verifies the token against the issuer, and returns the documented
137
+ * {@link UserinfoResponse} envelope (`data.user` + `data.claims` +
138
+ * `data.tenantId`).
139
+ *
140
+ * Independent of `mountHelperRoutes` so apps can opt into `/me` without
141
+ * also auto-mounting `/callback`/`/refresh`/`/signout` (e.g. if they
142
+ * already manage those routes themselves).
143
+ */
144
+ mountUserinfo?: boolean;
145
+ /**
146
+ * Optional hook to add app-specific fields to the `data.user` returned by
147
+ * the auto-mounted userinfo route. Receives the verified claims and the
148
+ * raw framework request object (typed `unknown` because adapters differ).
149
+ * Returned partial is shallow-merged over the claim-derived defaults so
150
+ * integrators can override e.g. `name`, or add fields not present on
151
+ * `SessionUser` itself (the response shape stays stable).
152
+ */
153
+ userinfoEnricher?: (claims: JwtClaims, req: unknown) => Partial<SessionUser> | Promise<Partial<SessionUser>>;
154
+ /**
155
+ * Token verification overrides forwarded to the cached `TokensModule` used
156
+ * by the userinfo handler. Mirrors the per-call shape on `tokens.verify`.
157
+ */
158
+ verify?: TokenVerifyOptions;
159
+ /**
160
+ * Task #126: Optional debug + timing-event hook. When `debug` is true, the
161
+ * helper handlers emit `console.debug("[iqauth_helper]", evt)` for each
162
+ * phase (`callback`, `refresh`, `signout`). When `onTimingEvent` is set,
163
+ * the same event is also passed to the callback, with `{ phase, durationMs,
164
+ * ok, code? }` shape. Use to push timings into your APM (Datadog, OTEL).
165
+ */
166
+ debug?: boolean;
167
+ onTimingEvent?: (event: IQAuthTimingEvent) => void;
168
+ /**
169
+ * Pluggable registry that tracks recently-signed-out idempotency tokens
170
+ * (synthetic per-session opaque strings; see {@link SignoutRegistry}).
171
+ * Defaults to a module-scoped in-memory `Map` shared by every adapter
172
+ * instance in this process. Multi-instance deployments (Railway replicas,
173
+ * autoscaled containers, blue/green) MUST plug in a shared store
174
+ * (Redis is the obvious fit) so a refresh routed to instance B can see
175
+ * the signout that landed on instance A. The interface is intentionally
176
+ * tiny — `mark` records the token with a TTL, `has` queries it.
177
+ */
178
+ signoutRegistry?: SignoutRegistry;
179
+ /**
180
+ * TTL applied when {@link handleSignout} marks an idempotency token.
181
+ * Defaults to 60 seconds — long enough to swallow any reasonable
182
+ * refresh-then-signout race, short enough that it can never delay a
183
+ * legitimate fresh sign-in.
184
+ */
185
+ signoutMarkerTtlMs?: number;
186
+ }
187
+ interface IQAuthTimingEvent {
188
+ phase: "callback" | "refresh" | "signout" | "bootstrap" | "signIn";
189
+ durationMs: number;
190
+ ok: boolean;
191
+ code?: string;
192
+ }
193
+ /**
194
+ * Pluggable backing store for the refresh/signout collapse cache. The
195
+ * default in-memory implementation is process-local and works fine for
196
+ * single-process deployments; multi-instance deployments should provide a
197
+ * shared backend (Redis is the canonical choice — `mark` ⇒ `SETEX`,
198
+ * `has` ⇒ `EXISTS`). Both methods may be sync or async; handlers always
199
+ * `await` the result.
200
+ */
201
+ interface SignoutRegistry {
202
+ mark(idempotencyToken: string, ttlMs: number): void | Promise<void>;
203
+ has(idempotencyToken: string): boolean | Promise<boolean>;
89
204
  }
90
- interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure" | "cookieNames">> {
205
+ interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure" | "cookieNames" | "mountUserinfo" | "userinfoEnricher" | "verify" | "debug" | "onTimingEvent" | "signoutRegistry" | "signoutMarkerTtlMs">> {
91
206
  cookieNames?: {
92
207
  access?: string;
93
208
  refresh?: string;
@@ -99,7 +214,23 @@ interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" |
99
214
  appId: string;
100
215
  tenantId: string;
101
216
  clearCookiesOnRefreshFailure: "terminal-only" | "always" | "never";
217
+ debug?: boolean;
218
+ onTimingEvent?: (event: IQAuthTimingEvent) => void;
219
+ signoutRegistry: SignoutRegistry;
220
+ signoutMarkerTtlMs: number;
102
221
  }
222
+ /**
223
+ * Test-only hook for clearing the default in-memory signout marker cache
224
+ * between test runs. Custom registries are unaffected. Not part of the
225
+ * public surface.
226
+ */
227
+ declare function __resetSignoutMarkersForTests(): void;
228
+ /**
229
+ * Public factory for a fresh in-memory {@link SignoutRegistry}. Useful for
230
+ * tests that want isolated state per case, and as a reference implementation
231
+ * for apps building Redis-backed (or other shared-store) registries.
232
+ */
233
+ declare function createInMemorySignoutRegistry(): SignoutRegistry;
103
234
  /**
104
235
  * Serialize a cookie directive to a Set-Cookie header value. Adapters that
105
236
  * lack a typed cookie API (Hono, raw Node) use this; Express / Fastify /
@@ -123,12 +254,26 @@ declare function handleCallback(config: IQAuthHelperConfig, input: {
123
254
  */
124
255
  declare function handleRefresh(config: IQAuthHelperConfig, input: {
125
256
  refreshToken?: string;
257
+ idempotencyToken?: string;
126
258
  }): Promise<HandlerResponse>;
127
259
  /** POST /api/iqauth/signout — clear cookies and best-effort revoke at issuer. */
128
260
  declare function handleSignout(config: IQAuthHelperConfig, input: {
129
261
  accessToken?: string;
262
+ refreshToken?: string;
263
+ idempotencyToken?: string;
130
264
  ssoCookieHeader?: string;
131
265
  endSsoSession?: boolean;
132
266
  }): Promise<HandlerResponse>;
267
+ /**
268
+ * GET /api/iqauth/me — verify the access token and return the documented
269
+ * userinfo envelope. Accepts the token from `Authorization: Bearer …` OR
270
+ * the access cookie (default `iqauth_at`). Adapters call this directly; the
271
+ * `req` value is forwarded verbatim to `userinfoEnricher` so integrators can
272
+ * read additional context (headers, sub-paths, etc).
273
+ */
274
+ declare function handleUserinfo(config: IQAuthHelperConfig, input: {
275
+ accessToken?: string;
276
+ req?: unknown;
277
+ }): Promise<HandlerResponse>;
133
278
 
134
- export { type HandlerResponse, type IQAuthHelperConfig, type ResolvedConfig as ResolvedIQAuthHelperConfig, type SetCookieDirective, handleCallback, handleRefresh, handleSignout, serializeCookie };
279
+ export { type HandlerResponse, type IQAuthHelperConfig, type IQAuthTimingEvent, type ResolvedConfig as ResolvedIQAuthHelperConfig, type SetCookieDirective, type SignoutRegistry, type UserinfoResponse, __resetSignoutMarkersForTests, buildUserinfoResponse, createInMemorySignoutRegistry, handleCallback, handleRefresh, handleSignout, handleUserinfo, serializeCookie };