@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,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
 
@@ -26,11 +27,14 @@ export interface LinearOptions extends ProviderOptions<LinearUser> {
26
27
  clientId: string;
27
28
  }
28
29
 
30
+ const LINEAR_DEFAULT_SCOPES = ["read"];
31
+
29
32
  export const linear = (options: LinearOptions) => {
30
33
  const tokenEndpoint = "https://api.linear.app/oauth/token";
31
34
  return {
32
35
  id: "linear",
33
36
  name: "Linear",
37
+ callbackPath: "/callback/linear",
34
38
  createAuthorizationURL({
35
39
  state,
36
40
  scopes,
@@ -38,14 +42,16 @@ export const linear = (options: LinearOptions) => {
38
42
  redirectURI,
39
43
  additionalParams,
40
44
  }) {
41
- const _scopes = options.disableDefaultScope ? [] : ["read"];
42
- if (options.scope) _scopes.push(...options.scope);
43
- if (scopes) _scopes.push(...scopes);
45
+ const requestedScopes = resolveRequestedScopes(
46
+ options,
47
+ LINEAR_DEFAULT_SCOPES,
48
+ scopes,
49
+ );
44
50
  return createAuthorizationURL({
45
51
  id: "linear",
46
52
  options,
47
53
  authorizationEndpoint: "https://linear.app/oauth/authorize",
48
- scopes: _scopes,
54
+ scopes: requestedScopes,
49
55
  state,
50
56
  redirectURI,
51
57
  loginHint,
@@ -124,5 +130,5 @@ export const linear = (options: LinearOptions) => {
124
130
  };
125
131
  },
126
132
  options,
127
- } satisfies OAuthProvider<LinearUser>;
133
+ } satisfies UpstreamProvider<LinearUser>;
128
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
 
@@ -24,6 +25,8 @@ export interface LinkedInOptions extends ProviderOptions<LinkedInProfile> {
24
25
  clientId: string;
25
26
  }
26
27
 
28
+ const LINKEDIN_DEFAULT_SCOPES = ["profile", "email", "openid"];
29
+
27
30
  export const linkedin = (options: LinkedInOptions) => {
28
31
  const authorizationEndpoint =
29
32
  "https://www.linkedin.com/oauth/v2/authorization";
@@ -32,23 +35,24 @@ export const linkedin = (options: LinkedInOptions) => {
32
35
  return {
33
36
  id: "linkedin",
34
37
  name: "Linkedin",
35
- createAuthorizationURL: async ({
38
+ callbackPath: "/callback/linkedin",
39
+ createAuthorizationURL: ({
36
40
  state,
37
41
  scopes,
38
42
  redirectURI,
39
43
  loginHint,
40
44
  additionalParams,
41
45
  }) => {
42
- const _scopes = options.disableDefaultScope
43
- ? []
44
- : ["profile", "email", "openid"];
45
- if (options.scope) _scopes.push(...options.scope);
46
- if (scopes) _scopes.push(...scopes);
47
- return await createAuthorizationURL({
46
+ const requestedScopes = resolveRequestedScopes(
47
+ options,
48
+ LINKEDIN_DEFAULT_SCOPES,
49
+ scopes,
50
+ );
51
+ return createAuthorizationURL({
48
52
  id: "linkedin",
49
53
  options,
50
54
  authorizationEndpoint,
51
- scopes: _scopes,
55
+ scopes: requestedScopes,
52
56
  state,
53
57
  loginHint,
54
58
  redirectURI,
@@ -108,5 +112,5 @@ export const linkedin = (options: LinkedInOptions) => {
108
112
  };
109
113
  },
110
114
  options,
111
- } satisfies OAuthProvider<LinkedInProfile>;
115
+ } satisfies UpstreamProvider<LinkedInProfile>;
112
116
  };
@@ -1,13 +1,19 @@
1
1
  import { base64 } from "@better-auth/utils/base64";
2
2
  import { betterFetch } from "@better-fetch/fetch";
3
- import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
3
+ import { decodeJwt, importJWK } from "jose";
4
4
  import { logger } from "../env";
5
5
  import { APIError, BetterAuthError } from "../error";
6
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
6
+ import type {
7
+ ClientAssertionGetter,
8
+ ProviderOptions,
9
+ TokenEndpointAuth,
10
+ UpstreamProvider,
11
+ } from "../oauth2";
7
12
  import {
8
13
  createAuthorizationURL,
9
14
  getPrimaryClientId,
10
15
  refreshAccessToken,
16
+ resolveRequestedScopes,
11
17
  validateAuthorizationCode,
12
18
  } from "../oauth2";
13
19
 
@@ -122,33 +128,60 @@ export interface MicrosoftOptions
122
128
  * The tenant ID of the Microsoft account
123
129
  * @default "common"
124
130
  */
125
- tenantId?: string | undefined;
131
+ tenantId?: string;
126
132
  /**
127
133
  * The authentication authority URL. Use the default "https://login.microsoftonline.com" for standard Entra ID or "https://<tenant-id>.ciamlogin.com" for CIAM scenarios.
128
134
  * @default "https://login.microsoftonline.com"
129
135
  */
130
- authority?: string | undefined;
136
+ authority?: string;
137
+ /**
138
+ * Function that returns a JWT client assertion for token endpoint authentication.
139
+ *
140
+ * Use this instead of `clientSecret` when your Microsoft Entra ID app is
141
+ * configured for client authentication with assertions (private_key_jwt or
142
+ * workload identity federation).
143
+ */
144
+ clientAssertion?: ClientAssertionGetter;
131
145
  /**
132
146
  * The size of the profile photo
133
147
  * @default 48
134
148
  */
135
- profilePhotoSize?:
136
- | (48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648)
137
- | undefined;
149
+ profilePhotoSize?: 48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648;
138
150
  /**
139
151
  * Disable profile photo
140
152
  */
141
- disableProfilePhoto?: boolean | undefined;
153
+ disableProfilePhoto?: boolean;
142
154
  }
143
155
 
156
+ const MICROSOFT_ENTRA_ID_DEFAULT_SCOPES = [
157
+ "openid",
158
+ "profile",
159
+ "email",
160
+ "User.Read",
161
+ "offline_access",
162
+ ];
163
+
144
164
  export const microsoft = (options: MicrosoftOptions) => {
145
165
  const tenant = options.tenantId || "common";
146
166
  const authority = options.authority || "https://login.microsoftonline.com";
147
167
  const authorizationEndpoint = `${authority}/${tenant}/oauth2/v2.0/authorize`;
148
168
  const tokenEndpoint = `${authority}/${tenant}/oauth2/v2.0/token`;
169
+ if (options.clientSecret && options.clientAssertion) {
170
+ throw new BetterAuthError(
171
+ "Microsoft Entra ID clientAssertion cannot be combined with clientSecret",
172
+ );
173
+ }
174
+ const tokenEndpointAuth: TokenEndpointAuth | undefined =
175
+ options.clientAssertion
176
+ ? {
177
+ method: "private_key_jwt",
178
+ getClientAssertion: options.clientAssertion,
179
+ }
180
+ : undefined;
149
181
  return {
150
182
  id: "microsoft",
151
183
  name: "Microsoft EntraID",
184
+ callbackPath: "/callback/microsoft",
152
185
  createAuthorizationURL(data) {
153
186
  // Microsoft Entra supports public clients (SPA / native apps with
154
187
  // PKCE only), so clientSecret is intentionally not required here.
@@ -159,18 +192,18 @@ export const microsoft = (options: MicrosoftOptions) => {
159
192
  );
160
193
  throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
161
194
  }
162
- const scopes = options.disableDefaultScope
163
- ? []
164
- : ["openid", "profile", "email", "User.Read", "offline_access"];
165
- if (options.scope) scopes.push(...options.scope);
166
- if (data.scopes) scopes.push(...data.scopes);
195
+ const requestedScopes = resolveRequestedScopes(
196
+ options,
197
+ MICROSOFT_ENTRA_ID_DEFAULT_SCOPES,
198
+ data.scopes,
199
+ );
167
200
  return createAuthorizationURL({
168
201
  id: "microsoft",
169
202
  options,
170
203
  authorizationEndpoint,
171
204
  state: data.state,
172
205
  codeVerifier: data.codeVerifier,
173
- scopes,
206
+ scopes: requestedScopes,
174
207
  redirectURI: data.redirectURI,
175
208
  prompt: options.prompt,
176
209
  loginHint: data.loginHint,
@@ -184,57 +217,24 @@ export const microsoft = (options: MicrosoftOptions) => {
184
217
  redirectURI,
185
218
  options,
186
219
  tokenEndpoint,
220
+ tokenEndpointAuth,
187
221
  });
188
222
  },
189
- async verifyIdToken(token, nonce) {
190
- if (options.disableIdTokenSignIn) {
191
- return false;
192
- }
193
- if (options.verifyIdToken) {
194
- return options.verifyIdToken(token, nonce);
195
- }
196
-
197
- try {
198
- const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
199
- if (!kid || !jwtAlg) return false;
200
-
201
- const publicKey = await getMicrosoftPublicKey(kid, tenant, authority);
202
- const verifyOptions: {
203
- algorithms: [string];
204
- audience: string | string[];
205
- maxTokenAge: string;
206
- issuer?: string;
207
- } = {
208
- algorithms: [jwtAlg],
209
- audience: options.clientId,
210
- maxTokenAge: "1h",
211
- };
212
- /**
213
- * Issuer varies per user's tenant for multi-tenant endpoints, so only validate for specific tenants.
214
- * @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints
215
- */
216
- if (
217
- tenant !== "common" &&
218
- tenant !== "organizations" &&
219
- tenant !== "consumers"
220
- ) {
221
- verifyOptions.issuer = `${authority}/${tenant}/v2.0`;
222
- }
223
- const { payload: jwtClaims } = await jwtVerify(
224
- token,
225
- publicKey,
226
- verifyOptions,
227
- );
228
-
229
- if (nonce && jwtClaims.nonce !== nonce) {
230
- return false;
231
- }
232
-
233
- return true;
234
- } catch (error) {
235
- logger.error("Failed to verify ID token:", error);
236
- return false;
237
- }
223
+ idToken: {
224
+ jwks: (header) => getMicrosoftPublicKey(header.kid!, tenant, authority),
225
+ audience: options.clientId,
226
+ maxTokenAge: "1h",
227
+ /**
228
+ * Issuer varies per tenant for multi-tenant endpoints, so only validate it for
229
+ * specific tenants.
230
+ * @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints
231
+ */
232
+ issuer:
233
+ tenant !== "common" &&
234
+ tenant !== "organizations" &&
235
+ tenant !== "consumers"
236
+ ? `${authority}/${tenant}/v2.0`
237
+ : undefined,
238
238
  },
239
239
  async getUserInfo(token) {
240
240
  if (options.getUserInfo) {
@@ -314,10 +314,11 @@ export const microsoft = (options: MicrosoftOptions) => {
314
314
  scope: scopes.join(" "), // Include the scopes in request to microsoft
315
315
  },
316
316
  tokenEndpoint,
317
+ tokenEndpointAuth,
317
318
  });
318
319
  },
319
320
  options,
320
- } satisfies OAuthProvider;
321
+ } satisfies UpstreamProvider;
321
322
  };
322
323
 
323
324
  export const getMicrosoftPublicKey = async (
@@ -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
 
@@ -39,20 +40,25 @@ export interface NaverOptions extends ProviderOptions<NaverProfile> {
39
40
  clientId: string;
40
41
  }
41
42
 
43
+ const NAVER_DEFAULT_SCOPES = ["profile", "email"];
44
+
42
45
  export const naver = (options: NaverOptions) => {
43
46
  const tokenEndpoint = "https://nid.naver.com/oauth2.0/token";
44
47
  return {
45
48
  id: "naver",
46
49
  name: "Naver",
50
+ callbackPath: "/callback/naver",
47
51
  createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
48
- const _scopes = options.disableDefaultScope ? [] : ["profile", "email"];
49
- if (options.scope) _scopes.push(...options.scope);
50
- if (scopes) _scopes.push(...scopes);
52
+ const requestedScopes = resolveRequestedScopes(
53
+ options,
54
+ NAVER_DEFAULT_SCOPES,
55
+ scopes,
56
+ );
51
57
  return createAuthorizationURL({
52
58
  id: "naver",
53
59
  options,
54
60
  authorizationEndpoint: "https://nid.naver.com/oauth2.0/authorize",
55
- scopes: _scopes,
61
+ scopes: requestedScopes,
56
62
  state,
57
63
  redirectURI,
58
64
  additionalParams,
@@ -110,5 +116,5 @@ export const naver = (options: NaverOptions) => {
110
116
  };
111
117
  },
112
118
  options,
113
- } satisfies OAuthProvider<NaverProfile>;
119
+ } satisfies UpstreamProvider<NaverProfile>;
114
120
  };
@@ -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
 
@@ -23,11 +24,14 @@ export interface NotionOptions extends ProviderOptions<NotionProfile> {
23
24
  clientId: string;
24
25
  }
25
26
 
27
+ const NOTION_DEFAULT_SCOPES: string[] = [];
28
+
26
29
  export const notion = (options: NotionOptions) => {
27
30
  const tokenEndpoint = "https://api.notion.com/v1/oauth/token";
28
31
  return {
29
32
  id: "notion",
30
33
  name: "Notion",
34
+ callbackPath: "/callback/notion",
31
35
  createAuthorizationURL({
32
36
  state,
33
37
  scopes,
@@ -35,14 +39,16 @@ export const notion = (options: NotionOptions) => {
35
39
  redirectURI,
36
40
  additionalParams,
37
41
  }) {
38
- const _scopes: string[] = options.disableDefaultScope ? [] : [];
39
- if (options.scope) _scopes.push(...options.scope);
40
- if (scopes) _scopes.push(...scopes);
42
+ const requestedScopes = resolveRequestedScopes(
43
+ options,
44
+ NOTION_DEFAULT_SCOPES,
45
+ scopes,
46
+ );
41
47
  return createAuthorizationURL({
42
48
  id: "notion",
43
49
  options,
44
50
  authorizationEndpoint: "https://api.notion.com/v1/oauth/authorize",
45
- scopes: _scopes,
51
+ scopes: requestedScopes,
46
52
  state,
47
53
  redirectURI,
48
54
  loginHint,
@@ -111,5 +117,5 @@ export const notion = (options: NotionOptions) => {
111
117
  };
112
118
  },
113
119
  options,
114
- } satisfies OAuthProvider<NotionProfile>;
120
+ } satisfies UpstreamProvider<NotionProfile>;
115
121
  };
@@ -1,10 +1,11 @@
1
1
  import { decodeJwt } from "jose";
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
 
@@ -28,6 +29,8 @@ export interface PaybinOptions extends ProviderOptions<PaybinProfile> {
28
29
  issuer?: string | undefined;
29
30
  }
30
31
 
32
+ const PAYBIN_DEFAULT_SCOPES = ["openid", "email", "profile"];
33
+
31
34
  export const paybin = (options: PaybinOptions) => {
32
35
  const issuer = options.issuer || "https://idp.paybin.io";
33
36
  const authorizationEndpoint = `${issuer}/oauth2/authorize`;
@@ -36,7 +39,8 @@ export const paybin = (options: PaybinOptions) => {
36
39
  return {
37
40
  id: "paybin",
38
41
  name: "Paybin",
39
- async createAuthorizationURL({
42
+ callbackPath: "/callback/paybin",
43
+ createAuthorizationURL({
40
44
  state,
41
45
  scopes,
42
46
  codeVerifier,
@@ -53,16 +57,16 @@ export const paybin = (options: PaybinOptions) => {
53
57
  if (!codeVerifier) {
54
58
  throw new BetterAuthError("codeVerifier is required for Paybin");
55
59
  }
56
- const _scopes = options.disableDefaultScope
57
- ? []
58
- : ["openid", "email", "profile"];
59
- if (options.scope) _scopes.push(...options.scope);
60
- if (scopes) _scopes.push(...scopes);
61
- const url = await createAuthorizationURL({
60
+ const requestedScopes = resolveRequestedScopes(
61
+ options,
62
+ PAYBIN_DEFAULT_SCOPES,
63
+ scopes,
64
+ );
65
+ return createAuthorizationURL({
62
66
  id: "paybin",
63
67
  options,
64
68
  authorizationEndpoint,
65
- scopes: _scopes,
69
+ scopes: requestedScopes,
66
70
  state,
67
71
  codeVerifier,
68
72
  redirectURI,
@@ -70,7 +74,6 @@ export const paybin = (options: PaybinOptions) => {
70
74
  loginHint,
71
75
  additionalParams,
72
76
  });
73
- return url;
74
77
  },
75
78
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
76
79
  return validateAuthorizationCode({
@@ -116,5 +119,5 @@ export const paybin = (options: PaybinOptions) => {
116
119
  };
117
120
  },
118
121
  options,
119
- } satisfies OAuthProvider<PaybinProfile>;
122
+ } satisfies UpstreamProvider<PaybinProfile>;
120
123
  };
@@ -1,9 +1,8 @@
1
1
  import { base64 } from "@better-auth/utils/base64";
2
2
  import { betterFetch } from "@better-fetch/fetch";
3
- import { decodeJwt } from "jose";
4
3
  import { logger } from "../env";
5
4
  import { BetterAuthError } from "../error";
6
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
5
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
7
6
  import { createAuthorizationURL } from "../oauth2";
8
7
 
9
8
  export interface PayPalProfile {
@@ -78,7 +77,8 @@ export const paypal = (options: PayPalOptions) => {
78
77
  return {
79
78
  id: "paypal",
80
79
  name: "PayPal",
81
- async createAuthorizationURL({
80
+ callbackPath: "/callback/paypal",
81
+ createAuthorizationURL({
82
82
  state,
83
83
  codeVerifier,
84
84
  redirectURI,
@@ -97,20 +97,17 @@ export const paypal = (options: PayPalOptions) => {
97
97
  * We don't pass any scopes to avoid "invalid scope" errors
98
98
  **/
99
99
 
100
- const _scopes: string[] = [];
101
-
102
- const url = await createAuthorizationURL({
100
+ return createAuthorizationURL({
103
101
  id: "paypal",
104
102
  options,
105
103
  authorizationEndpoint,
106
- scopes: _scopes,
104
+ scopes: [],
107
105
  state,
108
106
  codeVerifier,
109
107
  redirectURI,
110
108
  prompt: options.prompt,
111
109
  additionalParams,
112
110
  });
113
- return url;
114
111
  },
115
112
 
116
113
  validateAuthorizationCode: async ({ code, redirectURI }) => {
@@ -200,22 +197,6 @@ export const paypal = (options: PayPalOptions) => {
200
197
  }
201
198
  },
202
199
 
203
- async verifyIdToken(token, nonce) {
204
- if (options.disableIdTokenSignIn) {
205
- return false;
206
- }
207
- if (options.verifyIdToken) {
208
- return options.verifyIdToken(token, nonce);
209
- }
210
- try {
211
- const payload = decodeJwt(token);
212
- return !!payload.sub;
213
- } catch (error) {
214
- logger.error("Failed to verify PayPal ID token:", error);
215
- return false;
216
- }
217
- },
218
-
219
200
  async getUserInfo(token) {
220
201
  if (options.getUserInfo) {
221
202
  return options.getUserInfo(token);
@@ -265,5 +246,5 @@ export const paypal = (options: PayPalOptions) => {
265
246
  },
266
247
 
267
248
  options,
268
- } satisfies OAuthProvider<PayPalProfile>;
249
+ } satisfies UpstreamProvider<PayPalProfile>;
269
250
  };
@@ -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
 
@@ -32,11 +33,14 @@ export interface PolarProfile {
32
33
 
33
34
  export interface PolarOptions extends ProviderOptions<PolarProfile> {}
34
35
 
36
+ const POLAR_DEFAULT_SCOPES = ["openid", "profile", "email"];
37
+
35
38
  export const polar = (options: PolarOptions) => {
36
39
  const tokenEndpoint = "https://api.polar.sh/v1/oauth2/token";
37
40
  return {
38
41
  id: "polar",
39
42
  name: "Polar",
43
+ callbackPath: "/callback/polar",
40
44
  createAuthorizationURL({
41
45
  state,
42
46
  scopes,
@@ -44,16 +48,16 @@ export const polar = (options: PolarOptions) => {
44
48
  redirectURI,
45
49
  additionalParams,
46
50
  }) {
47
- const _scopes = options.disableDefaultScope
48
- ? []
49
- : ["openid", "profile", "email"];
50
- if (options.scope) _scopes.push(...options.scope);
51
- if (scopes) _scopes.push(...scopes);
51
+ const requestedScopes = resolveRequestedScopes(
52
+ options,
53
+ POLAR_DEFAULT_SCOPES,
54
+ scopes,
55
+ );
52
56
  return createAuthorizationURL({
53
57
  id: "polar",
54
58
  options,
55
59
  authorizationEndpoint: "https://polar.sh/oauth2/authorize",
56
- scopes: _scopes,
60
+ scopes: requestedScopes,
57
61
  state,
58
62
  codeVerifier,
59
63
  redirectURI,
@@ -114,5 +118,5 @@ export const polar = (options: PolarOptions) => {
114
118
  };
115
119
  },
116
120
  options,
117
- } satisfies OAuthProvider<PolarProfile>;
121
+ } satisfies UpstreamProvider<PolarProfile>;
118
122
  };
@@ -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,27 +26,30 @@ export interface RailwayOptions extends ProviderOptions<RailwayProfile> {
25
26
  clientId: string;
26
27
  }
27
28
 
29
+ const RAILWAY_DEFAULT_SCOPES = ["openid", "email", "profile"];
30
+
28
31
  export const railway = (options: RailwayOptions) => {
29
32
  return {
30
33
  id: "railway",
31
34
  name: "Railway",
32
- createAuthorizationURL({
35
+ callbackPath: "/callback/railway",
36
+ async createAuthorizationURL({
33
37
  state,
34
38
  scopes,
35
39
  codeVerifier,
36
40
  redirectURI,
37
41
  additionalParams,
38
42
  }) {
39
- const _scopes = options.disableDefaultScope
40
- ? []
41
- : ["openid", "email", "profile"];
42
- if (options.scope) _scopes.push(...options.scope);
43
- if (scopes) _scopes.push(...scopes);
43
+ const requestedScopes = resolveRequestedScopes(
44
+ options,
45
+ RAILWAY_DEFAULT_SCOPES,
46
+ scopes,
47
+ );
44
48
  return createAuthorizationURL({
45
49
  id: "railway",
46
50
  options,
47
51
  authorizationEndpoint,
48
- scopes: _scopes,
52
+ scopes: requestedScopes,
49
53
  state,
50
54
  codeVerifier,
51
55
  redirectURI,
@@ -103,5 +107,5 @@ export const railway = (options: RailwayOptions) => {
103
107
  };
104
108
  },
105
109
  options,
106
- } satisfies OAuthProvider<RailwayProfile>;
110
+ } satisfies UpstreamProvider<RailwayProfile>;
107
111
  };