@better-auth/core 1.7.0-beta.4 → 1.7.0-beta.5

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 (150) hide show
  1. package/dist/api/index.d.mts +3 -3
  2. package/dist/context/global.mjs +1 -1
  3. package/dist/db/adapter/factory.mjs +2 -2
  4. package/dist/db/get-tables.mjs +3 -3
  5. package/dist/db/schema/account.d.mts +1 -1
  6. package/dist/db/schema/account.mjs +1 -1
  7. package/dist/env/env-impl.mjs +1 -1
  8. package/dist/error/codes.d.mts +5 -0
  9. package/dist/error/codes.mjs +5 -0
  10. package/dist/index.d.mts +2 -2
  11. package/dist/instrumentation/tracer.mjs +1 -1
  12. package/dist/oauth2/create-authorization-url.d.mts +4 -1
  13. package/dist/oauth2/create-authorization-url.mjs +5 -2
  14. package/dist/oauth2/index.d.mts +4 -2
  15. package/dist/oauth2/index.mjs +3 -1
  16. package/dist/oauth2/oauth-provider.d.mts +128 -9
  17. package/dist/oauth2/refresh-access-token.mjs +1 -1
  18. package/dist/oauth2/scopes.d.mts +76 -0
  19. package/dist/oauth2/scopes.mjs +96 -0
  20. package/dist/oauth2/utils.mjs +2 -1
  21. package/dist/oauth2/verify-id-token.d.mts +26 -0
  22. package/dist/oauth2/verify-id-token.mjs +62 -0
  23. package/dist/oauth2/verify.d.mts +14 -0
  24. package/dist/oauth2/verify.mjs +23 -7
  25. package/dist/social-providers/apple.d.mts +14 -2
  26. package/dist/social-providers/apple.mjs +12 -36
  27. package/dist/social-providers/atlassian.d.mts +5 -1
  28. package/dist/social-providers/atlassian.mjs +4 -4
  29. package/dist/social-providers/cognito.d.mts +13 -2
  30. package/dist/social-providers/cognito.mjs +24 -32
  31. package/dist/social-providers/discord.d.mts +5 -1
  32. package/dist/social-providers/discord.mjs +7 -6
  33. package/dist/social-providers/dropbox.d.mts +5 -1
  34. package/dist/social-providers/dropbox.mjs +5 -5
  35. package/dist/social-providers/facebook.d.mts +21 -2
  36. package/dist/social-providers/facebook.mjs +46 -22
  37. package/dist/social-providers/figma.d.mts +5 -1
  38. package/dist/social-providers/figma.mjs +5 -5
  39. package/dist/social-providers/github.d.mts +5 -1
  40. package/dist/social-providers/github.mjs +4 -4
  41. package/dist/social-providers/gitlab.d.mts +5 -1
  42. package/dist/social-providers/gitlab.mjs +6 -6
  43. package/dist/social-providers/google.d.mts +29 -3
  44. package/dist/social-providers/google.mjs +24 -30
  45. package/dist/social-providers/huggingface.d.mts +5 -1
  46. package/dist/social-providers/huggingface.mjs +8 -8
  47. package/dist/social-providers/index.d.mts +221 -42
  48. package/dist/social-providers/kakao.d.mts +5 -1
  49. package/dist/social-providers/kakao.mjs +8 -8
  50. package/dist/social-providers/kick.d.mts +5 -1
  51. package/dist/social-providers/kick.mjs +4 -4
  52. package/dist/social-providers/line.d.mts +8 -2
  53. package/dist/social-providers/line.mjs +12 -14
  54. package/dist/social-providers/linear.d.mts +5 -1
  55. package/dist/social-providers/linear.mjs +4 -4
  56. package/dist/social-providers/linkedin.d.mts +5 -1
  57. package/dist/social-providers/linkedin.mjs +10 -10
  58. package/dist/social-providers/microsoft-entra-id.d.mts +31 -6
  59. package/dist/social-providers/microsoft-entra-id.mjs +26 -37
  60. package/dist/social-providers/naver.d.mts +5 -1
  61. package/dist/social-providers/naver.mjs +4 -4
  62. package/dist/social-providers/notion.d.mts +5 -1
  63. package/dist/social-providers/notion.mjs +4 -4
  64. package/dist/social-providers/paybin.d.mts +5 -1
  65. package/dist/social-providers/paybin.mjs +10 -10
  66. package/dist/social-providers/paypal.d.mts +5 -2
  67. package/dist/social-providers/paypal.mjs +8 -13
  68. package/dist/social-providers/polar.d.mts +5 -1
  69. package/dist/social-providers/polar.mjs +8 -8
  70. package/dist/social-providers/railway.d.mts +5 -1
  71. package/dist/social-providers/railway.mjs +9 -9
  72. package/dist/social-providers/reddit.d.mts +5 -1
  73. package/dist/social-providers/reddit.mjs +9 -8
  74. package/dist/social-providers/roblox.d.mts +5 -1
  75. package/dist/social-providers/roblox.mjs +5 -5
  76. package/dist/social-providers/salesforce.d.mts +5 -1
  77. package/dist/social-providers/salesforce.mjs +8 -8
  78. package/dist/social-providers/slack.d.mts +5 -1
  79. package/dist/social-providers/slack.mjs +9 -9
  80. package/dist/social-providers/spotify.d.mts +5 -1
  81. package/dist/social-providers/spotify.mjs +5 -5
  82. package/dist/social-providers/tiktok.d.mts +5 -1
  83. package/dist/social-providers/tiktok.mjs +9 -5
  84. package/dist/social-providers/twitch.d.mts +5 -1
  85. package/dist/social-providers/twitch.mjs +4 -4
  86. package/dist/social-providers/twitter.d.mts +6 -4
  87. package/dist/social-providers/twitter.mjs +9 -9
  88. package/dist/social-providers/vercel.d.mts +5 -1
  89. package/dist/social-providers/vercel.mjs +4 -7
  90. package/dist/social-providers/vk.d.mts +5 -1
  91. package/dist/social-providers/vk.mjs +5 -5
  92. package/dist/social-providers/wechat.d.mts +5 -1
  93. package/dist/social-providers/wechat.mjs +9 -5
  94. package/dist/social-providers/zoom.d.mts +6 -1
  95. package/dist/social-providers/zoom.mjs +15 -9
  96. package/dist/types/context.d.mts +10 -8
  97. package/dist/types/index.d.mts +1 -1
  98. package/dist/types/init-options.d.mts +92 -1
  99. package/package.json +5 -5
  100. package/src/db/adapter/factory.ts +10 -2
  101. package/src/db/get-tables.ts +8 -3
  102. package/src/db/schema/account.ts +14 -2
  103. package/src/env/env-impl.ts +1 -2
  104. package/src/error/codes.ts +5 -0
  105. package/src/oauth2/create-authorization-url.ts +2 -2
  106. package/src/oauth2/index.ts +17 -1
  107. package/src/oauth2/oauth-provider.ts +140 -10
  108. package/src/oauth2/refresh-access-token.ts +2 -2
  109. package/src/oauth2/scopes.ts +118 -0
  110. package/src/oauth2/utils.ts +2 -5
  111. package/src/oauth2/verify-id-token.ts +111 -0
  112. package/src/oauth2/verify.ts +62 -11
  113. package/src/social-providers/apple.ts +24 -61
  114. package/src/social-providers/atlassian.ts +12 -8
  115. package/src/social-providers/cognito.ts +25 -47
  116. package/src/social-providers/discord.ts +19 -8
  117. package/src/social-providers/dropbox.ts +13 -7
  118. package/src/social-providers/facebook.ts +97 -51
  119. package/src/social-providers/figma.ts +13 -9
  120. package/src/social-providers/github.ts +12 -8
  121. package/src/social-providers/gitlab.ts +14 -8
  122. package/src/social-providers/google.ts +66 -47
  123. package/src/social-providers/huggingface.ts +12 -8
  124. package/src/social-providers/kakao.ts +16 -8
  125. package/src/social-providers/kick.ts +12 -7
  126. package/src/social-providers/line.ts +37 -37
  127. package/src/social-providers/linear.ts +12 -6
  128. package/src/social-providers/linkedin.ts +14 -10
  129. package/src/social-providers/microsoft-entra-id.ts +65 -64
  130. package/src/social-providers/naver.ts +12 -6
  131. package/src/social-providers/notion.ts +12 -6
  132. package/src/social-providers/paybin.ts +14 -11
  133. package/src/social-providers/paypal.ts +6 -25
  134. package/src/social-providers/polar.ts +12 -8
  135. package/src/social-providers/railway.ts +13 -9
  136. package/src/social-providers/reddit.ts +21 -10
  137. package/src/social-providers/roblox.ts +18 -7
  138. package/src/social-providers/salesforce.ts +12 -8
  139. package/src/social-providers/slack.ts +18 -9
  140. package/src/social-providers/spotify.ts +13 -7
  141. package/src/social-providers/tiktok.ts +13 -7
  142. package/src/social-providers/twitch.ts +12 -8
  143. package/src/social-providers/twitter.ts +17 -8
  144. package/src/social-providers/vercel.ts +16 -10
  145. package/src/social-providers/vk.ts +13 -7
  146. package/src/social-providers/wechat.ts +20 -8
  147. package/src/social-providers/zoom.ts +19 -6
  148. package/src/types/context.ts +8 -8
  149. package/src/types/index.ts +7 -0
  150. package/src/types/init-options.ts +119 -0
