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

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 (170) hide show
  1. package/dist/api/index.d.mts +47 -4
  2. package/dist/api/index.mjs +40 -1
  3. package/dist/context/global.mjs +1 -1
  4. package/dist/context/transaction.d.mts +7 -4
  5. package/dist/context/transaction.mjs +6 -3
  6. package/dist/db/adapter/factory.mjs +57 -31
  7. package/dist/db/adapter/index.d.mts +54 -10
  8. package/dist/db/adapter/types.d.mts +1 -1
  9. package/dist/db/get-tables.mjs +3 -3
  10. package/dist/db/schema/account.d.mts +1 -1
  11. package/dist/db/schema/account.mjs +1 -1
  12. package/dist/db/type.d.mts +12 -7
  13. package/dist/env/env-impl.mjs +1 -1
  14. package/dist/error/codes.d.mts +5 -0
  15. package/dist/error/codes.mjs +5 -0
  16. package/dist/index.d.mts +2 -2
  17. package/dist/instrumentation/tracer.mjs +1 -1
  18. package/dist/oauth2/create-authorization-url.d.mts +4 -1
  19. package/dist/oauth2/create-authorization-url.mjs +5 -2
  20. package/dist/oauth2/dpop.d.mts +142 -0
  21. package/dist/oauth2/dpop.mjs +246 -0
  22. package/dist/oauth2/index.d.mts +6 -3
  23. package/dist/oauth2/index.mjs +5 -2
  24. package/dist/oauth2/oauth-provider.d.mts +128 -9
  25. package/dist/oauth2/refresh-access-token.mjs +1 -1
  26. package/dist/oauth2/scopes.d.mts +76 -0
  27. package/dist/oauth2/scopes.mjs +96 -0
  28. package/dist/oauth2/utils.mjs +2 -1
  29. package/dist/oauth2/verify-id-token.d.mts +26 -0
  30. package/dist/oauth2/verify-id-token.mjs +62 -0
  31. package/dist/oauth2/verify.d.mts +88 -15
  32. package/dist/oauth2/verify.mjs +187 -19
  33. package/dist/social-providers/apple.d.mts +14 -2
  34. package/dist/social-providers/apple.mjs +12 -36
  35. package/dist/social-providers/atlassian.d.mts +5 -1
  36. package/dist/social-providers/atlassian.mjs +4 -4
  37. package/dist/social-providers/cognito.d.mts +13 -2
  38. package/dist/social-providers/cognito.mjs +24 -32
  39. package/dist/social-providers/discord.d.mts +5 -1
  40. package/dist/social-providers/discord.mjs +7 -6
  41. package/dist/social-providers/dropbox.d.mts +5 -1
  42. package/dist/social-providers/dropbox.mjs +5 -5
  43. package/dist/social-providers/facebook.d.mts +21 -2
  44. package/dist/social-providers/facebook.mjs +46 -22
  45. package/dist/social-providers/figma.d.mts +5 -1
  46. package/dist/social-providers/figma.mjs +5 -5
  47. package/dist/social-providers/github.d.mts +5 -1
  48. package/dist/social-providers/github.mjs +4 -4
  49. package/dist/social-providers/gitlab.d.mts +5 -1
  50. package/dist/social-providers/gitlab.mjs +6 -6
  51. package/dist/social-providers/google.d.mts +29 -3
  52. package/dist/social-providers/google.mjs +24 -30
  53. package/dist/social-providers/huggingface.d.mts +5 -1
  54. package/dist/social-providers/huggingface.mjs +8 -8
  55. package/dist/social-providers/index.d.mts +222 -42
  56. package/dist/social-providers/kakao.d.mts +5 -1
  57. package/dist/social-providers/kakao.mjs +8 -8
  58. package/dist/social-providers/kick.d.mts +5 -1
  59. package/dist/social-providers/kick.mjs +4 -4
  60. package/dist/social-providers/line.d.mts +8 -2
  61. package/dist/social-providers/line.mjs +12 -14
  62. package/dist/social-providers/linear.d.mts +5 -1
  63. package/dist/social-providers/linear.mjs +4 -4
  64. package/dist/social-providers/linkedin.d.mts +5 -1
  65. package/dist/social-providers/linkedin.mjs +10 -10
  66. package/dist/social-providers/microsoft-entra-id.d.mts +41 -6
  67. package/dist/social-providers/microsoft-entra-id.mjs +40 -36
  68. package/dist/social-providers/naver.d.mts +5 -1
  69. package/dist/social-providers/naver.mjs +4 -4
  70. package/dist/social-providers/notion.d.mts +5 -1
  71. package/dist/social-providers/notion.mjs +4 -4
  72. package/dist/social-providers/paybin.d.mts +5 -1
  73. package/dist/social-providers/paybin.mjs +10 -10
  74. package/dist/social-providers/paypal.d.mts +5 -2
  75. package/dist/social-providers/paypal.mjs +8 -13
  76. package/dist/social-providers/polar.d.mts +5 -1
  77. package/dist/social-providers/polar.mjs +8 -8
  78. package/dist/social-providers/railway.d.mts +5 -1
  79. package/dist/social-providers/railway.mjs +9 -9
  80. package/dist/social-providers/reddit.d.mts +5 -1
  81. package/dist/social-providers/reddit.mjs +9 -8
  82. package/dist/social-providers/roblox.d.mts +5 -1
  83. package/dist/social-providers/roblox.mjs +5 -5
  84. package/dist/social-providers/salesforce.d.mts +5 -1
  85. package/dist/social-providers/salesforce.mjs +8 -8
  86. package/dist/social-providers/slack.d.mts +5 -1
  87. package/dist/social-providers/slack.mjs +9 -9
  88. package/dist/social-providers/spotify.d.mts +5 -1
  89. package/dist/social-providers/spotify.mjs +5 -5
  90. package/dist/social-providers/tiktok.d.mts +5 -1
  91. package/dist/social-providers/tiktok.mjs +9 -5
  92. package/dist/social-providers/twitch.d.mts +5 -1
  93. package/dist/social-providers/twitch.mjs +4 -4
  94. package/dist/social-providers/twitter.d.mts +6 -4
  95. package/dist/social-providers/twitter.mjs +9 -9
  96. package/dist/social-providers/vercel.d.mts +5 -1
  97. package/dist/social-providers/vercel.mjs +4 -7
  98. package/dist/social-providers/vk.d.mts +5 -1
  99. package/dist/social-providers/vk.mjs +5 -5
  100. package/dist/social-providers/wechat.d.mts +5 -1
  101. package/dist/social-providers/wechat.mjs +10 -6
  102. package/dist/social-providers/zoom.d.mts +6 -1
  103. package/dist/social-providers/zoom.mjs +15 -9
  104. package/dist/types/context.d.mts +27 -8
  105. package/dist/types/index.d.mts +1 -1
  106. package/dist/types/init-options.d.mts +137 -6
  107. package/dist/types/plugin-client.d.mts +12 -2
  108. package/dist/utils/host.mjs +4 -0
  109. package/dist/utils/url.mjs +4 -3
  110. package/package.json +7 -7
  111. package/src/api/index.ts +82 -0
  112. package/src/context/transaction.ts +45 -12
  113. package/src/db/adapter/factory.ts +127 -64
  114. package/src/db/adapter/index.ts +54 -9
  115. package/src/db/adapter/types.ts +1 -0
  116. package/src/db/get-tables.ts +8 -3
  117. package/src/db/schema/account.ts +14 -2
  118. package/src/db/type.ts +12 -7
  119. package/src/env/env-impl.ts +1 -2
  120. package/src/error/codes.ts +5 -0
  121. package/src/oauth2/create-authorization-url.ts +2 -2
  122. package/src/oauth2/dpop.ts +568 -0
  123. package/src/oauth2/index.ts +61 -2
  124. package/src/oauth2/oauth-provider.ts +140 -10
  125. package/src/oauth2/refresh-access-token.ts +2 -2
  126. package/src/oauth2/scopes.ts +118 -0
  127. package/src/oauth2/utils.ts +2 -5
  128. package/src/oauth2/verify-id-token.ts +111 -0
  129. package/src/oauth2/verify.ts +372 -58
  130. package/src/social-providers/apple.ts +24 -61
  131. package/src/social-providers/atlassian.ts +12 -8
  132. package/src/social-providers/cognito.ts +25 -47
  133. package/src/social-providers/discord.ts +19 -8
  134. package/src/social-providers/dropbox.ts +13 -7
  135. package/src/social-providers/facebook.ts +97 -51
  136. package/src/social-providers/figma.ts +13 -9
  137. package/src/social-providers/github.ts +12 -8
  138. package/src/social-providers/gitlab.ts +14 -8
  139. package/src/social-providers/google.ts +66 -47
  140. package/src/social-providers/huggingface.ts +12 -8
  141. package/src/social-providers/kakao.ts +16 -8
  142. package/src/social-providers/kick.ts +12 -7
  143. package/src/social-providers/line.ts +37 -37
  144. package/src/social-providers/linear.ts +12 -6
  145. package/src/social-providers/linkedin.ts +14 -10
  146. package/src/social-providers/microsoft-entra-id.ts +103 -59
  147. package/src/social-providers/naver.ts +12 -6
  148. package/src/social-providers/notion.ts +12 -6
  149. package/src/social-providers/paybin.ts +14 -11
  150. package/src/social-providers/paypal.ts +6 -25
  151. package/src/social-providers/polar.ts +12 -8
  152. package/src/social-providers/railway.ts +13 -9
  153. package/src/social-providers/reddit.ts +25 -10
  154. package/src/social-providers/roblox.ts +18 -7
  155. package/src/social-providers/salesforce.ts +12 -8
  156. package/src/social-providers/slack.ts +18 -9
  157. package/src/social-providers/spotify.ts +13 -7
  158. package/src/social-providers/tiktok.ts +13 -7
  159. package/src/social-providers/twitch.ts +12 -8
  160. package/src/social-providers/twitter.ts +17 -8
  161. package/src/social-providers/vercel.ts +16 -10
  162. package/src/social-providers/vk.ts +13 -7
  163. package/src/social-providers/wechat.ts +28 -9
  164. package/src/social-providers/zoom.ts +19 -6
  165. package/src/types/context.ts +26 -8
  166. package/src/types/index.ts +7 -0
  167. package/src/types/init-options.ts +159 -8
  168. package/src/types/plugin-client.ts +16 -2
  169. package/src/utils/host.ts +15 -0
  170. package/src/utils/url.ts +10 -4
