@better-auth/oauth-provider 1.7.0-beta.4 → 1.7.0-beta.5
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-DmT1B6_6.mjs} +39 -48
- package/dist/client-resource.d.mts +15 -1
- package/dist/client-resource.mjs +2 -2
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +600 -279
- package/dist/{oauth-q7dn10NU.d.mts → oauth-BXrYl5x6.d.mts} +93 -0
- package/dist/{oauth-Vt3lTNHX.d.mts → oauth-DU6NeviY.d.mts} +106 -34
- package/dist/{utils-DKBWQ8fe.mjs → utils-D2dLqo7f.mjs} +33 -3
- package/dist/{version-nFnRm-a3.mjs → version-B1ZiRmxj.mjs} +1 -1
- package/package.json +7 -7
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { o as getClient } from "./utils-D2dLqo7f.mjs";
|
|
2
2
|
import { APIError } from "better-call";
|
|
3
3
|
import { CLIENT_ASSERTION_TYPE, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS } from "@better-auth/core/oauth2";
|
|
4
|
+
import { isPublicRoutableHost } from "@better-auth/core/utils/host";
|
|
5
|
+
import { base64Url } from "@better-auth/utils/base64";
|
|
6
|
+
import { createHash } from "@better-auth/utils/hash";
|
|
4
7
|
import { createLocalJWKSet, decodeJwt, decodeProtectedHeader, jwtVerify } from "jose";
|
|
5
8
|
//#region \0rolldown/runtime.js
|
|
6
9
|
var __defProp = Object.defineProperty;
|
|
@@ -34,33 +37,21 @@ function setJwksCache(uri, jwks, fetchedAt) {
|
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
39
|
const ALGORITHMS_LIST = [...PRIVATE_KEY_JWT_SIGNING_ALGORITHMS];
|
|
37
|
-
const pendingAssertionIds = /* @__PURE__ */ new Set();
|
|
38
40
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
+
* SSRF gate for user-supplied server-side fetch targets (`jwks_uri`,
|
|
42
|
+
* `backchannel_logout_uri`): returns true when the host is NOT publicly
|
|
43
|
+
* routable. That covers loopback, RFC 1918 private, link-local (including AWS
|
|
44
|
+
* IMDS `169.254.169.254`), shared-address-space (carrier-grade NAT),
|
|
45
|
+
* IPv4-mapped IPv6, 6to4/NAT64/Teredo tunnels, every other RFC 6890
|
|
46
|
+
* special-purpose range, and cloud-metadata FQDNs.
|
|
47
|
+
*
|
|
48
|
+
* Delegates to the audited single source of truth so this check cannot drift
|
|
49
|
+
* into the kind of encoding bypass that bespoke regexes invite. This is a
|
|
50
|
+
* syntactic check only: it does not resolve DNS, so a public name that
|
|
51
|
+
* resolves to a private address at fetch time is not caught here.
|
|
41
52
|
*/
|
|
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
53
|
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;
|
|
54
|
+
return !isPublicRoutableHost(hostname);
|
|
64
55
|
}
|
|
65
56
|
function validateJwksUri(ctx, jwksUri, clientIdUrlOrigin) {
|
|
66
57
|
const parsed = new URL(jwksUri);
|
|
@@ -244,33 +235,33 @@ async function verifyClientAssertion(ctx, opts, clientAssertion, clientAssertion
|
|
|
244
235
|
error_description: "client assertion must include jti claim",
|
|
245
236
|
error: "invalid_client"
|
|
246
237
|
});
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
error_description: "client assertion jti has already been used",
|
|
250
|
-
error: "invalid_client"
|
|
251
|
-
});
|
|
252
|
-
pendingAssertionIds.add(jtiIdentifier);
|
|
238
|
+
const jtiDigest = await createHash("SHA-256").digest(new TextEncoder().encode(`private_key_jwt:${clientId}:${payload.jti}`));
|
|
239
|
+
const jtiId = base64Url.encode(new Uint8Array(jtiDigest).slice(0, 24), { padding: false });
|
|
253
240
|
try {
|
|
254
|
-
|
|
241
|
+
await ctx.context.adapter.create({
|
|
242
|
+
model: "oauthClientAssertion",
|
|
243
|
+
data: {
|
|
244
|
+
id: jtiId,
|
|
245
|
+
expiresAt: /* @__PURE__ */ new Date(payload.exp * 1e3)
|
|
246
|
+
},
|
|
247
|
+
forceAllowId: true
|
|
248
|
+
});
|
|
249
|
+
} catch (createErr) {
|
|
250
|
+
let alreadyUsed = false;
|
|
251
|
+
try {
|
|
252
|
+
alreadyUsed = Boolean(await ctx.context.adapter.findOne({
|
|
253
|
+
model: "oauthClientAssertion",
|
|
254
|
+
where: [{
|
|
255
|
+
field: "id",
|
|
256
|
+
value: jtiId
|
|
257
|
+
}]
|
|
258
|
+
}));
|
|
259
|
+
} catch {}
|
|
260
|
+
if (alreadyUsed) throw new APIError("BAD_REQUEST", {
|
|
255
261
|
error_description: "client assertion jti has already been used",
|
|
256
262
|
error: "invalid_client"
|
|
257
263
|
});
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
await ctx.context.internalAdapter.createVerificationValue({
|
|
261
|
-
identifier: jtiIdentifier,
|
|
262
|
-
value: clientId,
|
|
263
|
-
expiresAt: jtiExpiry
|
|
264
|
-
});
|
|
265
|
-
} catch (createErr) {
|
|
266
|
-
if (await ctx.context.internalAdapter.findVerificationValue(jtiIdentifier)) throw new APIError("BAD_REQUEST", {
|
|
267
|
-
error_description: "client assertion jti has already been used",
|
|
268
|
-
error: "invalid_client"
|
|
269
|
-
});
|
|
270
|
-
throw createErr;
|
|
271
|
-
}
|
|
272
|
-
} finally {
|
|
273
|
-
pendingAssertionIds.delete(jtiIdentifier);
|
|
264
|
+
throw createErr;
|
|
274
265
|
}
|
|
275
266
|
return {
|
|
276
267
|
clientId,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { s as ResourceServerMetadata } from "./oauth-
|
|
1
|
+
import { s as ResourceServerMetadata } from "./oauth-BXrYl5x6.mjs";
|
|
2
2
|
import { JWTPayload, JWTVerifyOptions } from "jose";
|
|
3
3
|
import { BetterAuthOptions } from "better-auth/types";
|
|
4
4
|
|
|
@@ -49,6 +49,20 @@ interface VerifyAccessTokenRemote {
|
|
|
49
49
|
* is also still active.
|
|
50
50
|
*/
|
|
51
51
|
force?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Accept introspection responses that omit the `aud` claim even when a
|
|
54
|
+
* required `audience` is configured in `verifyOptions`.
|
|
55
|
+
*
|
|
56
|
+
* By default verification fails closed: if you configure an `audience` and
|
|
57
|
+
* the introspection response has no `aud` (or a mismatching one), the token
|
|
58
|
+
* is rejected. Some authorization servers legitimately omit `aud` from
|
|
59
|
+
* introspection responses (it is OPTIONAL per RFC 7662 §2.2); only enable
|
|
60
|
+
* this if you trust the issuer to bind the token to this resource through
|
|
61
|
+
* another mechanism, as it skips the audience check in that case.
|
|
62
|
+
*
|
|
63
|
+
* @default false
|
|
64
|
+
*/
|
|
65
|
+
allowMissingAudience?: boolean;
|
|
52
66
|
}
|
|
53
67
|
type VerifyAccessTokenOutput<T> = T extends undefined ? (token: string | undefined, opts: VerifyAccessTokenNoAuthOpts) => Promise<JWTPayload> : (token: string | undefined, opts?: VerifyAccessTokenAuthOpts) => Promise<JWTPayload>;
|
|
54
68
|
type VerifyAccessTokenAuthOpts = {
|
package/dist/client-resource.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as handleMcpErrors } from "./mcp-CYnz-MXn.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
2
|
+
import { c as getOAuthProviderPlugin, s as getJwtPlugin } from "./utils-D2dLqo7f.mjs";
|
|
3
|
+
import { t as PACKAGE_VERSION } from "./version-B1ZiRmxj.mjs";
|
|
4
4
|
import { verifyAccessToken } from "better-auth/oauth2";
|
|
5
5
|
import { APIError } from "better-call";
|
|
6
6
|
import { logger } from "@better-auth/core/env";
|
package/dist/client.d.mts
CHANGED
package/dist/client.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as SchemaClient, a as OAuthClient, b as VerificationValue, c as TokenEndpointAuthMethod, d as OAuthAuthorizationQuery, f as OAuthConsent, g as Prompt, h as OAuthRefreshToken, i as GrantType, l as AuthorizePrompt, m as OAuthOptions, n as AuthServerMetadata, o as OIDCMetadata, p as OAuthOpaqueAccessToken, r as BearerMethodsSupported, s as ResourceServerMetadata, t as AuthMethod, u as ClientDiscovery, v as Scope, x as Awaitable, y as StoreTokenType } from "./oauth-
|
|
2
|
-
import { a as OAuthErrorCode, c as OAuthRedirectOnError, i as OAuthEndpointRedirectContext, n as oauthProvider, o as OAuthFieldErrorCode, r as OAuthEndpointErrorResult, s as OAuthFieldErrorCodeMap, t as getOAuthProviderState } from "./oauth-
|
|
1
|
+
import { _ as SchemaClient, a as OAuthClient, b as VerificationValue, c as TokenEndpointAuthMethod, d as OAuthAuthorizationQuery, f as OAuthConsent, g as Prompt, h as OAuthRefreshToken, i as GrantType, l as AuthorizePrompt, m as OAuthOptions, n as AuthServerMetadata, o as OIDCMetadata, p as OAuthOpaqueAccessToken, r as BearerMethodsSupported, s as ResourceServerMetadata, t as AuthMethod, u as ClientDiscovery, v as Scope, x as Awaitable, y as StoreTokenType } from "./oauth-BXrYl5x6.mjs";
|
|
2
|
+
import { a as OAuthErrorCode, c as OAuthRedirectOnError, i as OAuthEndpointRedirectContext, n as oauthProvider, o as OAuthFieldErrorCode, r as OAuthEndpointErrorResult, s as OAuthFieldErrorCodeMap, t as getOAuthProviderState } from "./oauth-DU6NeviY.mjs";
|
|
3
3
|
import { verifyAccessToken } from "better-auth/oauth2";
|
|
4
4
|
import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
|
|
5
5
|
import { JWTPayload } from "jose";
|
|
@@ -60,6 +60,8 @@ declare function oidcServerMetadata(ctx: GenericEndpointContext, opts: OAuthOpti
|
|
|
60
60
|
code_challenge_methods_supported: "S256"[];
|
|
61
61
|
authorization_response_iss_parameter_supported?: boolean | undefined;
|
|
62
62
|
client_id_metadata_document_supported?: boolean | undefined;
|
|
63
|
+
backchannel_logout_supported?: boolean | undefined;
|
|
64
|
+
backchannel_logout_session_supported?: boolean | undefined;
|
|
63
65
|
id_token_signing_alg_values_supported: JWSAlgorithms[] | ["HS256"];
|
|
64
66
|
};
|
|
65
67
|
/**
|