@better-auth/oauth-provider 1.7.9 → 1.8.0-beta.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.
@@ -1,56 +1,14 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
2
- import { A as collectExtensionAccessTokenClaims, C as storeToken, E as validateClientCredentials, F as getExtensionGrantHandler, L as getSupportedGrantTypes, M as collectExtensionUserInfoClaims, R as hasUserInfoClaimExtension, T as toResourceList, a as getClient, b as resolveSubjectIdentifier, c as getStoredToken, h as parseClientMetadata, i as extractClientCredentials, j as collectExtensionIdTokenClaims, l as isPKCERequired, n as decryptStoredClientSecret, o as getJwtPlugin, p as normalizeTimestampValue, r as destructureCredentials, t as clientAllowsGrant, w as toAudienceClaim, y as resolveSessionAuthTime } from "./utils-DKU5wblX.mjs";
2
+ import { B as collectExtensionIdTokenClaims, D as resolveSessionAuthTime, F as validateClientCredentials, G as getExtensionGrantHandler, I as validateClientScopes, J as getSupportedGrantTypes, M as toAudienceClaim, N as toResourceList, O as resolveSubjectIdentifier, V as collectExtensionUserInfoClaims, X as hasUserInfoClaimExtension, a as decryptStoredClientSecret, c as extractClientCredentials, f as getJwtPlugin, h as isPKCERequired, j as storeToken, m as getStoredToken, r as clientAllowsGrant, s as destructureCredentials, u as getClient, x as parseClientMetadata, y as normalizeTimestampValue, z as collectExtensionAccessTokenClaims } from "./utils-BMNe8Sg2.mjs";
3
3
  import { APIError } from "better-auth/api";
