@better-auth/core 1.7.0-beta.6 → 1.7.0-beta.8

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.
Files changed (142) hide show
  1. package/dist/api/index.d.mts +3 -3
  2. package/dist/context/global.mjs +1 -1
  3. package/dist/db/get-tables.mjs +3 -3
  4. package/dist/db/schema/account.d.mts +1 -1
  5. package/dist/db/schema/account.mjs +1 -1
  6. package/dist/error/codes.d.mts +0 -5
  7. package/dist/error/codes.mjs +0 -5
  8. package/dist/instrumentation/tracer.mjs +1 -1
  9. package/dist/oauth2/create-authorization-url.d.mts +4 -5
  10. package/dist/oauth2/create-authorization-url.mjs +4 -5
  11. package/dist/oauth2/index.d.mts +3 -4
  12. package/dist/oauth2/index.mjs +2 -3
  13. package/dist/oauth2/oauth-provider.d.mts +44 -48
  14. package/dist/oauth2/refresh-access-token.mjs +17 -2
  15. package/dist/oauth2/utils.d.mts +6 -1
  16. package/dist/oauth2/utils.mjs +24 -2
  17. package/dist/oauth2/verify-id-token.d.mts +6 -5
  18. package/dist/oauth2/verify-id-token.mjs +2 -2
  19. package/dist/social-providers/apple.d.mts +3 -5
  20. package/dist/social-providers/apple.mjs +5 -5
  21. package/dist/social-providers/atlassian.d.mts +3 -5
  22. package/dist/social-providers/atlassian.mjs +4 -4
  23. package/dist/social-providers/cognito.d.mts +3 -5
  24. package/dist/social-providers/cognito.mjs +11 -18
  25. package/dist/social-providers/discord.d.mts +3 -5
  26. package/dist/social-providers/discord.mjs +6 -7
  27. package/dist/social-providers/dropbox.d.mts +3 -5
  28. package/dist/social-providers/dropbox.mjs +5 -5
  29. package/dist/social-providers/facebook.d.mts +3 -5
  30. package/dist/social-providers/facebook.mjs +5 -5
  31. package/dist/social-providers/figma.d.mts +3 -5
  32. package/dist/social-providers/figma.mjs +5 -5
  33. package/dist/social-providers/github.d.mts +3 -5
  34. package/dist/social-providers/github.mjs +4 -4
  35. package/dist/social-providers/gitlab.d.mts +3 -5
  36. package/dist/social-providers/gitlab.mjs +6 -6
  37. package/dist/social-providers/google.d.mts +10 -10
  38. package/dist/social-providers/google.mjs +12 -13
  39. package/dist/social-providers/huggingface.d.mts +3 -5
  40. package/dist/social-providers/huggingface.mjs +8 -8
  41. package/dist/social-providers/index.d.mts +105 -177
  42. package/dist/social-providers/kakao.d.mts +3 -5
  43. package/dist/social-providers/kakao.mjs +8 -8
  44. package/dist/social-providers/kick.d.mts +3 -5
  45. package/dist/social-providers/kick.mjs +4 -4
  46. package/dist/social-providers/line.d.mts +3 -5
  47. package/dist/social-providers/line.mjs +10 -10
  48. package/dist/social-providers/linear.d.mts +3 -5
  49. package/dist/social-providers/linear.mjs +4 -4
  50. package/dist/social-providers/linkedin.d.mts +3 -5
  51. package/dist/social-providers/linkedin.mjs +10 -10
  52. package/dist/social-providers/microsoft-entra-id.d.mts +3 -5
  53. package/dist/social-providers/microsoft-entra-id.mjs +10 -11
  54. package/dist/social-providers/naver.d.mts +3 -5
  55. package/dist/social-providers/naver.mjs +4 -4
  56. package/dist/social-providers/notion.d.mts +3 -5
  57. package/dist/social-providers/notion.mjs +4 -4
  58. package/dist/social-providers/paybin.d.mts +3 -5
  59. package/dist/social-providers/paybin.mjs +10 -10
  60. package/dist/social-providers/paypal.d.mts +3 -5
  61. package/dist/social-providers/paypal.mjs +2 -8
  62. package/dist/social-providers/polar.d.mts +3 -5
  63. package/dist/social-providers/polar.mjs +8 -8
  64. package/dist/social-providers/railway.d.mts +3 -5
  65. package/dist/social-providers/railway.mjs +9 -9
  66. package/dist/social-providers/reddit.d.mts +3 -5
  67. package/dist/social-providers/reddit.mjs +5 -5
  68. package/dist/social-providers/roblox.d.mts +3 -5
  69. package/dist/social-providers/roblox.mjs +5 -5
  70. package/dist/social-providers/salesforce.d.mts +3 -5
  71. package/dist/social-providers/salesforce.mjs +8 -8
  72. package/dist/social-providers/slack.d.mts +3 -5
  73. package/dist/social-providers/slack.mjs +9 -9
  74. package/dist/social-providers/spotify.d.mts +3 -5
  75. package/dist/social-providers/spotify.mjs +5 -5
  76. package/dist/social-providers/tiktok.d.mts +3 -5
  77. package/dist/social-providers/tiktok.mjs +5 -9
  78. package/dist/social-providers/twitch.d.mts +3 -5
  79. package/dist/social-providers/twitch.mjs +4 -4
  80. package/dist/social-providers/twitter.d.mts +3 -5
  81. package/dist/social-providers/twitter.mjs +9 -9
  82. package/dist/social-providers/vercel.d.mts +3 -5
  83. package/dist/social-providers/vercel.mjs +7 -4
  84. package/dist/social-providers/vk.d.mts +3 -5
  85. package/dist/social-providers/vk.mjs +5 -5
  86. package/dist/social-providers/wechat.d.mts +3 -5
  87. package/dist/social-providers/wechat.mjs +5 -9
  88. package/dist/social-providers/zoom.d.mts +3 -6
  89. package/dist/social-providers/zoom.mjs +9 -15
  90. package/dist/types/context.d.mts +6 -2
  91. package/dist/utils/host.d.mts +1 -1
  92. package/dist/utils/host.mjs +3 -0
  93. package/package.json +1 -1
  94. package/src/db/get-tables.ts +3 -8
  95. package/src/db/schema/account.ts +5 -14
  96. package/src/error/codes.ts +0 -5
  97. package/src/oauth2/create-authorization-url.ts +5 -1
  98. package/src/oauth2/index.ts +3 -12
  99. package/src/oauth2/oauth-provider.ts +46 -53
  100. package/src/oauth2/refresh-access-token.ts +30 -5
  101. package/src/oauth2/utils.ts +39 -1
  102. package/src/oauth2/verify-id-token.ts +9 -5
  103. package/src/social-providers/apple.ts +8 -13
  104. package/src/social-providers/atlassian.ts +8 -12
  105. package/src/social-providers/cognito.ts +11 -18
  106. package/src/social-providers/discord.ts +8 -19
  107. package/src/social-providers/dropbox.ts +7 -13
  108. package/src/social-providers/facebook.ts +9 -13
  109. package/src/social-providers/figma.ts +9 -13
  110. package/src/social-providers/github.ts +8 -12
  111. package/src/social-providers/gitlab.ts +8 -14
  112. package/src/social-providers/google.ts +23 -29
  113. package/src/social-providers/huggingface.ts +8 -12
  114. package/src/social-providers/kakao.ts +8 -16
  115. package/src/social-providers/kick.ts +7 -12
  116. package/src/social-providers/line.ts +10 -14
  117. package/src/social-providers/linear.ts +6 -12
  118. package/src/social-providers/linkedin.ts +10 -14
  119. package/src/social-providers/microsoft-entra-id.ts +8 -18
  120. package/src/social-providers/naver.ts +6 -12
  121. package/src/social-providers/notion.ts +6 -12
  122. package/src/social-providers/paybin.ts +11 -14
  123. package/src/social-providers/paypal.ts +8 -6
  124. package/src/social-providers/polar.ts +8 -12
  125. package/src/social-providers/railway.ts +9 -13
  126. package/src/social-providers/reddit.ts +7 -18
  127. package/src/social-providers/roblox.ts +7 -18
  128. package/src/social-providers/salesforce.ts +8 -12
  129. package/src/social-providers/slack.ts +9 -18
  130. package/src/social-providers/spotify.ts +7 -13
  131. package/src/social-providers/tiktok.ts +7 -13
  132. package/src/social-providers/twitch.ts +8 -12
  133. package/src/social-providers/twitter.ts +8 -17
  134. package/src/social-providers/vercel.ts +10 -16
  135. package/src/social-providers/vk.ts +7 -13
  136. package/src/social-providers/wechat.ts +8 -20
  137. package/src/social-providers/zoom.ts +6 -19
  138. package/src/types/context.ts +8 -2
  139. package/src/utils/host.ts +10 -1
  140. package/dist/oauth2/scopes.d.mts +0 -76
  141. package/dist/oauth2/scopes.mjs +0 -96
  142. package/src/oauth2/scopes.ts +0 -118
