@iqauth/sdk 2.6.3 → 2.7.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 +173 -1
- package/dist/browser-session.d.mts +4 -4
- package/dist/browser-session.d.ts +4 -4
- package/dist/browser-session.js +181 -41
- package/dist/browser-session.mjs +3 -3
- package/dist/browser.d.mts +5 -5
- package/dist/browser.d.ts +5 -5
- package/dist/browser.js +271 -32
- package/dist/browser.mjs +10 -8
- package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
- package/dist/chunk-C2ZTBOAC.mjs +36 -0
- package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
- package/dist/chunk-GLXSIGVS.mjs +66 -0
- package/dist/{chunk-TKZTCPEK.mjs → chunk-GN37E64I.mjs} +32 -40
- package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
- package/dist/{chunk-W3F4JYGP.mjs → chunk-JXQI62A7.mjs} +108 -18
- package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
- package/dist/chunk-PMAFENVI.mjs +229 -0
- package/dist/chunk-RR2MGPTK.mjs +2724 -0
- package/dist/{chunk-76W5TLQQ.mjs → chunk-RTJAIBXY.mjs} +220 -20
- package/dist/{chunk-6TDJJER7.mjs → chunk-RUJXRTEW.mjs} +164 -5
- package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
- package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
- package/dist/{chunk-BVV54LPI.mjs → chunk-YVALAG3B.mjs} +10 -4
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/{client-kYlJFgPv.d.mts → client-BGFnBpfc.d.mts} +47 -4
- package/dist/{client-BNQe3AgF.d.ts → client-CDQ21LvW.d.ts} +47 -4
- package/dist/{doctor-YYNHNMLD.mjs → doctor-JAFXWU3X.mjs} +2 -2
- package/dist/errors-Jl1Jtm-6.d.mts +107 -0
- package/dist/errors-Jl1Jtm-6.d.ts +107 -0
- package/dist/{express-B6_1vBYZ.d.mts → express-CVNQEkOr.d.mts} +2 -2
- package/dist/{express-CHpfa7D_.d.ts → express-Piv2WhWM.d.ts} +2 -2
- package/dist/express.d.mts +7 -6
- package/dist/express.d.ts +7 -6
- package/dist/express.js +349 -52
- package/dist/express.mjs +39 -12
- package/dist/fastify.d.mts +2 -0
- package/dist/fastify.d.ts +2 -0
- package/dist/fastify.js +332 -52
- package/dist/fastify.mjs +23 -8
- package/dist/hono.d.mts +2 -0
- package/dist/hono.d.ts +2 -0
- package/dist/hono.js +329 -52
- package/dist/hono.mjs +20 -8
- package/dist/index-5KSZEnDe.d.ts +1626 -0
- package/dist/index-CKoZHAoc.d.mts +1626 -0
- package/dist/index.d.mts +56 -8
- package/dist/index.d.ts +56 -8
- package/dist/index.js +565 -69
- package/dist/index.mjs +29 -9
- package/dist/{keys-NLWFAOEM.mjs → keys-6Y776TG2.mjs} +2 -2
- package/dist/locales.d.mts +1 -1
- package/dist/locales.d.ts +1 -1
- package/dist/mobile.d.mts +77 -7
- package/dist/mobile.d.ts +77 -7
- package/dist/mobile.js +276 -41
- package/dist/mobile.mjs +98 -3
- package/dist/next.d.mts +2 -1
- package/dist/next.d.ts +2 -1
- package/dist/next.js +391 -201
- package/dist/next.mjs +22 -7
- package/dist/pkce-7WKV4OIN.mjs +11 -0
- package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-CGpMRie4.d.ts} +1 -1
- package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-M5G47LWO.d.mts} +1 -1
- package/dist/{publishableKey-BaR0HoAH.d.ts → publishableKey-f2kq-rKw.d.mts} +1 -1
- package/dist/{publishableKey-BaR0HoAH.d.mts → publishableKey-f2kq-rKw.d.ts} +1 -1
- package/dist/react-permissions.d.mts +52 -0
- package/dist/react-permissions.d.ts +52 -0
- package/dist/react-permissions.js +239 -0
- package/dist/react-permissions.mjs +97 -0
- package/dist/react.d.mts +9 -1624
- package/dist/react.d.ts +9 -1624
- package/dist/react.js +343 -36
- package/dist/react.mjs +59 -2611
- package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
- package/dist/server/handlers.d.mts +148 -3
- package/dist/server/handlers.d.ts +148 -3
- package/dist/server/handlers.js +410 -11
- package/dist/server/handlers.mjs +12 -3
- package/dist/server.d.mts +151 -8
- package/dist/server.d.ts +151 -8
- package/dist/server.js +406 -50
- package/dist/server.mjs +93 -11
- package/dist/service.d.mts +4 -4
- package/dist/service.d.ts +4 -4
- package/dist/service.js +181 -41
- package/dist/service.mjs +3 -3
- package/dist/{signIn-CiIBTJIh.d.mts → signIn-BLFnz8SV.d.ts} +78 -3
- package/dist/{signIn-CCY4JE5G.mjs → signIn-SHBW6Z4T.mjs} +2 -1
- package/dist/{signIn-OCr88Zf8.d.ts → signIn-T-CZ6t6r.d.mts} +78 -3
- package/dist/test.mjs +3 -3
- package/dist/{tokens-DCyzzn8L.d.mts → tokens-Bqhmqq_R.d.ts} +9 -2
- package/dist/{tokens-aHiGFr_E.d.ts → tokens-CITeoG6P.d.mts} +9 -2
- package/dist/{types-6bNdxesb.d.ts → types-BdQ2lqfT.d.mts} +1 -1
- package/dist/{types-6bNdxesb.d.mts → types-BdQ2lqfT.d.ts} +1 -1
- package/dist/{types-DZAflmmq.d.mts → types-XOV9XPVi.d.mts} +99 -10
- package/dist/{types-DZAflmmq.d.ts → types-XOV9XPVi.d.ts} +99 -10
- package/dist/webhooks.d.mts +100 -17
- package/dist/webhooks.d.ts +100 -17
- package/dist/webhooks.js +164 -15
- package/dist/webhooks.mjs +7 -1
- package/dist/ws.d.mts +2 -2
- package/dist/ws.d.ts +2 -2
- package/dist/ws.js +80 -30
- package/dist/ws.mjs +4 -4
- package/docs/error-handling.md +101 -0
- package/docs/guides/effective-permissions.md +171 -0
- package/package.json +13 -3
- package/dist/chunk-UKZLOHZG.mjs +0 -83
- package/dist/errors-CDdl24MP.d.mts +0 -52
- package/dist/errors-CDdl24MP.d.ts +0 -52
package/dist/next.js
CHANGED
|
@@ -27,13 +27,30 @@ __export(next_exports, {
|
|
|
27
27
|
module.exports = __toCommonJS(next_exports);
|
|
28
28
|
|
|
29
29
|
// src/errors.ts
|
|
30
|
-
var IQAuthError = class extends Error {
|
|
31
|
-
constructor(code, message, status,
|
|
30
|
+
var IQAuthError = class _IQAuthError extends Error {
|
|
31
|
+
constructor(code, message, status, cause) {
|
|
32
32
|
super(message);
|
|
33
33
|
this.name = "IQAuthError";
|
|
34
34
|
this.code = code;
|
|
35
35
|
this.status = status;
|
|
36
|
-
this.
|
|
36
|
+
this.cause = cause;
|
|
37
|
+
this.raw = cause;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Type guard: true when `value` is an `IQAuthError`. Useful for adapters
|
|
41
|
+
* that round-trip errors through `unknown` (e.g. fastify's `setErrorHandler`).
|
|
42
|
+
*/
|
|
43
|
+
static isIQAuthError(value) {
|
|
44
|
+
return value instanceof _IQAuthError || typeof value === "object" && value !== null && value.name === "IQAuthError" && typeof value.code === "string";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Type-narrowed code check. Lets callers write
|
|
48
|
+
* `if (err.is("token_expired")) …` with full IntelliSense for the typed
|
|
49
|
+
* taxonomy without losing the ability to handle server codes via
|
|
50
|
+
* `err.code === "TOKEN_REVOKED"`.
|
|
51
|
+
*/
|
|
52
|
+
is(code) {
|
|
53
|
+
return this.code === code;
|
|
37
54
|
}
|
|
38
55
|
};
|
|
39
56
|
|
|
@@ -66,14 +83,14 @@ function assertPublishableKey(raw, opts) {
|
|
|
66
83
|
const ctx = opts?.context ? `${opts.context}: ` : "";
|
|
67
84
|
if (typeof raw !== "string" || raw.length === 0) {
|
|
68
85
|
throw new IQAuthError(
|
|
69
|
-
"
|
|
86
|
+
"config_invalid",
|
|
70
87
|
`${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.`
|
|
71
88
|
);
|
|
72
89
|
}
|
|
73
90
|
const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
|
|
74
91
|
if (!shapeMatch) {
|
|
75
92
|
throw new IQAuthError(
|
|
76
|
-
"
|
|
93
|
+
"config_invalid",
|
|
77
94
|
`${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.`
|
|
78
95
|
);
|
|
79
96
|
}
|
|
@@ -82,19 +99,19 @@ function assertPublishableKey(raw, opts) {
|
|
|
82
99
|
decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
|
|
83
100
|
} catch {
|
|
84
101
|
throw new IQAuthError(
|
|
85
|
-
"
|
|
102
|
+
"config_invalid",
|
|
86
103
|
`${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
|
|
87
104
|
);
|
|
88
105
|
}
|
|
89
106
|
if (!isPublishableKeyPayload(decoded)) {
|
|
90
107
|
throw new IQAuthError(
|
|
91
|
-
"
|
|
108
|
+
"config_invalid",
|
|
92
109
|
`${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
|
|
93
110
|
);
|
|
94
111
|
}
|
|
95
112
|
if (!isValidIssuerUrl(decoded.iss)) {
|
|
96
113
|
throw new IQAuthError(
|
|
97
|
-
"
|
|
114
|
+
"config_invalid",
|
|
98
115
|
`${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.`
|
|
99
116
|
);
|
|
100
117
|
}
|
|
@@ -106,7 +123,267 @@ function isPublishableKeyPayload(value) {
|
|
|
106
123
|
return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
|
|
107
124
|
}
|
|
108
125
|
|
|
126
|
+
// src/modules/tokens.ts
|
|
127
|
+
var import_jose = require("jose");
|
|
128
|
+
var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
129
|
+
var DEFAULT_TOKEN_ISSUER = [
|
|
130
|
+
"https://auth.dispositioniq.com",
|
|
131
|
+
"auth.dispositioniq.com"
|
|
132
|
+
];
|
|
133
|
+
var DEFAULT_TOKEN_AUDIENCE = [
|
|
134
|
+
"dispositioniq",
|
|
135
|
+
"iqcapture",
|
|
136
|
+
"iqreuse",
|
|
137
|
+
"iqvalidate"
|
|
138
|
+
];
|
|
139
|
+
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
140
|
+
function classifyJoseError(err) {
|
|
141
|
+
if (err instanceof import_jose.errors.JWTExpired) {
|
|
142
|
+
return { code: "token_expired", message: "Token has expired" };
|
|
143
|
+
}
|
|
144
|
+
if (err instanceof import_jose.errors.JOSEError) {
|
|
145
|
+
return { code: "token_invalid", message: err.message };
|
|
146
|
+
}
|
|
147
|
+
if (err instanceof Error) {
|
|
148
|
+
return { code: "token_invalid", message: err.message };
|
|
149
|
+
}
|
|
150
|
+
return { code: "token_invalid", message: "Token verification failed" };
|
|
151
|
+
}
|
|
152
|
+
function decodeProtectedHeader(token) {
|
|
153
|
+
const parts = token.split(".");
|
|
154
|
+
if (parts.length < 2) return null;
|
|
155
|
+
try {
|
|
156
|
+
const padded = parts[0] + "=".repeat((4 - parts[0].length % 4) % 4);
|
|
157
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
158
|
+
let json;
|
|
159
|
+
if (typeof atob === "function") {
|
|
160
|
+
json = atob(b64);
|
|
161
|
+
} else {
|
|
162
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
163
|
+
json = Buffer2.from(b64, "base64").toString("utf8");
|
|
164
|
+
}
|
|
165
|
+
return JSON.parse(json);
|
|
166
|
+
} catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
var TokensModule = class {
|
|
171
|
+
constructor(baseUrl, options = {}) {
|
|
172
|
+
this.jwksCache = null;
|
|
173
|
+
this.inFlightRefresh = null;
|
|
174
|
+
this.baseUrl = baseUrl;
|
|
175
|
+
this.defaultIssuer = options.issuer ?? DEFAULT_TOKEN_ISSUER;
|
|
176
|
+
this.defaultAudience = options.audience ?? DEFAULT_TOKEN_AUDIENCE;
|
|
177
|
+
this.defaultClockTolerance = options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Verify a JWT access token using RS256/ES256 via JWKS from
|
|
181
|
+
* `/.well-known/jwks.json`. Backed by `jose` (Web Crypto) so it runs on
|
|
182
|
+
* Node, browser, and edge runtimes alike — no `node:crypto` dependency.
|
|
183
|
+
* Caches JWKS for 1 hour and refetches once on unknown `kid`.
|
|
184
|
+
*/
|
|
185
|
+
async verify(token, options = {}) {
|
|
186
|
+
const header = decodeProtectedHeader(token);
|
|
187
|
+
if (!header) {
|
|
188
|
+
throw new IQAuthError("token_invalid", "Unable to decode token");
|
|
189
|
+
}
|
|
190
|
+
const kid = header.kid;
|
|
191
|
+
if (!kid) {
|
|
192
|
+
throw new IQAuthError("token_invalid", "Token missing kid header");
|
|
193
|
+
}
|
|
194
|
+
let cache = await this.ensureCache();
|
|
195
|
+
if (!cache.byKid.has(kid)) {
|
|
196
|
+
this.jwksCache = null;
|
|
197
|
+
cache = await this.ensureCache();
|
|
198
|
+
}
|
|
199
|
+
if (!cache.byKid.has(kid)) {
|
|
200
|
+
throw new IQAuthError("token_invalid", `Unknown key ID: ${kid}`);
|
|
201
|
+
}
|
|
202
|
+
const issuer = options.issuer ?? this.defaultIssuer;
|
|
203
|
+
const audience = options.audience ?? this.defaultAudience;
|
|
204
|
+
const clockTolerance = options.clockTolerance ?? this.defaultClockTolerance;
|
|
205
|
+
const algorithms = options.algorithms ?? ["RS256", "ES256"];
|
|
206
|
+
const verifyOptions = {
|
|
207
|
+
algorithms,
|
|
208
|
+
clockTolerance,
|
|
209
|
+
issuer,
|
|
210
|
+
audience
|
|
211
|
+
};
|
|
212
|
+
try {
|
|
213
|
+
const { payload } = await (0, import_jose.jwtVerify)(token, cache.verifier, verifyOptions);
|
|
214
|
+
return payload;
|
|
215
|
+
} catch (err) {
|
|
216
|
+
const classified = classifyJoseError(err);
|
|
217
|
+
throw new IQAuthError(classified.code, classified.message, void 0, err);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Decode a JWT without verification. Returns null if malformed.
|
|
222
|
+
*/
|
|
223
|
+
decode(token) {
|
|
224
|
+
try {
|
|
225
|
+
const parts = token.split(".");
|
|
226
|
+
if (parts.length < 2) return null;
|
|
227
|
+
const payload = parts[1];
|
|
228
|
+
const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
229
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
230
|
+
let json;
|
|
231
|
+
if (typeof atob === "function") {
|
|
232
|
+
json = atob(b64);
|
|
233
|
+
} else {
|
|
234
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
235
|
+
json = Buffer2.from(b64, "base64").toString("utf8");
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
json = decodeURIComponent(escape(json));
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
const claims = JSON.parse(json);
|
|
242
|
+
if (!claims || typeof claims !== "object") return null;
|
|
243
|
+
return claims;
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** Check if a token is expired based on the `exp` claim. */
|
|
249
|
+
isExpired(token) {
|
|
250
|
+
const claims = this.decode(token);
|
|
251
|
+
if (!claims?.exp) return true;
|
|
252
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
253
|
+
return claims.exp <= now;
|
|
254
|
+
}
|
|
255
|
+
/** Get the claims from a token without verification. */
|
|
256
|
+
getClaims(token) {
|
|
257
|
+
const claims = this.decode(token);
|
|
258
|
+
if (!claims) {
|
|
259
|
+
throw new IQAuthError("token_invalid", "Unable to decode token claims");
|
|
260
|
+
}
|
|
261
|
+
return claims;
|
|
262
|
+
}
|
|
263
|
+
async ensureCache() {
|
|
264
|
+
if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) {
|
|
265
|
+
return this.jwksCache;
|
|
266
|
+
}
|
|
267
|
+
await this.refreshJwks();
|
|
268
|
+
if (!this.jwksCache) {
|
|
269
|
+
throw new IQAuthError("jwks_unavailable", "JWKS cache unavailable after refresh");
|
|
270
|
+
}
|
|
271
|
+
return this.jwksCache;
|
|
272
|
+
}
|
|
273
|
+
async refreshJwks() {
|
|
274
|
+
if (this.inFlightRefresh) {
|
|
275
|
+
return this.inFlightRefresh;
|
|
276
|
+
}
|
|
277
|
+
this.inFlightRefresh = (async () => {
|
|
278
|
+
try {
|
|
279
|
+
let res;
|
|
280
|
+
try {
|
|
281
|
+
res = await fetch(`${this.baseUrl}/.well-known/jwks.json`);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
throw new IQAuthError(
|
|
284
|
+
"network",
|
|
285
|
+
err instanceof Error ? err.message : "JWKS fetch network error",
|
|
286
|
+
void 0,
|
|
287
|
+
err
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (!res.ok) {
|
|
291
|
+
throw new IQAuthError(
|
|
292
|
+
"jwks_fetch_failed",
|
|
293
|
+
`Failed to fetch JWKS: ${res.status}`,
|
|
294
|
+
res.status
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
let jwks;
|
|
298
|
+
try {
|
|
299
|
+
jwks = await res.json();
|
|
300
|
+
} catch (err) {
|
|
301
|
+
throw new IQAuthError(
|
|
302
|
+
"jwks_fetch_failed",
|
|
303
|
+
"Malformed JWKS response: invalid JSON",
|
|
304
|
+
res.status,
|
|
305
|
+
err
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
if (!jwks || !Array.isArray(jwks.keys)) {
|
|
309
|
+
throw new IQAuthError(
|
|
310
|
+
"jwks_fetch_failed",
|
|
311
|
+
"Malformed JWKS response: expected { keys: [...] }"
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
const byKid = /* @__PURE__ */ new Set();
|
|
315
|
+
for (const key of jwks.keys) {
|
|
316
|
+
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")) {
|
|
317
|
+
throw new IQAuthError(
|
|
318
|
+
"jwks_fetch_failed",
|
|
319
|
+
"Malformed JWKS response: key missing required fields"
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
byKid.add(key.kid);
|
|
323
|
+
}
|
|
324
|
+
const verifier = (0, import_jose.createLocalJWKSet)({ keys: jwks.keys });
|
|
325
|
+
this.jwksCache = { raw: jwks.keys, byKid, verifier, fetchedAt: Date.now() };
|
|
326
|
+
} finally {
|
|
327
|
+
this.inFlightRefresh = null;
|
|
328
|
+
}
|
|
329
|
+
})();
|
|
330
|
+
return this.inFlightRefresh;
|
|
331
|
+
}
|
|
332
|
+
/** @internal Exposed for testing — clears JWKS cache */
|
|
333
|
+
clearCache() {
|
|
334
|
+
this.jwksCache = null;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Task #126: Eagerly populate the JWKS cache so the first verify() call
|
|
338
|
+
* doesn't pay a network round-trip. Safe to call repeatedly — single-flight
|
|
339
|
+
* behavior is shared with the lazy refresh path. Errors are swallowed so
|
|
340
|
+
* callers (e.g. `attachHelpers` auto-prewarm) can fire-and-forget.
|
|
341
|
+
*/
|
|
342
|
+
async prewarm() {
|
|
343
|
+
if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) return;
|
|
344
|
+
try {
|
|
345
|
+
await this.refreshJwks();
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
109
351
|
// src/server/handlers.ts
|
|
352
|
+
async function buildUserinfoResponse(claims, opts = {}) {
|
|
353
|
+
const baseUser = {
|
|
354
|
+
sub: claims.sub,
|
|
355
|
+
email: claims.email,
|
|
356
|
+
name: claims.name,
|
|
357
|
+
tenantId: claims.tenantId,
|
|
358
|
+
vendorId: claims.vendorId,
|
|
359
|
+
roles: claims.roles ?? [],
|
|
360
|
+
entitlements: claims.entitlements ?? []
|
|
361
|
+
};
|
|
362
|
+
const enriched = opts.enrich ? await opts.enrich(claims) : null;
|
|
363
|
+
const user = enriched ? { ...baseUser, ...enriched } : baseUser;
|
|
364
|
+
return {
|
|
365
|
+
success: true,
|
|
366
|
+
data: {
|
|
367
|
+
user,
|
|
368
|
+
claims,
|
|
369
|
+
tenantId: claims.tenantId ?? null
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function emitTiming(cfg, event) {
|
|
374
|
+
if (cfg.debug) {
|
|
375
|
+
try {
|
|
376
|
+
console.debug("[iqauth_helper]", event);
|
|
377
|
+
} catch {
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (cfg.onTimingEvent) {
|
|
381
|
+
try {
|
|
382
|
+
cfg.onTimingEvent(event);
|
|
383
|
+
} catch {
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
110
387
|
var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
111
388
|
"TOKEN_REVOKED",
|
|
112
389
|
"SESSION_REVOKED",
|
|
@@ -146,7 +423,11 @@ function resolve(config) {
|
|
|
146
423
|
})),
|
|
147
424
|
appId: parsed.appId,
|
|
148
425
|
tenantId: parsed.tenantId,
|
|
149
|
-
clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
|
|
426
|
+
clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only",
|
|
427
|
+
debug: config.debug,
|
|
428
|
+
onTimingEvent: config.onTimingEvent,
|
|
429
|
+
signoutRegistry: config.signoutRegistry ?? defaultSignoutRegistry,
|
|
430
|
+
signoutMarkerTtlMs: config.signoutMarkerTtlMs ?? DEFAULT_SIGNOUT_TTL_MS
|
|
150
431
|
};
|
|
151
432
|
}
|
|
152
433
|
function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
@@ -163,15 +444,41 @@ function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
|
163
444
|
}
|
|
164
445
|
function clearCookies(cfg) {
|
|
165
446
|
return [
|
|
166
|
-
makeCookie(cfg, cfg.accessCookieName, "", 0),
|
|
167
|
-
makeCookie(cfg, cfg.refreshCookieName, "", 0)
|
|
447
|
+
{ ...makeCookie(cfg, cfg.accessCookieName, "", 0), clear: true },
|
|
448
|
+
{ ...makeCookie(cfg, cfg.refreshCookieName, "", 0), clear: true }
|
|
168
449
|
];
|
|
169
450
|
}
|
|
451
|
+
var DEFAULT_SIGNOUT_TTL_MS = 6e4;
|
|
452
|
+
var inMemorySignoutMarkers = /* @__PURE__ */ new Map();
|
|
453
|
+
function pruneInMemoryMarkers(now) {
|
|
454
|
+
if (inMemorySignoutMarkers.size === 0) return;
|
|
455
|
+
for (const [k, exp] of inMemorySignoutMarkers) {
|
|
456
|
+
if (exp <= now) inMemorySignoutMarkers.delete(k);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
var defaultSignoutRegistry = {
|
|
460
|
+
mark(token, ttlMs) {
|
|
461
|
+
const now = Date.now();
|
|
462
|
+
pruneInMemoryMarkers(now);
|
|
463
|
+
inMemorySignoutMarkers.set(token, now + ttlMs);
|
|
464
|
+
},
|
|
465
|
+
has(token) {
|
|
466
|
+
const now = Date.now();
|
|
467
|
+
const exp = inMemorySignoutMarkers.get(token);
|
|
468
|
+
if (!exp) return false;
|
|
469
|
+
if (exp <= now) {
|
|
470
|
+
inMemorySignoutMarkers.delete(token);
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
};
|
|
170
476
|
function serializeCookie(d) {
|
|
171
477
|
const parts = [`${d.name}=${encodeURIComponent(d.value)}`];
|
|
172
478
|
parts.push(`Path=${d.path}`);
|
|
173
479
|
if (d.domain) parts.push(`Domain=${d.domain}`);
|
|
174
480
|
parts.push(`Max-Age=${d.maxAge}`);
|
|
481
|
+
if (d.clear) parts.push("Expires=Thu, 01 Jan 1970 00:00:00 GMT");
|
|
175
482
|
if (d.secure) parts.push("Secure");
|
|
176
483
|
if (d.httpOnly) parts.push("HttpOnly");
|
|
177
484
|
parts.push(`SameSite=${d.sameSite}`);
|
|
@@ -179,7 +486,9 @@ function serializeCookie(d) {
|
|
|
179
486
|
}
|
|
180
487
|
async function handleCallback(config, input) {
|
|
181
488
|
const cfg = resolve(config);
|
|
489
|
+
const t0 = Date.now();
|
|
182
490
|
if (!input.code || !input.redirectUri) {
|
|
491
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "VALIDATION_ERROR" });
|
|
183
492
|
return {
|
|
184
493
|
status: 400,
|
|
185
494
|
body: { success: false, error: { code: "VALIDATION_ERROR", message: "code and redirectUri are required" } },
|
|
@@ -187,6 +496,7 @@ async function handleCallback(config, input) {
|
|
|
187
496
|
};
|
|
188
497
|
}
|
|
189
498
|
if (!cfg.secretKey) {
|
|
499
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "INTERNAL_ERROR" });
|
|
190
500
|
return {
|
|
191
501
|
status: 500,
|
|
192
502
|
body: { success: false, error: { code: "INTERNAL_ERROR", message: "secretKey is required for the callback handler" } },
|
|
@@ -210,6 +520,7 @@ async function handleCallback(config, input) {
|
|
|
210
520
|
});
|
|
211
521
|
const json = await res.json().catch(() => ({}));
|
|
212
522
|
if (!res.ok || !json.access_token) {
|
|
523
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: json.error || "OIDC_EXCHANGE_FAILED" });
|
|
213
524
|
return {
|
|
214
525
|
status: res.status || 502,
|
|
215
526
|
body: {
|
|
@@ -229,6 +540,7 @@ async function handleCallback(config, input) {
|
|
|
229
540
|
if (json.refresh_token) {
|
|
230
541
|
cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.refresh_token, REFRESH_TOKEN_TTL_SECONDS));
|
|
231
542
|
}
|
|
543
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: true });
|
|
232
544
|
return {
|
|
233
545
|
status: 200,
|
|
234
546
|
body: { success: true, data: { authenticated: true } },
|
|
@@ -237,8 +549,18 @@ async function handleCallback(config, input) {
|
|
|
237
549
|
}
|
|
238
550
|
async function handleRefresh(config, input) {
|
|
239
551
|
const cfg = resolve(config);
|
|
552
|
+
const t0 = Date.now();
|
|
240
553
|
const refreshToken = input.refreshToken;
|
|
554
|
+
const idemKey = input.idempotencyToken;
|
|
555
|
+
if (idemKey && await Promise.resolve(cfg.signoutRegistry.has(idemKey))) {
|
|
556
|
+
return {
|
|
557
|
+
status: 401,
|
|
558
|
+
body: { success: false, error: { code: "SESSION_REVOKED", message: "Session was signed out" } },
|
|
559
|
+
cookies: clearCookies(cfg)
|
|
560
|
+
};
|
|
561
|
+
}
|
|
241
562
|
if (!refreshToken) {
|
|
563
|
+
emitTiming(cfg, { phase: "refresh", durationMs: Date.now() - t0, ok: false, code: "TOKEN_INVALID" });
|
|
242
564
|
return {
|
|
243
565
|
status: 401,
|
|
244
566
|
body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
|
|
@@ -254,6 +576,7 @@ async function handleRefresh(config, input) {
|
|
|
254
576
|
if (!res.ok || !json.success || !json.data?.accessToken) {
|
|
255
577
|
const status = res.status || 401;
|
|
256
578
|
const errorCode = json.error?.code || "TOKEN_INVALID";
|
|
579
|
+
emitTiming(cfg, { phase: "refresh", durationMs: Date.now() - t0, ok: false, code: errorCode });
|
|
257
580
|
const shouldClear = shouldClearCookiesOnFailure(
|
|
258
581
|
cfg.clearCookiesOnRefreshFailure,
|
|
259
582
|
status,
|
|
@@ -277,6 +600,7 @@ async function handleRefresh(config, input) {
|
|
|
277
600
|
if (json.data.refreshToken) {
|
|
278
601
|
cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.data.refreshToken, REFRESH_TOKEN_TTL_SECONDS));
|
|
279
602
|
}
|
|
603
|
+
emitTiming(cfg, { phase: "refresh", durationMs: Date.now() - t0, ok: true });
|
|
280
604
|
return {
|
|
281
605
|
status: 200,
|
|
282
606
|
body: { success: true, data: { accessToken: json.data.accessToken } },
|
|
@@ -285,6 +609,10 @@ async function handleRefresh(config, input) {
|
|
|
285
609
|
}
|
|
286
610
|
async function handleSignout(config, input) {
|
|
287
611
|
const cfg = resolve(config);
|
|
612
|
+
const t0 = Date.now();
|
|
613
|
+
if (input.idempotencyToken) {
|
|
614
|
+
await Promise.resolve(cfg.signoutRegistry.mark(input.idempotencyToken, cfg.signoutMarkerTtlMs));
|
|
615
|
+
}
|
|
288
616
|
if (input.accessToken) {
|
|
289
617
|
try {
|
|
290
618
|
await cfg.fetchImpl(`${cfg.issuer}${cfg.logoutPath}`, {
|
|
@@ -306,204 +634,52 @@ async function handleSignout(config, input) {
|
|
|
306
634
|
} catch {
|
|
307
635
|
}
|
|
308
636
|
}
|
|
637
|
+
emitTiming(cfg, { phase: "signout", durationMs: Date.now() - t0, ok: true });
|
|
309
638
|
return {
|
|
310
639
|
status: 200,
|
|
311
640
|
body: { success: true, data: { signedOut: true } },
|
|
312
641
|
cookies: clearCookies(cfg)
|
|
313
642
|
};
|
|
314
643
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
"auth.dispositioniq.com"
|
|
322
|
-
];
|
|
323
|
-
var DEFAULT_TOKEN_AUDIENCE = [
|
|
324
|
-
"dispositioniq",
|
|
325
|
-
"iqcapture",
|
|
326
|
-
"iqreuse",
|
|
327
|
-
"iqvalidate"
|
|
328
|
-
];
|
|
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;
|
|
644
|
+
var TOKENS_CACHE = /* @__PURE__ */ new Map();
|
|
645
|
+
function getTokensFor(issuer) {
|
|
646
|
+
let m = TOKENS_CACHE.get(issuer);
|
|
647
|
+
if (!m) {
|
|
648
|
+
m = new TokensModule(issuer);
|
|
649
|
+
TOKENS_CACHE.set(issuer, m);
|
|
346
650
|
}
|
|
651
|
+
return m;
|
|
347
652
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
this.defaultClockTolerance = options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
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`.
|
|
362
|
-
*/
|
|
363
|
-
async verify(token, options = {}) {
|
|
364
|
-
const header = decodeProtectedHeader(token);
|
|
365
|
-
if (!header) {
|
|
366
|
-
throw new IQAuthError("TOKEN_INVALID", "Unable to decode token");
|
|
367
|
-
}
|
|
368
|
-
const kid = header.kid;
|
|
369
|
-
if (!kid) {
|
|
370
|
-
throw new IQAuthError("TOKEN_INVALID", "Token missing kid header");
|
|
371
|
-
}
|
|
372
|
-
let cache = await this.ensureCache();
|
|
373
|
-
if (!cache.byKid.has(kid)) {
|
|
374
|
-
this.jwksCache = null;
|
|
375
|
-
cache = await this.ensureCache();
|
|
376
|
-
}
|
|
377
|
-
if (!cache.byKid.has(kid)) {
|
|
378
|
-
throw new IQAuthError("TOKEN_INVALID", `Unknown key ID: ${kid}`);
|
|
379
|
-
}
|
|
380
|
-
const issuer = options.issuer ?? this.defaultIssuer;
|
|
381
|
-
const audience = options.audience ?? this.defaultAudience;
|
|
382
|
-
const clockTolerance = options.clockTolerance ?? this.defaultClockTolerance;
|
|
383
|
-
const algorithms = options.algorithms ?? ["RS256", "ES256"];
|
|
384
|
-
const verifyOptions = {
|
|
385
|
-
algorithms,
|
|
386
|
-
clockTolerance,
|
|
387
|
-
issuer,
|
|
388
|
-
audience
|
|
653
|
+
async function handleUserinfo(config, input) {
|
|
654
|
+
const cfg = resolve(config);
|
|
655
|
+
if (!input.accessToken) {
|
|
656
|
+
return {
|
|
657
|
+
status: 401,
|
|
658
|
+
body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing access token" } },
|
|
659
|
+
cookies: []
|
|
389
660
|
};
|
|
390
|
-
try {
|
|
391
|
-
const { payload } = await (0, import_jose.jwtVerify)(token, cache.verifier, verifyOptions);
|
|
392
|
-
return payload;
|
|
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
|
-
}
|
|
400
|
-
if (err instanceof Error) {
|
|
401
|
-
throw new IQAuthError("TOKEN_INVALID", err.message);
|
|
402
|
-
}
|
|
403
|
-
throw new IQAuthError("TOKEN_INVALID", "Token verification failed");
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Decode a JWT without verification. Returns null if malformed.
|
|
408
|
-
*/
|
|
409
|
-
decode(token) {
|
|
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
|
-
}
|
|
433
661
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
throw new IQAuthError("TOKEN_INVALID", "Unable to decode token claims");
|
|
446
|
-
}
|
|
447
|
-
return claims;
|
|
448
|
-
}
|
|
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");
|
|
456
|
-
}
|
|
457
|
-
return this.jwksCache;
|
|
458
|
-
}
|
|
459
|
-
async refreshJwks() {
|
|
460
|
-
if (this.inFlightRefresh) {
|
|
461
|
-
return this.inFlightRefresh;
|
|
462
|
-
}
|
|
463
|
-
this.inFlightRefresh = (async () => {
|
|
464
|
-
try {
|
|
465
|
-
const res = await fetch(`${this.baseUrl}/.well-known/jwks.json`);
|
|
466
|
-
if (!res.ok) {
|
|
467
|
-
throw new IQAuthError(
|
|
468
|
-
"INTERNAL_ERROR",
|
|
469
|
-
`Failed to fetch JWKS: ${res.status}`
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
let jwks;
|
|
473
|
-
try {
|
|
474
|
-
jwks = await res.json();
|
|
475
|
-
} catch {
|
|
476
|
-
throw new IQAuthError("INTERNAL_ERROR", "Malformed JWKS response: invalid JSON");
|
|
477
|
-
}
|
|
478
|
-
if (!jwks || !Array.isArray(jwks.keys)) {
|
|
479
|
-
throw new IQAuthError(
|
|
480
|
-
"INTERNAL_ERROR",
|
|
481
|
-
"Malformed JWKS response: expected { keys: [...] }"
|
|
482
|
-
);
|
|
483
|
-
}
|
|
484
|
-
const byKid = /* @__PURE__ */ new Set();
|
|
485
|
-
for (const key of jwks.keys) {
|
|
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")) {
|
|
487
|
-
throw new IQAuthError(
|
|
488
|
-
"INTERNAL_ERROR",
|
|
489
|
-
"Malformed JWKS response: key missing required fields"
|
|
490
|
-
);
|
|
491
|
-
}
|
|
492
|
-
byKid.add(key.kid);
|
|
493
|
-
}
|
|
494
|
-
const verifier = (0, import_jose.createLocalJWKSet)({ keys: jwks.keys });
|
|
495
|
-
this.jwksCache = { raw: jwks.keys, byKid, verifier, fetchedAt: Date.now() };
|
|
496
|
-
} finally {
|
|
497
|
-
this.inFlightRefresh = null;
|
|
498
|
-
}
|
|
499
|
-
})();
|
|
500
|
-
return this.inFlightRefresh;
|
|
501
|
-
}
|
|
502
|
-
/** @internal Exposed for testing — clears JWKS cache */
|
|
503
|
-
clearCache() {
|
|
504
|
-
this.jwksCache = null;
|
|
662
|
+
let claims;
|
|
663
|
+
try {
|
|
664
|
+
claims = await getTokensFor(cfg.issuer).verify(input.accessToken, config.verify);
|
|
665
|
+
} catch (err) {
|
|
666
|
+
const code = err instanceof IQAuthError ? err.code : err.code || "TOKEN_INVALID";
|
|
667
|
+
const message = err instanceof Error ? err.message : "Access token verification failed";
|
|
668
|
+
return {
|
|
669
|
+
status: 401,
|
|
670
|
+
body: { success: false, error: { code, message } },
|
|
671
|
+
cookies: []
|
|
672
|
+
};
|
|
505
673
|
}
|
|
506
|
-
|
|
674
|
+
const envelope = await buildUserinfoResponse(claims, {
|
|
675
|
+
enrich: config.userinfoEnricher ? (c) => config.userinfoEnricher(c, input.req) : void 0
|
|
676
|
+
});
|
|
677
|
+
return {
|
|
678
|
+
status: 200,
|
|
679
|
+
body: envelope,
|
|
680
|
+
cookies: []
|
|
681
|
+
};
|
|
682
|
+
}
|
|
507
683
|
|
|
508
684
|
// src/next.ts
|
|
509
685
|
function readCookieFromHeader(header, name) {
|
|
@@ -535,8 +711,19 @@ function handler(options) {
|
|
|
535
711
|
return async (req) => {
|
|
536
712
|
const url = new URL(req.url);
|
|
537
713
|
const action = url.pathname.split("/").pop();
|
|
538
|
-
const body = await req.json().catch(() => ({}));
|
|
539
714
|
const cookieHeader = req.headers.get("cookie");
|
|
715
|
+
if (action === "me" && req.method === "GET") {
|
|
716
|
+
if (!options.mountUserinfo) {
|
|
717
|
+
return new Response(JSON.stringify({ success: false, error: { code: "NOT_FOUND", message: "userinfo route not enabled" } }), {
|
|
718
|
+
status: 404,
|
|
719
|
+
headers: { "Content-Type": "application/json" }
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
const auth = req.headers.get("authorization");
|
|
723
|
+
const accessToken = auth && auth.replace(/^Bearer /i, "") || readCookieFromHeader(cookieHeader, accessCookie);
|
|
724
|
+
return toResponse(await handleUserinfo(helperConfig, { accessToken, req }));
|
|
725
|
+
}
|
|
726
|
+
const body = await req.json().catch(() => ({}));
|
|
540
727
|
if (action === "callback") {
|
|
541
728
|
return toResponse(await handleCallback(helperConfig, {
|
|
542
729
|
code: body.code,
|
|
@@ -546,12 +733,15 @@ function handler(options) {
|
|
|
546
733
|
}
|
|
547
734
|
if (action === "refresh") {
|
|
548
735
|
const refreshToken = body.refreshToken || readCookieFromHeader(cookieHeader, refreshCookie);
|
|
549
|
-
|
|
736
|
+
const idempotencyToken = req.headers.get("x-iqauth-idempotency") || body.idempotencyToken;
|
|
737
|
+
return toResponse(await handleRefresh(helperConfig, { refreshToken, idempotencyToken: idempotencyToken ?? void 0 }));
|
|
550
738
|
}
|
|
551
739
|
if (action === "signout") {
|
|
552
740
|
const auth = req.headers.get("authorization");
|
|
553
741
|
const accessToken = auth && auth.replace(/^Bearer /i, "") || readCookieFromHeader(cookieHeader, accessCookie);
|
|
554
|
-
|
|
742
|
+
const refreshToken = readCookieFromHeader(cookieHeader, refreshCookie);
|
|
743
|
+
const idempotencyToken = req.headers.get("x-iqauth-idempotency") ?? void 0;
|
|
744
|
+
return toResponse(await handleSignout(helperConfig, { accessToken, refreshToken, idempotencyToken, ssoCookieHeader: cookieHeader ?? void 0 }));
|
|
555
745
|
}
|
|
556
746
|
return new Response(JSON.stringify({ success: false, error: { code: "NOT_FOUND", message: `Unknown action: ${action}` } }), {
|
|
557
747
|
status: 404,
|