@better-auth/oauth-provider 1.7.0-beta.4 → 1.7.0-beta.6
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-DLMKVgoj.mjs → client-assertion-CctbJywV.mjs} +102 -87
- package/dist/client-resource.d.mts +31 -2
- package/dist/client-resource.mjs +45 -25
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +3 -13
- package/dist/index.d.mts +102 -17
- package/dist/index.mjs +1747 -1886
- package/dist/introspect-BXqKFUQZ.mjs +2115 -0
- package/dist/{oauth-Vt3lTNHX.d.mts → oauth-CAeemjD7.d.mts} +364 -175
- package/dist/{oauth-q7dn10NU.d.mts → oauth-CaXmZpoL.d.mts} +922 -33
- package/dist/resource-challenge-B-cqv4ur.mjs +63 -0
- package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
- package/dist/signed-query-CFv2jNMT.mjs +44 -0
- package/dist/utils-Baq6atYN.mjs +764 -0
- package/dist/{version-nFnRm-a3.mjs → version-CUu3vBtU.mjs} +1 -1
- package/package.json +8 -9
- package/dist/mcp-CYnz-MXn.mjs +0 -56
- package/dist/utils-DKBWQ8fe.mjs +0 -492
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
import { n as canonicalizeOAuthQueryParams } from "./signed-query-CFv2jNMT.mjs";
|
|
2
|
+
import { constantTimeEqual, makeSignature, symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
|
|
3
|
+
import { APIError } from "better-call";
|
|
4
|
+
import { logger } from "@better-auth/core/env";
|
|
5
|
+
import { BetterAuthError } from "@better-auth/core/error";
|
|
6
|
+
import { CLIENT_ASSERTION_TYPE, decodeBasicCredentials } from "@better-auth/core/oauth2";
|
|
7
|
+
import { base64Url } from "@better-auth/utils/base64";
|
|
8
|
+
import { createHash } from "@better-auth/utils/hash";
|
|
9
|
+
//#region src/extensions.ts
|
|
10
|
+
const DEFAULT_GRANT_TYPES = [
|
|
11
|
+
"authorization_code",
|
|
12
|
+
"client_credentials",
|
|
13
|
+
"refresh_token"
|
|
14
|
+
];
|
|
15
|
+
const BUILT_IN_CONFIDENTIAL_AUTH_METHODS = [
|
|
16
|
+
"client_secret_basic",
|
|
17
|
+
"client_secret_post",
|
|
18
|
+
"private_key_jwt"
|
|
19
|
+
];
|
|
20
|
+
const RESERVED_TOKEN_ENDPOINT_AUTH_METHODS = ["none", ...BUILT_IN_CONFIDENTIAL_AUTH_METHODS];
|
|
21
|
+
const RESERVED_TOKEN_ENDPOINT_AUTH_METHOD_SET = new Set(RESERVED_TOKEN_ENDPOINT_AUTH_METHODS);
|
|
22
|
+
function assertNonEmptyExtensionValue(name, value) {
|
|
23
|
+
if (value.trim().length > 0) return;
|
|
24
|
+
throw new BetterAuthError(`OAuth Provider extension ${name} cannot be empty`);
|
|
25
|
+
}
|
|
26
|
+
function assertAbsoluteUri(name, value) {
|
|
27
|
+
assertNonEmptyExtensionValue(name, value);
|
|
28
|
+
let url;
|
|
29
|
+
try {
|
|
30
|
+
url = new URL(value);
|
|
31
|
+
} catch {
|
|
32
|
+
url = void 0;
|
|
33
|
+
}
|
|
34
|
+
if (url?.protocol) return;
|
|
35
|
+
throw new BetterAuthError(`OAuth Provider extension ${name} must be an absolute URI: ${value}`);
|
|
36
|
+
}
|
|
37
|
+
function assertExtensionGrantType(grantType) {
|
|
38
|
+
assertAbsoluteUri("grant type", grantType);
|
|
39
|
+
}
|
|
40
|
+
function assertExtensionTokenEndpointAuthMethod(method) {
|
|
41
|
+
assertNonEmptyExtensionValue("token_endpoint_auth_method", method);
|
|
42
|
+
if (!RESERVED_TOKEN_ENDPOINT_AUTH_METHOD_SET.has(method)) return;
|
|
43
|
+
throw new BetterAuthError(`OAuth Provider extension token_endpoint_auth_method is reserved: ${method}`);
|
|
44
|
+
}
|
|
45
|
+
function assertExtensionClientAssertionType(assertionType) {
|
|
46
|
+
assertAbsoluteUri("client_assertion_type", assertionType);
|
|
47
|
+
if (assertionType !== CLIENT_ASSERTION_TYPE) return;
|
|
48
|
+
throw new BetterAuthError(`OAuth Provider extension client_assertion_type is reserved: ${assertionType}`);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validates one extension's dispatched keys (grant types, auth methods,
|
|
52
|
+
* assertion types) and returns them for the cross-extension disjointness check.
|
|
53
|
+
* Throws on a non-absolute grant/assertion URI, a reserved auth-method name, or
|
|
54
|
+
* an empty assertion-type list.
|
|
55
|
+
*/
|
|
56
|
+
function collectExtensionKeys(extension) {
|
|
57
|
+
const grantTypes = Object.keys(extension.grants ?? {});
|
|
58
|
+
for (const grantType of grantTypes) assertExtensionGrantType(grantType);
|
|
59
|
+
const authMethods = [];
|
|
60
|
+
const assertionTypes = [];
|
|
61
|
+
for (const [method, strategy] of Object.entries(extension.clientAuthentication ?? {})) {
|
|
62
|
+
assertExtensionTokenEndpointAuthMethod(method);
|
|
63
|
+
authMethods.push(method);
|
|
64
|
+
const methodAssertionTypes = strategy.assertionTypes ?? [method];
|
|
65
|
+
if (methodAssertionTypes.length === 0) throw new BetterAuthError(`OAuth Provider extension client_assertion_type list cannot be empty for ${method}`);
|
|
66
|
+
for (const assertionType of methodAssertionTypes) {
|
|
67
|
+
assertExtensionClientAssertionType(assertionType);
|
|
68
|
+
assertionTypes.push(assertionType);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
grantTypes,
|
|
73
|
+
authMethods,
|
|
74
|
+
assertionTypes
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function assertNoDuplicateAcrossExtensions(label, values) {
|
|
78
|
+
const seen = /* @__PURE__ */ new Set();
|
|
79
|
+
for (const value of values) {
|
|
80
|
+
if (seen.has(value)) throw new BetterAuthError(`OAuth Provider extensions register ${label} "${value}" more than once. Extension contributions must be disjoint.`);
|
|
81
|
+
seen.add(value);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Validates every extension and rejects two extensions registering the same
|
|
86
|
+
* grant type, auth method, or assertion type: otherwise the first would win and
|
|
87
|
+
* the second be silently unreachable. Runs at setup over the whole list;
|
|
88
|
+
* extensions number in the single digits, so a full re-scan per registration is
|
|
89
|
+
* cheaper than the bookkeeping to cache it.
|
|
90
|
+
*/
|
|
91
|
+
function validateOAuthProviderExtensions(extensions) {
|
|
92
|
+
const keys = (extensions ?? []).map(collectExtensionKeys);
|
|
93
|
+
assertNoDuplicateAcrossExtensions("grant type", keys.flatMap((k) => k.grantTypes));
|
|
94
|
+
assertNoDuplicateAcrossExtensions("token_endpoint_auth_method", keys.flatMap((k) => k.authMethods));
|
|
95
|
+
assertNoDuplicateAcrossExtensions("client_assertion_type", keys.flatMap((k) => k.assertionTypes));
|
|
96
|
+
}
|
|
97
|
+
function getOAuthProviderExtensions(opts) {
|
|
98
|
+
return opts.extensions ?? [];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Flattens the client-id discovery sources contributed by every registered
|
|
102
|
+
* extension into a single ordered list. `getClient()` consults them in order;
|
|
103
|
+
* the metadata endpoints merge their `discoveryMetadata`.
|
|
104
|
+
*/
|
|
105
|
+
function getClientDiscoveries(opts) {
|
|
106
|
+
return getOAuthProviderExtensions(opts).flatMap((extension) => {
|
|
107
|
+
const discovery = extension.clientDiscovery;
|
|
108
|
+
if (!discovery) return [];
|
|
109
|
+
return Array.isArray(discovery) ? discovery : [discovery];
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Registers an {@link OAuthProviderExtension} with the OAuth Provider plugin
|
|
114
|
+
* from a companion plugin's `init()` hook. An extension can add token grants,
|
|
115
|
+
* assertion-based client authentication methods, additive discovery metadata,
|
|
116
|
+
* access-token / ID-token / UserInfo claims, and client-id discovery, without
|
|
117
|
+
* forking provider core.
|
|
118
|
+
*
|
|
119
|
+
* Call this once, at `init()` time. It is idempotent in the same `extension`
|
|
120
|
+
* object, so re-running a plugin's `init()` (for example when one plugin factory
|
|
121
|
+
* result is shared across two `betterAuth()` instances) does not register it
|
|
122
|
+
* twice. It throws if the oauth-provider plugin is not installed, if a grant
|
|
123
|
+
* type or assertion type is not an absolute URI, if a client authentication
|
|
124
|
+
* method reuses a built-in name, or if the extension registers a grant type,
|
|
125
|
+
* auth method, or assertion type that another extension already registered
|
|
126
|
+
* (contributions must be disjoint).
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* init(ctx) {
|
|
131
|
+
* extendOAuthProvider(ctx, {
|
|
132
|
+
* grants: { "urn:example:grant": async ({ provider }) => provider.issueTokens(...) },
|
|
133
|
+
* });
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
function extendOAuthProvider(ctx, extension) {
|
|
138
|
+
const provider = ctx.getPlugin("oauth-provider");
|
|
139
|
+
if (!provider) throw new BetterAuthError("extendOAuthProvider requires the oauth-provider plugin.");
|
|
140
|
+
const existing = provider.options.extensions ?? [];
|
|
141
|
+
if (existing.includes(extension)) return;
|
|
142
|
+
const extensions = [...existing, extension];
|
|
143
|
+
validateOAuthProviderExtensions(extensions);
|
|
144
|
+
provider.options.extensions = extensions;
|
|
145
|
+
}
|
|
146
|
+
function getExtensionGrantTypes(opts) {
|
|
147
|
+
return getOAuthProviderExtensions(opts).flatMap((extension) => Object.keys(extension.grants ?? {}));
|
|
148
|
+
}
|
|
149
|
+
function getSupportedGrantTypes(opts) {
|
|
150
|
+
return Array.from(new Set([...opts.grantTypes ?? DEFAULT_GRANT_TYPES, ...getExtensionGrantTypes(opts)]));
|
|
151
|
+
}
|
|
152
|
+
function getExtensionGrantHandler(opts, grantType) {
|
|
153
|
+
for (const extension of getOAuthProviderExtensions(opts)) {
|
|
154
|
+
const handler = extension.grants?.[grantType];
|
|
155
|
+
if (handler) return handler;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function getExtensionTokenEndpointAuthMethods(opts) {
|
|
159
|
+
return getOAuthProviderExtensions(opts).flatMap((extension) => Object.keys(extension.clientAuthentication ?? {}));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Confidential and extension client-authentication methods the provider
|
|
163
|
+
* supports. Pass `includeNone` to prepend `"none"` for the token endpoint and
|
|
164
|
+
* DCR, where public clients are allowed; the introspection and revocation
|
|
165
|
+
* endpoints, which never accept public clients, omit it (the default).
|
|
166
|
+
*/
|
|
167
|
+
function getSupportedAuthMethods(opts, settings) {
|
|
168
|
+
return Array.from(new Set([
|
|
169
|
+
...settings?.includeNone ? ["none"] : [],
|
|
170
|
+
...BUILT_IN_CONFIDENTIAL_AUTH_METHODS,
|
|
171
|
+
...getExtensionTokenEndpointAuthMethods(opts)
|
|
172
|
+
]));
|
|
173
|
+
}
|
|
174
|
+
function isExtensionTokenEndpointAuthMethod(opts, method) {
|
|
175
|
+
return method ? getExtensionTokenEndpointAuthMethods(opts).includes(method) : false;
|
|
176
|
+
}
|
|
177
|
+
function getExtensionClientAuthenticationStrategy(opts, assertionType) {
|
|
178
|
+
if (assertionType === CLIENT_ASSERTION_TYPE) return void 0;
|
|
179
|
+
for (const extension of getOAuthProviderExtensions(opts)) {
|
|
180
|
+
const strategies = extension.clientAuthentication ?? {};
|
|
181
|
+
for (const [method, strategy] of Object.entries(strategies)) if ((strategy.assertionTypes ?? [method]).includes(assertionType)) return {
|
|
182
|
+
method,
|
|
183
|
+
strategy
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Merges each registered extension's `metadata()` contribution into `document`,
|
|
189
|
+
* first-wins: the provider owns every key it already wrote, so an extension can
|
|
190
|
+
* add fields but never override core. Each contributor sees the base `document`,
|
|
191
|
+
* not the running accumulation, so contributions stay order-independent.
|
|
192
|
+
*/
|
|
193
|
+
function applyOAuthProviderMetadataExtensions(ctx, opts, type, document) {
|
|
194
|
+
const next = { ...document };
|
|
195
|
+
for (const extension of getOAuthProviderExtensions(opts)) {
|
|
196
|
+
const contribution = extension.metadata?.({
|
|
197
|
+
ctx,
|
|
198
|
+
opts,
|
|
199
|
+
type,
|
|
200
|
+
document
|
|
201
|
+
});
|
|
202
|
+
for (const [key, value] of Object.entries(contribution ?? {})) if (!(key in next)) next[key] = value;
|
|
203
|
+
}
|
|
204
|
+
return next;
|
|
205
|
+
}
|
|
206
|
+
async function collectClaims(opts, run) {
|
|
207
|
+
const claims = {};
|
|
208
|
+
for (const extension of getOAuthProviderExtensions(opts)) {
|
|
209
|
+
const contribution = await run(extension) ?? {};
|
|
210
|
+
for (const [key, value] of Object.entries(contribution)) {
|
|
211
|
+
if (key in claims) {
|
|
212
|
+
logger.warn(`oauth-provider: two extensions contributed the claim "${key}"; keeping the first-registered value.`);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
claims[key] = value;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return claims;
|
|
219
|
+
}
|
|
220
|
+
function collectExtensionAccessTokenClaims(opts, input) {
|
|
221
|
+
return collectClaims(opts, (extension) => extension.claims?.accessToken?.(input));
|
|
222
|
+
}
|
|
223
|
+
function collectExtensionIdTokenClaims(opts, input) {
|
|
224
|
+
return collectClaims(opts, (extension) => extension.claims?.idToken?.(input));
|
|
225
|
+
}
|
|
226
|
+
function collectExtensionUserInfoClaims(opts, input) {
|
|
227
|
+
return collectClaims(opts, (extension) => extension.claims?.userInfo?.(input));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Whether any registered extension contributes UserInfo claims. Lets the
|
|
231
|
+
* UserInfo endpoint skip loading the client when nothing needs it.
|
|
232
|
+
*/
|
|
233
|
+
function hasUserInfoClaimExtension(opts) {
|
|
234
|
+
return getOAuthProviderExtensions(opts).some((extension) => extension.claims?.userInfo);
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/utils/index.ts
|
|
238
|
+
/**
|
|
239
|
+
* Extracts the credentials from an `Authorization: Bearer <token>` header.
|
|
240
|
+
*
|
|
241
|
+
* Returns `undefined` when the header is absent or carries a non-Bearer scheme,
|
|
242
|
+
* leaving the caller to decide whether that is an error. Throws an
|
|
243
|
+
* `invalid_request` `APIError` when the Bearer scheme is present but the
|
|
244
|
+
* credentials are missing or the header carries extra parts. The scheme match
|
|
245
|
+
* is case-insensitive and the credentials are the single token after it.
|
|
246
|
+
*
|
|
247
|
+
* @see https://datatracker.ietf.org/doc/html/rfc6750#section-2.1
|
|
248
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7235#section-2.1
|
|
249
|
+
*/
|
|
250
|
+
function parseBearerToken(authorization) {
|
|
251
|
+
if (!authorization) return void 0;
|
|
252
|
+
const [scheme, credentials, ...extraParts] = authorization.trim().split(/\s+/);
|
|
253
|
+
if (scheme?.toLowerCase() !== "bearer") return void 0;
|
|
254
|
+
if (!credentials || extraParts.length > 0) throw new APIError("BAD_REQUEST", {
|
|
255
|
+
error: "invalid_request",
|
|
256
|
+
error_description: "Malformed Bearer Authorization header"
|
|
257
|
+
});
|
|
258
|
+
return credentials;
|
|
259
|
+
}
|
|
260
|
+
var TTLCache = class {
|
|
261
|
+
cache = /* @__PURE__ */ new Map();
|
|
262
|
+
constructor() {}
|
|
263
|
+
set(key, value) {
|
|
264
|
+
this.cache.set(key, value);
|
|
265
|
+
}
|
|
266
|
+
get(key) {
|
|
267
|
+
const entry = this.cache.get(key);
|
|
268
|
+
if (!entry) return void 0;
|
|
269
|
+
if (entry.expiresAt && entry.expiresAt < /* @__PURE__ */ new Date()) {
|
|
270
|
+
this.cache.delete(key);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
return entry;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* Gets the oAuth Provider Plugin
|
|
278
|
+
* @internal
|
|
279
|
+
*/
|
|
280
|
+
const getOAuthProviderPlugin = (ctx) => {
|
|
281
|
+
return ctx.getPlugin("oauth-provider");
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Gets the JWT Plugin
|
|
285
|
+
* @internal
|
|
286
|
+
*/
|
|
287
|
+
const getJwtPlugin = (ctx) => {
|
|
288
|
+
const plugin = ctx.getPlugin("jwt");
|
|
289
|
+
if (!plugin) throw new BetterAuthError("jwt_config");
|
|
290
|
+
return plugin;
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* Normalizes timestamp-like values returned by adapters.
|
|
294
|
+
*
|
|
295
|
+
* Accepts Date instances, epoch milliseconds as numbers, and strings that are
|
|
296
|
+
* either ISO dates or numeric millisecond values such as "1774295570569.0".
|
|
297
|
+
*/
|
|
298
|
+
function normalizeTimestampValue(value) {
|
|
299
|
+
if (value == null) return;
|
|
300
|
+
if (value instanceof Date) return Number.isFinite(value.getTime()) ? value : void 0;
|
|
301
|
+
if (typeof value === "number") {
|
|
302
|
+
if (!Number.isFinite(value)) return;
|
|
303
|
+
const parsed = new Date(value);
|
|
304
|
+
return Number.isFinite(parsed.getTime()) ? parsed : void 0;
|
|
305
|
+
}
|
|
306
|
+
if (typeof value === "string") {
|
|
307
|
+
const trimmed = value.trim();
|
|
308
|
+
if (!trimmed.length) return;
|
|
309
|
+
const numeric = Number(trimmed);
|
|
310
|
+
if (Number.isFinite(numeric)) {
|
|
311
|
+
const parsed = new Date(numeric);
|
|
312
|
+
return Number.isFinite(parsed.getTime()) ? parsed : void 0;
|
|
313
|
+
}
|
|
314
|
+
const parsed = new Date(trimmed);
|
|
315
|
+
return Number.isFinite(parsed.getTime()) ? parsed : void 0;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Resolves a session auth time from common adapter return shapes.
|
|
320
|
+
*/
|
|
321
|
+
function resolveSessionAuthTime(value) {
|
|
322
|
+
if (value instanceof Date) return normalizeTimestampValue(value);
|
|
323
|
+
if (!value || typeof value !== "object") return normalizeTimestampValue(value);
|
|
324
|
+
const direct = normalizeTimestampValue(value.createdAt) ?? normalizeTimestampValue(value.created_at);
|
|
325
|
+
if (direct) return direct;
|
|
326
|
+
const nested = value.session;
|
|
327
|
+
if (!nested || typeof nested !== "object") return;
|
|
328
|
+
return normalizeTimestampValue(nested.createdAt) ?? normalizeTimestampValue(nested.created_at);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Normalizes OAuth resource values into a non-empty string array.
|
|
332
|
+
*/
|
|
333
|
+
function toResourceList(value) {
|
|
334
|
+
if (typeof value === "string") return [value];
|
|
335
|
+
if (!value?.length) return void 0;
|
|
336
|
+
return value;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Normalizes audience values for JWT claims.
|
|
340
|
+
*/
|
|
341
|
+
function toAudienceClaim(audience) {
|
|
342
|
+
if (typeof audience === "string") return audience;
|
|
343
|
+
if (!audience?.length) return void 0;
|
|
344
|
+
return audience.length === 1 ? audience.at(0) : audience;
|
|
345
|
+
}
|
|
346
|
+
const cachedTrustedClients = new TTLCache();
|
|
347
|
+
async function verifyOAuthQueryParams(oauth_query, secret) {
|
|
348
|
+
const queryParams = new URLSearchParams(oauth_query);
|
|
349
|
+
const sig = queryParams.get("sig");
|
|
350
|
+
const sigs = queryParams.getAll("sig");
|
|
351
|
+
const exp = Number(queryParams.get("exp"));
|
|
352
|
+
queryParams.delete("sig");
|
|
353
|
+
const verifySig = await makeSignature(canonicalizeOAuthQueryParams(queryParams).toString(), secret);
|
|
354
|
+
return sigs.length === 1 && !!sig && constantTimeEqual(sig, verifySig) && /* @__PURE__ */ new Date(exp * 1e3) >= /* @__PURE__ */ new Date();
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Get a client by ID, checking trusted clients first, then database
|
|
358
|
+
*/
|
|
359
|
+
async function getClient(ctx, options, clientId) {
|
|
360
|
+
const trustedClient = cachedTrustedClients.get(clientId);
|
|
361
|
+
if (trustedClient) return Object.assign({}, trustedClient);
|
|
362
|
+
let dbClient = await ctx.context.adapter.findOne({
|
|
363
|
+
model: options.schema?.oauthClient?.modelName ?? "oauthClient",
|
|
364
|
+
where: [{
|
|
365
|
+
field: "clientId",
|
|
366
|
+
value: clientId
|
|
367
|
+
}]
|
|
368
|
+
});
|
|
369
|
+
const discoveries = getClientDiscoveries(options);
|
|
370
|
+
for (const discovery of discoveries) {
|
|
371
|
+
if (!discovery.matches(clientId)) continue;
|
|
372
|
+
const resolved = await discovery.resolve(ctx, clientId, dbClient);
|
|
373
|
+
if (resolved) {
|
|
374
|
+
dbClient = resolved;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (dbClient && options.cachedTrustedClients?.has(clientId)) cachedTrustedClients.set(clientId, Object.assign({}, dbClient));
|
|
379
|
+
return dbClient;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Merge `discoveryMetadata` from every contributed {@link ClientDiscovery}
|
|
383
|
+
* into a single object. Entries are spread in order; later entries override
|
|
384
|
+
* earlier ones on key collisions.
|
|
385
|
+
*
|
|
386
|
+
* @internal
|
|
387
|
+
*/
|
|
388
|
+
function mergeDiscoveryMetadata(discoveries) {
|
|
389
|
+
return discoveries.reduce((acc, d) => ({
|
|
390
|
+
...acc,
|
|
391
|
+
...d.discoveryMetadata ?? {}
|
|
392
|
+
}), {});
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Default client secret hasher using SHA-256
|
|
396
|
+
*
|
|
397
|
+
* @internal
|
|
398
|
+
*/
|
|
399
|
+
const defaultHasher = async (value) => {
|
|
400
|
+
const hash = await createHash("SHA-256").digest(new TextEncoder().encode(value));
|
|
401
|
+
return base64Url.encode(new Uint8Array(hash), { padding: false });
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
404
|
+
* Decrypts a storedClientSecret for signing
|
|
405
|
+
*
|
|
406
|
+
* @internal
|
|
407
|
+
*/
|
|
408
|
+
async function decryptStoredClientSecret(ctx, storageMethod, storedClientSecret) {
|
|
409
|
+
if (storageMethod === "encrypted") return await symmetricDecrypt({
|
|
410
|
+
key: ctx.context.secretConfig,
|
|
411
|
+
data: storedClientSecret
|
|
412
|
+
});
|
|
413
|
+
else if (typeof storageMethod === "object" && "decrypt" in storageMethod) return await storageMethod.decrypt(storedClientSecret);
|
|
414
|
+
throw new BetterAuthError(`Unsupported decryption storageMethod type '${storageMethod}'`);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Verify stored client secret against provided client secret
|
|
418
|
+
*
|
|
419
|
+
* @internal
|
|
420
|
+
*/
|
|
421
|
+
async function verifyStoredClientSecret(ctx, opts, storedClientSecret, clientSecret) {
|
|
422
|
+
const storageMethod = opts.storeClientSecret ?? (opts.disableJwtPlugin ? "encrypted" : "hashed");
|
|
423
|
+
if (clientSecret && opts.prefix?.clientSecret) if (clientSecret.startsWith(opts.prefix?.clientSecret)) clientSecret = clientSecret.replace(opts.prefix.clientSecret, "");
|
|
424
|
+
else throw new APIError("UNAUTHORIZED", {
|
|
425
|
+
error_description: "invalid client_secret",
|
|
426
|
+
error: "invalid_client"
|
|
427
|
+
});
|
|
428
|
+
if (storageMethod === "hashed") {
|
|
429
|
+
const hashedClientSecret = clientSecret ? await defaultHasher(clientSecret) : void 0;
|
|
430
|
+
return !!hashedClientSecret && constantTimeEqual(hashedClientSecret, storedClientSecret);
|
|
431
|
+
} else if (typeof storageMethod === "object" && "hash" in storageMethod) if (storageMethod.verify) return !!clientSecret && await storageMethod.verify(clientSecret, storedClientSecret);
|
|
432
|
+
else {
|
|
433
|
+
const hashedClientSecret = clientSecret ? await storageMethod.hash(clientSecret) : void 0;
|
|
434
|
+
return !!hashedClientSecret && constantTimeEqual(hashedClientSecret, storedClientSecret);
|
|
435
|
+
}
|
|
436
|
+
else if (storageMethod === "encrypted") try {
|
|
437
|
+
const decryptedClientSecret = await decryptStoredClientSecret(ctx, storageMethod, storedClientSecret);
|
|
438
|
+
return !!clientSecret && constantTimeEqual(decryptedClientSecret, clientSecret);
|
|
439
|
+
} catch {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
else if (typeof storageMethod === "object" && "decrypt" in storageMethod) {
|
|
443
|
+
const decryptedClientSecret = await decryptStoredClientSecret(ctx, storageMethod, storedClientSecret);
|
|
444
|
+
return !!clientSecret && constantTimeEqual(decryptedClientSecret, clientSecret);
|
|
445
|
+
}
|
|
446
|
+
throw new BetterAuthError(`Unsupported verify storageMethod type '${storageMethod}'`);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Store client secret according to the configured storage method
|
|
450
|
+
*
|
|
451
|
+
* @internal
|
|
452
|
+
*/
|
|
453
|
+
async function storeClientSecret(ctx, opts, clientSecret) {
|
|
454
|
+
const storageMethod = opts.storeClientSecret ?? (opts.disableJwtPlugin ? "encrypted" : "hashed");
|
|
455
|
+
if (storageMethod === "encrypted") return await symmetricEncrypt({
|
|
456
|
+
key: ctx.context.secretConfig,
|
|
457
|
+
data: clientSecret
|
|
458
|
+
});
|
|
459
|
+
else if (storageMethod === "hashed") return await defaultHasher(clientSecret);
|
|
460
|
+
else if (typeof storageMethod === "object" && "hash" in storageMethod) return await storageMethod.hash(clientSecret);
|
|
461
|
+
else if (typeof storageMethod === "object" && "encrypt" in storageMethod) return await storageMethod.encrypt(clientSecret);
|
|
462
|
+
throw new BetterAuthError(`Unsupported storeClientSecret type '${storageMethod}'`);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Stores a token value (ie opaque tokens, refresh tokens, transaction tokens, verification codes)
|
|
466
|
+
* on the database in a secure hashed format.
|
|
467
|
+
*
|
|
468
|
+
* @internal
|
|
469
|
+
*/
|
|
470
|
+
async function storeToken(storageMethod = "hashed", token, type) {
|
|
471
|
+
if (storageMethod === "hashed") return await defaultHasher(token);
|
|
472
|
+
else if (typeof storageMethod === "object" && "hash" in storageMethod) return await storageMethod.hash(token, type);
|
|
473
|
+
throw new BetterAuthError(`storeToken: unsupported storageMethod type '${storageMethod}'`);
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Gets a hashed token value to find on the database.
|
|
477
|
+
*
|
|
478
|
+
* @internal
|
|
479
|
+
*/
|
|
480
|
+
async function getStoredToken(storageMethod = "hashed", token, type) {
|
|
481
|
+
if (storageMethod === "hashed") return await defaultHasher(token);
|
|
482
|
+
else if (typeof storageMethod === "object" && "hash" in storageMethod) return await storageMethod.hash(token, type);
|
|
483
|
+
throw new BetterAuthError(`getStoredToken: unsupported storageMethod type '${storageMethod}'`);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Converts a BASIC authorization header
|
|
487
|
+
* into its client_id and client_secret representation
|
|
488
|
+
*
|
|
489
|
+
* @internal
|
|
490
|
+
*/
|
|
491
|
+
const BASIC_SCHEME_PREFIX = /^Basic +/i;
|
|
492
|
+
function basicToClientCredentials(authorization) {
|
|
493
|
+
if (!BASIC_SCHEME_PREFIX.test(authorization)) return;
|
|
494
|
+
try {
|
|
495
|
+
const { clientId, clientSecret } = decodeBasicCredentials(authorization);
|
|
496
|
+
return {
|
|
497
|
+
client_id: clientId,
|
|
498
|
+
client_secret: clientSecret
|
|
499
|
+
};
|
|
500
|
+
} catch {
|
|
501
|
+
throw new APIError("BAD_REQUEST", {
|
|
502
|
+
error_description: "invalid authorization header format",
|
|
503
|
+
error: "invalid_client"
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Whether a client is allowed to use a given grant type.
|
|
509
|
+
*
|
|
510
|
+
* A client's registered `grantTypes` defaults to the documented default
|
|
511
|
+
* `["authorization_code"]` when unset (see client registration). Refresh tokens
|
|
512
|
+
* are only ever issued through the authorization_code flow, so a client allowed
|
|
513
|
+
* to use `authorization_code` is implicitly allowed to use `refresh_token`.
|
|
514
|
+
*
|
|
515
|
+
* @internal
|
|
516
|
+
*/
|
|
517
|
+
function clientAllowsGrant(client, grantType) {
|
|
518
|
+
const allowedGrants = client.grantTypes && client.grantTypes.length > 0 ? client.grantTypes : ["authorization_code"];
|
|
519
|
+
if (grantType === "refresh_token" && allowedGrants.includes("authorization_code")) return true;
|
|
520
|
+
return allowedGrants.includes(grantType);
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Resolves the registered client by id and authorizes it: existence, disabled
|
|
524
|
+
* state, registered auth method, requested scopes, and grant type. The record is
|
|
525
|
+
* always resolved here via `getClient`, so a client-auth strategy proves the
|
|
526
|
+
* caller controls `clientId` but never supplies the record. `preVerified` marks
|
|
527
|
+
* that an assertion already proved control, so the client-secret check is skipped.
|
|
528
|
+
*
|
|
529
|
+
* @internal
|
|
530
|
+
*/
|
|
531
|
+
async function validateClientCredentials(ctx, options, clientId, clientSecret, scopes, preVerified, grantType, authMethod) {
|
|
532
|
+
const client = await getClient(ctx, options, clientId);
|
|
533
|
+
if (!client) throw new APIError("BAD_REQUEST", {
|
|
534
|
+
error_description: "missing client",
|
|
535
|
+
error: "invalid_client"
|
|
536
|
+
});
|
|
537
|
+
if (client.disabled) throw new APIError("BAD_REQUEST", {
|
|
538
|
+
error_description: "client is disabled",
|
|
539
|
+
error: "invalid_client"
|
|
540
|
+
});
|
|
541
|
+
if (preVerified && authMethod) {
|
|
542
|
+
const registeredAuthMethod = client.tokenEndpointAuthMethod ?? "client_secret_basic";
|
|
543
|
+
if (registeredAuthMethod !== authMethod) throw new APIError("BAD_REQUEST", {
|
|
544
|
+
error_description: `client registered for ${registeredAuthMethod} cannot use ${authMethod}`,
|
|
545
|
+
error: "invalid_client"
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
if ((client.tokenEndpointAuthMethod === "private_key_jwt" || isExtensionTokenEndpointAuthMethod(options, client.tokenEndpointAuthMethod)) && !preVerified) throw new APIError("BAD_REQUEST", {
|
|
549
|
+
error_description: `client registered for ${client.tokenEndpointAuthMethod} must use client_assertion`,
|
|
550
|
+
error: "invalid_client"
|
|
551
|
+
});
|
|
552
|
+
if (!preVerified) {
|
|
553
|
+
if (!client.public && !clientSecret) throw new APIError("BAD_REQUEST", {
|
|
554
|
+
error_description: "client secret must be provided",
|
|
555
|
+
error: "invalid_client"
|
|
556
|
+
});
|
|
557
|
+
if (clientSecret && !client.clientSecret) throw new APIError("BAD_REQUEST", {
|
|
558
|
+
error_description: "public client, client secret should not be received",
|
|
559
|
+
error: "invalid_client"
|
|
560
|
+
});
|
|
561
|
+
if (clientSecret && !await verifyStoredClientSecret(ctx, options, client.clientSecret, clientSecret)) throw new APIError("UNAUTHORIZED", {
|
|
562
|
+
error_description: "invalid client_secret",
|
|
563
|
+
error: "invalid_client"
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
if (scopes && client.scopes) {
|
|
567
|
+
const validScopes = new Set(client.scopes);
|
|
568
|
+
for (const sc of scopes) if (!validScopes.has(sc)) throw new APIError("BAD_REQUEST", {
|
|
569
|
+
error_description: `client does not allow scope ${sc}`,
|
|
570
|
+
error: "invalid_scope"
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
if (grantType && !clientAllowsGrant(client, grantType)) throw new APIError("BAD_REQUEST", {
|
|
574
|
+
error_description: `client is not authorized to use grant type ${grantType}`,
|
|
575
|
+
error: "unauthorized_client"
|
|
576
|
+
});
|
|
577
|
+
return client;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Parse client metadata that may be stored as JSON string or already parsed object.
|
|
581
|
+
* Handles database adapters that auto-parse JSON columns.
|
|
582
|
+
*
|
|
583
|
+
* @internal
|
|
584
|
+
*/
|
|
585
|
+
function parseClientMetadata(metadata) {
|
|
586
|
+
if (!metadata) return void 0;
|
|
587
|
+
return typeof metadata === "string" ? JSON.parse(metadata) : metadata;
|
|
588
|
+
}
|
|
589
|
+
/** Unwraps ExtractedCredentials into the fields each grant handler needs. */
|
|
590
|
+
function destructureCredentials(credentials) {
|
|
591
|
+
return {
|
|
592
|
+
clientId: credentials?.clientId,
|
|
593
|
+
clientSecret: credentials?.kind === "client_secret" ? credentials.clientSecret : void 0,
|
|
594
|
+
preVerified: credentials?.kind === "pre_verified",
|
|
595
|
+
authMethod: credentials?.method,
|
|
596
|
+
confirmation: credentials?.kind === "pre_verified" ? credentials.confirmation : void 0
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Extracts and resolves client credentials from the request.
|
|
601
|
+
* Supports: client_secret_basic, client_secret_post, private_key_jwt, and none (public).
|
|
602
|
+
*/
|
|
603
|
+
async function extractClientCredentials(ctx, opts, expectedAudience) {
|
|
604
|
+
const body = ctx.body ?? {};
|
|
605
|
+
const authorization = ctx.request?.headers.get("authorization") ?? void 0;
|
|
606
|
+
if (body.client_assertion_type || body.client_assertion) {
|
|
607
|
+
if (!body.client_assertion || !body.client_assertion_type) throw new APIError("BAD_REQUEST", {
|
|
608
|
+
error_description: "client_assertion and client_assertion_type must both be provided",
|
|
609
|
+
error: "invalid_client"
|
|
610
|
+
});
|
|
611
|
+
if (body.client_secret || authorization && BASIC_SCHEME_PREFIX.test(authorization)) throw new APIError("BAD_REQUEST", {
|
|
612
|
+
error_description: "client_assertion cannot be combined with client_secret or Basic auth",
|
|
613
|
+
error: "invalid_client"
|
|
614
|
+
});
|
|
615
|
+
const assertion = body.client_assertion;
|
|
616
|
+
const assertionType = body.client_assertion_type;
|
|
617
|
+
const extensionStrategy = getExtensionClientAuthenticationStrategy(opts, assertionType);
|
|
618
|
+
if (extensionStrategy) {
|
|
619
|
+
const result = await extensionStrategy.strategy.authenticate({
|
|
620
|
+
ctx,
|
|
621
|
+
opts,
|
|
622
|
+
assertion,
|
|
623
|
+
assertionType,
|
|
624
|
+
clientId: body.client_id,
|
|
625
|
+
expectedAudience
|
|
626
|
+
});
|
|
627
|
+
return {
|
|
628
|
+
kind: "pre_verified",
|
|
629
|
+
method: extensionStrategy.method,
|
|
630
|
+
clientId: result.clientId,
|
|
631
|
+
confirmation: result.confirmation
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const { verifyClientAssertion: verify } = await import("./client-assertion-CctbJywV.mjs").then((n) => n.t);
|
|
635
|
+
return {
|
|
636
|
+
kind: "pre_verified",
|
|
637
|
+
method: "private_key_jwt",
|
|
638
|
+
clientId: (await verify(ctx, opts, assertion, assertionType, body.client_id, expectedAudience)).clientId
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
if (authorization && BASIC_SCHEME_PREFIX.test(authorization)) {
|
|
642
|
+
const res = basicToClientCredentials(authorization);
|
|
643
|
+
if (res) return {
|
|
644
|
+
kind: "client_secret",
|
|
645
|
+
method: "client_secret_basic",
|
|
646
|
+
clientId: res.client_id,
|
|
647
|
+
clientSecret: res.client_secret
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
if (body.client_id && body.client_secret) return {
|
|
651
|
+
kind: "client_secret",
|
|
652
|
+
method: "client_secret_post",
|
|
653
|
+
clientId: body.client_id,
|
|
654
|
+
clientSecret: body.client_secret
|
|
655
|
+
};
|
|
656
|
+
if (body.client_id) return {
|
|
657
|
+
kind: "public",
|
|
658
|
+
method: "none",
|
|
659
|
+
clientId: body.client_id
|
|
660
|
+
};
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Parse space-separated prompt string into a set of prompts
|
|
665
|
+
*
|
|
666
|
+
* @param prompt
|
|
667
|
+
*/
|
|
668
|
+
function parsePrompt(prompt) {
|
|
669
|
+
const prompts = prompt.split(" ").map((p) => p.trim());
|
|
670
|
+
const set = /* @__PURE__ */ new Set();
|
|
671
|
+
for (const p of prompts) if (p === "login" || p === "consent" || p === "create" || p === "select_account" || p === "none") set.add(p);
|
|
672
|
+
return new Set(set);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Extracts the sector identifier (hostname) from a client's first redirect URI.
|
|
676
|
+
*
|
|
677
|
+
* @see https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg
|
|
678
|
+
* @internal
|
|
679
|
+
*/
|
|
680
|
+
function getSectorIdentifier(client) {
|
|
681
|
+
const uri = client.redirectUris?.[0];
|
|
682
|
+
if (!uri) throw new BetterAuthError("Client has no redirect URIs for sector identifier");
|
|
683
|
+
return new URL(uri).host;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Computes a pairwise subject identifier using HMAC-SHA256.
|
|
687
|
+
*
|
|
688
|
+
* @see https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg
|
|
689
|
+
* @internal
|
|
690
|
+
*/
|
|
691
|
+
async function computePairwiseSub(userId, client, secret) {
|
|
692
|
+
return makeSignature(`${getSectorIdentifier(client)}.${userId}`, secret);
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Returns the appropriate subject identifier for a user+client pair.
|
|
696
|
+
* Uses pairwise when the client opts in and the server has a secret configured.
|
|
697
|
+
*
|
|
698
|
+
* @internal
|
|
699
|
+
*/
|
|
700
|
+
async function resolveSubjectIdentifier(userId, client, opts) {
|
|
701
|
+
if (client.subjectType === "pairwise" && opts.pairwiseSecret) return computePairwiseSub(userId, client, opts.pairwiseSecret);
|
|
702
|
+
return userId;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Converts URLSearchParams to a plain object, preserving
|
|
706
|
+
* multi-valued keys as arrays instead of discarding duplicates.
|
|
707
|
+
*/
|
|
708
|
+
function searchParamsToQuery(params) {
|
|
709
|
+
const result = Object.create(null);
|
|
710
|
+
for (const key of new Set(params.keys())) {
|
|
711
|
+
const values = params.getAll(key);
|
|
712
|
+
result[key] = values.length === 1 ? values[0] : values;
|
|
713
|
+
}
|
|
714
|
+
return result;
|
|
715
|
+
}
|
|
716
|
+
function isSessionFreshForSignedQuery(sessionCreatedAt, signedQueryIssuedAt) {
|
|
717
|
+
if (!signedQueryIssuedAt) return false;
|
|
718
|
+
const normalized = normalizeTimestampValue(sessionCreatedAt);
|
|
719
|
+
if (!normalized) return false;
|
|
720
|
+
return normalized.getTime() >= signedQueryIssuedAt.getTime();
|
|
721
|
+
}
|
|
722
|
+
function removePromptFromQuery(query, prompt) {
|
|
723
|
+
const nextQuery = new URLSearchParams(query);
|
|
724
|
+
const prompts = nextQuery.get("prompt")?.split(" ");
|
|
725
|
+
const foundPrompt = prompts?.findIndex((v) => v === prompt) ?? -1;
|
|
726
|
+
if (foundPrompt >= 0) {
|
|
727
|
+
prompts?.splice(foundPrompt, 1);
|
|
728
|
+
prompts?.length ? nextQuery.set("prompt", prompts.join(" ")) : nextQuery.delete("prompt");
|
|
729
|
+
}
|
|
730
|
+
return nextQuery;
|
|
731
|
+
}
|
|
732
|
+
function removeMaxAgeFromQuery(query) {
|
|
733
|
+
const nextQuery = new URLSearchParams(query);
|
|
734
|
+
nextQuery.delete("max_age");
|
|
735
|
+
return nextQuery;
|
|
736
|
+
}
|
|
737
|
+
var PKCERequirementErrors = /* @__PURE__ */ function(PKCERequirementErrors) {
|
|
738
|
+
PKCERequirementErrors["PUBLIC_CLIENT"] = "pkce is required for public clients";
|
|
739
|
+
PKCERequirementErrors["OFFLINE_ACCESS_SCOPE"] = "pkce is required when requesting offline_access scope";
|
|
740
|
+
PKCERequirementErrors["CLIENT_REQUIRE_PKCE"] = "pkce is required for this client";
|
|
741
|
+
return PKCERequirementErrors;
|
|
742
|
+
}(PKCERequirementErrors || {});
|
|
743
|
+
/**
|
|
744
|
+
* Determines if PKCE is required for a given client and scope.
|
|
745
|
+
*
|
|
746
|
+
* PKCE is always required for:
|
|
747
|
+
* 1. Public clients (cannot securely store client_secret)
|
|
748
|
+
* 2. Requests with offline_access scope (refresh token security)
|
|
749
|
+
*
|
|
750
|
+
* For confidential clients without offline_access:
|
|
751
|
+
* - Uses client.requirePKCE if set (defaults to true)
|
|
752
|
+
*
|
|
753
|
+
* Returns false if PKCE is not required, or the reason it is required.
|
|
754
|
+
*
|
|
755
|
+
* @internal
|
|
756
|
+
*/
|
|
757
|
+
function isPKCERequired(client, requestedScopes) {
|
|
758
|
+
if (client.tokenEndpointAuthMethod === "none" || client.type === "native" || client.type === "user-agent-based" || client.public === true) return PKCERequirementErrors.PUBLIC_CLIENT;
|
|
759
|
+
if (requestedScopes?.includes("offline_access")) return PKCERequirementErrors.OFFLINE_ACCESS_SCOPE;
|
|
760
|
+
if (client.requirePKCE ?? true) return PKCERequirementErrors.CLIENT_REQUIRE_PKCE;
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
//#endregion
|
|
764
|
+
export { collectExtensionUserInfoClaims as A, toAudienceClaim as C, applyOAuthProviderMetadataExtensions as D, verifyOAuthQueryParams as E, getSupportedGrantTypes as F, hasUserInfoClaimExtension as I, isExtensionTokenEndpointAuthMethod as L, getClientDiscoveries as M, getExtensionGrantHandler as N, collectExtensionAccessTokenClaims as O, getSupportedAuthMethods as P, validateOAuthProviderExtensions as R, storeToken as S, validateClientCredentials as T, removePromptFromQuery as _, getClient as a, searchParamsToQuery as b, getStoredToken as c, mergeDiscoveryMetadata as d, normalizeTimestampValue as f, removeMaxAgeFromQuery as g, parsePrompt as h, extractClientCredentials as i, extendOAuthProvider as j, collectExtensionIdTokenClaims as k, isPKCERequired as l, parseClientMetadata as m, decryptStoredClientSecret as n, getJwtPlugin as o, parseBearerToken as p, destructureCredentials as r, getOAuthProviderPlugin as s, clientAllowsGrant as t, isSessionFreshForSignedQuery as u, resolveSessionAuthTime as v, toResourceList as w, storeClientSecret as x, resolveSubjectIdentifier as y };
|