@@ -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
@@ -208,7 +220,14 @@ export const wechat = (options: WeChatOptions) => {
208
220
  user: {
209
221
  id: profile.unionid || profile.openid || openid,
210
222
  name: profile.nickname,
211
- email: profile.email || null,
223
+ // WeChat does not return an email, and the OAuth callback rejects a
224
+ // missing one, so the default sign-in would always fail. Synthesize a
225
+ // stable, non-routable placeholder (RFC 2606 `.invalid`) keyed to the
226
+ // user's WeChat id, left unverified. Applications that collect a real
227
+ // email override it via `mapProfileToUser`.
228
+ email:
229
+ profile.email ||
230
+ `${profile.unionid || profile.openid || openid}@wechat.invalid`,
212
231
  image: profile.headimgurl,
213
232
  emailVerified: false,
214
233
  ...userMap,
@@ -217,5 +236,5 @@ export const wechat = (options: WeChatOptions) => {
217
236
  };
218
237
  },
219
238
  options,
220
- } satisfies OAuthProvider<WeChatProfile, WeChatOptions>;
239
+ } satisfies UpstreamProvider<WeChatProfile, WeChatOptions>;
221
240
  };
@@ -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
 
@@ -143,6 +144,8 @@ export interface ZoomOptions extends ProviderOptions<ZoomProfile> {
143
144
  pkce?: boolean | undefined;
144
145
  }
