@better-auth/oauth-provider 1.6.2 → 1.6.3
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-resource.d.mts +1 -1
- package/dist/client-resource.mjs +1 -1
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +177 -125
- package/dist/{oauth-CetEXi_Z.d.mts → oauth-DH61OMT6.d.mts} +4 -2
- package/dist/{oauth-Cc0nzj5Q.d.mts → oauth-IpUqx-_N.d.mts} +28 -0
- package/dist/{version-Usf6Oz_M.mjs → version-BlcZ64XB.mjs} +1 -1
- package/package.json +5 -5
package/dist/client-resource.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as getJwtPlugin, o as getOAuthProviderPlugin, y as handleMcpErrors } from "./utils-B9Pj9EPf.mjs";
|
|
2
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
2
|
+
import { t as PACKAGE_VERSION } from "./version-BlcZ64XB.mjs";
|
|
3
3
|
import { verifyAccessToken } from "better-auth/oauth2";
|
|
4
4
|
import { APIError } from "better-call";
|
|
5
5
|
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 Awaitable, a as ResourceServerMetadata, c as OAuthConsent, d as OAuthRefreshToken, f as Prompt, g as VerificationValue, h as StoreTokenType, i as OIDCMetadata, l as OAuthOpaqueAccessToken, m as Scope, n as GrantType, o as AuthorizePrompt, p as SchemaClient, r as OAuthClient, s as OAuthAuthorizationQuery, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-
|
|
2
|
-
import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-
|
|
1
|
+
import { _ as Awaitable, a as ResourceServerMetadata, c as OAuthConsent, d as OAuthRefreshToken, f as Prompt, g as VerificationValue, h as StoreTokenType, i as OIDCMetadata, l as OAuthOpaqueAccessToken, m as Scope, n as GrantType, o as AuthorizePrompt, p as SchemaClient, r as OAuthClient, s as OAuthAuthorizationQuery, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-IpUqx-_N.mjs";
|
|
2
|
+
import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-DH61OMT6.mjs";
|
|
3
3
|
import { verifyAccessToken } from "better-auth/oauth2";
|
|
4
4
|
import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
|
|
5
5
|
import { JWTPayload } from "jose";
|
|
@@ -44,7 +44,7 @@ declare const oauthProviderAuthServerMetadata: <Auth extends {
|
|
|
44
44
|
};
|
|
45
45
|
}>(auth: Auth, opts?: {
|
|
46
46
|
headers?: HeadersInit;
|
|
47
|
-
}) => (
|
|
47
|
+
}) => (request: Request) => Promise<Response>;
|
|
48
48
|
/**
|
|
49
49
|
* Provides an exportable `/.well-known/openid-configuration`.
|
|
50
50
|
*
|
|
@@ -59,6 +59,6 @@ declare const oauthProviderOpenIdConfigMetadata: <Auth extends {
|
|
|
59
59
|
};
|
|
60
60
|
}>(auth: Auth, opts?: {
|
|
61
61
|
headers?: HeadersInit;
|
|
62
|
-
}) => (
|
|
62
|
+
}) => (request: Request) => Promise<Response>;
|
|
63
63
|
//#endregion
|
|
64
64
|
export { AuthServerMetadata, AuthorizePrompt, OAuthAuthorizationQuery, OAuthClient, OAuthConsent, OAuthOpaqueAccessToken, OAuthOptions, OAuthRefreshToken, OIDCMetadata, Prompt, ResourceServerMetadata, SchemaClient, Scope, StoreTokenType, VerificationValue, authServerMetadata, getOAuthProviderState, mcpHandler, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _ as validateClientCredentials, a as getJwtPlugin, b as mcpHandler, c as isPKCERequired, d as parsePrompt, f as resolveSessionAuthTime, g as storeToken, h as storeClientSecret, i as getClient, l as normalizeTimestampValue, m as searchParamsToQuery, n as decryptStoredClientSecret, p as resolveSubjectIdentifier, r as deleteFromPrompt, s as getStoredToken, t as basicToClientCredentials, u as parseClientMetadata, v as verifyOAuthQueryParams } from "./utils-B9Pj9EPf.mjs";
|
|
2
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
2
|
+
import { t as PACKAGE_VERSION } from "./version-BlcZ64XB.mjs";
|
|
3
3
|
import { APIError, createAuthEndpoint, createAuthMiddleware, getOAuthState, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
|
|
4
4
|
import { generateCodeChallenge, getJwks, verifyJwsAccessToken } from "better-auth/oauth2";
|
|
5
5
|
import { APIError as APIError$1 } from "better-call";
|
|
@@ -153,6 +153,81 @@ async function postLogin(ctx, opts) {
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
//#endregion
|
|
156
|
+
//#region src/types/zod.ts
|
|
157
|
+
const DANGEROUS_SCHEMES = [
|
|
158
|
+
"javascript:",
|
|
159
|
+
"data:",
|
|
160
|
+
"vbscript:"
|
|
161
|
+
];
|
|
162
|
+
function isLocalhost(hostname) {
|
|
163
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname.endsWith(".localhost");
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Runtime schema for OAuthAuthorizationQuery.
|
|
167
|
+
* Uses passthrough to tolerate fields added by future extensions (PAR, FPA, etc.)
|
|
168
|
+
*/
|
|
169
|
+
const oauthAuthorizationQuerySchema = z.object({
|
|
170
|
+
response_type: z.literal("code").optional(),
|
|
171
|
+
request_uri: z.string().optional(),
|
|
172
|
+
redirect_uri: z.string(),
|
|
173
|
+
scope: z.string().optional(),
|
|
174
|
+
state: z.string(),
|
|
175
|
+
client_id: z.string(),
|
|
176
|
+
prompt: z.string().optional(),
|
|
177
|
+
display: z.string().optional(),
|
|
178
|
+
ui_locales: z.string().optional(),
|
|
179
|
+
max_age: z.coerce.number().optional(),
|
|
180
|
+
acr_values: z.string().optional(),
|
|
181
|
+
login_hint: z.string().optional(),
|
|
182
|
+
id_token_hint: z.string().optional(),
|
|
183
|
+
code_challenge: z.string().optional(),
|
|
184
|
+
code_challenge_method: z.literal("S256").optional(),
|
|
185
|
+
nonce: z.string().optional()
|
|
186
|
+
}).passthrough();
|
|
187
|
+
/**
|
|
188
|
+
* Runtime schema for the authorization code verification value.
|
|
189
|
+
* Validates structure on deserialization from the JSON blob stored in the DB.
|
|
190
|
+
* Uses passthrough so future fields (e.g. from authorization challenge) don't break parsing.
|
|
191
|
+
*/
|
|
192
|
+
const verificationValueSchema = z.object({
|
|
193
|
+
type: z.literal("authorization_code"),
|
|
194
|
+
query: oauthAuthorizationQuerySchema,
|
|
195
|
+
sessionId: z.string(),
|
|
196
|
+
userId: z.string(),
|
|
197
|
+
referenceId: z.string().optional(),
|
|
198
|
+
authTime: z.number().optional()
|
|
199
|
+
}).passthrough();
|
|
200
|
+
/**
|
|
201
|
+
* Reusable URL validation for OAuth redirect URIs.
|
|
202
|
+
* - Blocks dangerous schemes (javascript:, data:, vbscript:)
|
|
203
|
+
* - For http/https: requires HTTPS (HTTP allowed only for localhost)
|
|
204
|
+
* - Allows custom schemes for mobile apps (e.g., myapp://callback)
|
|
205
|
+
*/
|
|
206
|
+
const SafeUrlSchema = z.url().superRefine((val, ctx) => {
|
|
207
|
+
if (!URL.canParse(val)) {
|
|
208
|
+
ctx.addIssue({
|
|
209
|
+
code: "custom",
|
|
210
|
+
message: "URL must be parseable",
|
|
211
|
+
fatal: true
|
|
212
|
+
});
|
|
213
|
+
return z.NEVER;
|
|
214
|
+
}
|
|
215
|
+
const u = new URL(val);
|
|
216
|
+
if (DANGEROUS_SCHEMES.includes(u.protocol)) {
|
|
217
|
+
ctx.addIssue({
|
|
218
|
+
code: "custom",
|
|
219
|
+
message: "URL cannot use javascript:, data:, or vbscript: scheme"
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (u.protocol === "http:" || u.protocol === "https:") {
|
|
224
|
+
if (u.protocol === "http:" && !isLocalhost(u.hostname)) ctx.addIssue({
|
|
225
|
+
code: "custom",
|
|
226
|
+
message: "Redirect URI must use HTTPS (HTTP allowed only for localhost)"
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
//#endregion
|
|
156
231
|
//#region src/userinfo.ts
|
|
157
232
|
/**
|
|
158
233
|
* Provides shared /userinfo and id_token claims functionality
|
|
@@ -265,7 +340,7 @@ async function createJwtAccessToken(ctx, opts, user, client, audience, scopes, r
|
|
|
265
340
|
options: jwtPluginOptions,
|
|
266
341
|
payload: {
|
|
267
342
|
...customClaims,
|
|
268
|
-
sub: user
|
|
343
|
+
sub: user?.id,
|
|
269
344
|
aud: typeof audience === "string" ? audience : audience?.length === 1 ? audience.at(0) : audience,
|
|
270
345
|
azp: client.clientId,
|
|
271
346
|
scope: scopes.join(" "),
|
|
@@ -399,21 +474,29 @@ async function checkResource(ctx, opts, scopes) {
|
|
|
399
474
|
}
|
|
400
475
|
return audience?.length === 1 ? audience.at(0) : audience;
|
|
401
476
|
}
|
|
402
|
-
async function createUserTokens(ctx, opts,
|
|
477
|
+
async function createUserTokens(ctx, opts, params) {
|
|
478
|
+
const { client, scopes, user, grantType, referenceId, sessionId, nonce, refreshToken: existingRefreshToken, authTime, verificationValue } = params;
|
|
403
479
|
const iat = Math.floor(Date.now() / 1e3);
|
|
404
|
-
const defaultExp = iat + (opts.accessTokenExpiresIn ?? 3600);
|
|
480
|
+
const defaultExp = iat + (user ? opts.accessTokenExpiresIn ?? 3600 : opts.m2mAccessTokenExpiresIn ?? 3600);
|
|
405
481
|
const exp = opts.scopeExpirations ? scopes.map((sc) => opts.scopeExpirations?.[sc] ? toExpJWT(opts.scopeExpirations[sc], iat) : defaultExp).reduce((prev, curr) => {
|
|
406
482
|
return prev < curr ? prev : curr;
|
|
407
483
|
}, defaultExp) : defaultExp;
|
|
408
484
|
const audience = await checkResource(ctx, opts, scopes);
|
|
409
|
-
const isRefreshToken =
|
|
485
|
+
const isRefreshToken = user && (existingRefreshToken?.scopes?.includes("offline_access") || scopes.includes("offline_access"));
|
|
410
486
|
const isJwtAccessToken = audience && !opts.disableJwtPlugin;
|
|
411
|
-
const isIdToken = scopes.includes("openid");
|
|
412
|
-
const
|
|
487
|
+
const isIdToken = user && scopes.includes("openid");
|
|
488
|
+
const customFields = opts.customTokenResponseFields ? await opts.customTokenResponseFields({
|
|
489
|
+
grantType,
|
|
490
|
+
user,
|
|
491
|
+
scopes,
|
|
492
|
+
metadata: parseClientMetadata(client.metadata),
|
|
493
|
+
verificationValue
|
|
494
|
+
}) : void 0;
|
|
495
|
+
const earlyRefreshToken = isRefreshToken && user && !isJwtAccessToken ? await createRefreshToken(ctx, opts, user, referenceId, client, scopes, {
|
|
413
496
|
iat,
|
|
414
497
|
exp: iat + (opts.refreshTokenExpiresIn ?? 2592e3),
|
|
415
498
|
sid: sessionId
|
|
416
|
-
},
|
|
499
|
+
}, existingRefreshToken, authTime) : void 0;
|
|
417
500
|
const [accessToken, refreshToken, idToken] = await Promise.all([
|
|
418
501
|
isJwtAccessToken ? createJwtAccessToken(ctx, opts, user, client, audience, scopes, referenceId, {
|
|
419
502
|
iat,
|
|
@@ -424,14 +507,15 @@ async function createUserTokens(ctx, opts, client, scopes, user, referenceId, se
|
|
|
424
507
|
exp,
|
|
425
508
|
sid: sessionId
|
|
426
509
|
}, referenceId, earlyRefreshToken?.id),
|
|
427
|
-
earlyRefreshToken ? earlyRefreshToken : isRefreshToken ? createRefreshToken(ctx, opts, user, referenceId, client, scopes, {
|
|
510
|
+
earlyRefreshToken ? earlyRefreshToken : isRefreshToken && user ? createRefreshToken(ctx, opts, user, referenceId, client, scopes, {
|
|
428
511
|
iat,
|
|
429
512
|
exp: iat + (opts.refreshTokenExpiresIn ?? 2592e3),
|
|
430
513
|
sid: sessionId
|
|
431
|
-
},
|
|
432
|
-
isIdToken ? createIdToken(ctx, opts, user, client, scopes, nonce, sessionId, authTime) : void 0
|
|
514
|
+
}, existingRefreshToken, authTime) : void 0,
|
|
515
|
+
isIdToken && user ? createIdToken(ctx, opts, user, client, scopes, nonce, sessionId, authTime) : void 0
|
|
433
516
|
]);
|
|
434
517
|
return ctx.json({
|
|
518
|
+
...customFields,
|
|
435
519
|
access_token: accessToken,
|
|
436
520
|
expires_in: exp - iat,
|
|
437
521
|
expires_at: exp,
|
|
@@ -447,7 +531,6 @@ async function createUserTokens(ctx, opts, client, scopes, user, referenceId, se
|
|
|
447
531
|
/** Checks verification value */
|
|
448
532
|
async function checkVerificationValue(ctx, opts, code, client_id, redirect_uri) {
|
|
449
533
|
const verification = await ctx.context.internalAdapter.findVerificationValue(await storeToken(opts.storeTokens, code, "authorization_code"));
|
|
450
|
-
const verificationValue = verification ? JSON.parse(verification?.value) : void 0;
|
|
451
534
|
if (!verification) throw new APIError("UNAUTHORIZED", {
|
|
452
535
|
error_description: "Invalid code",
|
|
453
536
|
error: "invalid_verification"
|
|
@@ -457,22 +540,25 @@ async function checkVerificationValue(ctx, opts, code, client_id, redirect_uri)
|
|
|
457
540
|
error_description: "code expired",
|
|
458
541
|
error: "invalid_verification"
|
|
459
542
|
});
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
|
|
543
|
+
let rawValue;
|
|
544
|
+
try {
|
|
545
|
+
rawValue = JSON.parse(verification.value);
|
|
546
|
+
} catch {
|
|
547
|
+
throw new APIError("UNAUTHORIZED", {
|
|
548
|
+
error_description: "malformed verification value",
|
|
549
|
+
error: "invalid_verification"
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
const parsed = verificationValueSchema.safeParse(rawValue);
|
|
553
|
+
if (!parsed.success) throw new APIError("UNAUTHORIZED", {
|
|
554
|
+
error_description: "malformed verification value",
|
|
466
555
|
error: "invalid_verification"
|
|
467
556
|
});
|
|
557
|
+
const verificationValue = parsed.data;
|
|
468
558
|
if (verificationValue.query.client_id !== client_id) throw new APIError("UNAUTHORIZED", {
|
|
469
559
|
error_description: "invalid client_id",
|
|
470
560
|
error: "invalid_client"
|
|
471
561
|
});
|
|
472
|
-
if (!verificationValue.userId) throw new APIError("BAD_REQUEST", {
|
|
473
|
-
error_description: "missing user_id on challenge",
|
|
474
|
-
error: "invalid_user"
|
|
475
|
-
});
|
|
476
562
|
if (verificationValue.query?.redirect_uri && verificationValue.query?.redirect_uri !== redirect_uri) throw new APIError("BAD_REQUEST", {
|
|
477
563
|
error_description: "redirect_uri mismatch",
|
|
478
564
|
error: "invalid_request"
|
|
@@ -565,7 +651,17 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
|
|
|
565
651
|
error: "invalid_request"
|
|
566
652
|
});
|
|
567
653
|
const authTime = verificationValue.authTime != null ? normalizeTimestampValue(verificationValue.authTime) : resolveSessionAuthTime(session);
|
|
568
|
-
return createUserTokens(ctx, opts,
|
|
654
|
+
return createUserTokens(ctx, opts, {
|
|
655
|
+
client,
|
|
656
|
+
scopes: verificationValue.query.scope?.split(" ") ?? [],
|
|
657
|
+
user,
|
|
658
|
+
grantType: "authorization_code",
|
|
659
|
+
referenceId: verificationValue.referenceId,
|
|
660
|
+
sessionId: session.id,
|
|
661
|
+
nonce: verificationValue.query?.nonce,
|
|
662
|
+
authTime,
|
|
663
|
+
verificationValue
|
|
664
|
+
});
|
|
569
665
|
}
|
|
570
666
|
/**
|
|
571
667
|
* Grant that allows direct access to an API using the application's credentials
|
|
@@ -608,43 +704,11 @@ async function handleClientCredentialsGrant(ctx, opts) {
|
|
|
608
704
|
});
|
|
609
705
|
}
|
|
610
706
|
if (!requestedScopes) requestedScopes = client.scopes ?? opts.clientCredentialGrantDefaultScopes ?? opts.scopes ?? [];
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const iat = Math.floor(Date.now() / 1e3);
|
|
614
|
-
const defaultExp = iat + (opts.m2mAccessTokenExpiresIn ?? 3600);
|
|
615
|
-
const exp = opts.scopeExpirations && requestedScopes ? requestedScopes.map((sc) => opts.scopeExpirations?.[sc] ? toExpJWT(opts.scopeExpirations[sc], iat) : defaultExp).reduce((prev, curr) => {
|
|
616
|
-
return prev < curr ? prev : curr;
|
|
617
|
-
}, defaultExp) : defaultExp;
|
|
618
|
-
const customClaims = opts.customAccessTokenClaims ? await opts.customAccessTokenClaims({
|
|
707
|
+
return createUserTokens(ctx, opts, {
|
|
708
|
+
client,
|
|
619
709
|
scopes: requestedScopes,
|
|
620
|
-
|
|
621
|
-
metadata: parseClientMetadata(client.metadata)
|
|
622
|
-
}) : {};
|
|
623
|
-
const accessToken = audience && !opts.disableJwtPlugin ? await signJWT(ctx, {
|
|
624
|
-
options: jwtPluginOptions,
|
|
625
|
-
payload: {
|
|
626
|
-
...customClaims,
|
|
627
|
-
aud: audience,
|
|
628
|
-
azp: client.clientId,
|
|
629
|
-
scope: requestedScopes.join(" "),
|
|
630
|
-
iss: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL,
|
|
631
|
-
iat,
|
|
632
|
-
exp
|
|
633
|
-
}
|
|
634
|
-
}) : await createOpaqueAccessToken(ctx, opts, void 0, client, requestedScopes, {
|
|
635
|
-
iat,
|
|
636
|
-
exp
|
|
710
|
+
grantType: "client_credentials"
|
|
637
711
|
});
|
|
638
|
-
return ctx.json({
|
|
639
|
-
access_token: accessToken,
|
|
640
|
-
expires_in: exp - iat,
|
|
641
|
-
expires_at: exp,
|
|
642
|
-
token_type: "Bearer",
|
|
643
|
-
scope: requestedScopes.join(" ")
|
|
644
|
-
}, { headers: {
|
|
645
|
-
"Cache-Control": "no-store",
|
|
646
|
-
Pragma: "no-cache"
|
|
647
|
-
} });
|
|
648
712
|
}
|
|
649
713
|
/**
|
|
650
714
|
* Obtains new Session Jwt and Refresh Tokens using a refresh token
|
|
@@ -720,7 +784,16 @@ async function handleRefreshTokenGrant(ctx, opts) {
|
|
|
720
784
|
error: "invalid_request"
|
|
721
785
|
});
|
|
722
786
|
const authTime = refreshToken.authTime != null ? normalizeTimestampValue(refreshToken.authTime) : void 0;
|
|
723
|
-
return createUserTokens(ctx, opts,
|
|
787
|
+
return createUserTokens(ctx, opts, {
|
|
788
|
+
client,
|
|
789
|
+
scopes: requestedScopes ?? scopes,
|
|
790
|
+
user,
|
|
791
|
+
grantType: "refresh_token",
|
|
792
|
+
referenceId: refreshToken.referenceId,
|
|
793
|
+
sessionId: refreshToken.sessionId,
|
|
794
|
+
refreshToken,
|
|
795
|
+
authTime
|
|
796
|
+
});
|
|
724
797
|
}
|
|
725
798
|
//#endregion
|
|
726
799
|
//#region src/introspect.ts
|
|
@@ -1102,6 +1175,21 @@ const publicSessionMiddleware = (opts) => createAuthMiddleware(async (ctx) => {
|
|
|
1102
1175
|
});
|
|
1103
1176
|
//#endregion
|
|
1104
1177
|
//#region src/register.ts
|
|
1178
|
+
/**
|
|
1179
|
+
* Resolves the auth method and type for unauthenticated DCR.
|
|
1180
|
+
* Overrides confidential methods to "none" per RFC 7591 Section 3.2.1.
|
|
1181
|
+
* When overriding, clears type "web" since it is only valid for confidential clients.
|
|
1182
|
+
*/
|
|
1183
|
+
function resolveUnauthenticatedAuth(body) {
|
|
1184
|
+
if (body.token_endpoint_auth_method === "none") return {
|
|
1185
|
+
tokenEndpointAuthMethod: "none",
|
|
1186
|
+
type: body.type
|
|
1187
|
+
};
|
|
1188
|
+
return {
|
|
1189
|
+
tokenEndpointAuthMethod: "none",
|
|
1190
|
+
type: body.type === "web" ? void 0 : body.type
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1105
1193
|
async function registerEndpoint(ctx, opts) {
|
|
1106
1194
|
if (!opts.allowDynamicClientRegistration) throw new APIError("FORBIDDEN", {
|
|
1107
1195
|
error: "access_denied",
|
|
@@ -1113,12 +1201,16 @@ async function registerEndpoint(ctx, opts) {
|
|
|
1113
1201
|
error: "invalid_token",
|
|
1114
1202
|
error_description: "Authentication required for client registration"
|
|
1115
1203
|
});
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1204
|
+
if (!session) {
|
|
1205
|
+
if (body.grant_types?.includes("client_credentials")) throw new APIError("BAD_REQUEST", {
|
|
1206
|
+
error: "invalid_client_metadata",
|
|
1207
|
+
error_description: "client_credentials grant requires authenticated registration"
|
|
1208
|
+
});
|
|
1209
|
+
const resolved = resolveUnauthenticatedAuth(body);
|
|
1210
|
+
body.token_endpoint_auth_method = resolved.tokenEndpointAuthMethod;
|
|
1211
|
+
body.type = resolved.type;
|
|
1212
|
+
}
|
|
1213
|
+
if (!body.scope) body.scope = (opts.clientRegistrationDefaultScopes ?? opts.scopes)?.join(" ");
|
|
1122
1214
|
return createOAuthClientEndpoint(ctx, opts, { isRegister: true });
|
|
1123
1215
|
}
|
|
1124
1216
|
async function checkOAuthClient(client, opts, settings) {
|
|
@@ -1309,46 +1401,6 @@ function schemaToOAuth(input) {
|
|
|
1309
1401
|
};
|
|
1310
1402
|
}
|
|
1311
1403
|
//#endregion
|
|
1312
|
-
//#region src/types/zod.ts
|
|
1313
|
-
const DANGEROUS_SCHEMES = [
|
|
1314
|
-
"javascript:",
|
|
1315
|
-
"data:",
|
|
1316
|
-
"vbscript:"
|
|
1317
|
-
];
|
|
1318
|
-
function isLocalhost(hostname) {
|
|
1319
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname.endsWith(".localhost");
|
|
1320
|
-
}
|
|
1321
|
-
/**
|
|
1322
|
-
* Reusable URL validation for OAuth redirect URIs.
|
|
1323
|
-
* - Blocks dangerous schemes (javascript:, data:, vbscript:)
|
|
1324
|
-
* - For http/https: requires HTTPS (HTTP allowed only for localhost)
|
|
1325
|
-
* - Allows custom schemes for mobile apps (e.g., myapp://callback)
|
|
1326
|
-
*/
|
|
1327
|
-
const SafeUrlSchema = z.url().superRefine((val, ctx) => {
|
|
1328
|
-
if (!URL.canParse(val)) {
|
|
1329
|
-
ctx.addIssue({
|
|
1330
|
-
code: "custom",
|
|
1331
|
-
message: "URL must be parseable",
|
|
1332
|
-
fatal: true
|
|
1333
|
-
});
|
|
1334
|
-
return z.NEVER;
|
|
1335
|
-
}
|
|
1336
|
-
const u = new URL(val);
|
|
1337
|
-
if (DANGEROUS_SCHEMES.includes(u.protocol)) {
|
|
1338
|
-
ctx.addIssue({
|
|
1339
|
-
code: "custom",
|
|
1340
|
-
message: "URL cannot use javascript:, data:, or vbscript: scheme"
|
|
1341
|
-
});
|
|
1342
|
-
return;
|
|
1343
|
-
}
|
|
1344
|
-
if (u.protocol === "http:" || u.protocol === "https:") {
|
|
1345
|
-
if (u.protocol === "http:" && !isLocalhost(u.hostname)) ctx.addIssue({
|
|
1346
|
-
code: "custom",
|
|
1347
|
-
message: "Redirect URI must use HTTPS (HTTP allowed only for localhost)"
|
|
1348
|
-
});
|
|
1349
|
-
}
|
|
1350
|
-
});
|
|
1351
|
-
//#endregion
|
|
1352
1404
|
//#region src/oauthClient/endpoints.ts
|
|
1353
1405
|
async function getClientEndpoint(ctx, opts) {
|
|
1354
1406
|
const session = await getSessionFromCtx(ctx);
|
|
@@ -3955,6 +4007,16 @@ function oidcServerMetadata(ctx, opts) {
|
|
|
3955
4007
|
]
|
|
3956
4008
|
};
|
|
3957
4009
|
}
|
|
4010
|
+
const METADATA_CACHE_CONTROL = "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400";
|
|
4011
|
+
function metadataResponse(body, extraHeaders) {
|
|
4012
|
+
const headers = new Headers(extraHeaders);
|
|
4013
|
+
if (!headers.has("Cache-Control")) headers.set("Cache-Control", METADATA_CACHE_CONTROL);
|
|
4014
|
+
headers.set("Content-Type", "application/json");
|
|
4015
|
+
return new Response(JSON.stringify(body), {
|
|
4016
|
+
status: 200,
|
|
4017
|
+
headers
|
|
4018
|
+
});
|
|
4019
|
+
}
|
|
3958
4020
|
/**
|
|
3959
4021
|
* Provides an exportable `/.well-known/oauth-authorization-server`.
|
|
3960
4022
|
*
|
|
@@ -3964,16 +4026,11 @@ function oidcServerMetadata(ctx, opts) {
|
|
|
3964
4026
|
* @external
|
|
3965
4027
|
*/
|
|
3966
4028
|
const oauthProviderAuthServerMetadata = (auth, opts) => {
|
|
3967
|
-
return async (
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
"Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
|
|
3973
|
-
...opts?.headers,
|
|
3974
|
-
"Content-Type": "application/json"
|
|
3975
|
-
}
|
|
3976
|
-
});
|
|
4029
|
+
return async (request) => {
|
|
4030
|
+
return metadataResponse(await auth.api.getOAuthServerConfig({
|
|
4031
|
+
request,
|
|
4032
|
+
asResponse: false
|
|
4033
|
+
}), opts?.headers);
|
|
3977
4034
|
};
|
|
3978
4035
|
};
|
|
3979
4036
|
/**
|
|
@@ -3985,16 +4042,11 @@ const oauthProviderAuthServerMetadata = (auth, opts) => {
|
|
|
3985
4042
|
* @external
|
|
3986
4043
|
*/
|
|
3987
4044
|
const oauthProviderOpenIdConfigMetadata = (auth, opts) => {
|
|
3988
|
-
return async (
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
"Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
|
|
3994
|
-
...opts?.headers,
|
|
3995
|
-
"Content-Type": "application/json"
|
|
3996
|
-
}
|
|
3997
|
-
});
|
|
4045
|
+
return async (request) => {
|
|
4046
|
+
return metadataResponse(await auth.api.getOpenIdConfig({
|
|
4047
|
+
request,
|
|
4048
|
+
asResponse: false
|
|
4049
|
+
}), opts?.headers);
|
|
3998
4050
|
};
|
|
3999
4051
|
};
|
|
4000
4052
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as OAuthConsent, i as OIDCMetadata, m as Scope, r as OAuthClient, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-
|
|
1
|
+
import { c as OAuthConsent, i as OIDCMetadata, m as Scope, r as OAuthClient, t as AuthServerMetadata, u as OAuthOptions } from "./oauth-IpUqx-_N.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";
|
|
@@ -428,8 +428,10 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
428
428
|
access_token: string;
|
|
429
429
|
expires_in: number;
|
|
430
430
|
expires_at: number;
|
|
431
|
-
token_type:
|
|
431
|
+
token_type: "Bearer";
|
|
432
|
+
refresh_token: string | undefined;
|
|
432
433
|
scope: string;
|
|
434
|
+
id_token: string | undefined;
|
|
433
435
|
}>;
|
|
434
436
|
oauth2Introspect: better_call0.StrictEndpoint<"/oauth2/introspect", {
|
|
435
437
|
method: "POST";
|
|
@@ -752,6 +752,34 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
752
752
|
resource?: string; /** oAuthClient metadata */
|
|
753
753
|
metadata?: Record<string, any>;
|
|
754
754
|
}) => Awaitable<Record<string, any>>;
|
|
755
|
+
/**
|
|
756
|
+
* Custom fields to include in the token response body.
|
|
757
|
+
*
|
|
758
|
+
* Unlike `customAccessTokenClaims` (which adds claims inside the JWT payload),
|
|
759
|
+
* this adds fields to the JSON response envelope alongside `access_token`,
|
|
760
|
+
* `token_type`, etc. Standard OAuth fields (`access_token`, `token_type`,
|
|
761
|
+
* `expires_in`, `expires_at`, `refresh_token`, `scope`, `id_token`) cannot
|
|
762
|
+
* be overridden.
|
|
763
|
+
*
|
|
764
|
+
* @param info - context that may be useful when creating custom fields
|
|
765
|
+
*/
|
|
766
|
+
customTokenResponseFields?: (info: {
|
|
767
|
+
/** The grant type being processed */grantType: GrantType;
|
|
768
|
+
/**
|
|
769
|
+
* The user, if applicable.
|
|
770
|
+
* Undefined for `client_credentials` (M2M, no user).
|
|
771
|
+
* Always present for `authorization_code` and `refresh_token`.
|
|
772
|
+
*/
|
|
773
|
+
user?: (User & Record<string, unknown>) | null; /** Scopes granted for this token */
|
|
774
|
+
scopes: Scopes; /** oAuthClient metadata */
|
|
775
|
+
metadata?: Record<string, any>;
|
|
776
|
+
/**
|
|
777
|
+
* The authorization code verification value.
|
|
778
|
+
* Only present for `authorization_code` grant. Contains the original
|
|
779
|
+
* authorization request parameters (`query`), `referenceId`, `sessionId`, etc.
|
|
780
|
+
*/
|
|
781
|
+
verificationValue?: VerificationValue;
|
|
782
|
+
}) => Awaitable<Record<string, unknown>>;
|
|
755
783
|
/**
|
|
756
784
|
* Overwrite specific /.well-known/openid-configuration
|
|
757
785
|
* values so they are not available publically.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/oauth-provider",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
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.6.
|
|
68
|
-
"better-auth": "1.6.
|
|
67
|
+
"@better-auth/core": "1.6.3",
|
|
68
|
+
"better-auth": "1.6.3"
|
|
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.6.
|
|
75
|
-
"better-auth": "^1.6.
|
|
74
|
+
"@better-auth/core": "^1.6.3",
|
|
75
|
+
"better-auth": "^1.6.3"
|
|
76
76
|
},
|
|
77
77
|
"scripts": {
|
|
78
78
|
"build": "tsdown",
|