@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/fastify.js
CHANGED
|
@@ -296,17 +296,27 @@ function parseLoginResponse(data, browserSessionMode) {
|
|
|
296
296
|
tenants: data.tenants
|
|
297
297
|
};
|
|
298
298
|
}
|
|
299
|
+
if (data.type === "scope_selection" && data.scopeSelectionToken && data.scopes && data.tenantId) {
|
|
300
|
+
return {
|
|
301
|
+
status: "scope_selection",
|
|
302
|
+
scopeSelectionToken: data.scopeSelectionToken,
|
|
303
|
+
tenantId: data.tenantId,
|
|
304
|
+
scopes: data.scopes
|
|
305
|
+
};
|
|
306
|
+
}
|
|
299
307
|
throw new Error("Unexpected login response shape");
|
|
300
308
|
}
|
|
301
309
|
var AuthModule = class {
|
|
302
310
|
constructor(http) {
|
|
303
311
|
this.http = http;
|
|
304
312
|
}
|
|
305
|
-
async login(email, password) {
|
|
313
|
+
async login(email, password, opts) {
|
|
314
|
+
const body = { email, password };
|
|
315
|
+
if (opts?.scopeHint) body.scopeHint = opts.scopeHint;
|
|
306
316
|
const data = await this.http.request(
|
|
307
317
|
"POST",
|
|
308
318
|
"/api/v1/auth/login",
|
|
309
|
-
|
|
319
|
+
body,
|
|
310
320
|
{ skipAutoRefresh: true }
|
|
311
321
|
);
|
|
312
322
|
return parseLoginResponse(data, this.http.isBrowserSession());
|
|
@@ -344,13 +354,29 @@ var AuthModule = class {
|
|
|
344
354
|
method
|
|
345
355
|
}, { skipAutoRefresh: true });
|
|
346
356
|
}
|
|
347
|
-
async selectTenant(tenantSelectionToken, tenantId) {
|
|
357
|
+
async selectTenant(tenantSelectionToken, tenantId, opts) {
|
|
358
|
+
const body = { tenantSelectionToken, tenantId };
|
|
359
|
+
if (opts?.scopeHint) body.scopeHint = opts.scopeHint;
|
|
348
360
|
const data = await this.http.request(
|
|
349
361
|
"POST",
|
|
350
362
|
"/api/v1/auth/select-tenant",
|
|
363
|
+
body,
|
|
364
|
+
{ skipAutoRefresh: true }
|
|
365
|
+
);
|
|
366
|
+
return parseLoginResponse(data, this.http.isBrowserSession());
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Task #171 — redeem a scope-selection token + chosen membership for a
|
|
370
|
+
* real authenticated session. `membershipId` must be one of the scopes
|
|
371
|
+
* returned in the prior `scope_selection` envelope.
|
|
372
|
+
*/
|
|
373
|
+
async selectScope(scopeSelectionToken, membershipId) {
|
|
374
|
+
const data = await this.http.request(
|
|
375
|
+
"POST",
|
|
376
|
+
"/api/v1/auth/select-scope",
|
|
351
377
|
{
|
|
352
|
-
|
|
353
|
-
|
|
378
|
+
scopeSelectionToken,
|
|
379
|
+
membershipId
|
|
354
380
|
},
|
|
355
381
|
{ skipAutoRefresh: true }
|
|
356
382
|
);
|
|
@@ -1993,7 +2019,11 @@ async function buildUserinfoResponse(claims, opts = {}) {
|
|
|
1993
2019
|
tenantId: claims.tenantId,
|
|
1994
2020
|
vendorId: claims.vendorId,
|
|
1995
2021
|
roles: claims.roles ?? [],
|
|
1996
|
-
entitlements: claims.entitlements ?? []
|
|
2022
|
+
entitlements: claims.entitlements ?? [],
|
|
2023
|
+
// Task #171 — project the active source/client scope onto the userinfo
|
|
2024
|
+
// payload so server handlers (`getSessionUser`, `/api/iqauth/userinfo`)
|
|
2025
|
+
// expose it without consumers having to re-decode the JWT.
|
|
2026
|
+
...claims.scopeContext !== void 0 ? { scopeContext: claims.scopeContext } : {}
|
|
1997
2027
|
};
|
|
1998
2028
|
const enriched = opts.enrich ? await opts.enrich(claims) : null;
|
|
1999
2029
|
const user = enriched ? { ...baseUser, ...enriched } : baseUser;
|
|
@@ -2038,19 +2068,62 @@ function shouldClearCookiesOnFailure(policy, status, errorCode) {
|
|
|
2038
2068
|
}
|
|
2039
2069
|
var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
|
|
2040
2070
|
var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
2071
|
+
function assertCookiePrefixInvariants(name, secure, path, domain) {
|
|
2072
|
+
if (name.startsWith("__Host-")) {
|
|
2073
|
+
if (!secure) {
|
|
2074
|
+
throw new IQAuthError(
|
|
2075
|
+
"config_invalid",
|
|
2076
|
+
`Cookie "${name}" uses the __Host- prefix, which browsers only accept on a Secure cookie. Set secure:true (and serve over HTTPS).`
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
if (path !== "/") {
|
|
2080
|
+
throw new IQAuthError(
|
|
2081
|
+
"config_invalid",
|
|
2082
|
+
`Cookie "${name}" uses the __Host- prefix, which requires Path=/ (got "${path}"). Remove cookiePath or set it to "/".`
|
|
2083
|
+
);
|
|
2084
|
+
}
|
|
2085
|
+
if (domain) {
|
|
2086
|
+
throw new IQAuthError(
|
|
2087
|
+
"config_invalid",
|
|
2088
|
+
`Cookie "${name}" uses the __Host- prefix, which forbids a Domain attribute (the cookie is host-locked). Remove cookieDomain.`
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
2091
|
+
} else if (name.startsWith("__Secure-") && !secure) {
|
|
2092
|
+
throw new IQAuthError(
|
|
2093
|
+
"config_invalid",
|
|
2094
|
+
`Cookie "${name}" uses the __Secure- prefix, which browsers only accept on a Secure cookie. Set secure:true (and serve over HTTPS).`
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2041
2098
|
function resolve(config) {
|
|
2042
2099
|
const parsed = assertPublishableKey(config.publishableKey, { context: "@iqauth/sdk helpers" });
|
|
2043
2100
|
const inferredIssuer = parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`;
|
|
2101
|
+
maybeWarnDefaultSignoutRegistry(config);
|
|
2102
|
+
const secure = config.secure ?? true;
|
|
2103
|
+
if (config.secure === false && config.allowInsecureCookies !== true) {
|
|
2104
|
+
throw new IQAuthError(
|
|
2105
|
+
"config_invalid",
|
|
2106
|
+
"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."
|
|
2107
|
+
);
|
|
2108
|
+
}
|
|
2109
|
+
const accessCookieName = config.accessCookieName ?? config.cookieNames?.access ?? "iqauth_at";
|
|
2110
|
+
const refreshCookieName = config.refreshCookieName ?? config.cookieNames?.refresh ?? "iqauth_rt";
|
|
2111
|
+
const stateCookieName = config.stateCookieName ?? "iqauth_state";
|
|
2112
|
+
const cookiePath = config.cookiePath ?? "/";
|
|
2113
|
+
const cookieDomain = config.cookieDomain;
|
|
2114
|
+
for (const name of [accessCookieName, refreshCookieName, stateCookieName]) {
|
|
2115
|
+
assertCookiePrefixInvariants(name, secure, cookiePath, cookieDomain);
|
|
2116
|
+
}
|
|
2044
2117
|
return {
|
|
2045
2118
|
publishableKey: config.publishableKey,
|
|
2046
2119
|
secretKey: config.secretKey,
|
|
2047
2120
|
issuer: (config.issuer ?? inferredIssuer).replace(/\/+$/, ""),
|
|
2048
|
-
accessCookieName
|
|
2049
|
-
refreshCookieName
|
|
2050
|
-
cookieDomain
|
|
2121
|
+
accessCookieName,
|
|
2122
|
+
refreshCookieName,
|
|
2123
|
+
cookieDomain,
|
|
2051
2124
|
sameSite: config.sameSite ?? "lax",
|
|
2052
|
-
secure
|
|
2053
|
-
cookiePath
|
|
2125
|
+
secure,
|
|
2126
|
+
cookiePath,
|
|
2054
2127
|
tokenPath: config.tokenPath ?? "/oidc/token",
|
|
2055
2128
|
refreshPath: config.refreshPath ?? "/api/v1/auth/refresh",
|
|
2056
2129
|
logoutPath: config.logoutPath ?? "/api/v1/auth/logout",
|
|
@@ -2063,9 +2136,19 @@ function resolve(config) {
|
|
|
2063
2136
|
debug: config.debug,
|
|
2064
2137
|
onTimingEvent: config.onTimingEvent,
|
|
2065
2138
|
signoutRegistry: config.signoutRegistry ?? defaultSignoutRegistry,
|
|
2066
|
-
signoutMarkerTtlMs: config.signoutMarkerTtlMs ?? DEFAULT_SIGNOUT_TTL_MS
|
|
2139
|
+
signoutMarkerTtlMs: config.signoutMarkerTtlMs ?? DEFAULT_SIGNOUT_TTL_MS,
|
|
2140
|
+
requireOAuthState: config.requireOAuthState ?? true,
|
|
2141
|
+
stateCookieName: config.stateCookieName ?? "iqauth_state"
|
|
2067
2142
|
};
|
|
2068
2143
|
}
|
|
2144
|
+
function timingSafeEqualStr(a, b) {
|
|
2145
|
+
const len = Math.max(a.length, b.length);
|
|
2146
|
+
let diff = a.length ^ b.length;
|
|
2147
|
+
for (let i = 0; i < len; i++) {
|
|
2148
|
+
diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
|
|
2149
|
+
}
|
|
2150
|
+
return diff === 0;
|
|
2151
|
+
}
|
|
2069
2152
|
function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
2070
2153
|
return {
|
|
2071
2154
|
name,
|
|
@@ -2084,6 +2167,9 @@ function clearCookies(cfg) {
|
|
|
2084
2167
|
{ ...makeCookie(cfg, cfg.refreshCookieName, "", 0), clear: true }
|
|
2085
2168
|
];
|
|
2086
2169
|
}
|
|
2170
|
+
function clearStateCookie(cfg) {
|
|
2171
|
+
return { ...makeCookie(cfg, cfg.stateCookieName, "", 0, false), clear: true };
|
|
2172
|
+
}
|
|
2087
2173
|
var DEFAULT_SIGNOUT_TTL_MS = 6e4;
|
|
2088
2174
|
var inMemorySignoutMarkers = /* @__PURE__ */ new Map();
|
|
2089
2175
|
function pruneInMemoryMarkers(now) {
|
|
@@ -2109,6 +2195,15 @@ var defaultSignoutRegistry = {
|
|
|
2109
2195
|
return true;
|
|
2110
2196
|
}
|
|
2111
2197
|
};
|
|
2198
|
+
var warnedDefaultSignoutRegistry = false;
|
|
2199
|
+
function maybeWarnDefaultSignoutRegistry(config) {
|
|
2200
|
+
if (warnedDefaultSignoutRegistry) return;
|
|
2201
|
+
if (config.signoutRegistry) return;
|
|
2202
|
+
warnedDefaultSignoutRegistry = true;
|
|
2203
|
+
console.warn(
|
|
2204
|
+
"[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."
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2112
2207
|
function serializeCookie(d) {
|
|
2113
2208
|
const parts = [`${d.name}=${encodeURIComponent(d.value)}`];
|
|
2114
2209
|
parts.push(`Path=${d.path}`);
|
|
@@ -2131,6 +2226,23 @@ async function handleCallback(config, input) {
|
|
|
2131
2226
|
cookies: []
|
|
2132
2227
|
};
|
|
2133
2228
|
}
|
|
2229
|
+
const provided = input.state;
|
|
2230
|
+
const expected = input.expectedState;
|
|
2231
|
+
const stateOk = cfg.requireOAuthState ? !!expected && !!provided && timingSafeEqualStr(provided, expected) : !expected || !!provided && timingSafeEqualStr(provided, expected);
|
|
2232
|
+
if (!stateOk) {
|
|
2233
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "STATE_MISMATCH" });
|
|
2234
|
+
return {
|
|
2235
|
+
status: 400,
|
|
2236
|
+
body: {
|
|
2237
|
+
success: false,
|
|
2238
|
+
error: {
|
|
2239
|
+
code: "STATE_MISMATCH",
|
|
2240
|
+
message: "OAuth state validation failed; the sign-in could not be verified as originating from this browser."
|
|
2241
|
+
}
|
|
2242
|
+
},
|
|
2243
|
+
cookies: [clearStateCookie(cfg)]
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2134
2246
|
if (!cfg.secretKey) {
|
|
2135
2247
|
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "INTERNAL_ERROR" });
|
|
2136
2248
|
return {
|
|
@@ -2169,6 +2281,26 @@ async function handleCallback(config, input) {
|
|
|
2169
2281
|
cookies: []
|
|
2170
2282
|
};
|
|
2171
2283
|
}
|
|
2284
|
+
try {
|
|
2285
|
+
await getTokensFor(cfg.issuer).verify(json.access_token, {
|
|
2286
|
+
issuer: cfg.issuer,
|
|
2287
|
+
...config.verify
|
|
2288
|
+
});
|
|
2289
|
+
} catch (err) {
|
|
2290
|
+
const code = err instanceof IQAuthError ? err.code : err.code || "TOKEN_INVALID";
|
|
2291
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code });
|
|
2292
|
+
return {
|
|
2293
|
+
status: 502,
|
|
2294
|
+
body: {
|
|
2295
|
+
success: false,
|
|
2296
|
+
error: {
|
|
2297
|
+
code: "ACCESS_TOKEN_VERIFICATION_FAILED",
|
|
2298
|
+
message: "The issuer returned an access token that failed verification; no session was established."
|
|
2299
|
+
}
|
|
2300
|
+
},
|
|
2301
|
+
cookies: []
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2172
2304
|
const cookies = [];
|
|
2173
2305
|
cookies.push(
|
|
2174
2306
|
makeCookie(cfg, cfg.accessCookieName, json.access_token, json.expires_in ?? ACCESS_TOKEN_TTL_SECONDS)
|
|
@@ -2176,6 +2308,7 @@ async function handleCallback(config, input) {
|
|
|
2176
2308
|
if (json.refresh_token) {
|
|
2177
2309
|
cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.refresh_token, REFRESH_TOKEN_TTL_SECONDS));
|
|
2178
2310
|
}
|
|
2311
|
+
cookies.push(clearStateCookie(cfg));
|
|
2179
2312
|
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: true });
|
|
2180
2313
|
return {
|
|
2181
2314
|
status: 200,
|
|
@@ -2297,7 +2430,10 @@ async function handleUserinfo(config, input) {
|
|
|
2297
2430
|
}
|
|
2298
2431
|
let claims;
|
|
2299
2432
|
try {
|
|
2300
|
-
claims = await getTokensFor(cfg.issuer).verify(input.accessToken,
|
|
2433
|
+
claims = await getTokensFor(cfg.issuer).verify(input.accessToken, {
|
|
2434
|
+
issuer: cfg.issuer,
|
|
2435
|
+
...config.verify
|
|
2436
|
+
});
|
|
2301
2437
|
} catch (err) {
|
|
2302
2438
|
const code = err instanceof IQAuthError ? err.code : err.code || "TOKEN_INVALID";
|
|
2303
2439
|
const message = err instanceof Error ? err.message : "Access token verification failed";
|
|
@@ -2317,7 +2453,44 @@ async function handleUserinfo(config, input) {
|
|
|
2317
2453
|
};
|
|
2318
2454
|
}
|
|
2319
2455
|
|
|
2456
|
+
// src/browser/returnTo.ts
|
|
2457
|
+
function normalizeOrigin(o) {
|
|
2458
|
+
try {
|
|
2459
|
+
return new URL(o).origin;
|
|
2460
|
+
} catch {
|
|
2461
|
+
return o.replace(/\/+$/, "");
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
function sanitizeReturnTo(input, options = {}) {
|
|
2465
|
+
const fallback = options.fallback ?? "/";
|
|
2466
|
+
if (!input || typeof input !== "string") return fallback;
|
|
2467
|
+
const trimmed = input.trim();
|
|
2468
|
+
if (!trimmed) return fallback;
|
|
2469
|
+
if (trimmed.includes("\\")) return fallback;
|
|
2470
|
+
if (trimmed.startsWith("//")) return fallback;
|
|
2471
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("#") || trimmed.startsWith("?")) {
|
|
2472
|
+
return trimmed;
|
|
2473
|
+
}
|
|
2474
|
+
if (!/^[a-z][a-z0-9+\-.]*:/i.test(trimmed)) {
|
|
2475
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
2476
|
+
}
|
|
2477
|
+
let parsed;
|
|
2478
|
+
try {
|
|
2479
|
+
parsed = new URL(trimmed);
|
|
2480
|
+
} catch {
|
|
2481
|
+
return fallback;
|
|
2482
|
+
}
|
|
2483
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return fallback;
|
|
2484
|
+
const currentOrigin = options.currentOrigin ?? (typeof window !== "undefined" ? window.location.origin : "");
|
|
2485
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
2486
|
+
if (currentOrigin) allowed.add(normalizeOrigin(currentOrigin));
|
|
2487
|
+
for (const o of options.allowedOrigins ?? []) allowed.add(normalizeOrigin(o));
|
|
2488
|
+
if (allowed.has(parsed.origin)) return parsed.toString();
|
|
2489
|
+
return fallback;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2320
2492
|
// src/fastify.ts
|
|
2493
|
+
var PKCE_COOKIE = "iqauth_pkce";
|
|
2321
2494
|
var KNOWN_AUTH_ERRORS = /* @__PURE__ */ new Set([
|
|
2322
2495
|
"TOKEN_INVALID",
|
|
2323
2496
|
"TOKEN_EXPIRED",
|
|
@@ -2339,6 +2512,54 @@ function applyResponse(reply, hr) {
|
|
|
2339
2512
|
}
|
|
2340
2513
|
reply.code(hr.status).send(hr.body);
|
|
2341
2514
|
}
|
|
2515
|
+
function applyCallbackResponse(reply, hr, requestOrigin, returnToCookieValue, returnToCookieName) {
|
|
2516
|
+
const returnTo = sanitizeReturnTo(
|
|
2517
|
+
returnToCookieValue || hr.body?.returnTo,
|
|
2518
|
+
{ currentOrigin: requestOrigin, fallback: "/" }
|
|
2519
|
+
);
|
|
2520
|
+
for (const c of hr.cookies) {
|
|
2521
|
+
const cookie = serializeCookie(c);
|
|
2522
|
+
const existing = reply.getHeader?.("set-cookie") ?? [];
|
|
2523
|
+
const list = Array.isArray(existing) ? existing : [existing];
|
|
2524
|
+
list.push(cookie);
|
|
2525
|
+
reply.header("set-cookie", list);
|
|
2526
|
+
}
|
|
2527
|
+
if (hr.status < 400) {
|
|
2528
|
+
const existing = reply.getHeader?.("set-cookie") ?? [];
|
|
2529
|
+
const list = Array.isArray(existing) ? existing : [existing];
|
|
2530
|
+
list.push(`${returnToCookieName}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
2531
|
+
reply.header("set-cookie", list);
|
|
2532
|
+
}
|
|
2533
|
+
reply.code(hr.status).send({ ...hr.body, returnTo });
|
|
2534
|
+
}
|
|
2535
|
+
function applyCallbackRedirect(reply, hr, requestOrigin, returnToCookieValue, cookieNames) {
|
|
2536
|
+
const pushCookie = (value) => {
|
|
2537
|
+
const existing = reply.getHeader?.("set-cookie") ?? [];
|
|
2538
|
+
const list = Array.isArray(existing) ? existing : [existing];
|
|
2539
|
+
list.push(value);
|
|
2540
|
+
reply.header("set-cookie", list);
|
|
2541
|
+
};
|
|
2542
|
+
for (const c of hr.cookies) pushCookie(serializeCookie(c));
|
|
2543
|
+
pushCookie(`${cookieNames.state}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
2544
|
+
pushCookie(`${cookieNames.pkce}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
2545
|
+
if (hr.status >= 400) {
|
|
2546
|
+
reply.header("location", "/");
|
|
2547
|
+
reply.code(302).send();
|
|
2548
|
+
return;
|
|
2549
|
+
}
|
|
2550
|
+
const dest = sanitizeReturnTo(returnToCookieValue, {
|
|
2551
|
+
currentOrigin: requestOrigin,
|
|
2552
|
+
fallback: "/"
|
|
2553
|
+
});
|
|
2554
|
+
pushCookie(`${cookieNames.returnTo}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
2555
|
+
reply.header("location", dest);
|
|
2556
|
+
reply.code(302).send();
|
|
2557
|
+
}
|
|
2558
|
+
function requestOriginOf(req) {
|
|
2559
|
+
const proto = req.headers?.["x-forwarded-proto"]?.split(",")[0]?.trim() || (typeof req.protocol === "string" ? req.protocol : void 0) || "https";
|
|
2560
|
+
const host = req.headers?.["x-forwarded-host"]?.split(",")[0]?.trim() || req.headers?.host || "";
|
|
2561
|
+
return host ? `${proto}://${host}` : "";
|
|
2562
|
+
}
|
|
2342
2563
|
function readCookie(req, name) {
|
|
2343
2564
|
if (req.cookies && typeof req.cookies[name] === "string") return req.cookies[name];
|
|
2344
2565
|
const raw = req.headers?.cookie;
|
|
@@ -2372,6 +2593,7 @@ async function iqAuth(fastify, options) {
|
|
|
2372
2593
|
} : void 0;
|
|
2373
2594
|
const accessCookie = options.accessCookieName ?? "iqauth_at";
|
|
2374
2595
|
const refreshCookie = options.refreshCookieName ?? "iqauth_rt";
|
|
2596
|
+
const returnToCookie = options.returnToCookieName ?? "iqauth_return_to";
|
|
2375
2597
|
const mount = (options.mountPath ?? "/api/iqauth").replace(/\/+$/, "");
|
|
2376
2598
|
const mountHelpers = options.mountHelperRoutes !== false;
|
|
2377
2599
|
const isPublic = (p) => {
|
|
@@ -2406,11 +2628,33 @@ async function iqAuth(fastify, options) {
|
|
|
2406
2628
|
if (mountHelpers) {
|
|
2407
2629
|
fastify.post(`${mount}/callback`, async (req, reply) => {
|
|
2408
2630
|
const body = req.body || {};
|
|
2409
|
-
|
|
2631
|
+
const hr = await handleCallback(helperConfig, {
|
|
2410
2632
|
code: body.code,
|
|
2411
2633
|
codeVerifier: body.codeVerifier,
|
|
2412
|
-
redirectUri: body.redirectUri
|
|
2413
|
-
|
|
2634
|
+
redirectUri: body.redirectUri,
|
|
2635
|
+
// M-2: bind callback to this browser; handleCallback fails closed.
|
|
2636
|
+
state: body.state,
|
|
2637
|
+
expectedState: readCookie(req, helperConfig.stateCookieName ?? "iqauth_state")
|
|
2638
|
+
});
|
|
2639
|
+
applyCallbackResponse(reply, hr, requestOriginOf(req), readCookie(req, returnToCookie), returnToCookie);
|
|
2640
|
+
});
|
|
2641
|
+
fastify.get(`${mount}/callback`, async (req, reply) => {
|
|
2642
|
+
const stateCookie = helperConfig.stateCookieName ?? "iqauth_state";
|
|
2643
|
+
const q = req.query || {};
|
|
2644
|
+
const origin = requestOriginOf(req);
|
|
2645
|
+
const redirectUri = `${origin}${mount}/callback`;
|
|
2646
|
+
const hr = await handleCallback(helperConfig, {
|
|
2647
|
+
code: q.code,
|
|
2648
|
+
codeVerifier: readCookie(req, PKCE_COOKIE),
|
|
2649
|
+
redirectUri,
|
|
2650
|
+
state: q.state,
|
|
2651
|
+
expectedState: readCookie(req, stateCookie)
|
|
2652
|
+
});
|
|
2653
|
+
applyCallbackRedirect(reply, hr, origin, readCookie(req, returnToCookie), {
|
|
2654
|
+
returnTo: returnToCookie,
|
|
2655
|
+
state: stateCookie,
|
|
2656
|
+
pkce: PKCE_COOKIE
|
|
2657
|
+
});
|
|
2414
2658
|
});
|
|
2415
2659
|
fastify.post(`${mount}/refresh`, async (req, reply) => {
|
|
2416
2660
|
const body = req.body || {};
|
package/dist/fastify.mjs
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sanitizeReturnTo
|
|
3
|
+
} from "./chunk-JRDVUWAL.mjs";
|
|
1
4
|
import {
|
|
2
5
|
handleCallback,
|
|
3
6
|
handleRefresh,
|
|
4
7
|
handleSignout,
|
|
5
8
|
handleUserinfo,
|
|
6
9
|
serializeCookie
|
|
7
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-WSH4SW7F.mjs";
|
|
8
11
|
import {
|
|
9
12
|
assertPublishableKey
|
|
10
13
|
} from "./chunk-HVHNYPDC.mjs";
|
|
11
14
|
import {
|
|
12
15
|
IQAuthClient
|
|
13
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-ZLJPABB7.mjs";
|
|
14
17
|
import "./chunk-NUO2I65G.mjs";
|
|
15
18
|
import {
|
|
16
19
|
IQAuthError
|
|
@@ -18,6 +21,7 @@ import {
|
|
|
18
21
|
import "./chunk-Y6FXYEAI.mjs";
|
|
19
22
|
|
|
20
23
|
// src/fastify.ts
|
|
24
|
+
var PKCE_COOKIE = "iqauth_pkce";
|
|
21
25
|
var KNOWN_AUTH_ERRORS = /* @__PURE__ */ new Set([
|
|
22
26
|
"TOKEN_INVALID",
|
|
23
27
|
"TOKEN_EXPIRED",
|
|
@@ -39,6 +43,54 @@ function applyResponse(reply, hr) {
|
|
|
39
43
|
}
|
|
40
44
|
reply.code(hr.status).send(hr.body);
|
|
41
45
|
}
|
|
46
|
+
function applyCallbackResponse(reply, hr, requestOrigin, returnToCookieValue, returnToCookieName) {
|
|
47
|
+
const returnTo = sanitizeReturnTo(
|
|
48
|
+
returnToCookieValue || hr.body?.returnTo,
|
|
49
|
+
{ currentOrigin: requestOrigin, fallback: "/" }
|
|
50
|
+
);
|
|
51
|
+
for (const c of hr.cookies) {
|
|
52
|
+
const cookie = serializeCookie(c);
|
|
53
|
+
const existing = reply.getHeader?.("set-cookie") ?? [];
|
|
54
|
+
const list = Array.isArray(existing) ? existing : [existing];
|
|
55
|
+
list.push(cookie);
|
|
56
|
+
reply.header("set-cookie", list);
|
|
57
|
+
}
|
|
58
|
+
if (hr.status < 400) {
|
|
59
|
+
const existing = reply.getHeader?.("set-cookie") ?? [];
|
|
60
|
+
const list = Array.isArray(existing) ? existing : [existing];
|
|
61
|
+
list.push(`${returnToCookieName}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
62
|
+
reply.header("set-cookie", list);
|
|
63
|
+
}
|
|
64
|
+
reply.code(hr.status).send({ ...hr.body, returnTo });
|
|
65
|
+
}
|
|
66
|
+
function applyCallbackRedirect(reply, hr, requestOrigin, returnToCookieValue, cookieNames) {
|
|
67
|
+
const pushCookie = (value) => {
|
|
68
|
+
const existing = reply.getHeader?.("set-cookie") ?? [];
|
|
69
|
+
const list = Array.isArray(existing) ? existing : [existing];
|
|
70
|
+
list.push(value);
|
|
71
|
+
reply.header("set-cookie", list);
|
|
72
|
+
};
|
|
73
|
+
for (const c of hr.cookies) pushCookie(serializeCookie(c));
|
|
74
|
+
pushCookie(`${cookieNames.state}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
75
|
+
pushCookie(`${cookieNames.pkce}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
76
|
+
if (hr.status >= 400) {
|
|
77
|
+
reply.header("location", "/");
|
|
78
|
+
reply.code(302).send();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const dest = sanitizeReturnTo(returnToCookieValue, {
|
|
82
|
+
currentOrigin: requestOrigin,
|
|
83
|
+
fallback: "/"
|
|
84
|
+
});
|
|
85
|
+
pushCookie(`${cookieNames.returnTo}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
86
|
+
reply.header("location", dest);
|
|
87
|
+
reply.code(302).send();
|
|
88
|
+
}
|
|
89
|
+
function requestOriginOf(req) {
|
|
90
|
+
const proto = req.headers?.["x-forwarded-proto"]?.split(",")[0]?.trim() || (typeof req.protocol === "string" ? req.protocol : void 0) || "https";
|
|
91
|
+
const host = req.headers?.["x-forwarded-host"]?.split(",")[0]?.trim() || req.headers?.host || "";
|
|
92
|
+
return host ? `${proto}://${host}` : "";
|
|
93
|
+
}
|
|
42
94
|
function readCookie(req, name) {
|
|
43
95
|
if (req.cookies && typeof req.cookies[name] === "string") return req.cookies[name];
|
|
44
96
|
const raw = req.headers?.cookie;
|
|
@@ -72,6 +124,7 @@ async function iqAuth(fastify, options) {
|
|
|
72
124
|
} : void 0;
|
|
73
125
|
const accessCookie = options.accessCookieName ?? "iqauth_at";
|
|
74
126
|
const refreshCookie = options.refreshCookieName ?? "iqauth_rt";
|
|
127
|
+
const returnToCookie = options.returnToCookieName ?? "iqauth_return_to";
|
|
75
128
|
const mount = (options.mountPath ?? "/api/iqauth").replace(/\/+$/, "");
|
|
76
129
|
const mountHelpers = options.mountHelperRoutes !== false;
|
|
77
130
|
const isPublic = (p) => {
|
|
@@ -106,11 +159,33 @@ async function iqAuth(fastify, options) {
|
|
|
106
159
|
if (mountHelpers) {
|
|
107
160
|
fastify.post(`${mount}/callback`, async (req, reply) => {
|
|
108
161
|
const body = req.body || {};
|
|
109
|
-
|
|
162
|
+
const hr = await handleCallback(helperConfig, {
|
|
110
163
|
code: body.code,
|
|
111
164
|
codeVerifier: body.codeVerifier,
|
|
112
|
-
redirectUri: body.redirectUri
|
|
113
|
-
|
|
165
|
+
redirectUri: body.redirectUri,
|
|
166
|
+
// M-2: bind callback to this browser; handleCallback fails closed.
|
|
167
|
+
state: body.state,
|
|
168
|
+
expectedState: readCookie(req, helperConfig.stateCookieName ?? "iqauth_state")
|
|
169
|
+
});
|
|
170
|
+
applyCallbackResponse(reply, hr, requestOriginOf(req), readCookie(req, returnToCookie), returnToCookie);
|
|
171
|
+
});
|
|
172
|
+
fastify.get(`${mount}/callback`, async (req, reply) => {
|
|
173
|
+
const stateCookie = helperConfig.stateCookieName ?? "iqauth_state";
|
|
174
|
+
const q = req.query || {};
|
|
175
|
+
const origin = requestOriginOf(req);
|
|
176
|
+
const redirectUri = `${origin}${mount}/callback`;
|
|
177
|
+
const hr = await handleCallback(helperConfig, {
|
|
178
|
+
code: q.code,
|
|
179
|
+
codeVerifier: readCookie(req, PKCE_COOKIE),
|
|
180
|
+
redirectUri,
|
|
181
|
+
state: q.state,
|
|
182
|
+
expectedState: readCookie(req, stateCookie)
|
|
183
|
+
});
|
|
184
|
+
applyCallbackRedirect(reply, hr, origin, readCookie(req, returnToCookie), {
|
|
185
|
+
returnTo: returnToCookie,
|
|
186
|
+
state: stateCookie,
|
|
187
|
+
pkce: PKCE_COOKIE
|
|
188
|
+
});
|
|
114
189
|
});
|
|
115
190
|
fastify.post(`${mount}/refresh`, async (req, reply) => {
|
|
116
191
|
const body = req.body || {};
|
package/dist/hono.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IQAuthHelperConfig } from './server/handlers.mjs';
|
|
2
|
-
import './tokens-
|
|
3
|
-
import './types-
|
|
2
|
+
import './tokens-B06VtvUi.mjs';
|
|
3
|
+
import './types-Bn8O-OEd.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @iqauth/sdk/hono — Hono adapter.
|
|
@@ -18,6 +18,14 @@ interface IQAuthHonoOptions extends IQAuthHelperConfig {
|
|
|
18
18
|
mountPath?: string;
|
|
19
19
|
mountHelperRoutes?: boolean;
|
|
20
20
|
publicPaths?: string[] | ((path: string) => boolean);
|
|
21
|
+
/**
|
|
22
|
+
* Cookie name the browser SDK publishes the post-login destination into
|
|
23
|
+
* before redirect. The callback handler reads it, surfaces it as `returnTo`
|
|
24
|
+
* in the JSON response body after a successful exchange, and clears it on
|
|
25
|
+
* success. Mirrors the express inline-callback adapter. Defaults to
|
|
26
|
+
* `iqauth_return_to`.
|
|
27
|
+
*/
|
|
28
|
+
returnToCookieName?: string;
|
|
21
29
|
}
|
|
22
30
|
declare function iqAuth(options: IQAuthHonoOptions): (c: any, next: () => Promise<void>) => Promise<any>;
|
|
23
31
|
|
package/dist/hono.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IQAuthHelperConfig } from './server/handlers.js';
|
|
2
|
-
import './tokens-
|
|
3
|
-
import './types-
|
|
2
|
+
import './tokens-9F6ETrzk.js';
|
|
3
|
+
import './types-Bn8O-OEd.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @iqauth/sdk/hono — Hono adapter.
|
|
@@ -18,6 +18,14 @@ interface IQAuthHonoOptions extends IQAuthHelperConfig {
|
|
|
18
18
|
mountPath?: string;
|
|
19
19
|
mountHelperRoutes?: boolean;
|
|
20
20
|
publicPaths?: string[] | ((path: string) => boolean);
|
|
21
|
+
/**
|
|
22
|
+
* Cookie name the browser SDK publishes the post-login destination into
|
|
23
|
+
* before redirect. The callback handler reads it, surfaces it as `returnTo`
|
|
24
|
+
* in the JSON response body after a successful exchange, and clears it on
|
|
25
|
+
* success. Mirrors the express inline-callback adapter. Defaults to
|
|
26
|
+
* `iqauth_return_to`.
|
|
27
|
+
*/
|
|
28
|
+
returnToCookieName?: string;
|
|
21
29
|
}
|
|
22
30
|
declare function iqAuth(options: IQAuthHonoOptions): (c: any, next: () => Promise<void>) => Promise<any>;
|
|
23
31
|
|