145
146
 
147
+ const ZOOM_DEFAULT_SCOPES: string[] = [];
148
+
146
149
  export const zoom = (userOptions: ZoomOptions) => {
147
150
  const options = {
148
151
  pkce: true,
@@ -152,21 +155,31 @@ export const zoom = (userOptions: ZoomOptions) => {
152
155
  return {
153
156
  id: "zoom",
154
157
  name: "Zoom",
155
- createAuthorizationURL: async ({
158
+ callbackPath: "/callback/zoom",
159
+ createAuthorizationURL: ({
156
160
  state,
161
+ scopes,
157
162
  redirectURI,
158
163
  codeVerifier,
159
164
  additionalParams,
160
- }) =>
161
- createAuthorizationURL({
165
+ }) => {
166
+ const requestedScopes = resolveRequestedScopes(
167
+ options,
168
+ ZOOM_DEFAULT_SCOPES,
169
+ scopes,
170
+ );
171
+
172
+ return createAuthorizationURL({
162
173
  id: "zoom",
163
174
  options,
164
175
  authorizationEndpoint: "https://zoom.us/oauth/authorize",
176
+ scopes: requestedScopes,
165
177
  state,
166
178
  redirectURI,
167
179
  codeVerifier: options.pkce ? codeVerifier : undefined,
168
180
  additionalParams,
169
- }),
181
+ });
182
+ },
170
183
  validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {
171
184
  return validateAuthorizationCode({
172
185
  code,
@@ -222,5 +235,5 @@ export const zoom = (userOptions: ZoomOptions) => {
222
235
  },
223
236
  };
224
237
  },
225
- } satisfies OAuthProvider<ZoomProfile>;
238
+ } satisfies UpstreamProvider<ZoomProfile>;
226
239
  };