@iqauth/sdk 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/browser-session.d.mts +1 -2
- package/dist/browser-session.d.ts +1 -2
- package/dist/browser-session.js +89 -68
- package/dist/browser-session.mjs +2 -1
- package/dist/browser.d.mts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +13 -2
- package/dist/browser.mjs +2 -2
- package/dist/{chunk-D72UL5HL.mjs → chunk-EKTNEZIH.mjs} +4 -4
- package/dist/{chunk-M4J6BPK7.mjs → chunk-KGEPDXHU.mjs} +10 -1
- package/dist/{chunk-QZB745C2.mjs → chunk-RACIPVLD.mjs} +13 -2
- package/dist/chunk-UNYDG2L4.mjs +209 -0
- package/dist/{chunk-MDUHPQMM.mjs → chunk-W3F4JYGP.mjs} +8 -180
- package/dist/{chunk-QEJB7WEQ.mjs → chunk-WQWBJSSS.mjs} +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/{client-DXbHb2ul.d.ts → client-DTX4hNdS.d.ts} +16 -21
- package/dist/{client-Dv4v92Mj.d.mts → client-vdh2a9fJ.d.mts} +16 -21
- package/dist/{doctor-XCI77BQS.mjs → doctor-A5E7LSFW.mjs} +1 -1
- package/dist/{express-BZmF1llh.d.mts → express-A0-dWEMy.d.mts} +1 -1
- package/dist/{express-B4o3P8vK.d.ts → express-Bo_pJKHN.d.ts} +1 -1
- package/dist/express.d.mts +75 -5
- package/dist/express.d.ts +75 -5
- package/dist/express.js +300 -70
- package/dist/express.mjs +208 -7
- package/dist/fastify.js +101 -70
- package/dist/fastify.mjs +8 -6
- package/dist/hono.js +100 -70
- package/dist/hono.mjs +7 -6
- package/dist/index.d.mts +2 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.js +90 -69
- package/dist/index.mjs +15 -13
- package/dist/mobile.d.mts +1 -2
- package/dist/mobile.d.ts +1 -2
- package/dist/mobile.js +89 -68
- package/dist/mobile.mjs +2 -1
- package/dist/next.d.mts +9 -0
- package/dist/next.d.ts +9 -0
- package/dist/next.js +99 -1616
- package/dist/next.mjs +9 -9
- package/dist/react.d.mts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +13 -2
- package/dist/react.mjs +2 -2
- package/dist/server/handlers.d.mts +2 -0
- package/dist/server/handlers.d.ts +2 -0
- package/dist/server/handlers.js +10 -1
- package/dist/server/handlers.mjs +2 -2
- package/dist/server.d.mts +2 -3
- package/dist/server.d.ts +2 -3
- package/dist/server.js +99 -69
- package/dist/server.mjs +7 -6
- package/dist/service.d.mts +1 -2
- package/dist/service.d.ts +1 -2
- package/dist/service.js +89 -68
- package/dist/service.mjs +2 -1
- package/dist/{signIn-D_kP3v-c.d.mts → signIn-Cd0P4y9d.d.mts} +8 -0
- package/dist/{signIn-BVDTIA_t.d.ts → signIn-DKakyzeu.d.ts} +8 -0
- package/package.json +3 -2
package/dist/express.mjs
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_REFRESH_COOKIE,
|
|
3
3
|
iqAuthMiddleware
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EKTNEZIH.mjs";
|
|
5
|
+
import {
|
|
6
|
+
IQAuthClient
|
|
7
|
+
} from "./chunk-W3F4JYGP.mjs";
|
|
5
8
|
import {
|
|
6
9
|
handleCallback,
|
|
7
10
|
handleRefresh,
|
|
8
11
|
handleSignout
|
|
9
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-KGEPDXHU.mjs";
|
|
10
13
|
import {
|
|
11
14
|
assertPublishableKey
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import
|
|
14
|
-
IQAuthClient
|
|
15
|
-
} from "./chunk-MDUHPQMM.mjs";
|
|
15
|
+
} from "./chunk-WQWBJSSS.mjs";
|
|
16
|
+
import "./chunk-UNYDG2L4.mjs";
|
|
16
17
|
import {
|
|
17
18
|
ErrorCodes,
|
|
18
19
|
IQAuthError
|
|
@@ -20,6 +21,78 @@ import {
|
|
|
20
21
|
import "./chunk-Y6FXYEAI.mjs";
|
|
21
22
|
|
|
22
23
|
// src/express.ts
|
|
24
|
+
var PKCE_COOKIE = "iqauth_pkce";
|
|
25
|
+
function escapeHtml(s) {
|
|
26
|
+
return s.replace(/[&<>"']/g, (c) => {
|
|
27
|
+
switch (c) {
|
|
28
|
+
case "&":
|
|
29
|
+
return "&";
|
|
30
|
+
case "<":
|
|
31
|
+
return "<";
|
|
32
|
+
case ">":
|
|
33
|
+
return ">";
|
|
34
|
+
case '"':
|
|
35
|
+
return """;
|
|
36
|
+
case "'":
|
|
37
|
+
return "'";
|
|
38
|
+
default:
|
|
39
|
+
return c;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function appendErrorParam(path, errorCode) {
|
|
44
|
+
const sep = path.includes("?") ? "&" : "?";
|
|
45
|
+
return `${path}${sep}error=${encodeURIComponent(errorCode)}`;
|
|
46
|
+
}
|
|
47
|
+
function defaultBrandedSpinner(args) {
|
|
48
|
+
return `<!doctype html>
|
|
49
|
+
<html lang="en">
|
|
50
|
+
<head>
|
|
51
|
+
<meta charset="utf-8" />
|
|
52
|
+
<title>Signing you in\u2026</title>
|
|
53
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
54
|
+
<style>
|
|
55
|
+
body { margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center; font-family: system-ui, -apple-system, "Segoe UI", sans-serif; background:#f7f7f8; color:#111; }
|
|
56
|
+
.iqauth-card { text-align:center; padding:2rem; }
|
|
57
|
+
.iqauth-spinner { width:36px; height:36px; border:3px solid #e5e7eb; border-top-color:#111; border-radius:50%; margin:0 auto 1rem; animation:iqauth-spin 0.9s linear infinite; }
|
|
58
|
+
@keyframes iqauth-spin { to { transform: rotate(360deg); } }
|
|
59
|
+
.iqauth-msg { font-size:0.95rem; color:#374151; }
|
|
60
|
+
</style>
|
|
61
|
+
</head>
|
|
62
|
+
<body>
|
|
63
|
+
<div class="iqauth-card" data-testid="iqauth-inline-callback-spinner">
|
|
64
|
+
<div class="iqauth-spinner" aria-hidden="true"></div>
|
|
65
|
+
<div class="iqauth-msg">Signing you in\u2026</div>
|
|
66
|
+
</div>
|
|
67
|
+
<script>
|
|
68
|
+
(function(){
|
|
69
|
+
var code = ${JSON.stringify(args.code)};
|
|
70
|
+
var state = ${JSON.stringify(args.state)};
|
|
71
|
+
var errorPath = ${JSON.stringify(args.errorPath || "")};
|
|
72
|
+
function fail(reason){
|
|
73
|
+
if (errorPath) { window.location.replace(errorPath + (errorPath.indexOf("?")>=0?"&":"?") + "error=" + encodeURIComponent(reason)); return; }
|
|
74
|
+
window.location.replace("/");
|
|
75
|
+
}
|
|
76
|
+
var verifier = (document.cookie.split('; ').find(function(c){return c.indexOf('${PKCE_COOKIE}=')===0;})||'').slice(${PKCE_COOKIE.length + 1});
|
|
77
|
+
try { verifier = decodeURIComponent(verifier); } catch (e) {}
|
|
78
|
+
fetch(${JSON.stringify(args.exchangePath)}, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
credentials: "include",
|
|
81
|
+
headers: { "Content-Type": "application/json" },
|
|
82
|
+
body: JSON.stringify({ code: code, state: state, codeVerifier: verifier, redirectUri: window.location.origin + window.location.pathname })
|
|
83
|
+
}).then(function(r){ return r.json().then(function(j){ return { status:r.status, body:j }; }); })
|
|
84
|
+
.then(function(out){
|
|
85
|
+
if (out.status >= 400) { fail((out.body && out.body.error && out.body.error.code) || "exchange_failed"); return; }
|
|
86
|
+
var dest = (out.body && out.body.returnTo) || sessionStorage.getItem("iqauth_return_to") || "/";
|
|
87
|
+
sessionStorage.removeItem("iqauth_return_to");
|
|
88
|
+
window.location.replace(dest);
|
|
89
|
+
})
|
|
90
|
+
.catch(function(){ fail("network_error"); });
|
|
91
|
+
})();
|
|
92
|
+
</script>
|
|
93
|
+
</body>
|
|
94
|
+
</html>`;
|
|
95
|
+
}
|
|
23
96
|
function applyHandlerResponse(res, hr) {
|
|
24
97
|
for (const c of hr.cookies) {
|
|
25
98
|
if (typeof res.cookie === "function") {
|
|
@@ -83,6 +156,8 @@ function iqAuth(options) {
|
|
|
83
156
|
if (mountHelpers && path.startsWith(mount + "/")) return next();
|
|
84
157
|
return verify(req, res, next);
|
|
85
158
|
};
|
|
159
|
+
const inline = options.inlineCallback === true ? {} : options.inlineCallback && typeof options.inlineCallback === "object" ? options.inlineCallback : null;
|
|
160
|
+
const inlineBranded = inline?.branded === true ? {} : inline?.branded && typeof inline.branded === "object" ? inline.branded : null;
|
|
86
161
|
const attachHelpers = (app) => {
|
|
87
162
|
app.post(`${mount}/callback`, async (req, res) => {
|
|
88
163
|
const body = readBody(req);
|
|
@@ -93,6 +168,131 @@ function iqAuth(options) {
|
|
|
93
168
|
});
|
|
94
169
|
applyHandlerResponse(res, hr);
|
|
95
170
|
});
|
|
171
|
+
if (inline && typeof app.get === "function") {
|
|
172
|
+
const callbackPath = `${mount}/callback`;
|
|
173
|
+
const exchangePath = `${callbackPath}/exchange`;
|
|
174
|
+
const stateCookie = inline.stateCookieName ?? "iqauth_state";
|
|
175
|
+
const returnToCookie = inline.returnToCookieName ?? "iqauth_return_to";
|
|
176
|
+
const errorPath = inline.errorPath;
|
|
177
|
+
const clearCookie = (res, name) => {
|
|
178
|
+
if (typeof res.clearCookie === "function") {
|
|
179
|
+
res.clearCookie(name, { path: "/" });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const existing = res.getHeader?.("Set-Cookie") || [];
|
|
183
|
+
const list = Array.isArray(existing) ? existing : [existing];
|
|
184
|
+
list.push(`${name}=; Path=/; Max-Age=0; SameSite=Lax`);
|
|
185
|
+
res.setHeader?.("Set-Cookie", list);
|
|
186
|
+
};
|
|
187
|
+
const failPlain = (res, errorCode, fallback) => {
|
|
188
|
+
if (errorPath) {
|
|
189
|
+
const dest = appendErrorParam(errorPath, errorCode);
|
|
190
|
+
if (typeof res.redirect === "function") return res.redirect(302, dest);
|
|
191
|
+
res.status(302);
|
|
192
|
+
res.setHeader?.("Location", dest);
|
|
193
|
+
return res.end?.();
|
|
194
|
+
}
|
|
195
|
+
fallback();
|
|
196
|
+
};
|
|
197
|
+
if (inlineBranded) {
|
|
198
|
+
const render = inlineBranded.render ?? defaultBrandedSpinner;
|
|
199
|
+
app.get(callbackPath, (req, res) => {
|
|
200
|
+
const q = req.query || {};
|
|
201
|
+
const html = render({
|
|
202
|
+
issuer,
|
|
203
|
+
exchangePath,
|
|
204
|
+
code: escapeHtml(q.code ?? ""),
|
|
205
|
+
state: escapeHtml(q.state ?? ""),
|
|
206
|
+
errorPath: errorPath ?? ""
|
|
207
|
+
});
|
|
208
|
+
res.status(200);
|
|
209
|
+
if (typeof res.set === "function") res.set("Content-Type", "text/html; charset=utf-8");
|
|
210
|
+
else if (typeof res.setHeader === "function") res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
211
|
+
if (typeof res.send === "function") res.send(html);
|
|
212
|
+
else res.end?.(html);
|
|
213
|
+
});
|
|
214
|
+
app.post(exchangePath, async (req, res) => {
|
|
215
|
+
const body = readBody(req);
|
|
216
|
+
const stateFromBody = body.state || void 0;
|
|
217
|
+
const stateFromCookie = readCookieFromReq(req, stateCookie);
|
|
218
|
+
if (stateFromCookie && stateFromBody !== stateFromCookie) {
|
|
219
|
+
clearCookie(res, stateCookie);
|
|
220
|
+
res.status(400);
|
|
221
|
+
return res.json ? res.json({ success: false, error: { code: "STATE_MISMATCH", message: "OAuth state mismatch" } }) : res.end?.(JSON.stringify({ success: false, error: { code: "STATE_MISMATCH", message: "OAuth state mismatch" } }));
|
|
222
|
+
}
|
|
223
|
+
const hr = await handleCallback(helperConfig, {
|
|
224
|
+
code: body.code,
|
|
225
|
+
codeVerifier: body.codeVerifier || readCookieFromReq(req, PKCE_COOKIE) || "",
|
|
226
|
+
redirectUri: body.redirectUri
|
|
227
|
+
});
|
|
228
|
+
clearCookie(res, stateCookie);
|
|
229
|
+
clearCookie(res, PKCE_COOKIE);
|
|
230
|
+
const returnTo = readCookieFromReq(req, returnToCookie) || hr.body?.returnTo || "/";
|
|
231
|
+
if (hr.status < 400) clearCookie(res, returnToCookie);
|
|
232
|
+
const enriched = {
|
|
233
|
+
...hr,
|
|
234
|
+
body: { ...hr.body, returnTo }
|
|
235
|
+
};
|
|
236
|
+
applyHandlerResponse(res, enriched);
|
|
237
|
+
});
|
|
238
|
+
} else {
|
|
239
|
+
app.get(callbackPath, async (req, res) => {
|
|
240
|
+
const q = req.query || {};
|
|
241
|
+
const code = q.code;
|
|
242
|
+
if (!code) {
|
|
243
|
+
return failPlain(res, "missing_code", () => {
|
|
244
|
+
res.status(400);
|
|
245
|
+
if (res.json) res.json({ success: false, error: { code: "MISSING_CODE", message: "Missing authorization code" } });
|
|
246
|
+
else res.end?.("Missing authorization code");
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
const stateFromQuery = q.state;
|
|
250
|
+
const stateFromCookie = readCookieFromReq(req, stateCookie);
|
|
251
|
+
if (stateFromCookie && stateFromQuery !== stateFromCookie) {
|
|
252
|
+
clearCookie(res, stateCookie);
|
|
253
|
+
return failPlain(res, "state_mismatch", () => {
|
|
254
|
+
res.status(400);
|
|
255
|
+
if (res.json) res.json({ success: false, error: { code: "STATE_MISMATCH", message: "OAuth state mismatch" } });
|
|
256
|
+
else res.end?.("OAuth state mismatch");
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const codeVerifier = readCookieFromReq(req, PKCE_COOKIE) || "";
|
|
260
|
+
const proto = req.headers?.["x-forwarded-proto"] || req.protocol || "https";
|
|
261
|
+
const host = req.headers?.["x-forwarded-host"] || req.headers?.host || "";
|
|
262
|
+
const redirectUri = `${proto}://${host}${callbackPath}`;
|
|
263
|
+
const hr = await handleCallback(helperConfig, { code, codeVerifier, redirectUri });
|
|
264
|
+
for (const c of hr.cookies) {
|
|
265
|
+
if (typeof res.cookie === "function") {
|
|
266
|
+
const opts = {
|
|
267
|
+
httpOnly: c.httpOnly,
|
|
268
|
+
secure: c.secure,
|
|
269
|
+
sameSite: c.sameSite,
|
|
270
|
+
path: c.path,
|
|
271
|
+
maxAge: c.maxAge * 1e3
|
|
272
|
+
};
|
|
273
|
+
if (c.domain) opts.domain = c.domain;
|
|
274
|
+
res.cookie(c.name, c.value, opts);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
clearCookie(res, stateCookie);
|
|
278
|
+
clearCookie(res, PKCE_COOKIE);
|
|
279
|
+
if (hr.status >= 400) {
|
|
280
|
+
const code2 = hr.body?.error?.code || "exchange_failed";
|
|
281
|
+
return failPlain(res, code2, () => {
|
|
282
|
+
res.status(hr.status);
|
|
283
|
+
if (res.json) res.json(hr.body);
|
|
284
|
+
else res.end?.(JSON.stringify(hr.body));
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const returnTo = readCookieFromReq(req, returnToCookie) || hr.body?.returnTo || "/";
|
|
288
|
+
clearCookie(res, returnToCookie);
|
|
289
|
+
if (typeof res.redirect === "function") return res.redirect(302, returnTo);
|
|
290
|
+
res.status(302);
|
|
291
|
+
if (typeof res.setHeader === "function") res.setHeader("Location", returnTo);
|
|
292
|
+
res.end?.();
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
96
296
|
app.post(`${mount}/refresh`, async (req, res) => {
|
|
97
297
|
const body = readBody(req);
|
|
98
298
|
const refreshToken = body.refreshToken || readCookieFromReq(req, refreshCookie);
|
|
@@ -101,7 +301,8 @@ function iqAuth(options) {
|
|
|
101
301
|
});
|
|
102
302
|
app.post(`${mount}/signout`, async (req, res) => {
|
|
103
303
|
const accessToken = req.headers?.authorization?.replace(/^Bearer /i, "") || readCookieFromReq(req, accessCookie);
|
|
104
|
-
const
|
|
304
|
+
const ssoCookieHeader = req.headers?.cookie;
|
|
305
|
+
const hr = await handleSignout(helperConfig, { accessToken, ssoCookieHeader });
|
|
105
306
|
applyHandlerResponse(res, hr);
|
|
106
307
|
});
|
|
107
308
|
};
|
package/dist/fastify.js
CHANGED
|
@@ -407,8 +407,7 @@ function parseMfaResponse(data, browserSessionMode) {
|
|
|
407
407
|
}
|
|
408
408
|
|
|
409
409
|
// src/modules/tokens.ts
|
|
410
|
-
var
|
|
411
|
-
var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
|
|
410
|
+
var import_jose = require("jose");
|
|
412
411
|
var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
413
412
|
var DEFAULT_TOKEN_ISSUER = [
|
|
414
413
|
"https://auth.dispositioniq.com",
|
|
@@ -421,6 +420,24 @@ var DEFAULT_TOKEN_AUDIENCE = [
|
|
|
421
420
|
"iqvalidate"
|
|
422
421
|
];
|
|
423
422
|
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
423
|
+
function decodeProtectedHeader(token) {
|
|
424
|
+
const parts = token.split(".");
|
|
425
|
+
if (parts.length < 2) return null;
|
|
426
|
+
try {
|
|
427
|
+
const padded = parts[0] + "=".repeat((4 - parts[0].length % 4) % 4);
|
|
428
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
429
|
+
let json;
|
|
430
|
+
if (typeof atob === "function") {
|
|
431
|
+
json = atob(b64);
|
|
432
|
+
} else {
|
|
433
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
434
|
+
json = Buffer2.from(b64, "base64").toString("utf8");
|
|
435
|
+
}
|
|
436
|
+
return JSON.parse(json);
|
|
437
|
+
} catch {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
424
441
|
var TokensModule = class {
|
|
425
442
|
constructor(baseUrl, options = {}) {
|
|
426
443
|
this.jwksCache = null;
|
|
@@ -431,49 +448,49 @@ var TokensModule = class {
|
|
|
431
448
|
this.defaultClockTolerance = options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
|
|
432
449
|
}
|
|
433
450
|
/**
|
|
434
|
-
* Verify a JWT access token using RS256 via JWKS from
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
* clock tolerance default to client config but can be overridden per call.
|
|
451
|
+
* Verify a JWT access token using RS256/ES256 via JWKS from
|
|
452
|
+
* `/.well-known/jwks.json`. Backed by `jose` (Web Crypto) so it runs on
|
|
453
|
+
* Node, browser, and edge runtimes alike — no `node:crypto` dependency.
|
|
454
|
+
* Caches JWKS for 1 hour and refetches once on unknown `kid`.
|
|
439
455
|
*/
|
|
440
456
|
async verify(token, options = {}) {
|
|
441
|
-
const
|
|
442
|
-
if (!
|
|
457
|
+
const header = decodeProtectedHeader(token);
|
|
458
|
+
if (!header) {
|
|
443
459
|
throw new IQAuthError("TOKEN_INVALID", "Unable to decode token");
|
|
444
460
|
}
|
|
445
|
-
const kid =
|
|
461
|
+
const kid = header.kid;
|
|
446
462
|
if (!kid) {
|
|
447
463
|
throw new IQAuthError("TOKEN_INVALID", "Token missing kid header");
|
|
448
464
|
}
|
|
449
|
-
let
|
|
450
|
-
if (!
|
|
451
|
-
|
|
452
|
-
|
|
465
|
+
let cache = await this.ensureCache();
|
|
466
|
+
if (!cache.byKid.has(kid)) {
|
|
467
|
+
this.jwksCache = null;
|
|
468
|
+
cache = await this.ensureCache();
|
|
453
469
|
}
|
|
454
|
-
if (!
|
|
470
|
+
if (!cache.byKid.has(kid)) {
|
|
455
471
|
throw new IQAuthError("TOKEN_INVALID", `Unknown key ID: ${kid}`);
|
|
456
472
|
}
|
|
457
473
|
const issuer = options.issuer ?? this.defaultIssuer;
|
|
458
474
|
const audience = options.audience ?? this.defaultAudience;
|
|
459
475
|
const clockTolerance = options.clockTolerance ?? this.defaultClockTolerance;
|
|
460
|
-
const algorithms = options.algorithms ?? ["RS256"];
|
|
476
|
+
const algorithms = options.algorithms ?? ["RS256", "ES256"];
|
|
477
|
+
const verifyOptions = {
|
|
478
|
+
algorithms,
|
|
479
|
+
clockTolerance,
|
|
480
|
+
issuer,
|
|
481
|
+
audience
|
|
482
|
+
};
|
|
461
483
|
try {
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
clockTolerance,
|
|
465
|
-
// The jsonwebtoken types insist on tuple types for arrays; runtime
|
|
466
|
-
// accepts plain string[] so we cast to satisfy the compiler.
|
|
467
|
-
issuer,
|
|
468
|
-
audience
|
|
469
|
-
};
|
|
470
|
-
const verified = import_jsonwebtoken.default.verify(token, publicKey, verifyOptions);
|
|
471
|
-
return verified;
|
|
484
|
+
const { payload } = await (0, import_jose.jwtVerify)(token, cache.verifier, verifyOptions);
|
|
485
|
+
return payload;
|
|
472
486
|
} catch (err) {
|
|
487
|
+
if (err instanceof import_jose.errors.JWTExpired) {
|
|
488
|
+
throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
|
|
489
|
+
}
|
|
490
|
+
if (err instanceof import_jose.errors.JOSEError) {
|
|
491
|
+
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
492
|
+
}
|
|
473
493
|
if (err instanceof Error) {
|
|
474
|
-
if (err.name === "TokenExpiredError") {
|
|
475
|
-
throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
|
|
476
|
-
}
|
|
477
494
|
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
478
495
|
}
|
|
479
496
|
throw new IQAuthError("TOKEN_INVALID", "Token verification failed");
|
|
@@ -481,29 +498,40 @@ var TokensModule = class {
|
|
|
481
498
|
}
|
|
482
499
|
/**
|
|
483
500
|
* Decode a JWT without verification. Returns null if malformed.
|
|
484
|
-
*
|
|
485
|
-
* @remarks Local decode only — no network call
|
|
486
501
|
*/
|
|
487
502
|
decode(token) {
|
|
488
|
-
|
|
489
|
-
|
|
503
|
+
try {
|
|
504
|
+
const parts = token.split(".");
|
|
505
|
+
if (parts.length < 2) return null;
|
|
506
|
+
const payload = parts[1];
|
|
507
|
+
const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
508
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
509
|
+
let json;
|
|
510
|
+
if (typeof atob === "function") {
|
|
511
|
+
json = atob(b64);
|
|
512
|
+
} else {
|
|
513
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
514
|
+
json = Buffer2.from(b64, "base64").toString("utf8");
|
|
515
|
+
}
|
|
516
|
+
try {
|
|
517
|
+
json = decodeURIComponent(escape(json));
|
|
518
|
+
} catch {
|
|
519
|
+
}
|
|
520
|
+
const claims = JSON.parse(json);
|
|
521
|
+
if (!claims || typeof claims !== "object") return null;
|
|
522
|
+
return claims;
|
|
523
|
+
} catch {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
490
526
|
}
|
|
491
|
-
/**
|
|
492
|
-
* Check if a token is expired based on the `exp` claim.
|
|
493
|
-
*
|
|
494
|
-
* @remarks Local check only — no network call
|
|
495
|
-
*/
|
|
527
|
+
/** Check if a token is expired based on the `exp` claim. */
|
|
496
528
|
isExpired(token) {
|
|
497
529
|
const claims = this.decode(token);
|
|
498
530
|
if (!claims?.exp) return true;
|
|
499
531
|
const now = Math.floor(Date.now() / 1e3);
|
|
500
532
|
return claims.exp <= now;
|
|
501
533
|
}
|
|
502
|
-
/**
|
|
503
|
-
* Get the claims from a token without verification.
|
|
504
|
-
*
|
|
505
|
-
* @remarks Local decode only — no network call
|
|
506
|
-
*/
|
|
534
|
+
/** Get the claims from a token without verification. */
|
|
507
535
|
getClaims(token) {
|
|
508
536
|
const claims = this.decode(token);
|
|
509
537
|
if (!claims) {
|
|
@@ -511,11 +539,15 @@ var TokensModule = class {
|
|
|
511
539
|
}
|
|
512
540
|
return claims;
|
|
513
541
|
}
|
|
514
|
-
async
|
|
515
|
-
if (
|
|
516
|
-
|
|
542
|
+
async ensureCache() {
|
|
543
|
+
if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) {
|
|
544
|
+
return this.jwksCache;
|
|
545
|
+
}
|
|
546
|
+
await this.refreshJwks();
|
|
547
|
+
if (!this.jwksCache) {
|
|
548
|
+
throw new IQAuthError("INTERNAL_ERROR", "JWKS cache unavailable after refresh");
|
|
517
549
|
}
|
|
518
|
-
return this.jwksCache
|
|
550
|
+
return this.jwksCache;
|
|
519
551
|
}
|
|
520
552
|
async refreshJwks() {
|
|
521
553
|
if (this.inFlightRefresh) {
|
|
@@ -542,35 +574,24 @@ var TokensModule = class {
|
|
|
542
574
|
"Malformed JWKS response: expected { keys: [...] }"
|
|
543
575
|
);
|
|
544
576
|
}
|
|
545
|
-
const
|
|
577
|
+
const byKid = /* @__PURE__ */ new Set();
|
|
546
578
|
for (const key of jwks.keys) {
|
|
547
|
-
if (!key || typeof key.kid !== "string" || typeof key.n !== "string" || typeof key.e !== "string") {
|
|
579
|
+
if (!key || typeof key.kid !== "string" || typeof key.n !== "string" && typeof key.x !== "string" || key.kty === "RSA" && (typeof key.n !== "string" || typeof key.e !== "string")) {
|
|
548
580
|
throw new IQAuthError(
|
|
549
581
|
"INTERNAL_ERROR",
|
|
550
582
|
"Malformed JWKS response: key missing required fields"
|
|
551
583
|
);
|
|
552
584
|
}
|
|
553
|
-
|
|
554
|
-
keys.set(key.kid, pem);
|
|
585
|
+
byKid.add(key.kid);
|
|
555
586
|
}
|
|
556
|
-
|
|
587
|
+
const verifier = (0, import_jose.createLocalJWKSet)({ keys: jwks.keys });
|
|
588
|
+
this.jwksCache = { raw: jwks.keys, byKid, verifier, fetchedAt: Date.now() };
|
|
557
589
|
} finally {
|
|
558
590
|
this.inFlightRefresh = null;
|
|
559
591
|
}
|
|
560
592
|
})();
|
|
561
593
|
return this.inFlightRefresh;
|
|
562
594
|
}
|
|
563
|
-
jwkToPem(jwk) {
|
|
564
|
-
const keyObject = import_crypto.default.createPublicKey({
|
|
565
|
-
key: {
|
|
566
|
-
kty: jwk.kty,
|
|
567
|
-
n: jwk.n,
|
|
568
|
-
e: jwk.e
|
|
569
|
-
},
|
|
570
|
-
format: "jwk"
|
|
571
|
-
});
|
|
572
|
-
return keyObject.export({ type: "spki", format: "pem" });
|
|
573
|
-
}
|
|
574
595
|
/** @internal Exposed for testing — clears JWKS cache */
|
|
575
596
|
clearCache() {
|
|
576
597
|
this.jwksCache = null;
|
|
@@ -778,7 +799,7 @@ var PermissionsModule = class {
|
|
|
778
799
|
};
|
|
779
800
|
|
|
780
801
|
// src/modules/oidc.ts
|
|
781
|
-
var
|
|
802
|
+
var import_crypto = __toESM(require("crypto"));
|
|
782
803
|
var InMemoryOidcStateStore = class {
|
|
783
804
|
constructor() {
|
|
784
805
|
this.map = /* @__PURE__ */ new Map();
|
|
@@ -859,12 +880,12 @@ var OidcModule = class {
|
|
|
859
880
|
* ready to redirect the user to.
|
|
860
881
|
*/
|
|
861
882
|
async createAuthRequest(params) {
|
|
862
|
-
const codeVerifier = base64UrlEncode(
|
|
883
|
+
const codeVerifier = base64UrlEncode(import_crypto.default.randomBytes(32));
|
|
863
884
|
const codeChallenge = base64UrlEncode(
|
|
864
|
-
|
|
885
|
+
import_crypto.default.createHash("sha256").update(codeVerifier).digest()
|
|
865
886
|
);
|
|
866
|
-
const state = base64UrlEncode(
|
|
867
|
-
const nonce = base64UrlEncode(
|
|
887
|
+
const state = base64UrlEncode(import_crypto.default.randomBytes(16));
|
|
888
|
+
const nonce = base64UrlEncode(import_crypto.default.randomBytes(16));
|
|
868
889
|
await this.stateStore.set(state, {
|
|
869
890
|
codeVerifier,
|
|
870
891
|
state,
|
|
@@ -1812,7 +1833,7 @@ function assertPublishableKey(raw, opts) {
|
|
|
1812
1833
|
if (!isValidIssuerUrl(decoded.iss)) {
|
|
1813
1834
|
throw new IQAuthError(
|
|
1814
1835
|
"CONFIG_INVALID",
|
|
1815
|
-
`${ctx}IQAuth publishable key encodes an invalid issuer (iss=${JSON.stringify(decoded.iss)}). Expected a fully-qualified URL like "https://auth.example.com" (scheme required). Regenerate the key from the IQAuth admin console
|
|
1836
|
+
`${ctx}IQAuth publishable key encodes an invalid issuer (iss=${JSON.stringify(decoded.iss)}). Expected a fully-qualified URL like "https://auth.example.com" (scheme required). Regenerate the key from the IQAuth admin console \u2014 the new key will encode a valid issuer URL.`
|
|
1816
1837
|
);
|
|
1817
1838
|
}
|
|
1818
1839
|
return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
|
|
@@ -2014,6 +2035,15 @@ async function handleSignout(config, input) {
|
|
|
2014
2035
|
} catch {
|
|
2015
2036
|
}
|
|
2016
2037
|
}
|
|
2038
|
+
if (input.endSsoSession !== false && input.ssoCookieHeader) {
|
|
2039
|
+
try {
|
|
2040
|
+
await cfg.fetchImpl(`${cfg.issuer}/oidc/sso-logout`, {
|
|
2041
|
+
method: "POST",
|
|
2042
|
+
headers: { Cookie: input.ssoCookieHeader }
|
|
2043
|
+
});
|
|
2044
|
+
} catch {
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2017
2047
|
return {
|
|
2018
2048
|
status: 200,
|
|
2019
2049
|
body: { success: true, data: { signedOut: true } },
|
|
@@ -2120,7 +2150,8 @@ async function iqAuth(fastify, options) {
|
|
|
2120
2150
|
fastify.post(`${mount}/signout`, async (req, reply) => {
|
|
2121
2151
|
const auth = req.headers?.authorization;
|
|
2122
2152
|
const accessToken = (typeof auth === "string" ? auth.replace(/^Bearer /i, "") : void 0) || readCookie(req, accessCookie);
|
|
2123
|
-
|
|
2153
|
+
const ssoCookieHeader = typeof req.headers?.cookie === "string" ? req.headers.cookie : void 0;
|
|
2154
|
+
applyResponse(reply, await handleSignout(helperConfig, { accessToken, ssoCookieHeader }));
|
|
2124
2155
|
});
|
|
2125
2156
|
}
|
|
2126
2157
|
fastify.decorate("iqauth", { client, issuer });
|
package/dist/fastify.mjs
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IQAuthClient
|
|
3
|
+
} from "./chunk-W3F4JYGP.mjs";
|
|
1
4
|
import {
|
|
2
5
|
handleCallback,
|
|
3
6
|
handleRefresh,
|
|
4
7
|
handleSignout,
|
|
5
8
|
serializeCookie
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-KGEPDXHU.mjs";
|
|
7
10
|
import {
|
|
8
11
|
assertPublishableKey
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import
|
|
11
|
-
IQAuthClient
|
|
12
|
-
} from "./chunk-MDUHPQMM.mjs";
|
|
12
|
+
} from "./chunk-WQWBJSSS.mjs";
|
|
13
|
+
import "./chunk-UNYDG2L4.mjs";
|
|
13
14
|
import {
|
|
14
15
|
IQAuthError
|
|
15
16
|
} from "./chunk-6I6RM4MN.mjs";
|
|
@@ -114,7 +115,8 @@ async function iqAuth(fastify, options) {
|
|
|
114
115
|
fastify.post(`${mount}/signout`, async (req, reply) => {
|
|
115
116
|
const auth = req.headers?.authorization;
|
|
116
117
|
const accessToken = (typeof auth === "string" ? auth.replace(/^Bearer /i, "") : void 0) || readCookie(req, accessCookie);
|
|
117
|
-
|
|
118
|
+
const ssoCookieHeader = typeof req.headers?.cookie === "string" ? req.headers.cookie : void 0;
|
|
119
|
+
applyResponse(reply, await handleSignout(helperConfig, { accessToken, ssoCookieHeader }));
|
|
118
120
|
});
|
|
119
121
|
}
|
|
120
122
|
fastify.decorate("iqauth", { client, issuer });
|