@better-auth/oauth-provider 1.7.0-beta.0 → 1.7.0-beta.1
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-CderPEmR.mjs} +12 -3
- package/dist/client-resource.d.mts +1 -1
- package/dist/client-resource.mjs +2 -2
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/index.d.mts +50 -6
- package/dist/index.mjs +236 -156
- package/dist/{oauth-Dh4YXCXY.d.mts → oauth-B_qonG53.d.mts} +93 -4
- package/dist/{oauth-C8aTlaAC.d.mts → oauth-CU79t-eG.d.mts} +102 -5
- package/dist/{utils-CIbcUsZ5.mjs → utils-Cx_XnD9i.mjs} +35 -3
- package/dist/{version-BGWhjYBb.mjs → version-DIwdpXrQ.mjs} +1 -1
- package/package.json +5 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as getClient } from "./utils-
|
|
1
|
+
import { a as getClient } from "./utils-Cx_XnD9i.mjs";
|
|
2
2
|
import { APIError } from "better-call";
|
|
3
3
|
import { ASSERTION_SIGNING_ALGORITHMS, CLIENT_ASSERTION_TYPE } from "@better-auth/core/oauth2";
|
|
4
4
|
import { createLocalJWKSet, decodeJwt, decodeProtectedHeader, jwtVerify } from "jose";
|
|
@@ -62,7 +62,7 @@ function isPrivateHostname(hostname) {
|
|
|
62
62
|
if (host === "metadata.google.internal") return true;
|
|
63
63
|
return false;
|
|
64
64
|
}
|
|
65
|
-
function validateJwksUri(ctx, jwksUri) {
|
|
65
|
+
function validateJwksUri(ctx, jwksUri, clientIdUrlOrigin) {
|
|
66
66
|
const parsed = new URL(jwksUri);
|
|
67
67
|
if (parsed.protocol !== "https:") throw new APIError("BAD_REQUEST", {
|
|
68
68
|
error_description: "jwks_uri must use HTTPS",
|
|
@@ -72,11 +72,20 @@ function validateJwksUri(ctx, jwksUri) {
|
|
|
72
72
|
error_description: "jwks_uri must not point to a private or reserved address",
|
|
73
73
|
error: "invalid_client"
|
|
74
74
|
});
|
|
75
|
+
if (clientIdUrlOrigin && parsed.origin === clientIdUrlOrigin) return;
|
|
75
76
|
if (!ctx.context.isTrustedOrigin(parsed.href)) throw new APIError("BAD_REQUEST", {
|
|
76
77
|
error_description: "client jwks_uri is not trusted",
|
|
77
78
|
error: "invalid_client"
|
|
78
79
|
});
|
|
79
80
|
}
|
|
81
|
+
function urlClientIdOrigin(clientId) {
|
|
82
|
+
if (!clientId.startsWith("https://") && !clientId.startsWith("http://")) return;
|
|
83
|
+
try {
|
|
84
|
+
return new URL(clientId).origin;
|
|
85
|
+
} catch {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
80
89
|
async function fetchJwksFromUri(jwksUri) {
|
|
81
90
|
const controller = new AbortController();
|
|
82
91
|
const timeout = setTimeout(() => controller.abort(), JWKS_FETCH_TIMEOUT_MS);
|
|
@@ -100,7 +109,7 @@ async function fetchClientJwks(ctx, client) {
|
|
|
100
109
|
error_description: "client has no JWKS configured",
|
|
101
110
|
error: "invalid_client"
|
|
102
111
|
});
|
|
103
|
-
validateJwksUri(ctx, client.jwksUri);
|
|
112
|
+
validateJwksUri(ctx, client.jwksUri, urlClientIdOrigin(client.clientId));
|
|
104
113
|
const now = Date.now();
|
|
105
114
|
const cached = jwksCache.get(client.jwksUri);
|
|
106
115
|
if (cached && now - cached.fetchedAt < JWKS_CACHE_TTL_MS) return cached.jwks;
|
package/dist/client-resource.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as handleMcpErrors } from "./mcp-CYnz-MXn.mjs";
|
|
2
|
-
import { o as getJwtPlugin, s as getOAuthProviderPlugin } from "./utils-
|
|
3
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
2
|
+
import { o as getJwtPlugin, s as getOAuthProviderPlugin } from "./utils-Cx_XnD9i.mjs";
|
|
3
|
+
import { t as PACKAGE_VERSION } from "./version-DIwdpXrQ.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,9 +1,10 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-
|
|
1
|
+
import { _ as Scope, a as OIDCMetadata, b as Awaitable, c as AuthorizePrompt, d as OAuthConsent, f as OAuthOpaqueAccessToken, g as SchemaClient, h as Prompt, i as OAuthClient, l as ClientDiscovery, m as OAuthRefreshToken, n as AuthServerMetadata, o as ResourceServerMetadata, p as OAuthOptions, r as GrantType, s as TokenEndpointAuthMethod, t as AuthMethod, u as OAuthAuthorizationQuery, v as StoreTokenType, y as VerificationValue } from "./oauth-CU79t-eG.mjs";
|
|
2
|
+
import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-B_qonG53.mjs";
|
|
3
3
|
import { verifyAccessToken } from "better-auth/oauth2";
|
|
4
4
|
import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
|
|
5
5
|
import { JWTPayload } from "jose";
|
|
6
6
|
import { GenericEndpointContext } from "@better-auth/core";
|
|
7
|
+
import * as better_auth0 from "better-auth";
|
|
7
8
|
|
|
8
9
|
//#region src/mcp.d.ts
|
|
9
10
|
/**
|
|
@@ -27,7 +28,37 @@ declare function authServerMetadata(ctx: GenericEndpointContext, opts?: JwtOptio
|
|
|
27
28
|
}): AuthServerMetadata;
|
|
28
29
|
declare function oidcServerMetadata(ctx: GenericEndpointContext, opts: OAuthOptions<Scope[]> & {
|
|
29
30
|
claims?: string[];
|
|
30
|
-
}):
|
|
31
|
+
}): {
|
|
32
|
+
jwks_uri?: string | undefined;
|
|
33
|
+
userinfo_endpoint: string;
|
|
34
|
+
acr_values_supported: string[];
|
|
35
|
+
subject_types_supported: ("public" | "pairwise")[];
|
|
36
|
+
claims_supported: string[];
|
|
37
|
+
end_session_endpoint: string;
|
|
38
|
+
prompt_values_supported: Prompt[];
|
|
39
|
+
issuer: string;
|
|
40
|
+
authorization_endpoint: string;
|
|
41
|
+
token_endpoint: string;
|
|
42
|
+
registration_endpoint: string;
|
|
43
|
+
scopes_supported?: string[] | undefined;
|
|
44
|
+
response_types_supported: "code"[];
|
|
45
|
+
response_modes_supported: "query"[];
|
|
46
|
+
grant_types_supported: GrantType[];
|
|
47
|
+
token_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[] | undefined;
|
|
48
|
+
token_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
|
|
49
|
+
service_documentation?: string | undefined;
|
|
50
|
+
ui_locales_supported?: string[] | undefined;
|
|
51
|
+
op_policy_uri?: string | undefined;
|
|
52
|
+
op_tos_uri?: string | undefined;
|
|
53
|
+
revocation_endpoint?: string | undefined;
|
|
54
|
+
revocation_endpoint_auth_methods_supported?: AuthMethod[] | undefined;
|
|
55
|
+
revocation_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
|
|
56
|
+
introspection_endpoint?: string | undefined;
|
|
57
|
+
introspection_endpoint_auth_methods_supported?: AuthMethod[] | undefined;
|
|
58
|
+
introspection_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
|
|
59
|
+
code_challenge_methods_supported: "S256"[];
|
|
60
|
+
authorization_response_iss_parameter_supported?: boolean | undefined;
|
|
61
|
+
client_id_metadata_document_supported?: boolean | undefined;
|
|
31
62
|
id_token_signing_alg_values_supported: JWSAlgorithms[] | ["HS256"];
|
|
32
63
|
};
|
|
33
64
|
/**
|
|
@@ -44,7 +75,7 @@ declare const oauthProviderAuthServerMetadata: <Auth extends {
|
|
|
44
75
|
};
|
|
45
76
|
}>(auth: Auth, opts?: {
|
|
46
77
|
headers?: HeadersInit;
|
|
47
|
-
}) => (
|
|
78
|
+
}) => (request: Request) => Promise<Response>;
|
|
48
79
|
/**
|
|
49
80
|
* Provides an exportable `/.well-known/openid-configuration`.
|
|
50
81
|
*
|
|
@@ -59,6 +90,19 @@ declare const oauthProviderOpenIdConfigMetadata: <Auth extends {
|
|
|
59
90
|
};
|
|
60
91
|
}>(auth: Auth, opts?: {
|
|
61
92
|
headers?: HeadersInit;
|
|
62
|
-
}) => (
|
|
93
|
+
}) => (request: Request) => Promise<Response>;
|
|
63
94
|
//#endregion
|
|
64
|
-
|
|
95
|
+
//#region src/register.d.ts
|
|
96
|
+
declare function checkOAuthClient(client: OAuthClient, opts: OAuthOptions<Scope[]>, settings?: {
|
|
97
|
+
isRegister?: boolean;
|
|
98
|
+
ctx?: GenericEndpointContext;
|
|
99
|
+
}): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Converts an OAuth 2.0 Dynamic Client Schema to a Database Schema
|
|
102
|
+
*
|
|
103
|
+
* @param input
|
|
104
|
+
* @returns
|
|
105
|
+
*/
|
|
106
|
+
declare function oauthToSchema(input: OAuthClient): SchemaClient<Scope[]>;
|
|
107
|
+
//#endregion
|
|
108
|
+
export { AuthServerMetadata, AuthorizePrompt, ClientDiscovery, OAuthAuthorizationQuery, OAuthClient, OAuthConsent, OAuthOpaqueAccessToken, OAuthOptions, OAuthRefreshToken, OIDCMetadata, Prompt, ResourceServerMetadata, SchemaClient, Scope, StoreTokenType, VerificationValue, authServerMetadata, checkOAuthClient, getOAuthProviderState, mcpHandler, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oauthToSchema, oidcServerMetadata };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as isPrivateHostname } from "./client-assertion-
|
|
1
|
+
import { n as isPrivateHostname } from "./client-assertion-CderPEmR.mjs";
|
|
2
2
|
import { n as mcpHandler } from "./mcp-CYnz-MXn.mjs";
|
|
3
|
-
import { _ as
|
|
4
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
3
|
+
import { _ as storeClientSecret, a as getClient, b as validateClientCredentials, c as getStoredToken, d as normalizeTimestampValue, f as parseClientMetadata, g as searchParamsToQuery, h as resolveSubjectIdentifier, i as extractClientCredentials, l as isPKCERequired, m as resolveSessionAuthTime, n as deleteFromPrompt, o as getJwtPlugin, p as parsePrompt, r as destructureCredentials, t as decryptStoredClientSecret, u as mergeDiscoveryMetadata, v as storeToken, x as verifyOAuthQueryParams, y as toClientDiscoveryArray } from "./utils-Cx_XnD9i.mjs";
|
|
4
|
+
import { t as PACKAGE_VERSION } from "./version-DIwdpXrQ.mjs";
|
|
5
5
|
import { APIError, createAuthEndpoint, createAuthMiddleware, getOAuthState, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
|
|
6
6
|
import { generateCodeChallenge, getJwks, verifyJwsAccessToken } from "better-auth/oauth2";
|
|
7
7
|
import { APIError as APIError$1 } from "better-call";
|
|
@@ -14,8 +14,8 @@ import { BetterAuthError } from "@better-auth/core/error";
|
|
|
14
14
|
import { parseSetCookieHeader } from "better-auth/cookies";
|
|
15
15
|
import { mergeSchema } from "better-auth/db";
|
|
16
16
|
import * as z from "zod";
|
|
17
|
-
import { signJWT, toExpJWT } from "better-auth/plugins";
|
|
18
|
-
import { SignJWT, compactVerify, createLocalJWKSet, decodeJwt } from "jose";
|
|
17
|
+
import { resolveSigningKey, signJWT, toExpJWT } from "better-auth/plugins";
|
|
18
|
+
import { SignJWT, base64url, compactVerify, createLocalJWKSet, decodeJwt, decodeProtectedHeader } from "jose";
|
|
19
19
|
//#region src/consent.ts
|
|
20
20
|
async function consentEndpoint(ctx, opts) {
|
|
21
21
|
const _query = (await oAuthState.get())?.query;
|
|
@@ -156,6 +156,81 @@ async function postLogin(ctx, opts) {
|
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
158
|
//#endregion
|
|
159
|
+
//#region src/types/zod.ts
|
|
160
|
+
const DANGEROUS_SCHEMES = [
|
|
161
|
+
"javascript:",
|
|
162
|
+
"data:",
|
|
163
|
+
"vbscript:"
|
|
164
|
+
];
|
|
165
|
+
function isLocalhost(hostname) {
|
|
166
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname.endsWith(".localhost");
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Runtime schema for OAuthAuthorizationQuery.
|
|
170
|
+
* Uses passthrough to tolerate fields added by future extensions (PAR, FPA, etc.)
|
|
171
|
+
*/
|
|
172
|
+
const oauthAuthorizationQuerySchema = z.object({
|
|
173
|
+
response_type: z.literal("code").optional(),
|
|
174
|
+
request_uri: z.string().optional(),
|
|
175
|
+
redirect_uri: z.string(),
|
|
176
|
+
scope: z.string().optional(),
|
|
177
|
+
state: z.string().optional(),
|
|
178
|
+
client_id: z.string(),
|
|
179
|
+
prompt: z.string().optional(),
|
|
180
|
+
display: z.string().optional(),
|
|
181
|
+
ui_locales: z.string().optional(),
|
|
182
|
+
max_age: z.coerce.number().optional(),
|
|
183
|
+
acr_values: z.string().optional(),
|
|
184
|
+
login_hint: z.string().optional(),
|
|
185
|
+
id_token_hint: z.string().optional(),
|
|
186
|
+
code_challenge: z.string().optional(),
|
|
187
|
+
code_challenge_method: z.literal("S256").optional(),
|
|
188
|
+
nonce: z.string().optional()
|
|
189
|
+
}).passthrough();
|
|
190
|
+
/**
|
|
191
|
+
* Runtime schema for the authorization code verification value.
|
|
192
|
+
* Validates structure on deserialization from the JSON blob stored in the DB.
|
|
193
|
+
* Uses passthrough so future fields (e.g. from authorization challenge) don't break parsing.
|
|
194
|
+
*/
|
|
195
|
+
const verificationValueSchema = z.object({
|
|
196
|
+
type: z.literal("authorization_code"),
|
|
197
|
+
query: oauthAuthorizationQuerySchema,
|
|
198
|
+
sessionId: z.string(),
|
|
199
|
+
userId: z.string(),
|
|
200
|
+
referenceId: z.string().optional(),
|
|
201
|
+
authTime: z.number().optional()
|
|
202
|
+
}).passthrough();
|
|
203
|
+
/**
|
|
204
|
+
* Reusable URL validation for OAuth redirect URIs.
|
|
205
|
+
* - Blocks dangerous schemes (javascript:, data:, vbscript:)
|
|
206
|
+
* - For http/https: requires HTTPS (HTTP allowed only for localhost)
|
|
207
|
+
* - Allows custom schemes for mobile apps (e.g., myapp://callback)
|
|
208
|
+
*/
|
|
209
|
+
const SafeUrlSchema = z.url().superRefine((val, ctx) => {
|
|
210
|
+
if (!URL.canParse(val)) {
|
|
211
|
+
ctx.addIssue({
|
|
212
|
+
code: "custom",
|
|
213
|
+
message: "URL must be parseable",
|
|
214
|
+
fatal: true
|
|
215
|
+
});
|
|
216
|
+
return z.NEVER;
|
|
217
|
+
}
|
|
218
|
+
const u = new URL(val);
|
|
219
|
+
if (DANGEROUS_SCHEMES.includes(u.protocol)) {
|
|
220
|
+
ctx.addIssue({
|
|
221
|
+
code: "custom",
|
|
222
|
+
message: "URL cannot use javascript:, data:, or vbscript: scheme"
|
|
223
|
+
});
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (u.protocol === "http:" || u.protocol === "https:") {
|
|
227
|
+
if (u.protocol === "http:" && !isLocalhost(u.hostname)) ctx.addIssue({
|
|
228
|
+
code: "custom",
|
|
229
|
+
message: "Redirect URI must use HTTPS (HTTP allowed only for localhost)"
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
//#endregion
|
|
159
234
|
//#region src/userinfo.ts
|
|
160
235
|
/**
|
|
161
236
|
* Provides shared /userinfo and id_token claims functionality
|
|
@@ -268,7 +343,7 @@ async function createJwtAccessToken(ctx, opts, user, client, audience, scopes, r
|
|
|
268
343
|
options: jwtPluginOptions,
|
|
269
344
|
payload: {
|
|
270
345
|
...customClaims,
|
|
271
|
-
sub: user
|
|
346
|
+
sub: user?.id,
|
|
272
347
|
aud: typeof audience === "string" ? audience : audience?.length === 1 ? audience.at(0) : audience,
|
|
273
348
|
azp: client.clientId,
|
|
274
349
|
scope: scopes.join(" "),
|
|
@@ -280,10 +355,23 @@ async function createJwtAccessToken(ctx, opts, user, client, audience, scopes, r
|
|
|
280
355
|
});
|
|
281
356
|
}
|
|
282
357
|
/**
|
|
358
|
+
* Computes an OIDC hash (at_hash, c_hash) per OIDC Core §3.1.3.6.
|
|
359
|
+
* Hashes the token, takes the left half, and base64url-encodes it.
|
|
360
|
+
*/
|
|
361
|
+
async function computeOidcHash(token, signingAlg) {
|
|
362
|
+
let hashAlg;
|
|
363
|
+
if (signingAlg === "EdDSA") hashAlg = "SHA-512";
|
|
364
|
+
else if (signingAlg.endsWith("384")) hashAlg = "SHA-384";
|
|
365
|
+
else if (signingAlg.endsWith("512")) hashAlg = "SHA-512";
|
|
366
|
+
else hashAlg = "SHA-256";
|
|
367
|
+
const digest = new Uint8Array(await crypto.subtle.digest(hashAlg, new TextEncoder().encode(token)));
|
|
368
|
+
return base64url.encode(digest.slice(0, digest.length / 2));
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
283
371
|
* Creates a user id token in code_authorization with scope of 'openid'
|
|
284
372
|
* and hybrid/implicit (not yet implemented) flows
|
|
285
373
|
*/
|
|
286
|
-
async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId, authTime) {
|
|
374
|
+
async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId, authTime, accessToken) {
|
|
287
375
|
const iat = Math.floor(Date.now() / 1e3);
|
|
288
376
|
const exp = iat + (opts.idTokenExpiresIn ?? 36e3);
|
|
289
377
|
const userClaims = userNormalClaims(user, scopes);
|
|
@@ -296,11 +384,15 @@ async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId,
|
|
|
296
384
|
metadata: parseClientMetadata(client.metadata)
|
|
297
385
|
}) : {};
|
|
298
386
|
const jwtPluginOptions = opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context).options;
|
|
387
|
+
const resolvedKey = !opts.disableJwtPlugin && !jwtPluginOptions?.jwt?.sign ? await resolveSigningKey(ctx, jwtPluginOptions) : void 0;
|
|
388
|
+
const signingAlg = opts.disableJwtPlugin ? "HS256" : resolvedKey?.alg ?? jwtPluginOptions?.jwks?.keyPairConfig?.alg;
|
|
389
|
+
const atHash = accessToken ? await computeOidcHash(accessToken, signingAlg) : void 0;
|
|
299
390
|
const payload = {
|
|
300
391
|
...userClaims,
|
|
301
392
|
auth_time: authTimeSec,
|
|
302
393
|
acr,
|
|
303
394
|
...customClaims,
|
|
395
|
+
at_hash: atHash,
|
|
304
396
|
iss: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL,
|
|
305
397
|
sub: resolvedSub,
|
|
306
398
|
aud: client.clientId,
|
|
@@ -310,10 +402,19 @@ async function createIdToken(ctx, opts, user, client, scopes, nonce, sessionId,
|
|
|
310
402
|
sid: client.enableEndSession ? sessionId : void 0
|
|
311
403
|
};
|
|
312
404
|
if (opts.disableJwtPlugin && !client.clientSecret) return;
|
|
313
|
-
|
|
405
|
+
const idToken = opts.disableJwtPlugin ? await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).sign(new TextEncoder().encode(await decryptStoredClientSecret(ctx, opts.storeClientSecret, client.clientSecret))) : await signJWT(ctx, {
|
|
314
406
|
options: jwtPluginOptions,
|
|
315
|
-
payload
|
|
407
|
+
payload,
|
|
408
|
+
resolvedKey: resolvedKey ?? void 0
|
|
316
409
|
});
|
|
410
|
+
if (idToken && atHash && jwtPluginOptions?.jwt?.sign) {
|
|
411
|
+
const header = decodeProtectedHeader(idToken);
|
|
412
|
+
if (header.alg !== signingAlg) throw new APIError("INTERNAL_SERVER_ERROR", {
|
|
413
|
+
error_description: `ID token signed with "${header.alg}" but at_hash was computed for "${signingAlg}". Ensure jwt.sign uses the algorithm declared in keyPairConfig.alg.`,
|
|
414
|
+
error: "server_error"
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
return idToken;
|
|
317
418
|
}
|
|
318
419
|
/**
|
|
319
420
|
* Encodes a refresh token for a client
|
|
@@ -402,39 +503,45 @@ async function checkResource(ctx, opts, scopes) {
|
|
|
402
503
|
}
|
|
403
504
|
return audience?.length === 1 ? audience.at(0) : audience;
|
|
404
505
|
}
|
|
405
|
-
async function createUserTokens(ctx, opts,
|
|
506
|
+
async function createUserTokens(ctx, opts, params) {
|
|
507
|
+
const { client, scopes, user, grantType, referenceId, sessionId, nonce, refreshToken: existingRefreshToken, authTime, verificationValue } = params;
|
|
406
508
|
const iat = Math.floor(Date.now() / 1e3);
|
|
407
|
-
const defaultExp = iat + (opts.accessTokenExpiresIn ?? 3600);
|
|
509
|
+
const defaultExp = iat + (user ? opts.accessTokenExpiresIn ?? 3600 : opts.m2mAccessTokenExpiresIn ?? 3600);
|
|
408
510
|
const exp = opts.scopeExpirations ? scopes.map((sc) => opts.scopeExpirations?.[sc] ? toExpJWT(opts.scopeExpirations[sc], iat) : defaultExp).reduce((prev, curr) => {
|
|
409
511
|
return prev < curr ? prev : curr;
|
|
410
512
|
}, defaultExp) : defaultExp;
|
|
411
513
|
const audience = await checkResource(ctx, opts, scopes);
|
|
412
|
-
const isRefreshToken =
|
|
514
|
+
const isRefreshToken = user && (existingRefreshToken?.scopes?.includes("offline_access") || scopes.includes("offline_access"));
|
|
413
515
|
const isJwtAccessToken = audience && !opts.disableJwtPlugin;
|
|
414
|
-
const isIdToken = scopes.includes("openid");
|
|
415
|
-
const
|
|
516
|
+
const isIdToken = user && scopes.includes("openid");
|
|
517
|
+
const customFields = opts.customTokenResponseFields ? await opts.customTokenResponseFields({
|
|
518
|
+
grantType,
|
|
519
|
+
user,
|
|
520
|
+
scopes,
|
|
521
|
+
metadata: parseClientMetadata(client.metadata),
|
|
522
|
+
verificationValue
|
|
523
|
+
}) : void 0;
|
|
524
|
+
const earlyRefreshToken = isRefreshToken && user && !isJwtAccessToken ? await createRefreshToken(ctx, opts, user, referenceId, client, scopes, {
|
|
416
525
|
iat,
|
|
417
526
|
exp: iat + (opts.refreshTokenExpiresIn ?? 2592e3),
|
|
418
527
|
sid: sessionId
|
|
419
|
-
},
|
|
420
|
-
const [accessToken, refreshToken
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}, additional?.refreshToken, authTime) : void 0,
|
|
435
|
-
isIdToken ? createIdToken(ctx, opts, user, client, scopes, nonce, sessionId, authTime) : void 0
|
|
436
|
-
]);
|
|
528
|
+
}, existingRefreshToken, authTime) : void 0;
|
|
529
|
+
const [accessToken, refreshToken] = await Promise.all([isJwtAccessToken ? createJwtAccessToken(ctx, opts, user, client, audience, scopes, referenceId, {
|
|
530
|
+
iat,
|
|
531
|
+
exp,
|
|
532
|
+
sid: sessionId
|
|
533
|
+
}) : createOpaqueAccessToken(ctx, opts, user, client, scopes, {
|
|
534
|
+
iat,
|
|
535
|
+
exp,
|
|
536
|
+
sid: sessionId
|
|
537
|
+
}, referenceId, earlyRefreshToken?.id), earlyRefreshToken ? earlyRefreshToken : isRefreshToken && user ? createRefreshToken(ctx, opts, user, referenceId, client, scopes, {
|
|
538
|
+
iat,
|
|
539
|
+
exp: iat + (opts.refreshTokenExpiresIn ?? 2592e3),
|
|
540
|
+
sid: sessionId
|
|
541
|
+
}, existingRefreshToken, authTime) : void 0]);
|
|
542
|
+
const idToken = isIdToken ? await createIdToken(ctx, opts, user, client, scopes, nonce, sessionId, authTime, accessToken) : void 0;
|
|
437
543
|
return ctx.json({
|
|
544
|
+
...customFields,
|
|
438
545
|
access_token: accessToken,
|
|
439
546
|
expires_in: exp - iat,
|
|
440
547
|
expires_at: exp,
|
|
@@ -450,7 +557,6 @@ async function createUserTokens(ctx, opts, client, scopes, user, referenceId, se
|
|
|
450
557
|
/** Checks verification value */
|
|
451
558
|
async function checkVerificationValue(ctx, opts, code, client_id, redirect_uri) {
|
|
452
559
|
const verification = await ctx.context.internalAdapter.findVerificationValue(await storeToken(opts.storeTokens, code, "authorization_code"));
|
|
453
|
-
const verificationValue = verification ? JSON.parse(verification?.value) : void 0;
|
|
454
560
|
if (!verification) throw new APIError("UNAUTHORIZED", {
|
|
455
561
|
error_description: "Invalid code",
|
|
456
562
|
error: "invalid_verification"
|
|
@@ -460,22 +566,25 @@ async function checkVerificationValue(ctx, opts, code, client_id, redirect_uri)
|
|
|
460
566
|
error_description: "code expired",
|
|
461
567
|
error: "invalid_verification"
|
|
462
568
|
});
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
|
|
569
|
+
let rawValue;
|
|
570
|
+
try {
|
|
571
|
+
rawValue = JSON.parse(verification.value);
|
|
572
|
+
} catch {
|
|
573
|
+
throw new APIError("UNAUTHORIZED", {
|
|
574
|
+
error_description: "malformed verification value",
|
|
575
|
+
error: "invalid_verification"
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
const parsed = verificationValueSchema.safeParse(rawValue);
|
|
579
|
+
if (!parsed.success) throw new APIError("UNAUTHORIZED", {
|
|
580
|
+
error_description: "malformed verification value",
|
|
469
581
|
error: "invalid_verification"
|
|
470
582
|
});
|
|
583
|
+
const verificationValue = parsed.data;
|
|
471
584
|
if (verificationValue.query.client_id !== client_id) throw new APIError("UNAUTHORIZED", {
|
|
472
585
|
error_description: "invalid client_id",
|
|
473
586
|
error: "invalid_client"
|
|
474
587
|
});
|
|
475
|
-
if (!verificationValue.userId) throw new APIError("BAD_REQUEST", {
|
|
476
|
-
error_description: "missing user_id on challenge",
|
|
477
|
-
error: "invalid_user"
|
|
478
|
-
});
|
|
479
588
|
if (verificationValue.query?.redirect_uri && verificationValue.query?.redirect_uri !== redirect_uri) throw new APIError("BAD_REQUEST", {
|
|
480
589
|
error_description: "redirect_uri mismatch",
|
|
481
590
|
error: "invalid_request"
|
|
@@ -563,7 +672,17 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
|
|
|
563
672
|
error: "invalid_request"
|
|
564
673
|
});
|
|
565
674
|
const authTime = verificationValue.authTime != null ? normalizeTimestampValue(verificationValue.authTime) : resolveSessionAuthTime(session);
|
|
566
|
-
return createUserTokens(ctx, opts,
|
|
675
|
+
return createUserTokens(ctx, opts, {
|
|
676
|
+
client,
|
|
677
|
+
scopes: verificationValue.query.scope?.split(" ") ?? [],
|
|
678
|
+
user,
|
|
679
|
+
grantType: "authorization_code",
|
|
680
|
+
referenceId: verificationValue.referenceId,
|
|
681
|
+
sessionId: session.id,
|
|
682
|
+
nonce: verificationValue.query?.nonce,
|
|
683
|
+
authTime,
|
|
684
|
+
verificationValue
|
|
685
|
+
});
|
|
567
686
|
}
|
|
568
687
|
/**
|
|
569
688
|
* Grant that allows direct access to an API using the application's credentials
|
|
@@ -601,43 +720,11 @@ async function handleClientCredentialsGrant(ctx, opts) {
|
|
|
601
720
|
});
|
|
602
721
|
}
|
|
603
722
|
if (!requestedScopes) requestedScopes = client.scopes ?? opts.clientCredentialGrantDefaultScopes ?? opts.scopes ?? [];
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const iat = Math.floor(Date.now() / 1e3);
|
|
607
|
-
const defaultExp = iat + (opts.m2mAccessTokenExpiresIn ?? 3600);
|
|
608
|
-
const exp = opts.scopeExpirations && requestedScopes ? requestedScopes.map((sc) => opts.scopeExpirations?.[sc] ? toExpJWT(opts.scopeExpirations[sc], iat) : defaultExp).reduce((prev, curr) => {
|
|
609
|
-
return prev < curr ? prev : curr;
|
|
610
|
-
}, defaultExp) : defaultExp;
|
|
611
|
-
const customClaims = opts.customAccessTokenClaims ? await opts.customAccessTokenClaims({
|
|
723
|
+
return createUserTokens(ctx, opts, {
|
|
724
|
+
client,
|
|
612
725
|
scopes: requestedScopes,
|
|
613
|
-
|
|
614
|
-
metadata: parseClientMetadata(client.metadata)
|
|
615
|
-
}) : {};
|
|
616
|
-
const accessToken = audience && !opts.disableJwtPlugin ? await signJWT(ctx, {
|
|
617
|
-
options: jwtPluginOptions,
|
|
618
|
-
payload: {
|
|
619
|
-
...customClaims,
|
|
620
|
-
aud: audience,
|
|
621
|
-
azp: client.clientId,
|
|
622
|
-
scope: requestedScopes.join(" "),
|
|
623
|
-
iss: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL,
|
|
624
|
-
iat,
|
|
625
|
-
exp
|
|
626
|
-
}
|
|
627
|
-
}) : await createOpaqueAccessToken(ctx, opts, void 0, client, requestedScopes, {
|
|
628
|
-
iat,
|
|
629
|
-
exp
|
|
726
|
+
grantType: "client_credentials"
|
|
630
727
|
});
|
|
631
|
-
return ctx.json({
|
|
632
|
-
access_token: accessToken,
|
|
633
|
-
expires_in: exp - iat,
|
|
634
|
-
expires_at: exp,
|
|
635
|
-
token_type: "Bearer",
|
|
636
|
-
scope: requestedScopes.join(" ")
|
|
637
|
-
}, { headers: {
|
|
638
|
-
"Cache-Control": "no-store",
|
|
639
|
-
Pragma: "no-cache"
|
|
640
|
-
} });
|
|
641
728
|
}
|
|
642
729
|
/**
|
|
643
730
|
* Obtains new Session Jwt and Refresh Tokens using a refresh token
|
|
@@ -708,7 +795,16 @@ async function handleRefreshTokenGrant(ctx, opts) {
|
|
|
708
795
|
error: "invalid_request"
|
|
709
796
|
});
|
|
710
797
|
const authTime = refreshToken.authTime != null ? normalizeTimestampValue(refreshToken.authTime) : void 0;
|
|
711
|
-
return createUserTokens(ctx, opts,
|
|
798
|
+
return createUserTokens(ctx, opts, {
|
|
799
|
+
client,
|
|
800
|
+
scopes: requestedScopes ?? scopes,
|
|
801
|
+
user,
|
|
802
|
+
grantType: "refresh_token",
|
|
803
|
+
referenceId: refreshToken.referenceId,
|
|
804
|
+
sessionId: refreshToken.sessionId,
|
|
805
|
+
refreshToken,
|
|
806
|
+
authTime
|
|
807
|
+
});
|
|
712
808
|
}
|
|
713
809
|
//#endregion
|
|
714
810
|
//#region src/introspect.ts
|
|
@@ -1085,6 +1181,21 @@ const publicSessionMiddleware = (opts) => createAuthMiddleware(async (ctx) => {
|
|
|
1085
1181
|
});
|
|
1086
1182
|
//#endregion
|
|
1087
1183
|
//#region src/register.ts
|
|
1184
|
+
/**
|
|
1185
|
+
* Resolves the auth method and type for unauthenticated DCR.
|
|
1186
|
+
* Overrides confidential methods to "none" per RFC 7591 Section 3.2.1.
|
|
1187
|
+
* When overriding, clears type "web" since it is only valid for confidential clients.
|
|
1188
|
+
*/
|
|
1189
|
+
function resolveUnauthenticatedAuth(body) {
|
|
1190
|
+
if (body.token_endpoint_auth_method === "none") return {
|
|
1191
|
+
tokenEndpointAuthMethod: "none",
|
|
1192
|
+
type: body.type
|
|
1193
|
+
};
|
|
1194
|
+
return {
|
|
1195
|
+
tokenEndpointAuthMethod: "none",
|
|
1196
|
+
type: body.type === "web" ? void 0 : body.type
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1088
1199
|
async function registerEndpoint(ctx, opts) {
|
|
1089
1200
|
if (!opts.allowDynamicClientRegistration) throw new APIError("FORBIDDEN", {
|
|
1090
1201
|
error: "access_denied",
|
|
@@ -1096,12 +1207,16 @@ async function registerEndpoint(ctx, opts) {
|
|
|
1096
1207
|
error: "invalid_token",
|
|
1097
1208
|
error_description: "Authentication required for client registration"
|
|
1098
1209
|
});
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1210
|
+
if (!session) {
|
|
1211
|
+
if (body.grant_types?.includes("client_credentials")) throw new APIError("BAD_REQUEST", {
|
|
1212
|
+
error: "invalid_client_metadata",
|
|
1213
|
+
error_description: "client_credentials grant requires authenticated registration"
|
|
1214
|
+
});
|
|
1215
|
+
const resolved = resolveUnauthenticatedAuth(body);
|
|
1216
|
+
body.token_endpoint_auth_method = resolved.tokenEndpointAuthMethod;
|
|
1217
|
+
body.type = resolved.type;
|
|
1218
|
+
}
|
|
1219
|
+
if (!body.scope) body.scope = (opts.clientRegistrationDefaultScopes ?? opts.scopes)?.join(" ");
|
|
1105
1220
|
return createOAuthClientEndpoint(ctx, opts, { isRegister: true });
|
|
1106
1221
|
}
|
|
1107
1222
|
async function checkOAuthClient(client, opts, settings) {
|
|
@@ -1340,46 +1455,6 @@ function schemaToOAuth(input) {
|
|
|
1340
1455
|
};
|
|
1341
1456
|
}
|
|
1342
1457
|
//#endregion
|
|
1343
|
-
//#region src/types/zod.ts
|
|
1344
|
-
const DANGEROUS_SCHEMES = [
|
|
1345
|
-
"javascript:",
|
|
1346
|
-
"data:",
|
|
1347
|
-
"vbscript:"
|
|
1348
|
-
];
|
|
1349
|
-
function isLocalhost(hostname) {
|
|
1350
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname.endsWith(".localhost");
|
|
1351
|
-
}
|
|
1352
|
-
/**
|
|
1353
|
-
* Reusable URL validation for OAuth redirect URIs.
|
|
1354
|
-
* - Blocks dangerous schemes (javascript:, data:, vbscript:)
|
|
1355
|
-
* - For http/https: requires HTTPS (HTTP allowed only for localhost)
|
|
1356
|
-
* - Allows custom schemes for mobile apps (e.g., myapp://callback)
|
|
1357
|
-
*/
|
|
1358
|
-
const SafeUrlSchema = z.url().superRefine((val, ctx) => {
|
|
1359
|
-
if (!URL.canParse(val)) {
|
|
1360
|
-
ctx.addIssue({
|
|
1361
|
-
code: "custom",
|
|
1362
|
-
message: "URL must be parseable",
|
|
1363
|
-
fatal: true
|
|
1364
|
-
});
|
|
1365
|
-
return z.NEVER;
|
|
1366
|
-
}
|
|
1367
|
-
const u = new URL(val);
|
|
1368
|
-
if (DANGEROUS_SCHEMES.includes(u.protocol)) {
|
|
1369
|
-
ctx.addIssue({
|
|
1370
|
-
code: "custom",
|
|
1371
|
-
message: "URL cannot use javascript:, data:, or vbscript: scheme"
|
|
1372
|
-
});
|
|
1373
|
-
return;
|
|
1374
|
-
}
|
|
1375
|
-
if (u.protocol === "http:" || u.protocol === "https:") {
|
|
1376
|
-
if (u.protocol === "http:" && !isLocalhost(u.hostname)) ctx.addIssue({
|
|
1377
|
-
code: "custom",
|
|
1378
|
-
message: "Redirect URI must use HTTPS (HTTP allowed only for localhost)"
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1381
|
-
});
|
|
1382
|
-
//#endregion
|
|
1383
1458
|
//#region src/oauthClient/endpoints.ts
|
|
1384
1459
|
async function getClientEndpoint(ctx, opts) {
|
|
1385
1460
|
const session = await getSessionFromCtx(ctx);
|
|
@@ -1543,6 +1618,7 @@ async function updateClientEndpoint(ctx, opts) {
|
|
|
1543
1618
|
else {
|
|
1544
1619
|
schemaUpdates.jwks = null;
|
|
1545
1620
|
schemaUpdates.jwksUri = null;
|
|
1621
|
+
if (!schemaUpdates.clientSecret) schemaUpdates.clientSecret = await storeClientSecret(ctx, opts, opts.generateClientSecret?.() || generateRandomString(32, "a-z", "A-Z"));
|
|
1546
1622
|
}
|
|
1547
1623
|
const updatedClient = await ctx.context.adapter.update({
|
|
1548
1624
|
model: "oauthClient",
|
|
@@ -2800,7 +2876,7 @@ const oauthProvider = (options) => {
|
|
|
2800
2876
|
queryParams.delete("sig");
|
|
2801
2877
|
queryParams.delete("exp");
|
|
2802
2878
|
await oAuthState.set({ query: queryParams.toString() });
|
|
2803
|
-
if (ctx.path === "/sign-in/social"
|
|
2879
|
+
if (ctx.path === "/sign-in/social") {
|
|
2804
2880
|
if (ctx.body.additionalData?.query) return;
|
|
2805
2881
|
if (!ctx.body.additionalData) ctx.body.additionalData = {};
|
|
2806
2882
|
ctx.body.additionalData.query = queryParams.toString();
|
|
@@ -2834,12 +2910,15 @@ const oauthProvider = (options) => {
|
|
|
2834
2910
|
metadata: { SERVER_ONLY: true }
|
|
2835
2911
|
}, async (ctx) => {
|
|
2836
2912
|
if (opts.scopes && opts.scopes.includes("openid")) return oidcServerMetadata(ctx, opts);
|
|
2837
|
-
else return
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2913
|
+
else return {
|
|
2914
|
+
...authServerMetadata(ctx, opts.disableJwtPlugin ? void 0 : getJwtPlugin(ctx.context)?.options, {
|
|
2915
|
+
scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
|
|
2916
|
+
public_client_supported: opts.allowUnauthenticatedClientRegistration || toClientDiscoveryArray(opts.clientDiscovery).length > 0,
|
|
2917
|
+
grant_types_supported: opts.grantTypes,
|
|
2918
|
+
jwt_disabled: opts.disableJwtPlugin
|
|
2919
|
+
}),
|
|
2920
|
+
...mergeDiscoveryMetadata(opts.clientDiscovery)
|
|
2921
|
+
};
|
|
2843
2922
|
}),
|
|
2844
2923
|
getOpenIdConfig: createAuthEndpoint("/.well-known/openid-configuration", {
|
|
2845
2924
|
method: "GET",
|
|
@@ -4004,7 +4083,7 @@ function oidcServerMetadata(ctx, opts) {
|
|
|
4004
4083
|
return {
|
|
4005
4084
|
...authServerMetadata(ctx, jwtPluginOptions, {
|
|
4006
4085
|
scopes_supported: opts.advertisedMetadata?.scopes_supported ?? opts.scopes,
|
|
4007
|
-
public_client_supported: opts.allowUnauthenticatedClientRegistration,
|
|
4086
|
+
public_client_supported: opts.allowUnauthenticatedClientRegistration || toClientDiscoveryArray(opts.clientDiscovery).length > 0,
|
|
4008
4087
|
grant_types_supported: opts.grantTypes,
|
|
4009
4088
|
jwt_disabled: opts.disableJwtPlugin
|
|
4010
4089
|
}),
|
|
@@ -4020,9 +4099,20 @@ function oidcServerMetadata(ctx, opts) {
|
|
|
4020
4099
|
"create",
|
|
4021
4100
|
"select_account",
|
|
4022
4101
|
"none"
|
|
4023
|
-
]
|
|
4102
|
+
],
|
|
4103
|
+
...mergeDiscoveryMetadata(opts.clientDiscovery)
|
|
4024
4104
|
};
|
|
4025
4105
|
}
|
|
4106
|
+
const METADATA_CACHE_CONTROL = "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400";
|
|
4107
|
+
function metadataResponse(body, extraHeaders) {
|
|
4108
|
+
const headers = new Headers(extraHeaders);
|
|
4109
|
+
if (!headers.has("Cache-Control")) headers.set("Cache-Control", METADATA_CACHE_CONTROL);
|
|
4110
|
+
headers.set("Content-Type", "application/json");
|
|
4111
|
+
return new Response(JSON.stringify(body), {
|
|
4112
|
+
status: 200,
|
|
4113
|
+
headers
|
|
4114
|
+
});
|
|
4115
|
+
}
|
|
4026
4116
|
/**
|
|
4027
4117
|
* Provides an exportable `/.well-known/oauth-authorization-server`.
|
|
4028
4118
|
*
|
|
@@ -4032,16 +4122,11 @@ function oidcServerMetadata(ctx, opts) {
|
|
|
4032
4122
|
* @external
|
|
4033
4123
|
*/
|
|
4034
4124
|
const oauthProviderAuthServerMetadata = (auth, opts) => {
|
|
4035
|
-
return async (
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
"Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
|
|
4041
|
-
...opts?.headers,
|
|
4042
|
-
"Content-Type": "application/json"
|
|
4043
|
-
}
|
|
4044
|
-
});
|
|
4125
|
+
return async (request) => {
|
|
4126
|
+
return metadataResponse(await auth.api.getOAuthServerConfig({
|
|
4127
|
+
request,
|
|
4128
|
+
asResponse: false
|
|
4129
|
+
}), opts?.headers);
|
|
4045
4130
|
};
|
|
4046
4131
|
};
|
|
4047
4132
|
/**
|
|
@@ -4053,17 +4138,12 @@ const oauthProviderAuthServerMetadata = (auth, opts) => {
|
|
|
4053
4138
|
* @external
|
|
4054
4139
|
*/
|
|
4055
4140
|
const oauthProviderOpenIdConfigMetadata = (auth, opts) => {
|
|
4056
|
-
return async (
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
"Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
|
|
4062
|
-
...opts?.headers,
|
|
4063
|
-
"Content-Type": "application/json"
|
|
4064
|
-
}
|
|
4065
|
-
});
|
|
4141
|
+
return async (request) => {
|
|
4142
|
+
return metadataResponse(await auth.api.getOpenIdConfig({
|
|
4143
|
+
request,
|
|
4144
|
+
asResponse: false
|
|
4145
|
+
}), opts?.headers);
|
|
4066
4146
|
};
|
|
4067
4147
|
};
|
|
4068
4148
|
//#endregion
|
|
4069
|
-
export { authServerMetadata, getOAuthProviderState, mcpHandler, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
|
|
4149
|
+
export { authServerMetadata, checkOAuthClient, getOAuthProviderState, mcpHandler, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oauthToSchema, oidcServerMetadata };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as Scope, d as OAuthConsent, h as Prompt, i as OAuthClient, p as OAuthOptions, r as GrantType, s as TokenEndpointAuthMethod, t as AuthMethod } from "./oauth-CU79t-eG.mjs";
|
|
2
2
|
import * as better_call0 from "better-call";
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
import * as better_auth_plugins0 from "better-auth/plugins";
|
|
@@ -54,7 +54,64 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
54
54
|
metadata: {
|
|
55
55
|
SERVER_ONLY: true;
|
|
56
56
|
};
|
|
57
|
-
},
|
|
57
|
+
}, {
|
|
58
|
+
jwks_uri?: string | undefined;
|
|
59
|
+
userinfo_endpoint: string;
|
|
60
|
+
acr_values_supported: string[];
|
|
61
|
+
subject_types_supported: ("public" | "pairwise")[];
|
|
62
|
+
claims_supported: string[];
|
|
63
|
+
end_session_endpoint: string;
|
|
64
|
+
prompt_values_supported: Prompt[];
|
|
65
|
+
issuer: string;
|
|
66
|
+
authorization_endpoint: string;
|
|
67
|
+
token_endpoint: string;
|
|
68
|
+
registration_endpoint: string;
|
|
69
|
+
scopes_supported?: string[] | undefined;
|
|
70
|
+
response_types_supported: "code"[];
|
|
71
|
+
response_modes_supported: "query"[];
|
|
72
|
+
grant_types_supported: GrantType[];
|
|
73
|
+
token_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[] | undefined;
|
|
74
|
+
token_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
|
|
75
|
+
service_documentation?: string | undefined;
|
|
76
|
+
ui_locales_supported?: string[] | undefined;
|
|
77
|
+
op_policy_uri?: string | undefined;
|
|
78
|
+
op_tos_uri?: string | undefined;
|
|
79
|
+
revocation_endpoint?: string | undefined;
|
|
80
|
+
revocation_endpoint_auth_methods_supported?: AuthMethod[] | undefined;
|
|
81
|
+
revocation_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
|
|
82
|
+
introspection_endpoint?: string | undefined;
|
|
83
|
+
introspection_endpoint_auth_methods_supported?: AuthMethod[] | undefined;
|
|
84
|
+
introspection_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
|
|
85
|
+
code_challenge_methods_supported: "S256"[];
|
|
86
|
+
authorization_response_iss_parameter_supported?: boolean | undefined;
|
|
87
|
+
client_id_metadata_document_supported?: boolean | undefined;
|
|
88
|
+
id_token_signing_alg_values_supported: better_auth_plugins0.JWSAlgorithms[] | ["HS256"];
|
|
89
|
+
} | {
|
|
90
|
+
issuer: string;
|
|
91
|
+
authorization_endpoint: string;
|
|
92
|
+
token_endpoint: string;
|
|
93
|
+
jwks_uri?: string;
|
|
94
|
+
registration_endpoint: string;
|
|
95
|
+
scopes_supported?: string[];
|
|
96
|
+
response_types_supported: "code"[];
|
|
97
|
+
response_modes_supported: "query"[];
|
|
98
|
+
grant_types_supported: GrantType[];
|
|
99
|
+
token_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[];
|
|
100
|
+
token_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[];
|
|
101
|
+
service_documentation?: string;
|
|
102
|
+
ui_locales_supported?: string[];
|
|
103
|
+
op_policy_uri?: string;
|
|
104
|
+
op_tos_uri?: string;
|
|
105
|
+
revocation_endpoint?: string;
|
|
106
|
+
revocation_endpoint_auth_methods_supported?: AuthMethod[];
|
|
107
|
+
revocation_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[];
|
|
108
|
+
introspection_endpoint?: string;
|
|
109
|
+
introspection_endpoint_auth_methods_supported?: AuthMethod[];
|
|
110
|
+
introspection_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[];
|
|
111
|
+
code_challenge_methods_supported: "S256"[];
|
|
112
|
+
authorization_response_iss_parameter_supported?: boolean;
|
|
113
|
+
client_id_metadata_document_supported?: boolean;
|
|
114
|
+
}>;
|
|
58
115
|
/**
|
|
59
116
|
* A server-only endpoint that helps provide the
|
|
60
117
|
* OpenId configuration at the well-known endpoint.
|
|
@@ -67,7 +124,37 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
67
124
|
metadata: {
|
|
68
125
|
SERVER_ONLY: true;
|
|
69
126
|
};
|
|
70
|
-
},
|
|
127
|
+
}, {
|
|
128
|
+
jwks_uri?: string | undefined;
|
|
129
|
+
userinfo_endpoint: string;
|
|
130
|
+
acr_values_supported: string[];
|
|
131
|
+
subject_types_supported: ("public" | "pairwise")[];
|
|
132
|
+
claims_supported: string[];
|
|
133
|
+
end_session_endpoint: string;
|
|
134
|
+
prompt_values_supported: Prompt[];
|
|
135
|
+
issuer: string;
|
|
136
|
+
authorization_endpoint: string;
|
|
137
|
+
token_endpoint: string;
|
|
138
|
+
registration_endpoint: string;
|
|
139
|
+
scopes_supported?: string[] | undefined;
|
|
140
|
+
response_types_supported: "code"[];
|
|
141
|
+
response_modes_supported: "query"[];
|
|
142
|
+
grant_types_supported: GrantType[];
|
|
143
|
+
token_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[] | undefined;
|
|
144
|
+
token_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
|
|
145
|
+
service_documentation?: string | undefined;
|
|
146
|
+
ui_locales_supported?: string[] | undefined;
|
|
147
|
+
op_policy_uri?: string | undefined;
|
|
148
|
+
op_tos_uri?: string | undefined;
|
|
149
|
+
revocation_endpoint?: string | undefined;
|
|
150
|
+
revocation_endpoint_auth_methods_supported?: AuthMethod[] | undefined;
|
|
151
|
+
revocation_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
|
|
152
|
+
introspection_endpoint?: string | undefined;
|
|
153
|
+
introspection_endpoint_auth_methods_supported?: AuthMethod[] | undefined;
|
|
154
|
+
introspection_endpoint_auth_signing_alg_values_supported?: better_auth0.AssertionSigningAlgorithm[] | undefined;
|
|
155
|
+
code_challenge_methods_supported: "S256"[];
|
|
156
|
+
authorization_response_iss_parameter_supported?: boolean | undefined;
|
|
157
|
+
client_id_metadata_document_supported?: boolean | undefined;
|
|
71
158
|
id_token_signing_alg_values_supported: better_auth_plugins0.JWSAlgorithms[] | ["HS256"];
|
|
72
159
|
}>;
|
|
73
160
|
oauth2Authorize: better_call0.StrictEndpoint<"/oauth2/authorize", {
|
|
@@ -430,8 +517,10 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
430
517
|
access_token: string;
|
|
431
518
|
expires_in: number;
|
|
432
519
|
expires_at: number;
|
|
433
|
-
token_type:
|
|
520
|
+
token_type: "Bearer";
|
|
521
|
+
refresh_token: string | undefined;
|
|
434
522
|
scope: string;
|
|
523
|
+
id_token: string | undefined;
|
|
435
524
|
}>;
|
|
436
525
|
oauth2Introspect: better_call0.StrictEndpoint<"/oauth2/introspect", {
|
|
437
526
|
method: "POST";
|
|
@@ -315,6 +315,46 @@ type InternallySupportedScopes = "openid" | "profile" | "email" | "offline_acces
|
|
|
315
315
|
type Scope = LiteralString | InternallySupportedScopes;
|
|
316
316
|
type Prompt = "none" | "consent" | "login" | "create" | "select_account";
|
|
317
317
|
type AuthorizePrompt = Prompt | "login consent" | "select_account consent";
|
|
318
|
+
/**
|
|
319
|
+
* Describes how to resolve a `client_id` from an external source (a URL-based
|
|
320
|
+
* metadata document, a federated registry, an attestation header, etc.) and
|
|
321
|
+
* what fields that source contributes to discovery metadata.
|
|
322
|
+
*
|
|
323
|
+
* Plugins install one of these onto {@link OAuthOptions.clientDiscovery}.
|
|
324
|
+
* The host walks the configured entries in order and returns the first
|
|
325
|
+
* non-null `resolve()` result.
|
|
326
|
+
*/
|
|
327
|
+
interface ClientDiscovery<Scopes extends readonly Scope[] = InternallySupportedScopes[]> {
|
|
328
|
+
/**
|
|
329
|
+
* Stable identifier used in error messages and diagnostics. Convention
|
|
330
|
+
* is to match the plugin id (for example `"cimd"`).
|
|
331
|
+
*/
|
|
332
|
+
readonly id: string;
|
|
333
|
+
/**
|
|
334
|
+
* Return `true` if this discovery handles the given `client_id`. Called
|
|
335
|
+
* on every `getClient()` lookup for every configured discovery, so keep
|
|
336
|
+
* it cheap and synchronous.
|
|
337
|
+
*/
|
|
338
|
+
matches: (clientId: string) => boolean;
|
|
339
|
+
/**
|
|
340
|
+
* Resolve a client when this discovery matches. Receives the existing DB
|
|
341
|
+
* record (or `null`) so an implementation can decide between creating,
|
|
342
|
+
* refreshing, or passing through to the database result.
|
|
343
|
+
*
|
|
344
|
+
* Return:
|
|
345
|
+
* - a client record: `getClient()` returns it (creation / refresh / takeover).
|
|
346
|
+
* - `null`: `getClient()` falls through to the next matching discovery
|
|
347
|
+
* or to the database record (if any).
|
|
348
|
+
*/
|
|
349
|
+
resolve: (ctx: GenericEndpointContext, clientId: string, existing: SchemaClient<Scopes> | null) => Awaitable<SchemaClient<Scopes> | null>;
|
|
350
|
+
/**
|
|
351
|
+
* Fields merged into `/.well-known/oauth-authorization-server` and
|
|
352
|
+
* `/.well-known/openid-configuration` responses. Useful for advertising
|
|
353
|
+
* RFC-registered discovery flags like
|
|
354
|
+
* `client_id_metadata_document_supported`.
|
|
355
|
+
*/
|
|
356
|
+
discoveryMetadata?: Record<string, unknown>;
|
|
357
|
+
}
|
|
318
358
|
interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScopes[]> {
|
|
319
359
|
/**
|
|
320
360
|
* Custom schema definitions
|
|
@@ -412,10 +452,13 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
412
452
|
/**
|
|
413
453
|
* Allow unauthenticated dynamic client registration.
|
|
414
454
|
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
455
|
+
* When enabled, the `/oauth2/register` endpoint accepts requests
|
|
456
|
+
* without a session, but only for public clients
|
|
457
|
+
* (`token_endpoint_auth_method: "none"`).
|
|
458
|
+
*
|
|
459
|
+
* For verified client discovery (MCP), consider installing the
|
|
460
|
+
* `@better-auth/cimd` plugin, which verifies client identity through
|
|
461
|
+
* domain ownership via Client ID Metadata Documents.
|
|
419
462
|
*
|
|
420
463
|
* @default false
|
|
421
464
|
*/
|
|
@@ -426,6 +469,21 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
426
469
|
* @default false
|
|
427
470
|
*/
|
|
428
471
|
allowDynamicClientRegistration?: boolean;
|
|
472
|
+
/**
|
|
473
|
+
* Discovery implementations consulted by `getClient()` when resolving
|
|
474
|
+
* a `client_id`. Each entry decides whether it handles the `client_id`
|
|
475
|
+
* via {@link ClientDiscovery.matches}, then creates, refreshes, or
|
|
476
|
+
* passes on a client record. Entries run in order; the first one to
|
|
477
|
+
* return a client wins.
|
|
478
|
+
*
|
|
479
|
+
* Each entry also contributes {@link ClientDiscovery.discoveryMetadata}
|
|
480
|
+
* into the `/.well-known/oauth-authorization-server` and
|
|
481
|
+
* `/.well-known/openid-configuration` responses.
|
|
482
|
+
*
|
|
483
|
+
* Plugins such as `@better-auth/cimd` install an entry here at init
|
|
484
|
+
* time; users can also pass discovery implementations directly.
|
|
485
|
+
*/
|
|
486
|
+
clientDiscovery?: ClientDiscovery<Scopes> | ClientDiscovery<Scopes>[];
|
|
429
487
|
/**
|
|
430
488
|
* List of scopes for newly registered clients
|
|
431
489
|
* if not requested.
|
|
@@ -768,6 +826,34 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
768
826
|
resource?: string; /** oAuthClient metadata */
|
|
769
827
|
metadata?: Record<string, any>;
|
|
770
828
|
}) => Awaitable<Record<string, any>>;
|
|
829
|
+
/**
|
|
830
|
+
* Custom fields to include in the token response body.
|
|
831
|
+
*
|
|
832
|
+
* Unlike `customAccessTokenClaims` (which adds claims inside the JWT payload),
|
|
833
|
+
* this adds fields to the JSON response envelope alongside `access_token`,
|
|
834
|
+
* `token_type`, etc. Standard OAuth fields (`access_token`, `token_type`,
|
|
835
|
+
* `expires_in`, `expires_at`, `refresh_token`, `scope`, `id_token`) cannot
|
|
836
|
+
* be overridden.
|
|
837
|
+
*
|
|
838
|
+
* @param info - context that may be useful when creating custom fields
|
|
839
|
+
*/
|
|
840
|
+
customTokenResponseFields?: (info: {
|
|
841
|
+
/** The grant type being processed */grantType: GrantType;
|
|
842
|
+
/**
|
|
843
|
+
* The user, if applicable.
|
|
844
|
+
* Undefined for `client_credentials` (M2M, no user).
|
|
845
|
+
* Always present for `authorization_code` and `refresh_token`.
|
|
846
|
+
*/
|
|
847
|
+
user?: (User & Record<string, unknown>) | null; /** Scopes granted for this token */
|
|
848
|
+
scopes: Scopes; /** oAuthClient metadata */
|
|
849
|
+
metadata?: Record<string, any>;
|
|
850
|
+
/**
|
|
851
|
+
* The authorization code verification value.
|
|
852
|
+
* Only present for `authorization_code` grant. Contains the original
|
|
853
|
+
* authorization request parameters (`query`), `referenceId`, `sessionId`, etc.
|
|
854
|
+
*/
|
|
855
|
+
verificationValue?: VerificationValue;
|
|
856
|
+
}) => Awaitable<Record<string, unknown>>;
|
|
771
857
|
/**
|
|
772
858
|
* Overwrite specific /.well-known/openid-configuration
|
|
773
859
|
* values so they are not available publically.
|
|
@@ -1476,6 +1562,17 @@ interface AuthServerMetadata {
|
|
|
1476
1562
|
* @default true
|
|
1477
1563
|
*/
|
|
1478
1564
|
authorization_response_iss_parameter_supported?: boolean;
|
|
1565
|
+
/**
|
|
1566
|
+
* Whether the authorization server supports discovering clients via
|
|
1567
|
+
* [Client ID Metadata Documents](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/)
|
|
1568
|
+
* (an HTTPS URL as `client_id`).
|
|
1569
|
+
*
|
|
1570
|
+
* Set at runtime by the `@better-auth/cimd` plugin (or any other
|
|
1571
|
+
* `ClientDiscovery` that contributes this field via
|
|
1572
|
+
* {@link ClientDiscovery.discoveryMetadata}). oauth-provider never sets
|
|
1573
|
+
* it on its own.
|
|
1574
|
+
*/
|
|
1575
|
+
client_id_metadata_document_supported?: boolean;
|
|
1479
1576
|
}
|
|
1480
1577
|
/**
|
|
1481
1578
|
* Metadata returned by the openid-configuration endpoint:
|
|
@@ -1642,4 +1739,4 @@ interface ResourceServerMetadata {
|
|
|
1642
1739
|
dpop_bound_access_tokens_required?: boolean;
|
|
1643
1740
|
}
|
|
1644
1741
|
//#endregion
|
|
1645
|
-
export {
|
|
1742
|
+
export { Scope as _, OIDCMetadata as a, Awaitable as b, AuthorizePrompt as c, OAuthConsent as d, OAuthOpaqueAccessToken as f, SchemaClient as g, Prompt as h, OAuthClient as i, ClientDiscovery as l, OAuthRefreshToken as m, AuthServerMetadata as n, ResourceServerMetadata as o, OAuthOptions as p, GrantType as r, TokenEndpointAuthMethod as s, AuthMethod as t, OAuthAuthorizationQuery as u, StoreTokenType as v, VerificationValue as y };
|
|
@@ -89,17 +89,49 @@ async function verifyOAuthQueryParams(oauth_query, secret) {
|
|
|
89
89
|
async function getClient(ctx, options, clientId) {
|
|
90
90
|
const trustedClient = cachedTrustedClients.get(clientId);
|
|
91
91
|
if (trustedClient) return Object.assign({}, trustedClient);
|
|
92
|
-
|
|
92
|
+
let dbClient = await ctx.context.adapter.findOne({
|
|
93
93
|
model: options.schema?.oauthClient?.modelName ?? "oauthClient",
|
|
94
94
|
where: [{
|
|
95
95
|
field: "clientId",
|
|
96
96
|
value: clientId
|
|
97
97
|
}]
|
|
98
98
|
});
|
|
99
|
+
const discoveries = toClientDiscoveryArray(options.clientDiscovery);
|
|
100
|
+
for (const discovery of discoveries) {
|
|
101
|
+
if (!discovery.matches(clientId)) continue;
|
|
102
|
+
const resolved = await discovery.resolve(ctx, clientId, dbClient);
|
|
103
|
+
if (resolved) {
|
|
104
|
+
dbClient = resolved;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
99
108
|
if (dbClient && options.cachedTrustedClients?.has(clientId)) cachedTrustedClients.set(clientId, Object.assign({}, dbClient));
|
|
100
109
|
return dbClient;
|
|
101
110
|
}
|
|
102
111
|
/**
|
|
112
|
+
* Normalize the `clientDiscovery` option into an array. Accepts a single
|
|
113
|
+
* {@link ClientDiscovery}, an array of them, or `undefined`.
|
|
114
|
+
*
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
function toClientDiscoveryArray(discovery) {
|
|
118
|
+
if (!discovery) return [];
|
|
119
|
+
return Array.isArray(discovery) ? discovery : [discovery];
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Merge `discoveryMetadata` from every configured {@link ClientDiscovery}
|
|
123
|
+
* into a single object. Entries are spread in order; later entries override
|
|
124
|
+
* earlier ones on key collisions.
|
|
125
|
+
*
|
|
126
|
+
* @internal
|
|
127
|
+
*/
|
|
128
|
+
function mergeDiscoveryMetadata(discovery) {
|
|
129
|
+
return toClientDiscoveryArray(discovery).reduce((acc, d) => ({
|
|
130
|
+
...acc,
|
|
131
|
+
...d.discoveryMetadata ?? {}
|
|
132
|
+
}), {});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
103
135
|
* Default client secret hasher using SHA-256
|
|
104
136
|
*
|
|
105
137
|
* @internal
|
|
@@ -292,7 +324,7 @@ async function extractClientCredentials(ctx, opts, expectedAudience) {
|
|
|
292
324
|
error_description: "client_assertion cannot be combined with client_secret or Basic auth",
|
|
293
325
|
error: "invalid_client"
|
|
294
326
|
});
|
|
295
|
-
const { verifyClientAssertion: verify } = await import("./client-assertion-
|
|
327
|
+
const { verifyClientAssertion: verify } = await import("./client-assertion-CderPEmR.mjs").then((n) => n.t);
|
|
296
328
|
const result = await verify(ctx, opts, body.client_assertion, body.client_assertion_type, body.client_id, expectedAudience);
|
|
297
329
|
return {
|
|
298
330
|
method: "private_key_jwt",
|
|
@@ -414,4 +446,4 @@ function isPKCERequired(client, requestedScopes) {
|
|
|
414
446
|
return false;
|
|
415
447
|
}
|
|
416
448
|
//#endregion
|
|
417
|
-
export {
|
|
449
|
+
export { storeClientSecret as _, getClient as a, validateClientCredentials as b, getStoredToken as c, normalizeTimestampValue as d, parseClientMetadata as f, searchParamsToQuery as g, resolveSubjectIdentifier as h, extractClientCredentials as i, isPKCERequired as l, resolveSessionAuthTime as m, deleteFromPrompt as n, getJwtPlugin as o, parsePrompt as p, destructureCredentials as r, getOAuthProviderPlugin as s, decryptStoredClientSecret as t, mergeDiscoveryMetadata as u, storeToken as v, verifyOAuthQueryParams as x, toClientDiscoveryArray as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/oauth-provider",
|
|
3
|
-
"version": "1.7.0-beta.
|
|
3
|
+
"version": "1.7.0-beta.1",
|
|
4
4
|
"description": "An oauth provider plugin for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,15 +64,15 @@
|
|
|
64
64
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
65
65
|
"listhen": "^1.9.0",
|
|
66
66
|
"tsdown": "0.21.1",
|
|
67
|
-
"@better-auth/core": "1.7.0-beta.
|
|
68
|
-
"better-auth": "1.7.0-beta.
|
|
67
|
+
"@better-auth/core": "1.7.0-beta.1",
|
|
68
|
+
"better-auth": "1.7.0-beta.1"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"@better-auth/utils": "0.4.0",
|
|
72
72
|
"@better-fetch/fetch": "1.1.21",
|
|
73
73
|
"better-call": "1.3.5",
|
|
74
|
-
"@better-auth/core": "^1.7.0-beta.
|
|
75
|
-
"better-auth": "^1.7.0-beta.
|
|
74
|
+
"@better-auth/core": "^1.7.0-beta.1",
|
|
75
|
+
"better-auth": "^1.7.0-beta.1"
|
|
76
76
|
},
|
|
77
77
|
"scripts": {
|
|
78
78
|
"build": "tsdown",
|