@better-auth/oauth-provider 1.7.0-beta.7 → 1.7.0-beta.9
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/dist/{client-assertion-CctbJywV.mjs → client-assertion-D-tAYsKC.mjs} +1 -1
- package/dist/client-resource.d.mts +1 -1
- package/dist/client-resource.mjs +2 -2
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +2 -2
- package/dist/index.d.mts +7 -4
- package/dist/index.mjs +288 -181
- package/dist/{introspect-BXNvkz8S.mjs → introspect-Bzbar3iY.mjs} +328 -90
- package/dist/{oauth-CqOygaZd.d.mts → oauth-C7baUP-L.d.mts} +36 -42
- package/dist/{oauth-CPWY2Few.d.mts → oauth-Di-k6QeJ.d.mts} +150 -20
- package/dist/{utils-Baq6atYN.mjs → utils-DO8lmoDw.mjs} +13 -8
- package/dist/{version-DkFgXWfN.mjs → version-M87fotrf.mjs} +1 -1
- package/package.json +5 -5
- /package/dist/{signed-query-CFv2jNMT.mjs → signed-query-Df1MNiSH.mjs} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
|
|
2
|
-
import { A as collectExtensionUserInfoClaims, C as toAudienceClaim, F as getSupportedGrantTypes, I as hasUserInfoClaimExtension, N as getExtensionGrantHandler, O as collectExtensionAccessTokenClaims, S as storeToken, T as validateClientCredentials, a as getClient, c as getStoredToken, f as normalizeTimestampValue, i as extractClientCredentials, k as collectExtensionIdTokenClaims, l as isPKCERequired, m as parseClientMetadata, n as decryptStoredClientSecret, o as getJwtPlugin, r as destructureCredentials, t as clientAllowsGrant, v as resolveSessionAuthTime, w as toResourceList, y as resolveSubjectIdentifier } from "./utils-
|
|
2
|
+
import { A as collectExtensionUserInfoClaims, C as toAudienceClaim, F as getSupportedGrantTypes, I as hasUserInfoClaimExtension, N as getExtensionGrantHandler, O as collectExtensionAccessTokenClaims, S as storeToken, T as validateClientCredentials, a as getClient, c as getStoredToken, f as normalizeTimestampValue, i as extractClientCredentials, k as collectExtensionIdTokenClaims, l as isPKCERequired, m as parseClientMetadata, n as decryptStoredClientSecret, o as getJwtPlugin, r as destructureCredentials, t as clientAllowsGrant, v as resolveSessionAuthTime, w as toResourceList, y as resolveSubjectIdentifier } from "./utils-DO8lmoDw.mjs";
|
|
3
3
|
import { APIError } from "better-auth/api";
|
|
4
4
|
import { generateRandomString } from "better-auth/crypto";
|
|
5
5
|
import { APIError as APIError$1 } from "better-call";
|
|
@@ -9,6 +9,156 @@ import { createDpopReplayStore, enforceDpopBinding, generateCodeChallenge, getCo
|
|
|
9
9
|
import { SignJWT, base64url, createLocalJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
|
|
10
10
|
import { resolveSigningKey, signJWT, toExpJWT } from "better-auth/plugins";
|
|
11
11
|
import { SafeUrlSchema } from "@better-auth/core/utils/redirect-uri";
|
|
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
|
|
54
|
+
//#region src/claims-request.ts
|
|
55
|
+
const claimRequestMemberSchema = z.union([z.null(), z.record(z.string(), z.unknown())]);
|
|
56
|
+
const claimsRequestObjectSchema = z.object({
|
|
57
|
+
userinfo: z.record(z.string(), claimRequestMemberSchema).optional(),
|
|
58
|
+
id_token: z.record(z.string(), claimRequestMemberSchema).optional()
|
|
59
|
+
}).passthrough();
|
|
60
|
+
function parseClaimsRequestValue(value) {
|
|
61
|
+
if (typeof value === "string") try {
|
|
62
|
+
return JSON.parse(value);
|
|
63
|
+
} catch {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
function parseClaimsRequestObject(value) {
|
|
69
|
+
const parsed = parseClaimsRequestValue(value);
|
|
70
|
+
const result = claimsRequestObjectSchema.safeParse(parsed);
|
|
71
|
+
return result.success ? result.data : void 0;
|
|
72
|
+
}
|
|
73
|
+
const claimsRequestParameterSchema = z.union([z.string(), z.record(z.string(), z.unknown())]).superRefine((value, ctx) => {
|
|
74
|
+
if (!parseClaimsRequestObject(value)) ctx.addIssue({
|
|
75
|
+
code: "custom",
|
|
76
|
+
message: "claims must be a JSON object"
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
function getRequestedUserInfoClaims(value, supportedClaims) {
|
|
80
|
+
const userInfoClaims = parseClaimsRequestObject(value)?.userinfo;
|
|
81
|
+
if (!userInfoClaims) return [];
|
|
82
|
+
const names = Object.keys(userInfoClaims);
|
|
83
|
+
if (!supportedClaims) return names;
|
|
84
|
+
const allowed = new Set(supportedClaims);
|
|
85
|
+
return names.filter((name) => allowed.has(name));
|
|
86
|
+
}
|
|
87
|
+
function filterClaimsRequestUserInfoClaims(value, allowedUserInfoClaims) {
|
|
88
|
+
const claimsRequest = parseClaimsRequestObject(value);
|
|
89
|
+
if (!claimsRequest) return void 0;
|
|
90
|
+
const allowedClaimSet = new Set(allowedUserInfoClaims);
|
|
91
|
+
const userInfoClaims = Object.fromEntries(Object.entries(claimsRequest.userinfo ?? {}).filter(([claim]) => allowedClaimSet.has(claim)));
|
|
92
|
+
const filteredClaimsRequest = Object.keys(userInfoClaims).length ? {
|
|
93
|
+
...claimsRequest,
|
|
94
|
+
userinfo: userInfoClaims
|
|
95
|
+
} : Object.fromEntries(Object.entries(claimsRequest).filter(([key]) => key !== "userinfo"));
|
|
96
|
+
return Object.keys(filteredClaimsRequest).length ? filteredClaimsRequest : void 0;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/standard-claims.ts
|
|
100
|
+
function splitDisplayName(name) {
|
|
101
|
+
const parts = name.split(" ").filter((part) => part !== "");
|
|
102
|
+
if (parts.length <= 1) return {};
|
|
103
|
+
return {
|
|
104
|
+
given: parts.slice(0, -1).join(" "),
|
|
105
|
+
family: parts.at(-1)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The OIDC Standard Claims (OIDC Core §5.1) that Better Auth resolves from its
|
|
110
|
+
* own user model, each paired with the scope that requests it (§5.4).
|
|
111
|
+
*
|
|
112
|
+
* This is the single source for UserInfo scope-claim resolution, individual
|
|
113
|
+
* `claims.userinfo` resolution, the discovery `claims_supported` advertisement,
|
|
114
|
+
* and the bound on requested claim names. Adding a standard claim Better Auth
|
|
115
|
+
* can resolve means adding one entry here, not editing UserInfo, the metadata,
|
|
116
|
+
* and the plugin init separately.
|
|
117
|
+
*
|
|
118
|
+
* These claims are delivered only at the UserInfo endpoint, never the ID token:
|
|
119
|
+
* the authorization code flow always issues an access token, so §5.4 routes
|
|
120
|
+
* scope-requested claims to UserInfo. Deployments that support further standard
|
|
121
|
+
* profile claims (such as `birthdate` or `zoneinfo`) supply them through
|
|
122
|
+
* `customUserInfoClaims` and advertise them in `claims_supported`.
|
|
123
|
+
*/
|
|
124
|
+
const STANDARD_CLAIMS = {
|
|
125
|
+
name: {
|
|
126
|
+
scope: "profile",
|
|
127
|
+
resolve: (user) => user.name ?? void 0
|
|
128
|
+
},
|
|
129
|
+
picture: {
|
|
130
|
+
scope: "profile",
|
|
131
|
+
resolve: (user) => user.image ?? void 0
|
|
132
|
+
},
|
|
133
|
+
given_name: {
|
|
134
|
+
scope: "profile",
|
|
135
|
+
resolve: (user) => splitDisplayName(user.name).given
|
|
136
|
+
},
|
|
137
|
+
family_name: {
|
|
138
|
+
scope: "profile",
|
|
139
|
+
resolve: (user) => splitDisplayName(user.name).family
|
|
140
|
+
},
|
|
141
|
+
email: {
|
|
142
|
+
scope: "email",
|
|
143
|
+
resolve: (user) => user.email ?? void 0
|
|
144
|
+
},
|
|
145
|
+
email_verified: {
|
|
146
|
+
scope: "email",
|
|
147
|
+
resolve: (user) => user.emailVerified ?? false
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const STANDARD_CLAIM_NAMES = Object.keys(STANDARD_CLAIMS);
|
|
151
|
+
/**
|
|
152
|
+
* The claim names this provider advertises it can supply (discovery
|
|
153
|
+
* `claims_supported`): the operator's `advertisedMetadata.claims_supported`
|
|
154
|
+
* override when set, otherwise the scope-derived standard set computed at plugin
|
|
155
|
+
* init. Used both to advertise and to bound requested `claims.userinfo` names so
|
|
156
|
+
* a client cannot pin arbitrary attacker-controlled strings onto stored tokens.
|
|
157
|
+
*/
|
|
158
|
+
function getSupportedClaims(opts) {
|
|
159
|
+
return opts.advertisedMetadata?.claims_supported ?? opts.claims ?? [];
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
12
162
|
//#region src/claims.ts
|
|
13
163
|
/**
|
|
14
164
|
* Claim names the authorization server owns on a JWT access token, which no
|
|
@@ -851,11 +1001,12 @@ const dpopJktSchema = z.string().regex(/^[A-Za-z0-9_-]{43}$/, "dpop_jkt must be
|
|
|
851
1001
|
*/
|
|
852
1002
|
const authorizationQuerySchema = z.object({
|
|
853
1003
|
response_type: z.string().pipe(z.enum(["code"])).optional(),
|
|
1004
|
+
request: z.string().optional(),
|
|
854
1005
|
request_uri: z.string().optional(),
|
|
855
1006
|
redirect_uri: SafeUrlSchema.optional(),
|
|
856
1007
|
scope: z.string().optional(),
|
|
857
1008
|
state: z.string().optional(),
|
|
858
|
-
client_id: z.string(),
|
|
1009
|
+
client_id: z.string().min(1, "client_id is required"),
|
|
859
1010
|
prompt: authorizationPromptSchema.optional(),
|
|
860
1011
|
display: z.string().optional(),
|
|
861
1012
|
ui_locales: z.string().optional(),
|
|
@@ -866,10 +1017,11 @@ const authorizationQuerySchema = z.object({
|
|
|
866
1017
|
code_challenge: z.string().optional(),
|
|
867
1018
|
code_challenge_method: z.string().pipe(z.enum(["S256"])).optional(),
|
|
868
1019
|
nonce: z.string().optional(),
|
|
1020
|
+
claims: claimsRequestParameterSchema.optional(),
|
|
869
1021
|
dpop_jkt: dpopJktSchema.optional(),
|
|
870
1022
|
resource: z.union([ResourceUriSchema, z.array(ResourceUriSchema).min(1)]).optional()
|
|
871
1023
|
}).passthrough();
|
|
872
|
-
const storedAuthorizationQuerySchema = authorizationQuerySchema.extend({ redirect_uri: SafeUrlSchema });
|
|
1024
|
+
const storedAuthorizationQuerySchema = authorizationQuerySchema.extend({ redirect_uri: SafeUrlSchema.optional() });
|
|
873
1025
|
/**
|
|
874
1026
|
* Runtime schema for the authorization code verification value.
|
|
875
1027
|
* Validates structure on deserialization from the JSON blob stored in the DB.
|
|
@@ -930,27 +1082,20 @@ const clientRegistrationRequestSchema = z.object({
|
|
|
930
1082
|
//#endregion
|
|
931
1083
|
//#region src/userinfo.ts
|
|
932
1084
|
/**
|
|
933
|
-
*
|
|
1085
|
+
* Builds the standard OIDC claims (OIDC Core §5.1) for the UserInfo response.
|
|
1086
|
+
*
|
|
1087
|
+
* A claim is included when its backing scope was granted, or when it was named
|
|
1088
|
+
* individually through the `claims.userinfo` request parameter (§5.4, §5.5).
|
|
1089
|
+
* `sub` is always present. Values come from the one claim registry, so the
|
|
1090
|
+
* advertisement, the scope mapping, and the resolution cannot drift apart.
|
|
934
1091
|
*
|
|
935
1092
|
* @see https://openid.net/specs/openid-connect-core-1_0.html#NormalClaims
|
|
936
1093
|
*/
|
|
937
|
-
function userNormalClaims(user, scopes) {
|
|
938
|
-
const
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
given_name: name.length > 1 ? name.slice(0, -1).join(" ") : void 0,
|
|
943
|
-
family_name: name.length > 1 ? name.at(-1) : void 0
|
|
944
|
-
};
|
|
945
|
-
const email = {
|
|
946
|
-
email: user.email ?? void 0,
|
|
947
|
-
email_verified: user.emailVerified ?? false
|
|
948
|
-
};
|
|
949
|
-
return {
|
|
950
|
-
sub: user.id ?? void 0,
|
|
951
|
-
...scopes.includes("profile") ? profile : {},
|
|
952
|
-
...scopes.includes("email") ? email : {}
|
|
953
|
-
};
|
|
1094
|
+
function userNormalClaims(user, scopes, requestedClaims = []) {
|
|
1095
|
+
const requested = new Set(requestedClaims);
|
|
1096
|
+
const claims = { sub: user.id ?? void 0 };
|
|
1097
|
+
for (const [name, definition] of Object.entries(STANDARD_CLAIMS)) if (scopes.includes(definition.scope) || requested.has(name)) claims[name] = definition.resolve(user);
|
|
1098
|
+
return claims;
|
|
954
1099
|
}
|
|
955
1100
|
/**
|
|
956
1101
|
* Returns the defined-valued entries of `claims`, dropping any key already
|
|
@@ -976,17 +1121,33 @@ function pickClaims(claims, base) {
|
|
|
976
1121
|
}
|
|
977
1122
|
return next;
|
|
978
1123
|
}
|
|
1124
|
+
function getUserInfoAccessToken(ctx) {
|
|
1125
|
+
const authorization = ctx.headers?.get("authorization");
|
|
1126
|
+
const headerAccessTokenAuthorization = parseAccessTokenAuthorization(authorization);
|
|
1127
|
+
const bodyToken = ctx.request?.method === "POST" ? ctx.body?.access_token ?? void 0 : void 0;
|
|
1128
|
+
if (headerAccessTokenAuthorization && bodyToken) throw new APIError("BAD_REQUEST", {
|
|
1129
|
+
error_description: "Multiple access token transport methods are not allowed",
|
|
1130
|
+
error: "invalid_request"
|
|
1131
|
+
});
|
|
1132
|
+
const accessTokenAuthorization = headerAccessTokenAuthorization ?? (bodyToken ? {
|
|
1133
|
+
scheme: "Bearer",
|
|
1134
|
+
token: bodyToken
|
|
1135
|
+
} : void 0);
|
|
1136
|
+
return {
|
|
1137
|
+
authorization: accessTokenAuthorization,
|
|
1138
|
+
token: accessTokenAuthorization?.token
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
979
1141
|
/**
|
|
980
1142
|
* Handles the /oauth2/userinfo endpoint
|
|
981
1143
|
*/
|
|
982
1144
|
async function userInfoEndpoint(ctx, opts) {
|
|
983
|
-
const authorization = ctx
|
|
984
|
-
const accessTokenAuthorization = parseAccessTokenAuthorization(authorization);
|
|
1145
|
+
const { authorization: accessTokenAuthorization } = getUserInfoAccessToken(ctx);
|
|
985
1146
|
if (!accessTokenAuthorization?.token) throw new APIError("UNAUTHORIZED", {
|
|
986
|
-
error_description: "
|
|
1147
|
+
error_description: "access token not found",
|
|
987
1148
|
error: "invalid_request"
|
|
988
1149
|
});
|
|
989
|
-
const jwt = await
|
|
1150
|
+
const { payload: jwt, requestedUserInfoClaims: requestedClaims } = await requireActiveAccessTokenWithClaims(ctx, opts, accessTokenAuthorization.token);
|
|
990
1151
|
if (getDpopJktFromPayload(jwt) && !ctx.request) throw new APIError("UNAUTHORIZED", {
|
|
991
1152
|
error_description: "DPoP-bound access token requires an HTTP request context",
|
|
992
1153
|
error: "invalid_token"
|
|
@@ -1023,7 +1184,7 @@ async function userInfoEndpoint(ctx, opts) {
|
|
|
1023
1184
|
error_description: "user not found",
|
|
1024
1185
|
error: "invalid_request"
|
|
1025
1186
|
});
|
|
1026
|
-
const baseUserClaims = userNormalClaims(user, scopes ?? []);
|
|
1187
|
+
const baseUserClaims = userNormalClaims(user, scopes ?? [], requestedClaims);
|
|
1027
1188
|
const clientId = jwt.client_id ?? jwt.azp;
|
|
1028
1189
|
const client = clientId && (opts.pairwiseSecret || hasUserInfoClaimExtension(opts)) ? await getClient(ctx, opts, clientId) : void 0;
|
|
1029
1190
|
if (opts.pairwiseSecret && client) baseUserClaims.sub = await resolveSubjectIdentifier(user.id, client, opts);
|
|
@@ -1033,12 +1194,14 @@ async function userInfoEndpoint(ctx, opts) {
|
|
|
1033
1194
|
user,
|
|
1034
1195
|
scopes,
|
|
1035
1196
|
jwt,
|
|
1036
|
-
client: client ?? void 0
|
|
1197
|
+
client: client ?? void 0,
|
|
1198
|
+
requestedClaims
|
|
1037
1199
|
}) : {};
|
|
1038
1200
|
const additionalInfoUserClaims = opts.customUserInfoClaims && scopes?.length ? await opts.customUserInfoClaims({
|
|
1039
1201
|
user,
|
|
1040
1202
|
scopes,
|
|
1041
|
-
jwt
|
|
1203
|
+
jwt,
|
|
1204
|
+
requestedClaims
|
|
1042
1205
|
}) : {};
|
|
1043
1206
|
return {
|
|
1044
1207
|
...baseUserClaims,
|
|
@@ -1049,6 +1212,7 @@ async function userInfoEndpoint(ctx, opts) {
|
|
|
1049
1212
|
}
|
|
1050
1213
|
//#endregion
|
|
1051
1214
|
//#region src/token.ts
|
|
1215
|
+
const ID_TOKEN_SCOPE_CLAIM_GUARDS = Object.fromEntries(STANDARD_CLAIM_NAMES.map((name) => [name, void 0]));
|
|
1052
1216
|
/**
|
|
1053
1217
|
* Token presentation scheme implied by a confirmation: a DPoP key thumbprint
|
|
1054
1218
|
* (`jkt`) yields `"DPoP"`; any other confirmation (including mTLS `x5t#S256`)
|
|
@@ -1183,24 +1347,22 @@ async function computeOidcHash(token, signingAlg) {
|
|
|
1183
1347
|
async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId, authTime, accessToken, extraClaims) {
|
|
1184
1348
|
const iat = Math.floor(Date.now() / 1e3);
|
|
1185
1349
|
const exp = iat + (opts.idTokenExpiresIn ?? 36e3);
|
|
1186
|
-
const userClaims = userNormalClaims(user, scopes);
|
|
1187
1350
|
const resolvedSub = await resolveSubjectIdentifier(user.id, client, opts);
|
|
1188
1351
|
const authTimeSec = authTime != null ? Math.floor(authTime.getTime() / 1e3) : void 0;
|
|
1189
|
-
const
|
|
1190
|
-
const customClaims = opts.customIdTokenClaims ? await opts.customIdTokenClaims({
|
|
1352
|
+
const customClaims = stripReservedIdTokenClaims(opts.customIdTokenClaims ? await opts.customIdTokenClaims({
|
|
1191
1353
|
user,
|
|
1192
1354
|
scopes,
|
|
1193
1355
|
metadata: parseClientMetadata(client.metadata)
|
|
1194
|
-
}) :
|
|
1356
|
+
}) : void 0);
|
|
1195
1357
|
const jwtPluginOptions = opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options;
|
|
1196
1358
|
const resolvedKey = !opts.disableJwtPlugin && !jwtPluginOptions?.jwt?.sign ? await resolveSigningKey(ctx, jwtPluginOptions) : void 0;
|
|
1197
1359
|
const signingAlg = opts.disableJwtPlugin ? "HS256" : resolvedKey?.alg ?? jwtPluginOptions?.jwks?.keyPairConfig?.alg;
|
|
1198
1360
|
const atHash = accessToken ? await computeOidcHash(accessToken, signingAlg) : void 0;
|
|
1199
1361
|
const emitSid = Boolean(client.enableEndSession || client.backchannelLogoutUri);
|
|
1200
1362
|
const payload = {
|
|
1201
|
-
...
|
|
1363
|
+
...ID_TOKEN_SCOPE_CLAIM_GUARDS,
|
|
1202
1364
|
auth_time: authTimeSec,
|
|
1203
|
-
acr,
|
|
1365
|
+
acr: "0",
|
|
1204
1366
|
...customClaims,
|
|
1205
1367
|
at_hash: atHash,
|
|
1206
1368
|
iss: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL,
|
|
@@ -1211,7 +1373,8 @@ async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId,
|
|
|
1211
1373
|
exp,
|
|
1212
1374
|
sid: emitSid ? sessionId : void 0
|
|
1213
1375
|
};
|
|
1214
|
-
|
|
1376
|
+
const additiveClaims = stripReservedIdTokenClaims(extraClaims);
|
|
1377
|
+
Object.assign(payload, pickClaims(additiveClaims, payload));
|
|
1215
1378
|
if (opts.disableJwtPlugin && !client.clientSecret) return;
|
|
1216
1379
|
const idToken = opts.disableJwtPlugin ? await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).sign(new TextEncoder().encode(await decryptStoredClientSecret(ctx, opts.storeClientSecret, client.clientSecret))) : await signJWT(ctx, {
|
|
1217
1380
|
options: jwtPluginOptions,
|
|
@@ -1246,7 +1409,7 @@ async function decodeRefreshToken(opts, token) {
|
|
|
1246
1409
|
});
|
|
1247
1410
|
return opts.formatRefreshToken?.decrypt ? opts.formatRefreshToken?.decrypt(token) : { token };
|
|
1248
1411
|
}
|
|
1249
|
-
async function createOpaqueAccessToken(ctx, opts, user, client, scopes, payload, resources, referenceId, refreshId, confirmation) {
|
|
1412
|
+
async function createOpaqueAccessToken(ctx, opts, user, client, scopes, payload, resources, referenceId, authorizationCodeId, refreshId, confirmation, requestedUserInfoClaims) {
|
|
1250
1413
|
const iat = payload.iat ?? Math.floor(Date.now() / 1e3);
|
|
1251
1414
|
const exp = payload?.exp ?? iat + (opts.accessTokenExpiresIn ?? 3600);
|
|
1252
1415
|
const token = opts.generateOpaqueAccessToken ? await opts.generateOpaqueAccessToken() : generateRandomString(32, "A-Z", "a-z");
|
|
@@ -1258,9 +1421,11 @@ async function createOpaqueAccessToken(ctx, opts, user, client, scopes, payload,
|
|
|
1258
1421
|
sessionId: payload?.sid,
|
|
1259
1422
|
userId: user?.id,
|
|
1260
1423
|
referenceId,
|
|
1424
|
+
authorizationCodeId,
|
|
1261
1425
|
resources,
|
|
1262
1426
|
refreshId,
|
|
1263
1427
|
confirmation,
|
|
1428
|
+
requestedUserInfoClaims: requestedUserInfoClaims?.length ? requestedUserInfoClaims : void 0,
|
|
1264
1429
|
scopes,
|
|
1265
1430
|
createdAt: /* @__PURE__ */ new Date(iat * 1e3),
|
|
1266
1431
|
expiresAt: /* @__PURE__ */ new Date(exp * 1e3)
|
|
@@ -1315,7 +1480,24 @@ async function invalidateRefreshFamily(ctx, clientId, userId) {
|
|
|
1315
1480
|
}]
|
|
1316
1481
|
});
|
|
1317
1482
|
}
|
|
1318
|
-
async function
|
|
1483
|
+
async function revokeTokensIssuedForAuthorizationCode(ctx, authorizationCodeId) {
|
|
1484
|
+
const deleteIssuedTokens = async (model) => {
|
|
1485
|
+
try {
|
|
1486
|
+
await ctx.context.adapter.deleteMany({
|
|
1487
|
+
model,
|
|
1488
|
+
where: [{
|
|
1489
|
+
field: "authorizationCodeId",
|
|
1490
|
+
value: authorizationCodeId
|
|
1491
|
+
}]
|
|
1492
|
+
});
|
|
1493
|
+
} catch (error) {
|
|
1494
|
+
ctx.context.logger.error("authorization code replay cleanup failed", error);
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
await deleteIssuedTokens("oauthAccessToken");
|
|
1498
|
+
await deleteIssuedTokens("oauthRefreshToken");
|
|
1499
|
+
}
|
|
1500
|
+
async function createRefreshToken(ctx, opts, user, referenceId, authorizationCodeId, client, scopes, payload, originalRefresh, authTime, resources, confirmation, requestedUserInfoClaims) {
|
|
1319
1501
|
const iat = payload.iat ?? Math.floor(Date.now() / 1e3);
|
|
1320
1502
|
const exp = payload?.exp ?? iat + (opts.refreshTokenExpiresIn ?? 2592e3);
|
|
1321
1503
|
const token = opts.generateRefreshToken ? await opts.generateRefreshToken() : generateRandomString(32, "A-Z", "a-z");
|
|
@@ -1326,8 +1508,10 @@ async function createRefreshToken(ctx, opts, user, referenceId, client, scopes,
|
|
|
1326
1508
|
sessionId,
|
|
1327
1509
|
userId: user.id,
|
|
1328
1510
|
referenceId,
|
|
1511
|
+
authorizationCodeId,
|
|
1329
1512
|
authTime,
|
|
1330
1513
|
confirmation,
|
|
1514
|
+
requestedUserInfoClaims: requestedUserInfoClaims?.length ? requestedUserInfoClaims : void 0,
|
|
1331
1515
|
scopes,
|
|
1332
1516
|
resources,
|
|
1333
1517
|
createdAt: /* @__PURE__ */ new Date(iat * 1e3),
|
|
@@ -1473,11 +1657,12 @@ async function createUserTokens(ctx, opts, params) {
|
|
|
1473
1657
|
verificationValue,
|
|
1474
1658
|
refreshToken: existingRefreshToken
|
|
1475
1659
|
});
|
|
1476
|
-
const
|
|
1660
|
+
const requestedUserInfoClaims = params.requestedUserInfoClaims ?? existingRefreshToken?.requestedUserInfoClaims ?? [];
|
|
1661
|
+
const earlyRefreshToken = isRefreshToken && user && !isJwtAccessToken ? await createRefreshToken(ctx, opts, user, referenceId, params.authorizationCodeId, client, effectiveScopes, {
|
|
1477
1662
|
iat,
|
|
1478
1663
|
exp: refreshTokenExp,
|
|
1479
1664
|
sid: sessionId
|
|
1480
|
-
}, existingRefreshToken, authTime, refreshResources, confirmation) : void 0;
|
|
1665
|
+
}, existingRefreshToken, authTime, refreshResources, confirmation, requestedUserInfoClaims) : void 0;
|
|
1481
1666
|
const accessTokenClaims = isJwtAccessToken ? await resolveAccessTokenClaims({
|
|
1482
1667
|
ctx,
|
|
1483
1668
|
opts,
|
|
@@ -1504,11 +1689,11 @@ async function createUserTokens(ctx, opts, params) {
|
|
|
1504
1689
|
iat,
|
|
1505
1690
|
exp,
|
|
1506
1691
|
sid: sessionId
|
|
1507
|
-
}, params?.resources, referenceId, earlyRefreshToken?.id, confirmation), earlyRefreshToken ? earlyRefreshToken : isRefreshToken && user ? createRefreshToken(ctx, opts, user, referenceId, client, effectiveScopes, {
|
|
1692
|
+
}, params?.resources, referenceId, params.authorizationCodeId, earlyRefreshToken?.id, confirmation, requestedUserInfoClaims), earlyRefreshToken ? earlyRefreshToken : isRefreshToken && user ? createRefreshToken(ctx, opts, user, referenceId, params.authorizationCodeId, client, effectiveScopes, {
|
|
1508
1693
|
iat,
|
|
1509
1694
|
exp: refreshTokenExp,
|
|
1510
1695
|
sid: sessionId
|
|
1511
|
-
}, existingRefreshToken, authTime, refreshResources, confirmation) : void 0]);
|
|
1696
|
+
}, existingRefreshToken, authTime, refreshResources, confirmation, requestedUserInfoClaims) : void 0]);
|
|
1512
1697
|
const idToken = isIdToken ? await createIdToken(ctx, opts, user, client, effectiveScopes, nonce, sessionId, authTime, accessToken, additionalIdTokenClaims) : void 0;
|
|
1513
1698
|
return ctx.json({
|
|
1514
1699
|
...customFields,
|
|
@@ -1524,33 +1709,47 @@ async function createUserTokens(ctx, opts, params) {
|
|
|
1524
1709
|
}
|
|
1525
1710
|
/** Checks verification value */
|
|
1526
1711
|
async function checkVerificationValue(ctx, opts, code, client_id, redirect_uri, resource) {
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1712
|
+
const authorizationCodeId = await storeToken(opts.storeTokens, code, "authorization_code");
|
|
1713
|
+
const verification = await ctx.context.internalAdapter.consumeVerificationValue(authorizationCodeId);
|
|
1714
|
+
if (!verification) {
|
|
1715
|
+
await revokeTokensIssuedForAuthorizationCode(ctx, authorizationCodeId);
|
|
1716
|
+
throw new APIError("BAD_REQUEST", {
|
|
1717
|
+
error_description: "invalid code",
|
|
1718
|
+
error: "invalid_grant"
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1532
1721
|
let rawValue;
|
|
1533
1722
|
try {
|
|
1534
1723
|
rawValue = JSON.parse(verification.value);
|
|
1535
1724
|
} catch {
|
|
1536
|
-
throw new APIError("
|
|
1725
|
+
throw new APIError("BAD_REQUEST", {
|
|
1537
1726
|
error_description: "malformed verification value",
|
|
1538
1727
|
error: "invalid_grant"
|
|
1539
1728
|
});
|
|
1540
1729
|
}
|
|
1541
1730
|
const parsed = verificationValueSchema.safeParse(rawValue);
|
|
1542
|
-
if (!parsed.success) throw new APIError("
|
|
1731
|
+
if (!parsed.success) throw new APIError("BAD_REQUEST", {
|
|
1543
1732
|
error_description: "malformed verification value",
|
|
1544
1733
|
error: "invalid_grant"
|
|
1545
1734
|
});
|
|
1546
1735
|
const verificationValue = parsed.data;
|
|
1547
|
-
if (verificationValue.query.client_id !== client_id) throw new APIError("
|
|
1736
|
+
if (verificationValue.query.client_id !== client_id) throw new APIError("BAD_REQUEST", {
|
|
1548
1737
|
error_description: "invalid client_id",
|
|
1549
|
-
error: "
|
|
1738
|
+
error: "invalid_grant"
|
|
1550
1739
|
});
|
|
1551
|
-
|
|
1740
|
+
const boundRedirectUri = verificationValue.query.redirect_uri;
|
|
1741
|
+
if (boundRedirectUri) {
|
|
1742
|
+
if (!redirect_uri) throw new APIError("BAD_REQUEST", {
|
|
1743
|
+
error_description: "redirect_uri is required",
|
|
1744
|
+
error: "invalid_request"
|
|
1745
|
+
});
|
|
1746
|
+
if (boundRedirectUri !== redirect_uri) throw new APIError("BAD_REQUEST", {
|
|
1747
|
+
error_description: "redirect_uri mismatch",
|
|
1748
|
+
error: "invalid_grant"
|
|
1749
|
+
});
|
|
1750
|
+
} else if (redirect_uri) throw new APIError("BAD_REQUEST", {
|
|
1552
1751
|
error_description: "redirect_uri mismatch",
|
|
1553
|
-
error: "
|
|
1752
|
+
error: "invalid_grant"
|
|
1554
1753
|
});
|
|
1555
1754
|
const storedResources = toResourceList(verificationValue.resource) ?? toResourceList(verificationValue.query.resource);
|
|
1556
1755
|
const effectiveResources = resource ?? storedResources;
|
|
@@ -1565,7 +1764,8 @@ async function checkVerificationValue(ctx, opts, code, client_id, redirect_uri,
|
|
|
1565
1764
|
return {
|
|
1566
1765
|
verificationValue,
|
|
1567
1766
|
effectiveResources,
|
|
1568
|
-
authorizedResources: storedResources
|
|
1767
|
+
authorizedResources: storedResources,
|
|
1768
|
+
authorizationCodeId
|
|
1569
1769
|
};
|
|
1570
1770
|
}
|
|
1571
1771
|
/**
|
|
@@ -1583,10 +1783,6 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
|
|
|
1583
1783
|
error_description: "code is required",
|
|
1584
1784
|
error: "invalid_request"
|
|
1585
1785
|
});
|
|
1586
|
-
if (!redirect_uri) throw new APIError("BAD_REQUEST", {
|
|
1587
|
-
error_description: "redirect_uri is required",
|
|
1588
|
-
error: "invalid_request"
|
|
1589
|
-
});
|
|
1590
1786
|
const isAuthCodeWithSecret = client_id && client_secret;
|
|
1591
1787
|
const isAuthCodeWithPkce = client_id && code && code_verifier;
|
|
1592
1788
|
if (!isAuthCodeWithSecret && !isAuthCodeWithPkce && !preVerified) throw new APIError("BAD_REQUEST", {
|
|
@@ -1594,7 +1790,7 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
|
|
|
1594
1790
|
error: "invalid_request"
|
|
1595
1791
|
});
|
|
1596
1792
|
/** Get and check Verification Value */
|
|
1597
|
-
const { verificationValue, effectiveResources, authorizedResources } = await checkVerificationValue(ctx, opts, code, client_id, redirect_uri, resources);
|
|
1793
|
+
const { verificationValue, effectiveResources, authorizedResources, authorizationCodeId } = await checkVerificationValue(ctx, opts, code, client_id, redirect_uri, resources);
|
|
1598
1794
|
const scopes = verificationValue.query.scope?.split(" ");
|
|
1599
1795
|
if (!scopes) throw new APIError("INTERNAL_SERVER_ERROR", {
|
|
1600
1796
|
error_description: "verification scope unset",
|
|
@@ -1602,7 +1798,10 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
|
|
|
1602
1798
|
});
|
|
1603
1799
|
/** Verify Client */
|
|
1604
1800
|
const client = await validateClientCredentials(ctx, opts, client_id, client_secret, scopes, preVerified, "authorization_code", authMethod);
|
|
1605
|
-
if (isPKCERequired(client,
|
|
1801
|
+
if (isPKCERequired(client, {
|
|
1802
|
+
scopes: (verificationValue.query?.scope)?.split(" ") || [],
|
|
1803
|
+
nonce: verificationValue.query?.nonce
|
|
1804
|
+
})) {
|
|
1606
1805
|
if (!isAuthCodeWithPkce) throw new APIError("BAD_REQUEST", {
|
|
1607
1806
|
error_description: "PKCE is required for this client",
|
|
1608
1807
|
error: "invalid_request"
|
|
@@ -1650,6 +1849,7 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
|
|
|
1650
1849
|
error: "invalid_request"
|
|
1651
1850
|
});
|
|
1652
1851
|
const authTime = verificationValue.authTime != null ? normalizeTimestampValue(verificationValue.authTime) : resolveSessionAuthTime(session);
|
|
1852
|
+
const requestedUserInfoClaims = getRequestedUserInfoClaims(verificationValue.query.claims, getSupportedClaims(opts));
|
|
1653
1853
|
return createUserTokens(ctx, opts, {
|
|
1654
1854
|
client,
|
|
1655
1855
|
scopes: verificationValue.query.scope?.split(" ") ?? [],
|
|
@@ -1660,6 +1860,8 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
|
|
|
1660
1860
|
nonce: verificationValue.query?.nonce,
|
|
1661
1861
|
authTime,
|
|
1662
1862
|
verificationValue,
|
|
1863
|
+
authorizationCodeId,
|
|
1864
|
+
requestedUserInfoClaims,
|
|
1663
1865
|
resources: effectiveResources,
|
|
1664
1866
|
originalResources: authorizedResources
|
|
1665
1867
|
});
|
|
@@ -1739,8 +1941,8 @@ async function handleRefreshTokenGrant(ctx, opts) {
|
|
|
1739
1941
|
error: "invalid_grant"
|
|
1740
1942
|
});
|
|
1741
1943
|
if (refreshToken.clientId !== client_id) throw new APIError("BAD_REQUEST", {
|
|
1742
|
-
error_description: "invalid
|
|
1743
|
-
error: "
|
|
1944
|
+
error_description: "invalid refresh token",
|
|
1945
|
+
error: "invalid_grant"
|
|
1744
1946
|
});
|
|
1745
1947
|
if (refreshToken.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("BAD_REQUEST", {
|
|
1746
1948
|
error_description: "invalid refresh token",
|
|
@@ -1779,10 +1981,12 @@ async function handleRefreshTokenGrant(ctx, opts) {
|
|
|
1779
1981
|
user,
|
|
1780
1982
|
grantType: "refresh_token",
|
|
1781
1983
|
referenceId: refreshToken.referenceId,
|
|
1984
|
+
authorizationCodeId: refreshToken.authorizationCodeId,
|
|
1782
1985
|
sessionId: refreshToken.sessionId,
|
|
1783
1986
|
refreshToken,
|
|
1784
1987
|
resources: resources ?? refreshToken.resources,
|
|
1785
|
-
authTime
|
|
1988
|
+
authTime,
|
|
1989
|
+
requestedUserInfoClaims: refreshToken.requestedUserInfoClaims
|
|
1786
1990
|
});
|
|
1787
1991
|
}
|
|
1788
1992
|
//#endregion
|
|
@@ -1790,10 +1994,17 @@ async function handleRefreshTokenGrant(ctx, opts) {
|
|
|
1790
1994
|
var introspect_exports = /* @__PURE__ */ __exportAll({
|
|
1791
1995
|
introspectEndpoint: () => introspectEndpoint,
|
|
1792
1996
|
requireActiveAccessToken: () => requireActiveAccessToken,
|
|
1997
|
+
requireActiveAccessTokenWithClaims: () => requireActiveAccessTokenWithClaims,
|
|
1793
1998
|
validateAccessToken: () => validateAccessToken
|
|
1794
1999
|
});
|
|
1795
2000
|
const INVALID_ACCESS_TOKEN_ERROR_DESCRIPTION = "Invalid access token";
|
|
1796
2001
|
const INVALID_ACCESS_TOKEN_WWW_AUTHENTICATE = `Bearer error="invalid_token", error_description="${INVALID_ACCESS_TOKEN_ERROR_DESCRIPTION}"`;
|
|
2002
|
+
function inactiveAccessToken() {
|
|
2003
|
+
return {
|
|
2004
|
+
payload: { active: false },
|
|
2005
|
+
requestedUserInfoClaims: []
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
1797
2008
|
/**
|
|
1798
2009
|
* IMPORTANT NOTES:
|
|
1799
2010
|
* Introspection follows RFC7662
|
|
@@ -1903,18 +2114,18 @@ async function validateOpaqueAccessToken(ctx, opts, token, clientId) {
|
|
|
1903
2114
|
error_description: "opaque access token not found",
|
|
1904
2115
|
error: "invalid_token"
|
|
1905
2116
|
});
|
|
1906
|
-
if (!accessToken.expiresAt || accessToken.expiresAt < /* @__PURE__ */ new Date()) return
|
|
1907
|
-
if (accessToken.revoked) return
|
|
2117
|
+
if (!accessToken.expiresAt || accessToken.expiresAt < /* @__PURE__ */ new Date()) return inactiveAccessToken();
|
|
2118
|
+
if (accessToken.revoked) return inactiveAccessToken();
|
|
1908
2119
|
const resources = Array.isArray(accessToken.resources) ? accessToken.resources : void 0;
|
|
1909
2120
|
let client;
|
|
1910
2121
|
if (accessToken.clientId) {
|
|
1911
2122
|
client = await getClient(ctx, opts, accessToken.clientId);
|
|
1912
|
-
if (!client || client?.disabled) return
|
|
2123
|
+
if (!client || client?.disabled) return inactiveAccessToken();
|
|
1913
2124
|
if (!await isIntrospectionAuthorized(ctx, opts, {
|
|
1914
2125
|
introspectingClientId: clientId,
|
|
1915
2126
|
issuerClientId: accessToken.clientId,
|
|
1916
2127
|
audienceResources: resources ?? []
|
|
1917
|
-
})) return
|
|
2128
|
+
})) return inactiveAccessToken();
|
|
1918
2129
|
}
|
|
1919
2130
|
const sessionId = accessToken.sessionId ?? void 0;
|
|
1920
2131
|
if (sessionId) {
|
|
@@ -1925,12 +2136,12 @@ async function validateOpaqueAccessToken(ctx, opts, token, clientId) {
|
|
|
1925
2136
|
value: sessionId
|
|
1926
2137
|
}]
|
|
1927
2138
|
});
|
|
1928
|
-
if (!session || session.expiresAt < /* @__PURE__ */ new Date()) return
|
|
2139
|
+
if (!session || session.expiresAt < /* @__PURE__ */ new Date()) return inactiveAccessToken();
|
|
1929
2140
|
}
|
|
1930
2141
|
let user;
|
|
1931
2142
|
if (accessToken.userId) user = await ctx.context.internalAdapter.findUserById(accessToken?.userId);
|
|
1932
2143
|
const userInfoEndpoint = `${ctx.context.baseURL}/oauth2/userinfo`;
|
|
1933
|
-
if (resources?.length && !await isAudienceClaimAllowed(ctx, opts, resources, [userInfoEndpoint])) return
|
|
2144
|
+
if (resources?.length && !await isAudienceClaimAllowed(ctx, opts, resources, [userInfoEndpoint])) return inactiveAccessToken();
|
|
1934
2145
|
const audienceClaim = resources ? [...resources] : void 0;
|
|
1935
2146
|
if (audienceClaim?.length && accessToken.scopes?.includes("openid")) {
|
|
1936
2147
|
if (!audienceClaim.includes(userInfoEndpoint)) audienceClaim.push(userInfoEndpoint);
|
|
@@ -1952,19 +2163,22 @@ async function validateOpaqueAccessToken(ctx, opts, token, clientId) {
|
|
|
1952
2163
|
}) : {};
|
|
1953
2164
|
const jwtPluginOptions = (opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context))?.options;
|
|
1954
2165
|
return {
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
2166
|
+
payload: {
|
|
2167
|
+
...accessTokenClaims,
|
|
2168
|
+
active: true,
|
|
2169
|
+
iss: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL,
|
|
2170
|
+
aud: toAudienceClaim(audienceClaim),
|
|
2171
|
+
client_id: accessToken.clientId,
|
|
2172
|
+
azp: accessToken.clientId,
|
|
2173
|
+
sub: user?.id,
|
|
2174
|
+
sid: sessionId,
|
|
2175
|
+
exp: Math.floor(new Date(accessToken.expiresAt).getTime() / 1e3),
|
|
2176
|
+
iat: Math.floor(new Date(accessToken.createdAt).getTime() / 1e3),
|
|
2177
|
+
scope: accessToken.scopes?.join(" "),
|
|
2178
|
+
token_type: confirmationTokenType(accessToken.confirmation),
|
|
2179
|
+
...accessToken.confirmation ? { cnf: accessToken.confirmation } : {}
|
|
2180
|
+
},
|
|
2181
|
+
requestedUserInfoClaims: accessToken.requestedUserInfoClaims ?? []
|
|
1968
2182
|
};
|
|
1969
2183
|
}
|
|
1970
2184
|
/**
|
|
@@ -2023,16 +2237,17 @@ function isInactiveTokenError(error) {
|
|
|
2023
2237
|
return error.status === "BAD_REQUEST" || error.body?.error === "invalid_token";
|
|
2024
2238
|
}
|
|
2025
2239
|
/**
|
|
2026
|
-
* We don't know the access token format so we try to validate it
|
|
2027
|
-
*
|
|
2028
|
-
*
|
|
2029
|
-
*
|
|
2030
|
-
*
|
|
2031
|
-
* @internal
|
|
2240
|
+
* We don't know the access token format so we try to validate it as a JWT
|
|
2241
|
+
* first, then as an opaque token. Returns the RFC 7662 introspection payload
|
|
2242
|
+
* alongside the per-issuance UserInfo claims the opaque row persisted (empty for
|
|
2243
|
+
* a JWT access token, which resolves UserInfo claims by scope).
|
|
2032
2244
|
*/
|
|
2033
|
-
async function
|
|
2245
|
+
async function resolveAccessTokenValidation(ctx, opts, token, clientId) {
|
|
2034
2246
|
try {
|
|
2035
|
-
return
|
|
2247
|
+
return {
|
|
2248
|
+
payload: await validateJwtAccessToken(ctx, opts, token, clientId),
|
|
2249
|
+
requestedUserInfoClaims: []
|
|
2250
|
+
};
|
|
2036
2251
|
} catch (err) {
|
|
2037
2252
|
if (err instanceof APIError$1) {} else if (err instanceof Error) throw err;
|
|
2038
2253
|
else throw new Error(err);
|
|
@@ -2045,12 +2260,35 @@ async function validateAccessToken(ctx, opts, token, clientId) {
|
|
|
2045
2260
|
}
|
|
2046
2261
|
throw createInvalidAccessTokenError();
|
|
2047
2262
|
}
|
|
2263
|
+
/**
|
|
2264
|
+
* Validates an access token (JWT or opaque) and returns its RFC 7662
|
|
2265
|
+
* introspection-format payload.
|
|
2266
|
+
*
|
|
2267
|
+
* @internal
|
|
2268
|
+
*/
|
|
2269
|
+
async function validateAccessToken(ctx, opts, token, clientId) {
|
|
2270
|
+
return (await resolveAccessTokenValidation(ctx, opts, token, clientId)).payload;
|
|
2271
|
+
}
|
|
2048
2272
|
async function requireActiveAccessToken(ctx, opts, token, clientId) {
|
|
2049
2273
|
const payload = await validateAccessToken(ctx, opts, token, clientId);
|
|
2050
2274
|
if (payload.active) return payload;
|
|
2051
2275
|
throw createInvalidAccessTokenError();
|
|
2052
2276
|
}
|
|
2053
2277
|
/**
|
|
2278
|
+
* UserInfo entry point: validates the access token, requires it active, and
|
|
2279
|
+
* returns the introspection payload together with the OIDC `claims.userinfo`
|
|
2280
|
+
* names persisted for the token. Opaque tokens carry the names on their row;
|
|
2281
|
+
* JWT access tokens return an empty list and resolve UserInfo claims by scope.
|
|
2282
|
+
*/
|
|
2283
|
+
async function requireActiveAccessTokenWithClaims(ctx, opts, token, clientId) {
|
|
2284
|
+
const { payload, requestedUserInfoClaims } = await resolveAccessTokenValidation(ctx, opts, token, clientId);
|
|
2285
|
+
if (payload.active) return {
|
|
2286
|
+
payload,
|
|
2287
|
+
requestedUserInfoClaims
|
|
2288
|
+
};
|
|
2289
|
+
throw createInvalidAccessTokenError();
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2054
2292
|
* Resolves pairwise sub on an introspection payload.
|
|
2055
2293
|
* Applied at the presentation layer so internal validation functions
|
|
2056
2294
|
* keep real user.id (needed for user lookup in /userinfo).
|
|
@@ -2116,4 +2354,4 @@ async function introspectEndpoint(ctx, opts) {
|
|
|
2116
2354
|
}
|
|
2117
2355
|
}
|
|
2118
2356
|
//#endregion
|
|
2119
|
-
export { invalidateResourceCache as _, invalidateRefreshFamily as a, resolveResourcePolicy as b, ResourceUriSchema as c, clientRegistrationRequestSchema as d, JWS_ALGORITHMS as f, getResource as g, extractRepeatedResourceFromForm as h, getOAuthProviderApi as i, SafeUrlSchema as l, buildClientResourceLinkId as m, introspect_exports as n, tokenEndpoint as o, assertIdentifierValid as p, decodeRefreshToken as r, userInfoEndpoint as s, introspectEndpoint as t, authorizationQuerySchema as u, isAudienceClaimAllowed as v, seedResources as x, logEnforcePerClientResourcesResolution as y };
|
|
2357
|
+
export { STANDARD_CLAIM_NAMES as C, getRequestedUserInfoClaims as D, filterClaimsRequestUserInfoClaims as E, STANDARD_CLAIMS as S, claimsRequestParameterSchema as T, invalidateResourceCache as _, invalidateRefreshFamily as a, resolveResourcePolicy as b, ResourceUriSchema as c, clientRegistrationRequestSchema as d, JWS_ALGORITHMS as f, getResource as g, extractRepeatedResourceFromForm as h, getOAuthProviderApi as i, SafeUrlSchema as l, buildClientResourceLinkId as m, introspect_exports as n, tokenEndpoint as o, assertIdentifierValid as p, decodeRefreshToken as r, userInfoEndpoint as s, introspectEndpoint as t, authorizationQuerySchema as u, isAudienceClaimAllowed as v, getSupportedClaims as w, seedResources as x, logEnforcePerClientResourcesResolution as y };
|