@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.
- package/README.md +173 -1
- package/dist/browser-session.d.mts +4 -4
- package/dist/browser-session.d.ts +4 -4
- package/dist/browser-session.js +181 -41
- package/dist/browser-session.mjs +3 -3
- package/dist/browser.d.mts +5 -5
- package/dist/browser.d.ts +5 -5
- package/dist/browser.js +271 -32
- package/dist/browser.mjs +5 -5
- package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
- package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
- package/dist/chunk-GLXSIGVS.mjs +66 -0
- package/dist/{chunk-DJIBN2N7.mjs → chunk-GN37E64I.mjs} +29 -7
- package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
- package/dist/{chunk-W3F4JYGP.mjs → chunk-JXQI62A7.mjs} +108 -18
- package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
- package/dist/chunk-PMAFENVI.mjs +229 -0
- package/dist/chunk-RR2MGPTK.mjs +2724 -0
- package/dist/{chunk-XAWYUPMO.mjs → chunk-RTJAIBXY.mjs} +220 -20
- package/dist/{chunk-6TDJJER7.mjs → chunk-RUJXRTEW.mjs} +164 -5
- package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
- package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
- package/dist/{chunk-BVV54LPI.mjs → chunk-YVALAG3B.mjs} +10 -4
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/{client-kYlJFgPv.d.mts → client-BGFnBpfc.d.mts} +47 -4
- package/dist/{client-BNQe3AgF.d.ts → client-CDQ21LvW.d.ts} +47 -4
- package/dist/{doctor-YYNHNMLD.mjs → doctor-JAFXWU3X.mjs} +2 -2
- package/dist/errors-Jl1Jtm-6.d.mts +107 -0
- package/dist/errors-Jl1Jtm-6.d.ts +107 -0
- package/dist/{express-B6_1vBYZ.d.mts → express-CVNQEkOr.d.mts} +2 -2
- package/dist/{express-CHpfa7D_.d.ts → express-Piv2WhWM.d.ts} +2 -2
- package/dist/express.d.mts +7 -6
- package/dist/express.d.ts +7 -6
- package/dist/express.js +349 -52
- package/dist/express.mjs +39 -12
- package/dist/fastify.d.mts +2 -0
- package/dist/fastify.d.ts +2 -0
- package/dist/fastify.js +332 -52
- package/dist/fastify.mjs +23 -8
- package/dist/hono.d.mts +2 -0
- package/dist/hono.d.ts +2 -0
- package/dist/hono.js +329 -52
- package/dist/hono.mjs +20 -8
- package/dist/index-5KSZEnDe.d.ts +1626 -0
- package/dist/index-CKoZHAoc.d.mts +1626 -0
- package/dist/index.d.mts +56 -8
- package/dist/index.d.ts +56 -8
- package/dist/index.js +565 -69
- package/dist/index.mjs +29 -9
- package/dist/{keys-NLWFAOEM.mjs → keys-6Y776TG2.mjs} +2 -2
- package/dist/locales.d.mts +1 -1
- package/dist/locales.d.ts +1 -1
- package/dist/mobile.d.mts +77 -7
- package/dist/mobile.d.ts +77 -7
- package/dist/mobile.js +276 -41
- package/dist/mobile.mjs +98 -3
- package/dist/next.d.mts +2 -1
- package/dist/next.d.ts +2 -1
- package/dist/next.js +391 -201
- package/dist/next.mjs +22 -7
- package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-CGpMRie4.d.ts} +1 -1
- package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-M5G47LWO.d.mts} +1 -1
- package/dist/{publishableKey-BaR0HoAH.d.ts → publishableKey-f2kq-rKw.d.mts} +1 -1
- package/dist/{publishableKey-BaR0HoAH.d.mts → publishableKey-f2kq-rKw.d.ts} +1 -1
- package/dist/react-permissions.d.mts +52 -0
- package/dist/react-permissions.d.ts +52 -0
- package/dist/react-permissions.js +239 -0
- package/dist/react-permissions.mjs +97 -0
- package/dist/react.d.mts +9 -1624
- package/dist/react.d.ts +9 -1624
- package/dist/react.js +313 -33
- package/dist/react.mjs +58 -2632
- package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
- package/dist/server/handlers.d.mts +148 -3
- package/dist/server/handlers.d.ts +148 -3
- package/dist/server/handlers.js +410 -11
- package/dist/server/handlers.mjs +12 -3
- package/dist/server.d.mts +151 -8
- package/dist/server.d.ts +151 -8
- package/dist/server.js +406 -50
- package/dist/server.mjs +93 -11
- package/dist/service.d.mts +4 -4
- package/dist/service.d.ts +4 -4
- package/dist/service.js +181 -41
- package/dist/service.mjs +3 -3
- package/dist/{signIn-OCr88Zf8.d.ts → signIn-BLFnz8SV.d.ts} +78 -3
- package/dist/{signIn-4OKLDEIH.mjs → signIn-SHBW6Z4T.mjs} +1 -1
- package/dist/{signIn-CiIBTJIh.d.mts → signIn-T-CZ6t6r.d.mts} +78 -3
- package/dist/test.mjs +3 -3
- package/dist/{tokens-DCyzzn8L.d.mts → tokens-Bqhmqq_R.d.ts} +9 -2
- package/dist/{tokens-aHiGFr_E.d.ts → tokens-CITeoG6P.d.mts} +9 -2
- package/dist/{types-6bNdxesb.d.ts → types-BdQ2lqfT.d.mts} +1 -1
- package/dist/{types-6bNdxesb.d.mts → types-BdQ2lqfT.d.ts} +1 -1
- package/dist/{types-DZAflmmq.d.mts → types-XOV9XPVi.d.mts} +99 -10
- package/dist/{types-DZAflmmq.d.ts → types-XOV9XPVi.d.ts} +99 -10
- package/dist/webhooks.d.mts +100 -17
- package/dist/webhooks.d.ts +100 -17
- package/dist/webhooks.js +164 -15
- package/dist/webhooks.mjs +7 -1
- package/dist/ws.d.mts +2 -2
- package/dist/ws.d.ts +2 -2
- package/dist/ws.js +80 -30
- package/dist/ws.mjs +4 -4
- package/docs/error-handling.md +101 -0
- package/docs/guides/effective-permissions.md +171 -0
- package/package.json +13 -3
- package/dist/chunk-UKZLOHZG.mjs +0 -83
- package/dist/errors-CDdl24MP.d.mts +0 -52
- 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-
|
|
8
|
-
import "./chunk-
|
|
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 };
|