4
4
  import { generateRandomString, symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
5
5
  import { APIError as APIError$1 } from "better-call";
6
6
  import { logger } from "@better-auth/core/env";
7
7
  import * as z from "zod";
8
8
  import { createDpopReplayStore, enforceDpopBinding, generateCodeChallenge, getConfirmationJkt, getDpopJktFromPayload, getJwks, isDpopBindingError, isDpopProofError, parseAccessTokenAuthorization, stripAccessTokenAuthorizationScheme, verifyDpopProof } from "better-auth/oauth2";
9
- import { SignJWT, base64url, createLocalJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
10
9
  import { SafeUrlSchema } from "@better-auth/core/utils/redirect-uri";
11
10
  import { resolveSigningKey, signJWT, toExpJWT } from "better-auth/plugins";
12
- //#region src/authentication-context.ts
13
- const RESERVED_ID_TOKEN_CLAIMS = new Set([
14
- "iss",
15
- "sub",
16
- "aud",
17
- "exp",
18
- "nbf",
19
- "iat",
20
- "jti",
21
- "auth_time",
22
- "nonce",
23
- "acr",
24
- "amr",
25
- "azp",
26
- "sid",
27
- "at_hash",
28
- "c_hash",
29
- "s_hash"
30
- ]);
31
- /**
32
- * Removes provider-owned ID token claim names from custom claims.
33
- *
34
- * `customIdTokenClaims` is an extension point for additional claims. It must not
35
- * replace the issuer, subject, audience, lifetime, nonce, authorized party,
36
- * token-binding hashes, session binding, or authentication context that the
37
- * provider owns.
38
- */
39
- function stripReservedIdTokenClaims(claims) {
40
- if (!claims) return {};
41
- const stripped = [];
42
- const safeClaims = Object.create(null);
43
- for (const [key, value] of Object.entries(claims)) {
44
- if (RESERVED_ID_TOKEN_CLAIMS.has(key)) {
45
- stripped.push(key);
46
- continue;
47
- }
48
- safeClaims[key] = value;
49
- }
50
- if (stripped.length > 0) logger.warn(`oauth-provider: stripped reserved id-token claim name(s): ${stripped.join(", ")}. The AS owns these claim values.`);
51
- return safeClaims;
52
- }
53
- //#endregion
11
+ import { SignJWT, base64url, createLocalJWKSet, decodeProtectedHeader, errors, jwtVerify } from "jose";
54
12
  //#region src/claims-request.ts
55
13
  const claimRequestMemberSchema = z.record(z.string(), z.unknown()).nullable();
56
14
  const acrClaimRequestMemberSchema = z.looseObject({
@@ -99,6 +57,18 @@ function canSatisfyEssentialAcrRequest(value, currentAcr) {
99
57
  const valuesMatch = acrRequest.values === void 0 || acrRequest.values.includes(currentAcr);
100
58
  return valueMatches && valuesMatch;
101
59
  }
60
+ /**
61
+ * Lists the ACR values requested through `claims.id_token.acr` and the
62
+ * space-separated `acr_values` parameter, without duplicates.
63
+ */
64
+ function getRequestedAcrValues(claims, acrValues) {
65
+ const acrRequest = parseOidcClaimsRequestObject(claims)?.id_token?.acr;
66
+ return Array.from(new Set([
67
+ ...acrRequest?.value !== void 0 ? [acrRequest.value] : [],
68
+ ...acrRequest?.values ?? [],
69
+ ...acrValues?.split(" ").filter(Boolean) ?? []
70
+ ]));
71
+ }
102
72
  function filterClaimsRequestUserInfoClaims(value, allowedUserInfoClaims) {
103
73
  const claimsRequest = parseOidcClaimsRequestObject(value);
104
74
  if (!claimsRequest) return void 0;
@@ -110,6 +80,81 @@ function filterClaimsRequestUserInfoClaims(value, allowedUserInfoClaims) {
110
80
  } : Object.fromEntries(Object.entries(claimsRequest).filter(([key]) => key !== "userinfo"));
111
81
  return Object.keys(filteredClaimsRequest).length ? filteredClaimsRequest : void 0;
112
82
  }
83
+ /** The ACR values the provider may assert, advertised in discovery. */
84
+ function getAcrValuesSupported(opts) {
85
+ return opts.authenticationContext?.acrValuesSupported ?? ["0"];
86
+ }
87
+ function invalidAuthenticationContext(description) {
88
+ throw new APIError$1("INTERNAL_SERVER_ERROR", {
89
+ error: "server_error",
90
+ error_description: `authenticationContext.resolve ${description}`
91
+ });
92
+ }
93
+ /**
94
+ * Runs the configured `authenticationContext.resolve` for an approval and
95
+ * validates its result. Returns an empty context, which ID tokens report as
96
+ * `acr: "0"`, when no resolver is configured or no ID token can be issued.
97
+ * Invalid results throw an `APIError` with `server_error`.
98
+ */
99
+ async function resolveAuthenticationContext(opts, info) {
100
+ const resolve = opts.authenticationContext?.resolve;
101
+ if (!resolve || !info.scopes.includes("openid")) return {};
102
+ const { query, ...resolveInfo } = info;
103
+ const resolved = await resolve({
104
+ ...resolveInfo,
105
+ requestedAcrValues: getRequestedAcrValues(query.claims, query.acr_values)
106
+ });
107
+ if (resolved !== void 0 && (typeof resolved !== "object" || resolved === null)) invalidAuthenticationContext("returned a value that is not an object");
108
+ const { acr = "0", amr } = resolved ?? {};
109
+ if (typeof acr !== "string") invalidAuthenticationContext("returned a non-string acr");
110
+ if (!getAcrValuesSupported(opts).includes(acr)) invalidAuthenticationContext(`returned acr "${acr}", which is not in acrValuesSupported`);
111
+ if (amr === void 0) return { acr };
112
+ if (!Array.isArray(amr) || !Array.from(amr).every((value) => typeof value === "string")) invalidAuthenticationContext("returned an amr that is not a string array");
113
+ return {
114
+ acr,
115
+ amr: amr.length ? amr : void 0
116
+ };
117
+ }
118
+ const RESERVED_ID_TOKEN_CLAIMS = new Set([
119
+ "iss",
120
+ "sub",
121
+ "aud",
122
+ "exp",
123
+ "nbf",
124
+ "iat",
125
+ "jti",
126
+ "auth_time",
127
+ "nonce",
128
+ "acr",
129
+ "amr",
130
+ "azp",
131
+ "sid",
132
+ "at_hash",
133
+ "c_hash",
134
+ "s_hash"
135
+ ]);
136
+ /**
137
+ * Removes provider-owned ID token claim names from custom claims.
138
+ *
139
+ * `customIdTokenClaims` is an extension point for additional claims. It must not
140
+ * replace the issuer, subject, audience, lifetime, nonce, authorized party,
141
+ * token-binding hashes, session binding, or authentication context that the
142
+ * provider owns.
143
+ */
144
+ function stripReservedIdTokenClaims(claims) {
145
+ if (!claims) return {};
146
+ const stripped = [];
147
+ const safeClaims = Object.create(null);
148
+ for (const [key, value] of Object.entries(claims)) {
149
+ if (RESERVED_ID_TOKEN_CLAIMS.has(key)) {
150
+ stripped.push(key);
151
+ continue;
152
+ }
153
+ safeClaims[key] = value;
154
+ }
155
+ if (stripped.length > 0) logger.warn(`oauth-provider: stripped reserved id-token claim name(s): ${stripped.join(", ")}. The AS owns these claim values.`);
156
+ return safeClaims;
157
+ }
113
158
  //#endregion
114
159
  //#region src/standard-claims.ts
115
160
  function splitDisplayName(name) {
@@ -198,7 +243,8 @@ const RESERVED_ACCESS_TOKEN_CLAIMS = new Set([
198
243
  "auth_time",
199
244
  "acr",
200
245
  "amr",
201
- "cnf"
246
+ "cnf",
247
+ "reference_id"
202
248
  ]);
203
249
  /**
204
250
  * Returns a copy of `claims` with reserved AS-owned names removed. Emits a
@@ -388,6 +434,8 @@ const verificationValueSchema = z.object({
388
434
  userId: z.string(),
389
435
  referenceId: z.string().optional(),
390
436
  authTime: z.number().optional(),
437
+ acr: z.string().optional(),
438
+ amr: z.array(z.string()).optional(),
391
439
  resource: z.array(z.string()).optional()
392
440
  }).passthrough();
393
441
  /**
@@ -418,6 +466,8 @@ const clientRegistrationRequestSchema = z.object({
418
466
  post_logout_redirect_uris: z.array(SafeUrlSchema).min(1).optional(),
419
467
  backchannel_logout_uri: SafeUrlSchema.optional(),
420
468
  backchannel_logout_session_required: z.boolean().optional(),
469
+ frontchannel_logout_uri: SafeUrlSchema.optional(),
470
+ frontchannel_logout_session_required: z.boolean().optional(),
421
471
  token_endpoint_auth_method: z.string().trim().min(1).optional(),
422
472
  jwks: clientJwksSchema.optional(),
423
473
  jwks_uri: z.string().optional(),
@@ -525,40 +575,6 @@ async function assertIdentifierValid(opts, identifier) {
525
575
  */
526
576
  const userInfoResource = (baseURL) => `${baseURL}/oauth2/userinfo`;
527
577
  /**
528
- * Re-parses a request's raw `application/x-www-form-urlencoded` body to
529
- * recover every value of the repeated `resource` parameter (RFC 8707 §2).
530
- *
531
- * Workaround for better-call's form-body parser (better-call ≤1.3.5), which
532
- * collapses repeated form keys with last-write-wins. A client sending
533
- * `resource=https://a&resource=https://b` would otherwise arrive in the
534
- * handler as `{ resource: "https://b" }`, silently narrowing the issued
535
- * token's `aud` to a single resource.
536
- *
537
- * Returns the full ordered list when the body is form-encoded and contains
538
- * any `resource` entries; `undefined` otherwise. Caller MUST enable
539
- * `cloneRequest: true` on the endpoint — the body stream is read here a
540
- * second time, and that only works when better-call cloned the request
541
- * before its own parse.
542
- *
543
- * Note: the URL-encoded body path is the only one affected. The query-string
544
- * path (e.g. `/oauth2/authorize?resource=…&resource=…`) is parsed by the
545
- * router itself, which DOES array-promote duplicate keys correctly.
546
- *
547
- * @internal
548
- */
549
- async function extractRepeatedResourceFromForm(request) {
550
- if (!(request.headers.get("content-type")?.toLowerCase() ?? "").includes("application/x-www-form-urlencoded")) return;
551
- let text;
552
- try {
553
- text = await request.clone().text();
554
- } catch {
555
- return;
556
- }
557
- if (!text) return void 0;
558
- const values = new URLSearchParams(text).getAll("resource").filter((value) => value.length > 0);
559
- return values.length > 0 ? values : void 0;
560
- }
561
- /**
562
578
  * Normalizes a `resource` parameter into an array. Accepts the RFC 8707
563
579
  * forms: a single string, a string[], or undefined.
564
580
  *
@@ -1084,14 +1100,21 @@ function getDpopProofJwt(ctx) {
1084
1100
  return ctx.headers?.get("dpop") ?? void 0;
1085
1101
  }
1086
1102
  /**
1087
- * The endpoint's public URL, built from `baseURL`, which clients use as the
1088
- * DPoP `htu`. Not `request.url`: proxies and dev servers can rewrite its host.
1103
+ * The endpoint's public URL, which clients use as the DPoP `htu`: the origin
1104
+ * and base path of `baseURL` (not `request.url`, whose host proxies and dev
1105
+ * servers can rewrite) plus the request's own path below the base path, so
1106
+ * route params and a trailing slash match what the client called. `path` is
1107
+ * used when there is no HTTP request, such as an `auth.api` call.
1089
1108
  */
1090
1109
  function getEndpointUrl(ctx, path) {
1091
1110
  const { baseURL } = ctx.context;
1092
1111
  let end = baseURL.length;
1093
1112
  while (end > 0 && baseURL[end - 1] === "/") end--;
1094
- return `${baseURL.slice(0, end)}${path}`;
1113
+ const base = baseURL.slice(0, end);
1114
+ if (!ctx.request || !base) return `${base}${path}`;
1115
+ const basePath = new URL(base).pathname.replace(/\/$/, "");
1116
+ const requestPath = new URL(ctx.request.url).pathname;
1117
+ return `${base}${requestPath.startsWith(`${basePath}/`) ? requestPath.slice(basePath.length) : path}`;
1095
1118
  }
1096
1119
  //#endregion
1097
1120
  //#region src/oauthClient/client-credentials.ts
@@ -1125,6 +1148,129 @@ function validateClientCredentialsScopes(scopes, grantTypes, tokenEndpointAuthMe
1125
1148
  });
1126
1149
  }
1127
1150
  //#endregion
1151
+ //#region src/token-lookup.ts
1152
+ const jwksInfrastructureErrorCodes = new Set([
1153
+ errors.JWKSTimeout.code,
1154
+ errors.JWKSInvalid.code,
1155
+ errors.JWKSMultipleMatchingKeys.code
1156
+ ]);
1157
+ /**
1158
+ * Verifies a JWT access token issued by this server. Returns `"inactive"` for
1159
+ * one that is expired or fails claim validation, so the lookup can stop: it
1160
+ * cannot be another token type. Returns `null` for anything else (an opaque
1161
+ * token, a malformed or forged JWT, a JWT that is not an access token) so the
1162
+ * lookup can continue. Key-set failures still throw.
1163
+ */
1164
+ async function verifyJwtAccessToken(ctx, opts, token) {
1165
+ if (opts.disableJwtPlugin) return null;
1166
+ if (token.split(".").length !== 3) return null;
1167
+ try {
1168
+ decodeProtectedHeader(token);
1169
+ } catch {
1170
+ return null;
1171
+ }
1172
+ const jwtPlugin = getJwtPlugin(ctx.context);
1173
+ const jwtPluginOptions = jwtPlugin.options;
1174
+ const jwks = await getJwks(token, {
1175
+ jwksFetch: jwtPluginOptions?.jwks?.remoteUrl ? jwtPluginOptions.jwks.remoteUrl : async () => {
1176
+ return (await jwtPlugin.endpoints.getJwks(ctx))?.response;
1177
+ },
1178
+ jwksCacheKey: jwtPlugin
1179
+ });
1180
+ let payload;
1181
+ try {
1182
+ ({payload} = await jwtVerify(token, createLocalJWKSet(jwks), { issuer: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL }));
1183
+ } catch (error) {
1184
+ if (error instanceof errors.JWTExpired || error instanceof errors.JWTClaimValidationFailed) return isAccessTokenPayload(error.payload) ? "inactive" : null;
1185
+ if (error instanceof errors.JOSEError && !jwksInfrastructureErrorCodes.has(error.code)) return null;
1186
+ throw error;
1187
+ }
1188
+ return isAccessTokenPayload(payload) ? payload : null;
1189
+ }
1190
+ /**
1191
+ * The OAuth token endpoint stamps `azp` (the client) on every JWT access token.
1192
+ * The JWT plugin signs other JWTs with the same issuer and keys, such as its
1193
+ * `/token` session JWT, so one without `azp` is not an access token.
1194
+ */
1195
+ function isAccessTokenPayload(payload) {
1196
+ return typeof payload.azp === "string" && payload.azp.length > 0;
1197
+ }
1198
+ async function findOpaqueAccessToken(ctx, opts, token) {
1199
+ const prefix = opts.prefix?.opaqueAccessToken;
1200
+ if (prefix && !token.startsWith(prefix)) return null;
1201
+ return ctx.context.adapter.findOne({
1202
+ model: "oauthAccessToken",
1203
+ where: [{
1204
+ field: "token",
1205
+ value: await getStoredToken(opts.storeTokens, prefix ? token.slice(prefix.length) : token, "access_token")
1206
+ }]
1207
+ });
1208
+ }
1209
+ /**
1210
+ * Looks up a refresh token by its presented value, after removing the
1211
+ * configured prefix and applying `formatRefreshToken.decrypt`. Returns `null`
1212
+ * when the value is not a refresh token this server stored.
1213
+ */
1214
+ async function findRefreshToken(ctx, opts, token) {
1215
+ const prefix = opts.prefix?.refreshToken;
1216
+ if (prefix && !token.startsWith(prefix)) return null;
1217
+ const value = prefix ? token.slice(prefix.length) : token;
1218
+ let decoded;
1219
+ try {
1220
+ decoded = opts.formatRefreshToken?.decrypt ? await opts.formatRefreshToken.decrypt(value) : { token: value };
1221
+ } catch (error) {
1222
+ if (error instanceof APIError$1 && error.statusCode === 400 || error instanceof errors.JOSEError) return null;
1223
+ throw error;
1224
+ }
1225
+ return ctx.context.adapter.findOne({
1226
+ model: "oauthRefreshToken",
1227
+ where: [{
1228
+ field: "token",
1229
+ value: await getStoredToken(opts.storeTokens, decoded.token, "refresh_token")
1230
+ }]
1231
+ });
1232
+ }
1233
+ async function identifyAccessToken(ctx, opts, token) {
1234
+ const payload = await verifyJwtAccessToken(ctx, opts, token);
1235
+ if (payload === "inactive") return payload;
1236
+ if (payload) return {
1237
+ type: "jwt",
1238
+ payload
1239
+ };
1240
+ const record = await findOpaqueAccessToken(ctx, opts, token);
1241
+ return record ? {
1242
+ type: "opaque",
1243
+ record
1244
+ } : null;
1245
+ }
1246
+ /**
1247
+ * Identifies an access token as a JWT or an opaque token issued by this
1248
+ * server. Returns `null` when it is neither, including an expired JWT.
1249
+ */
1250
+ async function findAccessToken(ctx, opts, token) {
1251
+ const found = await identifyAccessToken(ctx, opts, token);
1252
+ return found === "inactive" ? null : found;
1253
+ }
1254
+ /**
1255
+ * Identifies a token presented to revocation or introspection. Every token
1256
+ * type is searched; `token_type_hint` only decides which one is tried first
1257
+ * (RFC 7009 §2.1, RFC 7662 §2.1). Returns `null` when no type matches. Only
1258
+ * infrastructure failures (key set, database, refresh-token decryption) throw.
1259
+ */
1260
+ async function findToken(ctx, opts, token, tokenTypeHint) {
1261
+ const asRefreshToken = async () => {
1262
+ const record = await findRefreshToken(ctx, opts, token);
1263
+ return record ? {
1264
+ type: "refresh",
1265
+ record
1266
+ } : null;
1267
+ };
1268
+ if (tokenTypeHint === "refresh_token") return await asRefreshToken() ?? findAccessToken(ctx, opts, token);
1269
+ const found = await identifyAccessToken(ctx, opts, token);
1270
+ if (found === "inactive") return null;
1271
+ return found ?? asRefreshToken();
1272
+ }
1273
+ //#endregion
1128
1274
  //#region src/userinfo.ts
1129
1275
  /**
1130
1276
  * Builds the standard OIDC claims (OIDC Core §5.1) for the UserInfo response.
@@ -1335,6 +1481,18 @@ function getOAuthProviderApi(ctx, opts, grantType) {
1335
1481
  grantType
1336
1482
  });
1337
1483
  },
1484
+ resolveConfirmation: async (params) => {
1485
+ if (params.confirmation) return params.confirmation;
1486
+ const policy = await resolveResourcePolicy(ctx, opts, {
1487
+ resource: params.resources,
1488
+ clientId: params.client.clientId,
1489
+ requestedScopes: params.scopes
1490
+ });
1491
+ return resolveDpopTokenBinding(ctx, opts, {
1492
+ client: params.client,
1493
+ grantIssuance: { dpopBoundAccessTokensRequired: policy.dpopBoundAccessTokensRequired }
1494
+ });
1495
+ },
1338
1496
  hashToken: (token, type) => storeToken(opts.storeTokens, token, type),
1339
1497
  validateAccessToken: async (token, clientId) => {
1340
1498
  const { validateAccessToken } = await Promise.resolve().then(() => introspect_exports);
@@ -1364,6 +1522,7 @@ async function createJwtAccessToken(ctx, opts, user, client, audienceClaim, scop
1364
1522
  azp: client.clientId,
1365
1523
  scope: scopes.join(" "),
1366
1524
  sid: overrides?.sid,
1525
+ reference_id: overrides?.referenceId,
1367
1526
  iss: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL,
1368
1527
  iat,
1369
1528
  exp,
@@ -1389,7 +1548,7 @@ async function computeOidcHash(token, signingAlg) {
1389
1548
  * Creates a user id token in code_authorization with scope of 'openid'
1390
1549
  * and hybrid/implicit (not yet implemented) flows
1391
1550
  */
1392
- async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId, authTime, accessToken, extraClaims) {
1551
+ async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId, authTime, accessToken, extraClaims, authenticationContext) {
1393
1552
  const iat = Math.floor(Date.now() / 1e3);
1394
1553
  const exp = iat + (opts.idTokenExpiresIn ?? 36e3);
1395
1554
  const resolvedSub = await resolveSubjectIdentifier(user.id, client, opts);
@@ -1403,11 +1562,12 @@ async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId,
1403
1562
  const resolvedKey = !opts.disableJwtPlugin && !jwtPluginOptions?.jwt?.sign ? await resolveSigningKey(ctx, jwtPluginOptions) : void 0;
1404
1563
  const signingAlg = opts.disableJwtPlugin ? "HS256" : resolvedKey?.alg ?? jwtPluginOptions?.jwks?.keyPairConfig?.alg;
1405
1564
  const atHash = accessToken ? await computeOidcHash(accessToken, signingAlg) : void 0;
1406
- const emitSid = Boolean(client.enableEndSession || client.backchannelLogoutUri);
1565
+ const emitSid = Boolean(client.enableEndSession || client.backchannelLogoutUri || client.frontchannelLogoutUri);
1407
1566
  const payload = {
1408
1567
  ...ID_TOKEN_SCOPE_CLAIM_GUARDS,
1409
1568
  auth_time: authTimeSec,
1410
- acr: "0",
1569
+ acr: authenticationContext?.acr ?? "0",
1570
+ amr: authenticationContext?.amr,
1411
1571
  ...customClaims,
1412
1572
  at_hash: atHash,
1413
1573
  iss: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL,
@@ -1439,20 +1599,7 @@ async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId,
1439
1599
  * Encodes a refresh token for a client
1440
1600
  */
1441
1601
  async function encodeRefreshToken(opts, token, sessionId) {
1442
- return (opts.prefix?.refreshToken ?? "") + (opts.formatRefreshToken?.encrypt ? opts.formatRefreshToken.encrypt(token, sessionId) : token);
1443
- }
1444
- /**
1445
- * Decodes a refresh token for a client
1446
- *
1447
- * @internal
1448
- */
1449
- async function decodeRefreshToken(opts, token) {
1450
- if (opts.prefix?.refreshToken) if (token.startsWith(opts.prefix.refreshToken)) token = token.replace(opts.prefix.refreshToken, "");
1451
- else throw new APIError("BAD_REQUEST", {
1452
- error_description: "refresh token not found",
1453
- error: "invalid_token"
1454
- });
1455
- return opts.formatRefreshToken?.decrypt ? opts.formatRefreshToken?.decrypt(token) : { token };
1602
+ return (opts.prefix?.refreshToken ?? "") + (opts.formatRefreshToken?.encrypt ? await opts.formatRefreshToken.encrypt(token, sessionId) : token);
1456
1603
  }
1457
1604
  async function createOpaqueAccessToken(ctx, opts, user, client, scopes, payload, resources, referenceId, authorizationCodeId, refreshId, confirmation, requestedUserInfoClaims) {
1458
1605
  const iat = payload.iat ?? Math.floor(Date.now() / 1e3);
@@ -1542,7 +1689,7 @@ async function revokeTokensIssuedForAuthorizationCode(ctx, authorizationCodeId)
1542
1689
  await deleteIssuedTokens("oauthAccessToken");
1543
1690
  await deleteIssuedTokens("oauthRefreshToken");
1544
1691
  }
1545
- async function createRefreshToken(ctx, opts, user, referenceId, authorizationCodeId, client, scopes, payload, originalRefresh, authTime, resources, confirmation, requestedUserInfoClaims) {
1692
+ async function createRefreshToken(ctx, opts, user, referenceId, authorizationCodeId, client, scopes, payload, originalRefresh, authTime, resources, confirmation, requestedUserInfoClaims, authenticationContext) {
1546
1693
  const iat = payload.iat ?? Math.floor(Date.now() / 1e3);
1547
1694
  const exp = payload?.exp ?? iat + (opts.refreshTokenExpiresIn ?? 2592e3);
1548
1695
  const token = opts.generateRefreshToken ? await opts.generateRefreshToken() : generateRandomString(32, "A-Z", "a-z");
@@ -1555,6 +1702,8 @@ async function createRefreshToken(ctx, opts, user, referenceId, authorizationCod
1555
1702
  referenceId,
1556
1703
  authorizationCodeId,
1557
1704
  authTime,
1705
+ acr: authenticationContext?.acr,
1706
+ amr: authenticationContext?.amr,
1558
1707
  confirmation,
1559
1708
  requestedUserInfoClaims: requestedUserInfoClaims?.length ? requestedUserInfoClaims : void 0,
1560
1709
  scopes,
@@ -1777,9 +1926,18 @@ function replayRefreshTokenRotationResponse(ctx, response) {
1777
1926
  });
1778
1927
  }
1779
1928
  async function createUserTokens(ctx, opts, params) {
1780
- const { client, scopes, user, grantType, referenceId, sessionId, nonce, refreshToken: existingRefreshToken, authTime, verificationValue } = params;
1929
+ const { client, scopes, user, grantType, referenceId, sessionId, nonce, refreshToken: existingRefreshToken, authTime, acr, amr, verificationValue } = params;
1930
+ const authenticationContext = {
1931
+ acr,
1932
+ amr
1933
+ };
1781
1934
  const iat = Math.floor(Date.now() / 1e3);
1782
- const defaultExp = iat + (user ? opts.accessTokenExpiresIn ?? 3600 : opts.m2mAccessTokenExpiresIn ?? 3600);
1935
+ const baseExpiry = user ? opts.accessTokenExpiresIn ?? 3600 : opts.m2mAccessTokenExpiresIn ?? 3600;
1936
+ const defaultExp = Math.min(iat + baseExpiry, params.maxExpiresAt ? Math.floor(params.maxExpiresAt.getTime() / 1e3) : Number.POSITIVE_INFINITY);
1937
+ if (defaultExp <= iat) throw new APIError("BAD_REQUEST", {
1938
+ error: "invalid_request",
1939
+ error_description: "the token this grant derives from has expired"
1940
+ });
1783
1941
  const scopeExp = opts.scopeExpirations ? scopes.map((sc) => opts.scopeExpirations?.[sc] ? toExpJWT(opts.scopeExpirations[sc], iat) : defaultExp).reduce((prev, curr) => {
1784
1942
  return prev < curr ? prev : curr;
1785
1943
  }, defaultExp) : defaultExp;
@@ -1834,7 +1992,7 @@ async function createUserTokens(ctx, opts, params) {
1834
1992
  iat,
1835
1993
  exp: refreshTokenExp,
1836
1994
  sid: sessionId
1837
- }, existingRefreshToken, authTime, refreshResources, confirmation, requestedUserInfoClaims) : void 0;
1995
+ }, existingRefreshToken, authTime, refreshResources, confirmation, requestedUserInfoClaims, authenticationContext) : void 0;
1838
1996
  const accessTokenClaims = isJwtAccessToken ? await resolveAccessTokenClaims({
1839
1997
  ctx,
1840
1998
  opts,
@@ -1853,6 +2011,7 @@ async function createUserTokens(ctx, opts, params) {
1853
2011
  iat,
1854
2012
  exp,
1855
2013
  sid: sessionId,
2014
+ referenceId,
1856
2015
  signingAlgorithm: grantIssuance.signingAlgorithm,
1857
2016
  signingKeyId: grantIssuance.signingKeyId,
1858
2017
  accessTokenClaims,
@@ -1865,8 +2024,8 @@ async function createUserTokens(ctx, opts, params) {
1865
2024
  iat,
1866
2025
  exp: refreshTokenExp,
1867
2026
  sid: sessionId
1868
- }, existingRefreshToken, authTime, refreshResources, confirmation, requestedUserInfoClaims) : void 0]);
1869
- const idToken = isIdToken ? await createIdToken(ctx, opts, user, client, effectiveScopes, nonce, sessionId, authTime, accessToken, additionalIdTokenClaims) : void 0;
2027
+ }, existingRefreshToken, authTime, refreshResources, confirmation, requestedUserInfoClaims, authenticationContext) : void 0]);
2028
+ const idToken = isIdToken ? await createIdToken(ctx, opts, user, client, effectiveScopes, nonce, sessionId, authTime, accessToken, additionalIdTokenClaims, authenticationContext) : void 0;
1870
2029
  const responseBody = {
1871
2030
  ...customFields,
1872
2031
  ...params.tokenResponse ?? {},
@@ -1971,6 +2130,10 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
1971
2130
  error_description: "Either code_verifier or client_secret is required",
1972
2131
  error: "invalid_request"
1973
2132
  });
