@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/hono.js
CHANGED
|
@@ -295,17 +295,27 @@ function parseLoginResponse(data, browserSessionMode) {
|
|
|
295
295
|
tenants: data.tenants
|
|
296
296
|
};
|
|
297
297
|
}
|
|
298
|
+
if (data.type === "scope_selection" && data.scopeSelectionToken && data.scopes && data.tenantId) {
|
|
299
|
+
return {
|
|
300
|
+
status: "scope_selection",
|
|
301
|
+
scopeSelectionToken: data.scopeSelectionToken,
|
|
302
|
+
tenantId: data.tenantId,
|
|
303
|
+
scopes: data.scopes
|
|
304
|
+
};
|
|
305
|
+
}
|
|
298
306
|
throw new Error("Unexpected login response shape");
|
|
299
307
|
}
|
|
300
308
|
var AuthModule = class {
|
|
301
309
|
constructor(http) {
|
|
302
310
|
this.http = http;
|
|
303
311
|
}
|
|
304
|
-
async login(email, password) {
|
|
312
|
+
async login(email, password, opts) {
|
|
313
|
+
const body = { email, password };
|
|
314
|
+
if (opts?.scopeHint) body.scopeHint = opts.scopeHint;
|
|
305
315
|
const data = await this.http.request(
|
|
306
316
|
"POST",
|
|
307
317
|
"/api/v1/auth/login",
|
|
308
|
-
|
|
318
|
+
body,
|
|
309
319
|
{ skipAutoRefresh: true }
|
|
310
320
|
);
|
|
311
321
|
return parseLoginResponse(data, this.http.isBrowserSession());
|
|
@@ -343,13 +353,29 @@ var AuthModule = class {
|
|
|
343
353
|
method
|
|
344
354
|
}, { skipAutoRefresh: true });
|
|
345
355
|
}
|
|
346
|
-
async selectTenant(tenantSelectionToken, tenantId) {
|
|
356
|
+
async selectTenant(tenantSelectionToken, tenantId, opts) {
|
|
357
|
+
const body = { tenantSelectionToken, tenantId };
|
|
358
|
+
if (opts?.scopeHint) body.scopeHint = opts.scopeHint;
|
|
347
359
|
const data = await this.http.request(
|
|
348
360
|
"POST",
|
|
349
361
|
"/api/v1/auth/select-tenant",
|
|
362
|
+
body,
|
|
363
|
+
{ skipAutoRefresh: true }
|
|
364
|
+
);
|
|
365
|
+
return parseLoginResponse(data, this.http.isBrowserSession());
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Task #171 — redeem a scope-selection token + chosen membership for a
|
|
369
|
+
* real authenticated session. `membershipId` must be one of the scopes
|
|
370
|
+
* returned in the prior `scope_selection` envelope.
|
|
371
|
+
*/
|
|
372
|
+
async selectScope(scopeSelectionToken, membershipId) {
|
|
373
|
+
const data = await this.http.request(
|
|
374
|
+
"POST",
|
|
375
|
+
"/api/v1/auth/select-scope",
|
|
350
376
|
{
|
|
351
|
-
|
|
352
|
-
|
|
377
|
+
scopeSelectionToken,
|
|
378
|
+
membershipId
|
|
353
379
|
},
|
|
354
380
|
{ skipAutoRefresh: true }
|
|
355
381
|
);
|
|
@@ -1992,7 +2018,11 @@ async function buildUserinfoResponse(claims, opts = {}) {
|
|
|
1992
2018
|
tenantId: claims.tenantId,
|
|
1993
2019
|
vendorId: claims.vendorId,
|
|
1994
2020
|
roles: claims.roles ?? [],
|
|
1995
|
-
entitlements: claims.entitlements ?? []
|
|
2021
|
+
entitlements: claims.entitlements ?? [],
|
|
2022
|
+
// Task #171 — project the active source/client scope onto the userinfo
|
|
2023
|
+
// payload so server handlers (`getSessionUser`, `/api/iqauth/userinfo`)
|
|
2024
|
+
// expose it without consumers having to re-decode the JWT.
|
|
2025
|
+
...claims.scopeContext !== void 0 ? { scopeContext: claims.scopeContext } : {}
|
|
1996
2026
|
};
|
|
1997
2027
|
const enriched = opts.enrich ? await opts.enrich(claims) : null;
|
|
1998
2028
|
const user = enriched ? { ...baseUser, ...enriched } : baseUser;
|
|
@@ -2037,19 +2067,62 @@ function shouldClearCookiesOnFailure(policy, status, errorCode) {
|
|
|
2037
2067
|
}
|
|
2038
2068
|
var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
|
|
2039
2069
|
var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
2070
|
+
function assertCookiePrefixInvariants(name, secure, path, domain) {
|
|
2071
|
+
if (name.startsWith("__Host-")) {
|
|
2072
|
+
if (!secure) {
|
|
2073
|
+
throw new IQAuthError(
|
|
2074
|
+
"config_invalid",
|
|
2075
|
+
`Cookie "${name}" uses the __Host- prefix, which browsers only accept on a Secure cookie. Set secure:true (and serve over HTTPS).`
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
if (path !== "/") {
|
|
2079
|
+
throw new IQAuthError(
|
|
2080
|
+
"config_invalid",
|
|
2081
|
+
`Cookie "${name}" uses the __Host- prefix, which requires Path=/ (got "${path}"). Remove cookiePath or set it to "/".`
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2084
|
+
if (domain) {
|
|
2085
|
+
throw new IQAuthError(
|
|
2086
|
+
"config_invalid",
|
|
2087
|
+
`Cookie "${name}" uses the __Host- prefix, which forbids a Domain attribute (the cookie is host-locked). Remove cookieDomain.`
|
|
2088
|
+
);
|
|
2089
|
+
}
|
|
2090
|
+
} else if (name.startsWith("__Secure-") && !secure) {
|
|
2091
|
+
throw new IQAuthError(
|
|
2092
|
+
"config_invalid",
|
|
2093
|
+
`Cookie "${name}" uses the __Secure- prefix, which browsers only accept on a Secure cookie. Set secure:true (and serve over HTTPS).`
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2040
2097
|
function resolve(config) {
|
|
2041
2098
|
const parsed = assertPublishableKey(config.publishableKey, { context: "@iqauth/sdk helpers" });
|
|
2042
2099
|
const inferredIssuer = parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`;
|
|
2100
|
+
maybeWarnDefaultSignoutRegistry(config);
|
|
2101
|
+
const secure = config.secure ?? true;
|
|
2102
|
+
if (config.secure === false && config.allowInsecureCookies !== true) {
|
|
2103
|
+
throw new IQAuthError(
|
|
2104
|
+
"config_invalid",
|
|
2105
|
+
"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."
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
const accessCookieName = config.accessCookieName ?? config.cookieNames?.access ?? "iqauth_at";
|
|
2109
|
+
const refreshCookieName = config.refreshCookieName ?? config.cookieNames?.refresh ?? "iqauth_rt";
|
|
2110
|
+
const stateCookieName = config.stateCookieName ?? "iqauth_state";
|
|
2111
|
+
const cookiePath = config.cookiePath ?? "/";
|
|
2112
|
+
const cookieDomain = config.cookieDomain;
|
|
2113
|
+
for (const name of [accessCookieName, refreshCookieName, stateCookieName]) {
|
|
2114
|
+
assertCookiePrefixInvariants(name, secure, cookiePath, cookieDomain);
|
|
2115
|
+
}
|
|
2043
2116
|
return {
|
|
2044
2117
|
publishableKey: config.publishableKey,
|
|
2045
2118
|
secretKey: config.secretKey,
|
|
2046
2119
|
issuer: (config.issuer ?? inferredIssuer).replace(/\/+$/, ""),
|
|
2047
|
-
accessCookieName
|
|
2048
|
-
refreshCookieName
|
|
2049
|
-
cookieDomain
|
|
2120
|
+
accessCookieName,
|
|
2121
|
+
refreshCookieName,
|
|
2122
|
+
cookieDomain,
|
|
2050
2123
|
sameSite: config.sameSite ?? "lax",
|
|
2051
|
-
secure
|
|
2052
|
-
cookiePath
|
|
2124
|
+
secure,
|
|
2125
|
+
cookiePath,
|
|
2053
2126
|
tokenPath: config.tokenPath ?? "/oidc/token",
|
|
2054
2127
|
refreshPath: config.refreshPath ?? "/api/v1/auth/refresh",
|
|
2055
2128
|
logoutPath: config.logoutPath ?? "/api/v1/auth/logout",
|
|
@@ -2062,9 +2135,19 @@ function resolve(config) {
|
|
|
2062
2135
|
debug: config.debug,
|
|
2063
2136
|
onTimingEvent: config.onTimingEvent,
|
|
2064
2137
|
signoutRegistry: config.signoutRegistry ?? defaultSignoutRegistry,
|
|
2065
|
-
signoutMarkerTtlMs: config.signoutMarkerTtlMs ?? DEFAULT_SIGNOUT_TTL_MS
|
|
2138
|
+
signoutMarkerTtlMs: config.signoutMarkerTtlMs ?? DEFAULT_SIGNOUT_TTL_MS,
|
|
2139
|
+
requireOAuthState: config.requireOAuthState ?? true,
|
|
2140
|
+
stateCookieName: config.stateCookieName ?? "iqauth_state"
|
|
2066
2141
|
};
|
|
2067
2142
|
}
|
|
2143
|
+
function timingSafeEqualStr(a, b) {
|
|
2144
|
+
const len = Math.max(a.length, b.length);
|
|
2145
|
+
let diff = a.length ^ b.length;
|
|
2146
|
+
for (let i = 0; i < len; i++) {
|
|
2147
|
+
diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
|
|
2148
|
+
}
|
|
2149
|
+
return diff === 0;
|
|
2150
|
+
}
|
|
2068
2151
|
function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
2069
2152
|
return {
|
|
2070
2153
|
name,
|
|
@@ -2083,6 +2166,9 @@ function clearCookies(cfg) {
|
|
|
2083
2166
|
{ ...makeCookie(cfg, cfg.refreshCookieName, "", 0), clear: true }
|
|
2084
2167
|
];
|
|
2085
2168
|
}
|
|
2169
|
+
function clearStateCookie(cfg) {
|
|
2170
|
+
return { ...makeCookie(cfg, cfg.stateCookieName, "", 0, false), clear: true };
|
|
2171
|
+
}
|
|
2086
2172
|
var DEFAULT_SIGNOUT_TTL_MS = 6e4;
|
|
2087
2173
|
var inMemorySignoutMarkers = /* @__PURE__ */ new Map();
|
|
2088
2174
|
function pruneInMemoryMarkers(now) {
|
|
@@ -2108,6 +2194,15 @@ var defaultSignoutRegistry = {
|
|
|
2108
2194
|
return true;
|
|
2109
2195
|
}
|
|
2110
2196
|
};
|
|
2197
|
+
var warnedDefaultSignoutRegistry = false;
|
|
2198
|
+
function maybeWarnDefaultSignoutRegistry(config) {
|
|
2199
|
+
if (warnedDefaultSignoutRegistry) return;
|
|
2200
|
+
if (config.signoutRegistry) return;
|
|
2201
|
+
warnedDefaultSignoutRegistry = true;
|
|
2202
|
+
console.warn(
|
|
2203
|
+
"[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."
|
|
2204
|
+
);
|
|
2205
|
+
}
|
|
2111
2206
|
function serializeCookie(d) {
|
|
2112
2207
|
const parts = [`${d.name}=${encodeURIComponent(d.value)}`];
|
|
2113
2208
|
parts.push(`Path=${d.path}`);
|
|
@@ -2130,6 +2225,23 @@ async function handleCallback(config, input) {
|
|
|
2130
2225
|
cookies: []
|
|
2131
2226
|
};
|
|
2132
2227
|
}
|
|
2228
|
+
const provided = input.state;
|
|
2229
|
+
const expected = input.expectedState;
|
|
2230
|
+
const stateOk = cfg.requireOAuthState ? !!expected && !!provided && timingSafeEqualStr(provided, expected) : !expected || !!provided && timingSafeEqualStr(provided, expected);
|
|
2231
|
+
if (!stateOk) {
|
|
2232
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "STATE_MISMATCH" });
|
|
2233
|
+
return {
|
|
2234
|
+
status: 400,
|
|
2235
|
+
body: {
|
|
2236
|
+
success: false,
|
|
2237
|
+
error: {
|
|
2238
|
+
code: "STATE_MISMATCH",
|
|
2239
|
+
message: "OAuth state validation failed; the sign-in could not be verified as originating from this browser."
|
|
2240
|
+
}
|
|
2241
|
+
},
|
|
2242
|
+
cookies: [clearStateCookie(cfg)]
|
|
2243
|
+
};
|
|
2244
|
+
}
|
|
2133
2245
|
if (!cfg.secretKey) {
|
|
2134
2246
|
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "INTERNAL_ERROR" });
|
|
2135
2247
|
return {
|
|
@@ -2168,6 +2280,26 @@ async function handleCallback(config, input) {
|
|
|
2168
2280
|
cookies: []
|
|
2169
2281
|
};
|
|
2170
2282
|
}
|
|
2283
|
+
try {
|
|
2284
|
+
await getTokensFor(cfg.issuer).verify(json.access_token, {
|
|
2285
|
+
issuer: cfg.issuer,
|
|
2286
|
+
...config.verify
|
|
2287
|
+
});
|
|
2288
|
+
} catch (err) {
|
|
2289
|
+
const code = err instanceof IQAuthError ? err.code : err.code || "TOKEN_INVALID";
|
|
2290
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code });
|
|
2291
|
+
return {
|
|
2292
|
+
status: 502,
|
|
2293
|
+
body: {
|
|
2294
|
+
success: false,
|
|
2295
|
+
error: {
|
|
2296
|
+
code: "ACCESS_TOKEN_VERIFICATION_FAILED",
|
|
2297
|
+
message: "The issuer returned an access token that failed verification; no session was established."
|
|
2298
|
+
}
|
|
2299
|
+
},
|
|
2300
|
+
cookies: []
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2171
2303
|
const cookies = [];
|
|
2172
2304
|
cookies.push(
|
|
2173
2305
|
makeCookie(cfg, cfg.accessCookieName, json.access_token, json.expires_in ?? ACCESS_TOKEN_TTL_SECONDS)
|
|
@@ -2175,6 +2307,7 @@ async function handleCallback(config, input) {
|
|
|
2175
2307
|
if (json.refresh_token) {
|
|
2176
2308
|
cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.refresh_token, REFRESH_TOKEN_TTL_SECONDS));
|
|
2177
2309
|
}
|
|
2310
|
+
cookies.push(clearStateCookie(cfg));
|
|
2178
2311
|
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: true });
|
|
2179
2312
|
return {
|
|
2180
2313
|
status: 200,
|
|
@@ -2296,7 +2429,10 @@ async function handleUserinfo(config, input) {
|
|
|
2296
2429
|
}
|
|
2297
2430
|
let claims;
|
|
2298
2431
|
try {
|
|
2299
|
-
claims = await getTokensFor(cfg.issuer).verify(input.accessToken,
|
|
2432
|
+
claims = await getTokensFor(cfg.issuer).verify(input.accessToken, {
|
|
2433
|
+
issuer: cfg.issuer,
|
|
2434
|
+
...config.verify
|
|
2435
|
+
});
|
|
2300
2436
|
} catch (err) {
|
|
2301
2437
|
const code = err instanceof IQAuthError ? err.code : err.code || "TOKEN_INVALID";
|
|
2302
2438
|
const message = err instanceof Error ? err.message : "Access token verification failed";
|
|
@@ -2316,7 +2452,44 @@ async function handleUserinfo(config, input) {
|
|
|
2316
2452
|
};
|
|
2317
2453
|
}
|
|
2318
2454
|
|
|
2455
|
+
// src/browser/returnTo.ts
|
|
2456
|
+
function normalizeOrigin(o) {
|
|
2457
|
+
try {
|
|
2458
|
+
return new URL(o).origin;
|
|
2459
|
+
} catch {
|
|
2460
|
+
return o.replace(/\/+$/, "");
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
function sanitizeReturnTo(input, options = {}) {
|
|
2464
|
+
const fallback = options.fallback ?? "/";
|
|
2465
|
+
if (!input || typeof input !== "string") return fallback;
|
|
2466
|
+
const trimmed = input.trim();
|
|
2467
|
+
if (!trimmed) return fallback;
|
|
2468
|
+
if (trimmed.includes("\\")) return fallback;
|
|
2469
|
+
if (trimmed.startsWith("//")) return fallback;
|
|
2470
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("#") || trimmed.startsWith("?")) {
|
|
2471
|
+
return trimmed;
|
|
2472
|
+
}
|
|
2473
|
+
if (!/^[a-z][a-z0-9+\-.]*:/i.test(trimmed)) {
|
|
2474
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
2475
|
+
}
|
|
2476
|
+
let parsed;
|
|
2477
|
+
try {
|
|
2478
|
+
parsed = new URL(trimmed);
|
|
2479
|
+
} catch {
|
|
2480
|
+
return fallback;
|
|
2481
|
+
}
|
|
2482
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return fallback;
|
|
2483
|
+
const currentOrigin = options.currentOrigin ?? (typeof window !== "undefined" ? window.location.origin : "");
|
|
2484
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
2485
|
+
if (currentOrigin) allowed.add(normalizeOrigin(currentOrigin));
|
|
2486
|
+
for (const o of options.allowedOrigins ?? []) allowed.add(normalizeOrigin(o));
|
|
2487
|
+
if (allowed.has(parsed.origin)) return parsed.toString();
|
|
2488
|
+
return fallback;
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2319
2491
|
// src/hono.ts
|
|
2492
|
+
var PKCE_COOKIE = "iqauth_pkce";
|
|
2320
2493
|
var KNOWN_AUTH_ERRORS = /* @__PURE__ */ new Set([
|
|
2321
2494
|
"TOKEN_INVALID",
|
|
2322
2495
|
"TOKEN_EXPIRED",
|
|
@@ -2348,6 +2521,36 @@ function honoResponse(hr) {
|
|
|
2348
2521
|
for (const c of hr.cookies) headers.append("set-cookie", serializeCookie(c));
|
|
2349
2522
|
return new Response(JSON.stringify(hr.body), { status: hr.status, headers });
|
|
2350
2523
|
}
|
|
2524
|
+
function honoCallbackResponse(hr, requestOrigin, returnToCookieValue, returnToCookieName) {
|
|
2525
|
+
const returnTo = sanitizeReturnTo(
|
|
2526
|
+
returnToCookieValue || hr.body?.returnTo,
|
|
2527
|
+
{ currentOrigin: requestOrigin, fallback: "/" }
|
|
2528
|
+
);
|
|
2529
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
2530
|
+
for (const c of hr.cookies) headers.append("set-cookie", serializeCookie(c));
|
|
2531
|
+
if (hr.status < 400) {
|
|
2532
|
+
headers.append("set-cookie", `${returnToCookieName}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
2533
|
+
}
|
|
2534
|
+
const body = { ...hr.body, returnTo };
|
|
2535
|
+
return new Response(JSON.stringify(body), { status: hr.status, headers });
|
|
2536
|
+
}
|
|
2537
|
+
function honoCallbackRedirect(hr, requestOrigin, returnToCookieValue, cookieNames) {
|
|
2538
|
+
const headers = new Headers();
|
|
2539
|
+
for (const c of hr.cookies) headers.append("set-cookie", serializeCookie(c));
|
|
2540
|
+
headers.append("set-cookie", `${cookieNames.state}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
2541
|
+
headers.append("set-cookie", `${cookieNames.pkce}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
2542
|
+
if (hr.status >= 400) {
|
|
2543
|
+
headers.set("location", "/");
|
|
2544
|
+
return new Response(null, { status: 302, headers });
|
|
2545
|
+
}
|
|
2546
|
+
const dest = sanitizeReturnTo(returnToCookieValue, {
|
|
2547
|
+
currentOrigin: requestOrigin,
|
|
2548
|
+
fallback: "/"
|
|
2549
|
+
});
|
|
2550
|
+
headers.append("set-cookie", `${cookieNames.returnTo}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
2551
|
+
headers.set("location", dest);
|
|
2552
|
+
return new Response(null, { status: 302, headers });
|
|
2553
|
+
}
|
|
2351
2554
|
function iqAuth(options) {
|
|
2352
2555
|
const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/hono" });
|
|
2353
2556
|
const issuer = (options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
|
|
@@ -2355,6 +2558,7 @@ function iqAuth(options) {
|
|
|
2355
2558
|
const client = new IQAuthClient({ baseUrl: issuer, environment: "server" });
|
|
2356
2559
|
const accessCookie = options.accessCookieName ?? "iqauth_at";
|
|
2357
2560
|
const refreshCookie = options.refreshCookieName ?? "iqauth_rt";
|
|
2561
|
+
const returnToCookie = options.returnToCookieName ?? "iqauth_return_to";
|
|
2358
2562
|
const mount = (options.mountPath ?? "/api/iqauth").replace(/\/+$/, "");
|
|
2359
2563
|
const mountHelpers = options.mountHelperRoutes !== false;
|
|
2360
2564
|
const isPublic = (p) => {
|
|
@@ -2370,15 +2574,35 @@ function iqAuth(options) {
|
|
|
2370
2574
|
const accessToken = auth2 && auth2.replace(/^Bearer /i, "") || readCookieFromHeader(c.req.header("cookie"), accessCookie);
|
|
2371
2575
|
return honoResponse(await handleUserinfo(helperConfig, { accessToken, req: c.req }));
|
|
2372
2576
|
}
|
|
2577
|
+
if (mountHelpers && path === `${mount}/callback` && c.req.method === "GET") {
|
|
2578
|
+
const cookieHeader = c.req.header("cookie");
|
|
2579
|
+
const stateCookie = helperConfig.stateCookieName ?? "iqauth_state";
|
|
2580
|
+
const hr = await handleCallback(helperConfig, {
|
|
2581
|
+
code: url.searchParams.get("code") ?? void 0,
|
|
2582
|
+
codeVerifier: readCookieFromHeader(cookieHeader, PKCE_COOKIE),
|
|
2583
|
+
redirectUri: `${url.origin}${url.pathname}`,
|
|
2584
|
+
state: url.searchParams.get("state") ?? void 0,
|
|
2585
|
+
expectedState: readCookieFromHeader(cookieHeader, stateCookie)
|
|
2586
|
+
});
|
|
2587
|
+
return honoCallbackRedirect(hr, url.origin, readCookieFromHeader(cookieHeader, returnToCookie), {
|
|
2588
|
+
returnTo: returnToCookie,
|
|
2589
|
+
state: stateCookie,
|
|
2590
|
+
pkce: PKCE_COOKIE
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2373
2593
|
if (mountHelpers && path.startsWith(mount + "/") && c.req.method === "POST") {
|
|
2374
2594
|
const body = await c.req.json().catch(() => ({}));
|
|
2375
2595
|
const cookieHeader = c.req.header("cookie");
|
|
2376
2596
|
if (path === `${mount}/callback`) {
|
|
2377
|
-
|
|
2597
|
+
const hr = await handleCallback(helperConfig, {
|
|
2378
2598
|
code: body.code,
|
|
2379
2599
|
codeVerifier: body.codeVerifier,
|
|
2380
|
-
redirectUri: body.redirectUri
|
|
2381
|
-
|
|
2600
|
+
redirectUri: body.redirectUri,
|
|
2601
|
+
// M-2: bind callback to this browser; handleCallback fails closed.
|
|
2602
|
+
state: body.state,
|
|
2603
|
+
expectedState: readCookieFromHeader(cookieHeader, helperConfig.stateCookieName ?? "iqauth_state")
|
|
2604
|
+
});
|
|
2605
|
+
return honoCallbackResponse(hr, url.origin, readCookieFromHeader(cookieHeader, returnToCookie), returnToCookie);
|
|
2382
2606
|
}
|
|
2383
2607
|
if (path === `${mount}/refresh`) {
|
|
2384
2608
|
const refreshToken = body.refreshToken || readCookieFromHeader(cookieHeader, refreshCookie);
|
package/dist/hono.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/hono.ts
|
|
24
|
+
var PKCE_COOKIE = "iqauth_pkce";
|
|
21
25
|
var KNOWN_AUTH_ERRORS = /* @__PURE__ */ new Set([
|
|
22
26
|
"TOKEN_INVALID",
|
|
23
27
|
"TOKEN_EXPIRED",
|
|
@@ -49,6 +53,36 @@ function honoResponse(hr) {
|
|
|
49
53
|
for (const c of hr.cookies) headers.append("set-cookie", serializeCookie(c));
|
|
50
54
|
return new Response(JSON.stringify(hr.body), { status: hr.status, headers });
|
|
51
55
|
}
|
|
56
|
+
function honoCallbackResponse(hr, requestOrigin, returnToCookieValue, returnToCookieName) {
|
|
57
|
+
const returnTo = sanitizeReturnTo(
|
|
58
|
+
returnToCookieValue || hr.body?.returnTo,
|
|
59
|
+
{ currentOrigin: requestOrigin, fallback: "/" }
|
|
60
|
+
);
|
|
61
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
62
|
+
for (const c of hr.cookies) headers.append("set-cookie", serializeCookie(c));
|
|
63
|
+
if (hr.status < 400) {
|
|
64
|
+
headers.append("set-cookie", `${returnToCookieName}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
65
|
+
}
|
|
66
|
+
const body = { ...hr.body, returnTo };
|
|
67
|
+
return new Response(JSON.stringify(body), { status: hr.status, headers });
|
|
68
|
+
}
|
|
69
|
+
function honoCallbackRedirect(hr, requestOrigin, returnToCookieValue, cookieNames) {
|
|
70
|
+
const headers = new Headers();
|
|
71
|
+
for (const c of hr.cookies) headers.append("set-cookie", serializeCookie(c));
|
|
72
|
+
headers.append("set-cookie", `${cookieNames.state}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
73
|
+
headers.append("set-cookie", `${cookieNames.pkce}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
74
|
+
if (hr.status >= 400) {
|
|
75
|
+
headers.set("location", "/");
|
|
76
|
+
return new Response(null, { status: 302, headers });
|
|
77
|
+
}
|
|
78
|
+
const dest = sanitizeReturnTo(returnToCookieValue, {
|
|
79
|
+
currentOrigin: requestOrigin,
|
|
80
|
+
fallback: "/"
|
|
81
|
+
});
|
|
82
|
+
headers.append("set-cookie", `${cookieNames.returnTo}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
83
|
+
headers.set("location", dest);
|
|
84
|
+
return new Response(null, { status: 302, headers });
|
|
85
|
+
}
|
|
52
86
|
function iqAuth(options) {
|
|
53
87
|
const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/hono" });
|
|
54
88
|
const issuer = (options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
|
|
@@ -56,6 +90,7 @@ function iqAuth(options) {
|
|
|
56
90
|
const client = new IQAuthClient({ baseUrl: issuer, environment: "server" });
|
|
57
91
|
const accessCookie = options.accessCookieName ?? "iqauth_at";
|
|
58
92
|
const refreshCookie = options.refreshCookieName ?? "iqauth_rt";
|
|
93
|
+
const returnToCookie = options.returnToCookieName ?? "iqauth_return_to";
|
|
59
94
|
const mount = (options.mountPath ?? "/api/iqauth").replace(/\/+$/, "");
|
|
60
95
|
const mountHelpers = options.mountHelperRoutes !== false;
|
|
61
96
|
const isPublic = (p) => {
|
|
@@ -71,15 +106,35 @@ function iqAuth(options) {
|
|
|
71
106
|
const accessToken = auth2 && auth2.replace(/^Bearer /i, "") || readCookieFromHeader(c.req.header("cookie"), accessCookie);
|
|
72
107
|
return honoResponse(await handleUserinfo(helperConfig, { accessToken, req: c.req }));
|
|
73
108
|
}
|
|
109
|
+
if (mountHelpers && path === `${mount}/callback` && c.req.method === "GET") {
|
|
110
|
+
const cookieHeader = c.req.header("cookie");
|
|
111
|
+
const stateCookie = helperConfig.stateCookieName ?? "iqauth_state";
|
|
112
|
+
const hr = await handleCallback(helperConfig, {
|
|
113
|
+
code: url.searchParams.get("code") ?? void 0,
|
|
114
|
+
codeVerifier: readCookieFromHeader(cookieHeader, PKCE_COOKIE),
|
|
115
|
+
redirectUri: `${url.origin}${url.pathname}`,
|
|
116
|
+
state: url.searchParams.get("state") ?? void 0,
|
|
117
|
+
expectedState: readCookieFromHeader(cookieHeader, stateCookie)
|
|
118
|
+
});
|
|
119
|
+
return honoCallbackRedirect(hr, url.origin, readCookieFromHeader(cookieHeader, returnToCookie), {
|
|
120
|
+
returnTo: returnToCookie,
|
|
121
|
+
state: stateCookie,
|
|
122
|
+
pkce: PKCE_COOKIE
|
|
123
|
+
});
|
|
124
|
+
}
|
|
74
125
|
if (mountHelpers && path.startsWith(mount + "/") && c.req.method === "POST") {
|
|
75
126
|
const body = await c.req.json().catch(() => ({}));
|
|
76
127
|
const cookieHeader = c.req.header("cookie");
|
|
77
128
|
if (path === `${mount}/callback`) {
|
|
78
|
-
|
|
129
|
+
const hr = await handleCallback(helperConfig, {
|
|
79
130
|
code: body.code,
|
|
80
131
|
codeVerifier: body.codeVerifier,
|
|
81
|
-
redirectUri: body.redirectUri
|
|
82
|
-
|
|
132
|
+
redirectUri: body.redirectUri,
|
|
133
|
+
// M-2: bind callback to this browser; handleCallback fails closed.
|
|
134
|
+
state: body.state,
|
|
135
|
+
expectedState: readCookieFromHeader(cookieHeader, helperConfig.stateCookieName ?? "iqauth_state")
|
|
136
|
+
});
|
|
137
|
+
return honoCallbackResponse(hr, url.origin, readCookieFromHeader(cookieHeader, returnToCookie), returnToCookie);
|
|
83
138
|
}
|
|
84
139
|
if (path === `${mount}/refresh`) {
|
|
85
140
|
const refreshToken = body.refreshToken || readCookieFromHeader(cookieHeader, refreshCookie);
|