@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.
- 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 +212 -46
- 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 +293 -34
- package/dist/browser.mjs +5 -5
- package/dist/{chunk-BVV54LPI.mjs → chunk-25SSYDIP.mjs} +10 -4
- package/dist/{chunk-XAWYUPMO.mjs → chunk-4V7FKOTG.mjs} +242 -22
- package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
- package/dist/{chunk-SL3KRS4W.mjs → chunk-CIJORODR.mjs} +23 -1
- 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-JRDVUWAL.mjs +46 -0
- package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
- package/dist/{chunk-5T7GHBX6.mjs → chunk-TLET552H.mjs} +36 -0
- package/dist/chunk-VYQ3ETCK.mjs +244 -0
- package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
- package/dist/chunk-WHT6WKTY.mjs +3180 -0
- package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
- package/dist/chunk-WSH4SW7F.mjs +490 -0
- package/dist/{chunk-W3F4JYGP.mjs → chunk-ZLJPABB7.mjs} +139 -23
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/{client-BNQe3AgF.d.ts → client-D8L-PaWr.d.mts} +59 -6
- package/dist/{client-kYlJFgPv.d.mts → client-DkPL0EPZ.d.ts} +59 -6
- 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-CHpfa7D_.d.ts → express-Budysq4h.d.ts} +2 -2
- package/dist/{express-B6_1vBYZ.d.mts → express-DDTA3qV1.d.mts} +2 -2
- package/dist/express.d.mts +7 -6
- package/dist/express.d.ts +7 -6
- package/dist/express.js +563 -85
- package/dist/express.mjs +73 -34
- package/dist/fastify.d.mts +10 -0
- package/dist/fastify.d.ts +10 -0
- package/dist/fastify.js +589 -65
- package/dist/fastify.mjs +101 -11
- package/dist/hono.d.mts +10 -0
- package/dist/hono.d.ts +10 -0
- package/dist/hono.js +566 -65
- package/dist/hono.mjs +78 -11
- package/dist/index-Cko-d5po.d.mts +1848 -0
- package/dist/index-RNqwEcmY.d.ts +1848 -0
- package/dist/index.d.mts +56 -8
- package/dist/index.d.ts +56 -8
- package/dist/index.js +694 -75
- package/dist/index.mjs +30 -10
- 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/locales.js +36 -0
- package/dist/locales.mjs +1 -1
- package/dist/mobile.d.mts +77 -7
- package/dist/mobile.d.ts +77 -7
- package/dist/mobile.js +307 -46
- package/dist/mobile.mjs +98 -3
- package/dist/next.d.mts +10 -1
- package/dist/next.d.ts +10 -1
- package/dist/next.js +596 -205
- package/dist/next.mjs +83 -10
- package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-BXPMZCLe.d.ts} +30 -2
- package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-IEycmsgb.d.mts} +30 -2
- 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 +98 -0
- package/dist/react.d.mts +9 -1624
- package/dist/react.d.ts +9 -1624
- package/dist/react.js +882 -73
- package/dist/react.mjs +71 -2631
- package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
- package/dist/server/handlers.d.mts +200 -4
- package/dist/server/handlers.d.ts +200 -4
- package/dist/server/handlers.js +530 -16
- package/dist/server/handlers.mjs +14 -3
- package/dist/server.d.mts +171 -8
- package/dist/server.d.ts +171 -8
- package/dist/server.js +579 -61
- package/dist/server.mjs +99 -12
- package/dist/service.d.mts +4 -4
- package/dist/service.d.ts +4 -4
- package/dist/service.js +212 -46
- package/dist/service.mjs +3 -3
- package/dist/{signIn-CiIBTJIh.d.mts → signIn-CReqfXsh.d.mts} +95 -3
- package/dist/{signIn-OCr88Zf8.d.ts → signIn-Cfa1GTpO.d.ts} +95 -3
- package/dist/{signIn-4OKLDEIH.mjs → signIn-SHBW6Z4T.mjs} +1 -1
- package/dist/test.mjs +3 -3
- package/dist/{tokens-DCyzzn8L.d.mts → tokens-9F6ETrzk.d.ts} +9 -2
- package/dist/{tokens-aHiGFr_E.d.ts → tokens-B06VtvUi.d.mts} +9 -2
- package/dist/{types-DZAflmmq.d.mts → types-Bn8O-OEd.d.mts} +164 -11
- package/dist/{types-DZAflmmq.d.ts → types-Bn8O-OEd.d.ts} +164 -11
- package/dist/{types-6bNdxesb.d.ts → types-DnU2LhXR.d.mts} +7 -1
- package/dist/{types-6bNdxesb.d.mts → types-DnU2LhXR.d.ts} +7 -1
- package/dist/webhooks.d.mts +113 -17
- package/dist/webhooks.d.ts +113 -17
- package/dist/webhooks.js +179 -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/docs/guides/invitations.md +65 -0
- package/package.json +19 -4
- package/dist/chunk-6TDJJER7.mjs +0 -217
- 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-B06VtvUi.mjs';
|
|
2
|
+
import { J as JwtClaims, S as SessionUser } from '../types-Bn8O-OEd.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 {
|
|
@@ -56,8 +99,21 @@ interface IQAuthHelperConfig {
|
|
|
56
99
|
cookieDomain?: string;
|
|
57
100
|
/** Cookie sameSite policy. Defaults to `lax`. */
|
|
58
101
|
sameSite?: "lax" | "strict" | "none";
|
|
59
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* Cookie secure flag. Defaults to `true`. Setting it to `false` ships auth
|
|
104
|
+
* cookies over plaintext HTTP, exposing them to passive network attackers —
|
|
105
|
+
* so it is REFUSED by default (the helper throws `config_invalid`). To run a
|
|
106
|
+
* local HTTP dev box you must explicitly acknowledge the risk by ALSO setting
|
|
107
|
+
* {@link IQAuthHelperConfig.allowInsecureCookies} to `true`.
|
|
108
|
+
*/
|
|
60
109
|
secure?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Explicit, opt-in acknowledgement that `secure: false` is intentional (local
|
|
112
|
+
* HTTP development only). Without this, `secure: false` throws so a misconfig
|
|
113
|
+
* can never silently downgrade production cookies to plaintext. Has no effect
|
|
114
|
+
* unless `secure` is `false`. Production MUST leave this unset / `false`.
|
|
115
|
+
*/
|
|
116
|
+
allowInsecureCookies?: boolean;
|
|
61
117
|
/** Cookie path. Defaults to `/`. */
|
|
62
118
|
cookiePath?: string;
|
|
63
119
|
/** Path of the OIDC token endpoint. */
|
|
@@ -86,8 +142,104 @@ interface IQAuthHelperConfig {
|
|
|
86
142
|
* apps that manage cookie lifecycle entirely outside the SDK helpers.
|
|
87
143
|
*/
|
|
88
144
|
clearCookiesOnRefreshFailure?: "terminal-only" | "always" | "never";
|
|
145
|
+
/**
|
|
146
|
+
* Mount the auto-mounted `GET ${mountPath}/me` userinfo route. Off by default
|
|
147
|
+
* in adapters until the integrator opts in. When true, the handler accepts
|
|
148
|
+
* either an `Authorization: Bearer …` header OR the `iqauth_at` cookie,
|
|
149
|
+
* verifies the token against the issuer, and returns the documented
|
|
150
|
+
* {@link UserinfoResponse} envelope (`data.user` + `data.claims` +
|
|
151
|
+
* `data.tenantId`).
|
|
152
|
+
*
|
|
153
|
+
* Independent of `mountHelperRoutes` so apps can opt into `/me` without
|
|
154
|
+
* also auto-mounting `/callback`/`/refresh`/`/signout` (e.g. if they
|
|
155
|
+
* already manage those routes themselves).
|
|
156
|
+
*/
|
|
157
|
+
mountUserinfo?: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* Optional hook to add app-specific fields to the `data.user` returned by
|
|
160
|
+
* the auto-mounted userinfo route. Receives the verified claims and the
|
|
161
|
+
* raw framework request object (typed `unknown` because adapters differ).
|
|
162
|
+
* Returned partial is shallow-merged over the claim-derived defaults so
|
|
163
|
+
* integrators can override e.g. `name`, or add fields not present on
|
|
164
|
+
* `SessionUser` itself (the response shape stays stable).
|
|
165
|
+
*/
|
|
166
|
+
userinfoEnricher?: (claims: JwtClaims, req: unknown) => Partial<SessionUser> | Promise<Partial<SessionUser>>;
|
|
167
|
+
/**
|
|
168
|
+
* Token verification overrides forwarded to the cached `TokensModule` used
|
|
169
|
+
* by the userinfo handler. Mirrors the per-call shape on `tokens.verify`.
|
|
170
|
+
*/
|
|
171
|
+
verify?: TokenVerifyOptions;
|
|
172
|
+
/**
|
|
173
|
+
* Task #126: Optional debug + timing-event hook. When `debug` is true, the
|
|
174
|
+
* helper handlers emit `console.debug("[iqauth_helper]", evt)` for each
|
|
175
|
+
* phase (`callback`, `refresh`, `signout`). When `onTimingEvent` is set,
|
|
176
|
+
* the same event is also passed to the callback, with `{ phase, durationMs,
|
|
177
|
+
* ok, code? }` shape. Use to push timings into your APM (Datadog, OTEL).
|
|
178
|
+
*/
|
|
179
|
+
debug?: boolean;
|
|
180
|
+
onTimingEvent?: (event: IQAuthTimingEvent) => void;
|
|
181
|
+
/**
|
|
182
|
+
* Pluggable registry that tracks recently-signed-out idempotency tokens
|
|
183
|
+
* (synthetic per-session opaque strings; see {@link SignoutRegistry}).
|
|
184
|
+
* Defaults to a module-scoped in-memory `Map` shared by every adapter
|
|
185
|
+
* instance in this process. Multi-instance deployments (Railway replicas,
|
|
186
|
+
* autoscaled containers, blue/green) MUST plug in a shared store
|
|
187
|
+
* (Redis is the obvious fit) so a refresh routed to instance B can see
|
|
188
|
+
* the signout that landed on instance A. The interface is intentionally
|
|
189
|
+
* tiny — `mark` records the token with a TTL, `has` queries it.
|
|
190
|
+
*/
|
|
191
|
+
signoutRegistry?: SignoutRegistry;
|
|
192
|
+
/**
|
|
193
|
+
* TTL applied when {@link handleSignout} marks an idempotency token.
|
|
194
|
+
* Defaults to 60 seconds — long enough to swallow any reasonable
|
|
195
|
+
* refresh-then-signout race, short enough that it can never delay a
|
|
196
|
+
* legitimate fresh sign-in.
|
|
197
|
+
*/
|
|
198
|
+
signoutMarkerTtlMs?: number;
|
|
199
|
+
/**
|
|
200
|
+
* M-2 — Server-side OAuth `state` (CSRF) enforcement on the callback.
|
|
201
|
+
*
|
|
202
|
+
* When `true` (the DEFAULT), {@link handleCallback} requires BOTH the
|
|
203
|
+
* `state` returned by the OAuth redirect AND a previously-stored
|
|
204
|
+
* `expectedState` (read by the adapter from the {@link stateCookieName}
|
|
205
|
+
* cookie the SDK publishes before redirect), and fails closed with
|
|
206
|
+
* `STATE_MISMATCH` (no code exchange, no session cookie) when either is
|
|
207
|
+
* missing or they do not match. This defeats login-CSRF / session-fixation
|
|
208
|
+
* where an attacker injects their own authorization code.
|
|
209
|
+
*
|
|
210
|
+
* Set to `false` to restore the prior permissive behavior (state is only
|
|
211
|
+
* validated when an `expectedState` cookie happens to be present). This is
|
|
212
|
+
* an escape hatch for integrators whose initiation step does not publish a
|
|
213
|
+
* state cookie; it weakens CSRF protection and is not recommended.
|
|
214
|
+
*/
|
|
215
|
+
requireOAuthState?: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Name of the first-party cookie the SDK publishes the OAuth `state` value
|
|
218
|
+
* into before redirecting to the issuer. The adapter reads it back on the
|
|
219
|
+
* callback as `expectedState`. Defaults to `iqauth_state` (matches the
|
|
220
|
+
* express inline-callback adapter and the React server-managed sign-in).
|
|
221
|
+
*/
|
|
222
|
+
stateCookieName?: string;
|
|
223
|
+
}
|
|
224
|
+
interface IQAuthTimingEvent {
|
|
225
|
+
phase: "callback" | "refresh" | "signout" | "bootstrap" | "signIn";
|
|
226
|
+
durationMs: number;
|
|
227
|
+
ok: boolean;
|
|
228
|
+
code?: string;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Pluggable backing store for the refresh/signout collapse cache. The
|
|
232
|
+
* default in-memory implementation is process-local and works fine for
|
|
233
|
+
* single-process deployments; multi-instance deployments should provide a
|
|
234
|
+
* shared backend (Redis is the canonical choice — `mark` ⇒ `SETEX`,
|
|
235
|
+
* `has` ⇒ `EXISTS`). Both methods may be sync or async; handlers always
|
|
236
|
+
* `await` the result.
|
|
237
|
+
*/
|
|
238
|
+
interface SignoutRegistry {
|
|
239
|
+
mark(idempotencyToken: string, ttlMs: number): void | Promise<void>;
|
|
240
|
+
has(idempotencyToken: string): boolean | Promise<boolean>;
|
|
89
241
|
}
|
|
90
|
-
interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure" | "cookieNames">> {
|
|
242
|
+
interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure" | "cookieNames" | "mountUserinfo" | "userinfoEnricher" | "verify" | "debug" | "onTimingEvent" | "signoutRegistry" | "signoutMarkerTtlMs" | "allowInsecureCookies">> {
|
|
91
243
|
cookieNames?: {
|
|
92
244
|
access?: string;
|
|
93
245
|
refresh?: string;
|
|
@@ -99,7 +251,29 @@ interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" |
|
|
|
99
251
|
appId: string;
|
|
100
252
|
tenantId: string;
|
|
101
253
|
clearCookiesOnRefreshFailure: "terminal-only" | "always" | "never";
|
|
254
|
+
debug?: boolean;
|
|
255
|
+
onTimingEvent?: (event: IQAuthTimingEvent) => void;
|
|
256
|
+
signoutRegistry: SignoutRegistry;
|
|
257
|
+
signoutMarkerTtlMs: number;
|
|
102
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Test-only hook for clearing the default in-memory signout marker cache
|
|
261
|
+
* between test runs. Custom registries are unaffected. Not part of the
|
|
262
|
+
* public surface.
|
|
263
|
+
*/
|
|
264
|
+
declare function __resetSignoutMarkersForTests(): void;
|
|
265
|
+
/**
|
|
266
|
+
* Test-only hook to reset the one-time default-registry boot warning so each
|
|
267
|
+
* test can assert the warn-once behavior in isolation. Not part of the public
|
|
268
|
+
* surface.
|
|
269
|
+
*/
|
|
270
|
+
declare function __resetSignoutRegistryWarningForTests(): void;
|
|
271
|
+
/**
|
|
272
|
+
* Public factory for a fresh in-memory {@link SignoutRegistry}. Useful for
|
|
273
|
+
* tests that want isolated state per case, and as a reference implementation
|
|
274
|
+
* for apps building Redis-backed (or other shared-store) registries.
|
|
275
|
+
*/
|
|
276
|
+
declare function createInMemorySignoutRegistry(): SignoutRegistry;
|
|
103
277
|
/**
|
|
104
278
|
* Serialize a cookie directive to a Set-Cookie header value. Adapters that
|
|
105
279
|
* lack a typed cookie API (Hono, raw Node) use this; Express / Fastify /
|
|
@@ -111,6 +285,14 @@ declare function handleCallback(config: IQAuthHelperConfig, input: {
|
|
|
111
285
|
code?: string;
|
|
112
286
|
codeVerifier?: string;
|
|
113
287
|
redirectUri?: string;
|
|
288
|
+
/** The `state` value returned by the OAuth redirect (query or body). */
|
|
289
|
+
state?: string;
|
|
290
|
+
/**
|
|
291
|
+
* The `state` value the SDK stored before redirect, read by the adapter
|
|
292
|
+
* from the {@link IQAuthHelperConfig.stateCookieName} cookie. Compared
|
|
293
|
+
* against `state` to bind the callback to this browser (M-2 CSRF).
|
|
294
|
+
*/
|
|
295
|
+
expectedState?: string;
|
|
114
296
|
}): Promise<HandlerResponse>;
|
|
115
297
|
/** POST /api/iqauth/refresh — rotate refresh + access cookies.
|
|
116
298
|
*
|
|
@@ -123,12 +305,26 @@ declare function handleCallback(config: IQAuthHelperConfig, input: {
|
|
|
123
305
|
*/
|
|
124
306
|
declare function handleRefresh(config: IQAuthHelperConfig, input: {
|
|
125
307
|
refreshToken?: string;
|
|
308
|
+
idempotencyToken?: string;
|
|
126
309
|
}): Promise<HandlerResponse>;
|
|
127
310
|
/** POST /api/iqauth/signout — clear cookies and best-effort revoke at issuer. */
|
|
128
311
|
declare function handleSignout(config: IQAuthHelperConfig, input: {
|
|
129
312
|
accessToken?: string;
|
|
313
|
+
refreshToken?: string;
|
|
314
|
+
idempotencyToken?: string;
|
|
130
315
|
ssoCookieHeader?: string;
|
|
131
316
|
endSsoSession?: boolean;
|
|
132
317
|
}): Promise<HandlerResponse>;
|
|
318
|
+
/**
|
|
319
|
+
* GET /api/iqauth/me — verify the access token and return the documented
|
|
320
|
+
* userinfo envelope. Accepts the token from `Authorization: Bearer …` OR
|
|
321
|
+
* the access cookie (default `iqauth_at`). Adapters call this directly; the
|
|
322
|
+
* `req` value is forwarded verbatim to `userinfoEnricher` so integrators can
|
|
323
|
+
* read additional context (headers, sub-paths, etc).
|
|
324
|
+
*/
|
|
325
|
+
declare function handleUserinfo(config: IQAuthHelperConfig, input: {
|
|
326
|
+
accessToken?: string;
|
|
327
|
+
req?: unknown;
|
|
328
|
+
}): Promise<HandlerResponse>;
|
|
133
329
|
|
|
134
|
-
export { type HandlerResponse, type IQAuthHelperConfig, type ResolvedConfig as ResolvedIQAuthHelperConfig, type SetCookieDirective, handleCallback, handleRefresh, handleSignout, serializeCookie };
|
|
330
|
+
export { type HandlerResponse, type IQAuthHelperConfig, type IQAuthTimingEvent, type ResolvedConfig as ResolvedIQAuthHelperConfig, type SetCookieDirective, type SignoutRegistry, type UserinfoResponse, __resetSignoutMarkersForTests, __resetSignoutRegistryWarningForTests, buildUserinfoResponse, createInMemorySignoutRegistry, handleCallback, handleRefresh, handleSignout, handleUserinfo, serializeCookie };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { c as TokenVerifyOptions } from '../tokens-9F6ETrzk.js';
|
|
2
|
+
import { J as JwtClaims, S as SessionUser } from '../types-Bn8O-OEd.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 {
|
|
@@ -56,8 +99,21 @@ interface IQAuthHelperConfig {
|
|
|
56
99
|
cookieDomain?: string;
|
|
57
100
|
/** Cookie sameSite policy. Defaults to `lax`. */
|
|
58
101
|
sameSite?: "lax" | "strict" | "none";
|
|
59
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* Cookie secure flag. Defaults to `true`. Setting it to `false` ships auth
|
|
104
|
+
* cookies over plaintext HTTP, exposing them to passive network attackers —
|
|
105
|
+
* so it is REFUSED by default (the helper throws `config_invalid`). To run a
|
|
106
|
+
* local HTTP dev box you must explicitly acknowledge the risk by ALSO setting
|
|
107
|
+
* {@link IQAuthHelperConfig.allowInsecureCookies} to `true`.
|
|
108
|
+
*/
|
|
60
109
|
secure?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Explicit, opt-in acknowledgement that `secure: false` is intentional (local
|
|
112
|
+
* HTTP development only). Without this, `secure: false` throws so a misconfig
|
|
113
|
+
* can never silently downgrade production cookies to plaintext. Has no effect
|
|
114
|
+
* unless `secure` is `false`. Production MUST leave this unset / `false`.
|
|
115
|
+
*/
|
|
116
|
+
allowInsecureCookies?: boolean;
|
|
61
117
|
/** Cookie path. Defaults to `/`. */
|
|
62
118
|
cookiePath?: string;
|
|
63
119
|
/** Path of the OIDC token endpoint. */
|
|
@@ -86,8 +142,104 @@ interface IQAuthHelperConfig {
|
|
|
86
142
|
* apps that manage cookie lifecycle entirely outside the SDK helpers.
|
|
87
143
|
*/
|
|
88
144
|
clearCookiesOnRefreshFailure?: "terminal-only" | "always" | "never";
|
|
145
|
+
/**
|
|
146
|
+
* Mount the auto-mounted `GET ${mountPath}/me` userinfo route. Off by default
|
|
147
|
+
* in adapters until the integrator opts in. When true, the handler accepts
|
|
148
|
+
* either an `Authorization: Bearer …` header OR the `iqauth_at` cookie,
|
|
149
|
+
* verifies the token against the issuer, and returns the documented
|
|
150
|
+
* {@link UserinfoResponse} envelope (`data.user` + `data.claims` +
|
|
151
|
+
* `data.tenantId`).
|
|
152
|
+
*
|
|
153
|
+
* Independent of `mountHelperRoutes` so apps can opt into `/me` without
|
|
154
|
+
* also auto-mounting `/callback`/`/refresh`/`/signout` (e.g. if they
|
|
155
|
+
* already manage those routes themselves).
|
|
156
|
+
*/
|
|
157
|
+
mountUserinfo?: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* Optional hook to add app-specific fields to the `data.user` returned by
|
|
160
|
+
* the auto-mounted userinfo route. Receives the verified claims and the
|
|
161
|
+
* raw framework request object (typed `unknown` because adapters differ).
|
|
162
|
+
* Returned partial is shallow-merged over the claim-derived defaults so
|
|
163
|
+
* integrators can override e.g. `name`, or add fields not present on
|
|
164
|
+
* `SessionUser` itself (the response shape stays stable).
|
|
165
|
+
*/
|
|
166
|
+
userinfoEnricher?: (claims: JwtClaims, req: unknown) => Partial<SessionUser> | Promise<Partial<SessionUser>>;
|
|
167
|
+
/**
|
|
168
|
+
* Token verification overrides forwarded to the cached `TokensModule` used
|
|
169
|
+
* by the userinfo handler. Mirrors the per-call shape on `tokens.verify`.
|
|
170
|
+
*/
|
|
171
|
+
verify?: TokenVerifyOptions;
|
|
172
|
+
/**
|
|
173
|
+
* Task #126: Optional debug + timing-event hook. When `debug` is true, the
|
|
174
|
+
* helper handlers emit `console.debug("[iqauth_helper]", evt)` for each
|
|
175
|
+
* phase (`callback`, `refresh`, `signout`). When `onTimingEvent` is set,
|
|
176
|
+
* the same event is also passed to the callback, with `{ phase, durationMs,
|
|
177
|
+
* ok, code? }` shape. Use to push timings into your APM (Datadog, OTEL).
|
|
178
|
+
*/
|
|
179
|
+
debug?: boolean;
|
|
180
|
+
onTimingEvent?: (event: IQAuthTimingEvent) => void;
|
|
181
|
+
/**
|
|
182
|
+
* Pluggable registry that tracks recently-signed-out idempotency tokens
|
|
183
|
+
* (synthetic per-session opaque strings; see {@link SignoutRegistry}).
|
|
184
|
+
* Defaults to a module-scoped in-memory `Map` shared by every adapter
|
|
185
|
+
* instance in this process. Multi-instance deployments (Railway replicas,
|
|
186
|
+
* autoscaled containers, blue/green) MUST plug in a shared store
|
|
187
|
+
* (Redis is the obvious fit) so a refresh routed to instance B can see
|
|
188
|
+
* the signout that landed on instance A. The interface is intentionally
|
|
189
|
+
* tiny — `mark` records the token with a TTL, `has` queries it.
|
|
190
|
+
*/
|
|
191
|
+
signoutRegistry?: SignoutRegistry;
|
|
192
|
+
/**
|
|
193
|
+
* TTL applied when {@link handleSignout} marks an idempotency token.
|
|
194
|
+
* Defaults to 60 seconds — long enough to swallow any reasonable
|
|
195
|
+
* refresh-then-signout race, short enough that it can never delay a
|
|
196
|
+
* legitimate fresh sign-in.
|
|
197
|
+
*/
|
|
198
|
+
signoutMarkerTtlMs?: number;
|
|
199
|
+
/**
|
|
200
|
+
* M-2 — Server-side OAuth `state` (CSRF) enforcement on the callback.
|
|
201
|
+
*
|
|
202
|
+
* When `true` (the DEFAULT), {@link handleCallback} requires BOTH the
|
|
203
|
+
* `state` returned by the OAuth redirect AND a previously-stored
|
|
204
|
+
* `expectedState` (read by the adapter from the {@link stateCookieName}
|
|
205
|
+
* cookie the SDK publishes before redirect), and fails closed with
|
|
206
|
+
* `STATE_MISMATCH` (no code exchange, no session cookie) when either is
|
|
207
|
+
* missing or they do not match. This defeats login-CSRF / session-fixation
|
|
208
|
+
* where an attacker injects their own authorization code.
|
|
209
|
+
*
|
|
210
|
+
* Set to `false` to restore the prior permissive behavior (state is only
|
|
211
|
+
* validated when an `expectedState` cookie happens to be present). This is
|
|
212
|
+
* an escape hatch for integrators whose initiation step does not publish a
|
|
213
|
+
* state cookie; it weakens CSRF protection and is not recommended.
|
|
214
|
+
*/
|
|
215
|
+
requireOAuthState?: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Name of the first-party cookie the SDK publishes the OAuth `state` value
|
|
218
|
+
* into before redirecting to the issuer. The adapter reads it back on the
|
|
219
|
+
* callback as `expectedState`. Defaults to `iqauth_state` (matches the
|
|
220
|
+
* express inline-callback adapter and the React server-managed sign-in).
|
|
221
|
+
*/
|
|
222
|
+
stateCookieName?: string;
|
|
223
|
+
}
|
|
224
|
+
interface IQAuthTimingEvent {
|
|
225
|
+
phase: "callback" | "refresh" | "signout" | "bootstrap" | "signIn";
|
|
226
|
+
durationMs: number;
|
|
227
|
+
ok: boolean;
|
|
228
|
+
code?: string;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Pluggable backing store for the refresh/signout collapse cache. The
|
|
232
|
+
* default in-memory implementation is process-local and works fine for
|
|
233
|
+
* single-process deployments; multi-instance deployments should provide a
|
|
234
|
+
* shared backend (Redis is the canonical choice — `mark` ⇒ `SETEX`,
|
|
235
|
+
* `has` ⇒ `EXISTS`). Both methods may be sync or async; handlers always
|
|
236
|
+
* `await` the result.
|
|
237
|
+
*/
|
|
238
|
+
interface SignoutRegistry {
|
|
239
|
+
mark(idempotencyToken: string, ttlMs: number): void | Promise<void>;
|
|
240
|
+
has(idempotencyToken: string): boolean | Promise<boolean>;
|
|
89
241
|
}
|
|
90
|
-
interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure" | "cookieNames">> {
|
|
242
|
+
interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure" | "cookieNames" | "mountUserinfo" | "userinfoEnricher" | "verify" | "debug" | "onTimingEvent" | "signoutRegistry" | "signoutMarkerTtlMs" | "allowInsecureCookies">> {
|
|
91
243
|
cookieNames?: {
|
|
92
244
|
access?: string;
|
|
93
245
|
refresh?: string;
|
|
@@ -99,7 +251,29 @@ interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" |
|
|
|
99
251
|
appId: string;
|
|
100
252
|
tenantId: string;
|
|
101
253
|
clearCookiesOnRefreshFailure: "terminal-only" | "always" | "never";
|
|
254
|
+
debug?: boolean;
|
|
255
|
+
onTimingEvent?: (event: IQAuthTimingEvent) => void;
|
|
256
|
+
signoutRegistry: SignoutRegistry;
|
|
257
|
+
signoutMarkerTtlMs: number;
|
|
102
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Test-only hook for clearing the default in-memory signout marker cache
|
|
261
|
+
* between test runs. Custom registries are unaffected. Not part of the
|
|
262
|
+
* public surface.
|
|
263
|
+
*/
|
|
264
|
+
declare function __resetSignoutMarkersForTests(): void;
|
|
265
|
+
/**
|
|
266
|
+
* Test-only hook to reset the one-time default-registry boot warning so each
|
|
267
|
+
* test can assert the warn-once behavior in isolation. Not part of the public
|
|
268
|
+
* surface.
|
|
269
|
+
*/
|
|
270
|
+
declare function __resetSignoutRegistryWarningForTests(): void;
|
|
271
|
+
/**
|
|
272
|
+
* Public factory for a fresh in-memory {@link SignoutRegistry}. Useful for
|
|
273
|
+
* tests that want isolated state per case, and as a reference implementation
|
|
274
|
+
* for apps building Redis-backed (or other shared-store) registries.
|
|
275
|
+
*/
|
|
276
|
+
declare function createInMemorySignoutRegistry(): SignoutRegistry;
|
|
103
277
|
/**
|
|
104
278
|
* Serialize a cookie directive to a Set-Cookie header value. Adapters that
|
|
105
279
|
* lack a typed cookie API (Hono, raw Node) use this; Express / Fastify /
|
|
@@ -111,6 +285,14 @@ declare function handleCallback(config: IQAuthHelperConfig, input: {
|
|
|
111
285
|
code?: string;
|
|
112
286
|
codeVerifier?: string;
|
|
113
287
|
redirectUri?: string;
|
|
288
|
+
/** The `state` value returned by the OAuth redirect (query or body). */
|
|
289
|
+
state?: string;
|
|
290
|
+
/**
|
|
291
|
+
* The `state` value the SDK stored before redirect, read by the adapter
|
|
292
|
+
* from the {@link IQAuthHelperConfig.stateCookieName} cookie. Compared
|
|
293
|
+
* against `state` to bind the callback to this browser (M-2 CSRF).
|
|
294
|
+
*/
|
|
295
|
+
expectedState?: string;
|
|
114
296
|
}): Promise<HandlerResponse>;
|
|
115
297
|
/** POST /api/iqauth/refresh — rotate refresh + access cookies.
|
|
116
298
|
*
|
|
@@ -123,12 +305,26 @@ declare function handleCallback(config: IQAuthHelperConfig, input: {
|
|
|
123
305
|
*/
|
|
124
306
|
declare function handleRefresh(config: IQAuthHelperConfig, input: {
|
|
125
307
|
refreshToken?: string;
|
|
308
|
+
idempotencyToken?: string;
|
|
126
309
|
}): Promise<HandlerResponse>;
|
|
127
310
|
/** POST /api/iqauth/signout — clear cookies and best-effort revoke at issuer. */
|
|
128
311
|
declare function handleSignout(config: IQAuthHelperConfig, input: {
|
|
129
312
|
accessToken?: string;
|
|
313
|
+
refreshToken?: string;
|
|
314
|
+
idempotencyToken?: string;
|
|
130
315
|
ssoCookieHeader?: string;
|
|
131
316
|
endSsoSession?: boolean;
|
|
132
317
|
}): Promise<HandlerResponse>;
|
|
318
|
+
/**
|
|
319
|
+
* GET /api/iqauth/me — verify the access token and return the documented
|
|
320
|
+
* userinfo envelope. Accepts the token from `Authorization: Bearer …` OR
|
|
321
|
+
* the access cookie (default `iqauth_at`). Adapters call this directly; the
|
|
322
|
+
* `req` value is forwarded verbatim to `userinfoEnricher` so integrators can
|
|
323
|
+
* read additional context (headers, sub-paths, etc).
|
|
324
|
+
*/
|
|
325
|
+
declare function handleUserinfo(config: IQAuthHelperConfig, input: {
|
|
326
|
+
accessToken?: string;
|
|
327
|
+
req?: unknown;
|
|
328
|
+
}): Promise<HandlerResponse>;
|
|
133
329
|
|
|
134
|
-
export { type HandlerResponse, type IQAuthHelperConfig, type ResolvedConfig as ResolvedIQAuthHelperConfig, type SetCookieDirective, handleCallback, handleRefresh, handleSignout, serializeCookie };
|
|
330
|
+
export { type HandlerResponse, type IQAuthHelperConfig, type IQAuthTimingEvent, type ResolvedConfig as ResolvedIQAuthHelperConfig, type SetCookieDirective, type SignoutRegistry, type UserinfoResponse, __resetSignoutMarkersForTests, __resetSignoutRegistryWarningForTests, buildUserinfoResponse, createInMemorySignoutRegistry, handleCallback, handleRefresh, handleSignout, handleUserinfo, serializeCookie };
|