@iqauth/sdk 2.6.4 → 2.8.1
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 +212 -46
- 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 +293 -34
- package/dist/browser.mjs +5 -5
- package/dist/{chunk-BVV54LPI.mjs → chunk-25SSYDIP.mjs} +10 -4
- package/dist/{chunk-XAWYUPMO.mjs → chunk-4V7FKOTG.mjs} +242 -22
- package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
- package/dist/{chunk-SL3KRS4W.mjs → chunk-CIJORODR.mjs} +23 -1
- package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
- package/dist/chunk-GLXSIGVS.mjs +66 -0
- package/dist/{chunk-DJIBN2N7.mjs → chunk-GN37E64I.mjs} +29 -7
- package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
- package/dist/chunk-JRDVUWAL.mjs +46 -0
- package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
- package/dist/{chunk-5T7GHBX6.mjs → chunk-TLET552H.mjs} +36 -0
- package/dist/chunk-VYQ3ETCK.mjs +244 -0
- package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
- package/dist/chunk-WHT6WKTY.mjs +3180 -0
- package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
- package/dist/chunk-WSH4SW7F.mjs +490 -0
- package/dist/{chunk-W3F4JYGP.mjs → chunk-ZLJPABB7.mjs} +139 -23
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/{client-BNQe3AgF.d.ts → client-D8L-PaWr.d.mts} +59 -6
- package/dist/{client-kYlJFgPv.d.mts → client-DkPL0EPZ.d.ts} +59 -6
- 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-CHpfa7D_.d.ts → express-Budysq4h.d.ts} +2 -2
- package/dist/{express-B6_1vBYZ.d.mts → express-DDTA3qV1.d.mts} +2 -2
- package/dist/express.d.mts +7 -6
- package/dist/express.d.ts +7 -6
- package/dist/express.js +563 -85
- package/dist/express.mjs +73 -34
- package/dist/fastify.d.mts +10 -0
- package/dist/fastify.d.ts +10 -0
- package/dist/fastify.js +589 -65
- package/dist/fastify.mjs +101 -11
- package/dist/hono.d.mts +10 -0
- package/dist/hono.d.ts +10 -0
- package/dist/hono.js +566 -65
- package/dist/hono.mjs +78 -11
- package/dist/index-Cko-d5po.d.mts +1848 -0
- package/dist/index-RNqwEcmY.d.ts +1848 -0
- package/dist/index.d.mts +56 -8
- package/dist/index.d.ts +56 -8
- package/dist/index.js +694 -75
- package/dist/index.mjs +30 -10
- 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/locales.js +36 -0
- package/dist/locales.mjs +1 -1
- package/dist/mobile.d.mts +77 -7
- package/dist/mobile.d.ts +77 -7
- package/dist/mobile.js +307 -46
- package/dist/mobile.mjs +98 -3
- package/dist/next.d.mts +10 -1
- package/dist/next.d.ts +10 -1
- package/dist/next.js +596 -205
- package/dist/next.mjs +83 -10
- package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-BXPMZCLe.d.ts} +30 -2
- package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-IEycmsgb.d.mts} +30 -2
- 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 +98 -0
- package/dist/react.d.mts +9 -1624
- package/dist/react.d.ts +9 -1624
- package/dist/react.js +882 -73
- package/dist/react.mjs +71 -2631
- package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
- package/dist/server/handlers.d.mts +200 -4
- package/dist/server/handlers.d.ts +200 -4
- package/dist/server/handlers.js +530 -16
- package/dist/server/handlers.mjs +14 -3
- package/dist/server.d.mts +171 -8
- package/dist/server.d.ts +171 -8
- package/dist/server.js +579 -61
- package/dist/server.mjs +99 -12
- package/dist/service.d.mts +4 -4
- package/dist/service.d.ts +4 -4
- package/dist/service.js +212 -46
- package/dist/service.mjs +3 -3
- package/dist/{signIn-CiIBTJIh.d.mts → signIn-CReqfXsh.d.mts} +95 -3
- package/dist/{signIn-OCr88Zf8.d.ts → signIn-Cfa1GTpO.d.ts} +95 -3
- package/dist/{signIn-4OKLDEIH.mjs → signIn-SHBW6Z4T.mjs} +1 -1
- package/dist/test.mjs +3 -3
- package/dist/{tokens-DCyzzn8L.d.mts → tokens-9F6ETrzk.d.ts} +9 -2
- package/dist/{tokens-aHiGFr_E.d.ts → tokens-B06VtvUi.d.mts} +9 -2
- package/dist/{types-DZAflmmq.d.mts → types-Bn8O-OEd.d.mts} +164 -11
- package/dist/{types-DZAflmmq.d.ts → types-Bn8O-OEd.d.ts} +164 -11
- package/dist/{types-6bNdxesb.d.ts → types-DnU2LhXR.d.mts} +7 -1
- package/dist/{types-6bNdxesb.d.mts → types-DnU2LhXR.d.ts} +7 -1
- package/dist/webhooks.d.mts +113 -17
- package/dist/webhooks.d.ts +113 -17
- package/dist/webhooks.js +179 -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/docs/guides/invitations.md +65 -0
- package/package.json +19 -4
- package/dist/chunk-6TDJJER7.mjs +0 -217
- 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/server/handlers.js
CHANGED
|
@@ -20,21 +20,43 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/server/handlers.ts
|
|
21
21
|
var handlers_exports = {};
|
|
22
22
|
__export(handlers_exports, {
|
|
23
|
+
__resetSignoutMarkersForTests: () => __resetSignoutMarkersForTests,
|
|
24
|
+
__resetSignoutRegistryWarningForTests: () => __resetSignoutRegistryWarningForTests,
|
|
25
|
+
buildUserinfoResponse: () => buildUserinfoResponse,
|
|
26
|
+
createInMemorySignoutRegistry: () => createInMemorySignoutRegistry,
|
|
23
27
|
handleCallback: () => handleCallback,
|
|
24
28
|
handleRefresh: () => handleRefresh,
|
|
25
29
|
handleSignout: () => handleSignout,
|
|
30
|
+
handleUserinfo: () => handleUserinfo,
|
|
26
31
|
serializeCookie: () => serializeCookie
|
|
27
32
|
});
|
|
28
33
|
module.exports = __toCommonJS(handlers_exports);
|
|
29
34
|
|
|
30
35
|
// src/errors.ts
|
|
31
|
-
var IQAuthError = class extends Error {
|
|
32
|
-
constructor(code, message, status,
|
|
36
|
+
var IQAuthError = class _IQAuthError extends Error {
|
|
37
|
+
constructor(code, message, status, cause) {
|
|
33
38
|
super(message);
|
|
34
39
|
this.name = "IQAuthError";
|
|
35
40
|
this.code = code;
|
|
36
41
|
this.status = status;
|
|
37
|
-
this.
|
|
42
|
+
this.cause = cause;
|
|
43
|
+
this.raw = cause;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Type guard: true when `value` is an `IQAuthError`. Useful for adapters
|
|
47
|
+
* that round-trip errors through `unknown` (e.g. fastify's `setErrorHandler`).
|
|
48
|
+
*/
|
|
49
|
+
static isIQAuthError(value) {
|
|
50
|
+
return value instanceof _IQAuthError || typeof value === "object" && value !== null && value.name === "IQAuthError" && typeof value.code === "string";
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Type-narrowed code check. Lets callers write
|
|
54
|
+
* `if (err.is("token_expired")) …` with full IntelliSense for the typed
|
|
55
|
+
* taxonomy without losing the ability to handle server codes via
|
|
56
|
+
* `err.code === "TOKEN_REVOKED"`.
|
|
57
|
+
*/
|
|
58
|
+
is(code) {
|
|
59
|
+
return this.code === code;
|
|
38
60
|
}
|
|
39
61
|
};
|
|
40
62
|
|
|
@@ -67,14 +89,14 @@ function assertPublishableKey(raw, opts) {
|
|
|
67
89
|
const ctx = opts?.context ? `${opts.context}: ` : "";
|
|
68
90
|
if (typeof raw !== "string" || raw.length === 0) {
|
|
69
91
|
throw new IQAuthError(
|
|
70
|
-
"
|
|
92
|
+
"config_invalid",
|
|
71
93
|
`${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.`
|
|
72
94
|
);
|
|
73
95
|
}
|
|
74
96
|
const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
|
|
75
97
|
if (!shapeMatch) {
|
|
76
98
|
throw new IQAuthError(
|
|
77
|
-
"
|
|
99
|
+
"config_invalid",
|
|
78
100
|
`${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.`
|
|
79
101
|
);
|
|
80
102
|
}
|
|
@@ -83,19 +105,19 @@ function assertPublishableKey(raw, opts) {
|
|
|
83
105
|
decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
|
|
84
106
|
} catch {
|
|
85
107
|
throw new IQAuthError(
|
|
86
|
-
"
|
|
108
|
+
"config_invalid",
|
|
87
109
|
`${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
|
|
88
110
|
);
|
|
89
111
|
}
|
|
90
112
|
if (!isPublishableKeyPayload(decoded)) {
|
|
91
113
|
throw new IQAuthError(
|
|
92
|
-
"
|
|
114
|
+
"config_invalid",
|
|
93
115
|
`${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
|
|
94
116
|
);
|
|
95
117
|
}
|
|
96
118
|
if (!isValidIssuerUrl(decoded.iss)) {
|
|
97
119
|
throw new IQAuthError(
|
|
98
|
-
"
|
|
120
|
+
"config_invalid",
|
|
99
121
|
`${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.`
|
|
100
122
|
);
|
|
101
123
|
}
|
|
@@ -107,7 +129,271 @@ function isPublishableKeyPayload(value) {
|
|
|
107
129
|
return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
|
|
108
130
|
}
|
|
109
131
|
|
|
132
|
+
// src/modules/tokens.ts
|
|
133
|
+
var import_jose = require("jose");
|
|
134
|
+
var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
135
|
+
var DEFAULT_TOKEN_ISSUER = [
|
|
136
|
+
"https://auth.dispositioniq.com",
|
|
137
|
+
"auth.dispositioniq.com"
|
|
138
|
+
];
|
|
139
|
+
var DEFAULT_TOKEN_AUDIENCE = [
|
|
140
|
+
"dispositioniq",
|
|
141
|
+
"iqcapture",
|
|
142
|
+
"iqreuse",
|
|
143
|
+
"iqvalidate"
|
|
144
|
+
];
|
|
145
|
+
var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
|
|
146
|
+
function classifyJoseError(err) {
|
|
147
|
+
if (err instanceof import_jose.errors.JWTExpired) {
|
|
148
|
+
return { code: "token_expired", message: "Token has expired" };
|
|
149
|
+
}
|
|
150
|
+
if (err instanceof import_jose.errors.JOSEError) {
|
|
151
|
+
return { code: "token_invalid", message: err.message };
|
|
152
|
+
}
|
|
153
|
+
if (err instanceof Error) {
|
|
154
|
+
return { code: "token_invalid", message: err.message };
|
|
155
|
+
}
|
|
156
|
+
return { code: "token_invalid", message: "Token verification failed" };
|
|
157
|
+
}
|
|
158
|
+
function decodeProtectedHeader(token) {
|
|
159
|
+
const parts = token.split(".");
|
|
160
|
+
if (parts.length < 2) return null;
|
|
161
|
+
try {
|
|
162
|
+
const padded = parts[0] + "=".repeat((4 - parts[0].length % 4) % 4);
|
|
163
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
164
|
+
let json;
|
|
165
|
+
if (typeof atob === "function") {
|
|
166
|
+
json = atob(b64);
|
|
167
|
+
} else {
|
|
168
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
169
|
+
json = Buffer2.from(b64, "base64").toString("utf8");
|
|
170
|
+
}
|
|
171
|
+
return JSON.parse(json);
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
var TokensModule = class {
|
|
177
|
+
constructor(baseUrl, options = {}) {
|
|
178
|
+
this.jwksCache = null;
|
|
179
|
+
this.inFlightRefresh = null;
|
|
180
|
+
this.baseUrl = baseUrl;
|
|
181
|
+
this.defaultIssuer = options.issuer ?? DEFAULT_TOKEN_ISSUER;
|
|
182
|
+
this.defaultAudience = options.audience ?? DEFAULT_TOKEN_AUDIENCE;
|
|
183
|
+
this.defaultClockTolerance = options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Verify a JWT access token using RS256/ES256 via JWKS from
|
|
187
|
+
* `/.well-known/jwks.json`. Backed by `jose` (Web Crypto) so it runs on
|
|
188
|
+
* Node, browser, and edge runtimes alike — no `node:crypto` dependency.
|
|
189
|
+
* Caches JWKS for 1 hour and refetches once on unknown `kid`.
|
|
190
|
+
*/
|
|
191
|
+
async verify(token, options = {}) {
|
|
192
|
+
const header = decodeProtectedHeader(token);
|
|
193
|
+
if (!header) {
|
|
194
|
+
throw new IQAuthError("token_invalid", "Unable to decode token");
|
|
195
|
+
}
|
|
196
|
+
const kid = header.kid;
|
|
197
|
+
if (!kid) {
|
|
198
|
+
throw new IQAuthError("token_invalid", "Token missing kid header");
|
|
199
|
+
}
|
|
200
|
+
let cache = await this.ensureCache();
|
|
201
|
+
if (!cache.byKid.has(kid)) {
|
|
202
|
+
this.jwksCache = null;
|
|
203
|
+
cache = await this.ensureCache();
|
|
204
|
+
}
|
|
205
|
+
if (!cache.byKid.has(kid)) {
|
|
206
|
+
throw new IQAuthError("token_invalid", `Unknown key ID: ${kid}`);
|
|
207
|
+
}
|
|
208
|
+
const issuer = options.issuer ?? this.defaultIssuer;
|
|
209
|
+
const audience = options.audience ?? this.defaultAudience;
|
|
210
|
+
const clockTolerance = options.clockTolerance ?? this.defaultClockTolerance;
|
|
211
|
+
const algorithms = options.algorithms ?? ["RS256", "ES256"];
|
|
212
|
+
const verifyOptions = {
|
|
213
|
+
algorithms,
|
|
214
|
+
clockTolerance,
|
|
215
|
+
issuer,
|
|
216
|
+
audience
|
|
217
|
+
};
|
|
218
|
+
try {
|
|
219
|
+
const { payload } = await (0, import_jose.jwtVerify)(token, cache.verifier, verifyOptions);
|
|
220
|
+
return payload;
|
|
221
|
+
} catch (err) {
|
|
222
|
+
const classified = classifyJoseError(err);
|
|
223
|
+
throw new IQAuthError(classified.code, classified.message, void 0, err);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Decode a JWT without verification. Returns null if malformed.
|
|
228
|
+
*/
|
|
229
|
+
decode(token) {
|
|
230
|
+
try {
|
|
231
|
+
const parts = token.split(".");
|
|
232
|
+
if (parts.length < 2) return null;
|
|
233
|
+
const payload = parts[1];
|
|
234
|
+
const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
235
|
+
const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
236
|
+
let json;
|
|
237
|
+
if (typeof atob === "function") {
|
|
238
|
+
json = atob(b64);
|
|
239
|
+
} else {
|
|
240
|
+
const { Buffer: Buffer2 } = require("buffer");
|
|
241
|
+
json = Buffer2.from(b64, "base64").toString("utf8");
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
json = decodeURIComponent(escape(json));
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
const claims = JSON.parse(json);
|
|
248
|
+
if (!claims || typeof claims !== "object") return null;
|
|
249
|
+
return claims;
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/** Check if a token is expired based on the `exp` claim. */
|
|
255
|
+
isExpired(token) {
|
|
256
|
+
const claims = this.decode(token);
|
|
257
|
+
if (!claims?.exp) return true;
|
|
258
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
259
|
+
return claims.exp <= now;
|
|
260
|
+
}
|
|
261
|
+
/** Get the claims from a token without verification. */
|
|
262
|
+
getClaims(token) {
|
|
263
|
+
const claims = this.decode(token);
|
|
264
|
+
if (!claims) {
|
|
265
|
+
throw new IQAuthError("token_invalid", "Unable to decode token claims");
|
|
266
|
+
}
|
|
267
|
+
return claims;
|
|
268
|
+
}
|
|
269
|
+
async ensureCache() {
|
|
270
|
+
if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) {
|
|
271
|
+
return this.jwksCache;
|
|
272
|
+
}
|
|
273
|
+
await this.refreshJwks();
|
|
274
|
+
if (!this.jwksCache) {
|
|
275
|
+
throw new IQAuthError("jwks_unavailable", "JWKS cache unavailable after refresh");
|
|
276
|
+
}
|
|
277
|
+
return this.jwksCache;
|
|
278
|
+
}
|
|
279
|
+
async refreshJwks() {
|
|
280
|
+
if (this.inFlightRefresh) {
|
|
281
|
+
return this.inFlightRefresh;
|
|
282
|
+
}
|
|
283
|
+
this.inFlightRefresh = (async () => {
|
|
284
|
+
try {
|
|
285
|
+
let res;
|
|
286
|
+
try {
|
|
287
|
+
res = await fetch(`${this.baseUrl}/.well-known/jwks.json`);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
throw new IQAuthError(
|
|
290
|
+
"network",
|
|
291
|
+
err instanceof Error ? err.message : "JWKS fetch network error",
|
|
292
|
+
void 0,
|
|
293
|
+
err
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
if (!res.ok) {
|
|
297
|
+
throw new IQAuthError(
|
|
298
|
+
"jwks_fetch_failed",
|
|
299
|
+
`Failed to fetch JWKS: ${res.status}`,
|
|
300
|
+
res.status
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
let jwks;
|
|
304
|
+
try {
|
|
305
|
+
jwks = await res.json();
|
|
306
|
+
} catch (err) {
|
|
307
|
+
throw new IQAuthError(
|
|
308
|
+
"jwks_fetch_failed",
|
|
309
|
+
"Malformed JWKS response: invalid JSON",
|
|
310
|
+
res.status,
|
|
311
|
+
err
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
if (!jwks || !Array.isArray(jwks.keys)) {
|
|
315
|
+
throw new IQAuthError(
|
|
316
|
+
"jwks_fetch_failed",
|
|
317
|
+
"Malformed JWKS response: expected { keys: [...] }"
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
const byKid = /* @__PURE__ */ new Set();
|
|
321
|
+
for (const key of jwks.keys) {
|
|
322
|
+
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")) {
|
|
323
|
+
throw new IQAuthError(
|
|
324
|
+
"jwks_fetch_failed",
|
|
325
|
+
"Malformed JWKS response: key missing required fields"
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
byKid.add(key.kid);
|
|
329
|
+
}
|
|
330
|
+
const verifier = (0, import_jose.createLocalJWKSet)({ keys: jwks.keys });
|
|
331
|
+
this.jwksCache = { raw: jwks.keys, byKid, verifier, fetchedAt: Date.now() };
|
|
332
|
+
} finally {
|
|
333
|
+
this.inFlightRefresh = null;
|
|
334
|
+
}
|
|
335
|
+
})();
|
|
336
|
+
return this.inFlightRefresh;
|
|
337
|
+
}
|
|
338
|
+
/** @internal Exposed for testing — clears JWKS cache */
|
|
339
|
+
clearCache() {
|
|
340
|
+
this.jwksCache = null;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Task #126: Eagerly populate the JWKS cache so the first verify() call
|
|
344
|
+
* doesn't pay a network round-trip. Safe to call repeatedly — single-flight
|
|
345
|
+
* behavior is shared with the lazy refresh path. Errors are swallowed so
|
|
346
|
+
* callers (e.g. `attachHelpers` auto-prewarm) can fire-and-forget.
|
|
347
|
+
*/
|
|
348
|
+
async prewarm() {
|
|
349
|
+
if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) return;
|
|
350
|
+
try {
|
|
351
|
+
await this.refreshJwks();
|
|
352
|
+
} catch {
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
110
357
|
// src/server/handlers.ts
|
|
358
|
+
async function buildUserinfoResponse(claims, opts = {}) {
|
|
359
|
+
const baseUser = {
|
|
360
|
+
sub: claims.sub,
|
|
361
|
+
email: claims.email,
|
|
362
|
+
name: claims.name,
|
|
363
|
+
tenantId: claims.tenantId,
|
|
364
|
+
vendorId: claims.vendorId,
|
|
365
|
+
roles: claims.roles ?? [],
|
|
366
|
+
entitlements: claims.entitlements ?? [],
|
|
367
|
+
// Task #171 — project the active source/client scope onto the userinfo
|
|
368
|
+
// payload so server handlers (`getSessionUser`, `/api/iqauth/userinfo`)
|
|
369
|
+
// expose it without consumers having to re-decode the JWT.
|
|
370
|
+
...claims.scopeContext !== void 0 ? { scopeContext: claims.scopeContext } : {}
|
|
371
|
+
};
|
|
372
|
+
const enriched = opts.enrich ? await opts.enrich(claims) : null;
|
|
373
|
+
const user = enriched ? { ...baseUser, ...enriched } : baseUser;
|
|
374
|
+
return {
|
|
375
|
+
success: true,
|
|
376
|
+
data: {
|
|
377
|
+
user,
|
|
378
|
+
claims,
|
|
379
|
+
tenantId: claims.tenantId ?? null
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function emitTiming(cfg, event) {
|
|
384
|
+
if (cfg.debug) {
|
|
385
|
+
try {
|
|
386
|
+
console.debug("[iqauth_helper]", event);
|
|
387
|
+
} catch {
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (cfg.onTimingEvent) {
|
|
391
|
+
try {
|
|
392
|
+
cfg.onTimingEvent(event);
|
|
393
|
+
} catch {
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
111
397
|
var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
112
398
|
"TOKEN_REVOKED",
|
|
113
399
|
"SESSION_REVOKED",
|
|
@@ -126,19 +412,62 @@ function shouldClearCookiesOnFailure(policy, status, errorCode) {
|
|
|
126
412
|
}
|
|
127
413
|
var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
|
|
128
414
|
var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
415
|
+
function assertCookiePrefixInvariants(name, secure, path, domain) {
|
|
416
|
+
if (name.startsWith("__Host-")) {
|
|
417
|
+
if (!secure) {
|
|
418
|
+
throw new IQAuthError(
|
|
419
|
+
"config_invalid",
|
|
420
|
+
`Cookie "${name}" uses the __Host- prefix, which browsers only accept on a Secure cookie. Set secure:true (and serve over HTTPS).`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
if (path !== "/") {
|
|
424
|
+
throw new IQAuthError(
|
|
425
|
+
"config_invalid",
|
|
426
|
+
`Cookie "${name}" uses the __Host- prefix, which requires Path=/ (got "${path}"). Remove cookiePath or set it to "/".`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
if (domain) {
|
|
430
|
+
throw new IQAuthError(
|
|
431
|
+
"config_invalid",
|
|
432
|
+
`Cookie "${name}" uses the __Host- prefix, which forbids a Domain attribute (the cookie is host-locked). Remove cookieDomain.`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
} else if (name.startsWith("__Secure-") && !secure) {
|
|
436
|
+
throw new IQAuthError(
|
|
437
|
+
"config_invalid",
|
|
438
|
+
`Cookie "${name}" uses the __Secure- prefix, which browsers only accept on a Secure cookie. Set secure:true (and serve over HTTPS).`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
129
442
|
function resolve(config) {
|
|
130
443
|
const parsed = assertPublishableKey(config.publishableKey, { context: "@iqauth/sdk helpers" });
|
|
131
444
|
const inferredIssuer = parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`;
|
|
445
|
+
maybeWarnDefaultSignoutRegistry(config);
|
|
446
|
+
const secure = config.secure ?? true;
|
|
447
|
+
if (config.secure === false && config.allowInsecureCookies !== true) {
|
|
448
|
+
throw new IQAuthError(
|
|
449
|
+
"config_invalid",
|
|
450
|
+
"Refusing to issue auth cookies with secure:false \u2014 this exposes session cookies over plaintext HTTP. For local HTTP development, set allowInsecureCookies:true to acknowledge the risk. Production MUST use HTTPS with secure cookies."
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
const accessCookieName = config.accessCookieName ?? config.cookieNames?.access ?? "iqauth_at";
|
|
454
|
+
const refreshCookieName = config.refreshCookieName ?? config.cookieNames?.refresh ?? "iqauth_rt";
|
|
455
|
+
const stateCookieName = config.stateCookieName ?? "iqauth_state";
|
|
456
|
+
const cookiePath = config.cookiePath ?? "/";
|
|
457
|
+
const cookieDomain = config.cookieDomain;
|
|
458
|
+
for (const name of [accessCookieName, refreshCookieName, stateCookieName]) {
|
|
459
|
+
assertCookiePrefixInvariants(name, secure, cookiePath, cookieDomain);
|
|
460
|
+
}
|
|
132
461
|
return {
|
|
133
462
|
publishableKey: config.publishableKey,
|
|
134
463
|
secretKey: config.secretKey,
|
|
135
464
|
issuer: (config.issuer ?? inferredIssuer).replace(/\/+$/, ""),
|
|
136
|
-
accessCookieName
|
|
137
|
-
refreshCookieName
|
|
138
|
-
cookieDomain
|
|
465
|
+
accessCookieName,
|
|
466
|
+
refreshCookieName,
|
|
467
|
+
cookieDomain,
|
|
139
468
|
sameSite: config.sameSite ?? "lax",
|
|
140
|
-
secure
|
|
141
|
-
cookiePath
|
|
469
|
+
secure,
|
|
470
|
+
cookiePath,
|
|
142
471
|
tokenPath: config.tokenPath ?? "/oidc/token",
|
|
143
472
|
refreshPath: config.refreshPath ?? "/api/v1/auth/refresh",
|
|
144
473
|
logoutPath: config.logoutPath ?? "/api/v1/auth/logout",
|
|
@@ -147,9 +476,23 @@ function resolve(config) {
|
|
|
147
476
|
})),
|
|
148
477
|
appId: parsed.appId,
|
|
149
478
|
tenantId: parsed.tenantId,
|
|
150
|
-
clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
|
|
479
|
+
clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only",
|
|
480
|
+
debug: config.debug,
|
|
481
|
+
onTimingEvent: config.onTimingEvent,
|
|
482
|
+
signoutRegistry: config.signoutRegistry ?? defaultSignoutRegistry,
|
|
483
|
+
signoutMarkerTtlMs: config.signoutMarkerTtlMs ?? DEFAULT_SIGNOUT_TTL_MS,
|
|
484
|
+
requireOAuthState: config.requireOAuthState ?? true,
|
|
485
|
+
stateCookieName: config.stateCookieName ?? "iqauth_state"
|
|
151
486
|
};
|
|
152
487
|
}
|
|
488
|
+
function timingSafeEqualStr(a, b) {
|
|
489
|
+
const len = Math.max(a.length, b.length);
|
|
490
|
+
let diff = a.length ^ b.length;
|
|
491
|
+
for (let i = 0; i < len; i++) {
|
|
492
|
+
diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
|
|
493
|
+
}
|
|
494
|
+
return diff === 0;
|
|
495
|
+
}
|
|
153
496
|
function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
154
497
|
return {
|
|
155
498
|
name,
|
|
@@ -164,15 +507,79 @@ function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
|
164
507
|
}
|
|
165
508
|
function clearCookies(cfg) {
|
|
166
509
|
return [
|
|
167
|
-
makeCookie(cfg, cfg.accessCookieName, "", 0),
|
|
168
|
-
makeCookie(cfg, cfg.refreshCookieName, "", 0)
|
|
510
|
+
{ ...makeCookie(cfg, cfg.accessCookieName, "", 0), clear: true },
|
|
511
|
+
{ ...makeCookie(cfg, cfg.refreshCookieName, "", 0), clear: true }
|
|
169
512
|
];
|
|
170
513
|
}
|
|
514
|
+
function clearStateCookie(cfg) {
|
|
515
|
+
return { ...makeCookie(cfg, cfg.stateCookieName, "", 0, false), clear: true };
|
|
516
|
+
}
|
|
517
|
+
var DEFAULT_SIGNOUT_TTL_MS = 6e4;
|
|
518
|
+
var inMemorySignoutMarkers = /* @__PURE__ */ new Map();
|
|
519
|
+
function pruneInMemoryMarkers(now) {
|
|
520
|
+
if (inMemorySignoutMarkers.size === 0) return;
|
|
521
|
+
for (const [k, exp] of inMemorySignoutMarkers) {
|
|
522
|
+
if (exp <= now) inMemorySignoutMarkers.delete(k);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
var defaultSignoutRegistry = {
|
|
526
|
+
mark(token, ttlMs) {
|
|
527
|
+
const now = Date.now();
|
|
528
|
+
pruneInMemoryMarkers(now);
|
|
529
|
+
inMemorySignoutMarkers.set(token, now + ttlMs);
|
|
530
|
+
},
|
|
531
|
+
has(token) {
|
|
532
|
+
const now = Date.now();
|
|
533
|
+
const exp = inMemorySignoutMarkers.get(token);
|
|
534
|
+
if (!exp) return false;
|
|
535
|
+
if (exp <= now) {
|
|
536
|
+
inMemorySignoutMarkers.delete(token);
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
var warnedDefaultSignoutRegistry = false;
|
|
543
|
+
function maybeWarnDefaultSignoutRegistry(config) {
|
|
544
|
+
if (warnedDefaultSignoutRegistry) return;
|
|
545
|
+
if (config.signoutRegistry) return;
|
|
546
|
+
warnedDefaultSignoutRegistry = true;
|
|
547
|
+
console.warn(
|
|
548
|
+
"[IQAuth] Using the in-memory signout registry (process-local). Signout idempotency is NOT shared across instances \u2014 in a multi-replica deployment a /refresh racing a /signout on another replica can reissue cookies after sign-out. Plug a shared backend (e.g. Redis) into IQAuthHelperConfig.signoutRegistry to fix this and silence this warning."
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
function __resetSignoutMarkersForTests() {
|
|
552
|
+
inMemorySignoutMarkers.clear();
|
|
553
|
+
}
|
|
554
|
+
function __resetSignoutRegistryWarningForTests() {
|
|
555
|
+
warnedDefaultSignoutRegistry = false;
|
|
556
|
+
}
|
|
557
|
+
function createInMemorySignoutRegistry() {
|
|
558
|
+
const store = /* @__PURE__ */ new Map();
|
|
559
|
+
return {
|
|
560
|
+
mark(token, ttlMs) {
|
|
561
|
+
const now = Date.now();
|
|
562
|
+
for (const [k, exp] of store) if (exp <= now) store.delete(k);
|
|
563
|
+
store.set(token, now + ttlMs);
|
|
564
|
+
},
|
|
565
|
+
has(token) {
|
|
566
|
+
const now = Date.now();
|
|
567
|
+
const exp = store.get(token);
|
|
568
|
+
if (!exp) return false;
|
|
569
|
+
if (exp <= now) {
|
|
570
|
+
store.delete(token);
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
}
|
|
171
577
|
function serializeCookie(d) {
|
|
172
578
|
const parts = [`${d.name}=${encodeURIComponent(d.value)}`];
|
|
173
579
|
parts.push(`Path=${d.path}`);
|
|
174
580
|
if (d.domain) parts.push(`Domain=${d.domain}`);
|
|
175
581
|
parts.push(`Max-Age=${d.maxAge}`);
|
|
582
|
+
if (d.clear) parts.push("Expires=Thu, 01 Jan 1970 00:00:00 GMT");
|
|
176
583
|
if (d.secure) parts.push("Secure");
|
|
177
584
|
if (d.httpOnly) parts.push("HttpOnly");
|
|
178
585
|
parts.push(`SameSite=${d.sameSite}`);
|
|
@@ -180,14 +587,34 @@ function serializeCookie(d) {
|
|
|
180
587
|
}
|
|
181
588
|
async function handleCallback(config, input) {
|
|
182
589
|
const cfg = resolve(config);
|
|
590
|
+
const t0 = Date.now();
|
|
183
591
|
if (!input.code || !input.redirectUri) {
|
|
592
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "VALIDATION_ERROR" });
|
|
184
593
|
return {
|
|
185
594
|
status: 400,
|
|
186
595
|
body: { success: false, error: { code: "VALIDATION_ERROR", message: "code and redirectUri are required" } },
|
|
187
596
|
cookies: []
|
|
188
597
|
};
|
|
189
598
|
}
|
|
599
|
+
const provided = input.state;
|
|
600
|
+
const expected = input.expectedState;
|
|
601
|
+
const stateOk = cfg.requireOAuthState ? !!expected && !!provided && timingSafeEqualStr(provided, expected) : !expected || !!provided && timingSafeEqualStr(provided, expected);
|
|
602
|
+
if (!stateOk) {
|
|
603
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "STATE_MISMATCH" });
|
|
604
|
+
return {
|
|
605
|
+
status: 400,
|
|
606
|
+
body: {
|
|
607
|
+
success: false,
|
|
608
|
+
error: {
|
|
609
|
+
code: "STATE_MISMATCH",
|
|
610
|
+
message: "OAuth state validation failed; the sign-in could not be verified as originating from this browser."
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
cookies: [clearStateCookie(cfg)]
|
|
614
|
+
};
|
|
615
|
+
}
|
|
190
616
|
if (!cfg.secretKey) {
|
|
617
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: "INTERNAL_ERROR" });
|
|
191
618
|
return {
|
|
192
619
|
status: 500,
|
|
193
620
|
body: { success: false, error: { code: "INTERNAL_ERROR", message: "secretKey is required for the callback handler" } },
|
|
@@ -211,6 +638,7 @@ async function handleCallback(config, input) {
|
|
|
211
638
|
});
|
|
212
639
|
const json = await res.json().catch(() => ({}));
|
|
213
640
|
if (!res.ok || !json.access_token) {
|
|
641
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code: json.error || "OIDC_EXCHANGE_FAILED" });
|
|
214
642
|
return {
|
|
215
643
|
status: res.status || 502,
|
|
216
644
|
body: {
|
|
@@ -223,6 +651,26 @@ async function handleCallback(config, input) {
|
|
|
223
651
|
cookies: []
|
|
224
652
|
};
|
|
225
653
|
}
|
|
654
|
+
try {
|
|
655
|
+
await getTokensFor(cfg.issuer).verify(json.access_token, {
|
|
656
|
+
issuer: cfg.issuer,
|
|
657
|
+
...config.verify
|
|
658
|
+
});
|
|
659
|
+
} catch (err) {
|
|
660
|
+
const code = err instanceof IQAuthError ? err.code : err.code || "TOKEN_INVALID";
|
|
661
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: false, code });
|
|
662
|
+
return {
|
|
663
|
+
status: 502,
|
|
664
|
+
body: {
|
|
665
|
+
success: false,
|
|
666
|
+
error: {
|
|
667
|
+
code: "ACCESS_TOKEN_VERIFICATION_FAILED",
|
|
668
|
+
message: "The issuer returned an access token that failed verification; no session was established."
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
cookies: []
|
|
672
|
+
};
|
|
673
|
+
}
|
|
226
674
|
const cookies = [];
|
|
227
675
|
cookies.push(
|
|
228
676
|
makeCookie(cfg, cfg.accessCookieName, json.access_token, json.expires_in ?? ACCESS_TOKEN_TTL_SECONDS)
|
|
@@ -230,6 +678,8 @@ async function handleCallback(config, input) {
|
|
|
230
678
|
if (json.refresh_token) {
|
|
231
679
|
cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.refresh_token, REFRESH_TOKEN_TTL_SECONDS));
|
|
232
680
|
}
|
|
681
|
+
cookies.push(clearStateCookie(cfg));
|
|
682
|
+
emitTiming(cfg, { phase: "callback", durationMs: Date.now() - t0, ok: true });
|
|
233
683
|
return {
|
|
234
684
|
status: 200,
|
|
235
685
|
body: { success: true, data: { authenticated: true } },
|
|
@@ -238,8 +688,18 @@ async function handleCallback(config, input) {
|
|
|
238
688
|
}
|
|
239
689
|
async function handleRefresh(config, input) {
|
|
240
690
|
const cfg = resolve(config);
|
|
691
|
+
const t0 = Date.now();
|
|
241
692
|
const refreshToken = input.refreshToken;
|
|
693
|
+
const idemKey = input.idempotencyToken;
|
|
694
|
+
if (idemKey && await Promise.resolve(cfg.signoutRegistry.has(idemKey))) {
|
|
695
|
+
return {
|
|
696
|
+
status: 401,
|
|
697
|
+
body: { success: false, error: { code: "SESSION_REVOKED", message: "Session was signed out" } },
|
|
698
|
+
cookies: clearCookies(cfg)
|
|
699
|
+
};
|
|
700
|
+
}
|
|
242
701
|
if (!refreshToken) {
|
|
702
|
+
emitTiming(cfg, { phase: "refresh", durationMs: Date.now() - t0, ok: false, code: "TOKEN_INVALID" });
|
|
243
703
|
return {
|
|
244
704
|
status: 401,
|
|
245
705
|
body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
|
|
@@ -255,6 +715,7 @@ async function handleRefresh(config, input) {
|
|
|
255
715
|
if (!res.ok || !json.success || !json.data?.accessToken) {
|
|
256
716
|
const status = res.status || 401;
|
|
257
717
|
const errorCode = json.error?.code || "TOKEN_INVALID";
|
|
718
|
+
emitTiming(cfg, { phase: "refresh", durationMs: Date.now() - t0, ok: false, code: errorCode });
|
|
258
719
|
const shouldClear = shouldClearCookiesOnFailure(
|
|
259
720
|
cfg.clearCookiesOnRefreshFailure,
|
|
260
721
|
status,
|
|
@@ -278,6 +739,7 @@ async function handleRefresh(config, input) {
|
|
|
278
739
|
if (json.data.refreshToken) {
|
|
279
740
|
cookies.push(makeCookie(cfg, cfg.refreshCookieName, json.data.refreshToken, REFRESH_TOKEN_TTL_SECONDS));
|
|
280
741
|
}
|
|
742
|
+
emitTiming(cfg, { phase: "refresh", durationMs: Date.now() - t0, ok: true });
|
|
281
743
|
return {
|
|
282
744
|
status: 200,
|
|
283
745
|
body: { success: true, data: { accessToken: json.data.accessToken } },
|
|
@@ -286,6 +748,10 @@ async function handleRefresh(config, input) {
|
|
|
286
748
|
}
|
|
287
749
|
async function handleSignout(config, input) {
|
|
288
750
|
const cfg = resolve(config);
|
|
751
|
+
const t0 = Date.now();
|
|
752
|
+
if (input.idempotencyToken) {
|
|
753
|
+
await Promise.resolve(cfg.signoutRegistry.mark(input.idempotencyToken, cfg.signoutMarkerTtlMs));
|
|
754
|
+
}
|
|
289
755
|
if (input.accessToken) {
|
|
290
756
|
try {
|
|
291
757
|
await cfg.fetchImpl(`${cfg.issuer}${cfg.logoutPath}`, {
|
|
@@ -307,16 +773,64 @@ async function handleSignout(config, input) {
|
|
|
307
773
|
} catch {
|
|
308
774
|
}
|
|
309
775
|
}
|
|
776
|
+
emitTiming(cfg, { phase: "signout", durationMs: Date.now() - t0, ok: true });
|
|
310
777
|
return {
|
|
311
778
|
status: 200,
|
|
312
779
|
body: { success: true, data: { signedOut: true } },
|
|
313
780
|
cookies: clearCookies(cfg)
|
|
314
781
|
};
|
|
315
782
|
}
|
|
783
|
+
var TOKENS_CACHE = /* @__PURE__ */ new Map();
|
|
784
|
+
function getTokensFor(issuer) {
|
|
785
|
+
let m = TOKENS_CACHE.get(issuer);
|
|
786
|
+
if (!m) {
|
|
787
|
+
m = new TokensModule(issuer);
|
|
788
|
+
TOKENS_CACHE.set(issuer, m);
|
|
789
|
+
}
|
|
790
|
+
return m;
|
|
791
|
+
}
|
|
792
|
+
async function handleUserinfo(config, input) {
|
|
793
|
+
const cfg = resolve(config);
|
|
794
|
+
if (!input.accessToken) {
|
|
795
|
+
return {
|
|
796
|
+
status: 401,
|
|
797
|
+
body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing access token" } },
|
|
798
|
+
cookies: []
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
let claims;
|
|
802
|
+
try {
|
|
803
|
+
claims = await getTokensFor(cfg.issuer).verify(input.accessToken, {
|
|
804
|
+
issuer: cfg.issuer,
|
|
805
|
+
...config.verify
|
|
806
|
+
});
|
|
807
|
+
} catch (err) {
|
|
808
|
+
const code = err instanceof IQAuthError ? err.code : err.code || "TOKEN_INVALID";
|
|
809
|
+
const message = err instanceof Error ? err.message : "Access token verification failed";
|
|
810
|
+
return {
|
|
811
|
+
status: 401,
|
|
812
|
+
body: { success: false, error: { code, message } },
|
|
813
|
+
cookies: []
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
const envelope = await buildUserinfoResponse(claims, {
|
|
817
|
+
enrich: config.userinfoEnricher ? (c) => config.userinfoEnricher(c, input.req) : void 0
|
|
818
|
+
});
|
|
819
|
+
return {
|
|
820
|
+
status: 200,
|
|
821
|
+
body: envelope,
|
|
822
|
+
cookies: []
|
|
823
|
+
};
|
|
824
|
+
}
|
|
316
825
|
// Annotate the CommonJS export names for ESM import in node:
|
|
317
826
|
0 && (module.exports = {
|
|
827
|
+
__resetSignoutMarkersForTests,
|
|
828
|
+
__resetSignoutRegistryWarningForTests,
|
|
829
|
+
buildUserinfoResponse,
|
|
830
|
+
createInMemorySignoutRegistry,
|
|
318
831
|
handleCallback,
|
|
319
832
|
handleRefresh,
|
|
320
833
|
handleSignout,
|
|
834
|
+
handleUserinfo,
|
|
321
835
|
serializeCookie
|
|
322
836
|
});
|