@better-auth/core 1.4.0-beta.9 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/.turbo/turbo-build.log +41 -76
  2. package/dist/api/index.d.mts +3 -0
  3. package/dist/api/index.mjs +26 -0
  4. package/dist/async_hooks/index.d.mts +2 -10
  5. package/dist/async_hooks/index.mjs +2 -24
  6. package/dist/async_hooks-BfRfbd1J.mjs +18 -0
  7. package/dist/context/index.d.mts +54 -0
  8. package/dist/context/index.mjs +4 -0
  9. package/dist/context-DgQ9XGBl.mjs +114 -0
  10. package/dist/db/adapter/index.d.mts +3 -14
  11. package/dist/db/adapter/index.mjs +1 -1
  12. package/dist/db/index.d.mts +3 -39
  13. package/dist/db/index.mjs +46 -55
  14. package/dist/env/index.d.mts +2 -36
  15. package/dist/env/index.mjs +2 -299
  16. package/dist/env-DwlNAN_D.mjs +245 -0
  17. package/dist/error/index.d.mts +31 -29
  18. package/dist/error/index.mjs +3 -40
  19. package/dist/error-BhAKg8LX.mjs +45 -0
  20. package/dist/index-CdubV7uy.d.mts +82 -0
  21. package/dist/index-CkAWdKH8.d.mts +7352 -0
  22. package/dist/index-DgwIISs7.d.mts +7 -0
  23. package/dist/index.d.mts +3 -180
  24. package/dist/index.mjs +1 -1
  25. package/dist/oauth2/index.d.mts +3 -99
  26. package/dist/oauth2/index.mjs +2 -356
  27. package/dist/oauth2-DmgZmPEg.mjs +236 -0
  28. package/dist/social-providers/index.d.mts +3 -3903
  29. package/dist/social-providers/index.mjs +2434 -2654
  30. package/dist/utils/index.d.mts +5 -6
  31. package/dist/utils/index.mjs +2 -4
  32. package/dist/utils-C5EN75oV.mjs +7 -0
  33. package/package.json +70 -111
  34. package/src/api/index.ts +53 -0
  35. package/src/async_hooks/index.ts +1 -9
  36. package/src/context/endpoint-context.ts +49 -0
  37. package/src/context/index.ts +21 -0
  38. package/src/context/request-state.test.ts +94 -0
  39. package/src/context/request-state.ts +90 -0
  40. package/src/context/transaction.ts +73 -0
  41. package/src/db/adapter/index.ts +215 -129
  42. package/src/db/index.ts +12 -13
  43. package/src/db/plugin.ts +3 -3
  44. package/src/db/type.ts +54 -42
  45. package/src/env/color-depth.ts +5 -4
  46. package/src/env/env-impl.ts +2 -1
  47. package/src/env/index.ts +9 -9
  48. package/src/env/logger.test.ts +3 -2
  49. package/src/env/logger.ts +11 -9
  50. package/src/error/codes.ts +1 -1
  51. package/src/error/index.ts +1 -1
  52. package/src/oauth2/client-credentials-token.ts +9 -9
  53. package/src/oauth2/create-authorization-url.ts +12 -12
  54. package/src/oauth2/index.ts +10 -11
  55. package/src/oauth2/oauth-provider.ts +96 -74
  56. package/src/oauth2/refresh-access-token.ts +12 -12
  57. package/src/oauth2/utils.ts +2 -0
  58. package/src/oauth2/validate-authorization-code.ts +13 -15
  59. package/src/social-providers/apple.ts +8 -8
  60. package/src/social-providers/atlassian.ts +21 -19
  61. package/src/social-providers/cognito.ts +15 -15
  62. package/src/social-providers/discord.ts +8 -11
  63. package/src/social-providers/dropbox.ts +5 -5
  64. package/src/social-providers/facebook.ts +12 -10
  65. package/src/social-providers/figma.ts +6 -6
  66. package/src/social-providers/github.ts +4 -4
  67. package/src/social-providers/gitlab.ts +13 -10
  68. package/src/social-providers/google.ts +13 -13
  69. package/src/social-providers/huggingface.ts +27 -25
  70. package/src/social-providers/index.ts +30 -24
  71. package/src/social-providers/kakao.ts +41 -41
  72. package/src/social-providers/kick.ts +7 -9
  73. package/src/social-providers/line.ts +12 -12
  74. package/src/social-providers/linear.ts +9 -8
  75. package/src/social-providers/linkedin.ts +5 -5
  76. package/src/social-providers/microsoft-entra-id.ts +31 -15
  77. package/src/social-providers/naver.ts +5 -5
  78. package/src/social-providers/notion.ts +11 -9
  79. package/src/social-providers/paybin.ts +122 -0
  80. package/src/social-providers/paypal.ts +31 -29
  81. package/src/social-providers/polar.ts +110 -0
  82. package/src/social-providers/reddit.ts +6 -6
  83. package/src/social-providers/roblox.ts +15 -14
  84. package/src/social-providers/salesforce.ts +20 -18
  85. package/src/social-providers/slack.ts +4 -7
  86. package/src/social-providers/spotify.ts +5 -5
  87. package/src/social-providers/tiktok.ts +32 -33
  88. package/src/social-providers/twitch.ts +8 -8
  89. package/src/social-providers/twitter.ts +49 -45
  90. package/src/social-providers/vk.ts +14 -17
  91. package/src/social-providers/zoom.ts +29 -14
  92. package/src/types/context.ts +67 -67
  93. package/src/types/cookie.ts +1 -0
  94. package/src/types/index.ts +13 -11
  95. package/src/types/init-options.ts +1134 -911
  96. package/src/types/plugin-client.ts +61 -13
  97. package/src/types/plugin.ts +81 -57
  98. package/tsconfig.json +2 -5
  99. package/{build.config.ts → tsdown.config.ts} +8 -11
  100. package/vitest.config.ts +3 -0
  101. package/dist/async_hooks/index.cjs +0 -27
  102. package/dist/async_hooks/index.d.cts +0 -10
  103. package/dist/async_hooks/index.d.ts +0 -10
  104. package/dist/db/adapter/index.cjs +0 -2
  105. package/dist/db/adapter/index.d.cts +0 -14
  106. package/dist/db/adapter/index.d.ts +0 -14
  107. package/dist/db/index.cjs +0 -91
  108. package/dist/db/index.d.cts +0 -39
  109. package/dist/db/index.d.ts +0 -39
  110. package/dist/env/index.cjs +0 -315
  111. package/dist/env/index.d.cts +0 -36
  112. package/dist/env/index.d.ts +0 -36
  113. package/dist/error/index.cjs +0 -44
  114. package/dist/error/index.d.cts +0 -33
  115. package/dist/error/index.d.ts +0 -33
  116. package/dist/index.cjs +0 -2
  117. package/dist/index.d.cts +0 -180
  118. package/dist/index.d.ts +0 -180
  119. package/dist/middleware/index.cjs +0 -25
  120. package/dist/middleware/index.d.cts +0 -14
  121. package/dist/middleware/index.d.mts +0 -14
  122. package/dist/middleware/index.d.ts +0 -14
  123. package/dist/middleware/index.mjs +0 -21
  124. package/dist/oauth2/index.cjs +0 -368
  125. package/dist/oauth2/index.d.cts +0 -99
  126. package/dist/oauth2/index.d.ts +0 -99
  127. package/dist/shared/core.2rWMW9q9.d.ts +0 -13
  128. package/dist/shared/core.40VTWh-p.d.cts +0 -217
  129. package/dist/shared/core.BfcVdsSf.d.cts +0 -181
  130. package/dist/shared/core.Bisb2Bdk.d.mts +0 -181
  131. package/dist/shared/core.BwoNUcJQ.d.cts +0 -53
  132. package/dist/shared/core.BwoNUcJQ.d.mts +0 -53
  133. package/dist/shared/core.BwoNUcJQ.d.ts +0 -53
  134. package/dist/shared/core.CErFRCOZ.d.mts +0 -1684
  135. package/dist/shared/core.CGN6D-Mh.d.ts +0 -181
  136. package/dist/shared/core.CPuIItYE.d.ts +0 -217
  137. package/dist/shared/core.CftpHMDz.d.cts +0 -13
  138. package/dist/shared/core.Db7zJyxf.d.ts +0 -1684
  139. package/dist/shared/core.DqaxObkf.d.cts +0 -1684
  140. package/dist/shared/core.MjcDoj7R.d.cts +0 -5
  141. package/dist/shared/core.MjcDoj7R.d.mts +0 -5
  142. package/dist/shared/core.MjcDoj7R.d.ts +0 -5
  143. package/dist/shared/core.g2ZbxAEV.d.mts +0 -217
  144. package/dist/shared/core.g9ACQ8v2.d.mts +0 -13
  145. package/dist/social-providers/index.cjs +0 -2793
  146. package/dist/social-providers/index.d.cts +0 -3903
  147. package/dist/social-providers/index.d.ts +0 -3903
  148. package/dist/utils/index.cjs +0 -7
  149. package/dist/utils/index.d.cts +0 -10
  150. package/dist/utils/index.d.ts +0 -10
  151. package/src/middleware/index.ts +0 -33