@@ -1,10 +1,11 @@
1
1
  import { base64 } from "@better-auth/utils/base64";
2
2
  import { betterFetch } from "@better-fetch/fetch";
3
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
4
4
  import {
5
5
  createAuthorizationURL,
6
6
  getOAuth2Tokens,
7
7
  refreshAccessToken,
8
+ resolveRequestedScopes,
8
9
  } from "../oauth2";
9
10
 
10
11
  export interface RedditProfile {
@@ -21,19 +22,29 @@ export interface RedditOptions extends ProviderOptions<RedditProfile> {
21
22
  duration?: string | undefined;
22
23
  }
23
24
 
25
+ const REDDIT_DEFAULT_SCOPES = ["identity"];
26
+
24
27
  export const reddit = (options: RedditOptions) => {
25
28
  return {
26
29
  id: "reddit",
27
30
  name: "Reddit",
28
- createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
29
- const _scopes = options.disableDefaultScope ? [] : ["identity"];
30
- if (options.scope) _scopes.push(...options.scope);
31
- if (scopes) _scopes.push(...scopes);
31
+ callbackPath: "/callback/reddit",
32
+ async createAuthorizationURL({
33
+ state,
34
+ scopes,
35
+ redirectURI,
36
+ additionalParams,
37
+ }) {
38
+ const requestedScopes = resolveRequestedScopes(
39
+ options,
40
+ REDDIT_DEFAULT_SCOPES,
41
+ scopes,
42
+ );
32
43
  return createAuthorizationURL({
33
44
  id: "reddit",
34
45
  options,
35
46
  authorizationEndpoint: "https://www.reddit.com/api/v1/authorize",
36
- scopes: _scopes,
47
+ scopes: requestedScopes,
37
48
  state,
38
49
  redirectURI,
39
50
  duration: options.duration,
@@ -105,19 +116,19 @@ export const reddit = (options: RedditOptions) => {
105
116
  }
106
117
 
107
118
  const userMap = await options.mapProfileToUser?.(profile);
108
-
119
+ const email = userMap?.email || `${profile.id}@reddit.com`;
109
120
  return {
110
121
  user: {
111
122
  id: profile.id,
112
123
  name: profile.name,
113
- email: profile.oauth_client_id,
114
- emailVerified: profile.has_verified_email,
115
124
  image: profile.icon_img?.split("?")[0]!,
116
125
  ...userMap,
126
+ email,
127
+ emailVerified: userMap?.emailVerified ?? false,
117
128
  },
118
129
  data: profile,
119
130
  };
120
131
  },
121
132
  options,
122
- } satisfies OAuthProvider<RedditProfile>;
133
+ } satisfies UpstreamProvider<RedditProfile>;
123
134
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -36,20 +37,30 @@ export interface RobloxOptions extends ProviderOptions<RobloxProfile> {
36
37
  | undefined;
37
38
  }
38
39
 
40
+ const ROBLOX_DEFAULT_SCOPES = ["openid", "profile"];
41
+
39
42
  export const roblox = (options: RobloxOptions) => {
40
43
  const tokenEndpoint = "https://apis.roblox.com/oauth/v1/token";
41
44
  return {
42
45
  id: "roblox",
43
46
  name: "Roblox",
44
- createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
45
- const _scopes = options.disableDefaultScope ? [] : ["openid", "profile"];
46
- if (options.scope) _scopes.push(...options.scope);
47
- if (scopes) _scopes.push(...scopes);
47
+ callbackPath: "/callback/roblox",
48
+ async createAuthorizationURL({
49
+ state,
50
+ scopes,
51
+ redirectURI,
52
+ additionalParams,
53
+ }) {
54
+ const requestedScopes = resolveRequestedScopes(
55
+ options,
56
+ ROBLOX_DEFAULT_SCOPES,
57
+ scopes,
58
+ );
48
59
  return createAuthorizationURL({
49
60
  id: "roblox",
50
61
  options,
51
62
  authorizationEndpoint: "https://apis.roblox.com/oauth/v1/authorize",
52
- scopes: _scopes,
63
+ scopes: requestedScopes,
53
64
  state,
54
65
  redirectURI,
55
66
  prompt: options.prompt || "select_account consent",
@@ -113,5 +124,5 @@ export const roblox = (options: RobloxOptions) => {
113
124
  };
114
125
  },
115
126
  options,
116
- } satisfies OAuthProvider<RobloxProfile>;
127
+ } satisfies UpstreamProvider<RobloxProfile>;
117
128
  };
@@ -1,10 +1,11 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
2
  import { logger } from "../env";
3
3
  import { BetterAuthError } from "../error";
4
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
4
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
5
5
  import {
6
6
  createAuthorizationURL,
7
7
  refreshAccessToken,
8
+ resolveRequestedScopes,
8
9
  validateAuthorizationCode,
9
10
  } from "../oauth2";
10
11
 
@@ -39,6 +40,8 @@ export interface SalesforceOptions extends ProviderOptions<SalesforceProfile> {
39
40
  redirectURI?: string | undefined;
40
41
  }
41
42
 
43
+ const SALESFORCE_DEFAULT_SCOPES = ["openid", "email", "profile"];
44
+
42
45
  export const salesforce = (options: SalesforceOptions) => {
43
46
  const environment = options.environment ?? "production";
44
47
  const isSandbox = environment === "sandbox";
@@ -63,6 +66,7 @@ export const salesforce = (options: SalesforceOptions) => {
63
66
  return {
64
67
  id: "salesforce",
65
68
  name: "Salesforce",
69
+ callbackPath: "/callback/salesforce",
66
70
 
67
71
  async createAuthorizationURL({
68
72
  state,
@@ -81,17 +85,17 @@ export const salesforce = (options: SalesforceOptions) => {
81
85
  throw new BetterAuthError("codeVerifier is required for Salesforce");
82
86
  }
83
87
 
84
- const _scopes = options.disableDefaultScope
85
- ? []
86
- : ["openid", "email", "profile"];
87
- if (options.scope) _scopes.push(...options.scope);
88
- if (scopes) _scopes.push(...scopes);
88
+ const requestedScopes = resolveRequestedScopes(
89
+ options,
90
+ SALESFORCE_DEFAULT_SCOPES,
91
+ scopes,
92
+ );
89
93
 
90
94
  return createAuthorizationURL({
91
95
  id: "salesforce",
92
96
  options,
93
97
  authorizationEndpoint,
94
- scopes: _scopes,
98
+ scopes: requestedScopes,
95
99
  state,
96
100
  codeVerifier,
97
101
  redirectURI: options.redirectURI || redirectURI,
@@ -162,5 +166,5 @@ export const salesforce = (options: SalesforceOptions) => {
162
166
  },
163
167
 
164
168
  options,
165
- } satisfies OAuthProvider<SalesforceProfile>;
169
+ } satisfies UpstreamProvider<SalesforceProfile>;
166
170
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -41,22 +42,30 @@ export interface SlackOptions extends ProviderOptions<SlackProfile> {
41
42
  clientId: string;
42
43
  }
43
44
 
45
+ const SLACK_DEFAULT_SCOPES = ["openid", "profile", "email"];
46
+
44
47
  export const slack = (options: SlackOptions) => {
45
48
  const tokenEndpoint = "https://slack.com/api/openid.connect.token";
46
49
  return {
47
50
  id: "slack",
48
51
  name: "Slack",
49
- createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
50
- const _scopes = options.disableDefaultScope
51
- ? []
52
- : ["openid", "profile", "email"];
53
- if (scopes) _scopes.push(...scopes);
54
- if (options.scope) _scopes.push(...options.scope);
52
+ callbackPath: "/callback/slack",
53
+ async createAuthorizationURL({
54
+ state,
55
+ scopes,
56
+ redirectURI,
57
+ additionalParams,
58
+ }) {
59
+ const requestedScopes = resolveRequestedScopes(
60
+ options,
61
+ SLACK_DEFAULT_SCOPES,
62
+ scopes,
63
+ );
55
64
  return createAuthorizationURL({
56
65
  id: "slack",
57
66
  options,
58
67
  authorizationEndpoint: "https://slack.com/openid/connect/authorize",
59
- scopes: _scopes,
68
+ scopes: requestedScopes,
60
69
  state,
61
70
  redirectURI,
62
71
  additionalParams,
@@ -114,5 +123,5 @@ export const slack = (options: SlackOptions) => {
114
123
  };
115
124
  },
116
125
  options,
117
- } satisfies OAuthProvider<SlackProfile>;
126
+ } satisfies UpstreamProvider<SlackProfile>;
118
127
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -19,26 +20,31 @@ export interface SpotifyOptions extends ProviderOptions<SpotifyProfile> {
19
20
  clientId: string;
20
21
  }
21
22
 
23
+ const SPOTIFY_DEFAULT_SCOPES = ["user-read-email"];
24
+
22
25
  export const spotify = (options: SpotifyOptions) => {
23
26
  const tokenEndpoint = "https://accounts.spotify.com/api/token";
24
27
  return {
25
28
  id: "spotify",
26
29
  name: "Spotify",
27
- createAuthorizationURL({
30
+ callbackPath: "/callback/spotify",
31
+ async createAuthorizationURL({
28
32
  state,
29
33
  scopes,
30
34
  codeVerifier,
31
35
  redirectURI,
32
36
  additionalParams,
33
37
  }) {
34
- const _scopes = options.disableDefaultScope ? [] : ["user-read-email"];
35
- if (options.scope) _scopes.push(...options.scope);
36
- if (scopes) _scopes.push(...scopes);
38
+ const requestedScopes = resolveRequestedScopes(
39
+ options,
40
+ SPOTIFY_DEFAULT_SCOPES,
41
+ scopes,
42
+ );
37
43
  return createAuthorizationURL({
38
44
  id: "spotify",
39
45
  options,
40
46
  authorizationEndpoint: "https://accounts.spotify.com/authorize",
41
- scopes: _scopes,
47
+ scopes: requestedScopes,
42
48
  state,
43
49
  codeVerifier,
44
50
  redirectURI,
@@ -97,5 +103,5 @@ export const spotify = (options: SpotifyOptions) => {
97
103
  };
98
104
  },
99
105
  options,
100
- } satisfies OAuthProvider<SpotifyProfile>;
106
+ } satisfies UpstreamProvider<SpotifyProfile>;
101
107
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  RESERVED_AUTHORIZATION_PARAMS_SET,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -130,19 +131,24 @@ export interface TiktokOptions extends ProviderOptions {
130
131
  clientKey: string;
131
132
  }
132
133
 
134
+ const TIKTOK_DEFAULT_SCOPES = ["user.info.profile"];
135
+
133
136
  export const tiktok = (options: TiktokOptions) => {
134
137
  const tokenEndpoint = "https://open.tiktokapis.com/v2/oauth/token/";
135
138
  return {
136
139
  id: "tiktok",
137
140
  name: "TikTok",
141
+ callbackPath: "/callback/tiktok",
138
142
  createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
139
- const _scopes = options.disableDefaultScope ? [] : ["user.info.profile"];
140
- if (options.scope) _scopes.push(...options.scope);
141
- if (scopes) _scopes.push(...scopes);
143
+ const requestedScopes = resolveRequestedScopes(
144
+ options,
145
+ TIKTOK_DEFAULT_SCOPES,
146
+ scopes,
147
+ );
142
148
  // TikTok uses `client_key` instead of the standard `client_id`, so the
143
149
  // shared createAuthorizationURL helper cannot be used directly.
144
150
  const url = new URL("https://www.tiktok.com/v2/auth/authorize");
145
- url.searchParams.set("scope", _scopes.join(","));
151
+ url.searchParams.set("scope", requestedScopes.join(","));
146
152
  url.searchParams.set("response_type", "code");
147
153
  url.searchParams.set("client_key", options.clientKey);
148
154
  url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
@@ -154,7 +160,7 @@ export const tiktok = (options: TiktokOptions) => {
154
160
  url.searchParams.set(key, value);
155
161
  }
156
162
  }
157
- return url;
163
+ return { url, requestedScopes };
158
164
  },
159
165
 
160
166
  validateAuthorizationCode: async ({ code, redirectURI }) => {
@@ -220,5 +226,5 @@ export const tiktok = (options: TiktokOptions) => {
220
226
  };
221
227
  },
222
228
  options,
223
- } satisfies OAuthProvider<TiktokProfile, TiktokOptions>;
229
+ } satisfies UpstreamProvider<TiktokProfile, TiktokOptions>;
224
230
  };
@@ -1,9 +1,10 @@
1
1
  import { decodeJwt } from "jose";
2
2
  import { logger } from "../env";
3
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
4
4
  import {
5
5
  createAuthorizationURL,
6
6
  refreshAccessToken,
7
+ resolveRequestedScopes,
7
8
  validateAuthorizationCode,
8
9
  } from "../oauth2";
9
10
 
@@ -37,23 +38,26 @@ export interface TwitchOptions extends ProviderOptions<TwitchProfile> {
37
38
  clientId: string;
38
39
  claims?: string[] | undefined;
39
40
  }
41
+ const TWITCH_DEFAULT_SCOPES = ["user:read:email", "openid"];
42
+
40
43
  export const twitch = (options: TwitchOptions) => {
41
44
  const tokenEndpoint = "https://id.twitch.tv/oauth2/token";
42
45
  return {
43
46
  id: "twitch",
44
47
  name: "Twitch",
48
+ callbackPath: "/callback/twitch",
45
49
  createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
46
- const _scopes = options.disableDefaultScope
47
- ? []
48
- : ["user:read:email", "openid"];
49
- if (options.scope) _scopes.push(...options.scope);
50
- if (scopes) _scopes.push(...scopes);
50
+ const requestedScopes = resolveRequestedScopes(
51
+ options,
52
+ TWITCH_DEFAULT_SCOPES,
53
+ scopes,
54
+ );
51
55
  return createAuthorizationURL({
52
56
  id: "twitch",
53
57
  redirectURI,
54
58
  options,
55
59
  authorizationEndpoint: "https://id.twitch.tv/oauth2/authorize",
56
- scopes: _scopes,
60
+ scopes: requestedScopes,
57
61
  state,
58
62
  claims: options.claims || [
59
63
  "email",
@@ -109,5 +113,5 @@ export const twitch = (options: TwitchOptions) => {
109
113
  };
110
114
  },
111
115
  options,
112
- } satisfies OAuthProvider<TwitchProfile>;
116
+ } satisfies UpstreamProvider<TwitchProfile>;
113
117
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -103,22 +104,30 @@ export interface TwitterOption extends ProviderOptions<TwitterProfile> {
103
104
  clientId: string;
104
105
  }
105
106
 
107
+ const TWITTER_DEFAULT_SCOPES = [
108
+ "users.read",
109
+ "tweet.read",
110
+ "offline.access",
111
+ "users.email",
112
+ ];
113
+
106
114
  export const twitter = (options: TwitterOption) => {
107
115
  const tokenEndpoint = "https://api.x.com/2/oauth2/token";
108
116
  return {
109
117
  id: "twitter",
110
118
  name: "Twitter",
119
+ callbackPath: "/callback/twitter",
111
120
  createAuthorizationURL(data) {
112
- const _scopes = options.disableDefaultScope
113
- ? []
114
- : ["users.read", "tweet.read", "offline.access", "users.email"];
115
- if (options.scope) _scopes.push(...options.scope);
116
- if (data.scopes) _scopes.push(...data.scopes);
121
+ const requestedScopes = resolveRequestedScopes(
122
+ options,
123
+ TWITTER_DEFAULT_SCOPES,
124
+ data.scopes,
125
+ );
117
126
  return createAuthorizationURL({
118
127
  id: "twitter",
119
128
  options,
120
129
  authorizationEndpoint: "https://x.com/i/oauth2/authorize",
121
- scopes: _scopes,
130
+ scopes: requestedScopes,
122
131
  state: data.state,
123
132
  codeVerifier: data.codeVerifier,
124
133
  redirectURI: data.redirectURI,
@@ -196,5 +205,5 @@ export const twitter = (options: TwitterOption) => {
196
205
  };
197
206
  },
198
207
  options,
199
- } satisfies OAuthProvider<TwitterProfile>;
208
+ } satisfies UpstreamProvider<TwitterProfile>;
200
209
  };
@@ -1,7 +1,11 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
2
  import { BetterAuthError } from "../error";
3
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
4
- import { createAuthorizationURL, validateAuthorizationCode } from "../oauth2";
3
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
4
+ import {
5
+ createAuthorizationURL,
6
+ resolveRequestedScopes,
7
+ validateAuthorizationCode,
8
+ } from "../oauth2";
5
9
 
6
10
  export interface VercelProfile {
7
11
  sub: string;
@@ -16,10 +20,13 @@ export interface VercelOptions extends ProviderOptions<VercelProfile> {
16
20
  clientId: string;
17
21
  }
18
22
 
23
+ const VERCEL_DEFAULT_SCOPES: string[] = [];
24
+
19
25
  export const vercel = (options: VercelOptions) => {
20
26
  return {
21
27
  id: "vercel",
22
28
  name: "Vercel",
29
+ callbackPath: "/callback/vercel",
23
30
  createAuthorizationURL({
24
31
  state,
25
32
  scopes,
@@ -31,18 +38,17 @@ export const vercel = (options: VercelOptions) => {
31
38
  throw new BetterAuthError("codeVerifier is required for Vercel");
32
39
  }
33
40
 
34
- let _scopes: string[] | undefined = undefined;
35
- if (options.scope !== undefined || scopes !== undefined) {
36
- _scopes = [];
37
- if (options.scope) _scopes.push(...options.scope);
38
- if (scopes) _scopes.push(...scopes);
39
- }
41
+ const requestedScopes = resolveRequestedScopes(
42
+ options,
43
+ VERCEL_DEFAULT_SCOPES,
44
+ scopes,
45
+ );
40
46
 
41
47
  return createAuthorizationURL({
42
48
  id: "vercel",
43
49
  options,
44
50
  authorizationEndpoint: "https://vercel.com/oauth/authorize",
45
- scopes: _scopes,
51
+ scopes: requestedScopes,
46
52
  state,
47
53
  codeVerifier,
48
54
  redirectURI,
@@ -90,5 +96,5 @@ export const vercel = (options: VercelOptions) => {
90
96
  };
91
97
  },
92
98
  options,
93
- } satisfies OAuthProvider<VercelProfile>;
99
+ } satisfies UpstreamProvider<VercelProfile>;
94
100
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -25,28 +26,33 @@ export interface VkOption extends ProviderOptions {
25
26
  scheme?: ("light" | "dark") | undefined;
26
27
  }
27
28
 
29
+ const VK_DEFAULT_SCOPES = ["email", "phone"];
30
+
28
31
  export const vk = (options: VkOption) => {
29
32
  const tokenEndpoint = "https://id.vk.com/oauth2/auth";
30
33
  return {
31
34
  id: "vk",
32
35
  name: "VK",
33
- async createAuthorizationURL({
36
+ callbackPath: "/callback/vk",
37
+ createAuthorizationURL({
34
38
  state,
35
39
  scopes,
36
40
  codeVerifier,
37
41
  redirectURI,
38
42
  additionalParams,
39
43
  }) {
40
- const _scopes = options.disableDefaultScope ? [] : ["email", "phone"];
41
- if (options.scope) _scopes.push(...options.scope);
42
- if (scopes) _scopes.push(...scopes);
44
+ const requestedScopes = resolveRequestedScopes(
45
+ options,
46
+ VK_DEFAULT_SCOPES,
47
+ scopes,
48
+ );
43
49
  const authorizationEndpoint = "https://id.vk.com/authorize";
44
50
 
45
51
  return createAuthorizationURL({
46
52
  id: "vk",
47
53
  options,
48
54
  authorizationEndpoint,
49
- scopes: _scopes,
55
+ scopes: requestedScopes,
50
56
  state,
51
57
  redirectURI,
52
58
  codeVerifier,
@@ -128,5 +134,5 @@ export const vk = (options: VkOption) => {
128
134
  };
129
135
  },
130
136
  options,
131
- } satisfies OAuthProvider<VkProfile>;
137
+ } satisfies UpstreamProvider<VkProfile>;
132
138
  };
@@ -1,6 +1,13 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuth2Tokens, OAuthProvider, ProviderOptions } from "../oauth2";
3
- import { RESERVED_AUTHORIZATION_PARAMS_SET } from "../oauth2";
2
+ import type {
3
+ OAuth2Tokens,
4
+ ProviderOptions,
5
+ UpstreamProvider,
6
+ } from "../oauth2";
7
+ import {
8
+ RESERVED_AUTHORIZATION_PARAMS_SET,
9
+ resolveRequestedScopes,
10
+ } from "../oauth2";
4
11
 
5
12
  /**
6
13
  * WeChat user profile information
@@ -55,19 +62,24 @@ export interface WeChatOptions extends ProviderOptions<WeChatProfile> {
55
62
  lang?: "cn" | "en";
56
63
  }
57
64
 
65
+ const WECHAT_DEFAULT_SCOPES = ["snsapi_login"];
66
+
58
67
  export const wechat = (options: WeChatOptions) => {
59
68
  return {
60
69
  id: "wechat",
61
70
  name: "WeChat",
71
+ callbackPath: "/callback/wechat",
62
72
  createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
63
- const _scopes = options.disableDefaultScope ? [] : ["snsapi_login"];
64
- options.scope && _scopes.push(...options.scope);
65
- scopes && _scopes.push(...scopes);
73
+ const requestedScopes = resolveRequestedScopes(
74
+ options,
75
+ WECHAT_DEFAULT_SCOPES,
76
+ scopes,
77
+ );
66
78
 
67
79
  // WeChat uses non-standard OAuth2 parameters (appid instead of client_id)
68
80
  // and requires a fragment (#wechat_redirect), so we construct the URL manually.
69
81
  const url = new URL("https://open.weixin.qq.com/connect/qrconnect");
70
- url.searchParams.set("scope", _scopes.join(","));
82
+ url.searchParams.set("scope", requestedScopes.join(","));
71
83
  url.searchParams.set("response_type", "code");
72
84
  url.searchParams.set("appid", options.clientId);
73
85
  url.searchParams.set("redirect_uri", options.redirectURI || redirectURI);
@@ -82,7 +94,7 @@ export const wechat = (options: WeChatOptions) => {
82
94
  }
83
95
  url.hash = "wechat_redirect";
84
96
 
85
- return url;
97
+ return { url, requestedScopes };
86
98
  },
87
99
 
88
100
  // WeChat uses non-standard token exchange (appid/secret instead of
@@ -217,5 +229,5 @@ export const wechat = (options: WeChatOptions) => {
217
229
  };
218
230
  },
219
231
  options,
220
- } satisfies OAuthProvider<WeChatProfile, WeChatOptions>;
232
+ } satisfies UpstreamProvider<WeChatProfile, WeChatOptions>;
221
233
  };