@better-auth/oauth-provider 1.6.14 → 1.6.16
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 +14 -0
- 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 +1 -1
- package/dist/index.mjs +168 -179
- package/dist/{oauth-CUqnBdrR.d.mts → oauth-DZ80cNUW.d.mts} +4 -1
- package/dist/{utils-DoYEeMrg.mjs → utils-BKGiA6QL.mjs} +21 -2
- package/dist/{version-CrTJknzp.mjs → version-BJguNh6q.mjs} +1 -1
- package/package.json +7 -7
|
@@ -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,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
1
|
+
import { C as handleMcpErrors, a as getJwtPlugin, o as getOAuthProviderPlugin } from "./utils-BKGiA6QL.mjs";
|
|
2
|
+
import { t as PACKAGE_VERSION } from "./version-BJguNh6q.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
1
|
import { _ as Scope, a as OAuthClient, b as Awaitable, c as TokenEndpointAuthMethod, d as OAuthConsent, f as OAuthOpaqueAccessToken, g as SchemaClient, h as Prompt, i as GrantType, l as AuthorizePrompt, m as OAuthRefreshToken, n as AuthServerMetadata, o as OIDCMetadata, p as OAuthOptions, r as BearerMethodsSupported, s as ResourceServerMetadata, t as AuthMethod, u as OAuthAuthorizationQuery, v as StoreTokenType, y as VerificationValue } from "./oauth-D74mBkw6.mjs";
|
|
2
|
-
import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-
|
|
2
|
+
import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-DZ80cNUW.mjs";
|
|
3
3
|
import { verifyAccessToken } from "better-auth/oauth2";
|
|
4
4
|
import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
|
|
5
5
|
import { JWTPayload } from "jose";
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
3
|
-
import { APIError, createAuthEndpoint, createAuthMiddleware, getOAuthState, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
|
|
1
|
+
import { S as verifyOAuthQueryParams, _ as searchParamsToQuery, a as getJwtPlugin, b as storeToken, c as getStoredToken, d as parseClientMetadata, f as parsePrompt, g as resolveSubjectIdentifier, h as resolveSessionAuthTime, i as getClient, l as isPKCERequired, m as removePromptFromQuery, n as clientAllowsGrant, p as postLoginClearedParam, r as decryptStoredClientSecret, s as getSignedQueryIssuedAt, t as basicToClientCredentials, u as normalizeTimestampValue, v as signedQueryIssuedAtParam, w as mcpHandler, x as validateClientCredentials, y as storeClientSecret } from "./utils-BKGiA6QL.mjs";
|
|
2
|
+
import { t as PACKAGE_VERSION } from "./version-BJguNh6q.mjs";
|
|
3
|
+
import { APIError, createAuthEndpoint, createAuthMiddleware, dispatchAuthEndpoint, 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";
|
|
6
6
|
import { isBrowserFetchRequest } from "@better-auth/core/utils/fetch-metadata";
|
|
@@ -16,7 +16,7 @@ import { signJWT, toExpJWT } from "better-auth/plugins";
|
|
|
16
16
|
import { SignJWT, compactVerify, createLocalJWKSet, decodeJwt } from "jose";
|
|
17
17
|
import { SafeUrlSchema } from "@better-auth/core/utils/redirect-uri";
|
|
18
18
|
//#region src/consent.ts
|
|
19
|
-
async function consentEndpoint(ctx, opts) {
|
|
19
|
+
async function consentEndpoint(ctx, opts, authorize) {
|
|
20
20
|
const oauthRequest = await oAuthState.get();
|
|
21
21
|
const _query = oauthRequest?.query;
|
|
22
22
|
if (!_query) throw new APIError("BAD_REQUEST", {
|
|
@@ -47,11 +47,7 @@ async function consentEndpoint(ctx, opts) {
|
|
|
47
47
|
if (hasLoginPrompt && !hasSatisfiedLoginPrompt) {
|
|
48
48
|
ctx?.headers?.set("accept", "application/json");
|
|
49
49
|
ctx.query = searchParamsToQuery(query);
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
redirect: true,
|
|
53
|
-
url
|
|
54
|
-
};
|
|
50
|
+
return await authorize(ctx);
|
|
55
51
|
}
|
|
56
52
|
const referenceId = await opts.postLogin?.consentReferenceId?.({
|
|
57
53
|
user: session?.user,
|
|
@@ -106,11 +102,7 @@ async function consentEndpoint(ctx, opts) {
|
|
|
106
102
|
let authorizationQuery = removePromptFromQuery(query, "consent");
|
|
107
103
|
if (hasSatisfiedLoginPrompt) authorizationQuery = removePromptFromQuery(authorizationQuery, "login");
|
|
108
104
|
ctx.query = searchParamsToQuery(authorizationQuery);
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
redirect: true,
|
|
112
|
-
url
|
|
113
|
-
};
|
|
105
|
+
return await authorize(ctx, { postLogin: oauthRequest?.postLoginClearedForSession !== void 0 && oauthRequest.postLoginClearedForSession === session?.session.id });
|
|
114
106
|
}
|
|
115
107
|
function sessionSatisfiesLoginPrompt(sessionCreatedAt, signedQueryIssuedAt) {
|
|
116
108
|
if (!signedQueryIssuedAt) return false;
|
|
@@ -120,16 +112,16 @@ function sessionSatisfiesLoginPrompt(sessionCreatedAt, signedQueryIssuedAt) {
|
|
|
120
112
|
}
|
|
121
113
|
//#endregion
|
|
122
114
|
//#region src/continue.ts
|
|
123
|
-
async function continueEndpoint(ctx,
|
|
124
|
-
if (ctx.body.selected === true) return await selected(ctx,
|
|
125
|
-
else if (ctx.body.created === true) return await created(ctx,
|
|
126
|
-
else if (ctx.body.postLogin === true) return await postLogin(ctx,
|
|
115
|
+
async function continueEndpoint(ctx, authorize) {
|
|
116
|
+
if (ctx.body.selected === true) return await selected(ctx, authorize);
|
|
117
|
+
else if (ctx.body.created === true) return await created(ctx, authorize);
|
|
118
|
+
else if (ctx.body.postLogin === true) return await postLogin(ctx, authorize);
|
|
127
119
|
else throw new APIError("BAD_REQUEST", {
|
|
128
120
|
error_description: "Missing parameters",
|
|
129
121
|
error: "invalid_request"
|
|
130
122
|
});
|
|
131
123
|
}
|
|
132
|
-
async function selected(ctx,
|
|
124
|
+
async function selected(ctx, authorize) {
|
|
133
125
|
const _query = (await oAuthState.get())?.query;
|
|
134
126
|
if (!_query) throw new APIError("BAD_REQUEST", {
|
|
135
127
|
error_description: "missing oauth query",
|
|
@@ -137,13 +129,9 @@ async function selected(ctx, opts) {
|
|
|
137
129
|
});
|
|
138
130
|
ctx.headers?.set("accept", "application/json");
|
|
139
131
|
ctx.query = searchParamsToQuery(removePromptFromQuery(new URLSearchParams(_query), "select_account"));
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
redirect: true,
|
|
143
|
-
url
|
|
144
|
-
};
|
|
132
|
+
return await authorize(ctx);
|
|
145
133
|
}
|
|
146
|
-
async function created(ctx,
|
|
134
|
+
async function created(ctx, authorize) {
|
|
147
135
|
const _query = (await oAuthState.get())?.query;
|
|
148
136
|
if (!_query) throw new APIError("BAD_REQUEST", {
|
|
149
137
|
error_description: "missing oauth query",
|
|
@@ -152,14 +140,11 @@ async function created(ctx, opts) {
|
|
|
152
140
|
const query = new URLSearchParams(_query);
|
|
153
141
|
ctx.headers?.set("accept", "application/json");
|
|
154
142
|
ctx.query = searchParamsToQuery(removePromptFromQuery(query, "create"));
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
redirect: true,
|
|
158
|
-
url
|
|
159
|
-
};
|
|
143
|
+
return await authorize(ctx);
|
|
160
144
|
}
|
|
161
|
-
async function postLogin(ctx,
|
|
162
|
-
const
|
|
145
|
+
async function postLogin(ctx, authorize) {
|
|
146
|
+
const state = await oAuthState.get();
|
|
147
|
+
const _query = state?.query;
|
|
163
148
|
if (!_query) throw new APIError("BAD_REQUEST", {
|
|
164
149
|
error_description: "missing oauth query",
|
|
165
150
|
error: "invalid_request"
|
|
@@ -167,11 +152,8 @@ async function postLogin(ctx, opts) {
|
|
|
167
152
|
const query = new URLSearchParams(_query);
|
|
168
153
|
ctx.headers?.set("accept", "application/json");
|
|
169
154
|
ctx.query = searchParamsToQuery(query);
|
|
170
|
-
const
|
|
171
|
-
return {
|
|
172
|
-
redirect: true,
|
|
173
|
-
url
|
|
174
|
-
};
|
|
155
|
+
const session = await getSessionFromCtx(ctx);
|
|
156
|
+
return await authorize(ctx, { postLogin: state?.postLoginClearedForSession !== void 0 && state.postLoginClearedForSession === session?.session.id });
|
|
175
157
|
}
|
|
176
158
|
//#endregion
|
|
177
159
|
//#region src/types/zod.ts
|
|
@@ -523,7 +505,7 @@ async function createUserTokens(ctx, opts, params) {
|
|
|
523
505
|
return prev < curr ? prev : curr;
|
|
524
506
|
}, defaultExp) : defaultExp;
|
|
525
507
|
const audience = await checkResource(ctx, opts, scopes);
|
|
526
|
-
const isRefreshToken = user && (existingRefreshToken?.scopes?.includes("offline_access") || scopes.includes("offline_access"));
|
|
508
|
+
const isRefreshToken = user && clientAllowsGrant(client, "refresh_token") && (existingRefreshToken?.scopes?.includes("offline_access") || scopes.includes("offline_access"));
|
|
527
509
|
const isJwtAccessToken = audience && !opts.disableJwtPlugin;
|
|
528
510
|
const isIdToken = user && scopes.includes("openid");
|
|
529
511
|
const customFields = opts.customTokenResponseFields ? await opts.customTokenResponseFields({
|
|
@@ -638,7 +620,7 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
|
|
|
638
620
|
error: "invalid_scope"
|
|
639
621
|
});
|
|
640
622
|
/** Verify Client */
|
|
641
|
-
const client = await validateClientCredentials(ctx, opts, client_id, client_secret, scopes);
|
|
623
|
+
const client = await validateClientCredentials(ctx, opts, client_id, client_secret, scopes, "authorization_code");
|
|
642
624
|
if (isPKCERequired(client, (verificationValue.query?.scope)?.split(" ") || [])) {
|
|
643
625
|
if (!isAuthCodeWithPkce) throw new APIError("BAD_REQUEST", {
|
|
644
626
|
error_description: "PKCE is required for this client",
|
|
@@ -721,7 +703,7 @@ async function handleClientCredentialsGrant(ctx, opts) {
|
|
|
721
703
|
error_description: "Missing a required client_secret",
|
|
722
704
|
error: "invalid_grant"
|
|
723
705
|
});
|
|
724
|
-
const client = await validateClientCredentials(ctx, opts, client_id, client_secret);
|
|
706
|
+
const client = await validateClientCredentials(ctx, opts, client_id, client_secret, void 0, "client_credentials");
|
|
725
707
|
let requestedScopes = scope?.split(" ");
|
|
726
708
|
if (requestedScopes) {
|
|
727
709
|
const validScopes = new Set(client.scopes ?? opts.scopes);
|
|
@@ -804,7 +786,7 @@ async function handleRefreshTokenGrant(ctx, opts) {
|
|
|
804
786
|
error: "invalid_scope"
|
|
805
787
|
});
|
|
806
788
|
}
|
|
807
|
-
const client = await validateClientCredentials(ctx, opts, client_id, client_secret, requestedScopes ?? scopes);
|
|
789
|
+
const client = await validateClientCredentials(ctx, opts, client_id, client_secret, requestedScopes ?? scopes, "refresh_token");
|
|
808
790
|
const user = await ctx.context.internalAdapter.findUserById(refreshToken.userId);
|
|
809
791
|
if (!user) throw new APIError("BAD_REQUEST", {
|
|
810
792
|
error_description: "user not found",
|
|
@@ -862,12 +844,10 @@ async function validateJwtAccessToken(ctx, opts, token, clientId) {
|
|
|
862
844
|
}
|
|
863
845
|
throw new Error(error);
|
|
864
846
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
if (clientId && jwtPayload.azp !== clientId) return { active: false };
|
|
870
|
-
}
|
|
847
|
+
if (!jwtPayload.azp) return { active: false };
|
|
848
|
+
const client = await getClient(ctx, opts, jwtPayload.azp);
|
|
849
|
+
if (!client || client?.disabled) return { active: false };
|
|
850
|
+
if (clientId && jwtPayload.azp !== clientId) return { active: false };
|
|
871
851
|
const sessionId = jwtPayload.sid;
|
|
872
852
|
if (sessionId) {
|
|
873
853
|
const session = await ctx.context.adapter.findOne({
|
|
@@ -879,7 +859,7 @@ async function validateJwtAccessToken(ctx, opts, token, clientId) {
|
|
|
879
859
|
});
|
|
880
860
|
if (!session || session.expiresAt < /* @__PURE__ */ new Date()) jwtPayload.sid = void 0;
|
|
881
861
|
}
|
|
882
|
-
|
|
862
|
+
jwtPayload.client_id = jwtPayload.azp;
|
|
883
863
|
jwtPayload.active = true;
|
|
884
864
|
return jwtPayload;
|
|
885
865
|
}
|
|
@@ -2849,6 +2829,140 @@ const oauthProvider = (options) => {
|
|
|
2849
2829
|
}
|
|
2850
2830
|
if (isOpenIdConfigRequest) return { response: createMetadataResponse(oidcServerMetadata(endpointCtx, opts)) };
|
|
2851
2831
|
};
|
|
2832
|
+
const oauth2AuthorizeEndpoint = createAuthEndpoint("/oauth2/authorize", {
|
|
2833
|
+
method: "GET",
|
|
2834
|
+
query: z.object({
|
|
2835
|
+
response_type: z.enum(["code"]).optional(),
|
|
2836
|
+
client_id: z.string(),
|
|
2837
|
+
redirect_uri: SafeUrlSchema.optional(),
|
|
2838
|
+
scope: z.string().optional(),
|
|
2839
|
+
state: z.string().optional(),
|
|
2840
|
+
request_uri: z.string().optional(),
|
|
2841
|
+
code_challenge: z.string().optional(),
|
|
2842
|
+
code_challenge_method: z.enum(["S256"]).optional(),
|
|
2843
|
+
nonce: z.string().optional(),
|
|
2844
|
+
prompt: z.enum([
|
|
2845
|
+
"none",
|
|
2846
|
+
"consent",
|
|
2847
|
+
"login",
|
|
2848
|
+
"create",
|
|
2849
|
+
"select_account",
|
|
2850
|
+
"login consent",
|
|
2851
|
+
"select_account consent"
|
|
2852
|
+
]).optional()
|
|
2853
|
+
}),
|
|
2854
|
+
metadata: { openapi: {
|
|
2855
|
+
description: "Authorize an OAuth2 request",
|
|
2856
|
+
parameters: [
|
|
2857
|
+
{
|
|
2858
|
+
name: "response_type",
|
|
2859
|
+
in: "query",
|
|
2860
|
+
required: false,
|
|
2861
|
+
schema: { type: "string" },
|
|
2862
|
+
description: "OAuth2 response type (e.g., 'code')"
|
|
2863
|
+
},
|
|
2864
|
+
{
|
|
2865
|
+
name: "client_id",
|
|
2866
|
+
in: "query",
|
|
2867
|
+
required: true,
|
|
2868
|
+
schema: { type: "string" },
|
|
2869
|
+
description: "OAuth2 client ID"
|
|
2870
|
+
},
|
|
2871
|
+
{
|
|
2872
|
+
name: "redirect_uri",
|
|
2873
|
+
in: "query",
|
|
2874
|
+
required: false,
|
|
2875
|
+
schema: {
|
|
2876
|
+
type: "string",
|
|
2877
|
+
format: "uri"
|
|
2878
|
+
},
|
|
2879
|
+
description: "OAuth2 redirect URI"
|
|
2880
|
+
},
|
|
2881
|
+
{
|
|
2882
|
+
name: "scope",
|
|
2883
|
+
in: "query",
|
|
2884
|
+
required: false,
|
|
2885
|
+
schema: { type: "string" },
|
|
2886
|
+
description: "OAuth2 scopes (space-separated)"
|
|
2887
|
+
},
|
|
2888
|
+
{
|
|
2889
|
+
name: "state",
|
|
2890
|
+
in: "query",
|
|
2891
|
+
required: false,
|
|
2892
|
+
schema: { type: "string" },
|
|
2893
|
+
description: "OAuth2 state parameter"
|
|
2894
|
+
},
|
|
2895
|
+
{
|
|
2896
|
+
name: "request_uri",
|
|
2897
|
+
in: "query",
|
|
2898
|
+
required: false,
|
|
2899
|
+
schema: { type: "string" },
|
|
2900
|
+
description: "Pushed Authorization Request URI referencing stored parameters"
|
|
2901
|
+
},
|
|
2902
|
+
{
|
|
2903
|
+
name: "code_challenge",
|
|
2904
|
+
in: "query",
|
|
2905
|
+
required: false,
|
|
2906
|
+
schema: { type: "string" },
|
|
2907
|
+
description: "PKCE code challenge"
|
|
2908
|
+
},
|
|
2909
|
+
{
|
|
2910
|
+
name: "code_challenge_method",
|
|
2911
|
+
in: "query",
|
|
2912
|
+
required: false,
|
|
2913
|
+
schema: { type: "string" },
|
|
2914
|
+
description: "PKCE code challenge method"
|
|
2915
|
+
},
|
|
2916
|
+
{
|
|
2917
|
+
name: "nonce",
|
|
2918
|
+
in: "query",
|
|
2919
|
+
required: false,
|
|
2920
|
+
schema: { type: "string" },
|
|
2921
|
+
description: "OpenID Connect nonce"
|
|
2922
|
+
},
|
|
2923
|
+
{
|
|
2924
|
+
name: "prompt",
|
|
2925
|
+
in: "query",
|
|
2926
|
+
required: false,
|
|
2927
|
+
schema: { type: "string" },
|
|
2928
|
+
description: "OAuth2 prompt parameter"
|
|
2929
|
+
}
|
|
2930
|
+
],
|
|
2931
|
+
responses: {
|
|
2932
|
+
"302": {
|
|
2933
|
+
description: "Redirect to client with code or error",
|
|
2934
|
+
headers: { Location: {
|
|
2935
|
+
description: "Redirect URI with code or error",
|
|
2936
|
+
schema: {
|
|
2937
|
+
type: "string",
|
|
2938
|
+
format: "uri"
|
|
2939
|
+
}
|
|
2940
|
+
} }
|
|
2941
|
+
},
|
|
2942
|
+
"400": {
|
|
2943
|
+
description: "Invalid request",
|
|
2944
|
+
content: { "application/json": { schema: {
|
|
2945
|
+
type: "object",
|
|
2946
|
+
properties: {
|
|
2947
|
+
error: { type: "string" },
|
|
2948
|
+
error_description: { type: "string" },
|
|
2949
|
+
state: { type: "string" }
|
|
2950
|
+
},
|
|
2951
|
+
required: ["error"]
|
|
2952
|
+
} } }
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
} }
|
|
2956
|
+
}, async (ctx) => {
|
|
2957
|
+
return authorizeEndpoint(ctx, opts, ctx.authorizeSettings ?? { isAuthorize: true });
|
|
2958
|
+
});
|
|
2959
|
+
const runOAuth2Authorize = (ctx, settings) => dispatchAuthEndpoint(oauth2AuthorizeEndpoint, {
|
|
2960
|
+
...ctx,
|
|
2961
|
+
asResponse: false,
|
|
2962
|
+
returnHeaders: false,
|
|
2963
|
+
returnStatus: false,
|
|
2964
|
+
authorizeSettings: settings ?? {}
|
|
2965
|
+
});
|
|
2852
2966
|
return {
|
|
2853
2967
|
id: "oauth-provider",
|
|
2854
2968
|
version: PACKAGE_VERSION,
|
|
@@ -2915,7 +3029,7 @@ const oauthProvider = (options) => {
|
|
|
2915
3029
|
const acceptHeader = ctx.request?.headers?.get("accept")?.toLowerCase() ?? "";
|
|
2916
3030
|
if (!(secFetchMode === "navigate" || !secFetchMode && (acceptHeader.includes("text/html") || acceptHeader.includes("application/xhtml+xml")))) ctx.headers?.set("accept", "application/json");
|
|
2917
3031
|
ctx.query = searchParamsToQuery(removePromptFromQuery(query, "login"));
|
|
2918
|
-
return await
|
|
3032
|
+
return await runOAuth2Authorize(ctx);
|
|
2919
3033
|
})
|
|
2920
3034
|
}]
|
|
2921
3035
|
},
|
|
@@ -2940,133 +3054,7 @@ const oauthProvider = (options) => {
|
|
|
2940
3054
|
if (opts.scopes && !opts.scopes.includes("openid")) throw new APIError("NOT_FOUND");
|
|
2941
3055
|
return oidcServerMetadata(ctx, opts);
|
|
2942
3056
|
}),
|
|
2943
|
-
oauth2Authorize:
|
|
2944
|
-
method: "GET",
|
|
2945
|
-
query: z.object({
|
|
2946
|
-
response_type: z.enum(["code"]).optional(),
|
|
2947
|
-
client_id: z.string(),
|
|
2948
|
-
redirect_uri: SafeUrlSchema.optional(),
|
|
2949
|
-
scope: z.string().optional(),
|
|
2950
|
-
state: z.string().optional(),
|
|
2951
|
-
request_uri: z.string().optional(),
|
|
2952
|
-
code_challenge: z.string().optional(),
|
|
2953
|
-
code_challenge_method: z.enum(["S256"]).optional(),
|
|
2954
|
-
nonce: z.string().optional(),
|
|
2955
|
-
prompt: z.enum([
|
|
2956
|
-
"none",
|
|
2957
|
-
"consent",
|
|
2958
|
-
"login",
|
|
2959
|
-
"create",
|
|
2960
|
-
"select_account",
|
|
2961
|
-
"login consent",
|
|
2962
|
-
"select_account consent"
|
|
2963
|
-
]).optional()
|
|
2964
|
-
}),
|
|
2965
|
-
metadata: { openapi: {
|
|
2966
|
-
description: "Authorize an OAuth2 request",
|
|
2967
|
-
parameters: [
|
|
2968
|
-
{
|
|
2969
|
-
name: "response_type",
|
|
2970
|
-
in: "query",
|
|
2971
|
-
required: false,
|
|
2972
|
-
schema: { type: "string" },
|
|
2973
|
-
description: "OAuth2 response type (e.g., 'code')"
|
|
2974
|
-
},
|
|
2975
|
-
{
|
|
2976
|
-
name: "client_id",
|
|
2977
|
-
in: "query",
|
|
2978
|
-
required: true,
|
|
2979
|
-
schema: { type: "string" },
|
|
2980
|
-
description: "OAuth2 client ID"
|
|
2981
|
-
},
|
|
2982
|
-
{
|
|
2983
|
-
name: "redirect_uri",
|
|
2984
|
-
in: "query",
|
|
2985
|
-
required: false,
|
|
2986
|
-
schema: {
|
|
2987
|
-
type: "string",
|
|
2988
|
-
format: "uri"
|
|
2989
|
-
},
|
|
2990
|
-
description: "OAuth2 redirect URI"
|
|
2991
|
-
},
|
|
2992
|
-
{
|
|
2993
|
-
name: "scope",
|
|
2994
|
-
in: "query",
|
|
2995
|
-
required: false,
|
|
2996
|
-
schema: { type: "string" },
|
|
2997
|
-
description: "OAuth2 scopes (space-separated)"
|
|
2998
|
-
},
|
|
2999
|
-
{
|
|
3000
|
-
name: "state",
|
|
3001
|
-
in: "query",
|
|
3002
|
-
required: false,
|
|
3003
|
-
schema: { type: "string" },
|
|
3004
|
-
description: "OAuth2 state parameter"
|
|
3005
|
-
},
|
|
3006
|
-
{
|
|
3007
|
-
name: "request_uri",
|
|
3008
|
-
in: "query",
|
|
3009
|
-
required: false,
|
|
3010
|
-
schema: { type: "string" },
|
|
3011
|
-
description: "Pushed Authorization Request URI referencing stored parameters"
|
|
3012
|
-
},
|
|
3013
|
-
{
|
|
3014
|
-
name: "code_challenge",
|
|
3015
|
-
in: "query",
|
|
3016
|
-
required: false,
|
|
3017
|
-
schema: { type: "string" },
|
|
3018
|
-
description: "PKCE code challenge"
|
|
3019
|
-
},
|
|
3020
|
-
{
|
|
3021
|
-
name: "code_challenge_method",
|
|
3022
|
-
in: "query",
|
|
3023
|
-
required: false,
|
|
3024
|
-
schema: { type: "string" },
|
|
3025
|
-
description: "PKCE code challenge method"
|
|
3026
|
-
},
|
|
3027
|
-
{
|
|
3028
|
-
name: "nonce",
|
|
3029
|
-
in: "query",
|
|
3030
|
-
required: false,
|
|
3031
|
-
schema: { type: "string" },
|
|
3032
|
-
description: "OpenID Connect nonce"
|
|
3033
|
-
},
|
|
3034
|
-
{
|
|
3035
|
-
name: "prompt",
|
|
3036
|
-
in: "query",
|
|
3037
|
-
required: false,
|
|
3038
|
-
schema: { type: "string" },
|
|
3039
|
-
description: "OAuth2 prompt parameter"
|
|
3040
|
-
}
|
|
3041
|
-
],
|
|
3042
|
-
responses: {
|
|
3043
|
-
"302": {
|
|
3044
|
-
description: "Redirect to client with code or error",
|
|
3045
|
-
headers: { Location: {
|
|
3046
|
-
description: "Redirect URI with code or error",
|
|
3047
|
-
schema: {
|
|
3048
|
-
type: "string",
|
|
3049
|
-
format: "uri"
|
|
3050
|
-
}
|
|
3051
|
-
} }
|
|
3052
|
-
},
|
|
3053
|
-
"400": {
|
|
3054
|
-
description: "Invalid request",
|
|
3055
|
-
content: { "application/json": { schema: {
|
|
3056
|
-
type: "object",
|
|
3057
|
-
properties: {
|
|
3058
|
-
error: { type: "string" },
|
|
3059
|
-
error_description: { type: "string" },
|
|
3060
|
-
state: { type: "string" }
|
|
3061
|
-
},
|
|
3062
|
-
required: ["error"]
|
|
3063
|
-
} } }
|
|
3064
|
-
}
|
|
3065
|
-
}
|
|
3066
|
-
} }
|
|
3067
|
-
}, async (ctx) => {
|
|
3068
|
-
return authorizeEndpoint(ctx, opts, { isAuthorize: true });
|
|
3069
|
-
}),
|
|
3057
|
+
oauth2Authorize: oauth2AuthorizeEndpoint,
|
|
3070
3058
|
oauth2Consent: createAuthEndpoint("/oauth2/consent", {
|
|
3071
3059
|
method: "POST",
|
|
3072
3060
|
body: z.object({
|
|
@@ -3091,7 +3079,7 @@ const oauthProvider = (options) => {
|
|
|
3091
3079
|
} }
|
|
3092
3080
|
} }
|
|
3093
3081
|
}, async (ctx) => {
|
|
3094
|
-
return consentEndpoint(ctx, opts);
|
|
3082
|
+
return consentEndpoint(ctx, opts, runOAuth2Authorize);
|
|
3095
3083
|
}),
|
|
3096
3084
|
oauth2Continue: createAuthEndpoint("/oauth2/continue", {
|
|
3097
3085
|
method: "POST",
|
|
@@ -3118,7 +3106,7 @@ const oauthProvider = (options) => {
|
|
|
3118
3106
|
} }
|
|
3119
3107
|
} }
|
|
3120
3108
|
}, async (ctx) => {
|
|
3121
|
-
return continueEndpoint(ctx,
|
|
3109
|
+
return continueEndpoint(ctx, runOAuth2Authorize);
|
|
3122
3110
|
}),
|
|
3123
3111
|
oauth2Token: createAuthEndpoint("/oauth2/token", {
|
|
3124
3112
|
method: "POST",
|
|
@@ -3432,7 +3420,7 @@ const oauthProvider = (options) => {
|
|
|
3432
3420
|
return revokeEndpoint(ctx, opts);
|
|
3433
3421
|
}),
|
|
3434
3422
|
oauth2UserInfo: createAuthEndpoint("/oauth2/userinfo", {
|
|
3435
|
-
method: "GET",
|
|
3423
|
+
method: ["GET", "POST"],
|
|
3436
3424
|
metadata: { openapi: {
|
|
3437
3425
|
description: "Get OpenID Connect user information (UserInfo endpoint)",
|
|
3438
3426
|
security: [{ bearerAuth: [] }, { OAuth2: [
|
|
@@ -3868,6 +3856,7 @@ async function authorizeEndpoint(ctx, opts, settings) {
|
|
|
3868
3856
|
const client = await getClient(ctx, opts, query.client_id);
|
|
3869
3857
|
if (!client) return handleRedirect(ctx, getErrorURL(ctx, "invalid_client", "client_id is required"));
|
|
3870
3858
|
if (client.disabled) return handleRedirect(ctx, getErrorURL(ctx, "client_disabled", "client is disabled"));
|
|
3859
|
+
if (!clientAllowsGrant(client, "authorization_code")) return handleRedirect(ctx, getErrorURL(ctx, "unauthorized_client", "client is not authorized to use the authorization_code grant"));
|
|
3871
3860
|
if (!client.redirectUris?.find((url) => {
|
|
3872
3861
|
if (url === query.redirect_uri) return true;
|
|
3873
3862
|
try {
|
|
@@ -234,6 +234,9 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
234
234
|
}, {
|
|
235
235
|
redirect: boolean;
|
|
236
236
|
url: string;
|
|
237
|
+
} | {
|
|
238
|
+
redirect: boolean;
|
|
239
|
+
url: string;
|
|
237
240
|
}>;
|
|
238
241
|
oauth2Continue: better_call0.StrictEndpoint<"/oauth2/continue", {
|
|
239
242
|
method: "POST";
|
|
@@ -661,7 +664,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
661
664
|
};
|
|
662
665
|
}, null | undefined>;
|
|
663
666
|
oauth2UserInfo: better_call0.StrictEndpoint<"/oauth2/userinfo", {
|
|
664
|
-
method: "GET";
|
|
667
|
+
method: ("GET" | "POST")[];
|
|
665
668
|
metadata: {
|
|
666
669
|
openapi: {
|
|
667
670
|
description: string;
|
|
@@ -272,12 +272,27 @@ function basicToClientCredentials(authorization) {
|
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
/**
|
|
275
|
+
* Whether a client is allowed to use a given grant type.
|
|
276
|
+
*
|
|
277
|
+
* A client's registered `grantTypes` defaults to the documented default
|
|
278
|
+
* `["authorization_code"]` when unset (see client registration). Refresh tokens
|
|
279
|
+
* are only ever issued through the authorization_code flow, so a client allowed
|
|
280
|
+
* to use `authorization_code` is implicitly allowed to use `refresh_token`.
|
|
281
|
+
*
|
|
282
|
+
* @internal
|
|
283
|
+
*/
|
|
284
|
+
function clientAllowsGrant(client, grantType) {
|
|
285
|
+
const allowedGrants = client.grantTypes && client.grantTypes.length > 0 ? client.grantTypes : ["authorization_code"];
|
|
286
|
+
if (grantType === "refresh_token" && allowedGrants.includes("authorization_code")) return true;
|
|
287
|
+
return allowedGrants.includes(grantType);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
275
290
|
* Validates client credentials failing on mismatches
|
|
276
291
|
* and incorrectly provided information
|
|
277
292
|
*
|
|
278
293
|
* @internal
|
|
279
294
|
*/
|
|
280
|
-
async function validateClientCredentials(ctx, options, clientId, clientSecret, scopes) {
|
|
295
|
+
async function validateClientCredentials(ctx, options, clientId, clientSecret, scopes, grantType) {
|
|
281
296
|
const client = await getClient(ctx, options, clientId);
|
|
282
297
|
if (!client) throw new APIError$1("BAD_REQUEST", {
|
|
283
298
|
error_description: "missing client",
|
|
@@ -306,6 +321,10 @@ async function validateClientCredentials(ctx, options, clientId, clientSecret, s
|
|
|
306
321
|
error: "invalid_scope"
|
|
307
322
|
});
|
|
308
323
|
}
|
|
324
|
+
if (grantType && !clientAllowsGrant(client, grantType)) throw new APIError$1("BAD_REQUEST", {
|
|
325
|
+
error_description: `client is not authorized to use grant type ${grantType}`,
|
|
326
|
+
error: "unauthorized_client"
|
|
327
|
+
});
|
|
309
328
|
return client;
|
|
310
329
|
}
|
|
311
330
|
/**
|
|
@@ -417,4 +436,4 @@ function isPKCERequired(client, requestedScopes) {
|
|
|
417
436
|
return false;
|
|
418
437
|
}
|
|
419
438
|
//#endregion
|
|
420
|
-
export {
|
|
439
|
+
export { handleMcpErrors as C, verifyOAuthQueryParams as S, searchParamsToQuery as _, getJwtPlugin as a, storeToken as b, getStoredToken as c, parseClientMetadata as d, parsePrompt as f, resolveSubjectIdentifier as g, resolveSessionAuthTime as h, getClient as i, isPKCERequired as l, removePromptFromQuery as m, clientAllowsGrant as n, getOAuthProviderPlugin as o, postLoginClearedParam as p, decryptStoredClientSecret as r, getSignedQueryIssuedAt as s, basicToClientCredentials as t, normalizeTimestampValue as u, signedQueryIssuedAtParam as v, mcpHandler as w, validateClientCredentials as x, storeClientSecret as y };
|
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.16",
|
|
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.16",
|
|
68
|
+
"better-auth": "1.6.16"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"@better-auth/utils": "0.4.1",
|
|
72
|
-
"@better-fetch/fetch": "1.
|
|
73
|
-
"better-call": "1.3.
|
|
74
|
-
"@better-auth/core": "^1.6.
|
|
75
|
-
"better-auth": "^1.6.
|
|
72
|
+
"@better-fetch/fetch": "1.2.2",
|
|
73
|
+
"better-call": "1.3.6",
|
|
74
|
+
"@better-auth/core": "^1.6.16",
|
|
75
|
+
"better-auth": "^1.6.16"
|
|
76
76
|
},
|
|
77
77
|
"scripts": {
|
|
78
78
|
"build": "tsdown",
|