@better-auth/oauth-provider 1.5.0-beta.8 → 1.5.0
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/README.md +17 -0
- package/dist/client-resource.d.mts +6 -8
- package/dist/client-resource.mjs +4 -3
- package/dist/client-resource.mjs.map +1 -0
- package/dist/client.d.mts +4 -3
- package/dist/client.mjs +2 -1
- package/dist/client.mjs.map +1 -0
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +678 -523
- package/dist/index.mjs.map +1 -0
- package/dist/{oauth-BrFoF22H.d.mts → oauth-7Jc-EFsq.d.mts} +118 -19
- package/dist/{oauth-BW67CKnu.d.mts → oauth-C_QoLKZA.d.mts} +55 -12
- package/dist/{utils-CUVT0Bep.mjs → utils-D6kv_BUA.mjs} +54 -20
- package/dist/utils-D6kv_BUA.mjs.map +1 -0
- package/package.json +38 -35
|
@@ -114,6 +114,10 @@ declare const schema: {
|
|
|
114
114
|
type: "string";
|
|
115
115
|
required: false;
|
|
116
116
|
};
|
|
117
|
+
requirePKCE: {
|
|
118
|
+
type: "boolean";
|
|
119
|
+
required: false;
|
|
120
|
+
};
|
|
117
121
|
referenceId: {
|
|
118
122
|
type: "string";
|
|
119
123
|
required: false;
|
|
@@ -174,6 +178,10 @@ declare const schema: {
|
|
|
174
178
|
type: "date";
|
|
175
179
|
required: false;
|
|
176
180
|
};
|
|
181
|
+
authTime: {
|
|
182
|
+
type: "date";
|
|
183
|
+
required: false;
|
|
184
|
+
};
|
|
177
185
|
scopes: {
|
|
178
186
|
type: "string[]";
|
|
179
187
|
required: true;
|
|
@@ -694,12 +702,10 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
694
702
|
* @returns Additional claims for userinfo request
|
|
695
703
|
*/
|
|
696
704
|
customUserInfoClaims?: (info: {
|
|
697
|
-
/** The user object */
|
|
698
|
-
user: User & Record<string, unknown>;
|
|
705
|
+
/** The user object */user: User & Record<string, unknown>;
|
|
699
706
|
/** The scopes from the access token used
|
|
700
707
|
* in the /userinfo request (matches jwt.scopes) */
|
|
701
|
-
scopes: Scopes;
|
|
702
|
-
/** The access token payload used in the /userinfo request */
|
|
708
|
+
scopes: Scopes; /** The access token payload used in the /userinfo request */
|
|
703
709
|
jwt: JWTPayload;
|
|
704
710
|
}) => Awaitable<Record<string, any>>;
|
|
705
711
|
/**
|
|
@@ -713,11 +719,8 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
713
719
|
* @param info - context that may be useful when creating custom claims
|
|
714
720
|
*/
|
|
715
721
|
customIdTokenClaims?: (info: {
|
|
716
|
-
/** The user object if token is associated to a user. */
|
|
717
|
-
|
|
718
|
-
/** Scopes granted for this token */
|
|
719
|
-
scopes: Scopes;
|
|
720
|
-
/** oAuthClient metadata */
|
|
722
|
+
/** The user object if token is associated to a user. */user: User & Record<string, unknown>; /** Scopes granted for this token */
|
|
723
|
+
scopes: Scopes; /** oAuthClient metadata */
|
|
721
724
|
metadata?: Record<string, any>;
|
|
722
725
|
}) => Awaitable<Record<string, any>>;
|
|
723
726
|
/**
|
|
@@ -734,15 +737,10 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
734
737
|
* @param info - context that may be useful when creating custom claims
|
|
735
738
|
*/
|
|
736
739
|
customAccessTokenClaims?: (info: {
|
|
737
|
-
/** The user object if token is associated to a user. Null if user doesn't exist. Undefined if user not applicable. */
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
|
|
741
|
-
/** Scopes granted for this token */
|
|
742
|
-
scopes: Scopes;
|
|
743
|
-
/** The resource requesting. Provided by the token endpoint. */
|
|
744
|
-
resource?: string;
|
|
745
|
-
/** oAuthClient metadata */
|
|
740
|
+
/** The user object if token is associated to a user. Null if user doesn't exist. Undefined if user not applicable. */user?: (User & Record<string, unknown>) | null; /** reference of the consent/authorization */
|
|
741
|
+
referenceId?: string; /** Scopes granted for this token */
|
|
742
|
+
scopes: Scopes; /** The resource requesting. Provided by the token endpoint. */
|
|
743
|
+
resource?: string; /** oAuthClient metadata */
|
|
746
744
|
metadata?: Record<string, any>;
|
|
747
745
|
}) => Awaitable<Record<string, any>>;
|
|
748
746
|
/**
|
|
@@ -869,6 +867,74 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
869
867
|
* @default false
|
|
870
868
|
*/
|
|
871
869
|
disableJwtPlugin?: boolean;
|
|
870
|
+
/**
|
|
871
|
+
* Rate limit configuration for OAuth endpoints.
|
|
872
|
+
*
|
|
873
|
+
* Each endpoint can be configured with a `window` (in seconds) and `max` requests.
|
|
874
|
+
* Set to `false` to disable rate limiting for a specific endpoint.
|
|
875
|
+
*
|
|
876
|
+
* @default
|
|
877
|
+
* ```ts
|
|
878
|
+
* {
|
|
879
|
+
* token: { window: 60, max: 20 },
|
|
880
|
+
* authorize: { window: 60, max: 30 },
|
|
881
|
+
* introspect: { window: 60, max: 100 },
|
|
882
|
+
* revoke: { window: 60, max: 30 },
|
|
883
|
+
* register: { window: 60, max: 5 },
|
|
884
|
+
* userinfo: { window: 60, max: 60 },
|
|
885
|
+
* }
|
|
886
|
+
* ```
|
|
887
|
+
*/
|
|
888
|
+
rateLimit?: {
|
|
889
|
+
/**
|
|
890
|
+
* Rate limit for /oauth2/token endpoint
|
|
891
|
+
* @default { window: 60, max: 20 }
|
|
892
|
+
*/
|
|
893
|
+
token?: {
|
|
894
|
+
window: number;
|
|
895
|
+
max: number;
|
|
896
|
+
} | false;
|
|
897
|
+
/**
|
|
898
|
+
* Rate limit for /oauth2/authorize endpoint
|
|
899
|
+
* @default { window: 60, max: 30 }
|
|
900
|
+
*/
|
|
901
|
+
authorize?: {
|
|
902
|
+
window: number;
|
|
903
|
+
max: number;
|
|
904
|
+
} | false;
|
|
905
|
+
/**
|
|
906
|
+
* Rate limit for /oauth2/introspect endpoint
|
|
907
|
+
* @default { window: 60, max: 100 }
|
|
908
|
+
*/
|
|
909
|
+
introspect?: {
|
|
910
|
+
window: number;
|
|
911
|
+
max: number;
|
|
912
|
+
} | false;
|
|
913
|
+
/**
|
|
914
|
+
* Rate limit for /oauth2/revoke endpoint
|
|
915
|
+
* @default { window: 60, max: 30 }
|
|
916
|
+
*/
|
|
917
|
+
revoke?: {
|
|
918
|
+
window: number;
|
|
919
|
+
max: number;
|
|
920
|
+
} | false;
|
|
921
|
+
/**
|
|
922
|
+
* Rate limit for /oauth2/register endpoint
|
|
923
|
+
* @default { window: 60, max: 5 }
|
|
924
|
+
*/
|
|
925
|
+
register?: {
|
|
926
|
+
window: number;
|
|
927
|
+
max: number;
|
|
928
|
+
} | false;
|
|
929
|
+
/**
|
|
930
|
+
* Rate limit for /oauth2/userinfo endpoint
|
|
931
|
+
* @default { window: 60, max: 60 }
|
|
932
|
+
*/
|
|
933
|
+
userinfo?: {
|
|
934
|
+
window: number;
|
|
935
|
+
max: number;
|
|
936
|
+
} | false;
|
|
937
|
+
};
|
|
872
938
|
}
|
|
873
939
|
interface OAuthAuthorizationQuery {
|
|
874
940
|
/**
|
|
@@ -991,6 +1057,7 @@ interface VerificationValue {
|
|
|
991
1057
|
sessionId: string;
|
|
992
1058
|
userId: string;
|
|
993
1059
|
referenceId?: string;
|
|
1060
|
+
authTime?: number;
|
|
994
1061
|
}
|
|
995
1062
|
/**
|
|
996
1063
|
* Client registered values as used within the plugin
|
|
@@ -1082,6 +1149,15 @@ interface SchemaClient<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
1082
1149
|
* - user-agent-based - A user-agent-based application (public client)
|
|
1083
1150
|
*/
|
|
1084
1151
|
type?: "web" | "native" | "user-agent-based";
|
|
1152
|
+
/**
|
|
1153
|
+
* Whether this client requires PKCE for authorization code flow.
|
|
1154
|
+
*
|
|
1155
|
+
* @default true
|
|
1156
|
+
*
|
|
1157
|
+
* Note: PKCE is always required for public clients and when
|
|
1158
|
+
* requesting offline_access scope, regardless of this setting.
|
|
1159
|
+
*/
|
|
1160
|
+
requirePKCE?: boolean;
|
|
1085
1161
|
/** Used to indicate if consent screen can be skipped */
|
|
1086
1162
|
skipConsent?: boolean;
|
|
1087
1163
|
/** Used to enable client to logout via the `/oauth2/end-session` endpoint */
|
|
@@ -1155,6 +1231,11 @@ interface OAuthRefreshToken<Scopes extends readonly Scope[] = InternallySupporte
|
|
|
1155
1231
|
* When token was revoked. If set, token is considered a replay attack.
|
|
1156
1232
|
*/
|
|
1157
1233
|
revoked?: Date;
|
|
1234
|
+
/**
|
|
1235
|
+
* The time the user originally authenticated.
|
|
1236
|
+
* Persisted so refreshed ID tokens can include a correct `auth_time` claim.
|
|
1237
|
+
*/
|
|
1238
|
+
authTime?: Date;
|
|
1158
1239
|
/**
|
|
1159
1240
|
* Scopes granted for this refresh token.
|
|
1160
1241
|
*
|
|
@@ -1331,6 +1412,14 @@ interface AuthServerMetadata {
|
|
|
1331
1412
|
* @default ["S256"]
|
|
1332
1413
|
*/
|
|
1333
1414
|
code_challenge_methods_supported: "S256"[];
|
|
1415
|
+
/**
|
|
1416
|
+
* Boolean value specifying whether the authorization server provides
|
|
1417
|
+
* the iss parameter in the authorization response (RFC 9207)
|
|
1418
|
+
*
|
|
1419
|
+
* @see https://datatracker.ietf.org/doc/html/rfc9207
|
|
1420
|
+
* @default true
|
|
1421
|
+
*/
|
|
1422
|
+
authorization_response_iss_parameter_supported?: boolean;
|
|
1334
1423
|
}
|
|
1335
1424
|
/**
|
|
1336
1425
|
* Metadata returned by the openid-configuration endpoint:
|
|
@@ -1438,6 +1527,15 @@ interface OAuthClient {
|
|
|
1438
1527
|
disabled?: boolean;
|
|
1439
1528
|
skip_consent?: boolean;
|
|
1440
1529
|
enable_end_session?: boolean;
|
|
1530
|
+
/**
|
|
1531
|
+
* Whether this client requires PKCE for authorization code flow.
|
|
1532
|
+
*
|
|
1533
|
+
* @default true
|
|
1534
|
+
*
|
|
1535
|
+
* Note: PKCE is always required for public clients and when
|
|
1536
|
+
* requesting offline_access scope, regardless of this setting.
|
|
1537
|
+
*/
|
|
1538
|
+
require_pkce?: boolean;
|
|
1441
1539
|
reference_id?: string;
|
|
1442
1540
|
[key: string]: unknown;
|
|
1443
1541
|
}
|
|
@@ -1479,4 +1577,5 @@ interface ResourceServerMetadata {
|
|
|
1479
1577
|
dpop_bound_access_tokens_required?: boolean;
|
|
1480
1578
|
}
|
|
1481
1579
|
//#endregion
|
|
1482
|
-
export { Awaitable as _, ResourceServerMetadata as a, OAuthConsent as c, OAuthRefreshToken as d, Prompt as f, VerificationValue as g, StoreTokenType as h, OIDCMetadata as i, OAuthOpaqueAccessToken as l, Scope as m, GrantType as n, AuthorizePrompt as o, SchemaClient as p, OAuthClient as r, OAuthAuthorizationQuery as s, AuthServerMetadata as t, OAuthOptions as u };
|
|
1580
|
+
export { Awaitable as _, ResourceServerMetadata as a, OAuthConsent as c, OAuthRefreshToken as d, Prompt as f, VerificationValue as g, StoreTokenType as h, OIDCMetadata as i, OAuthOpaqueAccessToken as l, Scope as m, GrantType as n, AuthorizePrompt as o, SchemaClient as p, OAuthClient as r, OAuthAuthorizationQuery as s, AuthServerMetadata as t, OAuthOptions as u };
|
|
1581
|
+
//# sourceMappingURL=oauth-7Jc-EFsq.d.mts.map
|
|
@@ -1,19 +1,22 @@
|
|
|
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-7Jc-EFsq.mjs";
|
|
2
2
|
import * as better_call0 from "better-call";
|
|
3
3
|
import "@better-auth/core/context";
|
|
4
4
|
import * as z from "zod";
|
|
5
5
|
import * as better_auth_plugins0 from "better-auth/plugins";
|
|
6
|
-
import * as
|
|
6
|
+
import * as jose from "jose";
|
|
7
7
|
import * as better_auth0 from "better-auth";
|
|
8
8
|
|
|
9
9
|
//#region src/oauth.d.ts
|
|
10
10
|
declare module "@better-auth/core" {
|
|
11
|
-
interface BetterAuthPluginRegistry<
|
|
11
|
+
interface BetterAuthPluginRegistry<AuthOptions, Options> {
|
|
12
12
|
"oauth-provider": {
|
|
13
13
|
creator: typeof oauthProvider;
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
declare const getOAuthProviderState: () => Promise<{
|
|
18
|
+
query?: string;
|
|
19
|
+
} | null>;
|
|
17
20
|
/**
|
|
18
21
|
* oAuth 2.1 provider plugin for Better Auth.
|
|
19
22
|
*
|
|
@@ -83,6 +86,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
83
86
|
}>>;
|
|
84
87
|
nonce: z.ZodOptional<z.ZodString>;
|
|
85
88
|
prompt: z.ZodOptional<z.ZodEnum<{
|
|
89
|
+
none: "none";
|
|
86
90
|
consent: "consent";
|
|
87
91
|
login: "login";
|
|
88
92
|
create: "create";
|
|
@@ -221,7 +225,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
221
225
|
};
|
|
222
226
|
}, {
|
|
223
227
|
redirect: boolean;
|
|
224
|
-
|
|
228
|
+
url: string;
|
|
225
229
|
}>;
|
|
226
230
|
oauth2Continue: better_call0.StrictEndpoint<"/oauth2/continue", {
|
|
227
231
|
method: "POST";
|
|
@@ -281,7 +285,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
281
285
|
};
|
|
282
286
|
}, {
|
|
283
287
|
redirect: boolean;
|
|
284
|
-
|
|
288
|
+
url: string;
|
|
285
289
|
}>;
|
|
286
290
|
oauth2Token: better_call0.StrictEndpoint<"/oauth2/token", {
|
|
287
291
|
method: "POST";
|
|
@@ -562,7 +566,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
562
566
|
};
|
|
563
567
|
};
|
|
564
568
|
};
|
|
565
|
-
},
|
|
569
|
+
}, jose.JWTPayload>;
|
|
566
570
|
oauth2Revoke: better_call0.StrictEndpoint<"/oauth2/revoke", {
|
|
567
571
|
method: "POST";
|
|
568
572
|
body: z.ZodObject<{
|
|
@@ -823,9 +827,9 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
823
827
|
software_statement: z.ZodOptional<z.ZodString>;
|
|
824
828
|
post_logout_redirect_uris: z.ZodOptional<z.ZodArray<z.ZodURL>>;
|
|
825
829
|
token_endpoint_auth_method: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
|
|
830
|
+
none: "none";
|
|
826
831
|
client_secret_basic: "client_secret_basic";
|
|
827
832
|
client_secret_post: "client_secret_post";
|
|
828
|
-
none: "none";
|
|
829
833
|
}>>>;
|
|
830
834
|
grant_types: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
831
835
|
authorization_code: "authorization_code";
|
|
@@ -850,8 +854,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
850
854
|
content: {
|
|
851
855
|
"application/json": {
|
|
852
856
|
schema: {
|
|
853
|
-
/** @returns {OauthClient} */
|
|
854
|
-
type: "object";
|
|
857
|
+
/** @returns {OauthClient} */type: "object";
|
|
855
858
|
properties: {
|
|
856
859
|
client_id: {
|
|
857
860
|
type: string;
|
|
@@ -992,9 +995,9 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
992
995
|
software_statement: z.ZodOptional<z.ZodString>;
|
|
993
996
|
post_logout_redirect_uris: z.ZodOptional<z.ZodArray<z.ZodURL>>;
|
|
994
997
|
token_endpoint_auth_method: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
|
|
998
|
+
none: "none";
|
|
995
999
|
client_secret_basic: "client_secret_basic";
|
|
996
1000
|
client_secret_post: "client_secret_post";
|
|
997
|
-
none: "none";
|
|
998
1001
|
}>>>;
|
|
999
1002
|
grant_types: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
1000
1003
|
authorization_code: "authorization_code";
|
|
@@ -1012,6 +1015,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
1012
1015
|
client_secret_expires_at: z.ZodDefault<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
1013
1016
|
skip_consent: z.ZodOptional<z.ZodBoolean>;
|
|
1014
1017
|
enable_end_session: z.ZodOptional<z.ZodBoolean>;
|
|
1018
|
+
require_pkce: z.ZodOptional<z.ZodBoolean>;
|
|
1015
1019
|
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
1016
1020
|
}, z.core.$strip>;
|
|
1017
1021
|
metadata: {
|
|
@@ -1131,6 +1135,11 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
1131
1135
|
type: string;
|
|
1132
1136
|
description: string;
|
|
1133
1137
|
};
|
|
1138
|
+
require_pkce: {
|
|
1139
|
+
type: string;
|
|
1140
|
+
description: string;
|
|
1141
|
+
default: boolean;
|
|
1142
|
+
};
|
|
1134
1143
|
metadata: {
|
|
1135
1144
|
type: string;
|
|
1136
1145
|
additionalProperties: boolean;
|
|
@@ -1186,9 +1195,9 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
1186
1195
|
software_statement: z.ZodOptional<z.ZodString>;
|
|
1187
1196
|
post_logout_redirect_uris: z.ZodOptional<z.ZodArray<z.ZodURL>>;
|
|
1188
1197
|
token_endpoint_auth_method: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
|
|
1198
|
+
none: "none";
|
|
1189
1199
|
client_secret_basic: "client_secret_basic";
|
|
1190
1200
|
client_secret_post: "client_secret_post";
|
|
1191
|
-
none: "none";
|
|
1192
1201
|
}>>>;
|
|
1193
1202
|
grant_types: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
1194
1203
|
authorization_code: "authorization_code";
|
|
@@ -1854,6 +1863,10 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
1854
1863
|
type: "string";
|
|
1855
1864
|
required: false;
|
|
1856
1865
|
};
|
|
1866
|
+
requirePKCE: {
|
|
1867
|
+
type: "boolean";
|
|
1868
|
+
required: false;
|
|
1869
|
+
};
|
|
1857
1870
|
referenceId: {
|
|
1858
1871
|
type: "string";
|
|
1859
1872
|
required: false;
|
|
@@ -1909,6 +1922,10 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
1909
1922
|
type: "date";
|
|
1910
1923
|
required: false;
|
|
1911
1924
|
};
|
|
1925
|
+
authTime: {
|
|
1926
|
+
type: "date";
|
|
1927
|
+
required: false;
|
|
1928
|
+
};
|
|
1912
1929
|
scopes: {
|
|
1913
1930
|
type: "string[]";
|
|
1914
1931
|
required: true;
|
|
@@ -2007,6 +2024,32 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
|
|
|
2007
2024
|
};
|
|
2008
2025
|
};
|
|
2009
2026
|
};
|
|
2027
|
+
rateLimit: ({
|
|
2028
|
+
pathMatcher: (path: string) => path is "/oauth2/token";
|
|
2029
|
+
window: number;
|
|
2030
|
+
max: number;
|
|
2031
|
+
} | {
|
|
2032
|
+
pathMatcher: (path: string) => path is "/oauth2/authorize";
|
|
2033
|
+
window: number;
|
|
2034
|
+
max: number;
|
|
2035
|
+
} | {
|
|
2036
|
+
pathMatcher: (path: string) => path is "/oauth2/introspect";
|
|
2037
|
+
window: number;
|
|
2038
|
+
max: number;
|
|
2039
|
+
} | {
|
|
2040
|
+
pathMatcher: (path: string) => path is "/oauth2/revoke";
|
|
2041
|
+
window: number;
|
|
2042
|
+
max: number;
|
|
2043
|
+
} | {
|
|
2044
|
+
pathMatcher: (path: string) => path is "/oauth2/register";
|
|
2045
|
+
window: number;
|
|
2046
|
+
max: number;
|
|
2047
|
+
} | {
|
|
2048
|
+
pathMatcher: (path: string) => path is "/oauth2/userinfo";
|
|
2049
|
+
window: number;
|
|
2050
|
+
max: number;
|
|
2051
|
+
})[];
|
|
2010
2052
|
};
|
|
2011
2053
|
//#endregion
|
|
2012
|
-
export { oauthProvider as t };
|
|
2054
|
+
export { oauthProvider as n, getOAuthProviderState as t };
|
|
2055
|
+
//# sourceMappingURL=oauth-C_QoLKZA.d.mts.map
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { isAPIError } from "better-auth/api";
|
|
1
2
|
import { verifyAccessToken } from "better-auth/oauth2";
|
|
2
|
-
import { APIError } from "better-call";
|
|
3
|
+
import { APIError as APIError$1 } from "better-call";
|
|
4
|
+
import { constantTimeEqual, symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
|
|
3
5
|
import { BetterAuthError } from "@better-auth/core/error";
|
|
4
6
|
import { base64, base64Url } from "@better-auth/utils/base64";
|
|
5
7
|
import { createHash } from "@better-auth/utils/hash";
|
|
6
|
-
import { constantTimeEqual, symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
|
|
7
8
|
|
|
8
9
|
//#region src/mcp.ts
|
|
9
10
|
/**
|
|
@@ -17,13 +18,13 @@ const mcpHandler = (verifyOptions, handler, opts) => {
|
|
|
17
18
|
const authorization = req.headers?.get("authorization") ?? void 0;
|
|
18
19
|
const accessToken = authorization?.startsWith("Bearer ") ? authorization.replace("Bearer ", "") : authorization;
|
|
19
20
|
try {
|
|
20
|
-
if (!accessToken?.length) throw new APIError("UNAUTHORIZED", { message: "missing authorization header" });
|
|
21
|
+
if (!accessToken?.length) throw new APIError$1("UNAUTHORIZED", { message: "missing authorization header" });
|
|
21
22
|
return handler(req, await verifyAccessToken(accessToken, verifyOptions));
|
|
22
23
|
} catch (error) {
|
|
23
24
|
try {
|
|
24
25
|
handleMcpErrors(error, verifyOptions.verifyOptions.audience, opts);
|
|
25
26
|
} catch (err) {
|
|
26
|
-
if (err instanceof APIError) return new Response(err.message, {
|
|
27
|
+
if (err instanceof APIError$1) return new Response(err.message, {
|
|
27
28
|
...err,
|
|
28
29
|
status: err.statusCode
|
|
29
30
|
});
|
|
@@ -39,7 +40,7 @@ const mcpHandler = (verifyOptions, handler, opts) => {
|
|
|
39
40
|
* @internal
|
|
40
41
|
*/
|
|
41
42
|
function handleMcpErrors(error, resource, opts) {
|
|
42
|
-
if (error
|
|
43
|
+
if (isAPIError(error) && error.status === "UNAUTHORIZED") {
|
|
43
44
|
const wwwAuthenticateValue = (Array.isArray(resource) ? resource : [resource]).map((v) => {
|
|
44
45
|
let audiencePath;
|
|
45
46
|
if (URL.canParse?.(v)) {
|
|
@@ -48,11 +49,11 @@ function handleMcpErrors(error, resource, opts) {
|
|
|
48
49
|
return `Bearer resource_metadata="${url.origin}/.well-known/oauth-protected-resource${audiencePath}"`;
|
|
49
50
|
} else {
|
|
50
51
|
const resourceMetadata = opts?.resourceMetadataMappings?.[v];
|
|
51
|
-
if (!resourceMetadata) throw new APIError("INTERNAL_SERVER_ERROR", { message: `missing resource_metadata mapping for ${v}` });
|
|
52
|
+
if (!resourceMetadata) throw new APIError$1("INTERNAL_SERVER_ERROR", { message: `missing resource_metadata mapping for ${v}` });
|
|
52
53
|
return `Bearer resource_metadata=${resourceMetadata}`;
|
|
53
54
|
}
|
|
54
55
|
}).join(", ");
|
|
55
|
-
throw new APIError("UNAUTHORIZED", { message: error.message }, { "WWW-Authenticate": wwwAuthenticateValue });
|
|
56
|
+
throw new APIError$1("UNAUTHORIZED", { message: error.message }, { "WWW-Authenticate": wwwAuthenticateValue });
|
|
56
57
|
} else if (error instanceof Error) throw error;
|
|
57
58
|
else throw new Error(error);
|
|
58
59
|
}
|
|
@@ -124,7 +125,7 @@ const defaultHasher = async (value) => {
|
|
|
124
125
|
*/
|
|
125
126
|
async function decryptStoredClientSecret(ctx, storageMethod, storedClientSecret) {
|
|
126
127
|
if (storageMethod === "encrypted") return await symmetricDecrypt({
|
|
127
|
-
key: ctx.context.
|
|
128
|
+
key: ctx.context.secretConfig,
|
|
128
129
|
data: storedClientSecret
|
|
129
130
|
});
|
|
130
131
|
else if (typeof storageMethod === "object" && "decrypt" in storageMethod) return await storageMethod.decrypt(storedClientSecret);
|
|
@@ -138,7 +139,7 @@ async function decryptStoredClientSecret(ctx, storageMethod, storedClientSecret)
|
|
|
138
139
|
async function verifyStoredClientSecret(ctx, opts, storedClientSecret, clientSecret) {
|
|
139
140
|
const storageMethod = opts.storeClientSecret ?? (opts.disableJwtPlugin ? "encrypted" : "hashed");
|
|
140
141
|
if (clientSecret && opts.prefix?.clientSecret) if (clientSecret.startsWith(opts.prefix?.clientSecret)) clientSecret = clientSecret.replace(opts.prefix.clientSecret, "");
|
|
141
|
-
else throw new APIError("UNAUTHORIZED", {
|
|
142
|
+
else throw new APIError$1("UNAUTHORIZED", {
|
|
142
143
|
error_description: "invalid client_secret",
|
|
143
144
|
error: "invalid_client"
|
|
144
145
|
});
|
|
@@ -150,7 +151,13 @@ async function verifyStoredClientSecret(ctx, opts, storedClientSecret, clientSec
|
|
|
150
151
|
const hashedClientSecret = clientSecret ? await storageMethod.hash(clientSecret) : void 0;
|
|
151
152
|
return !!hashedClientSecret && constantTimeEqual(hashedClientSecret, storedClientSecret);
|
|
152
153
|
}
|
|
153
|
-
else if (storageMethod === "encrypted"
|
|
154
|
+
else if (storageMethod === "encrypted") try {
|
|
155
|
+
const decryptedClientSecret = await decryptStoredClientSecret(ctx, storageMethod, storedClientSecret);
|
|
156
|
+
return !!clientSecret && constantTimeEqual(decryptedClientSecret, clientSecret);
|
|
157
|
+
} catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
else if (typeof storageMethod === "object" && "decrypt" in storageMethod) {
|
|
154
161
|
const decryptedClientSecret = await decryptStoredClientSecret(ctx, storageMethod, storedClientSecret);
|
|
155
162
|
return !!clientSecret && constantTimeEqual(decryptedClientSecret, clientSecret);
|
|
156
163
|
}
|
|
@@ -164,7 +171,7 @@ async function verifyStoredClientSecret(ctx, opts, storedClientSecret, clientSec
|
|
|
164
171
|
async function storeClientSecret(ctx, opts, clientSecret) {
|
|
165
172
|
const storageMethod = opts.storeClientSecret ?? (opts.disableJwtPlugin ? "encrypted" : "hashed");
|
|
166
173
|
if (storageMethod === "encrypted") return await symmetricEncrypt({
|
|
167
|
-
key: ctx.context.
|
|
174
|
+
key: ctx.context.secretConfig,
|
|
168
175
|
data: clientSecret
|
|
169
176
|
});
|
|
170
177
|
else if (storageMethod === "hashed") return await defaultHasher(clientSecret);
|
|
@@ -203,12 +210,12 @@ function basicToClientCredentials(authorization) {
|
|
|
203
210
|
if (authorization.startsWith("Basic ")) {
|
|
204
211
|
const encoded = authorization.replace("Basic ", "");
|
|
205
212
|
const decoded = new TextDecoder().decode(base64.decode(encoded));
|
|
206
|
-
if (!decoded.includes(":")) throw new APIError("BAD_REQUEST", {
|
|
213
|
+
if (!decoded.includes(":")) throw new APIError$1("BAD_REQUEST", {
|
|
207
214
|
error_description: "invalid authorization header format",
|
|
208
215
|
error: "invalid_client"
|
|
209
216
|
});
|
|
210
217
|
const [id, secret] = decoded.split(":", 2);
|
|
211
|
-
if (!id || !secret) throw new APIError("BAD_REQUEST", {
|
|
218
|
+
if (!id || !secret) throw new APIError$1("BAD_REQUEST", {
|
|
212
219
|
error_description: "invalid authorization header format",
|
|
213
220
|
error: "invalid_client"
|
|
214
221
|
});
|
|
@@ -226,29 +233,29 @@ function basicToClientCredentials(authorization) {
|
|
|
226
233
|
*/
|
|
227
234
|
async function validateClientCredentials(ctx, options, clientId, clientSecret, scopes) {
|
|
228
235
|
const client = await getClient(ctx, options, clientId);
|
|
229
|
-
if (!client) throw new APIError("BAD_REQUEST", {
|
|
236
|
+
if (!client) throw new APIError$1("BAD_REQUEST", {
|
|
230
237
|
error_description: "missing client",
|
|
231
238
|
error: "invalid_client"
|
|
232
239
|
});
|
|
233
|
-
if (client.disabled) throw new APIError("BAD_REQUEST", {
|
|
240
|
+
if (client.disabled) throw new APIError$1("BAD_REQUEST", {
|
|
234
241
|
error_description: "client is disabled",
|
|
235
242
|
error: "invalid_client"
|
|
236
243
|
});
|
|
237
|
-
if (!client.public && !clientSecret) throw new APIError("BAD_REQUEST", {
|
|
244
|
+
if (!client.public && !clientSecret) throw new APIError$1("BAD_REQUEST", {
|
|
238
245
|
error_description: "client secret must be provided",
|
|
239
246
|
error: "invalid_client"
|
|
240
247
|
});
|
|
241
|
-
if (clientSecret && !client.clientSecret) throw new APIError("BAD_REQUEST", {
|
|
248
|
+
if (clientSecret && !client.clientSecret) throw new APIError$1("BAD_REQUEST", {
|
|
242
249
|
error_description: "public client, client secret should not be received",
|
|
243
250
|
error: "invalid_client"
|
|
244
251
|
});
|
|
245
|
-
if (clientSecret && !await verifyStoredClientSecret(ctx, options, client.clientSecret, clientSecret)) throw new APIError("UNAUTHORIZED", {
|
|
252
|
+
if (clientSecret && !await verifyStoredClientSecret(ctx, options, client.clientSecret, clientSecret)) throw new APIError$1("UNAUTHORIZED", {
|
|
246
253
|
error_description: "invalid client_secret",
|
|
247
254
|
error: "invalid_client"
|
|
248
255
|
});
|
|
249
256
|
if (scopes && client.scopes) {
|
|
250
257
|
const validScopes = new Set(client.scopes);
|
|
251
|
-
for (const sc of scopes) if (!validScopes.has(sc)) throw new APIError("BAD_REQUEST", {
|
|
258
|
+
for (const sc of scopes) if (!validScopes.has(sc)) throw new APIError$1("BAD_REQUEST", {
|
|
252
259
|
error_description: `client does not allow scope ${sc}`,
|
|
253
260
|
error: "invalid_scope"
|
|
254
261
|
});
|
|
@@ -291,6 +298,33 @@ function deleteFromPrompt(query, prompt) {
|
|
|
291
298
|
}
|
|
292
299
|
return Object.fromEntries(query);
|
|
293
300
|
}
|
|
301
|
+
var PKCERequirementErrors = /* @__PURE__ */ function(PKCERequirementErrors) {
|
|
302
|
+
PKCERequirementErrors["PUBLIC_CLIENT"] = "pkce is required for public clients";
|
|
303
|
+
PKCERequirementErrors["OFFLINE_ACCESS_SCOPE"] = "pkce is required when requesting offline_access scope";
|
|
304
|
+
PKCERequirementErrors["CLIENT_REQUIRE_PKCE"] = "pkce is required for this client";
|
|
305
|
+
return PKCERequirementErrors;
|
|
306
|
+
}(PKCERequirementErrors || {});
|
|
307
|
+
/**
|
|
308
|
+
* Determines if PKCE is required for a given client and scope.
|
|
309
|
+
*
|
|
310
|
+
* PKCE is always required for:
|
|
311
|
+
* 1. Public clients (cannot securely store client_secret)
|
|
312
|
+
* 2. Requests with offline_access scope (refresh token security)
|
|
313
|
+
*
|
|
314
|
+
* For confidential clients without offline_access:
|
|
315
|
+
* - Uses client.requirePKCE if set (defaults to true)
|
|
316
|
+
*
|
|
317
|
+
* Returns false if PKCE is not required, or the reason it is required.
|
|
318
|
+
*
|
|
319
|
+
* @internal
|
|
320
|
+
*/
|
|
321
|
+
function isPKCERequired(client, requestedScopes) {
|
|
322
|
+
if (client.tokenEndpointAuthMethod === "none" || client.type === "native" || client.type === "user-agent-based" || client.public === true) return PKCERequirementErrors.PUBLIC_CLIENT;
|
|
323
|
+
if (requestedScopes?.includes("offline_access")) return PKCERequirementErrors.OFFLINE_ACCESS_SCOPE;
|
|
324
|
+
if (client.requirePKCE ?? true) return PKCERequirementErrors.CLIENT_REQUIRE_PKCE;
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
294
327
|
|
|
295
328
|
//#endregion
|
|
296
|
-
export { getJwtPlugin as a,
|
|
329
|
+
export { getJwtPlugin as a, isPKCERequired as c, storeClientSecret as d, storeToken as f, mcpHandler as h, getClient as i, parseClientMetadata as l, handleMcpErrors as m, decryptStoredClientSecret as n, getOAuthProviderPlugin as o, validateClientCredentials as p, deleteFromPrompt as r, getStoredToken as s, basicToClientCredentials as t, parsePrompt as u };
|
|
330
|
+
//# sourceMappingURL=utils-D6kv_BUA.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils-D6kv_BUA.mjs","names":["APIError","APIError"],"sources":["../src/mcp.ts","../src/utils/index.ts"],"sourcesContent":["import { isAPIError } from \"better-auth/api\";\nimport { verifyAccessToken } from \"better-auth/oauth2\";\nimport { APIError } from \"better-call\";\nimport type { JWTPayload } from \"jose\";\nimport type { Awaitable } from \"./types/helpers\";\n\n/**\n * A request middleware handler that checks and responds with\n * a WWW-Authenticate header for unauthenticated responses.\n *\n * @external\n */\nexport const mcpHandler = (\n\t/** Resource is the same url as the audience */\n\tverifyOptions: Parameters<typeof verifyAccessToken>[1],\n\thandler: (req: Request, jwt: JWTPayload) => Awaitable<Response>,\n\topts?: {\n\t\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\t\tresourceMetadataMappings: Record<string, string>;\n\t},\n) => {\n\treturn async (req: Request) => {\n\t\tconst authorization = req.headers?.get(\"authorization\") ?? undefined;\n\t\tconst accessToken = authorization?.startsWith(\"Bearer \")\n\t\t\t? authorization.replace(\"Bearer \", \"\")\n\t\t\t: authorization;\n\t\ttry {\n\t\t\tif (!accessToken?.length) {\n\t\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\t\tmessage: \"missing authorization header\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst token = await verifyAccessToken(accessToken, verifyOptions);\n\t\t\treturn handler(req, token);\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\thandleMcpErrors(error, verifyOptions.verifyOptions.audience, opts);\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof APIError) {\n\t\t\t\t\treturn new Response(err.message, {\n\t\t\t\t\t\t...err,\n\t\t\t\t\t\tstatus: err.statusCode,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tthrow new Error(String(err));\n\t\t\t}\n\t\t\tthrow new Error(String(error));\n\t\t}\n\t};\n};\n\n/**\n * The following handles all MCP errors and API errors\n *\n * @internal\n */\nexport function handleMcpErrors(\n\terror: unknown,\n\tresource: string | string[],\n\topts?: {\n\t\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\t\tresourceMetadataMappings?: Record<string, string>;\n\t},\n) {\n\tif (isAPIError(error) && error.status === \"UNAUTHORIZED\") {\n\t\tconst _resources = Array.isArray(resource) ? resource : [resource];\n\t\tconst wwwAuthenticateValue = _resources\n\t\t\t.map((v) => {\n\t\t\t\tlet audiencePath: string;\n\t\t\t\tif (URL.canParse?.(v)) {\n\t\t\t\t\tconst url = new URL(v);\n\t\t\t\t\taudiencePath = url.pathname.endsWith(\"/\")\n\t\t\t\t\t\t? url.pathname.slice(0, -1)\n\t\t\t\t\t\t: url.pathname;\n\t\t\t\t\treturn `Bearer resource_metadata=\"${url.origin}/.well-known/oauth-protected-resource${\n\t\t\t\t\t\taudiencePath\n\t\t\t\t\t}\"`;\n\t\t\t\t} else {\n\t\t\t\t\tconst resourceMetadata = opts?.resourceMetadataMappings?.[v];\n\t\t\t\t\tif (!resourceMetadata) {\n\t\t\t\t\t\tthrow new APIError(\"INTERNAL_SERVER_ERROR\", {\n\t\t\t\t\t\t\tmessage: `missing resource_metadata mapping for ${v}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\treturn `Bearer resource_metadata=${resourceMetadata}`;\n\t\t\t\t}\n\t\t\t})\n\t\t\t.join(\", \");\n\t\tthrow new APIError(\n\t\t\t\"UNAUTHORIZED\",\n\t\t\t{\n\t\t\t\tmessage: error.message,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"WWW-Authenticate\": wwwAuthenticateValue,\n\t\t\t},\n\t\t);\n\t} else if (error instanceof Error) {\n\t\tthrow error;\n\t} else {\n\t\tthrow new Error(error as unknown as string);\n\t}\n}\n","import type { AuthContext, GenericEndpointContext } from \"@better-auth/core\";\nimport { BetterAuthError } from \"@better-auth/core/error\";\nimport { base64, base64Url } from \"@better-auth/utils/base64\";\nimport { createHash } from \"@better-auth/utils/hash\";\nimport {\n\tconstantTimeEqual,\n\tsymmetricDecrypt,\n\tsymmetricEncrypt,\n} from \"better-auth/crypto\";\nimport type { jwt } from \"better-auth/plugins\";\nimport { APIError } from \"better-call\";\nimport type { oauthProvider } from \"../oauth\";\nimport type {\n\tOAuthOptions,\n\tPrompt,\n\tSchemaClient,\n\tScope,\n\tStoreTokenType,\n} from \"../types\";\n\nclass TTLCache<K, V extends { expiresAt?: Date }> {\n\tprivate cache = new Map<K, V>();\n\tconstructor() {}\n\n\tset(key: K, value: V) {\n\t\tthis.cache.set(key, value);\n\t}\n\n\tget(key: K): V | undefined {\n\t\tconst entry = this.cache.get(key);\n\t\tif (!entry) return undefined;\n\t\tif (entry.expiresAt && entry.expiresAt < new Date()) {\n\t\t\tthis.cache.delete(key);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn entry;\n\t}\n}\n\n/**\n * Gets the oAuth Provider Plugin\n * @internal\n */\nexport const getOAuthProviderPlugin = (ctx: AuthContext) => {\n\treturn ctx.getPlugin(\"oauth-provider\") satisfies ReturnType<\n\t\ttypeof oauthProvider\n\t> | null;\n};\n\n/**\n * Gets the JWT Plugin\n * @internal\n */\nexport const getJwtPlugin = (ctx: AuthContext) => {\n\tconst plugin = ctx.getPlugin(\"jwt\") satisfies ReturnType<typeof jwt> | null;\n\tif (!plugin) {\n\t\tthrow new BetterAuthError(\"jwt_config\", \"jwt plugin not found\");\n\t}\n\treturn plugin;\n};\n\nconst cachedTrustedClients = new TTLCache<string, SchemaClient<Scope[]>>();\n\n/**\n * Get a client by ID, checking trusted clients first, then database\n */\nexport async function getClient(\n\tctx: GenericEndpointContext,\n\toptions: OAuthOptions<Scope[]>,\n\tclientId: string,\n) {\n\tconst trustedClient = cachedTrustedClients.get(clientId);\n\tif (trustedClient) {\n\t\treturn Object.assign({}, trustedClient);\n\t}\n\n\tconst dbClient = await ctx.context.adapter.findOne<SchemaClient<Scope[]>>({\n\t\tmodel: options.schema?.oauthClient?.modelName ?? \"oauthClient\",\n\t\twhere: [{ field: \"clientId\", value: clientId }],\n\t});\n\n\tif (dbClient && options.cachedTrustedClients?.has(clientId)) {\n\t\tcachedTrustedClients.set(clientId, Object.assign({}, dbClient));\n\t}\n\n\treturn dbClient;\n}\n\n/**\n * Default client secret hasher using SHA-256\n *\n * @internal\n */\nconst defaultHasher = async (value: string) => {\n\tconst hash = await createHash(\"SHA-256\").digest(\n\t\tnew TextEncoder().encode(value),\n\t);\n\tconst hashed = base64Url.encode(new Uint8Array(hash), {\n\t\tpadding: false,\n\t});\n\treturn hashed;\n};\n\n/**\n * Decrypts a storedClientSecret for signing\n *\n * @internal\n */\nexport async function decryptStoredClientSecret(\n\tctx: GenericEndpointContext,\n\tstorageMethod: OAuthOptions<Scope[]>[\"storeClientSecret\"],\n\tstoredClientSecret: string,\n) {\n\tif (storageMethod === \"encrypted\") {\n\t\treturn await symmetricDecrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: storedClientSecret,\n\t\t});\n\t} else if (typeof storageMethod === \"object\" && \"decrypt\" in storageMethod) {\n\t\treturn await storageMethod.decrypt(storedClientSecret);\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`Unsupported decryption storageMethod type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Verify stored client secret against provided client secret\n *\n * @internal\n */\nasync function verifyStoredClientSecret(\n\tctx: GenericEndpointContext,\n\topts: OAuthOptions<Scope[]>,\n\tstoredClientSecret: string,\n\tclientSecret?: string,\n): Promise<boolean> {\n\tconst storageMethod =\n\t\topts.storeClientSecret ?? (opts.disableJwtPlugin ? \"encrypted\" : \"hashed\");\n\n\tif (clientSecret && opts.prefix?.clientSecret) {\n\t\tif (clientSecret.startsWith(opts.prefix?.clientSecret)) {\n\t\t\tclientSecret = clientSecret.replace(opts.prefix.clientSecret, \"\");\n\t\t} else {\n\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\terror_description: \"invalid client_secret\",\n\t\t\t\terror: \"invalid_client\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (storageMethod === \"hashed\") {\n\t\tconst hashedClientSecret = clientSecret\n\t\t\t? await defaultHasher(clientSecret)\n\t\t\t: undefined;\n\t\treturn (\n\t\t\t!!hashedClientSecret &&\n\t\t\tconstantTimeEqual(hashedClientSecret, storedClientSecret)\n\t\t);\n\t} else if (typeof storageMethod === \"object\" && \"hash\" in storageMethod) {\n\t\tif (storageMethod.verify) {\n\t\t\treturn (\n\t\t\t\t!!clientSecret &&\n\t\t\t\t(await storageMethod.verify(clientSecret, storedClientSecret))\n\t\t\t);\n\t\t} else {\n\t\t\tconst hashedClientSecret = clientSecret\n\t\t\t\t? await storageMethod.hash(clientSecret)\n\t\t\t\t: undefined;\n\t\t\treturn (\n\t\t\t\t!!hashedClientSecret &&\n\t\t\t\tconstantTimeEqual(hashedClientSecret, storedClientSecret)\n\t\t\t);\n\t\t}\n\t} else if (storageMethod === \"encrypted\") {\n\t\ttry {\n\t\t\tconst decryptedClientSecret = await decryptStoredClientSecret(\n\t\t\t\tctx,\n\t\t\t\tstorageMethod,\n\t\t\t\tstoredClientSecret,\n\t\t\t);\n\t\t\treturn (\n\t\t\t\t!!clientSecret && constantTimeEqual(decryptedClientSecret, clientSecret)\n\t\t\t);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t} else if (typeof storageMethod === \"object\" && \"decrypt\" in storageMethod) {\n\t\tconst decryptedClientSecret = await decryptStoredClientSecret(\n\t\t\tctx,\n\t\t\tstorageMethod,\n\t\t\tstoredClientSecret,\n\t\t);\n\t\treturn (\n\t\t\t!!clientSecret && constantTimeEqual(decryptedClientSecret, clientSecret)\n\t\t);\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`Unsupported verify storageMethod type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Store client secret according to the configured storage method\n *\n * @internal\n */\nexport async function storeClientSecret(\n\tctx: GenericEndpointContext,\n\topts: OAuthOptions<Scope[]>,\n\tclientSecret: string,\n) {\n\tconst storageMethod =\n\t\topts.storeClientSecret ?? (opts.disableJwtPlugin ? \"encrypted\" : \"hashed\");\n\n\tif (storageMethod === \"encrypted\") {\n\t\treturn await symmetricEncrypt({\n\t\t\tkey: ctx.context.secretConfig,\n\t\t\tdata: clientSecret,\n\t\t});\n\t} else if (storageMethod === \"hashed\") {\n\t\treturn await defaultHasher(clientSecret);\n\t} else if (typeof storageMethod === \"object\" && \"hash\" in storageMethod) {\n\t\treturn await storageMethod.hash(clientSecret);\n\t} else if (typeof storageMethod === \"object\" && \"encrypt\" in storageMethod) {\n\t\treturn await storageMethod.encrypt(clientSecret);\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`Unsupported storeClientSecret type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Stores a token value (ie opaque tokens, refresh tokens, transaction tokens, verification codes)\n * on the database in a secure hashed format.\n *\n * @internal\n */\nexport async function storeToken(\n\tstorageMethod: OAuthOptions<Scope[]>[\"storeTokens\"] = \"hashed\",\n\ttoken: string,\n\ttype: StoreTokenType,\n) {\n\tif (storageMethod === \"hashed\") {\n\t\treturn await defaultHasher(token);\n\t} else if (typeof storageMethod === \"object\" && \"hash\" in storageMethod) {\n\t\treturn await storageMethod.hash(token, type);\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`storeToken: unsupported storageMethod type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Gets a hashed token value to find on the database.\n *\n * @internal\n */\nexport async function getStoredToken(\n\tstorageMethod: OAuthOptions<Scope[]>[\"storeTokens\"] = \"hashed\",\n\ttoken: string,\n\ttype: StoreTokenType,\n) {\n\tif (storageMethod === \"hashed\") {\n\t\tconst hashedToken = await defaultHasher(token);\n\t\treturn hashedToken;\n\t} else if (typeof storageMethod === \"object\" && \"hash\" in storageMethod) {\n\t\tconst hashedToken = await storageMethod.hash(token, type);\n\t\treturn hashedToken;\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`getStoredToken: unsupported storageMethod type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Converts a BASIC authorization header\n * into its client_id and client_secret representation\n *\n * @internal\n */\nexport function basicToClientCredentials(authorization: string) {\n\tif (authorization.startsWith(\"Basic \")) {\n\t\tconst encoded = authorization.replace(\"Basic \", \"\");\n\t\tconst decoded = new TextDecoder().decode(base64.decode(encoded));\n\t\tif (!decoded.includes(\":\")) {\n\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\terror_description: \"invalid authorization header format\",\n\t\t\t\terror: \"invalid_client\",\n\t\t\t});\n\t\t}\n\t\tconst [id, secret] = decoded.split(\":\", 2);\n\t\tif (!id || !secret) {\n\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\terror_description: \"invalid authorization header format\",\n\t\t\t\terror: \"invalid_client\",\n\t\t\t});\n\t\t}\n\t\treturn {\n\t\t\tclient_id: id,\n\t\t\tclient_secret: secret,\n\t\t};\n\t}\n}\n\n/**\n * Validates client credentials failing on mismatches\n * and incorrectly provided information\n *\n * @internal\n */\nexport async function validateClientCredentials(\n\tctx: GenericEndpointContext,\n\toptions: OAuthOptions<Scope[]>,\n\tclientId: string,\n\tclientSecret?: string, // optional because required if client is confidential or this value is defined\n\tscopes?: string[], // checks requested scopes against allowed scopes\n) {\n\tconst client = await getClient(ctx, options, clientId);\n\tif (!client) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\terror_description: \"missing client\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\tif (client.disabled) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\terror_description: \"client is disabled\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\n\t// Require secret for confidential clients\n\tif (!client.public && !clientSecret) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\terror_description: \"client secret must be provided\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\n\t// Secret should not be received\n\tif (clientSecret && !client.clientSecret) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\terror_description: \"public client, client secret should not be received\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\n\t// Compare Secrets when secret is provided\n\tif (\n\t\tclientSecret &&\n\t\t!(await verifyStoredClientSecret(\n\t\t\tctx,\n\t\t\toptions,\n\t\t\tclient.clientSecret!,\n\t\t\tclientSecret,\n\t\t))\n\t) {\n\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\terror_description: \"invalid client_secret\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\n\t// If scopes set, check against client allowed scopes\n\tif (scopes && client.scopes) {\n\t\tconst validScopes = new Set(client.scopes);\n\t\tfor (const sc of scopes) {\n\t\t\tif (!validScopes.has(sc)) {\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\terror_description: `client does not allow scope ${sc}`,\n\t\t\t\t\terror: \"invalid_scope\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\treturn client;\n}\n\n/**\n * Parse client metadata that may be stored as JSON string or already parsed object.\n * Handles database adapters that auto-parse JSON columns.\n *\n * @internal\n */\nexport function parseClientMetadata(\n\tmetadata: string | object | undefined,\n): object | undefined {\n\tif (!metadata) return undefined;\n\treturn typeof metadata === \"string\" ? JSON.parse(metadata) : metadata;\n}\n\n/**\n * Parse space-separated prompt string into a set of prompts\n *\n * @param prompt\n */\nexport function parsePrompt(prompt: string) {\n\tconst prompts = prompt.split(\" \").map((p) => p.trim());\n\tconst set = new Set<Prompt>();\n\tfor (const p of prompts) {\n\t\tif (\n\t\t\tp === \"login\" ||\n\t\t\tp === \"consent\" ||\n\t\t\tp === \"create\" ||\n\t\t\tp === \"select_account\" ||\n\t\t\tp === \"none\"\n\t\t) {\n\t\t\tset.add(p);\n\t\t}\n\t}\n\treturn new Set(set);\n}\n\n/**\n * Deletes a prompt value\n *\n * @param ctx\n * @param prompt - the prompt value to delete\n */\nexport function deleteFromPrompt(query: URLSearchParams, prompt: Prompt) {\n\tconst prompts = query.get(\"prompt\")?.split(\" \");\n\tconst foundPrompt = prompts?.findIndex((v) => v === prompt) ?? -1;\n\tif (foundPrompt >= 0) {\n\t\tprompts?.splice(foundPrompt, 1);\n\t\tprompts?.length\n\t\t\t? query.set(\"prompt\", prompts.join(\" \"))\n\t\t\t: query.delete(\"prompt\");\n\t}\n\treturn Object.fromEntries(query);\n}\n\nenum PKCERequirementErrors {\n\tPUBLIC_CLIENT = \"pkce is required for public clients\",\n\tOFFLINE_ACCESS_SCOPE = \"pkce is required when requesting offline_access scope\",\n\tCLIENT_REQUIRE_PKCE = \"pkce is required for this client\",\n}\n/**\n * Determines if PKCE is required for a given client and scope.\n *\n * PKCE is always required for:\n * 1. Public clients (cannot securely store client_secret)\n * 2. Requests with offline_access scope (refresh token security)\n *\n * For confidential clients without offline_access:\n * - Uses client.requirePKCE if set (defaults to true)\n *\n * Returns false if PKCE is not required, or the reason it is required.\n *\n * @internal\n */\nexport function isPKCERequired(\n\tclient: SchemaClient<Scope[]>,\n\trequestedScopes?: string[],\n): false | PKCERequirementErrors {\n\t// Determine if client is public\n\tconst isPublicClient =\n\t\tclient.tokenEndpointAuthMethod === \"none\" ||\n\t\tclient.type === \"native\" ||\n\t\tclient.type === \"user-agent-based\" ||\n\t\tclient.public === true;\n\n\t// PKCE always required for public clients\n\tif (isPublicClient) {\n\t\treturn PKCERequirementErrors.PUBLIC_CLIENT;\n\t}\n\n\t// PKCE always required for offline_access scope (refresh tokens)\n\tif (requestedScopes?.includes(\"offline_access\")) {\n\t\treturn PKCERequirementErrors.OFFLINE_ACCESS_SCOPE;\n\t}\n\n\tif (client.requirePKCE ?? true) {\n\t\treturn PKCERequirementErrors.CLIENT_REQUIRE_PKCE;\n\t}\n\n\treturn false;\n}\n"],"mappings":"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"}
|