@iqauth/sdk 2.2.0 → 2.5.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 +134 -0
- package/dist/browser-session.d.mts +3 -3
- package/dist/browser-session.d.ts +3 -3
- package/dist/browser-session.js +89 -68
- package/dist/browser-session.mjs +2 -1
- package/dist/browser.d.mts +64 -29
- package/dist/browser.d.ts +64 -29
- package/dist/browser.js +794 -39
- package/dist/browser.mjs +44 -4
- package/dist/bundle-LUKDQYVQ.mjs +374 -0
- package/dist/chunk-3JULWS6F.mjs +106 -0
- package/dist/chunk-5T7GHBX6.mjs +1165 -0
- package/dist/{chunk-M4J6BPK7.mjs → chunk-6TDJJER7.mjs} +12 -3
- package/dist/{chunk-QZB745C2.mjs → chunk-76W5TLQQ.mjs} +264 -211
- package/dist/{chunk-D72UL5HL.mjs → chunk-BVV54LPI.mjs} +36 -4
- package/dist/chunk-LIZYFXH7.mjs +90 -0
- package/dist/chunk-MKKZULZR.mjs +241 -0
- package/dist/chunk-SL3KRS4W.mjs +54 -0
- package/dist/chunk-TKZTCPEK.mjs +232 -0
- package/dist/chunk-UKZLOHZG.mjs +83 -0
- 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.js +144 -36
- package/dist/cli/index.mjs +1 -1
- package/dist/{client-DXbHb2ul.d.ts → client-BNQe3AgF.d.ts} +3 -67
- package/dist/{client-Dv4v92Mj.d.mts → client-kYlJFgPv.d.mts} +3 -67
- package/dist/doctor-YYNHNMLD.mjs +198 -0
- package/dist/{express-BZmF1llh.d.mts → express-B6_1vBYZ.d.mts} +23 -2
- package/dist/{express-B4o3P8vK.d.ts → express-CHpfa7D_.d.ts} +23 -2
- package/dist/express.d.mts +77 -6
- package/dist/express.d.ts +77 -6
- package/dist/express.js +336 -74
- package/dist/express.mjs +209 -8
- package/dist/fastify.js +103 -72
- package/dist/fastify.mjs +6 -4
- package/dist/hono.js +102 -72
- package/dist/hono.mjs +5 -4
- package/dist/index.d.mts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +590 -73
- package/dist/index.mjs +30 -8
- package/dist/locales.d.mts +53 -0
- package/dist/locales.d.ts +53 -0
- package/dist/locales.js +1202 -0
- package/dist/locales.mjs +29 -0
- package/dist/mobile.d.mts +3 -3
- package/dist/mobile.d.ts +3 -3
- package/dist/mobile.js +89 -68
- package/dist/mobile.mjs +2 -1
- package/dist/next.d.mts +10 -1
- package/dist/next.d.ts +10 -1
- package/dist/next.js +101 -1618
- package/dist/next.mjs +9 -9
- package/dist/provisioningBridge-88xjOS2n.d.mts +86 -0
- package/dist/provisioningBridge-DnTfzdZK.d.ts +86 -0
- package/dist/react.d.mts +1349 -10
- package/dist/react.d.ts +1349 -10
- package/dist/react.js +2998 -569
- package/dist/react.mjs +1518 -95
- package/dist/reverify-4UEJXUS6.mjs +16 -0
- package/dist/server/handlers.d.mts +12 -1
- package/dist/server/handlers.d.ts +12 -1
- package/dist/server/handlers.js +12 -3
- package/dist/server/handlers.mjs +2 -2
- package/dist/server.d.mts +5 -4
- package/dist/server.d.ts +5 -4
- package/dist/server.js +188 -73
- package/dist/server.mjs +13 -8
- package/dist/service.d.mts +3 -3
- package/dist/service.d.ts +3 -3
- package/dist/service.js +89 -68
- package/dist/service.mjs +2 -1
- package/dist/signIn-CCY4JE5G.mjs +15 -0
- package/dist/{signIn-D_kP3v-c.d.mts → signIn-CiIBTJIh.d.mts} +232 -4
- package/dist/{signIn-BVDTIA_t.d.ts → signIn-OCr88Zf8.d.ts} +232 -4
- package/dist/test.d.mts +86 -0
- package/dist/test.d.ts +86 -0
- package/dist/test.js +289 -0
- package/dist/test.mjs +9 -0
- package/dist/tokens-DCyzzn8L.d.mts +63 -0
- package/dist/tokens-aHiGFr_E.d.ts +63 -0
- package/dist/types-6bNdxesb.d.mts +196 -0
- package/dist/types-6bNdxesb.d.ts +196 -0
- package/dist/{types-Cxl3bQHt.d.ts → types-DZAflmmq.d.mts} +6 -0
- package/dist/{types-Cxl3bQHt.d.mts → types-DZAflmmq.d.ts} +6 -0
- package/dist/webhooks.d.mts +61 -0
- package/dist/webhooks.d.ts +61 -0
- package/dist/webhooks.js +119 -0
- package/dist/webhooks.mjs +11 -0
- package/dist/ws.d.mts +73 -0
- package/dist/ws.d.ts +73 -0
- package/dist/ws.js +397 -0
- package/dist/ws.mjs +12 -0
- package/package.json +24 -3
- package/dist/doctor-XCI77BQS.mjs +0 -90
package/dist/next.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/next.ts
|
|
@@ -105,7 +95,7 @@ function assertPublishableKey(raw, opts) {
|
|
|
105
95
|
if (!isValidIssuerUrl(decoded.iss)) {
|
|
106
96
|
throw new IQAuthError(
|
|
107
97
|
"CONFIG_INVALID",
|
|
108
|
-
`${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
|
|
98
|
+
`${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.`
|
|
109
99
|
);
|
|
110
100
|
}
|
|
111
101
|
return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
|
|
@@ -142,8 +132,8 @@ function resolve(config) {
|
|
|
142
132
|
publishableKey: config.publishableKey,
|
|
143
133
|
secretKey: config.secretKey,
|
|
144
134
|
issuer: (config.issuer ?? inferredIssuer).replace(/\/+$/, ""),
|
|
145
|
-
accessCookieName: config.accessCookieName ?? "iqauth_at",
|
|
146
|
-
refreshCookieName: config.refreshCookieName ?? "iqauth_rt",
|
|
135
|
+
accessCookieName: config.accessCookieName ?? config.cookieNames?.access ?? "iqauth_at",
|
|
136
|
+
refreshCookieName: config.refreshCookieName ?? config.cookieNames?.refresh ?? "iqauth_rt",
|
|
147
137
|
cookieDomain: config.cookieDomain,
|
|
148
138
|
sameSite: config.sameSite ?? "lax",
|
|
149
139
|
secure: config.secure ?? true,
|
|
@@ -307,6 +297,15 @@ async function handleSignout(config, input) {
|
|
|
307
297
|
} catch {
|
|
308
298
|
}
|
|
309
299
|
}
|
|
300
|
+
if (input.endSsoSession !== false && input.ssoCookieHeader) {
|
|
301
|
+
try {
|
|
302
|
+
await cfg.fetchImpl(`${cfg.issuer}/oidc/sso-logout`, {
|
|
303
|
+
method: "POST",
|
|
304
|
+
headers: { Cookie: input.ssoCookieHeader }
|
|
305
|
+
});
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
}
|
|
310
309
|
return {
|
|
311
310
|
status: 200,
|
|
312
311
|
body: { success: true, data: { signedOut: true } },
|
|
@@ -314,369 +313,8 @@ async function handleSignout(config, input) {
|
|
|
314
313
|
};
|
|
315
314
|
}
|
|
316
315
|
|
|
317
|
-
// src/http.ts
|
|
318
|
-
var DEFAULT_RETRY = {
|
|
319
|
-
maxAttempts: 3,
|
|
320
|
-
baseDelayMs: 100,
|
|
321
|
-
maxDelayMs: 2e3
|
|
322
|
-
};
|
|
323
|
-
function resolveRetry(cfg) {
|
|
324
|
-
return {
|
|
325
|
-
maxAttempts: Math.max(1, cfg?.maxAttempts ?? DEFAULT_RETRY.maxAttempts),
|
|
326
|
-
baseDelayMs: Math.max(0, cfg?.baseDelayMs ?? DEFAULT_RETRY.baseDelayMs),
|
|
327
|
-
maxDelayMs: Math.max(0, cfg?.maxDelayMs ?? DEFAULT_RETRY.maxDelayMs)
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
function sleep(ms) {
|
|
331
|
-
if (ms <= 0) return Promise.resolve();
|
|
332
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
333
|
-
}
|
|
334
|
-
var HttpClient = class {
|
|
335
|
-
constructor(config) {
|
|
336
|
-
this.refreshPromise = null;
|
|
337
|
-
this.config = config;
|
|
338
|
-
this.retryConfig = resolveRetry(config.retry);
|
|
339
|
-
}
|
|
340
|
-
computeBackoffDelay(attempt) {
|
|
341
|
-
const exp = Math.min(this.retryConfig.maxDelayMs, this.retryConfig.baseDelayMs * 2 ** (attempt - 1));
|
|
342
|
-
return Math.floor(Math.random() * exp);
|
|
343
|
-
}
|
|
344
|
-
isRetryableStatus(status) {
|
|
345
|
-
return status === 429 || status >= 500 && status <= 599;
|
|
346
|
-
}
|
|
347
|
-
async fetchWithRetry(url, init) {
|
|
348
|
-
const { maxAttempts } = this.retryConfig;
|
|
349
|
-
let lastError;
|
|
350
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
351
|
-
try {
|
|
352
|
-
const res = await fetch(url, init);
|
|
353
|
-
if (this.isRetryableStatus(res.status) && attempt < maxAttempts) {
|
|
354
|
-
await sleep(this.computeBackoffDelay(attempt));
|
|
355
|
-
continue;
|
|
356
|
-
}
|
|
357
|
-
return res;
|
|
358
|
-
} catch (err) {
|
|
359
|
-
lastError = err;
|
|
360
|
-
if (attempt >= maxAttempts) break;
|
|
361
|
-
await sleep(this.computeBackoffDelay(attempt));
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
throw lastError instanceof Error ? lastError : new IQAuthError("INTERNAL_ERROR", "Network request failed");
|
|
365
|
-
}
|
|
366
|
-
get baseUrl() {
|
|
367
|
-
return this.config.baseUrl;
|
|
368
|
-
}
|
|
369
|
-
get environment() {
|
|
370
|
-
return this.config.environment;
|
|
371
|
-
}
|
|
372
|
-
isBrowserSession() {
|
|
373
|
-
return this.config.environment === "browser_session";
|
|
374
|
-
}
|
|
375
|
-
hasCredentials() {
|
|
376
|
-
return this.isBrowserSession() || !!(this.config.getApiKey() || this.config.getAccessToken());
|
|
377
|
-
}
|
|
378
|
-
buildHeaders(overrideAuth) {
|
|
379
|
-
const headers = {
|
|
380
|
-
"Content-Type": "application/json"
|
|
381
|
-
};
|
|
382
|
-
if (this.isBrowserSession()) {
|
|
383
|
-
const headerName = this.config.sessionHeaderName || "x-iqauth-session";
|
|
384
|
-
headers[headerName] = this.config.sessionHeaderValue || "cookie";
|
|
385
|
-
return headers;
|
|
386
|
-
}
|
|
387
|
-
const authMode = overrideAuth || (this.config.getApiKey() ? "apikey" : "bearer");
|
|
388
|
-
if (authMode === "apikey") {
|
|
389
|
-
const apiKey = this.config.getApiKey();
|
|
390
|
-
if (apiKey) {
|
|
391
|
-
headers["X-API-Key"] = apiKey;
|
|
392
|
-
}
|
|
393
|
-
} else {
|
|
394
|
-
const token = this.config.getAccessToken();
|
|
395
|
-
if (token) {
|
|
396
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return headers;
|
|
400
|
-
}
|
|
401
|
-
isTokenExpiringSoon() {
|
|
402
|
-
const token = this.config.getAccessToken();
|
|
403
|
-
if (!token) return false;
|
|
404
|
-
try {
|
|
405
|
-
const parts = token.split(".");
|
|
406
|
-
if (parts.length !== 3) return false;
|
|
407
|
-
const payload = JSON.parse(
|
|
408
|
-
typeof atob === "function" ? atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")) : Buffer.from(parts[1], "base64url").toString("utf8")
|
|
409
|
-
);
|
|
410
|
-
if (!payload.exp) return false;
|
|
411
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
412
|
-
return payload.exp - now < 60;
|
|
413
|
-
} catch {
|
|
414
|
-
return false;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
async attemptRefresh() {
|
|
418
|
-
if (this.refreshPromise) {
|
|
419
|
-
return this.refreshPromise;
|
|
420
|
-
}
|
|
421
|
-
this.refreshPromise = (async () => {
|
|
422
|
-
try {
|
|
423
|
-
const res = await this.fetchWithRetry(`${this.config.baseUrl}/api/v1/auth/refresh`, {
|
|
424
|
-
method: "POST",
|
|
425
|
-
headers: this.buildHeaders(),
|
|
426
|
-
...this.isBrowserSession() ? { credentials: "include" } : (() => {
|
|
427
|
-
const refreshToken = this.config.getRefreshToken();
|
|
428
|
-
if (!refreshToken) throw new IQAuthError("TOKEN_INVALID", "No refresh token available");
|
|
429
|
-
return { body: JSON.stringify({ refreshToken }) };
|
|
430
|
-
})()
|
|
431
|
-
});
|
|
432
|
-
const body = await res.json();
|
|
433
|
-
if (!body.success) {
|
|
434
|
-
throw new IQAuthError(
|
|
435
|
-
body.error.code,
|
|
436
|
-
body.error.message,
|
|
437
|
-
res.status,
|
|
438
|
-
body
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
if (this.isBrowserSession()) {
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
if (!body.data.accessToken || !body.data.refreshToken) {
|
|
445
|
-
throw new IQAuthError("TOKEN_INVALID", "Refresh response did not include a token pair");
|
|
446
|
-
}
|
|
447
|
-
const tokens = {
|
|
448
|
-
accessToken: body.data.accessToken,
|
|
449
|
-
refreshToken: body.data.refreshToken
|
|
450
|
-
};
|
|
451
|
-
this.config.setTokens(tokens);
|
|
452
|
-
this.config.onTokenRefresh?.(tokens);
|
|
453
|
-
} finally {
|
|
454
|
-
this.refreshPromise = null;
|
|
455
|
-
}
|
|
456
|
-
})();
|
|
457
|
-
return this.refreshPromise;
|
|
458
|
-
}
|
|
459
|
-
async request(method, path, body, options) {
|
|
460
|
-
return this.requestWithRetry(method, path, body, options, false);
|
|
461
|
-
}
|
|
462
|
-
async requestWithRetry(method, path, body, options, hasRetried) {
|
|
463
|
-
if (this.config.autoRefresh && !options?.skipAutoRefresh && !this.isBrowserSession() && this.config.getRefreshToken() && this.isTokenExpiringSoon()) {
|
|
464
|
-
await this.attemptRefresh();
|
|
465
|
-
}
|
|
466
|
-
const url = `${this.config.baseUrl}${path}`;
|
|
467
|
-
const headers = this.buildHeaders(options?.authMode);
|
|
468
|
-
const fetchOptions = {
|
|
469
|
-
method,
|
|
470
|
-
headers,
|
|
471
|
-
...this.isBrowserSession() ? { credentials: "include" } : {}
|
|
472
|
-
};
|
|
473
|
-
if (body !== void 0 && method !== "GET") {
|
|
474
|
-
fetchOptions.body = JSON.stringify(body);
|
|
475
|
-
}
|
|
476
|
-
const res = await this.fetchWithRetry(url, fetchOptions);
|
|
477
|
-
if (res.status === 204) {
|
|
478
|
-
return void 0;
|
|
479
|
-
}
|
|
480
|
-
const responseBody = await res.json();
|
|
481
|
-
if (!responseBody.success) {
|
|
482
|
-
const shouldRetryRefresh = !hasRetried && this.config.autoRefresh && !options?.skipAutoRefresh && responseBody.error.code === "TOKEN_EXPIRED" && (this.isBrowserSession() || !!this.config.getRefreshToken());
|
|
483
|
-
if (shouldRetryRefresh) {
|
|
484
|
-
await this.attemptRefresh();
|
|
485
|
-
return this.requestWithRetry(method, path, body, options, true);
|
|
486
|
-
}
|
|
487
|
-
throw new IQAuthError(
|
|
488
|
-
responseBody.error.code,
|
|
489
|
-
responseBody.error.message,
|
|
490
|
-
res.status,
|
|
491
|
-
responseBody
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
return responseBody.data;
|
|
495
|
-
}
|
|
496
|
-
async requestRaw(method, path, body) {
|
|
497
|
-
const url = `${this.config.baseUrl}${path}`;
|
|
498
|
-
const headers = { "Content-Type": "application/json" };
|
|
499
|
-
if (this.isBrowserSession()) {
|
|
500
|
-
const headerName = this.config.sessionHeaderName || "x-iqauth-session";
|
|
501
|
-
headers[headerName] = this.config.sessionHeaderValue || "cookie";
|
|
502
|
-
}
|
|
503
|
-
const token = this.config.getAccessToken();
|
|
504
|
-
if (token) {
|
|
505
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
506
|
-
}
|
|
507
|
-
const fetchOptions = { method, headers };
|
|
508
|
-
if (this.isBrowserSession()) {
|
|
509
|
-
fetchOptions.credentials = "include";
|
|
510
|
-
}
|
|
511
|
-
if (body !== void 0 && method !== "GET") {
|
|
512
|
-
fetchOptions.body = JSON.stringify(body);
|
|
513
|
-
}
|
|
514
|
-
const res = await fetch(url, fetchOptions);
|
|
515
|
-
return await res.json();
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
// src/modules/auth.ts
|
|
520
|
-
function parseLoginResponse(data, browserSessionMode) {
|
|
521
|
-
if (data.accessToken && data.refreshToken && data.user) {
|
|
522
|
-
return {
|
|
523
|
-
status: "authenticated",
|
|
524
|
-
authMode: "token",
|
|
525
|
-
tokens: { accessToken: data.accessToken, refreshToken: data.refreshToken },
|
|
526
|
-
user: data.user
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
if (browserSessionMode && data.user) {
|
|
530
|
-
return {
|
|
531
|
-
status: "authenticated",
|
|
532
|
-
authMode: "session",
|
|
533
|
-
user: data.user
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
if (data.mfaChallengeToken && !data.tenantSelectionToken) {
|
|
537
|
-
return {
|
|
538
|
-
status: "mfa_required",
|
|
539
|
-
mfaChallengeToken: data.mfaChallengeToken,
|
|
540
|
-
availableMethods: data.availableMethods ?? []
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
if (data.tenantSelectionToken && data.tenants) {
|
|
544
|
-
return {
|
|
545
|
-
status: "tenant_selection",
|
|
546
|
-
tenantSelectionToken: data.tenantSelectionToken,
|
|
547
|
-
tenants: data.tenants
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
throw new Error("Unexpected login response shape");
|
|
551
|
-
}
|
|
552
|
-
var AuthModule = class {
|
|
553
|
-
constructor(http) {
|
|
554
|
-
this.http = http;
|
|
555
|
-
}
|
|
556
|
-
async login(email, password) {
|
|
557
|
-
const data = await this.http.request(
|
|
558
|
-
"POST",
|
|
559
|
-
"/api/v1/auth/login",
|
|
560
|
-
{ email, password },
|
|
561
|
-
{ skipAutoRefresh: true }
|
|
562
|
-
);
|
|
563
|
-
return parseLoginResponse(data, this.http.isBrowserSession());
|
|
564
|
-
}
|
|
565
|
-
async signup(input) {
|
|
566
|
-
const data = await this.http.request(
|
|
567
|
-
"POST",
|
|
568
|
-
"/api/v1/auth/signup",
|
|
569
|
-
input,
|
|
570
|
-
{ skipAutoRefresh: true }
|
|
571
|
-
);
|
|
572
|
-
return parseLoginResponse(data, this.http.isBrowserSession());
|
|
573
|
-
}
|
|
574
|
-
async completeMfa(mfaChallengeToken, code, method) {
|
|
575
|
-
const data = await this.http.request(
|
|
576
|
-
"POST",
|
|
577
|
-
"/api/v1/mfa/verify",
|
|
578
|
-
{ mfaChallengeToken, code, method },
|
|
579
|
-
{ skipAutoRefresh: true }
|
|
580
|
-
);
|
|
581
|
-
return parseMfaResponse(data, this.http.isBrowserSession());
|
|
582
|
-
}
|
|
583
|
-
async completeMfaWithBackup(mfaChallengeToken, backupCode) {
|
|
584
|
-
const data = await this.http.request(
|
|
585
|
-
"POST",
|
|
586
|
-
"/api/v1/mfa/verify-backup",
|
|
587
|
-
{ mfaChallengeToken, backupCode },
|
|
588
|
-
{ skipAutoRefresh: true }
|
|
589
|
-
);
|
|
590
|
-
return parseMfaResponse(data, this.http.isBrowserSession());
|
|
591
|
-
}
|
|
592
|
-
async sendMfaChallenge(mfaChallengeToken, method) {
|
|
593
|
-
return this.http.request("POST", "/api/v1/mfa/challenge", {
|
|
594
|
-
mfaChallengeToken,
|
|
595
|
-
method
|
|
596
|
-
}, { skipAutoRefresh: true });
|
|
597
|
-
}
|
|
598
|
-
async selectTenant(tenantSelectionToken, tenantId) {
|
|
599
|
-
const data = await this.http.request(
|
|
600
|
-
"POST",
|
|
601
|
-
"/api/v1/auth/select-tenant",
|
|
602
|
-
{
|
|
603
|
-
tenantSelectionToken,
|
|
604
|
-
tenantId
|
|
605
|
-
},
|
|
606
|
-
{ skipAutoRefresh: true }
|
|
607
|
-
);
|
|
608
|
-
return parseLoginResponse(data, this.http.isBrowserSession());
|
|
609
|
-
}
|
|
610
|
-
async logout() {
|
|
611
|
-
return this.http.request("POST", "/api/v1/auth/logout");
|
|
612
|
-
}
|
|
613
|
-
async logoutAll() {
|
|
614
|
-
return this.http.request("POST", "/api/v1/auth/logout-all");
|
|
615
|
-
}
|
|
616
|
-
async refreshTokens(refreshToken) {
|
|
617
|
-
if (this.http.isBrowserSession()) {
|
|
618
|
-
throw new Error("refreshTokens(refreshToken) is not used in browser_session mode; the backend session should own refresh.");
|
|
619
|
-
}
|
|
620
|
-
const data = await this.http.request(
|
|
621
|
-
"POST",
|
|
622
|
-
"/api/v1/auth/refresh",
|
|
623
|
-
{ refreshToken },
|
|
624
|
-
{ skipAutoRefresh: true }
|
|
625
|
-
);
|
|
626
|
-
return { accessToken: data.accessToken, refreshToken: data.refreshToken };
|
|
627
|
-
}
|
|
628
|
-
async forgotPassword(email) {
|
|
629
|
-
return this.http.request("POST", "/api/v1/auth/password/reset/request", { email }, { skipAutoRefresh: true });
|
|
630
|
-
}
|
|
631
|
-
async resetPassword(token, newPassword) {
|
|
632
|
-
return this.http.request("POST", "/api/v1/auth/password/reset/confirm", { token, newPassword }, { skipAutoRefresh: true });
|
|
633
|
-
}
|
|
634
|
-
async changePassword(currentPassword, newPassword) {
|
|
635
|
-
return this.http.request("POST", "/api/v1/auth/password/change", {
|
|
636
|
-
currentPassword,
|
|
637
|
-
newPassword
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
async verifyToken() {
|
|
641
|
-
return this.http.request("GET", "/api/v1/auth/verify");
|
|
642
|
-
}
|
|
643
|
-
async exchangeOAuthCode(code) {
|
|
644
|
-
const data = await this.http.request(
|
|
645
|
-
"POST",
|
|
646
|
-
"/api/v1/auth/oauth/exchange",
|
|
647
|
-
{ code },
|
|
648
|
-
{ skipAutoRefresh: true }
|
|
649
|
-
);
|
|
650
|
-
return parseLoginResponse(data, this.http.isBrowserSession());
|
|
651
|
-
}
|
|
652
|
-
async getSessionUser() {
|
|
653
|
-
return this.http.request("GET", "/api/v1/auth/me");
|
|
654
|
-
}
|
|
655
|
-
};
|
|
656
|
-
function parseMfaResponse(data, browserSessionMode) {
|
|
657
|
-
if (data.accessToken && data.refreshToken && data.user) {
|
|
658
|
-
return {
|
|
659
|
-
authMode: "token",
|
|
660
|
-
tokens: { accessToken: data.accessToken, refreshToken: data.refreshToken },
|
|
661
|
-
user: data.user,
|
|
662
|
-
...data.remainingBackupCodes !== void 0 ? { remainingBackupCodes: data.remainingBackupCodes } : {},
|
|
663
|
-
...data.warning ? { warning: data.warning } : {}
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
if (browserSessionMode && data.user) {
|
|
667
|
-
return {
|
|
668
|
-
authMode: "session",
|
|
669
|
-
user: data.user,
|
|
670
|
-
...data.remainingBackupCodes !== void 0 ? { remainingBackupCodes: data.remainingBackupCodes } : {},
|
|
671
|
-
...data.warning ? { warning: data.warning } : {}
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
throw new Error("Unexpected MFA response shape");
|
|
675
|
-
}
|
|
676
|
-
|
|
677
316
|
// src/modules/tokens.ts
|
|
678
|
-
var
|
|
679
|
-
var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
|
|
317
|
+
var import_jose = require("jose");
|
|
680
318
|
var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
681
319
|
var DEFAULT_TOKEN_ISSUER = [
|
|
682
320
|
"https://auth.dispositioniq.com",
|
|
@@ -689,6 +327,24 @@ var DEFAULT_TOKEN_AUDIENCE = [
|
|
|
689
327
|
"iqvalidate"
|
|
690
328
|
];
|
|
691
329
|
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
330
|
+
function decodeProtectedHeader(token) {
|
|
331
|
+
const parts = token.split(".");
|
|
332
|
+
if (parts.length < 2) return null;
|
|
333
|
+
try {
|
|
334
|
+
const padded = parts[0] + "=".repeat((4 - parts[0].length % 4) % 4);
|
|
335
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
336
|
+
let json;
|
|
337
|
+
if (typeof atob === "function") {
|
|
338
|
+
json = atob(b64);
|
|
339
|
+
} else {
|
|
340
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
341
|
+
json = Buffer2.from(b64, "base64").toString("utf8");
|
|
342
|
+
}
|
|
343
|
+
return JSON.parse(json);
|
|
344
|
+
} catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
692
348
|
var TokensModule = class {
|
|
693
349
|
constructor(baseUrl, options = {}) {
|
|
694
350
|
this.jwksCache = null;
|
|
@@ -699,49 +355,49 @@ var TokensModule = class {
|
|
|
699
355
|
this.defaultClockTolerance = options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
|
|
700
356
|
}
|
|
701
357
|
/**
|
|
702
|
-
* Verify a JWT access token using RS256 via JWKS from
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
* clock tolerance default to client config but can be overridden per call.
|
|
358
|
+
* Verify a JWT access token using RS256/ES256 via JWKS from
|
|
359
|
+
* `/.well-known/jwks.json`. Backed by `jose` (Web Crypto) so it runs on
|
|
360
|
+
* Node, browser, and edge runtimes alike — no `node:crypto` dependency.
|
|
361
|
+
* Caches JWKS for 1 hour and refetches once on unknown `kid`.
|
|
707
362
|
*/
|
|
708
363
|
async verify(token, options = {}) {
|
|
709
|
-
const
|
|
710
|
-
if (!
|
|
364
|
+
const header = decodeProtectedHeader(token);
|
|
365
|
+
if (!header) {
|
|
711
366
|
throw new IQAuthError("TOKEN_INVALID", "Unable to decode token");
|
|
712
367
|
}
|
|
713
|
-
const kid =
|
|
368
|
+
const kid = header.kid;
|
|
714
369
|
if (!kid) {
|
|
715
370
|
throw new IQAuthError("TOKEN_INVALID", "Token missing kid header");
|
|
716
371
|
}
|
|
717
|
-
let
|
|
718
|
-
if (!
|
|
719
|
-
|
|
720
|
-
|
|
372
|
+
let cache = await this.ensureCache();
|
|
373
|
+
if (!cache.byKid.has(kid)) {
|
|
374
|
+
this.jwksCache = null;
|
|
375
|
+
cache = await this.ensureCache();
|
|
721
376
|
}
|
|
722
|
-
if (!
|
|
377
|
+
if (!cache.byKid.has(kid)) {
|
|
723
378
|
throw new IQAuthError("TOKEN_INVALID", `Unknown key ID: ${kid}`);
|
|
724
379
|
}
|
|
725
380
|
const issuer = options.issuer ?? this.defaultIssuer;
|
|
726
381
|
const audience = options.audience ?? this.defaultAudience;
|
|
727
382
|
const clockTolerance = options.clockTolerance ?? this.defaultClockTolerance;
|
|
728
|
-
const algorithms = options.algorithms ?? ["RS256"];
|
|
383
|
+
const algorithms = options.algorithms ?? ["RS256", "ES256"];
|
|
384
|
+
const verifyOptions = {
|
|
385
|
+
algorithms,
|
|
386
|
+
clockTolerance,
|
|
387
|
+
issuer,
|
|
388
|
+
audience
|
|
389
|
+
};
|
|
729
390
|
try {
|
|
730
|
-
const
|
|
731
|
-
|
|
732
|
-
clockTolerance,
|
|
733
|
-
// The jsonwebtoken types insist on tuple types for arrays; runtime
|
|
734
|
-
// accepts plain string[] so we cast to satisfy the compiler.
|
|
735
|
-
issuer,
|
|
736
|
-
audience
|
|
737
|
-
};
|
|
738
|
-
const verified = import_jsonwebtoken.default.verify(token, publicKey, verifyOptions);
|
|
739
|
-
return verified;
|
|
391
|
+
const { payload } = await (0, import_jose.jwtVerify)(token, cache.verifier, verifyOptions);
|
|
392
|
+
return payload;
|
|
740
393
|
} catch (err) {
|
|
394
|
+
if (err instanceof import_jose.errors.JWTExpired) {
|
|
395
|
+
throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
|
|
396
|
+
}
|
|
397
|
+
if (err instanceof import_jose.errors.JOSEError) {
|
|
398
|
+
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
399
|
+
}
|
|
741
400
|
if (err instanceof Error) {
|
|
742
|
-
if (err.name === "TokenExpiredError") {
|
|
743
|
-
throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
|
|
744
|
-
}
|
|
745
401
|
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
746
402
|
}
|
|
747
403
|
throw new IQAuthError("TOKEN_INVALID", "Token verification failed");
|
|
@@ -749,29 +405,40 @@ var TokensModule = class {
|
|
|
749
405
|
}
|
|
750
406
|
/**
|
|
751
407
|
* Decode a JWT without verification. Returns null if malformed.
|
|
752
|
-
*
|
|
753
|
-
* @remarks Local decode only — no network call
|
|
754
408
|
*/
|
|
755
409
|
decode(token) {
|
|
756
|
-
|
|
757
|
-
|
|
410
|
+
try {
|
|
411
|
+
const parts = token.split(".");
|
|
412
|
+
if (parts.length < 2) return null;
|
|
413
|
+
const payload = parts[1];
|
|
414
|
+
const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
415
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
416
|
+
let json;
|
|
417
|
+
if (typeof atob === "function") {
|
|
418
|
+
json = atob(b64);
|
|
419
|
+
} else {
|
|
420
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
421
|
+
json = Buffer2.from(b64, "base64").toString("utf8");
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
json = decodeURIComponent(escape(json));
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
const claims = JSON.parse(json);
|
|
428
|
+
if (!claims || typeof claims !== "object") return null;
|
|
429
|
+
return claims;
|
|
430
|
+
} catch {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
758
433
|
}
|
|
759
|
-
/**
|
|
760
|
-
* Check if a token is expired based on the `exp` claim.
|
|
761
|
-
*
|
|
762
|
-
* @remarks Local check only — no network call
|
|
763
|
-
*/
|
|
434
|
+
/** Check if a token is expired based on the `exp` claim. */
|
|
764
435
|
isExpired(token) {
|
|
765
436
|
const claims = this.decode(token);
|
|
766
437
|
if (!claims?.exp) return true;
|
|
767
438
|
const now = Math.floor(Date.now() / 1e3);
|
|
768
439
|
return claims.exp <= now;
|
|
769
440
|
}
|
|
770
|
-
/**
|
|
771
|
-
* Get the claims from a token without verification.
|
|
772
|
-
*
|
|
773
|
-
* @remarks Local decode only — no network call
|
|
774
|
-
*/
|
|
441
|
+
/** Get the claims from a token without verification. */
|
|
775
442
|
getClaims(token) {
|
|
776
443
|
const claims = this.decode(token);
|
|
777
444
|
if (!claims) {
|
|
@@ -779,11 +446,15 @@ var TokensModule = class {
|
|
|
779
446
|
}
|
|
780
447
|
return claims;
|
|
781
448
|
}
|
|
782
|
-
async
|
|
783
|
-
if (
|
|
784
|
-
|
|
449
|
+
async ensureCache() {
|
|
450
|
+
if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) {
|
|
451
|
+
return this.jwksCache;
|
|
452
|
+
}
|
|
453
|
+
await this.refreshJwks();
|
|
454
|
+
if (!this.jwksCache) {
|
|
455
|
+
throw new IQAuthError("INTERNAL_ERROR", "JWKS cache unavailable after refresh");
|
|
785
456
|
}
|
|
786
|
-
return this.jwksCache
|
|
457
|
+
return this.jwksCache;
|
|
787
458
|
}
|
|
788
459
|
async refreshJwks() {
|
|
789
460
|
if (this.inFlightRefresh) {
|
|
@@ -810,1218 +481,30 @@ var TokensModule = class {
|
|
|
810
481
|
"Malformed JWKS response: expected { keys: [...] }"
|
|
811
482
|
);
|
|
812
483
|
}
|
|
813
|
-
const
|
|
484
|
+
const byKid = /* @__PURE__ */ new Set();
|
|
814
485
|
for (const key of jwks.keys) {
|
|
815
|
-
if (!key || typeof key.kid !== "string" || typeof key.n !== "string" || typeof key.e !== "string") {
|
|
486
|
+
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")) {
|
|
816
487
|
throw new IQAuthError(
|
|
817
488
|
"INTERNAL_ERROR",
|
|
818
489
|
"Malformed JWKS response: key missing required fields"
|
|
819
490
|
);
|
|
820
491
|
}
|
|
821
|
-
|
|
822
|
-
keys.set(key.kid, pem);
|
|
492
|
+
byKid.add(key.kid);
|
|
823
493
|
}
|
|
824
|
-
|
|
494
|
+
const verifier = (0, import_jose.createLocalJWKSet)({ keys: jwks.keys });
|
|
495
|
+
this.jwksCache = { raw: jwks.keys, byKid, verifier, fetchedAt: Date.now() };
|
|
825
496
|
} finally {
|
|
826
497
|
this.inFlightRefresh = null;
|
|
827
498
|
}
|
|
828
499
|
})();
|
|
829
500
|
return this.inFlightRefresh;
|
|
830
501
|
}
|
|
831
|
-
jwkToPem(jwk) {
|
|
832
|
-
const keyObject = import_crypto.default.createPublicKey({
|
|
833
|
-
key: {
|
|
834
|
-
kty: jwk.kty,
|
|
835
|
-
n: jwk.n,
|
|
836
|
-
e: jwk.e
|
|
837
|
-
},
|
|
838
|
-
format: "jwk"
|
|
839
|
-
});
|
|
840
|
-
return keyObject.export({ type: "spki", format: "pem" });
|
|
841
|
-
}
|
|
842
502
|
/** @internal Exposed for testing — clears JWKS cache */
|
|
843
503
|
clearCache() {
|
|
844
504
|
this.jwksCache = null;
|
|
845
505
|
}
|
|
846
506
|
};
|
|
847
507
|
|
|
848
|
-
// src/modules/sessions.ts
|
|
849
|
-
var SessionsModule = class {
|
|
850
|
-
constructor(http) {
|
|
851
|
-
this.http = http;
|
|
852
|
-
}
|
|
853
|
-
/**
|
|
854
|
-
* List all active sessions for the current user.
|
|
855
|
-
*
|
|
856
|
-
* @remarks Wraps GET /api/v1/sessions
|
|
857
|
-
*/
|
|
858
|
-
async list() {
|
|
859
|
-
return this.http.request("GET", "/api/v1/sessions");
|
|
860
|
-
}
|
|
861
|
-
/**
|
|
862
|
-
* Revoke (terminate) a specific session by ID.
|
|
863
|
-
*
|
|
864
|
-
* @remarks Wraps DELETE /api/v1/sessions/:sessionId
|
|
865
|
-
*/
|
|
866
|
-
async revoke(sessionId) {
|
|
867
|
-
return this.http.request("DELETE", `/api/v1/sessions/${sessionId}`);
|
|
868
|
-
}
|
|
869
|
-
/**
|
|
870
|
-
* Revoke all sessions for the current user.
|
|
871
|
-
*
|
|
872
|
-
* @remarks Wraps POST /api/v1/auth/logout-all
|
|
873
|
-
*/
|
|
874
|
-
async revokeAll() {
|
|
875
|
-
return this.http.request("POST", "/api/v1/auth/logout-all");
|
|
876
|
-
}
|
|
877
|
-
};
|
|
878
|
-
|
|
879
|
-
// src/modules/users.ts
|
|
880
|
-
var UsersModule = class {
|
|
881
|
-
constructor(http) {
|
|
882
|
-
this.http = http;
|
|
883
|
-
}
|
|
884
|
-
/**
|
|
885
|
-
* Get the currently authenticated user's profile.
|
|
886
|
-
*
|
|
887
|
-
* @remarks Wraps GET /api/v1/users/me
|
|
888
|
-
*/
|
|
889
|
-
async getCurrent() {
|
|
890
|
-
return this.http.request("GET", "/api/v1/users/me");
|
|
891
|
-
}
|
|
892
|
-
/**
|
|
893
|
-
* Get a user by ID.
|
|
894
|
-
*
|
|
895
|
-
* @remarks Wraps GET /api/v1/users/:id
|
|
896
|
-
*/
|
|
897
|
-
async getById(userId) {
|
|
898
|
-
return this.http.request("GET", `/api/v1/users/${userId}`);
|
|
899
|
-
}
|
|
900
|
-
/**
|
|
901
|
-
* List users in the current tenant. Requires tenant_admin role.
|
|
902
|
-
*
|
|
903
|
-
* @remarks Wraps GET /api/v1/users
|
|
904
|
-
*/
|
|
905
|
-
async list(params) {
|
|
906
|
-
const query = new URLSearchParams();
|
|
907
|
-
if (params?.email) query.set("email", params.email);
|
|
908
|
-
if (params?.tenantId) query.set("tenantId", params.tenantId);
|
|
909
|
-
const qs = query.toString();
|
|
910
|
-
return this.http.request("GET", `/api/v1/users${qs ? `?${qs}` : ""}`);
|
|
911
|
-
}
|
|
912
|
-
/**
|
|
913
|
-
* Provision (create) a new user in a tenant. Requires platform_admin or tenant-scoped API key with admin role.
|
|
914
|
-
*
|
|
915
|
-
* @remarks Wraps POST /api/v1/tenants/:tenantId/users/provision
|
|
916
|
-
*/
|
|
917
|
-
async create(tenantId, data) {
|
|
918
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/provision`, data);
|
|
919
|
-
}
|
|
920
|
-
/**
|
|
921
|
-
* Update the current user's profile (name, picture).
|
|
922
|
-
*
|
|
923
|
-
* @remarks Wraps PATCH /api/v1/users/me
|
|
924
|
-
*/
|
|
925
|
-
async update(data) {
|
|
926
|
-
return this.http.request("PATCH", "/api/v1/users/me", data);
|
|
927
|
-
}
|
|
928
|
-
/**
|
|
929
|
-
* Deactivate a user in the current tenant. Requires tenant_admin role.
|
|
930
|
-
*
|
|
931
|
-
* @remarks Wraps PATCH /api/v1/users/:id/deactivate
|
|
932
|
-
*/
|
|
933
|
-
async deactivate(userId) {
|
|
934
|
-
return this.http.request("PATCH", `/api/v1/users/${userId}/deactivate`);
|
|
935
|
-
}
|
|
936
|
-
/**
|
|
937
|
-
* Reactivate a user in the current tenant. Requires tenant_admin role.
|
|
938
|
-
*
|
|
939
|
-
* @remarks Wraps PATCH /api/v1/users/:id/reactivate
|
|
940
|
-
*/
|
|
941
|
-
async reactivate(userId) {
|
|
942
|
-
return this.http.request("PATCH", `/api/v1/users/${userId}/reactivate`);
|
|
943
|
-
}
|
|
944
|
-
/**
|
|
945
|
-
* Unlock a locked user account in the current tenant. Requires tenant_admin role.
|
|
946
|
-
*
|
|
947
|
-
* @remarks Wraps PATCH /api/v1/users/:id/unlock
|
|
948
|
-
*/
|
|
949
|
-
async unlock(userId) {
|
|
950
|
-
return this.http.request("PATCH", `/api/v1/users/${userId}/unlock`);
|
|
951
|
-
}
|
|
952
|
-
/**
|
|
953
|
-
* Get effective permissions for a user for a specific product.
|
|
954
|
-
*
|
|
955
|
-
* @remarks Wraps GET /api/v1/users/:id/permissions?product=...
|
|
956
|
-
*/
|
|
957
|
-
async getPermissions(userId, product) {
|
|
958
|
-
return this.http.request("GET", `/api/v1/users/${userId}/permissions?product=${encodeURIComponent(product)}`);
|
|
959
|
-
}
|
|
960
|
-
/**
|
|
961
|
-
* Change the current user's password.
|
|
962
|
-
*
|
|
963
|
-
* @remarks Wraps POST /api/v1/auth/password/change
|
|
964
|
-
*/
|
|
965
|
-
async updatePassword(currentPassword, newPassword) {
|
|
966
|
-
return this.http.request("POST", "/api/v1/auth/password/change", {
|
|
967
|
-
currentPassword,
|
|
968
|
-
newPassword
|
|
969
|
-
});
|
|
970
|
-
}
|
|
971
|
-
};
|
|
972
|
-
|
|
973
|
-
// src/modules/permissions.ts
|
|
974
|
-
var PermissionsModule = class {
|
|
975
|
-
constructor(claimsProvider) {
|
|
976
|
-
this.getClaims = claimsProvider;
|
|
977
|
-
}
|
|
978
|
-
/**
|
|
979
|
-
* Get the roles from the current JWT claims.
|
|
980
|
-
*
|
|
981
|
-
* @remarks Extracted from JWT claim `roles` (string[])
|
|
982
|
-
*/
|
|
983
|
-
getRoles() {
|
|
984
|
-
return this.getClaims()?.roles ?? [];
|
|
985
|
-
}
|
|
986
|
-
/**
|
|
987
|
-
* Get the entitlements from the current JWT claims.
|
|
988
|
-
*
|
|
989
|
-
* @remarks Extracted from JWT claim `entitlements` (string[])
|
|
990
|
-
*/
|
|
991
|
-
getEntitlements() {
|
|
992
|
-
return this.getClaims()?.entitlements ?? [];
|
|
993
|
-
}
|
|
994
|
-
/**
|
|
995
|
-
* Check if the current user has a specific role.
|
|
996
|
-
*
|
|
997
|
-
* @remarks Checks against JWT claim `roles`
|
|
998
|
-
*/
|
|
999
|
-
hasRole(role) {
|
|
1000
|
-
return this.getRoles().includes(role);
|
|
1001
|
-
}
|
|
1002
|
-
/**
|
|
1003
|
-
* Check if the current user has a specific entitlement.
|
|
1004
|
-
*
|
|
1005
|
-
* @remarks Checks against JWT claim `entitlements`
|
|
1006
|
-
*/
|
|
1007
|
-
hasEntitlement(entitlement) {
|
|
1008
|
-
return this.getEntitlements().includes(entitlement);
|
|
1009
|
-
}
|
|
1010
|
-
/**
|
|
1011
|
-
* Check if the current user has all of the specified roles.
|
|
1012
|
-
*
|
|
1013
|
-
* @remarks Checks against JWT claim `roles`
|
|
1014
|
-
*/
|
|
1015
|
-
hasAllRoles(roles) {
|
|
1016
|
-
const userRoles = this.getRoles();
|
|
1017
|
-
return roles.every((r) => userRoles.includes(r));
|
|
1018
|
-
}
|
|
1019
|
-
/**
|
|
1020
|
-
* Check if the current user has any of the specified roles.
|
|
1021
|
-
*
|
|
1022
|
-
* @remarks Checks against JWT claim `roles`
|
|
1023
|
-
*/
|
|
1024
|
-
hasAnyRole(roles) {
|
|
1025
|
-
const userRoles = this.getRoles();
|
|
1026
|
-
return roles.some((r) => userRoles.includes(r));
|
|
1027
|
-
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Check if the current user has all of the specified entitlements.
|
|
1030
|
-
*
|
|
1031
|
-
* @remarks Checks against JWT claim `entitlements`
|
|
1032
|
-
*/
|
|
1033
|
-
hasAllEntitlements(entitlements) {
|
|
1034
|
-
const userEntitlements = this.getEntitlements();
|
|
1035
|
-
return entitlements.every((e) => userEntitlements.includes(e));
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Check if the current user has any of the specified entitlements.
|
|
1039
|
-
*
|
|
1040
|
-
* @remarks Checks against JWT claim `entitlements`
|
|
1041
|
-
*/
|
|
1042
|
-
hasAnyEntitlement(entitlements) {
|
|
1043
|
-
const userEntitlements = this.getEntitlements();
|
|
1044
|
-
return entitlements.some((e) => userEntitlements.includes(e));
|
|
1045
|
-
}
|
|
1046
|
-
};
|
|
1047
|
-
|
|
1048
|
-
// src/modules/oidc.ts
|
|
1049
|
-
var import_crypto2 = __toESM(require("crypto"));
|
|
1050
|
-
var InMemoryOidcStateStore = class {
|
|
1051
|
-
constructor() {
|
|
1052
|
-
this.map = /* @__PURE__ */ new Map();
|
|
1053
|
-
}
|
|
1054
|
-
set(state, value) {
|
|
1055
|
-
this.map.set(state, value);
|
|
1056
|
-
}
|
|
1057
|
-
get(state) {
|
|
1058
|
-
const entry = this.map.get(state);
|
|
1059
|
-
if (!entry) return null;
|
|
1060
|
-
if (entry.expiresAt < Date.now()) {
|
|
1061
|
-
this.map.delete(state);
|
|
1062
|
-
return null;
|
|
1063
|
-
}
|
|
1064
|
-
return entry;
|
|
1065
|
-
}
|
|
1066
|
-
delete(state) {
|
|
1067
|
-
this.map.delete(state);
|
|
1068
|
-
}
|
|
1069
|
-
};
|
|
1070
|
-
var DEFAULT_REQUEST_TTL_MS = 10 * 60 * 1e3;
|
|
1071
|
-
function base64UrlEncode(buf) {
|
|
1072
|
-
return buf.toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
1073
|
-
}
|
|
1074
|
-
var OidcModule = class {
|
|
1075
|
-
constructor(http, baseUrl, options = {}) {
|
|
1076
|
-
this.http = http;
|
|
1077
|
-
this.baseUrl = baseUrl;
|
|
1078
|
-
this.stateStore = options.stateStore ?? new InMemoryOidcStateStore();
|
|
1079
|
-
this.requestTtlMs = options.requestTtlMs ?? DEFAULT_REQUEST_TTL_MS;
|
|
1080
|
-
this.tokensModule = options.tokens;
|
|
1081
|
-
}
|
|
1082
|
-
/** @internal Allow the client to inject its TokensModule after construction. */
|
|
1083
|
-
_setTokensModule(tokens) {
|
|
1084
|
-
if (!this.tokensModule) this.tokensModule = tokens;
|
|
1085
|
-
}
|
|
1086
|
-
/**
|
|
1087
|
-
* Fetch the OpenID Connect discovery document.
|
|
1088
|
-
*
|
|
1089
|
-
* @remarks Wraps GET /.well-known/openid-configuration
|
|
1090
|
-
*/
|
|
1091
|
-
async getDiscovery() {
|
|
1092
|
-
const res = await fetch(`${this.baseUrl}/.well-known/openid-configuration`);
|
|
1093
|
-
if (!res.ok) throw new Error(`Failed to fetch OIDC discovery: ${res.status}`);
|
|
1094
|
-
return res.json();
|
|
1095
|
-
}
|
|
1096
|
-
/**
|
|
1097
|
-
* Fetch the JSON Web Key Set.
|
|
1098
|
-
*
|
|
1099
|
-
* @remarks Wraps GET /.well-known/jwks.json
|
|
1100
|
-
*/
|
|
1101
|
-
async getJwks() {
|
|
1102
|
-
const res = await fetch(`${this.baseUrl}/.well-known/jwks.json`);
|
|
1103
|
-
if (!res.ok) throw new Error(`Failed to fetch JWKS: ${res.status}`);
|
|
1104
|
-
return res.json();
|
|
1105
|
-
}
|
|
1106
|
-
/**
|
|
1107
|
-
* Build an OIDC authorization URL for redirect-based login.
|
|
1108
|
-
*
|
|
1109
|
-
* @remarks Constructs URL pointing to /oidc/authorize. Prefer
|
|
1110
|
-
* {@link createAuthRequest} which also generates and stores PKCE/state/nonce.
|
|
1111
|
-
*/
|
|
1112
|
-
buildAuthorizationUrl(params) {
|
|
1113
|
-
const url = new URL("/oidc/authorize", this.baseUrl);
|
|
1114
|
-
url.searchParams.set("response_type", "code");
|
|
1115
|
-
url.searchParams.set("client_id", params.clientId);
|
|
1116
|
-
url.searchParams.set("redirect_uri", params.redirectUri);
|
|
1117
|
-
url.searchParams.set("scope", params.scope || "openid");
|
|
1118
|
-
url.searchParams.set("state", params.state);
|
|
1119
|
-
if (params.nonce) url.searchParams.set("nonce", params.nonce);
|
|
1120
|
-
if (params.codeChallenge) url.searchParams.set("code_challenge", params.codeChallenge);
|
|
1121
|
-
if (params.codeChallengeMethod) url.searchParams.set("code_challenge_method", params.codeChallengeMethod);
|
|
1122
|
-
return url.toString();
|
|
1123
|
-
}
|
|
1124
|
-
/**
|
|
1125
|
-
* Generate `code_verifier`, `code_challenge`, `state`, and `nonce`, persist
|
|
1126
|
-
* them via the configured storage adapter, and return an authorization URL
|
|
1127
|
-
* ready to redirect the user to.
|
|
1128
|
-
*/
|
|
1129
|
-
async createAuthRequest(params) {
|
|
1130
|
-
const codeVerifier = base64UrlEncode(import_crypto2.default.randomBytes(32));
|
|
1131
|
-
const codeChallenge = base64UrlEncode(
|
|
1132
|
-
import_crypto2.default.createHash("sha256").update(codeVerifier).digest()
|
|
1133
|
-
);
|
|
1134
|
-
const state = base64UrlEncode(import_crypto2.default.randomBytes(16));
|
|
1135
|
-
const nonce = base64UrlEncode(import_crypto2.default.randomBytes(16));
|
|
1136
|
-
await this.stateStore.set(state, {
|
|
1137
|
-
codeVerifier,
|
|
1138
|
-
state,
|
|
1139
|
-
nonce,
|
|
1140
|
-
redirectUri: params.redirectUri,
|
|
1141
|
-
clientId: params.clientId,
|
|
1142
|
-
expiresAt: Date.now() + this.requestTtlMs
|
|
1143
|
-
});
|
|
1144
|
-
const authorizationUrl = this.buildAuthorizationUrl({
|
|
1145
|
-
clientId: params.clientId,
|
|
1146
|
-
redirectUri: params.redirectUri,
|
|
1147
|
-
scope: params.scope,
|
|
1148
|
-
state,
|
|
1149
|
-
nonce,
|
|
1150
|
-
codeChallenge,
|
|
1151
|
-
codeChallengeMethod: "S256"
|
|
1152
|
-
});
|
|
1153
|
-
return {
|
|
1154
|
-
authorizationUrl,
|
|
1155
|
-
state,
|
|
1156
|
-
nonce,
|
|
1157
|
-
codeVerifier,
|
|
1158
|
-
codeChallenge,
|
|
1159
|
-
codeChallengeMethod: "S256"
|
|
1160
|
-
};
|
|
1161
|
-
}
|
|
1162
|
-
/**
|
|
1163
|
-
* Validate the callback `state`, exchange the code with the bound PKCE
|
|
1164
|
-
* verifier, and verify that the returned `id_token` (if any) carries the
|
|
1165
|
-
* stored `nonce` and `aud === clientId`.
|
|
1166
|
-
*/
|
|
1167
|
-
async handleCallback(params) {
|
|
1168
|
-
if (!params.state) {
|
|
1169
|
-
throw new IQAuthError("VALIDATION_ERROR", "OIDC callback missing state parameter");
|
|
1170
|
-
}
|
|
1171
|
-
if (!params.code) {
|
|
1172
|
-
throw new IQAuthError("VALIDATION_ERROR", "OIDC callback missing code parameter");
|
|
1173
|
-
}
|
|
1174
|
-
const stored = await this.stateStore.get(params.state);
|
|
1175
|
-
if (!stored) {
|
|
1176
|
-
throw new IQAuthError("VALIDATION_ERROR", "Unknown or expired OIDC state");
|
|
1177
|
-
}
|
|
1178
|
-
let tokens;
|
|
1179
|
-
try {
|
|
1180
|
-
tokens = await this.exchangeCode({
|
|
1181
|
-
code: params.code,
|
|
1182
|
-
redirectUri: stored.redirectUri,
|
|
1183
|
-
clientId: stored.clientId,
|
|
1184
|
-
clientSecret: params.clientSecret,
|
|
1185
|
-
codeVerifier: stored.codeVerifier
|
|
1186
|
-
});
|
|
1187
|
-
} finally {
|
|
1188
|
-
await this.stateStore.delete(params.state);
|
|
1189
|
-
}
|
|
1190
|
-
let idTokenClaims = null;
|
|
1191
|
-
if (tokens.id_token) {
|
|
1192
|
-
if (!this.tokensModule) {
|
|
1193
|
-
throw new IQAuthError(
|
|
1194
|
-
"INTERNAL_ERROR",
|
|
1195
|
-
"OIDC handleCallback received an id_token but no TokensModule is configured for verification"
|
|
1196
|
-
);
|
|
1197
|
-
}
|
|
1198
|
-
idTokenClaims = await this.tokensModule.verify(tokens.id_token, {
|
|
1199
|
-
audience: stored.clientId
|
|
1200
|
-
});
|
|
1201
|
-
const claimsBag = idTokenClaims;
|
|
1202
|
-
const tokenNonce = typeof claimsBag.nonce === "string" ? claimsBag.nonce : void 0;
|
|
1203
|
-
if (!tokenNonce || tokenNonce !== stored.nonce) {
|
|
1204
|
-
throw new IQAuthError(
|
|
1205
|
-
"TOKEN_INVALID",
|
|
1206
|
-
"OIDC id_token nonce did not match the stored value"
|
|
1207
|
-
);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
return { tokens, idTokenClaims };
|
|
1211
|
-
}
|
|
1212
|
-
/**
|
|
1213
|
-
* Exchange an authorization code for tokens at the OIDC token endpoint.
|
|
1214
|
-
*
|
|
1215
|
-
* @remarks Wraps POST /oidc/token (or /api/v1/oidc/token)
|
|
1216
|
-
*/
|
|
1217
|
-
async exchangeCode(params) {
|
|
1218
|
-
const body = {
|
|
1219
|
-
grant_type: "authorization_code",
|
|
1220
|
-
code: params.code,
|
|
1221
|
-
redirect_uri: params.redirectUri,
|
|
1222
|
-
client_id: params.clientId
|
|
1223
|
-
};
|
|
1224
|
-
if (params.clientSecret) body.client_secret = params.clientSecret;
|
|
1225
|
-
if (params.codeVerifier) body.code_verifier = params.codeVerifier;
|
|
1226
|
-
const res = await fetch(`${this.baseUrl}/oidc/token`, {
|
|
1227
|
-
method: "POST",
|
|
1228
|
-
headers: { "Content-Type": "application/json" },
|
|
1229
|
-
body: JSON.stringify(body)
|
|
1230
|
-
});
|
|
1231
|
-
if (!res.ok) {
|
|
1232
|
-
const err = await res.json().catch(() => ({}));
|
|
1233
|
-
throw new Error(
|
|
1234
|
-
`OIDC token exchange failed: ${err.error_description || err.error || res.status}`
|
|
1235
|
-
);
|
|
1236
|
-
}
|
|
1237
|
-
return res.json();
|
|
1238
|
-
}
|
|
1239
|
-
/**
|
|
1240
|
-
* Get user info from the OIDC userinfo endpoint.
|
|
1241
|
-
*
|
|
1242
|
-
* @remarks Wraps GET /oidc/userinfo (requires Bearer token)
|
|
1243
|
-
*/
|
|
1244
|
-
async getUserInfo() {
|
|
1245
|
-
if (this.http.isBrowserSession()) {
|
|
1246
|
-
throw new Error("oidc.getUserInfo() requires a bearer token and is not supported in browser_session mode.");
|
|
1247
|
-
}
|
|
1248
|
-
return this.http.requestRaw("GET", "/oidc/userinfo");
|
|
1249
|
-
}
|
|
1250
|
-
async getClientContext(clientId) {
|
|
1251
|
-
const res = await fetch(`${this.baseUrl}/oidc/client-context?client_id=${encodeURIComponent(clientId)}`);
|
|
1252
|
-
if (!res.ok) throw new Error(`Failed to fetch OIDC client context: ${res.status}`);
|
|
1253
|
-
const payload = await res.json();
|
|
1254
|
-
if (!payload.success || !payload.data) {
|
|
1255
|
-
throw new Error("OIDC client context response was invalid");
|
|
1256
|
-
}
|
|
1257
|
-
return payload.data;
|
|
1258
|
-
}
|
|
1259
|
-
};
|
|
1260
|
-
|
|
1261
|
-
// src/modules/tenants.ts
|
|
1262
|
-
function normalizeBrandingConfig(config) {
|
|
1263
|
-
return {
|
|
1264
|
-
...config,
|
|
1265
|
-
brandName: config.brandName ?? config.companyName,
|
|
1266
|
-
loginHeadline: config.loginHeadline ?? config.headline ?? null,
|
|
1267
|
-
loginSubheadline: config.loginSubheadline ?? config.subheadline ?? null,
|
|
1268
|
-
companyName: config.companyName ?? config.brandName,
|
|
1269
|
-
headline: config.headline ?? config.loginHeadline ?? null,
|
|
1270
|
-
subheadline: config.subheadline ?? config.loginSubheadline ?? null
|
|
1271
|
-
};
|
|
1272
|
-
}
|
|
1273
|
-
var TenantsModule = class {
|
|
1274
|
-
constructor(http) {
|
|
1275
|
-
this.http = http;
|
|
1276
|
-
}
|
|
1277
|
-
async getCurrent(tenantId) {
|
|
1278
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}`);
|
|
1279
|
-
}
|
|
1280
|
-
async get(tenantId) {
|
|
1281
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}`);
|
|
1282
|
-
}
|
|
1283
|
-
async list(params) {
|
|
1284
|
-
const query = new URLSearchParams();
|
|
1285
|
-
if (params?.vendorId) query.set("vendorId", params.vendorId);
|
|
1286
|
-
const qs = query.toString();
|
|
1287
|
-
return this.http.request("GET", `/api/v1/tenants${qs ? `?${qs}` : ""}`);
|
|
1288
|
-
}
|
|
1289
|
-
async create(data) {
|
|
1290
|
-
return this.http.request("POST", "/api/v1/tenants", data);
|
|
1291
|
-
}
|
|
1292
|
-
async update(tenantId, data) {
|
|
1293
|
-
return this.http.request("PATCH", `/api/v1/tenants/${tenantId}`, data);
|
|
1294
|
-
}
|
|
1295
|
-
async delete(tenantId) {
|
|
1296
|
-
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}`);
|
|
1297
|
-
}
|
|
1298
|
-
async promoteToVendor(tenantId, data) {
|
|
1299
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/promote-to-vendor`, data);
|
|
1300
|
-
}
|
|
1301
|
-
async getUsers(tenantId) {
|
|
1302
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}/users`);
|
|
1303
|
-
}
|
|
1304
|
-
async inviteUser(tenantId, data) {
|
|
1305
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/invite`, data);
|
|
1306
|
-
}
|
|
1307
|
-
async changeUserRole(tenantId, userId, role) {
|
|
1308
|
-
return this.http.request("PATCH", `/api/v1/tenants/${tenantId}/users/${userId}/role`, { role });
|
|
1309
|
-
}
|
|
1310
|
-
async migrateUser(tenantId, userId, data) {
|
|
1311
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/migrate`, data);
|
|
1312
|
-
}
|
|
1313
|
-
async removeUser(tenantId, userId) {
|
|
1314
|
-
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/users/${userId}`);
|
|
1315
|
-
}
|
|
1316
|
-
async getPasswordPolicy(tenantId) {
|
|
1317
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}/password-policy`);
|
|
1318
|
-
}
|
|
1319
|
-
async updatePasswordPolicy(tenantId, data) {
|
|
1320
|
-
return this.http.request("PATCH", `/api/v1/tenants/${tenantId}/password-policy`, data);
|
|
1321
|
-
}
|
|
1322
|
-
async getMfaPolicies(tenantId) {
|
|
1323
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}/mfa-policies`);
|
|
1324
|
-
}
|
|
1325
|
-
async updateMfaPolicy(tenantId, role, data) {
|
|
1326
|
-
return this.http.request("PATCH", `/api/v1/tenants/${tenantId}/mfa-policies/${role}`, data);
|
|
1327
|
-
}
|
|
1328
|
-
async getPublicBranding(params) {
|
|
1329
|
-
const query = new URLSearchParams();
|
|
1330
|
-
if (params?.vendor) query.set("vendor", params.vendor);
|
|
1331
|
-
const qs = query.toString();
|
|
1332
|
-
const url = `${this.http.baseUrl}/api/public/branding${qs ? `?${qs}` : ""}`;
|
|
1333
|
-
const res = await fetch(url);
|
|
1334
|
-
if (!res.ok) return null;
|
|
1335
|
-
const body = await res.json();
|
|
1336
|
-
if (body.success && body.data) return normalizeBrandingConfig(body.data);
|
|
1337
|
-
return null;
|
|
1338
|
-
}
|
|
1339
|
-
async getPublicBrandingBySlug(vendorSlug) {
|
|
1340
|
-
const url = `${this.http.baseUrl}/api/public/branding/by-slug/${encodeURIComponent(vendorSlug)}`;
|
|
1341
|
-
const res = await fetch(url);
|
|
1342
|
-
if (!res.ok) return null;
|
|
1343
|
-
const body = await res.json();
|
|
1344
|
-
if (body.success && body.data) return normalizeBrandingConfig(body.data);
|
|
1345
|
-
return null;
|
|
1346
|
-
}
|
|
1347
|
-
};
|
|
1348
|
-
|
|
1349
|
-
// src/modules/apps.ts
|
|
1350
|
-
var AppsModule = class {
|
|
1351
|
-
constructor(http) {
|
|
1352
|
-
this.http = http;
|
|
1353
|
-
}
|
|
1354
|
-
/**
|
|
1355
|
-
* Self-service Phase A: provision an app + OIDC client + key pair in one shot.
|
|
1356
|
-
* Caller must be authenticated as a tenant_admin (or platform_admin).
|
|
1357
|
-
*
|
|
1358
|
-
* @remarks Wraps POST /api/v1/apps
|
|
1359
|
-
*/
|
|
1360
|
-
async create(data) {
|
|
1361
|
-
return this.http.request("POST", "/api/v1/apps", data);
|
|
1362
|
-
}
|
|
1363
|
-
async update(id, data) {
|
|
1364
|
-
return this.http.request("PATCH", `/api/v1/apps/${encodeURIComponent(id)}`, data);
|
|
1365
|
-
}
|
|
1366
|
-
async remove(id) {
|
|
1367
|
-
return this.http.request("DELETE", `/api/v1/apps/${encodeURIComponent(id)}`);
|
|
1368
|
-
}
|
|
1369
|
-
async listOrigins(id) {
|
|
1370
|
-
return this.http.request("GET", `/api/v1/apps/${encodeURIComponent(id)}/origins`);
|
|
1371
|
-
}
|
|
1372
|
-
async addOrigin(id, origin) {
|
|
1373
|
-
return this.http.request("POST", `/api/v1/apps/${encodeURIComponent(id)}/origins`, { origin });
|
|
1374
|
-
}
|
|
1375
|
-
async removeOrigin(id, origin) {
|
|
1376
|
-
return this.http.request("DELETE", `/api/v1/apps/${encodeURIComponent(id)}/origins/${encodeURIComponent(origin)}`);
|
|
1377
|
-
}
|
|
1378
|
-
async listKeys(id) {
|
|
1379
|
-
return this.http.request("GET", `/api/v1/apps/${encodeURIComponent(id)}/keys`);
|
|
1380
|
-
}
|
|
1381
|
-
async createKey(id, data) {
|
|
1382
|
-
return this.http.request("POST", `/api/v1/apps/${encodeURIComponent(id)}/keys`, data);
|
|
1383
|
-
}
|
|
1384
|
-
async rotateKey(id, keyId) {
|
|
1385
|
-
return this.http.request("POST", `/api/v1/apps/${encodeURIComponent(id)}/keys/${encodeURIComponent(keyId)}/rotate`, {});
|
|
1386
|
-
}
|
|
1387
|
-
async revokeKey(id, keyId) {
|
|
1388
|
-
return this.http.request("DELETE", `/api/v1/apps/${encodeURIComponent(id)}/keys/${encodeURIComponent(keyId)}`);
|
|
1389
|
-
}
|
|
1390
|
-
/**
|
|
1391
|
-
* List all registered applications.
|
|
1392
|
-
* Requires `apps.view` permission on the `iqauth-admin` app.
|
|
1393
|
-
*
|
|
1394
|
-
* @remarks Wraps GET /api/v1/apps
|
|
1395
|
-
*/
|
|
1396
|
-
async list() {
|
|
1397
|
-
return this.http.request("GET", "/api/v1/apps");
|
|
1398
|
-
}
|
|
1399
|
-
/**
|
|
1400
|
-
* Get a registered application by its unique key, including its permission nodes.
|
|
1401
|
-
* Requires `apps.view` permission on the `iqauth-admin` app.
|
|
1402
|
-
*
|
|
1403
|
-
* @remarks Wraps GET /api/v1/apps/:appKey
|
|
1404
|
-
*/
|
|
1405
|
-
async get(appKey) {
|
|
1406
|
-
return this.http.request("GET", `/api/v1/apps/${encodeURIComponent(appKey)}`);
|
|
1407
|
-
}
|
|
1408
|
-
/**
|
|
1409
|
-
* Register or sync an application manifest. This is an idempotent upsert —
|
|
1410
|
-
* it creates the app if it doesn't exist, or updates it if it does.
|
|
1411
|
-
* Permission nodes are also upserted (created or updated) from the manifest tree.
|
|
1412
|
-
*
|
|
1413
|
-
* Requires `platform_admin` role and `apps.manage` permission.
|
|
1414
|
-
*
|
|
1415
|
-
* @remarks Wraps POST /api/v1/apps/sync
|
|
1416
|
-
*/
|
|
1417
|
-
async register(manifest) {
|
|
1418
|
-
if (!this.http.hasCredentials()) {
|
|
1419
|
-
throw new IQAuthError(
|
|
1420
|
-
"AUTH_REQUIRED",
|
|
1421
|
-
"Cannot sync app manifest: no API key or access token configured. Initialize the client with an apiKey or accessToken.",
|
|
1422
|
-
401
|
|
1423
|
-
);
|
|
1424
|
-
}
|
|
1425
|
-
return this.http.request("POST", "/api/v1/apps/sync", manifest);
|
|
1426
|
-
}
|
|
1427
|
-
/**
|
|
1428
|
-
* Check if an application is registered by its key.
|
|
1429
|
-
* Returns `true` if the app exists, `false` otherwise.
|
|
1430
|
-
*
|
|
1431
|
-
* @remarks Uses GET /api/v1/apps/:appKey — catches 404 errors
|
|
1432
|
-
*/
|
|
1433
|
-
async isRegistered(appKey) {
|
|
1434
|
-
try {
|
|
1435
|
-
await this.get(appKey);
|
|
1436
|
-
return true;
|
|
1437
|
-
} catch (err) {
|
|
1438
|
-
if (err.code === "NOT_FOUND" || err.status === 404) {
|
|
1439
|
-
return false;
|
|
1440
|
-
}
|
|
1441
|
-
throw err;
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
};
|
|
1445
|
-
|
|
1446
|
-
// src/modules/roles.ts
|
|
1447
|
-
var RolesModule = class {
|
|
1448
|
-
constructor(http) {
|
|
1449
|
-
this.http = http;
|
|
1450
|
-
}
|
|
1451
|
-
async list(tenantId) {
|
|
1452
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}/roles`);
|
|
1453
|
-
}
|
|
1454
|
-
async create(tenantId, data) {
|
|
1455
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/roles`, data);
|
|
1456
|
-
}
|
|
1457
|
-
async update(tenantId, roleId, data) {
|
|
1458
|
-
return this.http.request("PATCH", `/api/v1/tenants/${tenantId}/roles/${roleId}`, data);
|
|
1459
|
-
}
|
|
1460
|
-
async delete(tenantId, roleId) {
|
|
1461
|
-
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/roles/${roleId}`);
|
|
1462
|
-
}
|
|
1463
|
-
async getUserRoles(tenantId, userId) {
|
|
1464
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/roles`);
|
|
1465
|
-
}
|
|
1466
|
-
async assignRole(tenantId, userId, data) {
|
|
1467
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/roles`, data);
|
|
1468
|
-
}
|
|
1469
|
-
async removeRole(tenantId, userId, roleId) {
|
|
1470
|
-
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/users/${userId}/roles/${roleId}`);
|
|
1471
|
-
}
|
|
1472
|
-
};
|
|
1473
|
-
|
|
1474
|
-
// src/modules/permissionGroups.ts
|
|
1475
|
-
var PermissionGroupsModule = class {
|
|
1476
|
-
constructor(http) {
|
|
1477
|
-
this.http = http;
|
|
1478
|
-
}
|
|
1479
|
-
async list(tenantId) {
|
|
1480
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}/permission-groups`);
|
|
1481
|
-
}
|
|
1482
|
-
async create(tenantId, name, description) {
|
|
1483
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/permission-groups`, { name, description });
|
|
1484
|
-
}
|
|
1485
|
-
async update(tenantId, groupId, data) {
|
|
1486
|
-
return this.http.request("PATCH", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}`, data);
|
|
1487
|
-
}
|
|
1488
|
-
async delete(tenantId, groupId) {
|
|
1489
|
-
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}`);
|
|
1490
|
-
}
|
|
1491
|
-
async getPermissions(tenantId, groupId) {
|
|
1492
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions`);
|
|
1493
|
-
}
|
|
1494
|
-
async addPermission(tenantId, groupId, data) {
|
|
1495
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions`, data);
|
|
1496
|
-
}
|
|
1497
|
-
async removePermission(tenantId, groupId, permissionId) {
|
|
1498
|
-
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions/${permissionId}`);
|
|
1499
|
-
}
|
|
1500
|
-
async addInheritance(tenantId, groupId, inheritsFromGroupId) {
|
|
1501
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/inherit`, { inheritsFromGroupId });
|
|
1502
|
-
}
|
|
1503
|
-
async removeInheritance(tenantId, groupId, inheritedGroupId) {
|
|
1504
|
-
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/inherit/${inheritedGroupId}`);
|
|
1505
|
-
}
|
|
1506
|
-
async getUserGroups(tenantId, userId) {
|
|
1507
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/groups`);
|
|
1508
|
-
}
|
|
1509
|
-
async assignUserToGroup(tenantId, userId, groupId) {
|
|
1510
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/groups`, { groupId });
|
|
1511
|
-
}
|
|
1512
|
-
async removeUserFromGroup(tenantId, userId, groupId) {
|
|
1513
|
-
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/users/${userId}/groups/${groupId}`);
|
|
1514
|
-
}
|
|
1515
|
-
async getUserOverrides(tenantId, userId) {
|
|
1516
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides`);
|
|
1517
|
-
}
|
|
1518
|
-
async addUserOverride(tenantId, userId, data) {
|
|
1519
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides`, data);
|
|
1520
|
-
}
|
|
1521
|
-
async removeUserOverride(tenantId, userId, overrideId) {
|
|
1522
|
-
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides/${overrideId}`);
|
|
1523
|
-
}
|
|
1524
|
-
async getEffectivePermissions(tenantId, userId, params) {
|
|
1525
|
-
const query = new URLSearchParams();
|
|
1526
|
-
if (params.product) query.set("product", params.product);
|
|
1527
|
-
if (params.appKey) query.set("appKey", params.appKey);
|
|
1528
|
-
const qs = query.toString();
|
|
1529
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/effective${qs ? `?${qs}` : ""}`);
|
|
1530
|
-
}
|
|
1531
|
-
async checkPermission(tenantId, userId, appKey, nodeKey) {
|
|
1532
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/check`, { appKey, nodeKey });
|
|
1533
|
-
}
|
|
1534
|
-
};
|
|
1535
|
-
|
|
1536
|
-
// src/modules/apiKeys.ts
|
|
1537
|
-
var ApiKeysModule = class {
|
|
1538
|
-
constructor(http) {
|
|
1539
|
-
this.http = http;
|
|
1540
|
-
}
|
|
1541
|
-
async create(data) {
|
|
1542
|
-
return this.http.request("POST", "/api/v1/api-keys", data);
|
|
1543
|
-
}
|
|
1544
|
-
async list(params) {
|
|
1545
|
-
const query = new URLSearchParams();
|
|
1546
|
-
if (params?.tenantId) query.set("tenantId", params.tenantId);
|
|
1547
|
-
const qs = query.toString();
|
|
1548
|
-
return this.http.request("GET", `/api/v1/api-keys${qs ? `?${qs}` : ""}`);
|
|
1549
|
-
}
|
|
1550
|
-
async revoke(id) {
|
|
1551
|
-
return this.http.request("DELETE", `/api/v1/api-keys/${id}`);
|
|
1552
|
-
}
|
|
1553
|
-
async introspect(apiKey) {
|
|
1554
|
-
return this.http.request("POST", "/api/v1/api-keys/introspect", { apiKey }, { skipAutoRefresh: true });
|
|
1555
|
-
}
|
|
1556
|
-
};
|
|
1557
|
-
|
|
1558
|
-
// src/modules/invites.ts
|
|
1559
|
-
var InvitesModule = class {
|
|
1560
|
-
constructor(http) {
|
|
1561
|
-
this.http = http;
|
|
1562
|
-
}
|
|
1563
|
-
async create(data) {
|
|
1564
|
-
return this.http.request("POST", "/api/v1/invites", data);
|
|
1565
|
-
}
|
|
1566
|
-
async validate(token) {
|
|
1567
|
-
return this.http.request("GET", `/api/v1/invites/${token}/validate`);
|
|
1568
|
-
}
|
|
1569
|
-
async accept(token, data) {
|
|
1570
|
-
return this.http.request("POST", `/api/v1/invites/${token}/accept`, data, { skipAutoRefresh: true });
|
|
1571
|
-
}
|
|
1572
|
-
};
|
|
1573
|
-
|
|
1574
|
-
// src/modules/webhooks.ts
|
|
1575
|
-
function normalizeWebhookDelivery(delivery) {
|
|
1576
|
-
return {
|
|
1577
|
-
...delivery,
|
|
1578
|
-
endpointId: delivery.endpointId ?? delivery.webhookEndpointId,
|
|
1579
|
-
webhookEndpointId: delivery.webhookEndpointId ?? delivery.endpointId,
|
|
1580
|
-
event: delivery.event ?? delivery.eventType,
|
|
1581
|
-
eventType: delivery.eventType ?? delivery.event,
|
|
1582
|
-
statusCode: delivery.statusCode ?? delivery.responseStatus ?? null,
|
|
1583
|
-
responseStatus: delivery.responseStatus ?? delivery.statusCode ?? null,
|
|
1584
|
-
response: delivery.response ?? delivery.responseBody ?? null,
|
|
1585
|
-
responseBody: delivery.responseBody ?? delivery.response ?? null,
|
|
1586
|
-
deliveredAt: delivery.deliveredAt ?? delivery.createdAt
|
|
1587
|
-
};
|
|
1588
|
-
}
|
|
1589
|
-
var WebhooksModule = class {
|
|
1590
|
-
constructor(http) {
|
|
1591
|
-
this.http = http;
|
|
1592
|
-
}
|
|
1593
|
-
async createEndpoint(data) {
|
|
1594
|
-
return this.http.request("POST", "/api/v1/webhooks/endpoints", data);
|
|
1595
|
-
}
|
|
1596
|
-
async listEndpoints() {
|
|
1597
|
-
return this.http.request("GET", "/api/v1/webhooks/endpoints");
|
|
1598
|
-
}
|
|
1599
|
-
async deleteEndpoint(id) {
|
|
1600
|
-
return this.http.request("DELETE", `/api/v1/webhooks/endpoints/${id}`);
|
|
1601
|
-
}
|
|
1602
|
-
async getDeliveries(endpointId) {
|
|
1603
|
-
const deliveries = await this.http.request("GET", `/api/v1/webhooks/deliveries?endpointId=${encodeURIComponent(endpointId)}`);
|
|
1604
|
-
return deliveries.map(normalizeWebhookDelivery);
|
|
1605
|
-
}
|
|
1606
|
-
async testEndpoint(id) {
|
|
1607
|
-
return this.http.request("POST", `/api/v1/webhooks/endpoints/${id}/test`);
|
|
1608
|
-
}
|
|
1609
|
-
async rotateSecret(id) {
|
|
1610
|
-
return this.http.request("POST", `/api/v1/webhooks/endpoints/${id}/rotate-secret`);
|
|
1611
|
-
}
|
|
1612
|
-
};
|
|
1613
|
-
|
|
1614
|
-
// src/modules/entitlements.ts
|
|
1615
|
-
var EntitlementsModule = class {
|
|
1616
|
-
constructor(http) {
|
|
1617
|
-
this.http = http;
|
|
1618
|
-
}
|
|
1619
|
-
async list(tenantId) {
|
|
1620
|
-
return this.http.request("GET", `/api/v1/tenants/${tenantId}/entitlements`);
|
|
1621
|
-
}
|
|
1622
|
-
async grant(tenantId, data) {
|
|
1623
|
-
return this.http.request("POST", `/api/v1/tenants/${tenantId}/entitlements`, data);
|
|
1624
|
-
}
|
|
1625
|
-
async revoke(tenantId, product) {
|
|
1626
|
-
return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/entitlements/${encodeURIComponent(product)}`);
|
|
1627
|
-
}
|
|
1628
|
-
};
|
|
1629
|
-
|
|
1630
|
-
// src/modules/vendors.ts
|
|
1631
|
-
var VendorsModule = class {
|
|
1632
|
-
constructor(http) {
|
|
1633
|
-
this.http = http;
|
|
1634
|
-
}
|
|
1635
|
-
async list() {
|
|
1636
|
-
return this.http.request("GET", "/api/v1/vendors");
|
|
1637
|
-
}
|
|
1638
|
-
async get(vendorId) {
|
|
1639
|
-
return this.http.request("GET", `/api/v1/vendors/${vendorId}`);
|
|
1640
|
-
}
|
|
1641
|
-
async create(data) {
|
|
1642
|
-
return this.http.request("POST", "/api/v1/vendors", data);
|
|
1643
|
-
}
|
|
1644
|
-
async update(vendorId, data) {
|
|
1645
|
-
return this.http.request("PATCH", `/api/v1/vendors/${vendorId}`, data);
|
|
1646
|
-
}
|
|
1647
|
-
async delete(vendorId) {
|
|
1648
|
-
return this.http.request("DELETE", `/api/v1/vendors/${vendorId}`);
|
|
1649
|
-
}
|
|
1650
|
-
};
|
|
1651
|
-
|
|
1652
|
-
// src/modules/sources.ts
|
|
1653
|
-
var SourcesModule = class {
|
|
1654
|
-
constructor(http) {
|
|
1655
|
-
this.http = http;
|
|
1656
|
-
}
|
|
1657
|
-
async create(vendorId, data) {
|
|
1658
|
-
return this.http.request("POST", `/api/v1/vendors/${vendorId}/sources`, data);
|
|
1659
|
-
}
|
|
1660
|
-
async listForVendor(vendorId) {
|
|
1661
|
-
return this.http.request("GET", `/api/v1/vendors/${vendorId}/sources`);
|
|
1662
|
-
}
|
|
1663
|
-
async get(sourceId) {
|
|
1664
|
-
return this.http.request("GET", `/api/v1/sources/${sourceId}`);
|
|
1665
|
-
}
|
|
1666
|
-
async update(sourceId, data) {
|
|
1667
|
-
return this.http.request("PATCH", `/api/v1/sources/${sourceId}`, data);
|
|
1668
|
-
}
|
|
1669
|
-
async delete(sourceId) {
|
|
1670
|
-
return this.http.request("DELETE", `/api/v1/sources/${sourceId}`);
|
|
1671
|
-
}
|
|
1672
|
-
async createClient(sourceId, data) {
|
|
1673
|
-
return this.http.request("POST", `/api/v1/sources/${sourceId}/clients`, data);
|
|
1674
|
-
}
|
|
1675
|
-
async listClients(sourceId) {
|
|
1676
|
-
return this.http.request("GET", `/api/v1/sources/${sourceId}/clients`);
|
|
1677
|
-
}
|
|
1678
|
-
};
|
|
1679
|
-
|
|
1680
|
-
// src/modules/clients.ts
|
|
1681
|
-
var ClientsModule = class {
|
|
1682
|
-
constructor(http) {
|
|
1683
|
-
this.http = http;
|
|
1684
|
-
}
|
|
1685
|
-
async get(clientId) {
|
|
1686
|
-
return this.http.request("GET", `/api/v1/clients/${clientId}`);
|
|
1687
|
-
}
|
|
1688
|
-
async update(clientId, data) {
|
|
1689
|
-
return this.http.request("PATCH", `/api/v1/clients/${clientId}`, data);
|
|
1690
|
-
}
|
|
1691
|
-
async delete(clientId) {
|
|
1692
|
-
return this.http.request("DELETE", `/api/v1/clients/${clientId}`);
|
|
1693
|
-
}
|
|
1694
|
-
};
|
|
1695
|
-
|
|
1696
|
-
// src/modules/hierarchy.ts
|
|
1697
|
-
var HierarchyModule = class {
|
|
1698
|
-
constructor(http) {
|
|
1699
|
-
this.http = http;
|
|
1700
|
-
}
|
|
1701
|
-
async getGraph() {
|
|
1702
|
-
return this.http.request("GET", "/api/v1/hierarchy");
|
|
1703
|
-
}
|
|
1704
|
-
async linkVendorSource(vendorId, sourceId) {
|
|
1705
|
-
return this.http.request("POST", "/api/v1/hierarchy/link/vendor-source", { vendorId, sourceId });
|
|
1706
|
-
}
|
|
1707
|
-
async unlinkVendorSource(vendorId, sourceId) {
|
|
1708
|
-
return this.http.request("DELETE", "/api/v1/hierarchy/link/vendor-source", { vendorId, sourceId });
|
|
1709
|
-
}
|
|
1710
|
-
async linkSourceClient(sourceId, clientId) {
|
|
1711
|
-
return this.http.request("POST", "/api/v1/hierarchy/link/source-client", { sourceId, clientId });
|
|
1712
|
-
}
|
|
1713
|
-
async unlinkSourceClient(sourceId, clientId) {
|
|
1714
|
-
return this.http.request("DELETE", "/api/v1/hierarchy/link/source-client", { sourceId, clientId });
|
|
1715
|
-
}
|
|
1716
|
-
};
|
|
1717
|
-
|
|
1718
|
-
// src/modules/memberships.ts
|
|
1719
|
-
var MembershipsModule = class {
|
|
1720
|
-
constructor(http) {
|
|
1721
|
-
this.http = http;
|
|
1722
|
-
}
|
|
1723
|
-
async listForUser(userId, tenantId) {
|
|
1724
|
-
return this.http.request("GET", `/api/v1/users/${userId}/memberships?tenantId=${encodeURIComponent(tenantId)}`);
|
|
1725
|
-
}
|
|
1726
|
-
async listForScope(scopeType, scopeId) {
|
|
1727
|
-
return this.http.request("GET", `/api/v1/memberships/scope/${scopeType}/${scopeId}`);
|
|
1728
|
-
}
|
|
1729
|
-
async grant(data) {
|
|
1730
|
-
return this.http.request("POST", "/api/v1/memberships", data);
|
|
1731
|
-
}
|
|
1732
|
-
async revoke(id) {
|
|
1733
|
-
return this.http.request("DELETE", `/api/v1/memberships/${id}`);
|
|
1734
|
-
}
|
|
1735
|
-
async update(id, data) {
|
|
1736
|
-
return this.http.request("PATCH", `/api/v1/memberships/${id}`, data);
|
|
1737
|
-
}
|
|
1738
|
-
async listForTenant(params) {
|
|
1739
|
-
const query = new URLSearchParams();
|
|
1740
|
-
if (params?.scopeType) query.set("scopeType", params.scopeType);
|
|
1741
|
-
if (params?.roleName) query.set("roleName", params.roleName);
|
|
1742
|
-
const qs = query.toString();
|
|
1743
|
-
return this.http.request("GET", `/api/v1/memberships/tenant${qs ? `?${qs}` : ""}`);
|
|
1744
|
-
}
|
|
1745
|
-
};
|
|
1746
|
-
|
|
1747
|
-
// src/modules/scope.ts
|
|
1748
|
-
var ScopeModule = class {
|
|
1749
|
-
constructor(http) {
|
|
1750
|
-
this.http = http;
|
|
1751
|
-
}
|
|
1752
|
-
async getAvailable() {
|
|
1753
|
-
return this.http.request("GET", "/api/v1/auth/available-scopes");
|
|
1754
|
-
}
|
|
1755
|
-
async switchScope(scopeType, scopeId) {
|
|
1756
|
-
return this.http.request("POST", "/api/v1/auth/switch-scope", { scopeType, scopeId });
|
|
1757
|
-
}
|
|
1758
|
-
};
|
|
1759
|
-
|
|
1760
|
-
// src/modules/gdpr.ts
|
|
1761
|
-
var GdprModule = class {
|
|
1762
|
-
constructor(http) {
|
|
1763
|
-
this.http = http;
|
|
1764
|
-
}
|
|
1765
|
-
async exportData() {
|
|
1766
|
-
return this.http.request("GET", "/api/v1/gdpr/export");
|
|
1767
|
-
}
|
|
1768
|
-
async deleteAccount(confirmEmail) {
|
|
1769
|
-
return this.http.request("POST", "/api/v1/gdpr/delete", { confirmEmail });
|
|
1770
|
-
}
|
|
1771
|
-
};
|
|
1772
|
-
|
|
1773
|
-
// src/modules/pin.ts
|
|
1774
|
-
var PinModule = class {
|
|
1775
|
-
constructor(http) {
|
|
1776
|
-
this.http = http;
|
|
1777
|
-
}
|
|
1778
|
-
async set(pin) {
|
|
1779
|
-
return this.http.request("POST", "/api/v1/pin/set", { pin });
|
|
1780
|
-
}
|
|
1781
|
-
async change(currentPin, newPin) {
|
|
1782
|
-
return this.http.request("POST", "/api/v1/pin/change", { currentPin, newPin });
|
|
1783
|
-
}
|
|
1784
|
-
async remove() {
|
|
1785
|
-
return this.http.request("DELETE", "/api/v1/pin");
|
|
1786
|
-
}
|
|
1787
|
-
async getStatus() {
|
|
1788
|
-
return this.http.request("GET", "/api/v1/pin/status");
|
|
1789
|
-
}
|
|
1790
|
-
async login(email, pin, deviceFingerprint) {
|
|
1791
|
-
const data = await this.http.request(
|
|
1792
|
-
"POST",
|
|
1793
|
-
"/api/v1/pin/login",
|
|
1794
|
-
{ email, pin, ...deviceFingerprint && { deviceFingerprint } },
|
|
1795
|
-
{ skipAutoRefresh: true }
|
|
1796
|
-
);
|
|
1797
|
-
if (data.type === "success" && data.accessToken && data.refreshToken && data.user) {
|
|
1798
|
-
return {
|
|
1799
|
-
status: "authenticated",
|
|
1800
|
-
authMode: "token",
|
|
1801
|
-
tokens: { accessToken: data.accessToken, refreshToken: data.refreshToken },
|
|
1802
|
-
user: data.user
|
|
1803
|
-
};
|
|
1804
|
-
}
|
|
1805
|
-
if (data.type === "mfa_required" && data.mfaChallengeToken) {
|
|
1806
|
-
return {
|
|
1807
|
-
status: "mfa_required",
|
|
1808
|
-
mfaChallengeToken: data.mfaChallengeToken,
|
|
1809
|
-
availableMethods: data.availableMethods || []
|
|
1810
|
-
};
|
|
1811
|
-
}
|
|
1812
|
-
if (data.type === "tenant_selection" && data.tenantSelectionToken && data.tenants) {
|
|
1813
|
-
return {
|
|
1814
|
-
status: "tenant_selection",
|
|
1815
|
-
tenantSelectionToken: data.tenantSelectionToken,
|
|
1816
|
-
tenants: data.tenants
|
|
1817
|
-
};
|
|
1818
|
-
}
|
|
1819
|
-
throw new Error("Unexpected PIN login response shape");
|
|
1820
|
-
}
|
|
1821
|
-
};
|
|
1822
|
-
|
|
1823
|
-
// src/modules/mfa.ts
|
|
1824
|
-
var MfaModule = class {
|
|
1825
|
-
constructor(http) {
|
|
1826
|
-
this.http = http;
|
|
1827
|
-
}
|
|
1828
|
-
async getAvailableMethods() {
|
|
1829
|
-
return this.http.request("GET", "/api/v1/mfa/methods");
|
|
1830
|
-
}
|
|
1831
|
-
async enrollTotp() {
|
|
1832
|
-
return this.http.request("POST", "/api/v1/mfa/enroll/totp");
|
|
1833
|
-
}
|
|
1834
|
-
async verifyTotpEnrollment(code, secret) {
|
|
1835
|
-
return this.http.request("POST", "/api/v1/mfa/enroll/totp/verify", { code, secret });
|
|
1836
|
-
}
|
|
1837
|
-
async enrollSms(phoneNumber) {
|
|
1838
|
-
return this.http.request("POST", "/api/v1/mfa/enroll/sms", { phoneNumber });
|
|
1839
|
-
}
|
|
1840
|
-
async verifySmsEnrollment(code, phoneNumber) {
|
|
1841
|
-
return this.http.request("POST", "/api/v1/mfa/enroll/sms/verify", { code, phoneNumber });
|
|
1842
|
-
}
|
|
1843
|
-
async startEmailEnrollment() {
|
|
1844
|
-
return this.http.request("POST", "/api/v1/mfa/enroll/email/start");
|
|
1845
|
-
}
|
|
1846
|
-
async verifyEmailEnrollment(code) {
|
|
1847
|
-
return this.http.request("POST", "/api/v1/mfa/enroll/email/verify", { code });
|
|
1848
|
-
}
|
|
1849
|
-
async listEnrollments() {
|
|
1850
|
-
return this.http.request("GET", "/api/v1/mfa/enrollments");
|
|
1851
|
-
}
|
|
1852
|
-
async setPrimaryEnrollment(enrollmentId) {
|
|
1853
|
-
return this.http.request("PATCH", `/api/v1/mfa/enrollments/${enrollmentId}/primary`);
|
|
1854
|
-
}
|
|
1855
|
-
async deactivateEnrollment(enrollmentId) {
|
|
1856
|
-
return this.http.request("DELETE", `/api/v1/mfa/enrollments/${enrollmentId}`);
|
|
1857
|
-
}
|
|
1858
|
-
async regenerateBackupCodes() {
|
|
1859
|
-
return this.http.request("POST", "/api/v1/mfa/backup-codes/regenerate");
|
|
1860
|
-
}
|
|
1861
|
-
async getBackupCodeCount() {
|
|
1862
|
-
return this.http.request("GET", "/api/v1/mfa/backup-codes/count");
|
|
1863
|
-
}
|
|
1864
|
-
};
|
|
1865
|
-
|
|
1866
|
-
// src/modules/branding.ts
|
|
1867
|
-
function normalizeBrandingConfig2(config) {
|
|
1868
|
-
return {
|
|
1869
|
-
...config,
|
|
1870
|
-
brandName: config.brandName ?? config.companyName,
|
|
1871
|
-
loginHeadline: config.loginHeadline ?? config.headline ?? null,
|
|
1872
|
-
loginSubheadline: config.loginSubheadline ?? config.subheadline ?? null,
|
|
1873
|
-
companyName: config.companyName ?? config.brandName,
|
|
1874
|
-
headline: config.headline ?? config.loginHeadline ?? null,
|
|
1875
|
-
subheadline: config.subheadline ?? config.loginSubheadline ?? null
|
|
1876
|
-
};
|
|
1877
|
-
}
|
|
1878
|
-
function normalizeBrandingAsset(asset) {
|
|
1879
|
-
return {
|
|
1880
|
-
...asset,
|
|
1881
|
-
url: asset.url ?? asset.publicUrl,
|
|
1882
|
-
publicUrl: asset.publicUrl ?? asset.url
|
|
1883
|
-
};
|
|
1884
|
-
}
|
|
1885
|
-
function normalizeBrandingUpdate(data) {
|
|
1886
|
-
return {
|
|
1887
|
-
...data,
|
|
1888
|
-
brandName: data.brandName ?? data.companyName,
|
|
1889
|
-
loginHeadline: data.loginHeadline ?? data.headline,
|
|
1890
|
-
loginSubheadline: data.loginSubheadline ?? data.subheadline
|
|
1891
|
-
};
|
|
1892
|
-
}
|
|
1893
|
-
var BrandingModule = class {
|
|
1894
|
-
constructor(http) {
|
|
1895
|
-
this.http = http;
|
|
1896
|
-
}
|
|
1897
|
-
async get(vendorId) {
|
|
1898
|
-
const config = await this.http.request("GET", `/api/v1/branding/${vendorId}`);
|
|
1899
|
-
return normalizeBrandingConfig2(config);
|
|
1900
|
-
}
|
|
1901
|
-
async updateBranding(vendorId, data) {
|
|
1902
|
-
const config = await this.http.request("PUT", `/api/v1/branding/${vendorId}`, normalizeBrandingUpdate(data));
|
|
1903
|
-
return normalizeBrandingConfig2(config);
|
|
1904
|
-
}
|
|
1905
|
-
async publishBranding(vendorId) {
|
|
1906
|
-
const config = await this.http.request("POST", `/api/v1/branding/${vendorId}/publish`);
|
|
1907
|
-
return normalizeBrandingConfig2(config);
|
|
1908
|
-
}
|
|
1909
|
-
async unpublishBranding(vendorId) {
|
|
1910
|
-
const config = await this.http.request("POST", `/api/v1/branding/${vendorId}/unpublish`);
|
|
1911
|
-
return normalizeBrandingConfig2(config);
|
|
1912
|
-
}
|
|
1913
|
-
async resetBranding(vendorId) {
|
|
1914
|
-
const config = await this.http.request("POST", `/api/v1/branding/${vendorId}/reset`);
|
|
1915
|
-
return normalizeBrandingConfig2(config);
|
|
1916
|
-
}
|
|
1917
|
-
async uploadAsset(vendorId, data) {
|
|
1918
|
-
const asset = await this.http.request("POST", `/api/v1/branding/${vendorId}/upload`, data);
|
|
1919
|
-
return normalizeBrandingAsset(asset);
|
|
1920
|
-
}
|
|
1921
|
-
async listAssets(vendorId) {
|
|
1922
|
-
const assets = await this.http.request("GET", `/api/v1/branding/${vendorId}/assets`);
|
|
1923
|
-
return assets.map(normalizeBrandingAsset);
|
|
1924
|
-
}
|
|
1925
|
-
async deleteAsset(vendorId, assetId) {
|
|
1926
|
-
return this.http.request("DELETE", `/api/v1/branding/${vendorId}/assets/${assetId}`);
|
|
1927
|
-
}
|
|
1928
|
-
async listDomains(vendorId) {
|
|
1929
|
-
return this.http.request("GET", `/api/v1/branding/${vendorId}/domains`);
|
|
1930
|
-
}
|
|
1931
|
-
async addDomain(vendorId, domain) {
|
|
1932
|
-
return this.http.request("POST", `/api/v1/branding/${vendorId}/domains`, { domain });
|
|
1933
|
-
}
|
|
1934
|
-
async removeDomain(vendorId, domainId) {
|
|
1935
|
-
return this.http.request("DELETE", `/api/v1/branding/${vendorId}/domains/${domainId}`);
|
|
1936
|
-
}
|
|
1937
|
-
};
|
|
1938
|
-
|
|
1939
|
-
// src/client.ts
|
|
1940
|
-
var IQAuthClient = class _IQAuthClient {
|
|
1941
|
-
constructor(config) {
|
|
1942
|
-
this.config = config;
|
|
1943
|
-
this.environment = _IQAuthClient.resolveEnvironment(config);
|
|
1944
|
-
this._accessToken = "accessToken" in config ? config.accessToken : void 0;
|
|
1945
|
-
this._refreshToken = "refreshToken" in config ? config.refreshToken : void 0;
|
|
1946
|
-
this._apiKey = "apiKey" in config ? config.apiKey : void 0;
|
|
1947
|
-
this.httpClient = new HttpClient({
|
|
1948
|
-
baseUrl: config.baseUrl,
|
|
1949
|
-
environment: this.environment,
|
|
1950
|
-
getAccessToken: () => this._accessToken,
|
|
1951
|
-
getRefreshToken: () => this._refreshToken,
|
|
1952
|
-
getApiKey: () => this._apiKey,
|
|
1953
|
-
setTokens: (tokens) => {
|
|
1954
|
-
this._accessToken = tokens.accessToken;
|
|
1955
|
-
this._refreshToken = tokens.refreshToken;
|
|
1956
|
-
},
|
|
1957
|
-
autoRefresh: "autoRefresh" in config ? config.autoRefresh !== false : true,
|
|
1958
|
-
onTokenRefresh: "onTokenRefresh" in config ? config.onTokenRefresh : void 0,
|
|
1959
|
-
sessionHeaderName: config.sessionHeaderName,
|
|
1960
|
-
sessionHeaderValue: config.sessionHeaderValue,
|
|
1961
|
-
retry: config.retry
|
|
1962
|
-
});
|
|
1963
|
-
this.auth = new AuthModule(this.httpClient);
|
|
1964
|
-
this.tokens = new TokensModule(config.baseUrl, {
|
|
1965
|
-
issuer: config.verify?.issuer,
|
|
1966
|
-
audience: config.verify?.audience,
|
|
1967
|
-
clockTolerance: config.verify?.clockTolerance
|
|
1968
|
-
});
|
|
1969
|
-
this.sessions = new SessionsModule(this.httpClient);
|
|
1970
|
-
this.users = new UsersModule(this.httpClient);
|
|
1971
|
-
this.permissions = new PermissionsModule(() => this.getCurrentClaims());
|
|
1972
|
-
this.oidc = new OidcModule(this.httpClient, config.baseUrl, { tokens: this.tokens });
|
|
1973
|
-
this.tenants = new TenantsModule(this.httpClient);
|
|
1974
|
-
this.apps = new AppsModule(this.httpClient);
|
|
1975
|
-
this.roles = new RolesModule(this.httpClient);
|
|
1976
|
-
this.permissionGroups = new PermissionGroupsModule(this.httpClient);
|
|
1977
|
-
this.apiKeys = new ApiKeysModule(this.httpClient);
|
|
1978
|
-
this.invites = new InvitesModule(this.httpClient);
|
|
1979
|
-
this.webhooks = new WebhooksModule(this.httpClient);
|
|
1980
|
-
this.entitlements = new EntitlementsModule(this.httpClient);
|
|
1981
|
-
this.vendors = new VendorsModule(this.httpClient);
|
|
1982
|
-
this.sources = new SourcesModule(this.httpClient);
|
|
1983
|
-
this.clients = new ClientsModule(this.httpClient);
|
|
1984
|
-
this.hierarchy = new HierarchyModule(this.httpClient);
|
|
1985
|
-
this.memberships = new MembershipsModule(this.httpClient);
|
|
1986
|
-
this.scope = new ScopeModule(this.httpClient);
|
|
1987
|
-
this.gdpr = new GdprModule(this.httpClient);
|
|
1988
|
-
this.pin = new PinModule(this.httpClient);
|
|
1989
|
-
this.mfa = new MfaModule(this.httpClient);
|
|
1990
|
-
this.branding = new BrandingModule(this.httpClient);
|
|
1991
|
-
}
|
|
1992
|
-
static forBrowserSession(config) {
|
|
1993
|
-
return new _IQAuthClient({ ...config, environment: "browser_session" });
|
|
1994
|
-
}
|
|
1995
|
-
static forServer(config) {
|
|
1996
|
-
return new _IQAuthClient({ ...config, environment: "server" });
|
|
1997
|
-
}
|
|
1998
|
-
static forMobile(config) {
|
|
1999
|
-
return new _IQAuthClient({ ...config, environment: "mobile" });
|
|
2000
|
-
}
|
|
2001
|
-
static forService(config) {
|
|
2002
|
-
return new _IQAuthClient({ ...config, environment: "service" });
|
|
2003
|
-
}
|
|
2004
|
-
setTokens(tokens) {
|
|
2005
|
-
this._accessToken = tokens.accessToken;
|
|
2006
|
-
this._refreshToken = tokens.refreshToken;
|
|
2007
|
-
}
|
|
2008
|
-
getAccessToken() {
|
|
2009
|
-
return this._accessToken;
|
|
2010
|
-
}
|
|
2011
|
-
getRefreshToken() {
|
|
2012
|
-
return this._refreshToken;
|
|
2013
|
-
}
|
|
2014
|
-
getCurrentClaims() {
|
|
2015
|
-
if (!this._accessToken) return null;
|
|
2016
|
-
return this.tokens.decode(this._accessToken);
|
|
2017
|
-
}
|
|
2018
|
-
static resolveEnvironment(config) {
|
|
2019
|
-
if (config.environment) return config.environment;
|
|
2020
|
-
if (config.apiKey) return "service";
|
|
2021
|
-
return "server";
|
|
2022
|
-
}
|
|
2023
|
-
};
|
|
2024
|
-
|
|
2025
508
|
// src/next.ts
|
|
2026
509
|
function readCookieFromHeader(header, name) {
|
|
2027
510
|
if (!header) return void 0;
|
|
@@ -2068,7 +551,7 @@ function handler(options) {
|
|
|
2068
551
|
if (action === "signout") {
|
|
2069
552
|
const auth = req.headers.get("authorization");
|
|
2070
553
|
const accessToken = auth && auth.replace(/^Bearer /i, "") || readCookieFromHeader(cookieHeader, accessCookie);
|
|
2071
|
-
return toResponse(await handleSignout(helperConfig, { accessToken }));
|
|
554
|
+
return toResponse(await handleSignout(helperConfig, { accessToken, ssoCookieHeader: cookieHeader ?? void 0 }));
|
|
2072
555
|
}
|
|
2073
556
|
return new Response(JSON.stringify({ success: false, error: { code: "NOT_FOUND", message: `Unknown action: ${action}` } }), {
|
|
2074
557
|
status: 404,
|
|
@@ -2080,7 +563,7 @@ function createMiddleware(options) {
|
|
|
2080
563
|
const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/next createMiddleware" });
|
|
2081
564
|
const issuer = (options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
|
|
2082
565
|
const accessCookie = options.accessCookieName ?? "iqauth_at";
|
|
2083
|
-
const
|
|
566
|
+
const tokens = new TokensModule(issuer);
|
|
2084
567
|
return async (req) => {
|
|
2085
568
|
const auth = req.headers.get("authorization");
|
|
2086
569
|
let token;
|
|
@@ -2093,7 +576,7 @@ function createMiddleware(options) {
|
|
|
2093
576
|
});
|
|
2094
577
|
}
|
|
2095
578
|
try {
|
|
2096
|
-
await
|
|
579
|
+
await tokens.verify(token);
|
|
2097
580
|
return void 0;
|
|
2098
581
|
} catch (err) {
|
|
2099
582
|
const code = err.code || "TOKEN_INVALID";
|
|
@@ -2122,9 +605,9 @@ async function getAuth(options) {
|
|
|
2122
605
|
}
|
|
2123
606
|
const token = cookieJar?.get(accessCookie)?.value;
|
|
2124
607
|
if (!token) return null;
|
|
2125
|
-
const
|
|
608
|
+
const tokens = new TokensModule(issuer);
|
|
2126
609
|
try {
|
|
2127
|
-
return await
|
|
610
|
+
return await tokens.verify(token);
|
|
2128
611
|
} catch {
|
|
2129
612
|
return null;
|
|
2130
613
|
}
|