@better-auth/oauth-provider 1.7.0-rc.2 → 1.7.0-rc.4
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-C-Cg0dCs.mjs → client-assertion-CsbEATZc.mjs} +191 -29
- package/dist/client-resource.d.mts +7 -4
- package/dist/client-resource.mjs +9 -4
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +2 -1
- package/dist/index.d.mts +64 -29
- package/dist/index.mjs +368 -1261
- package/dist/internal.d.mts +51 -0
- package/dist/internal.mjs +3 -0
- package/dist/{oauth-CSzjH7fO.d.mts → oauth-Bi2PA_d1.d.mts} +172 -80
- package/dist/{oauth-DdSU0qF6.d.mts → oauth-CgWbnA8o.d.mts} +76 -51
- package/dist/register-BotzQoS8.mjs +1557 -0
- package/dist/resource-challenge-CiJTlsEh.mjs +100 -0
- package/dist/{version-C0y8aPv-.mjs → signed-query-BQAwsV_w.mjs} +1 -4
- package/dist/{resource-challenge-2qgK1B-a.mjs → utils-GbnW6qPl.mjs} +81 -114
- package/dist/version-Do0kTnv7.mjs +5 -0
- package/package.json +14 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { N as getClientDiscoveries, a as getClient } from "./utils-GbnW6qPl.mjs";
|
|
2
2
|
import { isPublicRoutableHost } from "@better-auth/core/utils/host";
|
|
3
3
|
import { APIError } from "better-call";
|
|
4
4
|
import { CLIENT_ASSERTION_TYPE, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS } from "@better-auth/core/oauth2";
|
|
@@ -17,18 +17,105 @@ var __exportAll = (all, no_symbols) => {
|
|
|
17
17
|
return target;
|
|
18
18
|
};
|
|
19
19
|
//#endregion
|
|
20
|
+
//#region src/client-jwks.ts
|
|
21
|
+
const EC_PRIVATE_KEY_JWT_ALGORITHM_BY_CURVE = {
|
|
22
|
+
"P-256": "ES256",
|
|
23
|
+
"P-384": "ES384",
|
|
24
|
+
"P-521": "ES512"
|
|
25
|
+
};
|
|
26
|
+
const OKP_PRIVATE_KEY_JWT_SIGNING_CURVES = ["Ed25519"];
|
|
27
|
+
const PRIVATE_JWK_MEMBER_NAMES = [
|
|
28
|
+
"d",
|
|
29
|
+
"p",
|
|
30
|
+
"q",
|
|
31
|
+
"dp",
|
|
32
|
+
"dq",
|
|
33
|
+
"qi",
|
|
34
|
+
"oth"
|
|
35
|
+
];
|
|
36
|
+
function isRecord(value) {
|
|
37
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
38
|
+
}
|
|
39
|
+
function hasStringMember(key, memberName) {
|
|
40
|
+
return typeof key[memberName] === "string" && key[memberName].length > 0;
|
|
41
|
+
}
|
|
42
|
+
function isSupportedEcSigningCurve(curve) {
|
|
43
|
+
return typeof curve === "string" && Object.prototype.hasOwnProperty.call(EC_PRIVATE_KEY_JWT_ALGORITHM_BY_CURVE, curve);
|
|
44
|
+
}
|
|
45
|
+
function isSupportedOkpSigningCurve(curve) {
|
|
46
|
+
return OKP_PRIVATE_KEY_JWT_SIGNING_CURVES.some((signingCurve) => signingCurve === curve);
|
|
47
|
+
}
|
|
48
|
+
function isSupportedPublicJwk(key) {
|
|
49
|
+
switch (key.kty) {
|
|
50
|
+
case "RSA": return hasStringMember(key, "n") && hasStringMember(key, "e");
|
|
51
|
+
case "EC": return isSupportedEcSigningCurve(key.crv) && hasStringMember(key, "x") && hasStringMember(key, "y");
|
|
52
|
+
case "OKP": return isSupportedOkpSigningCurve(key.crv) && hasStringMember(key, "x");
|
|
53
|
+
default: return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function hasSupportedPrivateKeyJwtAlgorithm(key) {
|
|
57
|
+
if (key.alg === void 0) return true;
|
|
58
|
+
if (typeof key.alg !== "string" || !PRIVATE_KEY_JWT_SIGNING_ALGORITHMS.some((algorithm) => algorithm === key.alg)) return false;
|
|
59
|
+
switch (key.kty) {
|
|
60
|
+
case "RSA": return key.alg.startsWith("RS") || key.alg.startsWith("PS");
|
|
61
|
+
case "EC": return isSupportedEcSigningCurve(key.crv) && EC_PRIVATE_KEY_JWT_ALGORITHM_BY_CURVE[key.crv] === key.alg;
|
|
62
|
+
case "OKP": return isSupportedOkpSigningCurve(key.crv) && key.alg === "EdDSA";
|
|
63
|
+
default: return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Validates an OAuth client's public asymmetric JWK set.
|
|
68
|
+
*
|
|
69
|
+
* This boundary accepts only the RFC 7517 `{ keys: [...] }` representation.
|
|
70
|
+
* It performs no I/O and returns the validated set for downstream JOSE
|
|
71
|
+
* verification.
|
|
72
|
+
*
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
function validatePublicClientJwks(input) {
|
|
76
|
+
const keys = isRecord(input) && Array.isArray(input.keys) ? input.keys : void 0;
|
|
77
|
+
if (!keys?.length) return {
|
|
78
|
+
valid: false,
|
|
79
|
+
error: "jwks must be an RFC 7517 JWK Set object with a non-empty keys array"
|
|
80
|
+
};
|
|
81
|
+
for (const key of keys) {
|
|
82
|
+
if (!isRecord(key)) return {
|
|
83
|
+
valid: false,
|
|
84
|
+
error: "jwks keys must be supported public JWKs with required key parameters"
|
|
85
|
+
};
|
|
86
|
+
if (key.kty === "oct" || "k" in key || PRIVATE_JWK_MEMBER_NAMES.some((name) => name in key)) return {
|
|
87
|
+
valid: false,
|
|
88
|
+
error: "jwks must contain only public asymmetric keys"
|
|
89
|
+
};
|
|
90
|
+
if (!isSupportedPublicJwk(key)) return {
|
|
91
|
+
valid: false,
|
|
92
|
+
error: "jwks keys must be supported public JWKs with required key parameters"
|
|
93
|
+
};
|
|
94
|
+
if (!hasSupportedPrivateKeyJwtAlgorithm(key)) return {
|
|
95
|
+
valid: false,
|
|
96
|
+
error: "jwks key alg must be supported for private_key_jwt and compatible with its key type and signing curve"
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
valid: true,
|
|
101
|
+
jwks: { keys }
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
20
105
|
//#region src/utils/client-assertion.ts
|
|
21
106
|
var client_assertion_exports = /* @__PURE__ */ __exportAll({
|
|
22
107
|
consumeClientAssertion: () => consumeClientAssertion,
|
|
23
108
|
isPrivateHostname: () => isPrivateHostname,
|
|
24
109
|
verifyClientAssertion: () => verifyClientAssertion
|
|
25
110
|
});
|
|
26
|
-
const
|
|
111
|
+
const jwksCaches = /* @__PURE__ */ new WeakMap();
|
|
27
112
|
const JWKS_CACHE_TTL_MS = 300 * 1e3;
|
|
28
113
|
const JWKS_CACHE_MAX_ENTRIES = 500;
|
|
29
114
|
const JWKS_FETCH_TIMEOUT_MS = 5e3;
|
|
30
|
-
|
|
31
|
-
|
|
115
|
+
const MAX_JWKS_RESPONSE_BYTES = 64 * 1024;
|
|
116
|
+
const JSON_CONTENT_TYPE = /^application\/(?:[-\w.]+\+)?json\s*(?:;|$)/i;
|
|
117
|
+
function setJwksCache(jwksCache, cacheKey, jwks, fetchedAt) {
|
|
118
|
+
jwksCache.set(cacheKey, {
|
|
32
119
|
jwks,
|
|
33
120
|
fetchedAt
|
|
34
121
|
});
|
|
@@ -37,6 +124,16 @@ function setJwksCache(uri, jwks, fetchedAt) {
|
|
|
37
124
|
if (oldest !== void 0) jwksCache.delete(oldest);
|
|
38
125
|
}
|
|
39
126
|
}
|
|
127
|
+
function getJwksCache(opts) {
|
|
128
|
+
const existingCache = jwksCaches.get(opts);
|
|
129
|
+
if (existingCache) return existingCache;
|
|
130
|
+
const cache = /* @__PURE__ */ new Map();
|
|
131
|
+
jwksCaches.set(opts, cache);
|
|
132
|
+
return cache;
|
|
133
|
+
}
|
|
134
|
+
function getJwksCacheKey(client) {
|
|
135
|
+
return `${client.clientDiscoveryId ?? "managed"}:${client.jwksUri ?? ""}`;
|
|
136
|
+
}
|
|
40
137
|
const ALGORITHMS_LIST = [...PRIVATE_KEY_JWT_SIGNING_ALGORITHMS];
|
|
41
138
|
/**
|
|
42
139
|
* SSRF gate for user-supplied server-side fetch targets (`jwks_uri`,
|
|
@@ -60,6 +157,14 @@ function validateJwksUri(ctx, jwksUri, clientIdUrlOrigin) {
|
|
|
60
157
|
error_description: "jwks_uri must use HTTPS",
|
|
61
158
|
error: "invalid_client"
|
|
62
159
|
});
|
|
160
|
+
if (parsed.username || parsed.password) throw new APIError("BAD_REQUEST", {
|
|
161
|
+
error_description: "jwks_uri must not contain credentials",
|
|
162
|
+
error: "invalid_client"
|
|
163
|
+
});
|
|
164
|
+
if (jwksUri.includes("#")) throw new APIError("BAD_REQUEST", {
|
|
165
|
+
error_description: "jwks_uri must not include a fragment component",
|
|
166
|
+
error: "invalid_client"
|
|
167
|
+
});
|
|
63
168
|
if (isPrivateHostname(parsed.hostname)) throw new APIError("BAD_REQUEST", {
|
|
64
169
|
error_description: "jwks_uri must not point to a private or reserved address",
|
|
65
170
|
error: "invalid_client"
|
|
@@ -71,63 +176,120 @@ function validateJwksUri(ctx, jwksUri, clientIdUrlOrigin) {
|
|
|
71
176
|
});
|
|
72
177
|
}
|
|
73
178
|
function urlClientIdOrigin(clientId) {
|
|
74
|
-
if (!clientId.startsWith("https://") && !clientId.startsWith("http://")) return;
|
|
75
179
|
try {
|
|
76
|
-
|
|
180
|
+
const parsed = new URL(clientId);
|
|
181
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return;
|
|
182
|
+
return parsed.origin;
|
|
77
183
|
} catch {
|
|
78
184
|
return;
|
|
79
185
|
}
|
|
80
186
|
}
|
|
81
|
-
async function
|
|
187
|
+
async function readBoundedResponseBody(response) {
|
|
188
|
+
const contentLength = response.headers.get("content-length");
|
|
189
|
+
if (contentLength !== null && Number.isFinite(Number(contentLength)) && Number(contentLength) > MAX_JWKS_RESPONSE_BYTES) {
|
|
190
|
+
await response.body?.cancel();
|
|
191
|
+
throw new Error("JWKS response exceeds 64 KiB");
|
|
192
|
+
}
|
|
193
|
+
if (!response.body) return "";
|
|
194
|
+
const reader = response.body.getReader();
|
|
195
|
+
const chunks = [];
|
|
196
|
+
let totalBytes = 0;
|
|
197
|
+
while (true) {
|
|
198
|
+
const { done, value } = await reader.read();
|
|
199
|
+
if (done) break;
|
|
200
|
+
totalBytes += value.byteLength;
|
|
201
|
+
if (totalBytes > MAX_JWKS_RESPONSE_BYTES) {
|
|
202
|
+
await reader.cancel();
|
|
203
|
+
throw new Error("JWKS response exceeds 64 KiB");
|
|
204
|
+
}
|
|
205
|
+
chunks.push(value);
|
|
206
|
+
}
|
|
207
|
+
const bytes = new Uint8Array(totalBytes);
|
|
208
|
+
let offset = 0;
|
|
209
|
+
for (const chunk of chunks) {
|
|
210
|
+
bytes.set(chunk, offset);
|
|
211
|
+
offset += chunk.byteLength;
|
|
212
|
+
}
|
|
213
|
+
return new TextDecoder().decode(bytes);
|
|
214
|
+
}
|
|
215
|
+
async function fetchJwksFromUri(jwksUri, fetchClientMetadataResource = globalThis.fetch) {
|
|
82
216
|
const controller = new AbortController();
|
|
83
217
|
const timeout = setTimeout(() => controller.abort(), JWKS_FETCH_TIMEOUT_MS);
|
|
84
218
|
try {
|
|
85
|
-
const response = await
|
|
219
|
+
const response = await fetchClientMetadataResource(jwksUri, {
|
|
86
220
|
signal: controller.signal,
|
|
87
221
|
headers: { accept: "application/json" },
|
|
88
222
|
redirect: "error"
|
|
89
223
|
});
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
224
|
+
if (response.redirected) throw new Error("JWKS fetch redirected");
|
|
225
|
+
if (response.status !== 200) throw new Error(`JWKS fetch returned ${response.status}`);
|
|
226
|
+
const contentType = response.headers.get("content-type");
|
|
227
|
+
if (!contentType || !JSON_CONTENT_TYPE.test(contentType)) throw new Error("JWKS response must use a JSON media type");
|
|
228
|
+
const responseBody = await readBoundedResponseBody(response);
|
|
229
|
+
let parsedBody;
|
|
230
|
+
try {
|
|
231
|
+
parsedBody = JSON.parse(responseBody);
|
|
232
|
+
} catch {
|
|
233
|
+
return { valid: false };
|
|
234
|
+
}
|
|
235
|
+
const result = validatePublicClientJwks(parsedBody);
|
|
236
|
+
if (!result.valid) return { valid: false };
|
|
237
|
+
return {
|
|
238
|
+
valid: true,
|
|
239
|
+
jwks: result.jwks
|
|
240
|
+
};
|
|
94
241
|
} finally {
|
|
95
242
|
clearTimeout(timeout);
|
|
96
243
|
}
|
|
97
244
|
}
|
|
98
|
-
|
|
245
|
+
function createClientJwksFetchError() {
|
|
246
|
+
return new APIError("BAD_REQUEST", {
|
|
247
|
+
error_description: "failed to fetch client JWKS",
|
|
248
|
+
error: "invalid_client"
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
async function fetchClientJwks(ctx, opts, client) {
|
|
99
252
|
if (client.jwks) return JSON.parse(client.jwks);
|
|
100
253
|
if (!client.jwksUri) throw new APIError("BAD_REQUEST", {
|
|
101
254
|
error_description: "client has no JWKS configured",
|
|
102
255
|
error: "invalid_client"
|
|
103
256
|
});
|
|
104
|
-
|
|
257
|
+
const discovery = client.clientDiscoveryId ? getClientDiscoveries(opts).find((candidate) => candidate.id === client.clientDiscoveryId) : void 0;
|
|
258
|
+
if (client.clientDiscoveryId && !discovery?.fetchClientMetadataResource) throw new APIError("BAD_REQUEST", {
|
|
259
|
+
error_description: "client discovery does not provide a metadata resource transport",
|
|
260
|
+
error: "invalid_client"
|
|
261
|
+
});
|
|
262
|
+
validateJwksUri(ctx, client.jwksUri, client.clientDiscoveryId ? urlClientIdOrigin(client.clientId) : void 0);
|
|
105
263
|
const now = Date.now();
|
|
106
|
-
const
|
|
264
|
+
const cacheKey = getJwksCacheKey(client);
|
|
265
|
+
const jwksCache = getJwksCache(opts);
|
|
266
|
+
const cached = jwksCache.get(cacheKey);
|
|
107
267
|
if (cached && now - cached.fetchedAt < JWKS_CACHE_TTL_MS) return cached.jwks;
|
|
268
|
+
let result;
|
|
108
269
|
try {
|
|
109
|
-
|
|
110
|
-
setJwksCache(client.jwksUri, jwks, now);
|
|
111
|
-
return jwks;
|
|
270
|
+
result = await fetchJwksFromUri(client.jwksUri, discovery?.fetchClientMetadataResource);
|
|
112
271
|
} catch {
|
|
113
272
|
const staleLimitMs = JWKS_CACHE_TTL_MS * 2;
|
|
114
273
|
if (cached && now - cached.fetchedAt < staleLimitMs) return cached.jwks;
|
|
115
|
-
throw
|
|
116
|
-
error_description: "failed to fetch client JWKS",
|
|
117
|
-
error: "invalid_client"
|
|
118
|
-
});
|
|
274
|
+
throw createClientJwksFetchError();
|
|
119
275
|
}
|
|
276
|
+
if (!result.valid) throw createClientJwksFetchError();
|
|
277
|
+
setJwksCache(jwksCache, cacheKey, result.jwks, now);
|
|
278
|
+
return result.jwks;
|
|
120
279
|
}
|
|
121
280
|
/**
|
|
122
281
|
* Refetch JWKS from jwks_uri when signature verification fails with cached keys.
|
|
123
282
|
* Handles key rotation: the client may have published a new key that isn't in our cache yet.
|
|
124
283
|
*/
|
|
125
|
-
async function refetchClientJwks(client) {
|
|
284
|
+
async function refetchClientJwks(opts, client) {
|
|
126
285
|
if (!client.jwksUri) return null;
|
|
286
|
+
const discovery = client.clientDiscoveryId ? getClientDiscoveries(opts).find((candidate) => candidate.id === client.clientDiscoveryId) : void 0;
|
|
287
|
+
if (client.clientDiscoveryId && !discovery?.fetchClientMetadataResource) return null;
|
|
127
288
|
try {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
289
|
+
const result = await fetchJwksFromUri(client.jwksUri, discovery?.fetchClientMetadataResource);
|
|
290
|
+
if (!result.valid) return null;
|
|
291
|
+
setJwksCache(getJwksCache(opts), getJwksCacheKey(client), result.jwks, Date.now());
|
|
292
|
+
return result.jwks;
|
|
131
293
|
} catch {
|
|
132
294
|
return null;
|
|
133
295
|
}
|
|
@@ -265,7 +427,7 @@ async function verifyClientAssertion(ctx, opts, clientAssertion, clientAssertion
|
|
|
265
427
|
error_description: "client is not registered for private_key_jwt authentication",
|
|
266
428
|
error: "invalid_client"
|
|
267
429
|
});
|
|
268
|
-
const jwks = await fetchClientJwks(ctx, client);
|
|
430
|
+
const jwks = await fetchClientJwks(ctx, opts, client);
|
|
269
431
|
const audience = expectedAudience ?? `${ctx.context.baseURL}/oauth2/token`;
|
|
270
432
|
const verifyOpts = {
|
|
271
433
|
issuer: clientId,
|
|
@@ -278,7 +440,7 @@ async function verifyClientAssertion(ctx, opts, clientAssertion, clientAssertion
|
|
|
278
440
|
({payload} = await jwtVerify(clientAssertion, createLocalJWKSet(jwks), verifyOpts));
|
|
279
441
|
} catch (verifyErr) {
|
|
280
442
|
if (verifyErr instanceof Error && /no matching key|no applicable key/i.test(verifyErr.message)) {
|
|
281
|
-
const refreshed = await refetchClientJwks(client);
|
|
443
|
+
const refreshed = await refetchClientJwks(opts, client);
|
|
282
444
|
if (refreshed) try {
|
|
283
445
|
({payload} = await jwtVerify(clientAssertion, createLocalJWKSet(refreshed), verifyOpts));
|
|
284
446
|
} catch {
|
|
@@ -304,4 +466,4 @@ async function verifyClientAssertion(ctx, opts, clientAssertion, clientAssertion
|
|
|
304
466
|
return { clientId };
|
|
305
467
|
}
|
|
306
468
|
//#endregion
|
|
307
|
-
export { __exportAll as i, consumeClientAssertion as n, isPrivateHostname as r, client_assertion_exports as t };
|
|
469
|
+
export { __exportAll as a, validatePublicClientJwks as i, consumeClientAssertion as n, isPrivateHostname as r, client_assertion_exports as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as ResourceServerMetadata } from "./oauth-
|
|
1
|
+
import { c as ResourceServerMetadata } from "./oauth-Bi2PA_d1.mjs";
|
|
2
2
|
import { ResourceRequestInput, VerifyAccessTokenRequestOptions } from "better-auth/oauth2";
|
|
3
3
|
import { JWTPayload, JWTVerifyOptions } from "jose";
|
|
4
4
|
import { BetterAuthOptions } from "better-auth/types";
|
|
@@ -72,7 +72,8 @@ type VerifyAccessTokenOutput<T> = T extends undefined ? (token: string | undefin
|
|
|
72
72
|
type VerifyAccessTokenRequestOutput<T> = T extends undefined ? (request: Request | ResourceRequestInput, opts: VerifyAccessTokenRequestNoAuthOpts) => Promise<JWTPayload> : (request: Request | ResourceRequestInput, opts?: VerifyAccessTokenRequestAuthOpts) => Promise<JWTPayload>;
|
|
73
73
|
type VerifyAccessTokenAuthOpts = {
|
|
74
74
|
verifyOptions?: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience">>;
|
|
75
|
-
|
|
75
|
+
requiredScopes?: readonly string[];
|
|
76
|
+
isScopeSatisfied?: VerifyAccessTokenRequestOptions["isScopeSatisfied"];
|
|
76
77
|
jwksUrl?: string;
|
|
77
78
|
remoteVerify?: VerifyAccessTokenRemote;
|
|
78
79
|
/** Maps non-url (ie urn, client) resources to resource_metadata */
|
|
@@ -83,14 +84,16 @@ type VerifyAccessTokenRequestAuthOpts = VerifyAccessTokenAuthOpts & {
|
|
|
83
84
|
};
|
|
84
85
|
type VerifyAccessTokenNoAuthOpts = {
|
|
85
86
|
verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
|
|
86
|
-
|
|
87
|
+
requiredScopes?: readonly string[];
|
|
88
|
+
isScopeSatisfied?: VerifyAccessTokenRequestOptions["isScopeSatisfied"];
|
|
87
89
|
jwksUrl: string;
|
|
88
90
|
remoteVerify?: VerifyAccessTokenRemote;
|
|
89
91
|
/** Maps non-url (ie urn, client) resources to resource_metadata */
|
|
90
92
|
resourceMetadataMappings?: Record<string, string>;
|
|
91
93
|
} | {
|
|
92
94
|
verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
|
|
93
|
-
|
|
95
|
+
requiredScopes?: readonly string[];
|
|
96
|
+
isScopeSatisfied?: VerifyAccessTokenRequestOptions["isScopeSatisfied"];
|
|
94
97
|
jwksUrl?: string;
|
|
95
98
|
remoteVerify: VerifyAccessTokenRemote;
|
|
96
99
|
/** Maps non-url (ie urn, client) resources to resource_metadata */
|
package/dist/client-resource.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
1
|
+
import { o as getJwtPlugin, s as getOAuthProviderPlugin } from "./utils-GbnW6qPl.mjs";
|
|
2
|
+
import { t as PACKAGE_VERSION } from "./version-Do0kTnv7.mjs";
|
|
3
|
+
import { t as createResourceServerChallenge } from "./resource-challenge-CiJTlsEh.mjs";
|
|
3
4
|
import { APIError } from "better-call";
|
|
4
5
|
import { logger } from "@better-auth/core/env";
|
|
5
6
|
import { BetterAuthError } from "@better-auth/core/error";
|
|
@@ -63,10 +64,12 @@ const oauthProviderResourceClient = (auth) => {
|
|
|
63
64
|
if (!token?.length) throw new APIError("UNAUTHORIZED", { message: "missing authorization header" });
|
|
64
65
|
return await verifyBearerToken(token, verifyOptions);
|
|
65
66
|
} catch (error) {
|
|
66
|
-
|
|
67
|
+
const challenge = createResourceServerChallenge(error, verifyOptions.verifyOptions.audience, {
|
|
67
68
|
resourceMetadataMappings: opts?.resourceMetadataMappings,
|
|
68
69
|
dpopSigningAlgorithms: DPOP_SIGNING_ALGORITHMS
|
|
69
70
|
});
|
|
71
|
+
if (challenge) throw challenge;
|
|
72
|
+
throw error;
|
|
70
73
|
}
|
|
71
74
|
}),
|
|
72
75
|
/**
|
|
@@ -80,10 +83,12 @@ const oauthProviderResourceClient = (auth) => {
|
|
|
80
83
|
try {
|
|
81
84
|
return await verifyAccessTokenRequest(toResourceRequestInput(request), verifyOptions);
|
|
82
85
|
} catch (error) {
|
|
83
|
-
|
|
86
|
+
const challenge = createResourceServerChallenge(error, verifyOptions.verifyOptions.audience, {
|
|
84
87
|
resourceMetadataMappings: opts?.resourceMetadataMappings,
|
|
85
88
|
dpopSigningAlgorithms: opts?.dpop?.signingAlgorithms ?? DPOP_SIGNING_ALGORITHMS
|
|
86
89
|
});
|
|
90
|
+
if (challenge) throw challenge;
|
|
91
|
+
throw error;
|
|
87
92
|
}
|
|
88
93
|
}),
|
|
89
94
|
/**
|
package/dist/client.d.mts
CHANGED
package/dist/client.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as buildSignedOAuthQuery } from "./signed-query-BQAwsV_w.mjs";
|
|
2
|
+
import { t as PACKAGE_VERSION } from "./version-Do0kTnv7.mjs";
|
|
2
3
|
import { safeJSONParse } from "@better-auth/core/utils/json";
|
|
3
4
|
//#region src/client.ts
|
|
4
5
|
const oauthProviderClient = () => {
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,45 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { a as OAuthEndpointErrorResult, c as OAuthFieldErrorCode, i as getIssuer, l as OAuthFieldErrorCodeMap, n as getOAuthProviderState, o as OAuthEndpointRedirectContext, r as oauthProvider, s as OAuthErrorCode, t as DEFAULT_OAUTH_SCOPES, u as OAuthRedirectOnError } from "./oauth-
|
|
3
|
-
import {
|
|
1
|
+
import { A as OAuthOpaqueAccessToken, B as Prompt, C as OAuthClientAuthenticationStrategy, D as OAuthExtensionGrantHandler, E as OAuthConsent, F as OAuthResource, G as VerificationValue, H as Scope, I as OAuthResourceInput, J as ResourceUriSchema, K as ClientRegistrationRequest, L as OAuthTokenIssueParams, M as OAuthProviderApi, N as OAuthProviderExtension, O as OAuthExtensionGrantHandlerInput, P as OAuthRefreshToken, R as OAuthTokenResponse, S as OAuthClientAuthenticationResult, T as OAuthClientResource, U as StoreTokenType, V as SchemaClient, W as StoredAuthorizationQuery, Y as oauthClientMetadataSchema, _ as OAuthAuthorizationQuery, a as GrantType, b as OAuthClientAuthenticationInput, c as ResourceServerMetadata, d as ActiveAccessTokenPayload, f as AuthorizePrompt, g as OAuthAuthenticatedClient, h as InitialAccessTokenAuthorization, i as Confirmation, j as OAuthOptions, k as OAuthMetadataExtensionInput, l as TokenEndpointAuthMethod, m as ClientMetadataResourceFetch, n as AuthServerMetadata, o as OAuthClient, p as ClientDiscovery, q as OAuthClientMetadata, r as BearerMethodsSupported, s as OIDCMetadata, t as AuthMethod, u as TokenType, v as OAuthClaimExtensionInput, w as OAuthClientRegistrationResponse, x as OAuthClientAuthenticationRequest, y as OAuthClientAdministrativeResponse, z as OAuthUserInfoExtensionInput } from "./oauth-Bi2PA_d1.mjs";
|
|
2
|
+
import { a as OAuthEndpointErrorResult, c as OAuthFieldErrorCode, i as getIssuer, l as OAuthFieldErrorCodeMap, n as getOAuthProviderState, o as OAuthEndpointRedirectContext, r as oauthProvider, s as OAuthErrorCode, t as DEFAULT_OAUTH_SCOPES, u as OAuthRedirectOnError } from "./oauth-CgWbnA8o.mjs";
|
|
3
|
+
import { APIError } from "better-call";
|
|
4
4
|
import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
|
|
5
|
+
import { BetterAuthPlugin } from "better-auth/types";
|
|
5
6
|
import { AuthContext, GenericEndpointContext } from "@better-auth/core";
|
|
7
|
+
//#region src/device-code.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* RFC 8628 device authorization grant type. A registered OAuth client polls the
|
|
10
|
+
* token endpoint with this `grant_type` to exchange an approved device code for
|
|
11
|
+
* a first-class OAuth token set.
|
|
12
|
+
*/
|
|
13
|
+
declare const DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
14
|
+
/**
|
|
15
|
+
* Bridges the {@link https://datatracker.ietf.org/doc/html/rfc8628 RFC 8628}
|
|
16
|
+
* device authorization grant into the OAuth Provider. Pair it with the
|
|
17
|
+
* `device-authorization` plugin (which owns the `/device/code` request endpoint,
|
|
18
|
+
* the user verification flow, and the `deviceCode` table) and the
|
|
19
|
+
* `oauthProvider` plugin: this registers a `device_code` token grant on
|
|
20
|
+
* `/oauth2/token` that issues real OAuth tokens for a registered OAuth client,
|
|
21
|
+
* and advertises `device_authorization_endpoint` in discovery metadata.
|
|
22
|
+
*
|
|
23
|
+
* First-party device login (the device-authorization plugin's own
|
|
24
|
+
* `/device/token`, which mints a Better Auth session token) keeps working
|
|
25
|
+
* unchanged. To stop a registered OAuth client's device code from being redeemed
|
|
26
|
+
* there for a session token, a `before` hook rejects `/device/token` requests
|
|
27
|
+
* whose `client_id` resolves to a registered OAuth client, directing them to
|
|
28
|
+
* `/oauth2/token`.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const auth = betterAuth({
|
|
33
|
+
* plugins: [
|
|
34
|
+
* deviceAuthorization(),
|
|
35
|
+
* oauthProvider({ ... }),
|
|
36
|
+
* deviceCodeGrant(),
|
|
37
|
+
* ],
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare function deviceCodeGrant(): BetterAuthPlugin;
|
|
42
|
+
//#endregion
|
|
6
43
|
//#region src/extensions.d.ts
|
|
7
44
|
/**
|
|
8
45
|
* Registers an {@link OAuthProviderExtension} with the OAuth Provider plugin
|
|
@@ -17,8 +54,8 @@ import { AuthContext, GenericEndpointContext } from "@better-auth/core";
|
|
|
17
54
|
* twice. It throws if the oauth-provider plugin is not installed, if a grant
|
|
18
55
|
* type or assertion type is not an absolute URI, if a client authentication
|
|
19
56
|
* method reuses a built-in name, or if the extension registers a grant type,
|
|
20
|
-
* auth method,
|
|
21
|
-
* (contributions must be disjoint).
|
|
57
|
+
* auth method, assertion type, or client discovery identifier that another
|
|
58
|
+
* extension already registered (contributions must be disjoint).
|
|
22
59
|
*
|
|
23
60
|
* @example
|
|
24
61
|
* ```ts
|
|
@@ -116,39 +153,37 @@ declare const oauthProviderOpenIdConfigMetadata: <Auth extends {
|
|
|
116
153
|
headers?: HeadersInit;
|
|
117
154
|
}) => (request: Request) => Promise<Response>;
|
|
118
155
|
//#endregion
|
|
119
|
-
//#region src/register.d.ts
|
|
120
|
-
declare function checkOAuthClient(client: OAuthClient, opts: OAuthOptions<Scope[]>, settings?: {
|
|
121
|
-
isRegister?: boolean;
|
|
122
|
-
ctx?: GenericEndpointContext;
|
|
123
|
-
}): Promise<void>;
|
|
124
|
-
/**
|
|
125
|
-
* Converts an OAuth 2.0 Dynamic Client Schema to a Database Schema
|
|
126
|
-
*
|
|
127
|
-
* @param input
|
|
128
|
-
* @returns
|
|
129
|
-
*/
|
|
130
|
-
declare function oauthToSchema(input: OAuthClient): SchemaClient<Scope[]>;
|
|
131
|
-
//#endregion
|
|
132
156
|
//#region src/resource-challenge.d.ts
|
|
133
157
|
/**
|
|
134
|
-
*
|
|
158
|
+
* Create an OAuth resource-server challenge for a failed access-token request.
|
|
135
159
|
*
|
|
136
160
|
* Missing/invalid bearer credentials are reported with RFC 6750 plus the RFC
|
|
137
|
-
* 9728 `resource_metadata` pointer.
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
161
|
+
* 9728 `resource_metadata` pointer. Insufficient-scope failures (built with
|
|
162
|
+
* `createInsufficientScopeError`) are reported with RFC 6750 §3.1's
|
|
163
|
+
* `insufficient_scope` challenge on a 403 naming the scopes the token lacks, so
|
|
164
|
+
* clients can step up their authorization. DPoP-bound-token failures are
|
|
165
|
+
* reported with RFC 9449's `DPoP` challenge so clients know which proof
|
|
166
|
+
* algorithms to use. Non-URL resources (for example a `urn:` or a client id)
|
|
167
|
+
* resolve their metadata URL through `resourceMetadataMappings`.
|
|
141
168
|
*
|
|
142
|
-
*
|
|
169
|
+
* Every other error returns `undefined`, including a plain `FORBIDDEN`: a
|
|
170
|
+
* permission denial that re-authorizing cannot fix must not be answered with a
|
|
171
|
+
* challenge that sends the user through consent for scopes they already hold.
|
|
172
|
+
*
|
|
173
|
+
* @external
|
|
143
174
|
*/
|
|
144
|
-
declare function
|
|
175
|
+
declare function createResourceServerChallenge(error: unknown, resource: string | string[], opts?: {
|
|
145
176
|
/** Maps non-URL (urn, client) resources to their resource_metadata URL. */
|
|
146
177
|
resourceMetadataMappings?: Record<string, string>;
|
|
147
178
|
/** DPoP JWS algorithms to advertise in RFC 9449 challenges. */
|
|
148
179
|
dpopSigningAlgorithms?: readonly string[];
|
|
149
|
-
/**
|
|
150
|
-
|
|
151
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Scopes to advertise in RFC 6750 bearer challenges,
|
|
182
|
+
* hinting what an unauthenticated client should request. An
|
|
183
|
+
* insufficient-scope failure advertises the scopes it names instead.
|
|
184
|
+
*/
|
|
185
|
+
challengeScopes?: readonly string[];
|
|
186
|
+
}): APIError | undefined;
|
|
152
187
|
//#endregion
|
|
153
188
|
//#region src/token.d.ts
|
|
154
189
|
/**
|
|
@@ -195,4 +230,4 @@ declare function consumeClientAssertion(ctx: GenericEndpointContext, opts: OAuth
|
|
|
195
230
|
expectedAudience: string;
|
|
196
231
|
}): Promise<void>;
|
|
197
232
|
//#endregion
|
|
198
|
-
export { type ActiveAccessTokenPayload, type AuthMethod, type AuthServerMetadata, type AuthorizePrompt, type BearerMethodsSupported, type ClientDiscovery, type ClientRegistrationRequest, type Confirmation, DEFAULT_OAUTH_SCOPES, type GrantType, type InitialAccessTokenAuthorization, type OAuthAuthenticatedClient, type OAuthAuthorizationQuery, type OAuthClaimExtensionInput, type OAuthClient, type OAuthClientAuthenticationInput, type OAuthClientAuthenticationRequest, type OAuthClientAuthenticationResult, type OAuthClientAuthenticationStrategy, type OAuthClientResource, type OAuthConsent, type OAuthEndpointErrorResult, type OAuthEndpointRedirectContext, type OAuthErrorCode, type OAuthExtensionGrantHandler, type OAuthExtensionGrantHandlerInput, type OAuthFieldErrorCode, type OAuthFieldErrorCodeMap, type OAuthMetadataExtensionInput, type OAuthOpaqueAccessToken, type OAuthOptions, type OAuthProviderApi, type OAuthProviderExtension, type OAuthRedirectOnError, type OAuthRefreshToken, type OAuthResource, type OAuthResourceInput, type OAuthTokenIssueParams, type OAuthTokenResponse, type OAuthUserInfoExtensionInput, type OIDCMetadata, type Prompt, type ResourceServerMetadata, ResourceUriSchema, type SchemaClient, type Scope, type StoreTokenType, type StoredAuthorizationQuery, type TokenEndpointAuthMethod, type TokenType, type VerificationValue, authServerMetadata,
|
|
233
|
+
export { type ActiveAccessTokenPayload, type AuthMethod, type AuthServerMetadata, type AuthorizePrompt, type BearerMethodsSupported, type ClientDiscovery, type ClientMetadataResourceFetch, type ClientRegistrationRequest, type Confirmation, DEFAULT_OAUTH_SCOPES, DEVICE_CODE_GRANT_TYPE, type GrantType, type InitialAccessTokenAuthorization, type OAuthAuthenticatedClient, type OAuthAuthorizationQuery, type OAuthClaimExtensionInput, type OAuthClient, type OAuthClientAdministrativeResponse, type OAuthClientAuthenticationInput, type OAuthClientAuthenticationRequest, type OAuthClientAuthenticationResult, type OAuthClientAuthenticationStrategy, type OAuthClientMetadata, type OAuthClientRegistrationResponse, type OAuthClientResource, type OAuthConsent, type OAuthEndpointErrorResult, type OAuthEndpointRedirectContext, type OAuthErrorCode, type OAuthExtensionGrantHandler, type OAuthExtensionGrantHandlerInput, type OAuthFieldErrorCode, type OAuthFieldErrorCodeMap, type OAuthMetadataExtensionInput, type OAuthOpaqueAccessToken, type OAuthOptions, type OAuthProviderApi, type OAuthProviderExtension, type OAuthRedirectOnError, type OAuthRefreshToken, type OAuthResource, type OAuthResourceInput, type OAuthTokenIssueParams, type OAuthTokenResponse, type OAuthUserInfoExtensionInput, type OIDCMetadata, type Prompt, type ResourceServerMetadata, ResourceUriSchema, type SchemaClient, type Scope, type StoreTokenType, type StoredAuthorizationQuery, type TokenEndpointAuthMethod, type TokenType, type VerificationValue, authServerMetadata, consumeClientAssertion, createResourceServerChallenge, deviceCodeGrant, extendOAuthProvider, getIssuer, getOAuthProviderApi, getOAuthProviderState, metadataResponse, oauthAuthorizationServerMetadata, oauthClientMetadataSchema, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
|