@better-auth/oauth-provider 1.7.0-beta.0 → 1.7.0-beta.10
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-DZqo-L5j.mjs → client-assertion-D-tAYsKC.mjs} +116 -92
- package/dist/client-resource.d.mts +43 -7
- 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 +151 -18
- package/dist/index.mjs +2239 -1803
- package/dist/introspect-DvHp2a64.mjs +2507 -0
- package/dist/{oauth-Dh4YXCXY.d.mts → oauth-BrNRbP2A.d.mts} +532 -141
- package/dist/oauth-ScTJEcFV.d.mts +2849 -0
- package/dist/resource-challenge-B-cqv4ur.mjs +63 -0
- package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
- package/dist/signed-query-Df1MNiSH.mjs +44 -0
- package/dist/utils-DO8lmoDw.mjs +769 -0
- package/dist/{version-BGWhjYBb.mjs → version-a5HhJg8A.mjs} +1 -1
- package/package.json +9 -10
- package/dist/mcp-CYnz-MXn.mjs +0 -56
- package/dist/oauth-C8aTlaAC.d.mts +0 -1645
- package/dist/utils-CIbcUsZ5.mjs +0 -417
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
|
|
2
|
+
import { a as getClient } from "./utils-DO8lmoDw.mjs";
|
|
3
|
+
import { isPublicRoutableHost } from "@better-auth/core/utils/host";
|
|
2
4
|
import { APIError } from "better-call";
|
|
3
|
-
import {
|
|
5
|
+
import { CLIENT_ASSERTION_TYPE, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS } from "@better-auth/core/oauth2";
|
|
6
|
+
import { base64Url } from "@better-auth/utils/base64";
|
|
7
|
+
import { createHash } from "@better-auth/utils/hash";
|
|
4
8
|
import { createLocalJWKSet, decodeJwt, decodeProtectedHeader, jwtVerify } from "jose";
|
|
5
|
-
//#region \0rolldown/runtime.js
|
|
6
|
-
var __defProp = Object.defineProperty;
|
|
7
|
-
var __exportAll = (all, no_symbols) => {
|
|
8
|
-
let target = {};
|
|
9
|
-
for (var name in all) __defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true
|
|
12
|
-
});
|
|
13
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
14
|
-
return target;
|
|
15
|
-
};
|
|
16
|
-
//#endregion
|
|
17
9
|
//#region src/utils/client-assertion.ts
|
|
18
10
|
var client_assertion_exports = /* @__PURE__ */ __exportAll({
|
|
11
|
+
consumeClientAssertion: () => consumeClientAssertion,
|
|
19
12
|
isPrivateHostname: () => isPrivateHostname,
|
|
20
13
|
verifyClientAssertion: () => verifyClientAssertion
|
|
21
14
|
});
|
|
@@ -33,36 +26,24 @@ function setJwksCache(uri, jwks, fetchedAt) {
|
|
|
33
26
|
if (oldest !== void 0) jwksCache.delete(oldest);
|
|
34
27
|
}
|
|
35
28
|
}
|
|
36
|
-
const ALGORITHMS_LIST = [...
|
|
37
|
-
const pendingAssertionIds = /* @__PURE__ */ new Set();
|
|
29
|
+
const ALGORITHMS_LIST = [...PRIVATE_KEY_JWT_SIGNING_ALGORITHMS];
|
|
38
30
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
31
|
+
* SSRF gate for user-supplied server-side fetch targets (`jwks_uri`,
|
|
32
|
+
* `backchannel_logout_uri`): returns true when the host is NOT publicly
|
|
33
|
+
* routable. That covers loopback, RFC 1918 private, link-local (including AWS
|
|
34
|
+
* IMDS `169.254.169.254`), shared-address-space (carrier-grade NAT),
|
|
35
|
+
* IPv4-mapped IPv6, 6to4/NAT64/Teredo tunnels, every other RFC 6890
|
|
36
|
+
* special-purpose range, and cloud-metadata FQDNs.
|
|
37
|
+
*
|
|
38
|
+
* Delegates to the audited single source of truth so this check cannot drift
|
|
39
|
+
* into the kind of encoding bypass that bespoke regexes invite. This is a
|
|
40
|
+
* syntactic check only: it does not resolve DNS, so a public name that
|
|
41
|
+
* resolves to a private address at fetch time is not caught here.
|
|
41
42
|
*/
|
|
42
|
-
function isPrivateIpv4(hostname) {
|
|
43
|
-
const parts = hostname.split(".");
|
|
44
|
-
if (parts.length !== 4 || parts.some((p) => !/^\d{1,3}$/.test(p))) return false;
|
|
45
|
-
const octets = parts.map(Number);
|
|
46
|
-
const a = octets[0];
|
|
47
|
-
const b = octets[1];
|
|
48
|
-
return a === 10 || a === 0 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254 || a === 127;
|
|
49
|
-
}
|
|
50
43
|
function isPrivateHostname(hostname) {
|
|
51
|
-
|
|
52
|
-
const host = lower.startsWith("[") && lower.endsWith("]") ? lower.slice(1, -1) : lower;
|
|
53
|
-
if (host === "localhost" || host === "::1") return true;
|
|
54
|
-
if (isPrivateIpv4(host)) return true;
|
|
55
|
-
if (host.includes(":")) {
|
|
56
|
-
const v4MappedMatch = host.match(/^(?:0{0,4}:){0,4}:?(?:0{0,4}:)?ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
|
|
57
|
-
if (v4MappedMatch && isPrivateIpv4(v4MappedMatch[1])) return true;
|
|
58
|
-
const isLinkLocal = host.startsWith("fe8") || host.startsWith("fe9") || host.startsWith("fea") || host.startsWith("feb");
|
|
59
|
-
const isUniqueLocal = host.startsWith("fc") || host.startsWith("fd");
|
|
60
|
-
if (isLinkLocal || isUniqueLocal) return true;
|
|
61
|
-
}
|
|
62
|
-
if (host === "metadata.google.internal") return true;
|
|
63
|
-
return false;
|
|
44
|
+
return !isPublicRoutableHost(hostname);
|
|
64
45
|
}
|
|
65
|
-
function validateJwksUri(ctx, jwksUri) {
|
|
46
|
+
function validateJwksUri(ctx, jwksUri, clientIdUrlOrigin) {
|
|
66
47
|
const parsed = new URL(jwksUri);
|
|
67
48
|
if (parsed.protocol !== "https:") throw new APIError("BAD_REQUEST", {
|
|
68
49
|
error_description: "jwks_uri must use HTTPS",
|
|
@@ -72,11 +53,20 @@ function validateJwksUri(ctx, jwksUri) {
|
|
|
72
53
|
error_description: "jwks_uri must not point to a private or reserved address",
|
|
73
54
|
error: "invalid_client"
|
|
74
55
|
});
|
|
56
|
+
if (clientIdUrlOrigin && parsed.origin === clientIdUrlOrigin) return;
|
|
75
57
|
if (!ctx.context.isTrustedOrigin(parsed.href)) throw new APIError("BAD_REQUEST", {
|
|
76
58
|
error_description: "client jwks_uri is not trusted",
|
|
77
59
|
error: "invalid_client"
|
|
78
60
|
});
|
|
79
61
|
}
|
|
62
|
+
function urlClientIdOrigin(clientId) {
|
|
63
|
+
if (!clientId.startsWith("https://") && !clientId.startsWith("http://")) return;
|
|
64
|
+
try {
|
|
65
|
+
return new URL(clientId).origin;
|
|
66
|
+
} catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
80
70
|
async function fetchJwksFromUri(jwksUri) {
|
|
81
71
|
const controller = new AbortController();
|
|
82
72
|
const timeout = setTimeout(() => controller.abort(), JWKS_FETCH_TIMEOUT_MS);
|
|
@@ -100,7 +90,7 @@ async function fetchClientJwks(ctx, client) {
|
|
|
100
90
|
error_description: "client has no JWKS configured",
|
|
101
91
|
error: "invalid_client"
|
|
102
92
|
});
|
|
103
|
-
validateJwksUri(ctx, client.jwksUri);
|
|
93
|
+
validateJwksUri(ctx, client.jwksUri, urlClientIdOrigin(client.clientId));
|
|
104
94
|
const now = Date.now();
|
|
105
95
|
const cached = jwksCache.get(client.jwksUri);
|
|
106
96
|
if (cached && now - cached.fetchedAt < JWKS_CACHE_TTL_MS) return cached.jwks;
|
|
@@ -132,6 +122,84 @@ async function refetchClientJwks(client) {
|
|
|
132
122
|
}
|
|
133
123
|
}
|
|
134
124
|
/**
|
|
125
|
+
* Enforces the assertion-hygiene claims every client-assertion authentication
|
|
126
|
+
* method must check, independent of how the signature is verified or where the
|
|
127
|
+
* verification keys come from:
|
|
128
|
+
* - `aud` MUST include `expectedAudience` (RFC 7523 §3 rule 3),
|
|
129
|
+
* - `exp` MUST be present, unexpired, and at most `assertionMaxLifetime`
|
|
130
|
+
* seconds away (RFC 7523 §3 rule 4),
|
|
131
|
+
* - `iat`, when present, MUST be within `assertionMaxLifetime`,
|
|
132
|
+
* - `jti` MUST be present and single-use; this consumes a replay tombstone keyed
|
|
133
|
+
* by `` `${namespace}:${jti}` ``, inserted under the adapter's primary key so a
|
|
134
|
+
* replay across workers fails atomically.
|
|
135
|
+
*
|
|
136
|
+
* A custom {@link OAuthClientAuthenticationStrategy} should call this after
|
|
137
|
+
* verifying the assertion signature, so an extension method inherits the same
|
|
138
|
+
* replay, lifetime, and audience guarantees as the built-in `private_key_jwt`
|
|
139
|
+
* path, which calls it too.
|
|
140
|
+
*
|
|
141
|
+
* @param params.namespace Scopes the replay tombstone to the method and client,
|
|
142
|
+
* e.g. `` `${method}:${clientId}` ``, so the same `jti` can recur across
|
|
143
|
+
* distinct methods or clients but never within one.
|
|
144
|
+
*/
|
|
145
|
+
async function consumeClientAssertion(ctx, opts, params) {
|
|
146
|
+
const { namespace, payload, expectedAudience } = params;
|
|
147
|
+
if (!(Array.isArray(payload.aud) ? payload.aud : payload.aud != null ? [payload.aud] : []).includes(expectedAudience)) throw new APIError("BAD_REQUEST", {
|
|
148
|
+
error_description: "client assertion aud does not match the endpoint",
|
|
149
|
+
error: "invalid_client"
|
|
150
|
+
});
|
|
151
|
+
const maxLifetime = opts.assertionMaxLifetime ?? 300;
|
|
152
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
153
|
+
if (typeof payload.exp !== "number") throw new APIError("BAD_REQUEST", {
|
|
154
|
+
error_description: "client assertion must include exp claim",
|
|
155
|
+
error: "invalid_client"
|
|
156
|
+
});
|
|
157
|
+
if (payload.exp <= now) throw new APIError("BAD_REQUEST", {
|
|
158
|
+
error_description: "client assertion has expired",
|
|
159
|
+
error: "invalid_client"
|
|
160
|
+
});
|
|
161
|
+
if (payload.exp - now > maxLifetime) throw new APIError("BAD_REQUEST", {
|
|
162
|
+
error_description: `client assertion exp is too far in the future (max ${maxLifetime}s)`,
|
|
163
|
+
error: "invalid_client"
|
|
164
|
+
});
|
|
165
|
+
if (typeof payload.iat === "number" && now - payload.iat > maxLifetime) throw new APIError("BAD_REQUEST", {
|
|
166
|
+
error_description: `client assertion iat is too far in the past (max ${maxLifetime}s)`,
|
|
167
|
+
error: "invalid_client"
|
|
168
|
+
});
|
|
169
|
+
if (typeof payload.jti !== "string" || payload.jti.length === 0) throw new APIError("BAD_REQUEST", {
|
|
170
|
+
error_description: "client assertion must include jti claim",
|
|
171
|
+
error: "invalid_client"
|
|
172
|
+
});
|
|
173
|
+
const jtiDigest = await createHash("SHA-256").digest(new TextEncoder().encode(`${namespace}:${payload.jti}`));
|
|
174
|
+
const jtiId = base64Url.encode(new Uint8Array(jtiDigest).slice(0, 24), { padding: false });
|
|
175
|
+
try {
|
|
176
|
+
await ctx.context.adapter.create({
|
|
177
|
+
model: "oauthClientAssertion",
|
|
178
|
+
data: {
|
|
179
|
+
id: jtiId,
|
|
180
|
+
expiresAt: /* @__PURE__ */ new Date(payload.exp * 1e3)
|
|
181
|
+
},
|
|
182
|
+
forceAllowId: true
|
|
183
|
+
});
|
|
184
|
+
} catch (createErr) {
|
|
185
|
+
let alreadyUsed = false;
|
|
186
|
+
try {
|
|
187
|
+
alreadyUsed = Boolean(await ctx.context.adapter.findOne({
|
|
188
|
+
model: "oauthClientAssertion",
|
|
189
|
+
where: [{
|
|
190
|
+
field: "id",
|
|
191
|
+
value: jtiId
|
|
192
|
+
}]
|
|
193
|
+
}));
|
|
194
|
+
} catch {}
|
|
195
|
+
if (alreadyUsed) throw new APIError("BAD_REQUEST", {
|
|
196
|
+
error_description: "client assertion jti has already been used",
|
|
197
|
+
error: "invalid_client"
|
|
198
|
+
});
|
|
199
|
+
throw createErr;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
135
203
|
* Verifies a client assertion JWT for `private_key_jwt` authentication.
|
|
136
204
|
*
|
|
137
205
|
* Validates: signature, iss=client_id, sub=client_id, aud=token_endpoint,
|
|
@@ -151,7 +219,7 @@ async function verifyClientAssertion(ctx, opts, clientAssertion, clientAssertion
|
|
|
151
219
|
error: "invalid_client"
|
|
152
220
|
});
|
|
153
221
|
}
|
|
154
|
-
if (!header.alg || !
|
|
222
|
+
if (!header.alg || !ALGORITHMS_LIST.includes(header.alg)) throw new APIError("BAD_REQUEST", {
|
|
155
223
|
error_description: `unsupported assertion signing algorithm: ${header.alg}`,
|
|
156
224
|
error: "invalid_client"
|
|
157
225
|
});
|
|
@@ -188,7 +256,6 @@ async function verifyClientAssertion(ctx, opts, clientAssertion, clientAssertion
|
|
|
188
256
|
});
|
|
189
257
|
const jwks = await fetchClientJwks(ctx, client);
|
|
190
258
|
const audience = expectedAudience ?? `${ctx.context.baseURL}/oauth2/token`;
|
|
191
|
-
const maxLifetime = opts.assertionMaxLifetime ?? 300;
|
|
192
259
|
const verifyOpts = {
|
|
193
260
|
issuer: clientId,
|
|
194
261
|
subject: clientId,
|
|
@@ -218,55 +285,12 @@ async function verifyClientAssertion(ctx, opts, clientAssertion, clientAssertion
|
|
|
218
285
|
error: "invalid_client"
|
|
219
286
|
});
|
|
220
287
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
288
|
+
await consumeClientAssertion(ctx, opts, {
|
|
289
|
+
namespace: `private_key_jwt:${clientId}`,
|
|
290
|
+
payload,
|
|
291
|
+
expectedAudience: audience
|
|
225
292
|
});
|
|
226
|
-
|
|
227
|
-
error_description: `client assertion exp is too far in the future (max ${maxLifetime}s)`,
|
|
228
|
-
error: "invalid_client"
|
|
229
|
-
});
|
|
230
|
-
if (typeof payload.iat === "number" && now - payload.iat > maxLifetime) throw new APIError("BAD_REQUEST", {
|
|
231
|
-
error_description: `client assertion iat is too far in the past (max ${maxLifetime}s)`,
|
|
232
|
-
error: "invalid_client"
|
|
233
|
-
});
|
|
234
|
-
if (!payload.jti) throw new APIError("BAD_REQUEST", {
|
|
235
|
-
error_description: "client assertion must include jti claim",
|
|
236
|
-
error: "invalid_client"
|
|
237
|
-
});
|
|
238
|
-
const jtiIdentifier = `private_key_jwt:${clientId}:${payload.jti}`;
|
|
239
|
-
if (pendingAssertionIds.has(jtiIdentifier)) throw new APIError("BAD_REQUEST", {
|
|
240
|
-
error_description: "client assertion jti has already been used",
|
|
241
|
-
error: "invalid_client"
|
|
242
|
-
});
|
|
243
|
-
pendingAssertionIds.add(jtiIdentifier);
|
|
244
|
-
try {
|
|
245
|
-
if (await ctx.context.internalAdapter.findVerificationValue(jtiIdentifier)) throw new APIError("BAD_REQUEST", {
|
|
246
|
-
error_description: "client assertion jti has already been used",
|
|
247
|
-
error: "invalid_client"
|
|
248
|
-
});
|
|
249
|
-
const jtiExpiry = /* @__PURE__ */ new Date(payload.exp * 1e3);
|
|
250
|
-
try {
|
|
251
|
-
await ctx.context.internalAdapter.createVerificationValue({
|
|
252
|
-
identifier: jtiIdentifier,
|
|
253
|
-
value: clientId,
|
|
254
|
-
expiresAt: jtiExpiry
|
|
255
|
-
});
|
|
256
|
-
} catch (createErr) {
|
|
257
|
-
if (await ctx.context.internalAdapter.findVerificationValue(jtiIdentifier)) throw new APIError("BAD_REQUEST", {
|
|
258
|
-
error_description: "client assertion jti has already been used",
|
|
259
|
-
error: "invalid_client"
|
|
260
|
-
});
|
|
261
|
-
throw createErr;
|
|
262
|
-
}
|
|
263
|
-
} finally {
|
|
264
|
-
pendingAssertionIds.delete(jtiIdentifier);
|
|
265
|
-
}
|
|
266
|
-
return {
|
|
267
|
-
clientId,
|
|
268
|
-
client
|
|
269
|
-
};
|
|
293
|
+
return { clientId };
|
|
270
294
|
}
|
|
271
295
|
//#endregion
|
|
272
|
-
export {
|
|
296
|
+
export { consumeClientAssertion as n, isPrivateHostname as r, client_assertion_exports as t };
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as ResourceServerMetadata } from "./oauth-ScTJEcFV.mjs";
|
|
2
|
+
import { ResourceRequestInput, VerifyAccessTokenRequestOptions } from "better-auth/oauth2";
|
|
2
3
|
import { JWTPayload, JWTVerifyOptions } from "jose";
|
|
3
|
-
import {
|
|
4
|
+
import { BetterAuthOptions } from "better-auth/types";
|
|
4
5
|
|
|
5
6
|
//#region src/client-resource.d.ts
|
|
6
|
-
|
|
7
|
+
type ResourceClientAuth = {
|
|
8
|
+
options: {
|
|
9
|
+
baseURL?: BetterAuthOptions["baseURL"];
|
|
10
|
+
basePath?: BetterAuthOptions["basePath"];
|
|
11
|
+
};
|
|
12
|
+
$context: Promise<unknown>;
|
|
13
|
+
};
|
|
14
|
+
declare const oauthProviderResourceClient: <T extends ResourceClientAuth | undefined = undefined>(auth?: T) => {
|
|
7
15
|
id: "oauth-provider-resource-client";
|
|
8
16
|
version: string;
|
|
9
17
|
getActions(): {
|
|
@@ -15,7 +23,14 @@ declare const oauthProviderResourceClient: <T extends Auth | undefined>(auth?: T
|
|
|
15
23
|
*
|
|
16
24
|
* The optional auth parameter can fill known values automatically.
|
|
17
25
|
*/
|
|
18
|
-
|
|
26
|
+
verifyBearerToken: VerifyAccessTokenOutput<T>;
|
|
27
|
+
/**
|
|
28
|
+
* Performs verification of a protected-resource request. Use this for
|
|
29
|
+
* new resource-server integrations so sender-constrained DPoP access
|
|
30
|
+
* tokens are enforced with the request method, URL, Authorization
|
|
31
|
+
* scheme, DPoP proof, `ath`, and `cnf.jkt` binding.
|
|
32
|
+
*/
|
|
33
|
+
verifyAccessTokenRequest: VerifyAccessTokenRequestOutput<T>;
|
|
19
34
|
/**
|
|
20
35
|
* An authorization server does not typically publish
|
|
21
36
|
* the `/.well-known/oauth-protected-resource` themselves.
|
|
@@ -42,8 +57,23 @@ interface VerifyAccessTokenRemote {
|
|
|
42
57
|
* is also still active.
|
|
43
58
|
*/
|
|
44
59
|
force?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Accept introspection responses that omit the `aud` claim even when a
|
|
62
|
+
* required `audience` is configured in `verifyOptions`.
|
|
63
|
+
*
|
|
64
|
+
* By default verification fails closed: if you configure an `audience` and
|
|
65
|
+
* the introspection response has no `aud` (or a mismatching one), the token
|
|
66
|
+
* is rejected. Some authorization servers legitimately omit `aud` from
|
|
67
|
+
* introspection responses (it is OPTIONAL per RFC 7662 §2.2); only enable
|
|
68
|
+
* this if you trust the issuer to bind the token to this resource through
|
|
69
|
+
* another mechanism, as it skips the audience check in that case.
|
|
70
|
+
*
|
|
71
|
+
* @default false
|
|
72
|
+
*/
|
|
73
|
+
allowMissingAudience?: boolean;
|
|
45
74
|
}
|
|
46
|
-
type VerifyAccessTokenOutput<T> = T extends
|
|
75
|
+
type VerifyAccessTokenOutput<T> = T extends undefined ? (token: string | undefined, opts: VerifyAccessTokenNoAuthOpts) => Promise<JWTPayload> : (token: string | undefined, opts?: VerifyAccessTokenAuthOpts) => Promise<JWTPayload>;
|
|
76
|
+
type VerifyAccessTokenRequestOutput<T> = T extends undefined ? (request: Request | ResourceRequestInput, opts: VerifyAccessTokenRequestNoAuthOpts) => Promise<JWTPayload> : (request: Request | ResourceRequestInput, opts?: VerifyAccessTokenRequestAuthOpts) => Promise<JWTPayload>;
|
|
47
77
|
type VerifyAccessTokenAuthOpts = {
|
|
48
78
|
verifyOptions?: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience">>;
|
|
49
79
|
scopes?: string[];
|
|
@@ -51,6 +81,9 @@ type VerifyAccessTokenAuthOpts = {
|
|
|
51
81
|
remoteVerify?: VerifyAccessTokenRemote; /** Maps non-url (ie urn, client) resources to resource_metadata */
|
|
52
82
|
resourceMetadataMappings?: Record<string, string>;
|
|
53
83
|
};
|
|
84
|
+
type VerifyAccessTokenRequestAuthOpts = VerifyAccessTokenAuthOpts & {
|
|
85
|
+
dpop?: VerifyAccessTokenRequestOptions["dpop"];
|
|
86
|
+
};
|
|
54
87
|
type VerifyAccessTokenNoAuthOpts = {
|
|
55
88
|
verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
|
|
56
89
|
scopes?: string[];
|
|
@@ -64,12 +97,15 @@ type VerifyAccessTokenNoAuthOpts = {
|
|
|
64
97
|
remoteVerify: VerifyAccessTokenRemote; /** Maps non-url (ie urn, client) resources to resource_metadata */
|
|
65
98
|
resourceMetadataMappings?: Record<string, string>;
|
|
66
99
|
};
|
|
67
|
-
type
|
|
100
|
+
type VerifyAccessTokenRequestNoAuthOpts = VerifyAccessTokenNoAuthOpts & {
|
|
101
|
+
dpop?: VerifyAccessTokenRequestOptions["dpop"];
|
|
102
|
+
};
|
|
103
|
+
type ProtectedResourceMetadataOutput<T> = T extends undefined ? (overrides: ResourceServerMetadata, opts?: {
|
|
68
104
|
silenceWarnings?: {
|
|
69
105
|
oidcScopes?: boolean;
|
|
70
106
|
};
|
|
71
107
|
externalScopes?: string[];
|
|
72
|
-
}) => Promise<ResourceServerMetadata> : (overrides
|
|
108
|
+
}) => Promise<ResourceServerMetadata> : (overrides?: Partial<ResourceServerMetadata>, opts?: {
|
|
73
109
|
silenceWarnings?: {
|
|
74
110
|
oidcScopes?: boolean;
|
|
75
111
|
};
|
package/dist/client-resource.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { t as
|
|
4
|
-
import { verifyAccessToken } from "better-auth/oauth2";
|
|
1
|
+
import { o as getJwtPlugin, s as getOAuthProviderPlugin } from "./utils-DO8lmoDw.mjs";
|
|
2
|
+
import { t as PACKAGE_VERSION } from "./version-a5HhJg8A.mjs";
|
|
3
|
+
import { t as raiseResourceServerChallenge } from "./resource-challenge-B-cqv4ur.mjs";
|
|
5
4
|
import { APIError } from "better-call";
|
|
6
5
|
import { logger } from "@better-auth/core/env";
|
|
7
6
|
import { BetterAuthError } from "@better-auth/core/error";
|
|
7
|
+
import { DPOP_SIGNING_ALGORITHMS, requestToResourceInput, verifyAccessTokenRequest, verifyBearerToken } from "better-auth/oauth2";
|
|
8
8
|
//#region src/client-resource.ts
|
|
9
9
|
const oauthProviderResourceClient = (auth) => {
|
|
10
10
|
let oauthProviderPlugin;
|
|
@@ -22,36 +22,55 @@ const oauthProviderResourceClient = (auth) => {
|
|
|
22
22
|
return (await getJwtPluginOptions())?.jwt?.issuer ?? authServerBaseUrl;
|
|
23
23
|
};
|
|
24
24
|
const authServerBasePath = auth?.options.basePath;
|
|
25
|
+
const resolveVerifyAccessTokenOptions = async (opts) => {
|
|
26
|
+
const jwtPluginOptions = await getJwtPluginOptions();
|
|
27
|
+
const audience = opts?.verifyOptions?.audience ?? authServerBaseUrl;
|
|
28
|
+
const issuer = opts?.verifyOptions?.issuer ?? jwtPluginOptions?.jwt?.issuer ?? authServerBaseUrl;
|
|
29
|
+
if (!audience) throw Error("please define opts.verifyOptions.audience");
|
|
30
|
+
if (!issuer) throw Error("please define opts.verifyOptions.issuer");
|
|
31
|
+
const jwksUrl = opts?.jwksUrl ?? jwtPluginOptions?.jwks?.remoteUrl ?? (authServerBaseUrl ? `${authServerBaseUrl + (authServerBasePath ?? "")}${jwtPluginOptions?.jwks?.jwksPath ?? "/jwks"}` : void 0);
|
|
32
|
+
const introspectUrl = opts?.remoteVerify?.introspectUrl ?? (authServerBaseUrl ? `${authServerBaseUrl}${authServerBasePath ?? ""}/oauth2/introspect` : void 0);
|
|
33
|
+
return {
|
|
34
|
+
...opts,
|
|
35
|
+
jwksUrl,
|
|
36
|
+
verifyOptions: {
|
|
37
|
+
...opts?.verifyOptions,
|
|
38
|
+
audience,
|
|
39
|
+
issuer
|
|
40
|
+
},
|
|
41
|
+
remoteVerify: opts?.remoteVerify && introspectUrl ? {
|
|
42
|
+
...opts.remoteVerify,
|
|
43
|
+
introspectUrl
|
|
44
|
+
} : void 0
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
const toResourceRequestInput = (request) => typeof request.headers?.get === "function" ? requestToResourceInput(request) : request;
|
|
25
48
|
return {
|
|
26
49
|
id: "oauth-provider-resource-client",
|
|
27
50
|
version: PACKAGE_VERSION,
|
|
28
51
|
getActions() {
|
|
29
52
|
return {
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const audience = opts?.verifyOptions?.audience ?? authServerBaseUrl;
|
|
33
|
-
const issuer = opts?.verifyOptions?.issuer ?? jwtPluginOptions?.jwt?.issuer ?? authServerBaseUrl;
|
|
34
|
-
if (!audience) throw Error("please define opts.verifyOptions.audience");
|
|
35
|
-
if (!issuer) throw Error("please define opts.verifyOptions.issuer");
|
|
36
|
-
const jwksUrl = opts?.jwksUrl ?? jwtPluginOptions?.jwks?.remoteUrl ?? (authServerBaseUrl ? `${authServerBaseUrl + (authServerBasePath ?? "")}${jwtPluginOptions?.jwks?.jwksPath ?? "/jwks"}` : void 0);
|
|
37
|
-
const introspectUrl = opts?.remoteVerify?.introspectUrl ?? (authServerBaseUrl ? `${authServerBaseUrl}${authServerBasePath ?? ""}/oauth2/introspect` : void 0);
|
|
53
|
+
verifyBearerToken: (async (token, opts) => {
|
|
54
|
+
const verifyOptions = await resolveVerifyAccessTokenOptions(opts);
|
|
38
55
|
try {
|
|
39
56
|
if (!token?.length) throw new APIError("UNAUTHORIZED", { message: "missing authorization header" });
|
|
40
|
-
return await
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
audience,
|
|
46
|
-
issuer
|
|
47
|
-
},
|
|
48
|
-
remoteVerify: opts?.remoteVerify && introspectUrl ? {
|
|
49
|
-
...opts.remoteVerify,
|
|
50
|
-
introspectUrl
|
|
51
|
-
} : void 0
|
|
57
|
+
return await verifyBearerToken(token, verifyOptions);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
raiseResourceServerChallenge(error, verifyOptions.verifyOptions.audience, {
|
|
60
|
+
resourceMetadataMappings: opts?.resourceMetadataMappings,
|
|
61
|
+
dpopSigningAlgorithms: DPOP_SIGNING_ALGORITHMS
|
|
52
62
|
});
|
|
63
|
+
}
|
|
64
|
+
}),
|
|
65
|
+
verifyAccessTokenRequest: (async (request, opts) => {
|
|
66
|
+
const verifyOptions = await resolveVerifyAccessTokenOptions(opts);
|
|
67
|
+
try {
|
|
68
|
+
return await verifyAccessTokenRequest(toResourceRequestInput(request), verifyOptions);
|
|
53
69
|
} catch (error) {
|
|
54
|
-
|
|
70
|
+
raiseResourceServerChallenge(error, verifyOptions.verifyOptions.audience, {
|
|
71
|
+
resourceMetadataMappings: opts?.resourceMetadataMappings,
|
|
72
|
+
dpopSigningAlgorithms: opts?.dpop?.signingAlgorithms ?? DPOP_SIGNING_ALGORITHMS
|
|
73
|
+
});
|
|
55
74
|
}
|
|
56
75
|
}),
|
|
57
76
|
getProtectedResourceMetadata: (async (overrides, opts) => {
|
|
@@ -78,6 +97,7 @@ const oauthProviderResourceClient = (auth) => {
|
|
|
78
97
|
return {
|
|
79
98
|
resource,
|
|
80
99
|
authorization_servers: authorizationServer ? [authorizationServer] : void 0,
|
|
100
|
+
dpop_signing_alg_values_supported: [...oauthProviderOptions?.dpop?.signingAlgorithms ?? DPOP_SIGNING_ALGORITHMS],
|
|
81
101
|
...overrides
|
|
82
102
|
};
|
|
83
103
|
})
|
package/dist/client.d.mts
CHANGED
package/dist/client.mjs
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
import { t as
|
|
1
|
+
import { t as buildSignedOAuthQuery } from "./signed-query-Df1MNiSH.mjs";
|
|
2
|
+
import { t as PACKAGE_VERSION } from "./version-a5HhJg8A.mjs";
|
|
2
3
|
import { safeJSONParse } from "@better-auth/core/utils/json";
|
|
3
4
|
//#region src/client.ts
|
|
4
|
-
function parseSignedQuery(search) {
|
|
5
|
-
const params = new URLSearchParams(search);
|
|
6
|
-
if (params.has("sig")) {
|
|
7
|
-
const signedParams = new URLSearchParams();
|
|
8
|
-
for (const [key, value] of params.entries()) {
|
|
9
|
-
signedParams.append(key, value);
|
|
10
|
-
if (key === "sig") break;
|
|
11
|
-
}
|
|
12
|
-
return signedParams.toString();
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
5
|
const oauthProviderClient = () => {
|
|
16
6
|
return {
|
|
17
7
|
id: "oauth-provider-client",
|
|
@@ -26,7 +16,7 @@ const oauthProviderClient = () => {
|
|
|
26
16
|
if (body?.oauth_query) return;
|
|
27
17
|
if (typeof window !== "undefined" && window?.location?.search && !(ctx.method === "GET" || ctx.method === "DELETE")) ctx.body = JSON.stringify({
|
|
28
18
|
...body,
|
|
29
|
-
oauth_query:
|
|
19
|
+
oauth_query: buildSignedOAuthQuery(window.location.search)
|
|
30
20
|
});
|
|
31
21
|
} }
|
|
32
22
|
}],
|