@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
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IQAuthError
|
|
3
|
+
} from "./chunk-6I6RM4MN.mjs";
|
|
4
|
+
import {
|
|
5
|
+
__require
|
|
6
|
+
} from "./chunk-Y6FXYEAI.mjs";
|
|
7
|
+
|
|
8
|
+
// src/modules/tokens.ts
|
|
9
|
+
import {
|
|
10
|
+
createLocalJWKSet,
|
|
11
|
+
jwtVerify,
|
|
12
|
+
errors as joseErrors
|
|
13
|
+
} from "jose";
|
|
14
|
+
var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
15
|
+
var DEFAULT_TOKEN_ISSUER = [
|
|
16
|
+
"https://auth.dispositioniq.com",
|
|
17
|
+
"auth.dispositioniq.com"
|
|
18
|
+
];
|
|
19
|
+
var DEFAULT_TOKEN_AUDIENCE = [
|
|
20
|
+
"dispositioniq",
|
|
21
|
+
"iqcapture",
|
|
22
|
+
"iqreuse",
|
|
23
|
+
"iqvalidate"
|
|
24
|
+
];
|
|
25
|
+
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
26
|
+
function decodeProtectedHeader(token) {
|
|
27
|
+
const parts = token.split(".");
|
|
28
|
+
if (parts.length < 2) return null;
|
|
29
|
+
try {
|
|
30
|
+
const padded = parts[0] + "=".repeat((4 - parts[0].length % 4) % 4);
|
|
31
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
32
|
+
let json;
|
|
33
|
+
if (typeof atob === "function") {
|
|
34
|
+
json = atob(b64);
|
|
35
|
+
} else {
|
|
36
|
+
const { Buffer } = __require("buffer");
|
|
37
|
+
json = Buffer.from(b64, "base64").toString("utf8");
|
|
38
|
+
}
|
|
39
|
+
return JSON.parse(json);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
var TokensModule = class {
|
|
45
|
+
constructor(baseUrl, options = {}) {
|
|
46
|
+
this.jwksCache = null;
|
|
47
|
+
this.inFlightRefresh = null;
|
|
48
|
+
this.baseUrl = baseUrl;
|
|
49
|
+
this.defaultIssuer = options.issuer ?? DEFAULT_TOKEN_ISSUER;
|
|
50
|
+
this.defaultAudience = options.audience ?? DEFAULT_TOKEN_AUDIENCE;
|
|
51
|
+
this.defaultClockTolerance = options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Verify a JWT access token using RS256/ES256 via JWKS from
|
|
55
|
+
* `/.well-known/jwks.json`. Backed by `jose` (Web Crypto) so it runs on
|
|
56
|
+
* Node, browser, and edge runtimes alike — no `node:crypto` dependency.
|
|
57
|
+
* Caches JWKS for 1 hour and refetches once on unknown `kid`.
|
|
58
|
+
*/
|
|
59
|
+
async verify(token, options = {}) {
|
|
60
|
+
const header = decodeProtectedHeader(token);
|
|
61
|
+
if (!header) {
|
|
62
|
+
throw new IQAuthError("TOKEN_INVALID", "Unable to decode token");
|
|
63
|
+
}
|
|
64
|
+
const kid = header.kid;
|
|
65
|
+
if (!kid) {
|
|
66
|
+
throw new IQAuthError("TOKEN_INVALID", "Token missing kid header");
|
|
67
|
+
}
|
|
68
|
+
let cache = await this.ensureCache();
|
|
69
|
+
if (!cache.byKid.has(kid)) {
|
|
70
|
+
this.jwksCache = null;
|
|
71
|
+
cache = await this.ensureCache();
|
|
72
|
+
}
|
|
73
|
+
if (!cache.byKid.has(kid)) {
|
|
74
|
+
throw new IQAuthError("TOKEN_INVALID", `Unknown key ID: ${kid}`);
|
|
75
|
+
}
|
|
76
|
+
const issuer = options.issuer ?? this.defaultIssuer;
|
|
77
|
+
const audience = options.audience ?? this.defaultAudience;
|
|
78
|
+
const clockTolerance = options.clockTolerance ?? this.defaultClockTolerance;
|
|
79
|
+
const algorithms = options.algorithms ?? ["RS256", "ES256"];
|
|
80
|
+
const verifyOptions = {
|
|
81
|
+
algorithms,
|
|
82
|
+
clockTolerance,
|
|
83
|
+
issuer,
|
|
84
|
+
audience
|
|
85
|
+
};
|
|
86
|
+
try {
|
|
87
|
+
const { payload } = await jwtVerify(token, cache.verifier, verifyOptions);
|
|
88
|
+
return payload;
|
|
89
|
+
} catch (err) {
|
|
90
|
+
if (err instanceof joseErrors.JWTExpired) {
|
|
91
|
+
throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
|
|
92
|
+
}
|
|
93
|
+
if (err instanceof joseErrors.JOSEError) {
|
|
94
|
+
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
95
|
+
}
|
|
96
|
+
if (err instanceof Error) {
|
|
97
|
+
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
98
|
+
}
|
|
99
|
+
throw new IQAuthError("TOKEN_INVALID", "Token verification failed");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Decode a JWT without verification. Returns null if malformed.
|
|
104
|
+
*/
|
|
105
|
+
decode(token) {
|
|
106
|
+
try {
|
|
107
|
+
const parts = token.split(".");
|
|
108
|
+
if (parts.length < 2) return null;
|
|
109
|
+
const payload = parts[1];
|
|
110
|
+
const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
111
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
112
|
+
let json;
|
|
113
|
+
if (typeof atob === "function") {
|
|
114
|
+
json = atob(b64);
|
|
115
|
+
} else {
|
|
116
|
+
const { Buffer } = __require("buffer");
|
|
117
|
+
json = Buffer.from(b64, "base64").toString("utf8");
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
json = decodeURIComponent(escape(json));
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
const claims = JSON.parse(json);
|
|
124
|
+
if (!claims || typeof claims !== "object") return null;
|
|
125
|
+
return claims;
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Check if a token is expired based on the `exp` claim. */
|
|
131
|
+
isExpired(token) {
|
|
132
|
+
const claims = this.decode(token);
|
|
133
|
+
if (!claims?.exp) return true;
|
|
134
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
135
|
+
return claims.exp <= now;
|
|
136
|
+
}
|
|
137
|
+
/** Get the claims from a token without verification. */
|
|
138
|
+
getClaims(token) {
|
|
139
|
+
const claims = this.decode(token);
|
|
140
|
+
if (!claims) {
|
|
141
|
+
throw new IQAuthError("TOKEN_INVALID", "Unable to decode token claims");
|
|
142
|
+
}
|
|
143
|
+
return claims;
|
|
144
|
+
}
|
|
145
|
+
async ensureCache() {
|
|
146
|
+
if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) {
|
|
147
|
+
return this.jwksCache;
|
|
148
|
+
}
|
|
149
|
+
await this.refreshJwks();
|
|
150
|
+
if (!this.jwksCache) {
|
|
151
|
+
throw new IQAuthError("INTERNAL_ERROR", "JWKS cache unavailable after refresh");
|
|
152
|
+
}
|
|
153
|
+
return this.jwksCache;
|
|
154
|
+
}
|
|
155
|
+
async refreshJwks() {
|
|
156
|
+
if (this.inFlightRefresh) {
|
|
157
|
+
return this.inFlightRefresh;
|
|
158
|
+
}
|
|
159
|
+
this.inFlightRefresh = (async () => {
|
|
160
|
+
try {
|
|
161
|
+
const res = await fetch(`${this.baseUrl}/.well-known/jwks.json`);
|
|
162
|
+
if (!res.ok) {
|
|
163
|
+
throw new IQAuthError(
|
|
164
|
+
"INTERNAL_ERROR",
|
|
165
|
+
`Failed to fetch JWKS: ${res.status}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
let jwks;
|
|
169
|
+
try {
|
|
170
|
+
jwks = await res.json();
|
|
171
|
+
} catch {
|
|
172
|
+
throw new IQAuthError("INTERNAL_ERROR", "Malformed JWKS response: invalid JSON");
|
|
173
|
+
}
|
|
174
|
+
if (!jwks || !Array.isArray(jwks.keys)) {
|
|
175
|
+
throw new IQAuthError(
|
|
176
|
+
"INTERNAL_ERROR",
|
|
177
|
+
"Malformed JWKS response: expected { keys: [...] }"
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
const byKid = /* @__PURE__ */ new Set();
|
|
181
|
+
for (const key of jwks.keys) {
|
|
182
|
+
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")) {
|
|
183
|
+
throw new IQAuthError(
|
|
184
|
+
"INTERNAL_ERROR",
|
|
185
|
+
"Malformed JWKS response: key missing required fields"
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
byKid.add(key.kid);
|
|
189
|
+
}
|
|
190
|
+
const verifier = createLocalJWKSet({ keys: jwks.keys });
|
|
191
|
+
this.jwksCache = { raw: jwks.keys, byKid, verifier, fetchedAt: Date.now() };
|
|
192
|
+
} finally {
|
|
193
|
+
this.inFlightRefresh = null;
|
|
194
|
+
}
|
|
195
|
+
})();
|
|
196
|
+
return this.inFlightRefresh;
|
|
197
|
+
}
|
|
198
|
+
/** @internal Exposed for testing — clears JWKS cache */
|
|
199
|
+
clearCache() {
|
|
200
|
+
this.jwksCache = null;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export {
|
|
205
|
+
DEFAULT_TOKEN_ISSUER,
|
|
206
|
+
DEFAULT_TOKEN_AUDIENCE,
|
|
207
|
+
DEFAULT_CLOCK_TOLERANCE_SECONDS,
|
|
208
|
+
TokensModule
|
|
209
|
+
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TokensModule
|
|
3
|
+
} from "./chunk-UNYDG2L4.mjs";
|
|
1
4
|
import {
|
|
2
5
|
IQAuthError
|
|
3
6
|
} from "./chunk-6I6RM4MN.mjs";
|
|
@@ -160,177 +163,6 @@ function parseMfaResponse(data, browserSessionMode) {
|
|
|
160
163
|
throw new Error("Unexpected MFA response shape");
|
|
161
164
|
}
|
|
162
165
|
|
|
163
|
-
// src/modules/tokens.ts
|
|
164
|
-
import crypto from "crypto";
|
|
165
|
-
import jwt from "jsonwebtoken";
|
|
166
|
-
var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
167
|
-
var DEFAULT_TOKEN_ISSUER = [
|
|
168
|
-
"https://auth.dispositioniq.com",
|
|
169
|
-
"auth.dispositioniq.com"
|
|
170
|
-
];
|
|
171
|
-
var DEFAULT_TOKEN_AUDIENCE = [
|
|
172
|
-
"dispositioniq",
|
|
173
|
-
"iqcapture",
|
|
174
|
-
"iqreuse",
|
|
175
|
-
"iqvalidate"
|
|
176
|
-
];
|
|
177
|
-
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
178
|
-
var TokensModule = class {
|
|
179
|
-
constructor(baseUrl, options = {}) {
|
|
180
|
-
this.jwksCache = null;
|
|
181
|
-
this.inFlightRefresh = null;
|
|
182
|
-
this.baseUrl = baseUrl;
|
|
183
|
-
this.defaultIssuer = options.issuer ?? DEFAULT_TOKEN_ISSUER;
|
|
184
|
-
this.defaultAudience = options.audience ?? DEFAULT_TOKEN_AUDIENCE;
|
|
185
|
-
this.defaultClockTolerance = options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Verify a JWT access token using RS256 via JWKS from /.well-known/jwks.json.
|
|
189
|
-
* Caches JWKS keys for 1 hour. Retries once on unknown `kid`.
|
|
190
|
-
*
|
|
191
|
-
* @remarks Validates against /.well-known/jwks.json. Issuer, audience, and
|
|
192
|
-
* clock tolerance default to client config but can be overridden per call.
|
|
193
|
-
*/
|
|
194
|
-
async verify(token, options = {}) {
|
|
195
|
-
const decoded = jwt.decode(token, { complete: true });
|
|
196
|
-
if (!decoded || typeof decoded === "string") {
|
|
197
|
-
throw new IQAuthError("TOKEN_INVALID", "Unable to decode token");
|
|
198
|
-
}
|
|
199
|
-
const kid = decoded.header.kid;
|
|
200
|
-
if (!kid) {
|
|
201
|
-
throw new IQAuthError("TOKEN_INVALID", "Token missing kid header");
|
|
202
|
-
}
|
|
203
|
-
let publicKey = await this.getPublicKey(kid);
|
|
204
|
-
if (!publicKey) {
|
|
205
|
-
await this.refreshJwks();
|
|
206
|
-
publicKey = await this.getPublicKey(kid);
|
|
207
|
-
}
|
|
208
|
-
if (!publicKey) {
|
|
209
|
-
throw new IQAuthError("TOKEN_INVALID", `Unknown key ID: ${kid}`);
|
|
210
|
-
}
|
|
211
|
-
const issuer = options.issuer ?? this.defaultIssuer;
|
|
212
|
-
const audience = options.audience ?? this.defaultAudience;
|
|
213
|
-
const clockTolerance = options.clockTolerance ?? this.defaultClockTolerance;
|
|
214
|
-
const algorithms = options.algorithms ?? ["RS256"];
|
|
215
|
-
try {
|
|
216
|
-
const verifyOptions = {
|
|
217
|
-
algorithms,
|
|
218
|
-
clockTolerance,
|
|
219
|
-
// The jsonwebtoken types insist on tuple types for arrays; runtime
|
|
220
|
-
// accepts plain string[] so we cast to satisfy the compiler.
|
|
221
|
-
issuer,
|
|
222
|
-
audience
|
|
223
|
-
};
|
|
224
|
-
const verified = jwt.verify(token, publicKey, verifyOptions);
|
|
225
|
-
return verified;
|
|
226
|
-
} catch (err) {
|
|
227
|
-
if (err instanceof Error) {
|
|
228
|
-
if (err.name === "TokenExpiredError") {
|
|
229
|
-
throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
|
|
230
|
-
}
|
|
231
|
-
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
232
|
-
}
|
|
233
|
-
throw new IQAuthError("TOKEN_INVALID", "Token verification failed");
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Decode a JWT without verification. Returns null if malformed.
|
|
238
|
-
*
|
|
239
|
-
* @remarks Local decode only — no network call
|
|
240
|
-
*/
|
|
241
|
-
decode(token) {
|
|
242
|
-
const decoded = jwt.decode(token);
|
|
243
|
-
return decoded;
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Check if a token is expired based on the `exp` claim.
|
|
247
|
-
*
|
|
248
|
-
* @remarks Local check only — no network call
|
|
249
|
-
*/
|
|
250
|
-
isExpired(token) {
|
|
251
|
-
const claims = this.decode(token);
|
|
252
|
-
if (!claims?.exp) return true;
|
|
253
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
254
|
-
return claims.exp <= now;
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Get the claims from a token without verification.
|
|
258
|
-
*
|
|
259
|
-
* @remarks Local decode only — no network call
|
|
260
|
-
*/
|
|
261
|
-
getClaims(token) {
|
|
262
|
-
const claims = this.decode(token);
|
|
263
|
-
if (!claims) {
|
|
264
|
-
throw new IQAuthError("TOKEN_INVALID", "Unable to decode token claims");
|
|
265
|
-
}
|
|
266
|
-
return claims;
|
|
267
|
-
}
|
|
268
|
-
async getPublicKey(kid) {
|
|
269
|
-
if (!this.jwksCache || Date.now() - this.jwksCache.fetchedAt > JWKS_CACHE_TTL_MS) {
|
|
270
|
-
await this.refreshJwks();
|
|
271
|
-
}
|
|
272
|
-
return this.jwksCache?.keys.get(kid) ?? null;
|
|
273
|
-
}
|
|
274
|
-
async refreshJwks() {
|
|
275
|
-
if (this.inFlightRefresh) {
|
|
276
|
-
return this.inFlightRefresh;
|
|
277
|
-
}
|
|
278
|
-
this.inFlightRefresh = (async () => {
|
|
279
|
-
try {
|
|
280
|
-
const res = await fetch(`${this.baseUrl}/.well-known/jwks.json`);
|
|
281
|
-
if (!res.ok) {
|
|
282
|
-
throw new IQAuthError(
|
|
283
|
-
"INTERNAL_ERROR",
|
|
284
|
-
`Failed to fetch JWKS: ${res.status}`
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
let jwks;
|
|
288
|
-
try {
|
|
289
|
-
jwks = await res.json();
|
|
290
|
-
} catch {
|
|
291
|
-
throw new IQAuthError("INTERNAL_ERROR", "Malformed JWKS response: invalid JSON");
|
|
292
|
-
}
|
|
293
|
-
if (!jwks || !Array.isArray(jwks.keys)) {
|
|
294
|
-
throw new IQAuthError(
|
|
295
|
-
"INTERNAL_ERROR",
|
|
296
|
-
"Malformed JWKS response: expected { keys: [...] }"
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
const keys = /* @__PURE__ */ new Map();
|
|
300
|
-
for (const key of jwks.keys) {
|
|
301
|
-
if (!key || typeof key.kid !== "string" || typeof key.n !== "string" || typeof key.e !== "string") {
|
|
302
|
-
throw new IQAuthError(
|
|
303
|
-
"INTERNAL_ERROR",
|
|
304
|
-
"Malformed JWKS response: key missing required fields"
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
const pem = this.jwkToPem(key);
|
|
308
|
-
keys.set(key.kid, pem);
|
|
309
|
-
}
|
|
310
|
-
this.jwksCache = { keys, fetchedAt: Date.now() };
|
|
311
|
-
} finally {
|
|
312
|
-
this.inFlightRefresh = null;
|
|
313
|
-
}
|
|
314
|
-
})();
|
|
315
|
-
return this.inFlightRefresh;
|
|
316
|
-
}
|
|
317
|
-
jwkToPem(jwk) {
|
|
318
|
-
const keyObject = crypto.createPublicKey({
|
|
319
|
-
key: {
|
|
320
|
-
kty: jwk.kty,
|
|
321
|
-
n: jwk.n,
|
|
322
|
-
e: jwk.e
|
|
323
|
-
},
|
|
324
|
-
format: "jwk"
|
|
325
|
-
});
|
|
326
|
-
return keyObject.export({ type: "spki", format: "pem" });
|
|
327
|
-
}
|
|
328
|
-
/** @internal Exposed for testing — clears JWKS cache */
|
|
329
|
-
clearCache() {
|
|
330
|
-
this.jwksCache = null;
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
|
|
334
166
|
// src/modules/sessions.ts
|
|
335
167
|
var SessionsModule = class {
|
|
336
168
|
constructor(http) {
|
|
@@ -532,7 +364,7 @@ var PermissionsModule = class {
|
|
|
532
364
|
};
|
|
533
365
|
|
|
534
366
|
// src/modules/oidc.ts
|
|
535
|
-
import
|
|
367
|
+
import crypto from "crypto";
|
|
536
368
|
var InMemoryOidcStateStore = class {
|
|
537
369
|
constructor() {
|
|
538
370
|
this.map = /* @__PURE__ */ new Map();
|
|
@@ -613,12 +445,12 @@ var OidcModule = class {
|
|
|
613
445
|
* ready to redirect the user to.
|
|
614
446
|
*/
|
|
615
447
|
async createAuthRequest(params) {
|
|
616
|
-
const codeVerifier = base64UrlEncode(
|
|
448
|
+
const codeVerifier = base64UrlEncode(crypto.randomBytes(32));
|
|
617
449
|
const codeChallenge = base64UrlEncode(
|
|
618
|
-
|
|
450
|
+
crypto.createHash("sha256").update(codeVerifier).digest()
|
|
619
451
|
);
|
|
620
|
-
const state = base64UrlEncode(
|
|
621
|
-
const nonce = base64UrlEncode(
|
|
452
|
+
const state = base64UrlEncode(crypto.randomBytes(16));
|
|
453
|
+
const nonce = base64UrlEncode(crypto.randomBytes(16));
|
|
622
454
|
await this.stateStore.set(state, {
|
|
623
455
|
codeVerifier,
|
|
624
456
|
state,
|
|
@@ -1712,10 +1544,6 @@ var IQAuthClient = class _IQAuthClient {
|
|
|
1712
1544
|
|
|
1713
1545
|
export {
|
|
1714
1546
|
AuthModule,
|
|
1715
|
-
DEFAULT_TOKEN_ISSUER,
|
|
1716
|
-
DEFAULT_TOKEN_AUDIENCE,
|
|
1717
|
-
DEFAULT_CLOCK_TOLERANCE_SECONDS,
|
|
1718
|
-
TokensModule,
|
|
1719
1547
|
SessionsModule,
|
|
1720
1548
|
UsersModule,
|
|
1721
1549
|
PermissionsModule,
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IQAuthError
|
|
3
|
+
} from "./chunk-6I6RM4MN.mjs";
|
|
4
|
+
import {
|
|
5
|
+
__require
|
|
6
|
+
} from "./chunk-Y6FXYEAI.mjs";
|
|
7
|
+
|
|
8
|
+
// src/publishableKey.ts
|
|
9
|
+
function b64urlEncode(input) {
|
|
10
|
+
if (typeof btoa === "function") {
|
|
11
|
+
const bytes = new TextEncoder().encode(input);
|
|
12
|
+
let bin = "";
|
|
13
|
+
for (let i = 0; i < bytes.byteLength; i++) bin += String.fromCharCode(bytes[i]);
|
|
14
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
15
|
+
}
|
|
16
|
+
const { Buffer } = __require("buffer");
|
|
17
|
+
return Buffer.from(input, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
18
|
+
}
|
|
19
|
+
function b64urlDecode(input) {
|
|
20
|
+
const pad = input.length % 4 === 0 ? "" : "=".repeat(4 - input.length % 4);
|
|
21
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/") + pad;
|
|
22
|
+
if (typeof atob === "function") {
|
|
23
|
+
const bin = atob(normalized);
|
|
24
|
+
const bytes = new Uint8Array(bin.length);
|
|
25
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
26
|
+
return new TextDecoder().decode(bytes);
|
|
27
|
+
}
|
|
28
|
+
const { Buffer } = __require("buffer");
|
|
29
|
+
return Buffer.from(normalized, "base64").toString("utf8");
|
|
30
|
+
}
|
|
31
|
+
function encodePublishableKey(mode, payload) {
|
|
32
|
+
if (mode !== "test" && mode !== "live") throw new Error(`Invalid mode: ${mode}`);
|
|
33
|
+
return `pk_${mode}_${b64urlEncode(JSON.stringify(payload))}`;
|
|
34
|
+
}
|
|
35
|
+
function isValidIssuerUrl(iss) {
|
|
36
|
+
if (typeof iss !== "string" || iss.length === 0) return false;
|
|
37
|
+
if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
|
|
38
|
+
try {
|
|
39
|
+
const u = new URL(iss);
|
|
40
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return false;
|
|
41
|
+
if (!u.hostname) return false;
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function parsePublishableKey(raw) {
|
|
48
|
+
if (typeof raw !== "string") return null;
|
|
49
|
+
const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
|
|
50
|
+
if (!m) return null;
|
|
51
|
+
try {
|
|
52
|
+
const json = JSON.parse(b64urlDecode(m[2]));
|
|
53
|
+
if (!json || typeof json !== "object") return null;
|
|
54
|
+
if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
if (!isValidIssuerUrl(json.iss)) return null;
|
|
58
|
+
return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function assertPublishableKey(raw, opts) {
|
|
64
|
+
const ctx = opts?.context ? `${opts.context}: ` : "";
|
|
65
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
66
|
+
throw new IQAuthError(
|
|
67
|
+
"CONFIG_INVALID",
|
|
68
|
+
`${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.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
|
|
72
|
+
if (!shapeMatch) {
|
|
73
|
+
throw new IQAuthError(
|
|
74
|
+
"CONFIG_INVALID",
|
|
75
|
+
`${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.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
let decoded;
|
|
79
|
+
try {
|
|
80
|
+
decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
|
|
81
|
+
} catch {
|
|
82
|
+
throw new IQAuthError(
|
|
83
|
+
"CONFIG_INVALID",
|
|
84
|
+
`${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (!isPublishableKeyPayload(decoded)) {
|
|
88
|
+
throw new IQAuthError(
|
|
89
|
+
"CONFIG_INVALID",
|
|
90
|
+
`${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (!isValidIssuerUrl(decoded.iss)) {
|
|
94
|
+
throw new IQAuthError(
|
|
95
|
+
"CONFIG_INVALID",
|
|
96
|
+
`${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.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
|
|
100
|
+
}
|
|
101
|
+
function isPublishableKeyPayload(value) {
|
|
102
|
+
if (!value || typeof value !== "object") return false;
|
|
103
|
+
const v = value;
|
|
104
|
+
return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
|
|
105
|
+
}
|
|
106
|
+
function isPublishableKey(raw) {
|
|
107
|
+
return typeof raw === "string" && /^pk_(test|live)_/.test(raw);
|
|
108
|
+
}
|
|
109
|
+
function isSecretKey(raw) {
|
|
110
|
+
return typeof raw === "string" && /^sk_(test|live)_/.test(raw);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
encodePublishableKey,
|
|
115
|
+
parsePublishableKey,
|
|
116
|
+
assertPublishableKey,
|
|
117
|
+
isPublishableKey,
|
|
118
|
+
isSecretKey
|
|
119
|
+
};
|
package/dist/cli/index.js
CHANGED
|
@@ -278,6 +278,13 @@ var init_util = __esm({
|
|
|
278
278
|
}
|
|
279
279
|
});
|
|
280
280
|
|
|
281
|
+
// src/errors.ts
|
|
282
|
+
var init_errors = __esm({
|
|
283
|
+
"src/errors.ts"() {
|
|
284
|
+
"use strict";
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
281
288
|
// src/publishableKey.ts
|
|
282
289
|
function b64urlDecode(input) {
|
|
283
290
|
const pad = input.length % 4 === 0 ? "" : "=".repeat(4 - input.length % 4);
|
|
@@ -291,6 +298,18 @@ function b64urlDecode(input) {
|
|
|
291
298
|
const { Buffer: Buffer2 } = require("buffer");
|
|
292
299
|
return Buffer2.from(normalized, "base64").toString("utf8");
|
|
293
300
|
}
|
|
301
|
+
function isValidIssuerUrl(iss) {
|
|
302
|
+
if (typeof iss !== "string" || iss.length === 0) return false;
|
|
303
|
+
if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
|
|
304
|
+
try {
|
|
305
|
+
const u = new URL(iss);
|
|
306
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return false;
|
|
307
|
+
if (!u.hostname) return false;
|
|
308
|
+
return true;
|
|
309
|
+
} catch {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
294
313
|
function parsePublishableKey(raw) {
|
|
295
314
|
if (typeof raw !== "string") return null;
|
|
296
315
|
const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
|
|
@@ -301,6 +320,7 @@ function parsePublishableKey(raw) {
|
|
|
301
320
|
if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
|
|
302
321
|
return null;
|
|
303
322
|
}
|
|
323
|
+
if (!isValidIssuerUrl(json.iss)) return null;
|
|
304
324
|
return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
|
|
305
325
|
} catch {
|
|
306
326
|
return null;
|
|
@@ -309,6 +329,7 @@ function parsePublishableKey(raw) {
|
|
|
309
329
|
var init_publishableKey = __esm({
|
|
310
330
|
"src/publishableKey.ts"() {
|
|
311
331
|
"use strict";
|
|
332
|
+
init_errors();
|
|
312
333
|
}
|
|
313
334
|
});
|
|
314
335
|
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { I as IQAuthEnvironment, T as TokenPair, W as IQAuthRetryConfig, L as LoginResult, a$ as SignupRequest, D as MfaVerifyResult, d as SessionUser, J as JwtClaims, h as Session, U as UserProfile, H as ProvisionUserRequest, K as ProvisionUserResponse, G as UserPermissions, O as OidcDiscovery, t as JwksResponse, u as OidcTokenResponse, b0 as HostedClientContext, i as TenantInfo, C as CreateTenantRequest, j as UpdateTenantRequest, P as PromoteToVendorRequest, k as PromoteToVendorResult, a7 as TenantUser, l as InviteTenantUserRequest, m as InviteTenantUserResult, n as TenantUserRoleUpdate, M as MigrateUserRequest, E as PasswordPolicy, F as MfaPolicy, B as BrandingConfig, _ as AppInfo, $ as PermissionNodeInfo, Z as AppManifest, a0 as AppSyncResult, a1 as Role, a2 as CreateRoleRequest, a3 as UpdateRoleRequest, a4 as AssignRoleRequest, a5 as UserRoleAssignment, a8 as PermissionGroup, a9 as GroupPermission, aa as AddGroupPermissionRequest, ab as InheritanceRelation, a6 as UserGroupAssignment, ac as UserPermissionOverride, ad as AddUserOverrideRequest, ae as EffectivePermission, af as PermissionCheckResult, ah as CreateApiKeyRequest, ai as CreateApiKeyResult, ag as ApiKeyInfo, aj as ApiKeyIntrospection, al as CreateInviteRequest, ak as Invitation, am as InviteValidation, an as AcceptInviteRequest, ap as CreateWebhookRequest, aq as CreateWebhookResult, ao as WebhookEndpoint, ar as WebhookDelivery, as as WebhookTestResult, at as Entitlement, au as GrantEntitlementRequest, av as Vendor, aw as CreateVendorRequest, ax as UpdateVendorRequest, az as CreateSourceRequest, ay as Source, aA as UpdateSourceRequest, aC as CreateClientRequest, aB as Client, aD as UpdateClientRequest, aE as HierarchyVendor, aH as HierarchyLink, aL as MembershipWithDetails, aJ as CreateMembershipRequest, aI as Membership, aK as UpdateMembershipRequest, aM as AvailableScopesTree, aQ as ScopeSwitchResult, aR as GdprExportData, aS as PinStatus, aU as MfaAvailableMethods, aV as TotpEnrollResult, aW as TotpVerifyResult, aX as SmsEnrollResult, y as MfaEnrollment, aY as EmailEnrollResult, aZ as BackupCodesResult, a_ as BackupCodeCountResult, o as UpdateBrandingRequest, q as UploadAssetRequest, p as BrandingAsset, r as BrandingDomainMapping, a as IQAuthClientConfig, c as IQAuthBrowserSessionClientConfig, b as IQAuthTokenClientConfig } from './types-Cxl3bQHt.js';
|
|
2
|
-
import jwt from 'jsonwebtoken';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* SOURCE REFS:
|
|
@@ -90,6 +89,14 @@ declare class AuthModule {
|
|
|
90
89
|
* - Route file: src/lib/crypto.ts (key rotation with kid)
|
|
91
90
|
* - Verified claims: sub, email, name, tenantId, vendorId, roles, entitlements, sessionId, jti, iss, aud, exp, iat, scopeContext, loginMethod
|
|
92
91
|
* - Last verified: Phase 0 Research Summary
|
|
92
|
+
*
|
|
93
|
+
* 2.3.0: Verify path swapped from `jsonwebtoken` (which depends on
|
|
94
|
+
* `node:crypto`) to `jose` so the SDK works on Next.js / Vercel / Cloudflare
|
|
95
|
+
* edge runtimes. Edge has only Web Crypto, so every call from a Next
|
|
96
|
+
* middleware previously threw and was wrapped as `TOKEN_INVALID`,
|
|
97
|
+
* indistinguishable from a real bad token. We keep our own JWKS fetch +
|
|
98
|
+
* cache to preserve INTERNAL_ERROR mapping for malformed JWKS payloads and
|
|
99
|
+
* to keep the kid-aware "Unknown key ID" diagnostic.
|
|
93
100
|
*/
|
|
94
101
|
|
|
95
102
|
declare const DEFAULT_TOKEN_ISSUER: string[];
|
|
@@ -99,7 +106,7 @@ interface TokenVerifyOptions {
|
|
|
99
106
|
issuer?: string | string[];
|
|
100
107
|
audience?: string | string[];
|
|
101
108
|
clockTolerance?: number;
|
|
102
|
-
algorithms?:
|
|
109
|
+
algorithms?: string[];
|
|
103
110
|
}
|
|
104
111
|
interface TokensModuleOptions {
|
|
105
112
|
issuer?: string | string[];
|
|
@@ -115,34 +122,22 @@ declare class TokensModule {
|
|
|
115
122
|
private defaultClockTolerance;
|
|
116
123
|
constructor(baseUrl: string, options?: TokensModuleOptions);
|
|
117
124
|
/**
|
|
118
|
-
* Verify a JWT access token using RS256 via JWKS from
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* clock tolerance default to client config but can be overridden per call.
|
|
125
|
+
* Verify a JWT access token using RS256/ES256 via JWKS from
|
|
126
|
+
* `/.well-known/jwks.json`. Backed by `jose` (Web Crypto) so it runs on
|
|
127
|
+
* Node, browser, and edge runtimes alike — no `node:crypto` dependency.
|
|
128
|
+
* Caches JWKS for 1 hour and refetches once on unknown `kid`.
|
|
123
129
|
*/
|
|
124
130
|
verify(token: string, options?: TokenVerifyOptions): Promise<JwtClaims>;
|
|
125
131
|
/**
|
|
126
132
|
* Decode a JWT without verification. Returns null if malformed.
|
|
127
|
-
*
|
|
128
|
-
* @remarks Local decode only — no network call
|
|
129
133
|
*/
|
|
130
134
|
decode(token: string): JwtClaims | null;
|
|
131
|
-
/**
|
|
132
|
-
* Check if a token is expired based on the `exp` claim.
|
|
133
|
-
*
|
|
134
|
-
* @remarks Local check only — no network call
|
|
135
|
-
*/
|
|
135
|
+
/** Check if a token is expired based on the `exp` claim. */
|
|
136
136
|
isExpired(token: string): boolean;
|
|
137
|
-
/**
|
|
138
|
-
* Get the claims from a token without verification.
|
|
139
|
-
*
|
|
140
|
-
* @remarks Local decode only — no network call
|
|
141
|
-
*/
|
|
137
|
+
/** Get the claims from a token without verification. */
|
|
142
138
|
getClaims(token: string): JwtClaims;
|
|
143
|
-
private
|
|
139
|
+
private ensureCache;
|
|
144
140
|
private refreshJwks;
|
|
145
|
-
private jwkToPem;
|
|
146
141
|
/** @internal Exposed for testing — clears JWKS cache */
|
|
147
142
|
clearCache(): void;
|
|
148
143
|
}
|