@iqauth/sdk 2.1.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 +43 -3
- 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 +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +69 -7
- package/dist/browser.mjs +2 -2
- package/dist/{chunk-ZESHDJDU.mjs → chunk-EKTNEZIH.mjs} +5 -8
- package/dist/{chunk-JQRTY5MY.mjs → chunk-KGEPDXHU.mjs} +12 -8
- package/dist/{chunk-S3M2IXCE.mjs → chunk-RACIPVLD.mjs} +15 -9
- package/dist/chunk-UNYDG2L4.mjs +209 -0
- package/dist/{chunk-MDUHPQMM.mjs → chunk-W3F4JYGP.mjs} +8 -180
- package/dist/chunk-WQWBJSSS.mjs +119 -0
- package/dist/cli/index.js +21 -0
- 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-OHJRZBBT.mjs → doctor-A5E7LSFW.mjs} +2 -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 +353 -94
- package/dist/express.mjs +210 -12
- package/dist/fastify.js +153 -88
- package/dist/fastify.mjs +10 -9
- package/dist/hono.js +152 -88
- package/dist/hono.mjs +9 -9
- package/dist/index.d.mts +3 -4
- package/dist/index.d.ts +3 -4
- package/dist/index.js +148 -72
- package/dist/index.mjs +16 -12
- 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 +164 -1649
- package/dist/next.mjs +13 -16
- package/dist/{publishableKey-B5DIK81A.d.mts → publishableKey-BaR0HoAH.d.mts} +10 -1
- package/dist/{publishableKey-B5DIK81A.d.ts → publishableKey-BaR0HoAH.d.ts} +10 -1
- package/dist/react.d.mts +35 -3
- package/dist/react.d.ts +35 -3
- package/dist/react.js +78 -18
- package/dist/react.mjs +14 -2
- package/dist/server/handlers.d.mts +2 -0
- package/dist/server/handlers.d.ts +2 -0
- package/dist/server/handlers.js +72 -17
- package/dist/server/handlers.mjs +3 -2
- package/dist/server.d.mts +2 -3
- package/dist/server.d.ts +2 -3
- package/dist/server.js +151 -89
- 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-CEMdUAwd.d.mts → signIn-Cd0P4y9d.d.mts} +9 -1
- package/dist/{signIn-VRNzlNyG.d.ts → signIn-DKakyzeu.d.ts} +9 -1
- package/package.json +3 -2
- package/dist/chunk-5WFR6Y33.mjs +0 -59
package/dist/index.js
CHANGED
|
@@ -61,6 +61,7 @@ __export(index_exports, {
|
|
|
61
61
|
UsersModule: () => UsersModule,
|
|
62
62
|
VendorsModule: () => VendorsModule,
|
|
63
63
|
WebhooksModule: () => WebhooksModule,
|
|
64
|
+
assertPublishableKey: () => assertPublishableKey,
|
|
64
65
|
encodePublishableKey: () => encodePublishableKey,
|
|
65
66
|
iqAuthMiddleware: () => iqAuthMiddleware,
|
|
66
67
|
isPublishableKey: () => isPublishableKey,
|
|
@@ -477,8 +478,7 @@ function parseMfaResponse(data, browserSessionMode) {
|
|
|
477
478
|
}
|
|
478
479
|
|
|
479
480
|
// src/modules/tokens.ts
|
|
480
|
-
var
|
|
481
|
-
var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
|
|
481
|
+
var import_jose = require("jose");
|
|
482
482
|
var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
483
483
|
var DEFAULT_TOKEN_ISSUER = [
|
|
484
484
|
"https://auth.dispositioniq.com",
|
|
@@ -491,6 +491,24 @@ var DEFAULT_TOKEN_AUDIENCE = [
|
|
|
491
491
|
"iqvalidate"
|
|
492
492
|
];
|
|
493
493
|
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
494
|
+
function decodeProtectedHeader(token) {
|
|
495
|
+
const parts = token.split(".");
|
|
496
|
+
if (parts.length < 2) return null;
|
|
497
|
+
try {
|
|
498
|
+
const padded = parts[0] + "=".repeat((4 - parts[0].length % 4) % 4);
|
|
499
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
500
|
+
let json;
|
|
501
|
+
if (typeof atob === "function") {
|
|
502
|
+
json = atob(b64);
|
|
503
|
+
} else {
|
|
504
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
505
|
+
json = Buffer2.from(b64, "base64").toString("utf8");
|
|
506
|
+
}
|
|
507
|
+
return JSON.parse(json);
|
|
508
|
+
} catch {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
494
512
|
var TokensModule = class {
|
|
495
513
|
constructor(baseUrl, options = {}) {
|
|
496
514
|
this.jwksCache = null;
|
|
@@ -501,49 +519,49 @@ var TokensModule = class {
|
|
|
501
519
|
this.defaultClockTolerance = options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
|
|
502
520
|
}
|
|
503
521
|
/**
|
|
504
|
-
* Verify a JWT access token using RS256 via JWKS from
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
* clock tolerance default to client config but can be overridden per call.
|
|
522
|
+
* Verify a JWT access token using RS256/ES256 via JWKS from
|
|
523
|
+
* `/.well-known/jwks.json`. Backed by `jose` (Web Crypto) so it runs on
|
|
524
|
+
* Node, browser, and edge runtimes alike — no `node:crypto` dependency.
|
|
525
|
+
* Caches JWKS for 1 hour and refetches once on unknown `kid`.
|
|
509
526
|
*/
|
|
510
527
|
async verify(token, options = {}) {
|
|
511
|
-
const
|
|
512
|
-
if (!
|
|
528
|
+
const header = decodeProtectedHeader(token);
|
|
529
|
+
if (!header) {
|
|
513
530
|
throw new IQAuthError("TOKEN_INVALID", "Unable to decode token");
|
|
514
531
|
}
|
|
515
|
-
const kid =
|
|
532
|
+
const kid = header.kid;
|
|
516
533
|
if (!kid) {
|
|
517
534
|
throw new IQAuthError("TOKEN_INVALID", "Token missing kid header");
|
|
518
535
|
}
|
|
519
|
-
let
|
|
520
|
-
if (!
|
|
521
|
-
|
|
522
|
-
|
|
536
|
+
let cache = await this.ensureCache();
|
|
537
|
+
if (!cache.byKid.has(kid)) {
|
|
538
|
+
this.jwksCache = null;
|
|
539
|
+
cache = await this.ensureCache();
|
|
523
540
|
}
|
|
524
|
-
if (!
|
|
541
|
+
if (!cache.byKid.has(kid)) {
|
|
525
542
|
throw new IQAuthError("TOKEN_INVALID", `Unknown key ID: ${kid}`);
|
|
526
543
|
}
|
|
527
544
|
const issuer = options.issuer ?? this.defaultIssuer;
|
|
528
545
|
const audience = options.audience ?? this.defaultAudience;
|
|
529
546
|
const clockTolerance = options.clockTolerance ?? this.defaultClockTolerance;
|
|
530
|
-
const algorithms = options.algorithms ?? ["RS256"];
|
|
547
|
+
const algorithms = options.algorithms ?? ["RS256", "ES256"];
|
|
548
|
+
const verifyOptions = {
|
|
549
|
+
algorithms,
|
|
550
|
+
clockTolerance,
|
|
551
|
+
issuer,
|
|
552
|
+
audience
|
|
553
|
+
};
|
|
531
554
|
try {
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
clockTolerance,
|
|
535
|
-
// The jsonwebtoken types insist on tuple types for arrays; runtime
|
|
536
|
-
// accepts plain string[] so we cast to satisfy the compiler.
|
|
537
|
-
issuer,
|
|
538
|
-
audience
|
|
539
|
-
};
|
|
540
|
-
const verified = import_jsonwebtoken.default.verify(token, publicKey, verifyOptions);
|
|
541
|
-
return verified;
|
|
555
|
+
const { payload } = await (0, import_jose.jwtVerify)(token, cache.verifier, verifyOptions);
|
|
556
|
+
return payload;
|
|
542
557
|
} catch (err) {
|
|
558
|
+
if (err instanceof import_jose.errors.JWTExpired) {
|
|
559
|
+
throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
|
|
560
|
+
}
|
|
561
|
+
if (err instanceof import_jose.errors.JOSEError) {
|
|
562
|
+
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
563
|
+
}
|
|
543
564
|
if (err instanceof Error) {
|
|
544
|
-
if (err.name === "TokenExpiredError") {
|
|
545
|
-
throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
|
|
546
|
-
}
|
|
547
565
|
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
548
566
|
}
|
|
549
567
|
throw new IQAuthError("TOKEN_INVALID", "Token verification failed");
|
|
@@ -551,29 +569,40 @@ var TokensModule = class {
|
|
|
551
569
|
}
|
|
552
570
|
/**
|
|
553
571
|
* Decode a JWT without verification. Returns null if malformed.
|
|
554
|
-
*
|
|
555
|
-
* @remarks Local decode only — no network call
|
|
556
572
|
*/
|
|
557
573
|
decode(token) {
|
|
558
|
-
|
|
559
|
-
|
|
574
|
+
try {
|
|
575
|
+
const parts = token.split(".");
|
|
576
|
+
if (parts.length < 2) return null;
|
|
577
|
+
const payload = parts[1];
|
|
578
|
+
const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
579
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
580
|
+
let json;
|
|
581
|
+
if (typeof atob === "function") {
|
|
582
|
+
json = atob(b64);
|
|
583
|
+
} else {
|
|
584
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
585
|
+
json = Buffer2.from(b64, "base64").toString("utf8");
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
json = decodeURIComponent(escape(json));
|
|
589
|
+
} catch {
|
|
590
|
+
}
|
|
591
|
+
const claims = JSON.parse(json);
|
|
592
|
+
if (!claims || typeof claims !== "object") return null;
|
|
593
|
+
return claims;
|
|
594
|
+
} catch {
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
560
597
|
}
|
|
561
|
-
/**
|
|
562
|
-
* Check if a token is expired based on the `exp` claim.
|
|
563
|
-
*
|
|
564
|
-
* @remarks Local check only — no network call
|
|
565
|
-
*/
|
|
598
|
+
/** Check if a token is expired based on the `exp` claim. */
|
|
566
599
|
isExpired(token) {
|
|
567
600
|
const claims = this.decode(token);
|
|
568
601
|
if (!claims?.exp) return true;
|
|
569
602
|
const now = Math.floor(Date.now() / 1e3);
|
|
570
603
|
return claims.exp <= now;
|
|
571
604
|
}
|
|
572
|
-
/**
|
|
573
|
-
* Get the claims from a token without verification.
|
|
574
|
-
*
|
|
575
|
-
* @remarks Local decode only — no network call
|
|
576
|
-
*/
|
|
605
|
+
/** Get the claims from a token without verification. */
|
|
577
606
|
getClaims(token) {
|
|
578
607
|
const claims = this.decode(token);
|
|
579
608
|
if (!claims) {
|
|
@@ -581,11 +610,15 @@ var TokensModule = class {
|
|
|
581
610
|
}
|
|
582
611
|
return claims;
|
|
583
612
|
}
|
|
584
|
-
async
|
|
585
|
-
if (
|
|
586
|
-
|
|
613
|
+
async ensureCache() {
|
|
614
|
+
if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) {
|
|
615
|
+
return this.jwksCache;
|
|
616
|
+
}
|
|
617
|
+
await this.refreshJwks();
|
|
618
|
+
if (!this.jwksCache) {
|
|
619
|
+
throw new IQAuthError("INTERNAL_ERROR", "JWKS cache unavailable after refresh");
|
|
587
620
|
}
|
|
588
|
-
return this.jwksCache
|
|
621
|
+
return this.jwksCache;
|
|
589
622
|
}
|
|
590
623
|
async refreshJwks() {
|
|
591
624
|
if (this.inFlightRefresh) {
|
|
@@ -612,35 +645,24 @@ var TokensModule = class {
|
|
|
612
645
|
"Malformed JWKS response: expected { keys: [...] }"
|
|
613
646
|
);
|
|
614
647
|
}
|
|
615
|
-
const
|
|
648
|
+
const byKid = /* @__PURE__ */ new Set();
|
|
616
649
|
for (const key of jwks.keys) {
|
|
617
|
-
if (!key || typeof key.kid !== "string" || typeof key.n !== "string" || typeof key.e !== "string") {
|
|
650
|
+
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")) {
|
|
618
651
|
throw new IQAuthError(
|
|
619
652
|
"INTERNAL_ERROR",
|
|
620
653
|
"Malformed JWKS response: key missing required fields"
|
|
621
654
|
);
|
|
622
655
|
}
|
|
623
|
-
|
|
624
|
-
keys.set(key.kid, pem);
|
|
656
|
+
byKid.add(key.kid);
|
|
625
657
|
}
|
|
626
|
-
|
|
658
|
+
const verifier = (0, import_jose.createLocalJWKSet)({ keys: jwks.keys });
|
|
659
|
+
this.jwksCache = { raw: jwks.keys, byKid, verifier, fetchedAt: Date.now() };
|
|
627
660
|
} finally {
|
|
628
661
|
this.inFlightRefresh = null;
|
|
629
662
|
}
|
|
630
663
|
})();
|
|
631
664
|
return this.inFlightRefresh;
|
|
632
665
|
}
|
|
633
|
-
jwkToPem(jwk) {
|
|
634
|
-
const keyObject = import_crypto.default.createPublicKey({
|
|
635
|
-
key: {
|
|
636
|
-
kty: jwk.kty,
|
|
637
|
-
n: jwk.n,
|
|
638
|
-
e: jwk.e
|
|
639
|
-
},
|
|
640
|
-
format: "jwk"
|
|
641
|
-
});
|
|
642
|
-
return keyObject.export({ type: "spki", format: "pem" });
|
|
643
|
-
}
|
|
644
666
|
/** @internal Exposed for testing — clears JWKS cache */
|
|
645
667
|
clearCache() {
|
|
646
668
|
this.jwksCache = null;
|
|
@@ -848,7 +870,7 @@ var PermissionsModule = class {
|
|
|
848
870
|
};
|
|
849
871
|
|
|
850
872
|
// src/modules/oidc.ts
|
|
851
|
-
var
|
|
873
|
+
var import_crypto = __toESM(require("crypto"));
|
|
852
874
|
var InMemoryOidcStateStore = class {
|
|
853
875
|
constructor() {
|
|
854
876
|
this.map = /* @__PURE__ */ new Map();
|
|
@@ -929,12 +951,12 @@ var OidcModule = class {
|
|
|
929
951
|
* ready to redirect the user to.
|
|
930
952
|
*/
|
|
931
953
|
async createAuthRequest(params) {
|
|
932
|
-
const codeVerifier = base64UrlEncode(
|
|
954
|
+
const codeVerifier = base64UrlEncode(import_crypto.default.randomBytes(32));
|
|
933
955
|
const codeChallenge = base64UrlEncode(
|
|
934
|
-
|
|
956
|
+
import_crypto.default.createHash("sha256").update(codeVerifier).digest()
|
|
935
957
|
);
|
|
936
|
-
const state = base64UrlEncode(
|
|
937
|
-
const nonce = base64UrlEncode(
|
|
958
|
+
const state = base64UrlEncode(import_crypto.default.randomBytes(16));
|
|
959
|
+
const nonce = base64UrlEncode(import_crypto.default.randomBytes(16));
|
|
938
960
|
await this.stateStore.set(state, {
|
|
939
961
|
codeVerifier,
|
|
940
962
|
state,
|
|
@@ -1851,6 +1873,18 @@ function encodePublishableKey(mode, payload) {
|
|
|
1851
1873
|
if (mode !== "test" && mode !== "live") throw new Error(`Invalid mode: ${mode}`);
|
|
1852
1874
|
return `pk_${mode}_${b64urlEncode(JSON.stringify(payload))}`;
|
|
1853
1875
|
}
|
|
1876
|
+
function isValidIssuerUrl(iss) {
|
|
1877
|
+
if (typeof iss !== "string" || iss.length === 0) return false;
|
|
1878
|
+
if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
|
|
1879
|
+
try {
|
|
1880
|
+
const u = new URL(iss);
|
|
1881
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return false;
|
|
1882
|
+
if (!u.hostname) return false;
|
|
1883
|
+
return true;
|
|
1884
|
+
} catch {
|
|
1885
|
+
return false;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1854
1888
|
function parsePublishableKey(raw) {
|
|
1855
1889
|
if (typeof raw !== "string") return null;
|
|
1856
1890
|
const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
|
|
@@ -1861,11 +1895,55 @@ function parsePublishableKey(raw) {
|
|
|
1861
1895
|
if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
|
|
1862
1896
|
return null;
|
|
1863
1897
|
}
|
|
1898
|
+
if (!isValidIssuerUrl(json.iss)) return null;
|
|
1864
1899
|
return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
|
|
1865
1900
|
} catch {
|
|
1866
1901
|
return null;
|
|
1867
1902
|
}
|
|
1868
1903
|
}
|
|
1904
|
+
function assertPublishableKey(raw, opts) {
|
|
1905
|
+
const ctx = opts?.context ? `${opts.context}: ` : "";
|
|
1906
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
1907
|
+
throw new IQAuthError(
|
|
1908
|
+
"CONFIG_INVALID",
|
|
1909
|
+
`${ctx}IQAuth publishable key is missing. Set IQAUTH_PUBLISHABLE_KEY (or pass publishableKey) to a pk_test_\u2026 or pk_live_\u2026 value from the IQAuth admin console.`
|
|
1910
|
+
);
|
|
1911
|
+
}
|
|
1912
|
+
const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
|
|
1913
|
+
if (!shapeMatch) {
|
|
1914
|
+
throw new IQAuthError(
|
|
1915
|
+
"CONFIG_INVALID",
|
|
1916
|
+
`${ctx}IQAuth publishable key is malformed (got ${raw.slice(0, 12)}\u2026). Expected pk_test_\u2026 or pk_live_\u2026; regenerate the key from the IQAuth admin console.`
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
let decoded;
|
|
1920
|
+
try {
|
|
1921
|
+
decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
|
|
1922
|
+
} catch {
|
|
1923
|
+
throw new IQAuthError(
|
|
1924
|
+
"CONFIG_INVALID",
|
|
1925
|
+
`${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
if (!isPublishableKeyPayload(decoded)) {
|
|
1929
|
+
throw new IQAuthError(
|
|
1930
|
+
"CONFIG_INVALID",
|
|
1931
|
+
`${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
if (!isValidIssuerUrl(decoded.iss)) {
|
|
1935
|
+
throw new IQAuthError(
|
|
1936
|
+
"CONFIG_INVALID",
|
|
1937
|
+
`${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.`
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
|
|
1941
|
+
}
|
|
1942
|
+
function isPublishableKeyPayload(value) {
|
|
1943
|
+
if (!value || typeof value !== "object") return false;
|
|
1944
|
+
const v = value;
|
|
1945
|
+
return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
|
|
1946
|
+
}
|
|
1869
1947
|
function isPublishableKey(raw) {
|
|
1870
1948
|
return typeof raw === "string" && /^pk_(test|live)_/.test(raw);
|
|
1871
1949
|
}
|
|
@@ -1908,10 +1986,7 @@ function readCookie(req, name) {
|
|
|
1908
1986
|
return void 0;
|
|
1909
1987
|
}
|
|
1910
1988
|
function clientFromPublishableKey(opts) {
|
|
1911
|
-
const parsed =
|
|
1912
|
-
if (!parsed) {
|
|
1913
|
-
throw new Error("iqAuthMiddleware: invalid publishable key");
|
|
1914
|
-
}
|
|
1989
|
+
const parsed = assertPublishableKey(opts.publishableKey, { context: "iqAuthMiddleware" });
|
|
1915
1990
|
const issuer = (opts.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
|
|
1916
1991
|
return new IQAuthClient({ baseUrl: issuer, environment: "server" });
|
|
1917
1992
|
}
|
|
@@ -2065,6 +2140,7 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
|
|
|
2065
2140
|
UsersModule,
|
|
2066
2141
|
VendorsModule,
|
|
2067
2142
|
WebhooksModule,
|
|
2143
|
+
assertPublishableKey,
|
|
2068
2144
|
encodePublishableKey,
|
|
2069
2145
|
iqAuthMiddleware,
|
|
2070
2146
|
isPublishableKey,
|
package/dist/index.mjs
CHANGED
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
iqAuthMiddleware
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import {
|
|
5
|
-
encodePublishableKey,
|
|
6
|
-
isPublishableKey,
|
|
7
|
-
isSecretKey,
|
|
8
|
-
parsePublishableKey
|
|
9
|
-
} from "./chunk-5WFR6Y33.mjs";
|
|
3
|
+
} from "./chunk-EKTNEZIH.mjs";
|
|
10
4
|
import {
|
|
11
5
|
ApiKeysModule,
|
|
12
6
|
AppsModule,
|
|
13
7
|
AuthModule,
|
|
14
8
|
BrandingModule,
|
|
15
9
|
ClientsModule,
|
|
16
|
-
DEFAULT_CLOCK_TOLERANCE_SECONDS,
|
|
17
|
-
DEFAULT_TOKEN_AUDIENCE,
|
|
18
|
-
DEFAULT_TOKEN_ISSUER,
|
|
19
10
|
EntitlementsModule,
|
|
20
11
|
GdprModule,
|
|
21
12
|
HierarchyModule,
|
|
@@ -33,11 +24,23 @@ import {
|
|
|
33
24
|
SessionsModule,
|
|
34
25
|
SourcesModule,
|
|
35
26
|
TenantsModule,
|
|
36
|
-
TokensModule,
|
|
37
27
|
UsersModule,
|
|
38
28
|
VendorsModule,
|
|
39
29
|
WebhooksModule
|
|
40
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-W3F4JYGP.mjs";
|
|
31
|
+
import {
|
|
32
|
+
assertPublishableKey,
|
|
33
|
+
encodePublishableKey,
|
|
34
|
+
isPublishableKey,
|
|
35
|
+
isSecretKey,
|
|
36
|
+
parsePublishableKey
|
|
37
|
+
} from "./chunk-WQWBJSSS.mjs";
|
|
38
|
+
import {
|
|
39
|
+
DEFAULT_CLOCK_TOLERANCE_SECONDS,
|
|
40
|
+
DEFAULT_TOKEN_AUDIENCE,
|
|
41
|
+
DEFAULT_TOKEN_ISSUER,
|
|
42
|
+
TokensModule
|
|
43
|
+
} from "./chunk-UNYDG2L4.mjs";
|
|
41
44
|
import {
|
|
42
45
|
ErrorCodes,
|
|
43
46
|
IQAuthError
|
|
@@ -75,6 +78,7 @@ export {
|
|
|
75
78
|
UsersModule,
|
|
76
79
|
VendorsModule,
|
|
77
80
|
WebhooksModule,
|
|
81
|
+
assertPublishableKey,
|
|
78
82
|
encodePublishableKey,
|
|
79
83
|
iqAuthMiddleware,
|
|
80
84
|
isPublishableKey,
|
package/dist/mobile.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { I as IQAuthClient } from './client-
|
|
1
|
+
import { I as IQAuthClient } from './client-vdh2a9fJ.mjs';
|
|
2
2
|
import { b as IQAuthTokenClientConfig } from './types-Cxl3bQHt.mjs';
|
|
3
3
|
export { E as ErrorCodes, I as IQAuthError } from './errors-CDdl24MP.mjs';
|
|
4
|
-
import 'jsonwebtoken';
|
|
5
4
|
|
|
6
5
|
declare class MobileIQAuthClient extends IQAuthClient {
|
|
7
6
|
constructor(config: IQAuthTokenClientConfig);
|
package/dist/mobile.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { I as IQAuthClient } from './client-
|
|
1
|
+
import { I as IQAuthClient } from './client-DTX4hNdS.js';
|
|
2
2
|
import { b as IQAuthTokenClientConfig } from './types-Cxl3bQHt.js';
|
|
3
3
|
export { E as ErrorCodes, I as IQAuthError } from './errors-CDdl24MP.js';
|
|
4
|
-
import 'jsonwebtoken';
|
|
5
4
|
|
|
6
5
|
declare class MobileIQAuthClient extends IQAuthClient {
|
|
7
6
|
constructor(config: IQAuthTokenClientConfig);
|
package/dist/mobile.js
CHANGED
|
@@ -446,8 +446,7 @@ function parseMfaResponse(data, browserSessionMode) {
|
|
|
446
446
|
}
|
|
447
447
|
|
|
448
448
|
// src/modules/tokens.ts
|
|
449
|
-
var
|
|
450
|
-
var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
|
|
449
|
+
var import_jose = require("jose");
|
|
451
450
|
var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
452
451
|
var DEFAULT_TOKEN_ISSUER = [
|
|
453
452
|
"https://auth.dispositioniq.com",
|
|
@@ -460,6 +459,24 @@ var DEFAULT_TOKEN_AUDIENCE = [
|
|
|
460
459
|
"iqvalidate"
|
|
461
460
|
];
|
|
462
461
|
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
462
|
+
function decodeProtectedHeader(token) {
|
|
463
|
+
const parts = token.split(".");
|
|
464
|
+
if (parts.length < 2) return null;
|
|
465
|
+
try {
|
|
466
|
+
const padded = parts[0] + "=".repeat((4 - parts[0].length % 4) % 4);
|
|
467
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
468
|
+
let json;
|
|
469
|
+
if (typeof atob === "function") {
|
|
470
|
+
json = atob(b64);
|
|
471
|
+
} else {
|
|
472
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
473
|
+
json = Buffer2.from(b64, "base64").toString("utf8");
|
|
474
|
+
}
|
|
475
|
+
return JSON.parse(json);
|
|
476
|
+
} catch {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
463
480
|
var TokensModule = class {
|
|
464
481
|
constructor(baseUrl, options = {}) {
|
|
465
482
|
this.jwksCache = null;
|
|
@@ -470,49 +487,49 @@ var TokensModule = class {
|
|
|
470
487
|
this.defaultClockTolerance = options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
|
|
471
488
|
}
|
|
472
489
|
/**
|
|
473
|
-
* Verify a JWT access token using RS256 via JWKS from
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
* clock tolerance default to client config but can be overridden per call.
|
|
490
|
+
* Verify a JWT access token using RS256/ES256 via JWKS from
|
|
491
|
+
* `/.well-known/jwks.json`. Backed by `jose` (Web Crypto) so it runs on
|
|
492
|
+
* Node, browser, and edge runtimes alike — no `node:crypto` dependency.
|
|
493
|
+
* Caches JWKS for 1 hour and refetches once on unknown `kid`.
|
|
478
494
|
*/
|
|
479
495
|
async verify(token, options = {}) {
|
|
480
|
-
const
|
|
481
|
-
if (!
|
|
496
|
+
const header = decodeProtectedHeader(token);
|
|
497
|
+
if (!header) {
|
|
482
498
|
throw new IQAuthError("TOKEN_INVALID", "Unable to decode token");
|
|
483
499
|
}
|
|
484
|
-
const kid =
|
|
500
|
+
const kid = header.kid;
|
|
485
501
|
if (!kid) {
|
|
486
502
|
throw new IQAuthError("TOKEN_INVALID", "Token missing kid header");
|
|
487
503
|
}
|
|
488
|
-
let
|
|
489
|
-
if (!
|
|
490
|
-
|
|
491
|
-
|
|
504
|
+
let cache = await this.ensureCache();
|
|
505
|
+
if (!cache.byKid.has(kid)) {
|
|
506
|
+
this.jwksCache = null;
|
|
507
|
+
cache = await this.ensureCache();
|
|
492
508
|
}
|
|
493
|
-
if (!
|
|
509
|
+
if (!cache.byKid.has(kid)) {
|
|
494
510
|
throw new IQAuthError("TOKEN_INVALID", `Unknown key ID: ${kid}`);
|
|
495
511
|
}
|
|
496
512
|
const issuer = options.issuer ?? this.defaultIssuer;
|
|
497
513
|
const audience = options.audience ?? this.defaultAudience;
|
|
498
514
|
const clockTolerance = options.clockTolerance ?? this.defaultClockTolerance;
|
|
499
|
-
const algorithms = options.algorithms ?? ["RS256"];
|
|
515
|
+
const algorithms = options.algorithms ?? ["RS256", "ES256"];
|
|
516
|
+
const verifyOptions = {
|
|
517
|
+
algorithms,
|
|
518
|
+
clockTolerance,
|
|
519
|
+
issuer,
|
|
520
|
+
audience
|
|
521
|
+
};
|
|
500
522
|
try {
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
clockTolerance,
|
|
504
|
-
// The jsonwebtoken types insist on tuple types for arrays; runtime
|
|
505
|
-
// accepts plain string[] so we cast to satisfy the compiler.
|
|
506
|
-
issuer,
|
|
507
|
-
audience
|
|
508
|
-
};
|
|
509
|
-
const verified = import_jsonwebtoken.default.verify(token, publicKey, verifyOptions);
|
|
510
|
-
return verified;
|
|
523
|
+
const { payload } = await (0, import_jose.jwtVerify)(token, cache.verifier, verifyOptions);
|
|
524
|
+
return payload;
|
|
511
525
|
} catch (err) {
|
|
526
|
+
if (err instanceof import_jose.errors.JWTExpired) {
|
|
527
|
+
throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
|
|
528
|
+
}
|
|
529
|
+
if (err instanceof import_jose.errors.JOSEError) {
|
|
530
|
+
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
531
|
+
}
|
|
512
532
|
if (err instanceof Error) {
|
|
513
|
-
if (err.name === "TokenExpiredError") {
|
|
514
|
-
throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
|
|
515
|
-
}
|
|
516
533
|
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
517
534
|
}
|
|
518
535
|
throw new IQAuthError("TOKEN_INVALID", "Token verification failed");
|
|
@@ -520,29 +537,40 @@ var TokensModule = class {
|
|
|
520
537
|
}
|
|
521
538
|
/**
|
|
522
539
|
* Decode a JWT without verification. Returns null if malformed.
|
|
523
|
-
*
|
|
524
|
-
* @remarks Local decode only — no network call
|
|
525
540
|
*/
|
|
526
541
|
decode(token) {
|
|
527
|
-
|
|
528
|
-
|
|
542
|
+
try {
|
|
543
|
+
const parts = token.split(".");
|
|
544
|
+
if (parts.length < 2) return null;
|
|
545
|
+
const payload = parts[1];
|
|
546
|
+
const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
547
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
548
|
+
let json;
|
|
549
|
+
if (typeof atob === "function") {
|
|
550
|
+
json = atob(b64);
|
|
551
|
+
} else {
|
|
552
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
553
|
+
json = Buffer2.from(b64, "base64").toString("utf8");
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
json = decodeURIComponent(escape(json));
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
559
|
+
const claims = JSON.parse(json);
|
|
560
|
+
if (!claims || typeof claims !== "object") return null;
|
|
561
|
+
return claims;
|
|
562
|
+
} catch {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
529
565
|
}
|
|
530
|
-
/**
|
|
531
|
-
* Check if a token is expired based on the `exp` claim.
|
|
532
|
-
*
|
|
533
|
-
* @remarks Local check only — no network call
|
|
534
|
-
*/
|
|
566
|
+
/** Check if a token is expired based on the `exp` claim. */
|
|
535
567
|
isExpired(token) {
|
|
536
568
|
const claims = this.decode(token);
|
|
537
569
|
if (!claims?.exp) return true;
|
|
538
570
|
const now = Math.floor(Date.now() / 1e3);
|
|
539
571
|
return claims.exp <= now;
|
|
540
572
|
}
|
|
541
|
-
/**
|
|
542
|
-
* Get the claims from a token without verification.
|
|
543
|
-
*
|
|
544
|
-
* @remarks Local decode only — no network call
|
|
545
|
-
*/
|
|
573
|
+
/** Get the claims from a token without verification. */
|
|
546
574
|
getClaims(token) {
|
|
547
575
|
const claims = this.decode(token);
|
|
548
576
|
if (!claims) {
|
|
@@ -550,11 +578,15 @@ var TokensModule = class {
|
|
|
550
578
|
}
|
|
551
579
|
return claims;
|
|
552
580
|
}
|
|
553
|
-
async
|
|
554
|
-
if (
|
|
555
|
-
|
|
581
|
+
async ensureCache() {
|
|
582
|
+
if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) {
|
|
583
|
+
return this.jwksCache;
|
|
584
|
+
}
|
|
585
|
+
await this.refreshJwks();
|
|
586
|
+
if (!this.jwksCache) {
|
|
587
|
+
throw new IQAuthError("INTERNAL_ERROR", "JWKS cache unavailable after refresh");
|
|
556
588
|
}
|
|
557
|
-
return this.jwksCache
|
|
589
|
+
return this.jwksCache;
|
|
558
590
|
}
|
|
559
591
|
async refreshJwks() {
|
|
560
592
|
if (this.inFlightRefresh) {
|
|
@@ -581,35 +613,24 @@ var TokensModule = class {
|
|
|
581
613
|
"Malformed JWKS response: expected { keys: [...] }"
|
|
582
614
|
);
|
|
583
615
|
}
|
|
584
|
-
const
|
|
616
|
+
const byKid = /* @__PURE__ */ new Set();
|
|
585
617
|
for (const key of jwks.keys) {
|
|
586
|
-
if (!key || typeof key.kid !== "string" || typeof key.n !== "string" || typeof key.e !== "string") {
|
|
618
|
+
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")) {
|
|
587
619
|
throw new IQAuthError(
|
|
588
620
|
"INTERNAL_ERROR",
|
|
589
621
|
"Malformed JWKS response: key missing required fields"
|
|
590
622
|
);
|
|
591
623
|
}
|
|
592
|
-
|
|
593
|
-
keys.set(key.kid, pem);
|
|
624
|
+
byKid.add(key.kid);
|
|
594
625
|
}
|
|
595
|
-
|
|
626
|
+
const verifier = (0, import_jose.createLocalJWKSet)({ keys: jwks.keys });
|
|
627
|
+
this.jwksCache = { raw: jwks.keys, byKid, verifier, fetchedAt: Date.now() };
|
|
596
628
|
} finally {
|
|
597
629
|
this.inFlightRefresh = null;
|
|
598
630
|
}
|
|
599
631
|
})();
|
|
600
632
|
return this.inFlightRefresh;
|
|
601
633
|
}
|
|
602
|
-
jwkToPem(jwk) {
|
|
603
|
-
const keyObject = import_crypto.default.createPublicKey({
|
|
604
|
-
key: {
|
|
605
|
-
kty: jwk.kty,
|
|
606
|
-
n: jwk.n,
|
|
607
|
-
e: jwk.e
|
|
608
|
-
},
|
|
609
|
-
format: "jwk"
|
|
610
|
-
});
|
|
611
|
-
return keyObject.export({ type: "spki", format: "pem" });
|
|
612
|
-
}
|
|
613
634
|
/** @internal Exposed for testing — clears JWKS cache */
|
|
614
635
|
clearCache() {
|
|
615
636
|
this.jwksCache = null;
|
|
@@ -817,7 +838,7 @@ var PermissionsModule = class {
|
|
|
817
838
|
};
|
|
818
839
|
|
|
819
840
|
// src/modules/oidc.ts
|
|
820
|
-
var
|
|
841
|
+
var import_crypto = __toESM(require("crypto"));
|
|
821
842
|
var InMemoryOidcStateStore = class {
|
|
822
843
|
constructor() {
|
|
823
844
|
this.map = /* @__PURE__ */ new Map();
|
|
@@ -898,12 +919,12 @@ var OidcModule = class {
|
|
|
898
919
|
* ready to redirect the user to.
|
|
899
920
|
*/
|
|
900
921
|
async createAuthRequest(params) {
|
|
901
|
-
const codeVerifier = base64UrlEncode(
|
|
922
|
+
const codeVerifier = base64UrlEncode(import_crypto.default.randomBytes(32));
|
|
902
923
|
const codeChallenge = base64UrlEncode(
|
|
903
|
-
|
|
924
|
+
import_crypto.default.createHash("sha256").update(codeVerifier).digest()
|
|
904
925
|
);
|
|
905
|
-
const state = base64UrlEncode(
|
|
906
|
-
const nonce = base64UrlEncode(
|
|
926
|
+
const state = base64UrlEncode(import_crypto.default.randomBytes(16));
|
|
927
|
+
const nonce = base64UrlEncode(import_crypto.default.randomBytes(16));
|
|
907
928
|
await this.stateStore.set(state, {
|
|
908
929
|
codeVerifier,
|
|
909
930
|
state,
|