@iqauth/sdk 2.7.0 → 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/dist/browser-session.d.mts +3 -3
- package/dist/browser-session.d.ts +3 -3
- package/dist/browser-session.js +31 -5
- package/dist/browser-session.mjs +1 -1
- package/dist/browser.d.mts +3 -3
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +23 -3
- package/dist/browser.mjs +1 -1
- package/dist/{chunk-YVALAG3B.mjs → chunk-25SSYDIP.mjs} +1 -1
- package/dist/{chunk-RTJAIBXY.mjs → chunk-4V7FKOTG.mjs} +23 -3
- package/dist/{chunk-SL3KRS4W.mjs → chunk-CIJORODR.mjs} +23 -1
- package/dist/chunk-JRDVUWAL.mjs +46 -0
- package/dist/{chunk-5T7GHBX6.mjs → chunk-TLET552H.mjs} +36 -0
- package/dist/{chunk-PMAFENVI.mjs → chunk-VYQ3ETCK.mjs} +27 -12
- package/dist/{chunk-RR2MGPTK.mjs → chunk-WHT6WKTY.mjs} +539 -83
- package/dist/{chunk-RUJXRTEW.mjs → chunk-WSH4SW7F.mjs} +122 -8
- package/dist/{chunk-JXQI62A7.mjs → chunk-ZLJPABB7.mjs} +31 -5
- package/dist/{client-BGFnBpfc.d.mts → client-D8L-PaWr.d.mts} +14 -4
- package/dist/{client-CDQ21LvW.d.ts → client-DkPL0EPZ.d.ts} +14 -4
- package/dist/{express-Piv2WhWM.d.ts → express-Budysq4h.d.ts} +2 -2
- package/dist/{express-CVNQEkOr.d.mts → express-DDTA3qV1.d.mts} +2 -2
- package/dist/express.d.mts +5 -5
- package/dist/express.d.ts +5 -5
- package/dist/express.js +217 -36
- package/dist/express.mjs +38 -26
- package/dist/fastify.d.mts +10 -2
- package/dist/fastify.d.ts +10 -2
- package/dist/fastify.js +260 -16
- package/dist/fastify.mjs +80 -5
- package/dist/hono.d.mts +10 -2
- package/dist/hono.d.ts +10 -2
- package/dist/hono.js +240 -16
- package/dist/hono.mjs +60 -5
- package/dist/{index-5KSZEnDe.d.ts → index-Cko-d5po.d.mts} +227 -5
- package/dist/{index-CKoZHAoc.d.mts → index-RNqwEcmY.d.ts} +227 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +149 -26
- package/dist/index.mjs +5 -5
- 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 +3 -3
- package/dist/mobile.d.ts +3 -3
- package/dist/mobile.js +31 -5
- package/dist/mobile.mjs +1 -1
- package/dist/next.d.mts +10 -2
- package/dist/next.d.ts +10 -2
- package/dist/next.js +212 -11
- package/dist/next.mjs +62 -4
- package/dist/{provisioningBridge-M5G47LWO.d.mts → provisioningBridge-BXPMZCLe.d.ts} +30 -2
- package/dist/{provisioningBridge-CGpMRie4.d.ts → provisioningBridge-IEycmsgb.d.mts} +30 -2
- package/dist/react-permissions.d.mts +4 -4
- package/dist/react-permissions.d.ts +4 -4
- package/dist/react-permissions.mjs +4 -3
- package/dist/react.d.mts +4 -4
- package/dist/react.d.ts +4 -4
- package/dist/react.js +570 -41
- package/dist/react.mjs +19 -5
- package/dist/server/handlers.d.mts +56 -5
- package/dist/server/handlers.d.ts +56 -5
- package/dist/server/handlers.js +123 -8
- package/dist/server/handlers.mjs +3 -1
- package/dist/server.d.mts +28 -8
- package/dist/server.d.ts +28 -8
- package/dist/server.js +176 -14
- package/dist/server.mjs +9 -4
- package/dist/service.d.mts +3 -3
- package/dist/service.d.ts +3 -3
- package/dist/service.js +31 -5
- package/dist/service.mjs +1 -1
- package/dist/{signIn-T-CZ6t6r.d.mts → signIn-CReqfXsh.d.mts} +18 -1
- package/dist/{signIn-BLFnz8SV.d.ts → signIn-Cfa1GTpO.d.ts} +18 -1
- package/dist/{tokens-Bqhmqq_R.d.ts → tokens-9F6ETrzk.d.ts} +1 -1
- package/dist/{tokens-CITeoG6P.d.mts → tokens-B06VtvUi.d.mts} +1 -1
- package/dist/{types-XOV9XPVi.d.mts → types-Bn8O-OEd.d.mts} +66 -2
- package/dist/{types-XOV9XPVi.d.ts → types-Bn8O-OEd.d.ts} +66 -2
- package/dist/{types-BdQ2lqfT.d.mts → types-DnU2LhXR.d.mts} +6 -0
- package/dist/{types-BdQ2lqfT.d.ts → types-DnU2LhXR.d.ts} +6 -0
- package/dist/webhooks.d.mts +22 -9
- package/dist/webhooks.d.ts +22 -9
- package/dist/webhooks.js +27 -12
- package/dist/webhooks.mjs +1 -1
- package/dist/ws.d.mts +2 -2
- package/dist/ws.d.ts +2 -2
- package/docs/guides/invitations.md +65 -0
- package/package.json +7 -2
package/dist/react.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
Protect,
|
|
17
17
|
RedirectToSignIn,
|
|
18
18
|
RedirectToSignedIn,
|
|
19
|
+
ScopeSwitcher,
|
|
19
20
|
SignIn,
|
|
20
21
|
SignUp,
|
|
21
22
|
SignedIn,
|
|
@@ -25,12 +26,14 @@ import {
|
|
|
25
26
|
Waitlist,
|
|
26
27
|
__useIQAuthInternal,
|
|
27
28
|
__version__,
|
|
28
|
-
|
|
29
|
+
claimSatisfiesScope,
|
|
29
30
|
isSilentSsoEligible,
|
|
31
|
+
performScopeSwitch,
|
|
32
|
+
performTenantSwitch,
|
|
30
33
|
preflightReturnTo,
|
|
34
|
+
resolveAfterSignInDestination,
|
|
31
35
|
revokeSession,
|
|
32
36
|
sanitizeBrandCss,
|
|
33
|
-
sanitizeReturnTo,
|
|
34
37
|
slugify,
|
|
35
38
|
useAccountList,
|
|
36
39
|
useAccountSwitcher,
|
|
@@ -41,6 +44,7 @@ import {
|
|
|
41
44
|
useLinkedIdentities,
|
|
42
45
|
useLocale,
|
|
43
46
|
useMagicLink,
|
|
47
|
+
useMemberships,
|
|
44
48
|
useOrganization,
|
|
45
49
|
usePasskey,
|
|
46
50
|
useResolvedSdkBranding,
|
|
@@ -50,12 +54,16 @@ import {
|
|
|
50
54
|
useSessionList,
|
|
51
55
|
useT,
|
|
52
56
|
useUser
|
|
53
|
-
} from "./chunk-
|
|
54
|
-
import "./chunk-
|
|
57
|
+
} from "./chunk-WHT6WKTY.mjs";
|
|
58
|
+
import "./chunk-4V7FKOTG.mjs";
|
|
55
59
|
import "./chunk-GN37E64I.mjs";
|
|
56
60
|
import "./chunk-C2ZTBOAC.mjs";
|
|
61
|
+
import {
|
|
62
|
+
isReturnToAllowed,
|
|
63
|
+
sanitizeReturnTo
|
|
64
|
+
} from "./chunk-JRDVUWAL.mjs";
|
|
57
65
|
import "./chunk-HVHNYPDC.mjs";
|
|
58
|
-
import "./chunk-
|
|
66
|
+
import "./chunk-TLET552H.mjs";
|
|
59
67
|
import "./chunk-6PJRLRB4.mjs";
|
|
60
68
|
import "./chunk-Y6FXYEAI.mjs";
|
|
61
69
|
export {
|
|
@@ -76,6 +84,7 @@ export {
|
|
|
76
84
|
Protect,
|
|
77
85
|
RedirectToSignIn,
|
|
78
86
|
RedirectToSignedIn,
|
|
87
|
+
ScopeSwitcher,
|
|
79
88
|
SignIn,
|
|
80
89
|
SignUp,
|
|
81
90
|
SignedIn,
|
|
@@ -85,9 +94,13 @@ export {
|
|
|
85
94
|
Waitlist,
|
|
86
95
|
__useIQAuthInternal,
|
|
87
96
|
__version__,
|
|
97
|
+
claimSatisfiesScope,
|
|
88
98
|
isReturnToAllowed,
|
|
89
99
|
isSilentSsoEligible,
|
|
100
|
+
performScopeSwitch,
|
|
101
|
+
performTenantSwitch,
|
|
90
102
|
preflightReturnTo,
|
|
103
|
+
resolveAfterSignInDestination,
|
|
91
104
|
revokeSession,
|
|
92
105
|
sanitizeBrandCss,
|
|
93
106
|
sanitizeReturnTo,
|
|
@@ -101,6 +114,7 @@ export {
|
|
|
101
114
|
useLinkedIdentities,
|
|
102
115
|
useLocale,
|
|
103
116
|
useMagicLink,
|
|
117
|
+
useMemberships,
|
|
104
118
|
useOrganization,
|
|
105
119
|
usePasskey,
|
|
106
120
|
useResolvedSdkBranding,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as TokenVerifyOptions } from '../tokens-
|
|
2
|
-
import { J as JwtClaims, S as SessionUser } from '../types-
|
|
1
|
+
import { c as TokenVerifyOptions } from '../tokens-B06VtvUi.mjs';
|
|
2
|
+
import { J as JwtClaims, S as SessionUser } from '../types-Bn8O-OEd.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Framework-neutral helper handlers for the auto-mounted routes added by the
|
|
@@ -99,8 +99,21 @@ interface IQAuthHelperConfig {
|
|
|
99
99
|
cookieDomain?: string;
|
|
100
100
|
/** Cookie sameSite policy. Defaults to `lax`. */
|
|
101
101
|
sameSite?: "lax" | "strict" | "none";
|
|
102
|
-
/**
|
|
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
|
+
*/
|
|
103
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;
|
|
104
117
|
/** Cookie path. Defaults to `/`. */
|
|
105
118
|
cookiePath?: string;
|
|
106
119
|
/** Path of the OIDC token endpoint. */
|
|
@@ -183,6 +196,30 @@ interface IQAuthHelperConfig {
|
|
|
183
196
|
* legitimate fresh sign-in.
|
|
184
197
|
*/
|
|
185
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;
|
|
186
223
|
}
|
|
187
224
|
interface IQAuthTimingEvent {
|
|
188
225
|
phase: "callback" | "refresh" | "signout" | "bootstrap" | "signIn";
|
|
@@ -202,7 +239,7 @@ interface SignoutRegistry {
|
|
|
202
239
|
mark(idempotencyToken: string, ttlMs: number): void | Promise<void>;
|
|
203
240
|
has(idempotencyToken: string): boolean | Promise<boolean>;
|
|
204
241
|
}
|
|
205
|
-
interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure" | "cookieNames" | "mountUserinfo" | "userinfoEnricher" | "verify" | "debug" | "onTimingEvent" | "signoutRegistry" | "signoutMarkerTtlMs">> {
|
|
242
|
+
interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure" | "cookieNames" | "mountUserinfo" | "userinfoEnricher" | "verify" | "debug" | "onTimingEvent" | "signoutRegistry" | "signoutMarkerTtlMs" | "allowInsecureCookies">> {
|
|
206
243
|
cookieNames?: {
|
|
207
244
|
access?: string;
|
|
208
245
|
refresh?: string;
|
|
@@ -225,6 +262,12 @@ interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" |
|
|
|
225
262
|
* public surface.
|
|
226
263
|
*/
|
|
227
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;
|
|
228
271
|
/**
|
|
229
272
|
* Public factory for a fresh in-memory {@link SignoutRegistry}. Useful for
|
|
230
273
|
* tests that want isolated state per case, and as a reference implementation
|
|
@@ -242,6 +285,14 @@ declare function handleCallback(config: IQAuthHelperConfig, input: {
|
|
|
242
285
|
code?: string;
|
|
243
286
|
codeVerifier?: string;
|
|
244
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;
|
|
245
296
|
}): Promise<HandlerResponse>;
|
|
246
297
|
/** POST /api/iqauth/refresh — rotate refresh + access cookies.
|
|
247
298
|
*
|
|
@@ -276,4 +327,4 @@ declare function handleUserinfo(config: IQAuthHelperConfig, input: {
|
|
|
276
327
|
req?: unknown;
|
|
277
328
|
}): Promise<HandlerResponse>;
|
|
278
329
|
|
|
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 };
|
|
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,5 +1,5 @@
|
|
|
1
|
-
import { c as TokenVerifyOptions } from '../tokens-
|
|
2
|
-
import { J as JwtClaims, S as SessionUser } from '../types-
|
|
1
|
+
import { c as TokenVerifyOptions } from '../tokens-9F6ETrzk.js';
|
|
2
|
+
import { J as JwtClaims, S as SessionUser } from '../types-Bn8O-OEd.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Framework-neutral helper handlers for the auto-mounted routes added by the
|
|
@@ -99,8 +99,21 @@ interface IQAuthHelperConfig {
|
|
|
99
99
|
cookieDomain?: string;
|
|
100
100
|
/** Cookie sameSite policy. Defaults to `lax`. */
|
|
101
101
|
sameSite?: "lax" | "strict" | "none";
|
|
102
|
-
/**
|
|
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
|
+
*/
|
|
103
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;
|
|
104
117
|
/** Cookie path. Defaults to `/`. */
|
|
105
118
|
cookiePath?: string;
|
|
106
119
|
/** Path of the OIDC token endpoint. */
|
|
@@ -183,6 +196,30 @@ interface IQAuthHelperConfig {
|
|
|
183
196
|
* legitimate fresh sign-in.
|
|
184
197
|
*/
|
|
185
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;
|
|
186
223
|
}
|
|
187
224
|
interface IQAuthTimingEvent {
|
|
188
225
|
phase: "callback" | "refresh" | "signout" | "bootstrap" | "signIn";
|
|
@@ -202,7 +239,7 @@ interface SignoutRegistry {
|
|
|
202
239
|
mark(idempotencyToken: string, ttlMs: number): void | Promise<void>;
|
|
203
240
|
has(idempotencyToken: string): boolean | Promise<boolean>;
|
|
204
241
|
}
|
|
205
|
-
interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure" | "cookieNames" | "mountUserinfo" | "userinfoEnricher" | "verify" | "debug" | "onTimingEvent" | "signoutRegistry" | "signoutMarkerTtlMs">> {
|
|
242
|
+
interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure" | "cookieNames" | "mountUserinfo" | "userinfoEnricher" | "verify" | "debug" | "onTimingEvent" | "signoutRegistry" | "signoutMarkerTtlMs" | "allowInsecureCookies">> {
|
|
206
243
|
cookieNames?: {
|
|
207
244
|
access?: string;
|
|
208
245
|
refresh?: string;
|
|
@@ -225,6 +262,12 @@ interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" |
|
|
|
225
262
|
* public surface.
|
|
226
263
|
*/
|
|
227
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;
|
|
228
271
|
/**
|
|
229
272
|
* Public factory for a fresh in-memory {@link SignoutRegistry}. Useful for
|
|
230
273
|
* tests that want isolated state per case, and as a reference implementation
|
|
@@ -242,6 +285,14 @@ declare function handleCallback(config: IQAuthHelperConfig, input: {
|
|
|
242
285
|
code?: string;
|
|
243
286
|
codeVerifier?: string;
|
|
244
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;
|
|
245
296
|
}): Promise<HandlerResponse>;
|
|
246
297
|
/** POST /api/iqauth/refresh — rotate refresh + access cookies.
|
|
247
298
|
*
|
|
@@ -276,4 +327,4 @@ declare function handleUserinfo(config: IQAuthHelperConfig, input: {
|
|
|
276
327
|
req?: unknown;
|
|
277
328
|
}): Promise<HandlerResponse>;
|
|
278
329
|
|
|
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 };
|
|
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 };
|
package/dist/server/handlers.js
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var handlers_exports = {};
|
|
22
22
|
__export(handlers_exports, {
|
|
23
23
|
__resetSignoutMarkersForTests: () => __resetSignoutMarkersForTests,
|
|
24
|
+
__resetSignoutRegistryWarningForTests: () => __resetSignoutRegistryWarningForTests,
|
|
24
25
|
buildUserinfoResponse: () => buildUserinfoResponse,
|
|
25
26
|
createInMemorySignoutRegistry: () => createInMemorySignoutRegistry,
|
|
26
27
|
handleCallback: () => handleCallback,
|
|
@@ -362,7 +363,11 @@ async function buildUserinfoResponse(claims, opts = {}) {
|
|
|
362
363
|
tenantId: claims.tenantId,
|
|
363
364
|
vendorId: claims.vendorId,
|
|
364
365
|
roles: claims.roles ?? [],
|
|
365
|
-
entitlements: claims.entitlements ?? []
|
|
366
|
+
entitlements: claims.entitlements ?? [],
|
|
367
|
+
// Task #171 — project the active source/client scope onto the userinfo
|
|
368
|
+
// payload so server handlers (`getSessionUser`, `/api/iqauth/userinfo`)
|
|
369
|
+
// expose it without consumers having to re-decode the JWT.
|
|
370
|
+
...claims.scopeContext !== void 0 ? { scopeContext: claims.scopeContext } : {}
|
|
366
371
|
};
|
|
367
372
|
const enriched = opts.enrich ? await opts.enrich(claims) : null;
|
|
368
373
|
const user = enriched ? { ...baseUser, ...enriched } : baseUser;
|
|
@@ -407,19 +412,62 @@ function shouldClearCookiesOnFailure(policy, status, errorCode) {
|
|
|
407
412
|
}
|
|
408
413
|
var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
|
|
409
414
|
var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
415
|
+
function assertCookiePrefixInvariants(name, secure, path, domain) {
|
|
416
|
+
if (name.startsWith("__Host-")) {
|
|
417
|
+
if (!secure) {
|
|
418
|
+
throw new IQAuthError(
|
|
419
|
+
"config_invalid",
|
|
420
|
+
`Cookie "${name}" uses the __Host- prefix, which browsers only accept on a Secure cookie. Set secure:true (and serve over HTTPS).`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
if (path !== "/") {
|
|
424
|
+
throw new IQAuthError(
|
|
425
|
+
"config_invalid",
|
|
426
|
+
`Cookie "${name}" uses the __Host- prefix, which requires Path=/ (got "${path}"). Remove cookiePath or set it to "/".`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
if (domain) {
|
|
430
|
+
throw new IQAuthError(
|
|
431
|
+
"config_invalid",
|
|
432
|
+
`Cookie "${name}" uses the __Host- prefix, which forbids a Domain attribute (the cookie is host-locked). Remove cookieDomain.`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
} else if (name.startsWith("__Secure-") && !secure) {
|
|
436
|
+
throw new IQAuthError(
|
|
437
|
+
"config_invalid",
|
|
438
|
+
`Cookie "${name}" uses the __Secure- prefix, which browsers only accept on a Secure cookie. Set secure:true (and serve over HTTPS).`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
410
442
|
function resolve(config) {
|
|
411
443
|
const parsed = assertPublishableKey(config.publishableKey, { context: "@iqauth/sdk helpers" });
|
|
412
444
|
const inferredIssuer = parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`;
|
|
445
|
+
maybeWarnDefaultSignoutRegistry(config);
|
|
446
|
+
const secure = config.secure ?? true;
|
|
447
|
+
if (config.secure === false && config.allowInsecureCookies !== true) {
|
|
448
|
+
throw new IQAuthError(
|
|
449
|
+
"config_invalid",
|
|
450
|
+
"Refusing to issue auth cookies with secure:false \u2014 this exposes session cookies over plaintext HTTP. For local HTTP development, set allowInsecureCookies:true to acknowledge the risk. Production MUST use HTTPS with secure cookies."
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
const accessCookieName = config.accessCookieName ?? config.cookieNames?.access ?? "iqauth_at";
|
|
454
|
+
const refreshCookieName = config.refreshCookieName ?? config.cookieNames?.refresh ?? "iqauth_rt";
|
|
455
|
+
const stateCookieName = config.stateCookieName ?? "iqauth_state";
|
|
456
|
+
const cookiePath = config.cookiePath ?? "/";
|
|
457
|
+
const cookieDomain = config.cookieDomain;
|
|
458
|
+
for (const name of [accessCookieName, refreshCookieName, stateCookieName]) {
|
|
459
|
+
assertCookiePrefixInvariants(name, secure, cookiePath, cookieDomain);
|
|
460
|
+
}
|
|
413
461
|
return {
|
|
414
462
|
publishableKey: config.publishableKey,
|
|
415
463
|
secretKey: config.secretKey,
|
|
416
464
|
issuer: (config.issuer ?? inferredIssuer).replace(/\/+$/, ""),
|
|
417
|
-
accessCookieName
|
|
418
|
-
refreshCookieName
|
|
419
|
-
cookieDomain
|
|
465
|
+
accessCookieName,
|
|
466
|
+
refreshCookieName,
|
|
467
|
+
cookieDomain,
|
|
420
468
|
sameSite: config.sameSite ?? "lax",
|
|
421
|
-
secure
|
|
422
|
-
cookiePath
|
|
469
|
+
secure,
|
|
470
|
+
cookiePath,
|
|
423
471
|
tokenPath: config.tokenPath ?? "/oidc/token",
|
|
424
472
|
refreshPath: config.refreshPath ?? "/api/v1/auth/refresh",
|
|
425
473
|
logoutPath: config.logoutPath ?? "/api/v1/auth/logout",
|
|
@@ -432,9 +480,19 @@ function resolve(config) {
|
|
|
432
480
|
debug: config.debug,
|
|
433
481
|
onTimingEvent: config.onTimingEvent,
|
|
434
482
|
signoutRegistry: config.signoutRegistry ?? defaultSignoutRegistry,
|
|
435
|
-
signoutMarkerTtlMs: config.signoutMarkerTtlMs ?? DEFAULT_SIGNOUT_TTL_MS
|
|
483
|
+
signoutMarkerTtlMs: config.signoutMarkerTtlMs ?? DEFAULT_SIGNOUT_TTL_MS,
|
|
484
|
+
requireOAuthState: config.requireOAuthState ?? true,
|
|
485
|
+
stateCookieName: config.stateCookieName ?? "iqauth_state"
|
|
436
486
|
};
|
|
437
487
|
}
|
|
488
|
+
function timingSafeEqualStr(a, b) {
|
|
489
|
+
const len = Math.max(a.length, b.length);
|
|
490
|
+
let diff = a.length ^ b.length;
|
|
491
|
+
for (let i = 0; i < len; i++) {
|
|
492
|
+
diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
|
|
493
|
+
}
|
|
494
|
+
return diff === 0;
|
|
495
|
+
}
|
|
438
496
|
function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
439
497
|
return {
|
|
440
498
|
name,
|
|
@@ -453,6 +511,9 @@ function clearCookies(cfg) {
|
|
|
453
511
|
{ ...makeCookie(cfg, cfg.refreshCookieName, "", 0), clear: true }
|
|
454
512
|
];
|
|
455
513
|
}
|
|
514
|
+
function clearStateCookie(cfg) {
|
|
515
|
+
return { ...makeCookie(cfg, cfg.stateCookieName, "", 0, false), clear: true };
|
|
516
|
+
}
|
|
456
517
|
var DEFAULT_SIGNOUT_TTL_MS = 6e4;
|
|
457
518
|
var inMemorySignoutMarkers = /* @__PURE__ */ new Map();
|
|
458
519
|
function pruneInMemoryMarkers(now) {
|
|
@@ -478,9 +539,21 @@ var defaultSignoutRegistry = {
|
|
|
478
539
|
return true;
|
|
479
540
|
}
|
|
480
541
|
};
|
|
542
|
+
var warnedDefaultSignoutRegistry = false;
|
|
543
|
+
function maybeWarnDefaultSignoutRegistry(config) {
|
|
544
|
+
if (warnedDefaultSignoutRegistry) return;
|
|
545
|
+
if (config.signoutRegistry) return;
|
|
546
|
+
warnedDefaultSignoutRegistry = true;
|
|
547
|
+
console.warn(
|
|
548
|
+
"[IQAuth] Using the in-memory signout registry (process-local). Signout idempotency is NOT shared across instances \u2014 in a multi-replica deployment a /refresh racing a /signout on another replica can reissue cookies after sign-out. Plug a shared backend (e.g. Redis) into IQAuthHelperConfig.signoutRegistry to fix this and silence this warning."
|
|
549
|
+
);
|
|
550
|
+
}
|
|
481
551
|
function __resetSignoutMarkersForTests() {
|
|
482
552
|
inMemorySignoutMarkers.clear();
|
|
483
553
|
}
|
|
554
|
+
function __resetSignoutRegistryWarningForTests() {
|
|
555
|
+
warnedDefaultSignoutRegistry = false;
|
|
556
|
+
}
|
|
484
557
|
function createInMemorySignoutRegistry() {
|
|
485
558
|
const store = /* @__PURE__ */ new Map();
|
|
486
559
|
return {
|
|
@@ -523,6 +596,23 @@ async function handleCallback(config, input) {
|
|
|
523
596
|
cookies: []
|
|
524
597
|
};
|
|
525
598
|
}
|
|
599
|
+
const provided = input.state;
|
|
600
|
+
const expected = input.expectedState;
|
|
601
|
+
const stateOk = cfg.requireOAuthState ? !!expected && !!provided && timingSafeEqualStr(provided, expected) : !expected || !!provided && timingSafeEqualStr(provided, expected);
|
|
602
|
+
if (!stateOk) {
|
|
603
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "STATE_MISMATCH" });
|
|
604
|
+
return {
|
|
605
|
+
status: 400,
|
|
606
|
+
body: {
|
|
607
|
+
success: false,
|
|
608
|
+
error: {
|
|
609
|
+
code: "STATE_MISMATCH",
|
|
610
|
+
message: "OAuth state validation failed; the sign-in could not be verified as originating from this browser."
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
cookies: [clearStateCookie(cfg)]
|
|
614
|
+
};
|
|
615
|
+
}
|
|
526
616
|
if (!cfg.secretKey) {
|
|
527
617
|
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "INTERNAL_ERROR" });
|
|
528
618
|
return {
|
|
@@ -561,6 +651,26 @@ async function handleCallback(config, input) {
|
|
|
561
651
|
cookies: []
|
|
562
652
|
};
|
|
563
653
|
}
|
|
654
|
+
try {
|
|
655
|
+
await getTokensFor(cfg.issuer).verify(json.access_token, {
|
|
656
|
+
issuer: cfg.issuer,
|
|
657
|
+
...config.verify
|
|
658
|
+
});
|
|
659
|
+
} catch (err) {
|
|
660
|
+
const code = err instanceof IQAuthError ? err.code : err.code || "TOKEN_INVALID";
|
|
661
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code });
|
|
662
|
+
return {
|
|
663
|
+
status: 502,
|
|
664
|
+
body: {
|
|
665
|
+
success: false,
|
|
666
|
+
error: {
|
|
667
|
+
code: "ACCESS_TOKEN_VERIFICATION_FAILED",
|
|
668
|
+
message: "The issuer returned an access token that failed verification; no session was established."
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
cookies: []
|
|
672
|
+
};
|
|
673
|
+
}
|
|
564
674
|
const cookies = [];
|
|
565
675
|
cookies.push(
|
|
566
676
|
makeCookie(cfg, cfg.accessCookieName, json.access_token, json.expires_in ?? ACCESS_TOKEN_TTL_SECONDS)
|
|
@@ -568,6 +678,7 @@ async function handleCallback(config, input) {
|
|
|
568
678
|
if (json.refresh_token) {
|
|
569
679
|
cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.refresh_token, REFRESH_TOKEN_TTL_SECONDS));
|
|
570
680
|
}
|
|
681
|
+
cookies.push(clearStateCookie(cfg));
|
|
571
682
|
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: true });
|
|
572
683
|
return {
|
|
573
684
|
status: 200,
|
|
@@ -689,7 +800,10 @@ async function handleUserinfo(config, input) {
|
|
|
689
800
|
}
|
|
690
801
|
let claims;
|
|
691
802
|
try {
|
|
692
|
-
claims = await getTokensFor(cfg.issuer).verify(input.accessToken,
|
|
803
|
+
claims = await getTokensFor(cfg.issuer).verify(input.accessToken, {
|
|
804
|
+
issuer: cfg.issuer,
|
|
805
|
+
...config.verify
|
|
806
|
+
});
|
|
693
807
|
} catch (err) {
|
|
694
808
|
const code = err instanceof IQAuthError ? err.code : err.code || "TOKEN_INVALID";
|
|
695
809
|
const message = err instanceof Error ? err.message : "Access token verification failed";
|
|
@@ -711,6 +825,7 @@ async function handleUserinfo(config, input) {
|
|
|
711
825
|
// Annotate the CommonJS export names for ESM import in node:
|
|
712
826
|
0 && (module.exports = {
|
|
713
827
|
__resetSignoutMarkersForTests,
|
|
828
|
+
__resetSignoutRegistryWarningForTests,
|
|
714
829
|
buildUserinfoResponse,
|
|
715
830
|
createInMemorySignoutRegistry,
|
|
716
831
|
handleCallback,
|
package/dist/server/handlers.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__resetSignoutMarkersForTests,
|
|
3
|
+
__resetSignoutRegistryWarningForTests,
|
|
3
4
|
buildUserinfoResponse,
|
|
4
5
|
createInMemorySignoutRegistry,
|
|
5
6
|
handleCallback,
|
|
@@ -7,13 +8,14 @@ import {
|
|
|
7
8
|
handleSignout,
|
|
8
9
|
handleUserinfo,
|
|
9
10
|
serializeCookie
|
|
10
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-WSH4SW7F.mjs";
|
|
11
12
|
import "../chunk-HVHNYPDC.mjs";
|
|
12
13
|
import "../chunk-NUO2I65G.mjs";
|
|
13
14
|
import "../chunk-6PJRLRB4.mjs";
|
|
14
15
|
import "../chunk-Y6FXYEAI.mjs";
|
|
15
16
|
export {
|
|
16
17
|
__resetSignoutMarkersForTests,
|
|
18
|
+
__resetSignoutRegistryWarningForTests,
|
|
17
19
|
buildUserinfoResponse,
|
|
18
20
|
createInMemorySignoutRegistry,
|
|
19
21
|
handleCallback,
|
package/dist/server.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { J as JwtClaims, f as IQAuthTokenClientConfig, X as ExpressMiddlewareOptions, a as IQAuthRequestLike, b as IQAuthResponseLike, c as IQAuthNextFunction } from './types-
|
|
2
|
-
import { I as IQAuthClient } from './client-
|
|
1
|
+
import { J as JwtClaims, f as IQAuthTokenClientConfig, X as ExpressMiddlewareOptions, a as IQAuthRequestLike, b as IQAuthResponseLike, c as IQAuthNextFunction } from './types-Bn8O-OEd.mjs';
|
|
2
|
+
import { I as IQAuthClient } from './client-D8L-PaWr.mjs';
|
|
3
3
|
export { E as ErrorCodes, I as IQAuthError } from './errors-Jl1Jtm-6.mjs';
|
|
4
|
-
export { C as CookieAwareMiddlewareOptions, D as DEFAULT_ACCESS_COOKIE, a as DEFAULT_REFRESH_COOKIE, i as iqAuthMiddleware } from './express-
|
|
4
|
+
export { C as CookieAwareMiddlewareOptions, D as DEFAULT_ACCESS_COOKIE, a as DEFAULT_REFRESH_COOKIE, i as iqAuthMiddleware } from './express-DDTA3qV1.mjs';
|
|
5
5
|
export { HandlerResponse, IQAuthHelperConfig, SetCookieDirective, UserinfoResponse, buildUserinfoResponse, handleCallback, handleRefresh, handleSignout, handleUserinfo, serializeCookie } from './server/handlers.mjs';
|
|
6
|
-
export { P as ProvisioningBridge, a as ProvisioningBridgeOptions, d as ProvisioningContext, b as ProvisioningStorage, c as createProvisioningBridge } from './provisioningBridge-
|
|
7
|
-
import './tokens-
|
|
6
|
+
export { P as ProvisioningBridge, a as ProvisioningBridgeOptions, d as ProvisioningContext, e as ProvisioningError, b as ProvisioningStorage, c as createProvisioningBridge } from './provisioningBridge-IEycmsgb.mjs';
|
|
7
|
+
import './tokens-B06VtvUi.mjs';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* linkLocalUserToIqAuthSub — first-time-migration helper.
|
|
@@ -31,9 +31,17 @@ import './tokens-CITeoG6P.mjs';
|
|
|
31
31
|
* - 'linked' — wrote claims.sub onto the matched local row.
|
|
32
32
|
* - 'already_linked' — a row was already keyed by claims.sub. No write.
|
|
33
33
|
* - 'conflict' — matched row already has a different non-null sub,
|
|
34
|
-
* OR more than one local row matches the email
|
|
34
|
+
* OR more than one local row matches the email,
|
|
35
|
+
* OR the email is unverified and adoption is gated
|
|
36
|
+
* (reason 'unverified_email' — see H-4 below).
|
|
35
37
|
* - 'not_found' — no local row matched any of the provided lookupBy
|
|
36
38
|
* keys. Caller should provision a new row separately.
|
|
39
|
+
*
|
|
40
|
+
* Security (H-4): writing the IQAuth `sub` onto a pre-existing local row matched
|
|
41
|
+
* by email is a takeover of that account. It is only performed when the claims
|
|
42
|
+
* assert a verified email (`claims.email_verified === true`). When the email is
|
|
43
|
+
* unverified and `allowUnverifiedEmail` is not set, the helper fails closed with
|
|
44
|
+
* `{ status: 'conflict', reason: 'unverified_email' }` instead of linking.
|
|
37
45
|
*/
|
|
38
46
|
|
|
39
47
|
type LinkLookupBy = "email";
|
|
@@ -46,7 +54,7 @@ type LinkResult = {
|
|
|
46
54
|
} | {
|
|
47
55
|
status: "conflict";
|
|
48
56
|
userId?: string;
|
|
49
|
-
reason: "different_sub" | "duplicate_email";
|
|
57
|
+
reason: "different_sub" | "duplicate_email" | "unverified_email";
|
|
50
58
|
} | {
|
|
51
59
|
status: "not_found";
|
|
52
60
|
};
|
|
@@ -85,7 +93,7 @@ interface LinkAdapter {
|
|
|
85
93
|
}
|
|
86
94
|
interface LinkLocalUserOptions {
|
|
87
95
|
adapter: LinkAdapter;
|
|
88
|
-
claims: Pick<JwtClaims, "sub" | "email">;
|
|
96
|
+
claims: Pick<JwtClaims, "sub" | "email" | "email_verified">;
|
|
89
97
|
/**
|
|
90
98
|
* Lookup keys to try, in order. Currently only `'email'` is supported.
|
|
91
99
|
* Defaults to `['email']`.
|
|
@@ -96,6 +104,18 @@ interface LinkLocalUserOptions {
|
|
|
96
104
|
* but preserve the cased original at registration time.
|
|
97
105
|
*/
|
|
98
106
|
caseInsensitiveEmail?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Security gate (H-4): by default the email→sub link only proceeds when the
|
|
109
|
+
* claims assert a verified email (`claims.email_verified === true`). When the
|
|
110
|
+
* email is unverified and this flag is left `false`, the helper fails closed
|
|
111
|
+
* with `{ status: 'conflict', reason: 'unverified_email' }` rather than
|
|
112
|
+
* letting an unverified email take over a pre-existing local account.
|
|
113
|
+
*
|
|
114
|
+
* Set `true` ONLY when your issuer is trusted to never emit an unverified
|
|
115
|
+
* email for linking (or you have a compensating control). Defaults to
|
|
116
|
+
* `false` (secure).
|
|
117
|
+
*/
|
|
118
|
+
allowUnverifiedEmail?: boolean;
|
|
99
119
|
}
|
|
100
120
|
declare function linkLocalUserToIqAuthSub(options: LinkLocalUserOptions): Promise<LinkResult>;
|
|
101
121
|
interface DrizzleLikeDb {
|
package/dist/server.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { J as JwtClaims, f as IQAuthTokenClientConfig, X as ExpressMiddlewareOptions, a as IQAuthRequestLike, b as IQAuthResponseLike, c as IQAuthNextFunction } from './types-
|
|
2
|
-
import { I as IQAuthClient } from './client-
|
|
1
|
+
import { J as JwtClaims, f as IQAuthTokenClientConfig, X as ExpressMiddlewareOptions, a as IQAuthRequestLike, b as IQAuthResponseLike, c as IQAuthNextFunction } from './types-Bn8O-OEd.js';
|
|
2
|
+
import { I as IQAuthClient } from './client-DkPL0EPZ.js';
|
|
3
3
|
export { E as ErrorCodes, I as IQAuthError } from './errors-Jl1Jtm-6.js';
|
|
4
|
-
export { C as CookieAwareMiddlewareOptions, D as DEFAULT_ACCESS_COOKIE, a as DEFAULT_REFRESH_COOKIE, i as iqAuthMiddleware } from './express-
|
|
4
|
+
export { C as CookieAwareMiddlewareOptions, D as DEFAULT_ACCESS_COOKIE, a as DEFAULT_REFRESH_COOKIE, i as iqAuthMiddleware } from './express-Budysq4h.js';
|
|
5
5
|
export { HandlerResponse, IQAuthHelperConfig, SetCookieDirective, UserinfoResponse, buildUserinfoResponse, handleCallback, handleRefresh, handleSignout, handleUserinfo, serializeCookie } from './server/handlers.js';
|
|
6
|
-
export { P as ProvisioningBridge, a as ProvisioningBridgeOptions, d as ProvisioningContext, b as ProvisioningStorage, c as createProvisioningBridge } from './provisioningBridge-
|
|
7
|
-
import './tokens-
|
|
6
|
+
export { P as ProvisioningBridge, a as ProvisioningBridgeOptions, d as ProvisioningContext, e as ProvisioningError, b as ProvisioningStorage, c as createProvisioningBridge } from './provisioningBridge-BXPMZCLe.js';
|
|
7
|
+
import './tokens-9F6ETrzk.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* linkLocalUserToIqAuthSub — first-time-migration helper.
|
|
@@ -31,9 +31,17 @@ import './tokens-Bqhmqq_R.js';
|
|
|
31
31
|
* - 'linked' — wrote claims.sub onto the matched local row.
|
|
32
32
|
* - 'already_linked' — a row was already keyed by claims.sub. No write.
|
|
33
33
|
* - 'conflict' — matched row already has a different non-null sub,
|
|
34
|
-
* OR more than one local row matches the email
|
|
34
|
+
* OR more than one local row matches the email,
|
|
35
|
+
* OR the email is unverified and adoption is gated
|
|
36
|
+
* (reason 'unverified_email' — see H-4 below).
|
|
35
37
|
* - 'not_found' — no local row matched any of the provided lookupBy
|
|
36
38
|
* keys. Caller should provision a new row separately.
|
|
39
|
+
*
|
|
40
|
+
* Security (H-4): writing the IQAuth `sub` onto a pre-existing local row matched
|
|
41
|
+
* by email is a takeover of that account. It is only performed when the claims
|
|
42
|
+
* assert a verified email (`claims.email_verified === true`). When the email is
|
|
43
|
+
* unverified and `allowUnverifiedEmail` is not set, the helper fails closed with
|
|
44
|
+
* `{ status: 'conflict', reason: 'unverified_email' }` instead of linking.
|
|
37
45
|
*/
|
|
38
46
|
|
|
39
47
|
type LinkLookupBy = "email";
|
|
@@ -46,7 +54,7 @@ type LinkResult = {
|
|
|
46
54
|
} | {
|
|
47
55
|
status: "conflict";
|
|
48
56
|
userId?: string;
|
|
49
|
-
reason: "different_sub" | "duplicate_email";
|
|
57
|
+
reason: "different_sub" | "duplicate_email" | "unverified_email";
|
|
50
58
|
} | {
|
|
51
59
|
status: "not_found";
|
|
52
60
|
};
|
|
@@ -85,7 +93,7 @@ interface LinkAdapter {
|
|
|
85
93
|
}
|
|
86
94
|
interface LinkLocalUserOptions {
|
|
87
95
|
adapter: LinkAdapter;
|
|
88
|
-
claims: Pick<JwtClaims, "sub" | "email">;
|
|
96
|
+
claims: Pick<JwtClaims, "sub" | "email" | "email_verified">;
|
|
89
97
|
/**
|
|
90
98
|
* Lookup keys to try, in order. Currently only `'email'` is supported.
|
|
91
99
|
* Defaults to `['email']`.
|
|
@@ -96,6 +104,18 @@ interface LinkLocalUserOptions {
|
|
|
96
104
|
* but preserve the cased original at registration time.
|
|
97
105
|
*/
|
|
98
106
|
caseInsensitiveEmail?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Security gate (H-4): by default the email→sub link only proceeds when the
|
|
109
|
+
* claims assert a verified email (`claims.email_verified === true`). When the
|
|
110
|
+
* email is unverified and this flag is left `false`, the helper fails closed
|
|
111
|
+
* with `{ status: 'conflict', reason: 'unverified_email' }` rather than
|
|
112
|
+
* letting an unverified email take over a pre-existing local account.
|
|
113
|
+
*
|
|
114
|
+
* Set `true` ONLY when your issuer is trusted to never emit an unverified
|
|
115
|
+
* email for linking (or you have a compensating control). Defaults to
|
|
116
|
+
* `false` (secure).
|
|
117
|
+
*/
|
|
118
|
+
allowUnverifiedEmail?: boolean;
|
|
99
119
|
}
|
|
100
120
|
declare function linkLocalUserToIqAuthSub(options: LinkLocalUserOptions): Promise<LinkResult>;
|
|
101
121
|
interface DrizzleLikeDb {
|