@@ -6,6 +6,7 @@ import type {
6
6
  TokenEndpointSecretAuthentication,
7
7
  } from "./token-endpoint-auth";
8
8
  import { applyTokenEndpointAuth } from "./token-endpoint-auth";
9
+ import { parseScopeField } from "./utils";
9
10
 
10
11
  interface RefreshAccessTokenRequestInput {
11
12
  refreshToken: string;
@@ -29,6 +30,21 @@ interface RefreshAccessTokenInput extends RefreshAccessTokenRequestInput {
29
30
  tokenEndpoint: string;
30
31
  }
31
32
 
33
+ /**
34
+ * Body keys owned by the refresh-token flow or unsafe to copy from caller input.
35
+ */
36
+ const BLOCKED_REFRESH_TOKEN_PARAMS = [
37
+ "grant_type",
38
+ "refresh_token",
39
+ "__proto__",
40
+ "constructor",
41
+ "prototype",
42
+ ] as const;
43
+
44
+ const BLOCKED_REFRESH_TOKEN_PARAMS_SET: ReadonlySet<string> = new Set(
45
+ BLOCKED_REFRESH_TOKEN_PARAMS,
46
+ );
47
+
32
48
  export async function refreshAccessTokenRequest({
33
49
  refreshToken,
34
50
  options,
@@ -59,6 +75,17 @@ export async function refreshAccessTokenRequest({
59
75
  return request;
60
76
  }
61
77
 
78
+ function applyRefreshExtraParams(
79
+ body: URLSearchParams,
80
+ extraParams: Record<string, string> | undefined,
81
+ ) {
82
+ if (!extraParams) return;
83
+ for (const [key, value] of Object.entries(extraParams)) {
84
+ if (BLOCKED_REFRESH_TOKEN_PARAMS_SET.has(key)) continue;
85
+ body.set(key, value);
86
+ }
87
+ }
88
+
62
89
  function buildRefreshAccessTokenRequest({
63
90
  refreshToken,
64
91
  options,
@@ -83,9 +110,7 @@ function buildRefreshAccessTokenRequest({
83
110
  }
84
111
  }
85
112
  if (extraParams) {
86
- for (const [key, value] of Object.entries(extraParams)) {
87
- body.set(key, value);
88
- }
113
+ applyRefreshExtraParams(body, extraParams);
89
114
  }
90
115
 
91
116
  return {
@@ -119,7 +144,7 @@ export async function refreshAccessToken({
119
144
  expires_in?: number | undefined;
120
145
  refresh_token_expires_in?: number | undefined;
121
146
  token_type?: string | undefined;
122
- scope?: (string | string[]) | undefined;
147
+ scope?: unknown;
123
148
  id_token?: string | undefined;
124
149
  }>(tokenEndpoint, {
125
150
  method: "POST",
@@ -133,7 +158,7 @@ export async function refreshAccessToken({
133
158
  accessToken: data.access_token,
134
159
  refreshToken: data.refresh_token,
135
160
  tokenType: data.token_type,
136
- scopes: Array.isArray(data.scope) ? data.scope : data.scope?.split(" "),
161
+ scopes: parseScopeField(data.scope),
137
162
  idToken: data.id_token,
138
163
  };
139
164
 
@@ -1,6 +1,26 @@
1
1
  import { base64Url } from "@better-auth/utils/base64";
2
2
  import type { OAuth2Tokens } from "./oauth-provider";
3
- import { parseScopeField } from "./scopes";
3
+
4
+ /**
5
+ * Parse a provider's `scope` token-response field into a string array.
6
+ *
7
+ * RFC 6749 Section 3.3 defines `scope` as a space-delimited string, but
8
+ * providers vary: some return an already-split array. Accept both forms and
9
+ * drop empty or non-string entries.
10
+ *
11
+ * @see https://github.com/better-auth/better-auth/issues/9076
12
+ */
13
+ export function parseScopeField(scope: unknown): string[] {
14
+ if (Array.isArray(scope)) {
15
+ return scope
16
+ .map((s) => (typeof s === "string" ? s.trim() : ""))
17
+ .filter(Boolean);
18
+ }
19
+ if (typeof scope === "string") {
20
+ return scope.trim().split(/\s+/).filter(Boolean);
21
+ }
22
+ return [];
23
+ }
4
24
 
5
25
  export function getOAuth2Tokens(data: Record<string, any>): OAuth2Tokens {
6
26
  const getDate = (seconds: number) => {
@@ -44,6 +64,24 @@ export function applyDefaultAccessTokenExpiry(
44
64
  return tokens;
45
65
  }
46
66
 
67
+ /**
68
+ * Compute the union of stored and incoming OAuth scopes, preserving
69
+ * stored insertion order and dropping duplicates.
70
+ */
71
+ export function mergeScopes(
72
+ stored: string | null | undefined,
73
+ incoming: string[] | undefined,
74
+ ): string {
75
+ const existing = stored
76
+ ? stored
77
+ .split(",")
78
+ .map((scope) => scope.trim())
79
+ .filter(Boolean)
80
+ : [];
81
+ const next = (incoming ?? []).map((scope) => scope.trim()).filter(Boolean);
82
+ return [...new Set([...existing, ...next])].join(",");
83
+ }
84
+
47
85
  /**
48
86
  * Return the provider's primary Client ID: the single string, or the entry at
49
87
  * array index 0 for the cross-platform form used by ID token audience
@@ -1,5 +1,7 @@
1
1
  import { decodeProtectedHeader, jwtVerify } from "jose";
2
- import type { ProviderOptions, UpstreamProvider } from "./oauth-provider";
2
+ import type { OAuthProvider, ProviderOptions } from "./oauth-provider";
3
+
4
+ type ProviderWithIdTokenConfig = Pick<OAuthProvider, "idToken" | "options">;
3
5
 
4
6
  async function sha256Hex(value: string) {
5
7
  const data = new TextEncoder().encode(value);
@@ -29,12 +31,12 @@ async function nonceMatches(
29
31
  /**
30
32
  * Whether a provider can verify a client-submitted id_token.
31
33
  *
32
- * A provider supports id_token sign-in when it declares an {@link UpstreamProvider.idToken}
34
+ * A provider supports id_token sign-in when it declares an {@link OAuthProvider.idToken}
33
35
  * verification config, or when the integrator supplies a `verifyIdToken` override on the
34
36
  * provider options. A provider whose options set `disableIdTokenSignIn`, or that declares
35
37
  * neither, rejects the client id_token sign-in path with `ID_TOKEN_NOT_SUPPORTED`.
36
38
  */
37
- export function supportsIdTokenSignIn(provider: UpstreamProvider<any, any>) {
39
+ export function supportsIdTokenSignIn(provider: ProviderWithIdTokenConfig) {
38
40
  const options = (provider.options ?? {}) as Partial<ProviderOptions>;
39
41
  if (options.disableIdTokenSignIn) {
40
42
  return false;
@@ -46,7 +48,7 @@ export function supportsIdTokenSignIn(provider: UpstreamProvider<any, any>) {
46
48
  * Verify a client-submitted id_token against a provider's verification config.
47
49
  *
48
50
  * This is the single id_token verifier for every social provider. Providers no longer
49
- * implement their own boolean `verifyIdToken`; they declare an {@link UpstreamProvider.idToken}
51
+ * implement their own boolean `verifyIdToken`; they declare an {@link OAuthProvider.idToken}
50
52
  * config and this function performs the cryptographic check. The contract is fail-closed: a
51
53
  * provider without a config (and without an integrator `verifyIdToken` override) returns
52
54
  * `false`, so a forged token can never be accepted by omission.
@@ -54,7 +56,7 @@ export function supportsIdTokenSignIn(provider: UpstreamProvider<any, any>) {
54
56
  * @returns `true` only when the token is authentic for the provider.
55
57
  */
56
58
  export async function verifyProviderIdToken(
57
- provider: UpstreamProvider<any, any>,
59
+ provider: ProviderWithIdTokenConfig,
58
60
  token: string,
59
61
  nonce?: string,
60
62
  ): Promise<boolean> {
@@ -79,6 +81,8 @@ export async function verifyProviderIdToken(
79
81
  // Opaque (non-JWS) tokens carry no signature to check. They are accepted only when the
80
82
  // provider opts in, in which case getUserInfo resolves identity from the access token via
81
83
  // the provider's userinfo endpoint, which validates it (e.g. Facebook Graph access tokens).
84
+ // An expected `nonce` is not enforced here: an opaque token carries no `nonce` claim, and the
85
+ // access-token-backed userinfo exchange (not the token itself) is the identity source.
82
86
  if (token.split(".").length !== 3) {
83
87
  return config.allowOpaqueToken === true;
84
88
  }
@@ -3,12 +3,11 @@ import { betterFetch } from "@better-fetch/fetch";
3
3
  import { decodeJwt, importJWK } from "jose";
4
4
  import { logger } from "../env";
5
5
  import { APIError, BetterAuthError } from "../error";
6
- import type { ProviderOptions, UpstreamProvider } from "../oauth2";
6
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
7
7
  import {
8
8
  createAuthorizationURL,
9
9
  getPrimaryClientId,
10
10
  refreshAccessToken,
11
- resolveRequestedScopes,
12
11
  validateAuthorizationCode,
13
12
  } from "../oauth2";
14
13
  export interface AppleProfile {
@@ -78,14 +77,11 @@ export interface AppleOptions extends ProviderOptions<AppleProfile> {
78
77
  audience?: (string | string[]) | undefined;
79
78
  }
80
79
 
81
- const APPLE_DEFAULT_SCOPES = ["email", "name"];
82
-
83
80
  export const apple = (options: AppleOptions) => {
84
81
  const tokenEndpoint = "https://appleid.apple.com/auth/token";
85
82
  return {
86
83
  id: "apple",
87
84
  name: "Apple",
88
- callbackPath: "/callback/apple",
89
85
  async createAuthorizationURL({
90
86
  state,
91
87
  scopes,
@@ -98,22 +94,21 @@ export const apple = (options: AppleOptions) => {
98
94
  );
99
95
  throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
100
96
  }
101
- const requestedScopes = resolveRequestedScopes(
102
- options,
103
- APPLE_DEFAULT_SCOPES,
104
- scopes,
105
- );
106
- return createAuthorizationURL({
97
+ const _scope = options.disableDefaultScope ? [] : ["email", "name"];
98
+ if (options.scope) _scope.push(...options.scope);
99
+ if (scopes) _scope.push(...scopes);
100
+ const url = await createAuthorizationURL({
107
101
  id: "apple",
108
102
  options,
109
103
  authorizationEndpoint: "https://appleid.apple.com/auth/authorize",
110
- scopes: requestedScopes,
104
+ scopes: _scope,
111
105
  state,
112
106
  redirectURI,
113
107
  responseMode: "form_post",
114
108
  responseType: "code id_token",
115
109
  additionalParams,
116
110
  });
111
+ return url;
117
112
  },
118
113
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
119
114
  return validateAuthorizationCode({
@@ -189,7 +184,7 @@ export const apple = (options: AppleOptions) => {
189
184
  };
190
185
  },
191
186
  options,
192
- } satisfies UpstreamProvider<AppleProfile>;
187
+ } satisfies OAuthProvider<AppleProfile>;
193
188
  };
194
189
 
195
190
  export const getApplePublicKey = async (kid: string) => {
@@ -1,11 +1,10 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
2
  import { logger } from "../env";
3
3
  import { BetterAuthError } from "../error";
4
- import type { ProviderOptions, UpstreamProvider } from "../oauth2";
4
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
5
5
  import {
6
6
  createAuthorizationURL,
7
7
  refreshAccessToken,
8
- resolveRequestedScopes,
9
8
  validateAuthorizationCode,
10
9
  } from "../oauth2";
11
10
 
@@ -30,14 +29,11 @@ export interface AtlassianOptions extends ProviderOptions<AtlassianProfile> {
30
29
  clientId: string;
31
30
  }
32
31
 
33
- const ATLASSIAN_DEFAULT_SCOPES = ["read:jira-user", "offline_access"];
34
-
35
32
  export const atlassian = (options: AtlassianOptions) => {
36
33
  const tokenEndpoint = "https://auth.atlassian.com/oauth/token";
37
34
  return {
38
35
  id: "atlassian",
39
36
  name: "Atlassian",
40
- callbackPath: "/callback/atlassian",
41
37
 
42
38
  async createAuthorizationURL({
43
39
  state,
@@ -54,17 +50,17 @@ export const atlassian = (options: AtlassianOptions) => {
54
50
  throw new BetterAuthError("codeVerifier is required for Atlassian");
55
51
  }
56
52
 
57
- const requestedScopes = resolveRequestedScopes(
58
- options,
59
- ATLASSIAN_DEFAULT_SCOPES,
60
- scopes,
61
- );
53
+ const _scopes = options.disableDefaultScope
54
+ ? []
55
+ : ["read:jira-user", "offline_access"];
56
+ if (options.scope) _scopes.push(...options.scope);
57
+ if (scopes) _scopes.push(...scopes);
62
58
 
63
59
  return createAuthorizationURL({
64
60
  id: "atlassian",
65
61
  options,
66
62
  authorizationEndpoint: "https://auth.atlassian.com/authorize",
67
- scopes: requestedScopes,
63
+ scopes: _scopes,
68
64
  state,
69
65
  codeVerifier,
70
66
  redirectURI,
@@ -140,5 +136,5 @@ export const atlassian = (options: AtlassianOptions) => {
140
136
  },
141
137
 
142
138
  options,
143
- } satisfies UpstreamProvider<AtlassianProfile>;
139
+ } satisfies OAuthProvider<AtlassianProfile>;
144
140
  };
@@ -2,12 +2,11 @@ import { betterFetch } from "@better-fetch/fetch";
2
2
  import { decodeJwt, importJWK } from "jose";
3
3
  import { logger } from "../env";
4
4
  import { APIError, BetterAuthError } from "../error";
5
- import type { ProviderOptions, UpstreamProvider } from "../oauth2";
5
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
6
6
  import {
7
7
  createAuthorizationURL,
8
8
  getPrimaryClientId,
9
9
  refreshAccessToken,
10
- resolveRequestedScopes,
11
10
  validateAuthorizationCode,
12
11
  } from "../oauth2";
13
12
 
@@ -58,8 +57,6 @@ export interface CognitoOptions extends ProviderOptions<CognitoProfile> {
58
57
  identityProvider?: string | undefined;
59
58
  }
60
59
 
61
- const COGNITO_DEFAULT_SCOPES = ["openid", "profile", "email"];
62
-
63
60
  export const cognito = (options: CognitoOptions) => {
64
61
  if (!options.domain || !options.region || !options.userPoolId) {
65
62
  logger.error(
@@ -76,7 +73,6 @@ export const cognito = (options: CognitoOptions) => {
76
73
  return {
77
74
  id: "cognito",
78
75
  name: "Cognito",
79
- callbackPath: "/callback/cognito",
80
76
  async createAuthorizationURL({
81
77
  state,
82
78
  scopes,
@@ -97,19 +93,19 @@ export const cognito = (options: CognitoOptions) => {
97
93
  );
98
94
  throw new BetterAuthError("CLIENT_SECRET_REQUIRED");
99
95
  }
100
- const requestedScopes = resolveRequestedScopes(
101
- options,
102
- COGNITO_DEFAULT_SCOPES,
103
- scopes,
104
- );
96
+ const _scopes = options.disableDefaultScope
97
+ ? []
98
+ : ["openid", "profile", "email"];
99
+ if (options.scope) _scopes.push(...options.scope);
100
+ if (scopes) _scopes.push(...scopes);
105
101
 
106
- const { url } = await createAuthorizationURL({
102
+ const url = await createAuthorizationURL({
107
103
  id: "cognito",
108
104
  options: {
109
105
  ...options,
110
106
  },
111
107
  authorizationEndpoint,
112
- scopes: requestedScopes,
108
+ scopes: _scopes,
113
109
  state,
114
110
  codeVerifier,
115
111
  redirectURI,
@@ -130,12 +126,9 @@ export const cognito = (options: CognitoOptions) => {
130
126
  // Manually append the scope with proper encoding to the URL
131
127
  const urlString = url.toString();
132
128
  const separator = urlString.includes("?") ? "&" : "?";
133
- return {
134
- url: new URL(`${urlString}${separator}scope=${encodedScope}`),
135
- requestedScopes,
136
- };
129
+ return new URL(`${urlString}${separator}scope=${encodedScope}`);
137
130
  }
138
- return { url, requestedScopes };
131
+ return url;
139
132
  },
140
133
 
141
134
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
@@ -243,7 +236,7 @@ export const cognito = (options: CognitoOptions) => {
243
236
  },
244
237
 
245
238
  options,
246
- } satisfies UpstreamProvider<CognitoProfile>;
239
+ } satisfies OAuthProvider<CognitoProfile>;
247
240
  };
248
241
 
249
242
  export const getCognitoPublicKey = async (
@@ -1,9 +1,8 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { ProviderOptions, UpstreamProvider } from "../oauth2";
2
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
- resolveRequestedScopes,
7
6
  validateAuthorizationCode,
8
7
  } from "../oauth2";
9
8
  export interface DiscordProfile extends Record<string, any> {
@@ -84,31 +83,21 @@ export interface DiscordOptions extends ProviderOptions<DiscordProfile> {
84
83
  permissions?: number | undefined;
85
84
  }
86
85
 
87
- const DISCORD_DEFAULT_SCOPES = ["identify", "email"];
88
-
89
86
  export const discord = (options: DiscordOptions) => {
90
87
  const tokenEndpoint = "https://discord.com/api/oauth2/token";
91
88
  return {
92
89
  id: "discord",
93
90
  name: "Discord",
94
- callbackPath: "/callback/discord",
95
- async createAuthorizationURL({
96
- state,
97
- scopes,
98
- redirectURI,
99
- additionalParams,
100
- }) {
101
- const requestedScopes = resolveRequestedScopes(
102
- options,
103
- DISCORD_DEFAULT_SCOPES,
104
- scopes,
105
- );
106
- const hasBotScope = requestedScopes.includes("bot");
91
+ createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
92
+ const _scopes = options.disableDefaultScope ? [] : ["identify", "email"];
93
+ if (scopes) _scopes.push(...scopes);
94
+ if (options.scope) _scopes.push(...options.scope);
95
+ const hasBotScope = _scopes.includes("bot");
107
96
  return createAuthorizationURL({
108
97
  id: "discord",
109
98
  options,
110
99
  authorizationEndpoint: "https://discord.com/api/oauth2/authorize",
111
- scopes: requestedScopes,
100
+ scopes: _scopes,
112
101
  state,
113
102
  redirectURI,
114
103
  prompt: options.prompt || "none",
@@ -181,5 +170,5 @@ export const discord = (options: DiscordOptions) => {
181
170
  };
182
171
  },
183
172
  options,
184
- } satisfies UpstreamProvider<DiscordProfile>;
173
+ } satisfies OAuthProvider<DiscordProfile>;
185
174
  };
@@ -1,9 +1,8 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { ProviderOptions, UpstreamProvider } from "../oauth2";
2
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
- resolveRequestedScopes,
7
6
  validateAuthorizationCode,
8
7
  } from "../oauth2";
9
8
 
@@ -26,15 +25,12 @@ export interface DropboxOptions extends ProviderOptions<DropboxProfile> {
26
25
  accessType?: ("offline" | "online" | "legacy") | undefined;
27
26
  }
28
27
 
29
- const DROPBOX_DEFAULT_SCOPES = ["account_info.read"];
30
-
31
28
  export const dropbox = (options: DropboxOptions) => {
32
29
  const tokenEndpoint = "https://api.dropboxapi.com/oauth2/token";
33
30
 
34
31
  return {
35
32
  id: "dropbox",
36
33
  name: "Dropbox",
37
- callbackPath: "/callback/dropbox",
38
34
  createAuthorizationURL: async ({
39
35
  state,
40
36
  scopes,
@@ -42,16 +38,14 @@ export const dropbox = (options: DropboxOptions) => {
42
38
  redirectURI,
43
39
  additionalParams,
44
40
  }) => {
45
- const requestedScopes = resolveRequestedScopes(
46
- options,
47
- DROPBOX_DEFAULT_SCOPES,
48
- scopes,
49
- );
50
- return createAuthorizationURL({
41
+ const _scopes = options.disableDefaultScope ? [] : ["account_info.read"];
42
+ if (options.scope) _scopes.push(...options.scope);
43
+ if (scopes) _scopes.push(...scopes);
44
+ return await createAuthorizationURL({
51
45
  id: "dropbox",
52
46
  options,
53
47
  authorizationEndpoint: "https://www.dropbox.com/oauth2/authorize",
54
- scopes: requestedScopes,
48
+ scopes: _scopes,
55
49
  state,
56
50
  redirectURI,
57
51
  codeVerifier,
@@ -116,5 +110,5 @@ export const dropbox = (options: DropboxOptions) => {
116
110
  };
117
111
  },
118
112
  options,
119
- } satisfies UpstreamProvider<DropboxProfile>;
113
+ } satisfies OAuthProvider<DropboxProfile>;
120
114
  };
@@ -2,12 +2,11 @@ import { betterFetch } from "@better-fetch/fetch";
2
2
  import { createRemoteJWKSet, decodeJwt } from "jose";
3
3
  import { logger } from "../env";
4
4
  import { BetterAuthError } from "../error";
5
- import type { ProviderOptions, UpstreamProvider } from "../oauth2";
5
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
6
6
  import {
7
7
  createAuthorizationURL,
8
8
  getPrimaryClientId,
9
9
  refreshAccessToken,
10
- resolveRequestedScopes,
11
10
  validateAuthorizationCode,
12
11
  } from "../oauth2";
13
12
  export interface FacebookProfile {
@@ -92,13 +91,10 @@ export interface FacebookOptions extends ProviderOptions<FacebookProfile> {
92
91
  configId?: string | undefined;
93
92
  }
94
93
 
95
- const FACEBOOK_DEFAULT_SCOPES = ["email", "public_profile"];
96
-
97
94
  export const facebook = (options: FacebookOptions) => {
98
95
  return {
99
96
  id: "facebook",
100
97
  name: "Facebook",
101
- callbackPath: "/callback/facebook",
102
98
  async createAuthorizationURL({
103
99
  state,
104
100
  scopes,
@@ -112,16 +108,16 @@ export const facebook = (options: FacebookOptions) => {
112
108
  );
113
109
  throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
114
110
  }
115
- const requestedScopes = resolveRequestedScopes(
116
- options,
117
- FACEBOOK_DEFAULT_SCOPES,
118
- scopes,
119
- );
120
- return createAuthorizationURL({
111
+ const _scopes = options.disableDefaultScope
112
+ ? []
113
+ : ["email", "public_profile"];
114
+ if (options.scope) _scopes.push(...options.scope);
115
+ if (scopes) _scopes.push(...scopes);
116
+ return await createAuthorizationURL({
121
117
  id: "facebook",
122
118
  options,
123
119
  authorizationEndpoint: "https://www.facebook.com/v24.0/dialog/oauth",
124
- scopes: requestedScopes,
120
+ scopes: _scopes,
125
121
  state,
126
122
  redirectURI,
127
123
  loginHint,
@@ -262,5 +258,5 @@ export const facebook = (options: FacebookOptions) => {
262
258
  };
263
259
  },
264
260
  options,
265
- } satisfies UpstreamProvider<FacebookProfile>;
261
+ } satisfies OAuthProvider<FacebookProfile>;
266
262
  };
@@ -1,11 +1,10 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
2
  import { logger } from "../env";
3
3
  import { BetterAuthError } from "../error";
4
- import type { ProviderOptions, UpstreamProvider } from "../oauth2";
4
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
5
5
  import {
6
6
  createAuthorizationURL,
7
7
  refreshAccessToken,
8
- resolveRequestedScopes,
9
8
  validateAuthorizationCode,
10
9
  } from "../oauth2";
11
10
 
@@ -20,14 +19,11 @@ export interface FigmaOptions extends ProviderOptions<FigmaProfile> {
20
19
  clientId: string;
21
20
  }
22
21
 
23
- const FIGMA_DEFAULT_SCOPES = ["current_user:read"];
24
-
25
22
  export const figma = (options: FigmaOptions) => {
26
23
  const tokenEndpoint = "https://api.figma.com/v1/oauth/token";
27
24
  return {
28
25
  id: "figma",
29
26
  name: "Figma",
30
- callbackPath: "/callback/figma",
31
27
  async createAuthorizationURL({
32
28
  state,
33
29
  scopes,
@@ -45,22 +41,22 @@ export const figma = (options: FigmaOptions) => {
45
41
  throw new BetterAuthError("codeVerifier is required for Figma");
46
42
  }
47
43
 
48
- const requestedScopes = resolveRequestedScopes(
49
- options,
50
- FIGMA_DEFAULT_SCOPES,
51
- scopes,
52
- );
44
+ const _scopes = options.disableDefaultScope ? [] : ["current_user:read"];
45
+ if (options.scope) _scopes.push(...options.scope);
46
+ if (scopes) _scopes.push(...scopes);
53
47
 
54
- return createAuthorizationURL({
48
+ const url = await createAuthorizationURL({
55
49
  id: "figma",
56
50
  options,
57
51
  authorizationEndpoint: "https://www.figma.com/oauth",
58
- scopes: requestedScopes,
52
+ scopes: _scopes,
59
53
  state,
60
54
  codeVerifier,
61
55
  redirectURI,
62
56
  additionalParams,
63
57
  });
58
+
59
+ return url;
64
60
  },
65
61
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
66
62
  return validateAuthorizationCode({
@@ -125,5 +121,5 @@ export const figma = (options: FigmaOptions) => {
125
121
  }
126
122
  },
127
123
  options,
128
- } satisfies UpstreamProvider<FigmaProfile>;
124
+ } satisfies OAuthProvider<FigmaProfile>;
129
125
  };
@@ -1,11 +1,10 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
2
  import { logger } from "../env";
3
- import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
4
4
  import {
5
5
  createAuthorizationURL,
6
6
  getOAuth2Tokens,
7
7
  refreshAccessToken,
8
- resolveRequestedScopes,
9
8
  } from "../oauth2";
10
9
  import { authorizationCodeRequest } from "../oauth2/validate-authorization-code";
11
10
 
@@ -59,14 +58,11 @@ export interface GithubProfile {
59
58
  export interface GithubOptions extends ProviderOptions<GithubProfile> {
60
59
  clientId: string;
61
60
  }
62
- const GITHUB_DEFAULT_SCOPES = ["read:user", "user:email"];
63
-
64
61
  export const github = (options: GithubOptions) => {
65
62
  const tokenEndpoint = "https://github.com/login/oauth/access_token";
66
63
  return {
67
64
  id: "github",
68
65
  name: "GitHub",
69
- callbackPath: "/callback/github",
70
66
  createAuthorizationURL({
71
67
  state,
72
68
  scopes,
@@ -75,16 +71,16 @@ export const github = (options: GithubOptions) => {
75
71
  redirectURI,
76
72
  additionalParams,
77
73
  }) {
78
- const requestedScopes = resolveRequestedScopes(
79
- options,
80
- GITHUB_DEFAULT_SCOPES,
81
- scopes,
82
- );
74
+ const _scopes = options.disableDefaultScope
75
+ ? []
76
+ : ["read:user", "user:email"];
77
+ if (options.scope) _scopes.push(...options.scope);
78
+ if (scopes) _scopes.push(...scopes);
83
79
  return createAuthorizationURL({
84
80
  id: "github",
85
81
  options,
86
82
  authorizationEndpoint: "https://github.com/login/oauth/authorize",
87
- scopes: requestedScopes,
83
+ scopes: _scopes,
88
84
  state,
89
85
  codeVerifier,
90
86
  redirectURI,
@@ -186,5 +182,5 @@ export const github = (options: GithubOptions) => {
186
182
  };
187
183
  },
188
184
  options,
189
- } satisfies UpstreamProvider<GithubProfile>;
185
+ } satisfies OAuthProvider<GithubProfile>;
190
186
  };