@@ -1,11 +1,11 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
2
  import { decodeJwt } from "jose";
3
- import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
3
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
4
4
  import {
5
5
  createAuthorizationURL,
6
6
  refreshAccessToken,
7
7
  validateAuthorizationCode,
8
- } from "@better-auth/core/oauth2";
8
+ } from "../oauth2";
9
9
 
10
10
  export interface LineIdTokenPayload {
11
11
  iss: string;
@@ -13,18 +13,18 @@ export interface LineIdTokenPayload {
13
13
  aud: string;
14
14
  exp: number;
15
15
  iat: number;
16
- name?: string;
17
- picture?: string;
18
- email?: string;
19
- amr?: string[];
20
- nonce?: string;
16
+ name?: string | undefined;
17
+ picture?: string | undefined;
18
+ email?: string | undefined;
19
+ amr?: string[] | undefined;
20
+ nonce?: string | undefined;
21
21
  }
22
22
 
23
23
  export interface LineUserInfo {
24
24
  sub: string;
25
- name?: string;
26
- picture?: string;
27
- email?: string;
25
+ name?: string | undefined;
26
+ picture?: string | undefined;
27
+ email?: string | undefined;
28
28
  }
29
29
 
30
30
  export interface LineOptions
@@ -60,8 +60,8 @@ export const line = (options: LineOptions) => {
60
60
  const _scopes = options.disableDefaultScope
61
61
  ? []
62
62
  : ["openid", "profile", "email"];
63
- options.scope && _scopes.push(...options.scope);
64
- scopes && _scopes.push(...scopes);
63
+ if (options.scope) _scopes.push(...options.scope);
64
+ if (scopes) _scopes.push(...scopes);
65
65
  return await createAuthorizationURL({
66
66
  id: "line",
67
67
  options,
@@ -1,16 +1,16 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
2
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
6
  validateAuthorizationCode,
7
- } from "@better-auth/core/oauth2";
7
+ } from "../oauth2";
8
8
 
9
- interface LinearUser {
9
+ export interface LinearUser {
10
10
  id: string;
11
11
  name: string;
12
12
  email: string;
13
- avatarUrl?: string;
13
+ avatarUrl?: string | undefined;
14
14
  active: boolean;
15
15
  createdAt: string;
16
16
  updatedAt: string;
@@ -33,8 +33,8 @@ export const linear = (options: LinearOptions) => {
33
33
  name: "Linear",
34
34
  createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
35
35
  const _scopes = options.disableDefaultScope ? [] : ["read"];
36
- options.scope && _scopes.push(...options.scope);
37
- scopes && _scopes.push(...scopes);
36
+ if (options.scope) _scopes.push(...options.scope);
37
+ if (scopes) _scopes.push(...scopes);
38
38
  return createAuthorizationURL({
39
39
  id: "linear",
40
40
  options,
@@ -102,14 +102,15 @@ export const linear = (options: LinearOptions) => {
102
102
 
103
103
  const userData = profile.data.viewer;
104
104
  const userMap = await options.mapProfileToUser?.(userData);
105
-
105
+ // Linear does not provide email_verified claim.
106
+ // We default to false for security consistency.
106
107
  return {
107
108
  user: {
108
109
  id: profile.data.viewer.id,
109
110
  name: profile.data.viewer.name,
110
111
  email: profile.data.viewer.email,
111
112
  image: profile.data.viewer.avatarUrl,
112
- emailVerified: true,
113
+ emailVerified: false,
113
114
  ...userMap,
114
115
  },
115
116
  data: userData,
@@ -1,10 +1,10 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
2
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
- validateAuthorizationCode,
6
5
  refreshAccessToken,
7
- } from "@better-auth/core/oauth2";
6
+ validateAuthorizationCode,
7
+ } from "../oauth2";
8
8
 
9
9
  export interface LinkedInProfile {
10
10
  sub: string;
@@ -41,8 +41,8 @@ export const linkedin = (options: LinkedInOptions) => {
41
41
  const _scopes = options.disableDefaultScope
42
42
  ? []
43
43
  : ["profile", "email", "openid"];
44
- options.scope && _scopes.push(...options.scope);
45
- scopes && _scopes.push(...scopes);
44
+ if (options.scope) _scopes.push(...options.scope);
45
+ if (scopes) _scopes.push(...scopes);
46
46
  return await createAuthorizationURL({
47
47
  id: "linkedin",
48
48
  options,
@@ -1,13 +1,13 @@
1
+ import { base64 } from "@better-auth/utils/base64";
2
+ import { betterFetch } from "@better-fetch/fetch";
3
+ import { decodeJwt } from "jose";
4
+ import { logger } from "../env";
5
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
1
6
  import {
2
- validateAuthorizationCode,
3
7
  createAuthorizationURL,
4
8
  refreshAccessToken,
5
- } from "@better-auth/core/oauth2";
6
- import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
7
- import { betterFetch } from "@better-fetch/fetch";
8
- import { logger } from "@better-auth/core/env";
9
- import { decodeJwt } from "jose";
10
- import { base64 } from "@better-auth/utils/base64";
9
+ validateAuthorizationCode,
10
+ } from "../oauth2";
11
11
 
12
12
  /**
13
13
  * @see [Microsoft Identity Platform - Optional claims reference](https://learn.microsoft.com/en-us/entra/identity-platform/optional-claims-reference)
@@ -81,6 +81,8 @@ export interface MicrosoftEntraIDProfile extends Record<string, any> {
81
81
  verified_primary_email: string[];
82
82
  /** User's verified secondary email addresses */
83
83
  verified_secondary_email: string[];
84
+ /** Whether the user's email is verified (optional claim, must be configured in app registration) */
85
+ email_verified?: boolean | undefined;
84
86
  /** VNET specifier information */
85
87
  vnet: string;
86
88
  /** Client Capabilities */
@@ -118,21 +120,23 @@ export interface MicrosoftOptions
118
120
  * The tenant ID of the Microsoft account
119
121
  * @default "common"
120
122
  */
121
- tenantId?: string;
123
+ tenantId?: string | undefined;
122
124
  /**
123
125
  * The authentication authority URL. Use the default "https://login.microsoftonline.com" for standard Entra ID or "https://<tenant-id>.ciamlogin.com" for CIAM scenarios.
124
126
  * @default "https://login.microsoftonline.com"
125
127
  */
126
- authority?: string;
128
+ authority?: string | undefined;
127
129
  /**
128
130
  * The size of the profile photo
129
131
  * @default 48
130
132
  */
131
- profilePhotoSize?: 48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648;
133
+ profilePhotoSize?:
134
+ | (48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648)
135
+ | undefined;
132
136
  /**
133
137
  * Disable profile photo
134
138
  */
135
- disableProfilePhoto?: boolean;
139
+ disableProfilePhoto?: boolean | undefined;
136
140
  }
137
141
 
138
142
  export const microsoft = (options: MicrosoftOptions) => {
@@ -147,8 +151,8 @@ export const microsoft = (options: MicrosoftOptions) => {
147
151
  const scopes = options.disableDefaultScope
148
152
  ? []
149
153
  : ["openid", "profile", "email", "User.Read", "offline_access"];
150
- options.scope && scopes.push(...options.scope);
151
- data.scopes && scopes.push(...data.scopes);
154
+ if (options.scope) scopes.push(...options.scope);
155
+ if (data.scopes) scopes.push(...data.scopes);
152
156
  return createAuthorizationURL({
153
157
  id: "microsoft",
154
158
  options,
@@ -206,13 +210,25 @@ export const microsoft = (options: MicrosoftOptions) => {
206
210
  },
207
211
  );
208
212
  const userMap = await options.mapProfileToUser?.(user);
213
+ // Microsoft Entra ID does NOT include email_verified claim by default.
214
+ // It must be configured as an optional claim in the app registration.
215
+ // We default to false when not provided for security consistency.
216
+ // We can also check verified_primary_email/verified_secondary_email arrays as fallback.
217
+ const emailVerified =
218
+ user.email_verified !== undefined
219
+ ? user.email_verified
220
+ : user.email &&
221
+ (user.verified_primary_email?.includes(user.email) ||
222
+ user.verified_secondary_email?.includes(user.email))
223
+ ? true
224
+ : false;
209
225
  return {
210
226
  user: {
211
227
  id: user.sub,
212
228
  name: user.name,
213
229
  email: user.email,
214
230
  image: user.picture,
215
- emailVerified: true,
231
+ emailVerified,
216
232
  ...userMap,
217
233
  },
218
234
  data: user,
@@ -224,7 +240,7 @@ export const microsoft = (options: MicrosoftOptions) => {
224
240
  const scopes = options.disableDefaultScope
225
241
  ? []
226
242
  : ["openid", "profile", "email", "User.Read", "offline_access"];
227
- options.scope && scopes.push(...options.scope);
243
+ if (options.scope) scopes.push(...options.scope);
228
244
 
229
245
  return refreshAccessToken({
230
246
  refreshToken,
@@ -1,10 +1,10 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
2
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
- validateAuthorizationCode,
6
5
  refreshAccessToken,
7
- } from "@better-auth/core/oauth2";
6
+ validateAuthorizationCode,
7
+ } from "../oauth2";
8
8
 
9
9
  export interface NaverProfile {
10
10
  /** API response result code */
@@ -45,8 +45,8 @@ export const naver = (options: NaverOptions) => {
45
45
  name: "Naver",
46
46
  createAuthorizationURL({ state, scopes, redirectURI }) {
47
47
  const _scopes = options.disableDefaultScope ? [] : ["profile", "email"];
48
- options.scope && _scopes.push(...options.scope);
49
- scopes && _scopes.push(...scopes);
48
+ if (options.scope) _scopes.push(...options.scope);
49
+ if (scopes) _scopes.push(...scopes);
50
50
  return createAuthorizationURL({
51
51
  id: "naver",
52
52
  options,
@@ -1,20 +1,22 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
2
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
6
  validateAuthorizationCode,
7
- } from "@better-auth/core/oauth2";
7
+ } from "../oauth2";
8
8
 
9
9
  export interface NotionProfile {
10
10
  object: "user";
11
11
  id: string;
12
12
  type: "person" | "bot";
13
- name?: string;
14
- avatar_url?: string;
15
- person?: {
16
- email?: string;
17
- };
13
+ name?: string | undefined;
14
+ avatar_url?: string | undefined;
15
+ person?:
16
+ | {
17
+ email?: string;
18
+ }
19
+ | undefined;
18
20
  }
19
21
 
20
22
  export interface NotionOptions extends ProviderOptions<NotionProfile> {
@@ -28,8 +30,8 @@ export const notion = (options: NotionOptions) => {
28
30
  name: "Notion",
29
31
  createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
30
32
  const _scopes: string[] = options.disableDefaultScope ? [] : [];
31
- options.scope && _scopes.push(...options.scope);
32
- scopes && _scopes.push(...scopes);
33
+ if (options.scope) _scopes.push(...options.scope);
34
+ if (scopes) _scopes.push(...scopes);
33
35
  return createAuthorizationURL({
34
36
  id: "notion",
35
37
  options,
@@ -0,0 +1,122 @@
1
+ import { decodeJwt } from "jose";
2
+ import { logger } from "../env";
3
+ import { BetterAuthError } from "../error";
4
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
5
+ import {
6
+ createAuthorizationURL,
7
+ refreshAccessToken,
8
+ validateAuthorizationCode,
9
+ } from "../oauth2";
10
+
11
+ export interface PaybinProfile {
12
+ sub: string;
13
+ email: string;
14
+ email_verified?: boolean | undefined;
15
+ name?: string | undefined;
16
+ preferred_username?: string | undefined;
17
+ picture?: string | undefined;
18
+ given_name?: string | undefined;
19
+ family_name?: string | undefined;
20
+ }
21
+
22
+ export interface PaybinOptions extends ProviderOptions<PaybinProfile> {
23
+ clientId: string;
24
+ /**
25
+ * The issuer URL of your Paybin OAuth server
26
+ * @default "https://idp.paybin.io"
27
+ */
28
+ issuer?: string | undefined;
29
+ }
30
+
31
+ export const paybin = (options: PaybinOptions) => {
32
+ const issuer = options.issuer || "https://idp.paybin.io";
33
+ const authorizationEndpoint = `${issuer}/oauth2/authorize`;
34
+ const tokenEndpoint = `${issuer}/oauth2/token`;
35
+
36
+ return {
37
+ id: "paybin",
38
+ name: "Paybin",
39
+ async createAuthorizationURL({
40
+ state,
41
+ scopes,
42
+ codeVerifier,
43
+ redirectURI,
44
+ loginHint,
45
+ }) {
46
+ if (!options.clientId || !options.clientSecret) {
47
+ logger.error(
48
+ "Client Id and Client Secret is required for Paybin. Make sure to provide them in the options.",
49
+ );
50
+ throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
51
+ }
52
+ if (!codeVerifier) {
53
+ throw new BetterAuthError("codeVerifier is required for Paybin");
54
+ }
55
+ const _scopes = options.disableDefaultScope
56
+ ? []
57
+ : ["openid", "email", "profile"];
58
+ if (options.scope) _scopes.push(...options.scope);
59
+ if (scopes) _scopes.push(...scopes);
60
+ const url = await createAuthorizationURL({
61
+ id: "paybin",
62
+ options,
63
+ authorizationEndpoint,
64
+ scopes: _scopes,
65
+ state,
66
+ codeVerifier,
67
+ redirectURI,
68
+ prompt: options.prompt,
69
+ loginHint,
70
+ });
71
+ return url;
72
+ },
73
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
74
+ return validateAuthorizationCode({
75
+ code,
76
+ codeVerifier,
77
+ redirectURI,
78
+ options,
79
+ tokenEndpoint,
80
+ });
81
+ },
82
+ refreshAccessToken: options.refreshAccessToken
83
+ ? options.refreshAccessToken
84
+ : async (refreshToken) => {
85
+ return refreshAccessToken({
86
+ refreshToken,
87
+ options: {
88
+ clientId: options.clientId,
89
+ clientKey: options.clientKey,
90
+ clientSecret: options.clientSecret,
91
+ },
92
+ tokenEndpoint,
93
+ });
94
+ },
95
+ async getUserInfo(token) {
96
+ if (options.getUserInfo) {
97
+ return options.getUserInfo(token);
98
+ }
99
+ if (!token.idToken) {
100
+ return null;
101
+ }
102
+ const user = decodeJwt(token.idToken) as PaybinProfile;
103
+ const userMap = await options.mapProfileToUser?.(user);
104
+ return {
105
+ user: {
106
+ id: user.sub,
107
+ name:
108
+ user.name ||
109
+ user.preferred_username ||
110
+ (user.email ? user.email.split("@")[0] : "User") ||
111
+ "User",
112
+ email: user.email,
113
+ image: user.picture,
114
+ emailVerified: user.email_verified || false,
115
+ ...userMap,
116
+ },
117
+ data: user,
118
+ };
119
+ },
120
+ options,
121
+ } satisfies OAuthProvider<PaybinProfile>;
122
+ };
@@ -1,46 +1,48 @@
1
+ import { base64 } from "@better-auth/utils/base64";
1
2
  import { betterFetch } from "@better-fetch/fetch";
2
- import { BetterAuthError } from "../error";
3
- import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
4
- import { createAuthorizationURL } from "@better-auth/core/oauth2";
5
- import { logger } from "@better-auth/core/env";
6
3
  import { decodeJwt } from "jose";
7
- import { base64 } from "@better-auth/utils/base64";
4
+ import { logger } from "../env";
5
+ import { BetterAuthError } from "../error";
6
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
7
+ import { createAuthorizationURL } from "../oauth2";
8
8
 
9
9
  export interface PayPalProfile {
10
10
  user_id: string;
11
11
  name: string;
12
12
  given_name: string;
13
13
  family_name: string;
14
- middle_name?: string;
15
- picture?: string;
14
+ middle_name?: string | undefined;
15
+ picture?: string | undefined;
16
16
  email: string;
17
17
  email_verified: boolean;
18
- gender?: string;
19
- birthdate?: string;
20
- zoneinfo?: string;
21
- locale?: string;
22
- phone_number?: string;
23
- address?: {
24
- street_address?: string;
25
- locality?: string;
26
- region?: string;
27
- postal_code?: string;
28
- country?: string;
29
- };
30
- verified_account?: boolean;
31
- account_type?: string;
32
- age_range?: string;
33
- payer_id?: string;
18
+ gender?: string | undefined;
19
+ birthdate?: string | undefined;
20
+ zoneinfo?: string | undefined;
21
+ locale?: string | undefined;
22
+ phone_number?: string | undefined;
23
+ address?:
24
+ | {
25
+ street_address?: string;
26
+ locality?: string;
27
+ region?: string;
28
+ postal_code?: string;
29
+ country?: string;
30
+ }
31
+ | undefined;
32
+ verified_account?: boolean | undefined;
33
+ account_type?: string | undefined;
34
+ age_range?: string | undefined;
35
+ payer_id?: string | undefined;
34
36
  }
35
37
 
36
38
  export interface PayPalTokenResponse {
37
- scope?: string;
39
+ scope?: string | undefined;
38
40
  access_token: string;
39
- refresh_token?: string;
41
+ refresh_token?: string | undefined;
40
42
  token_type: "Bearer";
41
- id_token?: string;
43
+ id_token?: string | undefined;
42
44
  expires_in: number;
43
- nonce?: string;
45
+ nonce?: string | undefined;
44
46
  }
45
47
 
46
48
  export interface PayPalOptions extends ProviderOptions<PayPalProfile> {
@@ -49,12 +51,12 @@ export interface PayPalOptions extends ProviderOptions<PayPalProfile> {
49
51
  * PayPal environment - 'sandbox' for testing, 'live' for production
50
52
  * @default 'sandbox'
51
53
  */
52
- environment?: "sandbox" | "live";
54
+ environment?: ("sandbox" | "live") | undefined;
53
55
  /**
54
56
  * Whether to request shipping address information
55
57
  * @default false
56
58
  */
57
- requestShippingAddress?: boolean;
59
+ requestShippingAddress?: boolean | undefined;
58
60
  }
59
61
 
60
62
  export const paypal = (options: PayPalOptions) => {
@@ -0,0 +1,110 @@
1
+ import { betterFetch } from "@better-fetch/fetch";
2
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
+ import {
4
+ createAuthorizationURL,
5
+ refreshAccessToken,
6
+ validateAuthorizationCode,
7
+ } from "../oauth2";
8
+
9
+ export interface PolarProfile {
10
+ id: string;
11
+ email: string;
12
+ username: string;
13
+ avatar_url: string;
14
+ github_username?: string | undefined;
15
+ account_id?: string | undefined;
16
+ public_name?: string | undefined;
17
+ email_verified?: boolean | undefined;
18
+ profile_settings?:
19
+ | {
20
+ profile_settings_enabled?: boolean;
21
+ profile_settings_public_name?: string;
22
+ profile_settings_public_avatar?: string;
23
+ profile_settings_public_bio?: string;
24
+ profile_settings_public_location?: string;
25
+ profile_settings_public_website?: string;
26
+ profile_settings_public_twitter?: string;
27
+ profile_settings_public_github?: string;
28
+ profile_settings_public_email?: string;
29
+ }
30
+ | undefined;
31
+ }
32
+
33
+ export interface PolarOptions extends ProviderOptions<PolarProfile> {}
34
+
35
+ export const polar = (options: PolarOptions) => {
36
+ return {
37
+ id: "polar",
38
+ name: "Polar",
39
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
40
+ const _scopes = options.disableDefaultScope
41
+ ? []
42
+ : ["openid", "profile", "email"];
43
+ if (options.scope) _scopes.push(...options.scope);
44
+ if (scopes) _scopes.push(...scopes);
45
+ return createAuthorizationURL({
46
+ id: "polar",
47
+ options,
48
+ authorizationEndpoint: "https://polar.sh/oauth2/authorize",
49
+ scopes: _scopes,
50
+ state,
51
+ codeVerifier,
52
+ redirectURI,
53
+ prompt: options.prompt,
54
+ });
55
+ },
56
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
57
+ return validateAuthorizationCode({
58
+ code,
59
+ codeVerifier,
60
+ redirectURI,
61
+ options,
62
+ tokenEndpoint: "https://api.polar.sh/v1/oauth2/token",
63
+ });
64
+ },
65
+ refreshAccessToken: options.refreshAccessToken
66
+ ? options.refreshAccessToken
67
+ : async (refreshToken) => {
68
+ return refreshAccessToken({
69
+ refreshToken,
70
+ options: {
71
+ clientId: options.clientId,
72
+ clientKey: options.clientKey,
73
+ clientSecret: options.clientSecret,
74
+ },
75
+ tokenEndpoint: "https://api.polar.sh/v1/oauth2/token",
76
+ });
77
+ },
78
+ async getUserInfo(token) {
79
+ if (options.getUserInfo) {
80
+ return options.getUserInfo(token);
81
+ }
82
+ const { data: profile, error } = await betterFetch<PolarProfile>(
83
+ "https://api.polar.sh/v1/oauth2/userinfo",
84
+ {
85
+ headers: {
86
+ Authorization: `Bearer ${token.accessToken}`,
87
+ },
88
+ },
89
+ );
90
+ if (error) {
91
+ return null;
92
+ }
93
+ const userMap = await options.mapProfileToUser?.(profile);
94
+ // Polar may provide email_verified claim, but it's not guaranteed.
95
+ // We check for it first, then default to false for security consistency.
96
+ return {
97
+ user: {
98
+ id: profile.id,
99
+ name: profile.public_name || profile.username,
100
+ email: profile.email,
101
+ image: profile.avatar_url,
102
+ emailVerified: profile.email_verified ?? false,
103
+ ...userMap,
104
+ },
105
+ data: profile,
106
+ };
107
+ },
108
+ options,
109
+ } satisfies OAuthProvider<PolarProfile>;
110
+ };
@@ -1,11 +1,11 @@
1
+ import { base64 } from "@better-auth/utils/base64";
1
2
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "@better-auth/core/oauth2";
3
+ import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
4
  import {
4
5
  createAuthorizationURL,
5
6
  getOAuth2Tokens,
6
7
  refreshAccessToken,
7
- } from "@better-auth/core/oauth2";
8
- import { base64 } from "@better-auth/utils/base64";
8
+ } from "../oauth2";
9
9
 
10
10
  export interface RedditProfile {
11
11
  id: string;
@@ -18,7 +18,7 @@ export interface RedditProfile {
18
18
 
19
19
  export interface RedditOptions extends ProviderOptions<RedditProfile> {
20
20
  clientId: string;
21
- duration?: string;
21
+ duration?: string | undefined;
22
22
  }
23
23
 
24
24
  export const reddit = (options: RedditOptions) => {
@@ -27,8 +27,8 @@ export const reddit = (options: RedditOptions) => {
27
27
  name: "Reddit",
28
28
  createAuthorizationURL({ state, scopes, redirectURI }) {
29
29
  const _scopes = options.disableDefaultScope ? [] : ["identity"];
30
- options.scope && _scopes.push(...options.scope);
31
- scopes && _scopes.push(...scopes);
30
+ if (options.scope) _scopes.push(...options.scope);
31
+ if (scopes) _scopes.push(...scopes);
32
32
  return createAuthorizationURL({
33
33
  id: "reddit",
34
34
  options,