2133
+ /** Verify Client (RFC 6749 §5.2: an unknown or unauthenticated client is
2134
+ * `invalid_client`, so resolve it before the code lookup can report
2135
+ * `invalid_grant` and before a one-time code is consumed). */
2136
+ const client = await validateClientCredentials(ctx, opts, client_id, client_secret, void 0, preVerified, "authorization_code", authMethod);
1974
2137
  /** Get and check Verification Value */
1975
2138
  const { verificationValue, effectiveResources, authorizedResources, authorizationCodeId } = await checkVerificationValue(ctx, opts, code, client_id, redirect_uri, resources);
1976
2139
  const scopes = verificationValue.query.scope?.split(" ");
@@ -1978,8 +2141,7 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
1978
2141
  error_description: "verification scope unset",
1979
2142
  error: "invalid_scope"
1980
2143
  });
1981
- /** Verify Client */
1982
- const client = await validateClientCredentials(ctx, opts, client_id, client_secret, scopes, preVerified, "authorization_code", authMethod);
2144
+ validateClientScopes(client, scopes);
1983
2145
  if (isPKCERequired(client, {
1984
2146
  scopes: (verificationValue.query?.scope)?.split(" ") || [],
1985
2147
  nonce: verificationValue.query?.nonce
@@ -1996,17 +2158,17 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
1996
2158
  const pkceUsedInAuth = !!verificationValue.query?.code_challenge;
1997
2159
  const pkceUsedInToken = !!code_verifier;
1998
2160
  if (pkceUsedInAuth || pkceUsedInToken) {
1999
- if (pkceUsedInAuth && !pkceUsedInToken) throw new APIError("UNAUTHORIZED", {
2161
+ if (pkceUsedInAuth && !pkceUsedInToken) throw new APIError("BAD_REQUEST", {
2000
2162
  error_description: "code_verifier required because PKCE was used in authorization",
2001
2163
  error: "invalid_request"
2002
2164
  });
2003
- if (!pkceUsedInAuth && pkceUsedInToken) throw new APIError("UNAUTHORIZED", {
2165
+ if (!pkceUsedInAuth && pkceUsedInToken) throw new APIError("BAD_REQUEST", {
2004
2166
  error_description: "code_verifier provided but PKCE was not used in authorization",
2005
2167
  error: "invalid_request"
2006
2168
  });
2007
- if ((verificationValue.query?.code_challenge_method === "S256" ? await generateCodeChallenge(code_verifier) : void 0) !== verificationValue.query?.code_challenge) throw new APIError("UNAUTHORIZED", {
2169
+ if ((verificationValue.query?.code_challenge_method === "S256" ? await generateCodeChallenge(code_verifier) : void 0) !== verificationValue.query?.code_challenge) throw new APIError("BAD_REQUEST", {
2008
2170
  error_description: "code verification failed",
2009
- error: "invalid_request"
2171
+ error: "invalid_grant"
2010
2172
  });
2011
2173
  }
2012
2174
  /** Get user */
@@ -2041,6 +2203,8 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
2041
2203
  sessionId: session.id,
2042
2204
  nonce: verificationValue.query?.nonce,
2043
2205
  authTime,
2206
+ acr: verificationValue.acr,
2207
+ amr: verificationValue.amr,
2044
2208
  verificationValue,
2045
2209
  authorizationCodeId,
2046
2210
  requestedUserInfoClaims,
@@ -2109,14 +2273,11 @@ async function handleRefreshTokenGrant(ctx, opts) {
2109
2273
  error_description: "Missing a required refresh_token for refresh_token grant",
2110
2274
  error: "invalid_request"
2111
2275
  });
2112
- const decodedRefresh = await decodeRefreshToken(opts, refresh_token);
2113
- const refreshToken = await ctx.context.adapter.findOne({
2114
- model: "oauthRefreshToken",
2115
- where: [{
2116
- field: "token",
2117
- value: await getStoredToken(opts.storeTokens, decodedRefresh.token, "refresh_token")
2118
- }]
2119
- });
2276
+ /** Verify Client (RFC 6749 §5.2: an unknown or unauthenticated client is
2277
+ * `invalid_client`, so resolve it before the refresh token lookup can
2278
+ * report `invalid_grant`). */
2279
+ const client = await validateClientCredentials(ctx, opts, client_id, client_secret, void 0, preVerified, "refresh_token", authMethod);
2280
+ const refreshToken = await findRefreshToken(ctx, opts, refresh_token);
2120
2281
  if (!refreshToken) throw new APIError("BAD_REQUEST", {
2121
2282
  error_description: "session not found",
2122
2283
  error: "invalid_grant"
@@ -2142,7 +2303,7 @@ async function handleRefreshTokenGrant(ctx, opts) {
2142
2303
  error: "invalid_scope"
2143
2304
  });
2144
2305
  }
2145
- const client = await validateClientCredentials(ctx, opts, client_id, client_secret, requestedScopes ?? scopes, preVerified, "refresh_token", authMethod);
2306
+ validateClientScopes(client, requestedScopes ?? scopes);
2146
2307
  if (refreshToken.revoked) {
2147
2308
  if (isWithinRefreshTokenReuseInterval(refreshToken)) {
2148
2309
  const replay = await getRefreshTokenRotationReplay(ctx, refreshToken, await resolveRefreshTokenRotationReplayRequest(ctx, opts, {
@@ -2180,6 +2341,8 @@ async function handleRefreshTokenGrant(ctx, opts) {
2180
2341
  refreshToken,
2181
2342
  resources: resources ?? refreshToken.resources,
2182
2343
  authTime,
2344
+ acr: refreshToken.acr ?? void 0,
2345
+ amr: refreshToken.amr ?? void 0,
2183
2346
  requestedUserInfoClaims: refreshToken.requestedUserInfoClaims
2184
2347
  });
2185
2348
  }
@@ -2200,11 +2363,11 @@ function inactiveAccessToken() {
2200
2363
  };
2201
2364
  }
2202
2365
  /**
2203
- * IMPORTANT NOTES:
2204
- * Introspection follows RFC7662
2366
+ * Introspection follows RFC 7662
2205
2367
  * https://datatracker.ietf.org/doc/html/rfc7662
2206
- * - APIError: Continue catches (returnable to client)
2207
- * - Error: Should immediately stop catches (internal error)
2368
+ *
2369
+ * A token this server cannot find, or one the caller may not see, resolves as
2370
+ * `{ active: false }` (§2.2, §4). Only protocol errors surface.
2208
2371
  */
2209
2372
  /**
2210
2373
  * Resource identifiers in a token's audience, excluding the implicit
@@ -2230,38 +2393,14 @@ async function isIntrospectionAuthorized(ctx, opts, params) {
2230
2393
  return isClientLinkedToAnyResource(ctx, opts, introspectingClientId, audienceResources);
2231
2394
  }
2232
2395
  /**
2233
- * Validates a JWT access token against the configured JWKs.
2396
+ * Validates a JWT access token whose signature and issuer are verified.
2234
2397
  *
2235
2398
  * @returns RFC7662 introspection format
2236
2399
  */
2237
- async function validateJwtAccessToken(ctx, opts, token, clientId) {
2238
- const jwtPlugin = opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context);
2239
- const jwtPluginOptions = jwtPlugin?.options;
2240
- const userInfoAud = `${ctx.context.baseURL ?? ""}/oauth2/userinfo`;
2241
- const expectedIssuer = jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL;
2242
- let jwtPayload;
2243
- try {
2244
- jwtPayload = (await jwtVerify(token, createLocalJWKSet(await getJwks(token, {
2245
- jwksFetch: jwtPluginOptions?.jwks?.remoteUrl ? jwtPluginOptions.jwks.remoteUrl : async () => {
2246
- return (await jwtPlugin?.endpoints.getJwks(ctx))?.response;
2247
- },
2248
- jwksCacheKey: jwtPlugin
2249
- })), { issuer: expectedIssuer })).payload;
2250
- } catch (error) {
2251
- if (error instanceof Error) {
2252
- if (error.name === "TypeError" || error.name === "JWSInvalid") throw new APIError$1("BAD_REQUEST", {
2253
- error_description: "invalid JWT signature",
2254
- error: "invalid_request"
2255
- });
2256
- else if (error.name === "JWTExpired") return { active: false };
2257
- else if (error.name === "JWTInvalid") return { active: false };
2258
- throw error;
2259
- }
2260
- throw new Error(error);
2261
- }
2400
+ async function validateJwtAccessToken(ctx, opts, jwtPayload, clientId) {
2401
+ const userInfoAud = userInfoResource(ctx.context.baseURL ?? "");
2262
2402
  const rawAud = jwtPayload.aud;
2263
2403
  if (!await isAudienceClaimAllowed(ctx, opts, rawAud, [userInfoAud])) return { active: false };
2264
- if (!jwtPayload.azp) return { active: false };
2265
2404
  const client = await getClient(ctx, opts, jwtPayload.azp);
2266
2405
  if (!client || client?.disabled) return { active: false };
2267
2406
  if (!await isIntrospectionAuthorized(ctx, opts, {
@@ -2286,28 +2425,11 @@ async function validateJwtAccessToken(ctx, opts, token, clientId) {
2286
2425
  return jwtPayload;
2287
2426
  }
2288
2427
  /**
2289
- * Searches for an opaque access token in the database and validates it
2428
+ * Validates an opaque access token record.
2290
2429
  *
2291
2430
  * @returns RFC7662 introspection format
2292
2431
  */
2293
- async function validateOpaqueAccessToken(ctx, opts, token, clientId) {
2294
- let tokenValue = token;
2295
- if (opts.prefix?.opaqueAccessToken) if (tokenValue.startsWith(opts.prefix.opaqueAccessToken)) tokenValue = tokenValue.replace(opts.prefix.opaqueAccessToken, "");
2296
- else throw new APIError$1("BAD_REQUEST", {
2297
- error_description: "opaque access token not found",
2298
- error: "invalid_request"
2299
- });
2300
- const accessToken = await ctx.context.adapter.findOne({
2301
- model: "oauthAccessToken",
2302
- where: [{
2303
- field: "token",
2304
- value: await getStoredToken(opts.storeTokens, tokenValue, "access_token")
2305
- }]
2306
- });
2307
- if (!accessToken) throw new APIError$1("BAD_REQUEST", {
2308
- error_description: "opaque access token not found",
2309
- error: "invalid_token"
2310
- });
2432
+ async function validateOpaqueAccessToken(ctx, opts, accessToken, clientId) {
2311
2433
  if (!accessToken.expiresAt || accessToken.expiresAt < /* @__PURE__ */ new Date()) return inactiveAccessToken();
2312
2434
  if (accessToken.revoked) return inactiveAccessToken();
2313
2435
  const resources = Array.isArray(accessToken.resources) ? accessToken.resources : void 0;
@@ -2366,6 +2488,7 @@ async function validateOpaqueAccessToken(ctx, opts, token, clientId) {
2366
2488
  azp: accessToken.clientId,
2367
2489
  sub: user?.id,
2368
2490
  sid: sessionId,
2491
+ reference_id: accessToken.referenceId ?? void 0,
2369
2492
  exp: Math.floor(new Date(accessToken.expiresAt).getTime() / 1e3),
2370
2493
  iat: Math.floor(new Date(accessToken.createdAt).getTime() / 1e3),
2371
2494
  scope: accessToken.scopes?.join(" "),
@@ -2376,22 +2499,11 @@ async function validateOpaqueAccessToken(ctx, opts, token, clientId) {
2376
2499
  };
2377
2500
  }
2378
2501
  /**
2379
- * Validates a refresh token in the session store.
2502
+ * Validates a refresh token record.
2380
2503
  *
2381
2504
  * @returns payload in RFC7662 introspection format
2382
2505
  */
2383
- async function validateRefreshToken(ctx, opts, token, clientId) {
2384
- const refreshToken = await ctx.context.adapter.findOne({
2385
- model: "oauthRefreshToken",
2386
- where: [{
2387
- field: "token",
2388
- value: await getStoredToken(opts.storeTokens, token, "refresh_token")
2389
- }]
2390
- });
2391
- if (!refreshToken) throw new APIError$1("BAD_REQUEST", {
2392
- error_description: "token not found",
2393
- error: "invalid_token"
2394
- });
2506
+ async function validateRefreshToken(ctx, opts, refreshToken, clientId) {
2395
2507
  if (!refreshToken.clientId || refreshToken.clientId !== clientId) return { active: false };
2396
2508
  if (!refreshToken.expiresAt || refreshToken.expiresAt < /* @__PURE__ */ new Date()) return { active: false };
2397
2509
  if (refreshToken.revoked) return { active: false };
@@ -2414,6 +2526,7 @@ async function validateRefreshToken(ctx, opts, token, clientId) {
2414
2526
  iss: ((opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context))?.options)?.jwt?.issuer ?? ctx.context.baseURL,
2415
2527
  sub: user?.id,
2416
2528
  sid: sessionId,
2529
+ reference_id: refreshToken.referenceId ?? void 0,
2417
2530
  exp: Math.floor(new Date(refreshToken.expiresAt).getTime() / 1e3),
2418
2531
  iat: Math.floor(new Date(refreshToken.createdAt).getTime() / 1e3),
2419
2532
  scope: refreshToken.scopes?.join(" "),
@@ -2427,32 +2540,23 @@ function createInvalidAccessTokenError() {
2427
2540
  error: "invalid_token"
2428
2541
  }, { "WWW-Authenticate": INVALID_ACCESS_TOKEN_WWW_AUTHENTICATE });
2429
2542
  }
2430
- function isInactiveTokenError(error) {
2431
- return error.status === "BAD_REQUEST" || error.body?.error === "invalid_token";
2543
+ async function validateFoundAccessToken(ctx, opts, found, clientId) {
2544
+ if (found.type === "opaque") return validateOpaqueAccessToken(ctx, opts, found.record, clientId);
2545
+ return {
2546
+ payload: await validateJwtAccessToken(ctx, opts, found.payload, clientId),
2547
+ requestedUserInfoClaims: []
2548
+ };
2432
2549
  }
2433
2550
  /**
2434
- * We don't know the access token format so we try to validate it as a JWT
2435
- * first, then as an opaque token. Returns the RFC 7662 introspection payload
2436
- * alongside the per-issuance UserInfo claims the opaque row persisted (empty for
2437
- * a JWT access token, which resolves UserInfo claims by scope).
2551
+ * Validates an access token (JWT or opaque). Returns the RFC 7662
2552
+ * introspection payload, inactive for an unknown token, alongside the
2553
+ * per-issuance UserInfo claims the opaque row persisted (empty for a JWT access
2554
+ * token, which resolves UserInfo claims by scope).
2438
2555
  */
2439
2556
  async function resolveAccessTokenValidation(ctx, opts, token, clientId) {
2440
- try {
2441
- return {
2442
- payload: await validateJwtAccessToken(ctx, opts, token, clientId),
2443
- requestedUserInfoClaims: []
2444
- };
2445
- } catch (err) {
2446
- if (err instanceof APIError$1) {} else if (err instanceof Error) throw err;
2447
- else throw new Error(err);
2448
- }
2449
- try {
2450
- return await validateOpaqueAccessToken(ctx, opts, token, clientId);
2451
- } catch (err) {
2452
- if (err instanceof APIError$1) {} else if (err instanceof Error) throw err;
2453
- else throw new Error("Unknown error validating access token");
2454
- }
2455
- throw createInvalidAccessTokenError();
2557
+ const found = await findAccessToken(ctx, opts, token);
2558
+ if (!found) return inactiveAccessToken();
2559
+ return validateFoundAccessToken(ctx, opts, found, clientId);
2456
2560
  }
2457
2561
  /**
2458
2562
  * Validates an access token (JWT or opaque) and returns its RFC 7662
@@ -2501,7 +2605,6 @@ async function resolveIntrospectionSub(ctx, opts, payload, introspectingClient)
2501
2605
  }
2502
2606
  async function introspectEndpoint(ctx, opts) {
2503
2607
  let { token, token_type_hint } = ctx.body;
2504
- if (token_type_hint !== "access_token" && token_type_hint !== "refresh_token") token_type_hint = void 0;
2505
2608
  const { clientId: client_id, clientSecret: client_secret, preVerified, authMethod } = destructureCredentials(await extractClientCredentials(ctx, opts, `${ctx.context.baseURL}/oauth2/introspect`));
2506
2609
  if (!client_id || !client_secret && !preVerified) throw new APIError$1("UNAUTHORIZED", {
2507
2610
  error_description: "missing required credentials",
@@ -2514,38 +2617,16 @@ async function introspectEndpoint(ctx, opts) {
2514
2617
  });
2515
2618
  const client = await validateClientCredentials(ctx, opts, client_id, client_secret, void 0, preVerified, void 0, authMethod);
2516
2619
  try {
2517
- if (token_type_hint === void 0 || token_type_hint === "access_token") try {
2518
- return resolveIntrospectionSub(ctx, opts, await validateAccessToken(ctx, opts, token, client.clientId), client);
2519
- } catch (error) {
2520
- if (error instanceof APIError$1) {
2521
- if (token_type_hint === "access_token") throw error;
2522
- } else if (error instanceof Error) throw error;
2523
- else throw new Error(error);
2524
- }
2525
- if (token_type_hint === void 0 || token_type_hint === "refresh_token") try {
2526
- return resolveIntrospectionSub(ctx, opts, await validateRefreshToken(ctx, opts, (await decodeRefreshToken(opts, token)).token, client.clientId), client);
2527
- } catch (error) {
2528
- if (error instanceof APIError$1) {
2529
- if (token_type_hint === "refresh_token") throw error;
2530
- } else if (error instanceof Error) throw error;
2531
- else throw new Error(error);
2532
- }
2533
- throw new APIError$1("BAD_REQUEST", {
2534
- error_description: "token not found",
2535
- error: "invalid_request"
2536
- });
2620
+ const found = await findToken(ctx, opts, token, token_type_hint);
2621
+ if (!found) return { active: false };
2622
+ if (found.type === "refresh") return resolveIntrospectionSub(ctx, opts, await validateRefreshToken(ctx, opts, found.record, client.clientId), client);
2623
+ const { payload } = await validateFoundAccessToken(ctx, opts, found, client.clientId);
2624
+ return resolveIntrospectionSub(ctx, opts, payload, client);
2537
2625
  } catch (error) {
2538
- if (error instanceof APIError$1) {
2539
- if (isInactiveTokenError(error)) return { active: false };
2540
- throw error;
2541
- } else if (error instanceof Error) {
2542
- logger.error("Introspection error:", error.message, error.stack);
2543
- throw new APIError$1("INTERNAL_SERVER_ERROR");
2544
- } else {
2545
- logger.error("Introspection error:", error);
2546
- throw new APIError$1("INTERNAL_SERVER_ERROR");
2547
- }
2626
+ if (error instanceof APIError$1) throw error;
2627
+ logger.error("Introspection error:", error);
2628
+ throw new APIError$1("INTERNAL_SERVER_ERROR");
2548
2629
  }
2549
2630
  }
2550
2631
  //#endregion
2551
- export { claimsRequestParameterSchema as A, clientJwksSchema as C, STANDARD_CLAIM_NAMES as D, STANDARD_CLAIMS as E, getRequestedUserInfoClaims as M, isValidOidcClaimsRequest as N, getSupportedClaims as O, authorizationQuerySchema as S, oauthClientMetadataSchema as T, logStartupSeedFailure as _, invalidateRefreshFamily as a, ResourceUriSchema as b, normalizeClientCredentialsScopes as c, assertIdentifierValid as d, extractRepeatedResourceFromForm as f, logEnforcePerClientResourcesResolution as g, isAudienceClaimAllowed as h, getOAuthProviderApi as i, filterClaimsRequestUserInfoClaims as j, canSatisfyEssentialAcrRequest as k, validateClientCredentialsScopes as l, invalidateResourceCache as m, introspect_exports as n, tokenEndpoint as o, getResource as p, decodeRefreshToken as r, userInfoEndpoint as s, introspectEndpoint as t, JWS_ALGORITHMS as u, resolveResourcePolicy as v, clientRegistrationRequestSchema as w, SafeUrlSchema as x, seedResourcesOnce as y };
2632
+ export { getAcrValuesSupported as A, authorizationQuerySchema as C, STANDARD_CLAIMS as D, oauthClientMetadataSchema as E, getRequestedUserInfoClaims as F, isValidOidcClaimsRequest as I, canSatisfyEssentialAcrRequest as M, claimsRequestParameterSchema as N, STANDARD_CLAIM_NAMES as O, filterClaimsRequestUserInfoClaims as P, SafeUrlSchema as S, clientRegistrationRequestSchema as T, logStartupSeedFailure as _, tokenEndpoint as a, userInfoResource as b, normalizeClientCredentialsScopes as c, JWS_ALGORITHMS as d, assertIdentifierValid as f, logEnforcePerClientResourcesResolution as g, isAudienceClaimAllowed as h, invalidateRefreshFamily as i, resolveAuthenticationContext as j, getSupportedClaims as k, validateClientCredentialsScopes as l, invalidateResourceCache as m, introspect_exports as n, userInfoEndpoint as o, getResource as p, getOAuthProviderApi as r, findToken as s, introspectEndpoint as t, getEndpointUrl as u, resolveResourcePolicy as v, clientJwksSchema as w, ResourceUriSchema as x, seedResourcesOnce as y };