@better-auth/core 1.7.2 → 1.7.3

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 (78) hide show
  1. package/dist/api/index.d.mts +3 -0
  2. package/dist/context/global.mjs +1 -1
  3. package/dist/context/transaction.mjs +3 -0
  4. package/dist/db/adapter/atomic-fallback.mjs +134 -0
  5. package/dist/db/adapter/factory.mjs +22 -4
  6. package/dist/db/adapter/index.d.mts +15 -11
  7. package/dist/db/get-tables.mjs +1 -9
  8. package/dist/db/index.d.mts +2 -2
  9. package/dist/db/index.mjs +2 -2
  10. package/dist/db/internal.d.mts +3 -1
  11. package/dist/db/internal.mjs +3 -1
  12. package/dist/db/schema/account.d.mts +2 -13
  13. package/dist/db/schema/account.mjs +1 -19
  14. package/dist/db/schema-check.d.mts +48 -0
  15. package/dist/db/schema-check.mjs +80 -0
  16. package/dist/db/schema-diff.d.mts +104 -0
  17. package/dist/db/schema-diff.mjs +154 -0
  18. package/dist/instrumentation/tracer.mjs +1 -1
  19. package/dist/oauth2/index.d.mts +2 -2
  20. package/dist/oauth2/oauth-provider.d.mts +0 -10
  21. package/dist/oauth2/token-endpoint-auth.d.mts +26 -2
  22. package/dist/oauth2/token-endpoint-auth.mjs +11 -0
  23. package/dist/social-providers/apple.d.mts +0 -1
  24. package/dist/social-providers/apple.mjs +0 -1
  25. package/dist/social-providers/cloudflare.d.mts +132 -0
  26. package/dist/social-providers/cloudflare.mjs +85 -0
  27. package/dist/social-providers/cognito.d.mts +0 -1
  28. package/dist/social-providers/cognito.mjs +0 -1
  29. package/dist/social-providers/facebook.d.mts +0 -1
  30. package/dist/social-providers/facebook.mjs +0 -1
  31. package/dist/social-providers/google.d.mts +0 -1
  32. package/dist/social-providers/google.mjs +0 -1
  33. package/dist/social-providers/index.d.mts +53 -21
  34. package/dist/social-providers/index.mjs +3 -1
  35. package/dist/social-providers/line.d.mts +0 -1
  36. package/dist/social-providers/line.mjs +0 -1
  37. package/dist/social-providers/microsoft-entra-id.d.mts +0 -3
  38. package/dist/social-providers/microsoft-entra-id.mjs +0 -1
  39. package/dist/social-providers/paybin.d.mts +0 -1
  40. package/dist/social-providers/paybin.mjs +0 -1
  41. package/dist/social-providers/paypal.d.mts +3 -11
  42. package/dist/social-providers/paypal.mjs +20 -47
  43. package/dist/social-providers/reddit.mjs +17 -22
  44. package/dist/social-providers/tiktok.d.mts +1 -0
  45. package/dist/social-providers/tiktok.mjs +14 -9
  46. package/dist/types/context.d.mts +11 -0
  47. package/dist/types/init-options.d.mts +11 -0
  48. package/dist/utils/ip.mjs +11 -9
  49. package/package.json +2 -2
  50. package/src/context/transaction.ts +5 -0
  51. package/src/db/adapter/atomic-fallback.ts +237 -0
  52. package/src/db/adapter/factory.ts +33 -17
  53. package/src/db/adapter/index.ts +15 -11
  54. package/src/db/get-tables.ts +1 -14
  55. package/src/db/index.ts +0 -2
  56. package/src/db/internal.ts +19 -0
  57. package/src/db/schema/account.ts +3 -22
  58. package/src/db/schema/user.ts +1 -1
  59. package/src/db/schema-check.ts +107 -0
  60. package/src/db/schema-diff.ts +270 -0
  61. package/src/oauth2/index.ts +2 -0
  62. package/src/oauth2/oauth-provider.ts +0 -10
  63. package/src/oauth2/token-endpoint-auth.ts +39 -6
  64. package/src/social-providers/apple.ts +0 -1
  65. package/src/social-providers/cloudflare.ts +221 -0
  66. package/src/social-providers/cognito.ts +0 -1
  67. package/src/social-providers/facebook.ts +0 -1
  68. package/src/social-providers/google.ts +0 -1
  69. package/src/social-providers/index.ts +3 -0
  70. package/src/social-providers/line.ts +0 -1
  71. package/src/social-providers/microsoft-entra-id.ts +0 -1
  72. package/src/social-providers/paybin.ts +0 -1
  73. package/src/social-providers/paypal.ts +30 -71
  74. package/src/social-providers/reddit.ts +27 -36
  75. package/src/social-providers/tiktok.ts +18 -13
  76. package/src/types/context.ts +11 -0
  77. package/src/types/init-options.ts +11 -0
  78. package/src/utils/ip.ts +13 -9
@@ -1,5 +1,6 @@
1
1
  import { AppleNonConformUser, AppleOptions, AppleProfile, apple, getApplePublicKey } from "./apple.mjs";
2
2
  import { AtlassianOptions, AtlassianProfile, atlassian } from "./atlassian.mjs";
3
+ import { CloudflareOptions, CloudflareProfile, cloudflare } from "./cloudflare.mjs";
3
4
  import { CognitoOptions, CognitoProfile, cognito, getCognitoPublicKey } from "./cognito.mjs";
4
5
  import { DiscordOptions, DiscordProfile, discord } from "./discord.mjs";
5
6
  import { FacebookGraphProfile, FacebookLimitedLoginProfile, FacebookOptions, FacebookProfile, facebook } from "./facebook.mjs";
@@ -46,7 +47,6 @@ declare const socialProviders: {
46
47
  accountSubject: ({
47
48
  profile
48
49
  }: OAuthAccountKeyContext<AppleProfile>) => string;
49
- accountIssuer: string;
50
50
  createAuthorizationURL({
51
51
  state,
52
52
  scopes,
@@ -158,13 +158,59 @@ declare const socialProviders: {
158
158
  } | null>;
159
159
  options: AtlassianOptions;
160
160
  };
161
+ cloudflare: (options: CloudflareOptions) => {
162
+ id: "cloudflare";
163
+ name: string;
164
+ accountSubject: ({
165
+ profile
166
+ }: OAuthAccountKeyContext<CloudflareProfile>) => string;
167
+ createAuthorizationURL({
168
+ state,
169
+ scopes,
170
+ codeVerifier,
171
+ redirectURI
172
+ }: {
173
+ state: string;
174
+ codeVerifier: string;
175
+ scopes?: string[] | undefined;
176
+ redirectURI: string;
177
+ display?: string | undefined;
178
+ loginHint?: string | undefined;
179
+ idTokenNonce?: string | undefined;
180
+ additionalParams?: Record<string, string> | undefined;
181
+ }): Promise<URL>;
182
+ validateAuthorizationCode: ({
183
+ code,
184
+ codeVerifier,
185
+ redirectURI
186
+ }: {
187
+ code: string;
188
+ redirectURI: string;
189
+ codeVerifier?: string | undefined;
190
+ deviceId?: string | undefined;
191
+ }) => Promise<OAuth2Tokens>;
192
+ refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
193
+ getUserInfo(token: OAuth2Tokens & {
194
+ expectedIdTokenNonce?: string | undefined;
195
+ user?: {
196
+ name?: {
197
+ firstName?: string;
198
+ lastName?: string;
199
+ };
200
+ email?: string;
201
+ } | undefined;
202
+ }): Promise<{
203
+ user: OAuth2UserInfo & Record<string, unknown>;
204
+ data: CloudflareProfile;
205
+ } | null>;
206
+ options: CloudflareOptions;
207
+ };
161
208
  cognito: (options: CognitoOptions) => {
162
209
  id: "cognito";
163
210
  name: string;
164
211
  accountSubject: ({
165
212
  profile
166
213
  }: OAuthAccountKeyContext<CognitoProfile>) => string;
167
- accountIssuer: string;
168
214
  createAuthorizationURL({
169
215
  state,
170
216
  scopes,
@@ -292,7 +338,6 @@ declare const socialProviders: {
292
338
  accountSubject: ({
293
339
  profile
294
340
  }: OAuthAccountKeyContext<FacebookProfile>) => string;
295
- accountIssuer: string;
296
341
  createAuthorizationURL({
297
342
  state,
298
343
  scopes,
@@ -451,9 +496,6 @@ declare const socialProviders: {
451
496
  accountSubject: ({
452
497
  profile
453
498
  }: OAuthAccountKeyContext<MicrosoftEntraIDProfile>) => string;
454
- accountIssuer: ({
455
- profile
456
- }: OAuthAccountKeyContext<MicrosoftEntraIDProfile>) => string;
457
499
  createAuthorizationURL(data: {
458
500
  state: string;
459
501
  codeVerifier: string;
@@ -503,7 +545,6 @@ declare const socialProviders: {
503
545
  accountSubject: ({
504
546
  profile
505
547
  }: OAuthAccountKeyContext<GoogleProfile>) => string;
506
- accountIssuer: string;
507
548
  createAuthorizationURL({
508
549
  state,
509
550
  scopes,
@@ -1047,6 +1088,7 @@ declare const socialProviders: {
1047
1088
  }): URL;
1048
1089
  validateAuthorizationCode: ({
1049
1090
  code,
1091
+ codeVerifier,
1050
1092
  redirectURI
1051
1093
  }: {
1052
1094
  code: string;
@@ -1468,7 +1510,6 @@ declare const socialProviders: {
1468
1510
  accountSubject: ({
1469
1511
  profile
1470
1512
  }: OAuthAccountKeyContext<LineIdTokenPayload | LineUserInfo>) => string;
1471
- accountIssuer: string;
1472
1513
  createAuthorizationURL({
1473
1514
  state,
1474
1515
  scopes,
@@ -1521,7 +1562,6 @@ declare const socialProviders: {
1521
1562
  accountSubject: ({
1522
1563
  profile
1523
1564
  }: OAuthAccountKeyContext<PaybinProfile>) => string;
1524
- accountIssuer: string;
1525
1565
  createAuthorizationURL({
1526
1566
  state,
1527
1567
  scopes,
@@ -1588,23 +1628,15 @@ declare const socialProviders: {
1588
1628
  }): Promise<URL>;
1589
1629
  validateAuthorizationCode: ({
1590
1630
  code,
1631
+ codeVerifier,
1591
1632
  redirectURI
1592
1633
  }: {
1593
1634
  code: string;
1594
1635
  redirectURI: string;
1595
1636
  codeVerifier?: string | undefined;
1596
1637
  deviceId?: string | undefined;
1597
- }) => Promise<{
1598
- accessToken: string;
1599
- refreshToken: string | undefined;
1600
- accessTokenExpiresAt: Date | undefined;
1601
- idToken: string | undefined;
1602
- }>;
1603
- refreshAccessToken: ((refreshToken: string) => Promise<OAuth2Tokens>) | ((refreshToken: string) => Promise<{
1604
- accessToken: any;
1605
- refreshToken: any;
1606
- accessTokenExpiresAt: Date | undefined;
1607
- }>);
1638
+ }) => Promise<OAuth2Tokens>;
1639
+ refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
1608
1640
  getUserInfo(token: OAuth2Tokens & {
1609
1641
  expectedIdTokenNonce?: string | undefined;
1610
1642
  user?: {
@@ -1840,4 +1872,4 @@ type SocialProviders = { [K in SocialProviderList[number]]?: AwaitableFunction<P
1840
1872
  }> };
1841
1873
  type SocialProviderList = typeof socialProviderList;
1842
1874
  //#endregion
1843
- export { AccountStatus, AppleNonConformUser, AppleOptions, AppleProfile, AtlassianOptions, AtlassianProfile, CognitoOptions, CognitoProfile, DiscordOptions, DiscordProfile, DropboxOptions, DropboxProfile, FacebookGraphProfile, FacebookLimitedLoginProfile, FacebookOptions, FacebookProfile, FigmaOptions, FigmaProfile, GithubOptions, GithubProfile, GitlabOptions, GitlabProfile, GoogleOptions, GoogleProfile, HuggingFaceOptions, HuggingFaceProfile, KakaoOptions, KakaoProfile, KickOptions, KickProfile, LineIdTokenPayload, LineOptions, LineUserInfo, LinearOptions, LinearProfile, LinearUser, LinkedInOptions, LinkedInProfile, LoginType, MicrosoftEntraIDProfile, MicrosoftOptions, NaverOptions, NaverProfile, NotionOptions, NotionProfile, PayPalOptions, PayPalProfile, PayPalTokenResponse, PaybinOptions, PaybinProfile, PhoneNumber, PolarOptions, PolarProfile, PronounOption, RailwayOptions, RailwayProfile, RedditOptions, RedditProfile, RobloxOptions, RobloxProfile, SalesforceOptions, SalesforceProfile, SlackOptions, SlackProfile, SocialProvider, SocialProviderList, SocialProviderListEnum, SocialProviders, SpotifyOptions, SpotifyProfile, TiktokOptions, TiktokProfile, TwitchOptions, TwitchProfile, TwitterOption, TwitterProfile, VercelOptions, VercelProfile, VerifyGoogleIdTokenOptions, VkOption, VkProfile, WeChatOptions, WeChatProfile, ZoomOptions, ZoomProfile, apple, atlassian, cognito, discord, dropbox, facebook, figma, getApplePublicKey, getCognitoPublicKey, getGooglePublicKey, getMicrosoftPublicKey, github, gitlab, google, huggingface, isGoogleHostedDomainAllowed, kakao, kick, line, linear, linkedin, microsoft, naver, notion, paybin, paypal, polar, railway, reddit, roblox, salesforce, slack, socialProviderList, socialProviders, spotify, tiktok, twitch, twitter, vercel, verifyGoogleIdToken, vk, wechat, zoom };
1875
+ export { AccountStatus, AppleNonConformUser, AppleOptions, AppleProfile, AtlassianOptions, AtlassianProfile, CloudflareOptions, CloudflareProfile, CognitoOptions, CognitoProfile, DiscordOptions, DiscordProfile, DropboxOptions, DropboxProfile, FacebookGraphProfile, FacebookLimitedLoginProfile, FacebookOptions, FacebookProfile, FigmaOptions, FigmaProfile, GithubOptions, GithubProfile, GitlabOptions, GitlabProfile, GoogleOptions, GoogleProfile, HuggingFaceOptions, HuggingFaceProfile, KakaoOptions, KakaoProfile, KickOptions, KickProfile, LineIdTokenPayload, LineOptions, LineUserInfo, LinearOptions, LinearProfile, LinearUser, LinkedInOptions, LinkedInProfile, LoginType, MicrosoftEntraIDProfile, MicrosoftOptions, NaverOptions, NaverProfile, NotionOptions, NotionProfile, PayPalOptions, PayPalProfile, PayPalTokenResponse, PaybinOptions, PaybinProfile, PhoneNumber, PolarOptions, PolarProfile, PronounOption, RailwayOptions, RailwayProfile, RedditOptions, RedditProfile, RobloxOptions, RobloxProfile, SalesforceOptions, SalesforceProfile, SlackOptions, SlackProfile, SocialProvider, SocialProviderList, SocialProviderListEnum, SocialProviders, SpotifyOptions, SpotifyProfile, TiktokOptions, TiktokProfile, TwitchOptions, TwitchProfile, TwitterOption, TwitterProfile, VercelOptions, VercelProfile, VerifyGoogleIdTokenOptions, VkOption, VkProfile, WeChatOptions, WeChatProfile, ZoomOptions, ZoomProfile, apple, atlassian, cloudflare, cognito, discord, dropbox, facebook, figma, getApplePublicKey, getCognitoPublicKey, getGooglePublicKey, getMicrosoftPublicKey, github, gitlab, google, huggingface, isGoogleHostedDomainAllowed, kakao, kick, line, linear, linkedin, microsoft, naver, notion, paybin, paypal, polar, railway, reddit, roblox, salesforce, slack, socialProviderList, socialProviders, spotify, tiktok, twitch, twitter, vercel, verifyGoogleIdToken, vk, wechat, zoom };
@@ -1,5 +1,6 @@
1
1
  import { apple, getApplePublicKey } from "./apple.mjs";
2
2
  import { atlassian } from "./atlassian.mjs";
3
+ import { cloudflare } from "./cloudflare.mjs";
3
4
  import { cognito, getCognitoPublicKey } from "./cognito.mjs";
4
5
  import { discord } from "./discord.mjs";
5
6
  import { dropbox } from "./dropbox.mjs";
@@ -38,6 +39,7 @@ import * as z from "zod";
38
39
  const socialProviders = {
39
40
  apple,
40
41
  atlassian,
42
+ cloudflare,
41
43
  cognito,
42
44
  discord,
43
45
  facebook,
@@ -75,4 +77,4 @@ const socialProviders = {
75
77
  const socialProviderList = Object.keys(socialProviders);
76
78
  const SocialProviderListEnum = z.enum(socialProviderList).or(z.string());
77
79
  //#endregion
78
- export { SocialProviderListEnum, apple, atlassian, cognito, discord, dropbox, facebook, figma, getApplePublicKey, getCognitoPublicKey, getGooglePublicKey, getMicrosoftPublicKey, github, gitlab, google, huggingface, isGoogleHostedDomainAllowed, kakao, kick, line, linear, linkedin, microsoft, naver, notion, paybin, paypal, polar, railway, reddit, roblox, salesforce, slack, socialProviderList, socialProviders, spotify, tiktok, twitch, twitter, vercel, verifyGoogleIdToken, vk, wechat, zoom };
80
+ export { SocialProviderListEnum, apple, atlassian, cloudflare, cognito, discord, dropbox, facebook, figma, getApplePublicKey, getCognitoPublicKey, getGooglePublicKey, getMicrosoftPublicKey, github, gitlab, google, huggingface, isGoogleHostedDomainAllowed, kakao, kick, line, linear, linkedin, microsoft, naver, notion, paybin, paypal, polar, railway, reddit, roblox, salesforce, slack, socialProviderList, socialProviders, spotify, tiktok, twitch, twitter, vercel, verifyGoogleIdToken, vk, wechat, zoom };
@@ -36,7 +36,6 @@ declare const line: (options: LineOptions) => {
36
36
  accountSubject: ({
37
37
  profile
38
38
  }: OAuthAccountKeyContext<LineIdTokenPayload | LineUserInfo>) => string;
39
- accountIssuer: string;
40
39
  createAuthorizationURL({
41
40
  state,
42
41
  scopes,
@@ -22,7 +22,6 @@ const line = (options) => {
22
22
  id: "line",
23
23
  name: "LINE",
24
24
  accountSubject: ({ profile }) => profile.sub,
25
- accountIssuer: "https://access.line.me",
26
25
  async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, loginHint, additionalParams }) {
27
26
  const _scopes = options.disableDefaultScope ? [] : [
28
27
  "openid",
@@ -142,9 +142,6 @@ declare const microsoft: (options: MicrosoftOptions) => {
142
142
  accountSubject: ({
143
143
  profile
144
144
  }: OAuthAccountKeyContext<MicrosoftEntraIDProfile>) => string;
145
- accountIssuer: ({
146
- profile
147
- }: OAuthAccountKeyContext<MicrosoftEntraIDProfile>) => string;
148
145
  createAuthorizationURL(data: {
149
146
  state: string;
150
147
  codeVerifier: string;
@@ -30,7 +30,6 @@ const microsoft = (options) => {
30
30
  id: "microsoft",
31
31
  name: "Microsoft EntraID",
32
32
  accountSubject: ({ profile }) => profile.oid,
33
- accountIssuer: ({ profile }) => profile.iss,
34
33
  createAuthorizationURL(data) {
35
34
  if (!getPrimaryClientId(options.clientId)) {
36
35
  logger.error("Client Id is required for Microsoft Entra ID. Make sure to provide it in the options.");
@@ -24,7 +24,6 @@ declare const paybin: (options: PaybinOptions) => {
24
24
  accountSubject: ({
25
25
  profile
26
26
  }: OAuthAccountKeyContext<PaybinProfile>) => string;
27
- accountIssuer: string;
28
27
  createAuthorizationURL({
29
28
  state,
30
29
  scopes,
@@ -13,7 +13,6 @@ const paybin = (options) => {
13
13
  id: "paybin",
14
14
  name: "Paybin",
15
15
  accountSubject: ({ profile }) => profile.sub,
16
- accountIssuer: issuer,
17
16
  async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, loginHint, additionalParams }) {
18
17
  if (!options.clientId || !options.clientSecret) {
19
18
  logger.error("Client Id and Client Secret is required for Paybin. Make sure to provide them in the options.");
@@ -72,23 +72,15 @@ declare const paypal: (options: PayPalOptions) => {
72
72
  }): Promise<URL>;
73
73
  validateAuthorizationCode: ({
74
74
  code,
75
+ codeVerifier,
75
76
  redirectURI
76
77
  }: {
77
78
  code: string;
78
79
  redirectURI: string;
79
80
  codeVerifier?: string | undefined;
80
81
  deviceId?: string | undefined;
81
- }) => Promise<{
82
- accessToken: string;
83
- refreshToken: string | undefined;
84
- accessTokenExpiresAt: Date | undefined;
85
- idToken: string | undefined;
86
- }>;
87
- refreshAccessToken: ((refreshToken: string) => Promise<OAuth2Tokens>) | ((refreshToken: string) => Promise<{
88
- accessToken: any;
89
- refreshToken: any;
90
- accessTokenExpiresAt: Date | undefined;
91
- }>);
82
+ }) => Promise<OAuth2Tokens>;
83
+ refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
92
84
  getUserInfo(token: OAuth2Tokens & {
93
85
  expectedIdTokenNonce?: string | undefined;
94
86
  user?: {
@@ -1,7 +1,8 @@
1
1
  import { BetterAuthError } from "../error/index.mjs";
2
2
  import { logger } from "../env/logger.mjs";
3
3
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
4
- import { base64 } from "@better-auth/utils/base64";
4
+ import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
5
+ import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
5
6
  import { decodeJwt } from "jose";
6
7
  import { betterFetch } from "@better-fetch/fetch";
7
8
  //#region src/social-providers/paypal.ts
@@ -10,6 +11,11 @@ const paypal = (options) => {
10
11
  const authorizationEndpoint = isSandbox ? "https://www.sandbox.paypal.com/signin/authorize" : "https://www.paypal.com/signin/authorize";
11
12
  const tokenEndpoint = isSandbox ? "https://api-m.sandbox.paypal.com/v1/oauth2/token" : "https://api-m.paypal.com/v1/oauth2/token";
12
13
  const userInfoEndpoint = isSandbox ? "https://api-m.sandbox.paypal.com/v1/identity/oauth2/userinfo" : "https://api-m.paypal.com/v1/identity/oauth2/userinfo";
14
+ const tokenRequestOptions = {
15
+ clientId: options.clientId,
16
+ clientSecret: options.clientSecret
17
+ };
18
+ const tokenEndpointAuth = { method: "client_secret_basic" };
13
19
  return {
14
20
  id: "paypal",
15
21
  name: "PayPal",
@@ -31,62 +37,29 @@ const paypal = (options) => {
31
37
  additionalParams
32
38
  });
33
39
  },
34
- validateAuthorizationCode: async ({ code, redirectURI }) => {
35
- /**
36
- * PayPal requires Basic Auth for token exchange
37
- **/
38
- const credentials = base64.encode(`${options.clientId}:${options.clientSecret}`);
40
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
39
41
  try {
40
- const response = await betterFetch(tokenEndpoint, {
41
- method: "POST",
42
- headers: {
43
- Authorization: `Basic ${credentials}`,
44
- Accept: "application/json",
45
- "Accept-Language": "en_US",
46
- "Content-Type": "application/x-www-form-urlencoded"
47
- },
48
- body: new URLSearchParams({
49
- grant_type: "authorization_code",
50
- code,
51
- redirect_uri: redirectURI
52
- }).toString()
42
+ return await validateAuthorizationCode({
43
+ code,
44
+ codeVerifier,
45
+ redirectURI: options.redirectURI || redirectURI,
46
+ options: tokenRequestOptions,
47
+ tokenEndpoint,
48
+ tokenEndpointAuth
53
49
  });
54
- if (!response.data) throw new BetterAuthError("FAILED_TO_GET_ACCESS_TOKEN");
55
- const data = response.data;
56
- return {
57
- accessToken: data.access_token,
58
- refreshToken: data.refresh_token,
59
- accessTokenExpiresAt: data.expires_in ? new Date(Date.now() + data.expires_in * 1e3) : void 0,
60
- idToken: data.id_token
61
- };
62
50
  } catch (error) {
63
51
  logger.error("PayPal token exchange failed:", error);
64
52
  throw new BetterAuthError("FAILED_TO_GET_ACCESS_TOKEN");
65
53
  }
66
54
  },
67
55
  refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
68
- const credentials = base64.encode(`${options.clientId}:${options.clientSecret}`);
69
56
  try {
70
- const response = await betterFetch(tokenEndpoint, {
71
- method: "POST",
72
- headers: {
73
- Authorization: `Basic ${credentials}`,
74
- Accept: "application/json",
75
- "Accept-Language": "en_US",
76
- "Content-Type": "application/x-www-form-urlencoded"
77
- },
78
- body: new URLSearchParams({
79
- grant_type: "refresh_token",
80
- refresh_token: refreshToken
81
- }).toString()
57
+ return await refreshAccessToken({
58
+ refreshToken,
59
+ options: tokenRequestOptions,
60
+ tokenEndpoint,
61
+ tokenEndpointAuth
82
62
  });
83
- if (!response.data) throw new BetterAuthError("FAILED_TO_REFRESH_ACCESS_TOKEN");
84
- const data = response.data;
85
- return {
86
- accessToken: data.access_token,
87
- refreshToken: data.refresh_token,
88
- accessTokenExpiresAt: data.expires_in ? new Date(Date.now() + data.expires_in * 1e3) : void 0
89
- };
90
63
  } catch (error) {
91
64
  logger.error("PayPal token refresh failed:", error);
92
65
  throw new BetterAuthError("FAILED_TO_REFRESH_ACCESS_TOKEN");
@@ -1,11 +1,16 @@
1
- import { getOAuth2Tokens } from "../oauth2/utils.mjs";
2
1
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
3
2
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
3
+ import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
4
4
  import { createPlaceholderEmail } from "../utils/email.mjs";
5
- import { base64 } from "@better-auth/utils/base64";
6
5
  import { betterFetch } from "@better-fetch/fetch";
7
6
  //#region src/social-providers/reddit.ts
8
7
  const reddit = (options) => {
8
+ const tokenEndpoint = "https://www.reddit.com/api/v1/access_token";
9
+ const tokenRequestOptions = {
10
+ clientId: options.clientId,
11
+ clientSecret: options.clientSecret
12
+ };
13
+ const tokenEndpointAuth = { method: "client_secret_basic" };
9
14
  return {
10
15
  id: "reddit",
11
16
  name: "Reddit",
@@ -26,34 +31,24 @@ const reddit = (options) => {
26
31
  });
27
32
  },
28
33
  validateAuthorizationCode: async ({ code, redirectURI }) => {
29
- const body = new URLSearchParams({
30
- grant_type: "authorization_code",
34
+ return validateAuthorizationCode({
31
35
  code,
32
- redirect_uri: options.redirectURI || redirectURI
33
- });
34
- const { data, error } = await betterFetch("https://www.reddit.com/api/v1/access_token", {
35
- method: "POST",
36
+ redirectURI: options.redirectURI || redirectURI,
37
+ options: tokenRequestOptions,
38
+ tokenEndpoint,
39
+ tokenEndpointAuth,
36
40
  headers: {
37
- "content-type": "application/x-www-form-urlencoded",
38
41
  accept: "text/plain",
39
- "user-agent": "better-auth",
40
- Authorization: `Basic ${base64.encode(`${options.clientId}:${options.clientSecret}`)}`
41
- },
42
- body: body.toString()
42
+ "user-agent": "better-auth"
43
+ }
43
44
  });
44
- if (error) throw error;
45
- return getOAuth2Tokens(data);
46
45
  },
47
46
  refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
48
47
  return refreshAccessToken({
49
48
  refreshToken,
50
- options: {
51
- clientId: options.clientId,
52
- clientKey: options.clientKey,
53
- clientSecret: options.clientSecret
54
- },
55
- authentication: "basic",
56
- tokenEndpoint: "https://www.reddit.com/api/v1/access_token"
49
+ options: tokenRequestOptions,
50
+ tokenEndpoint,
51
+ tokenEndpointAuth
57
52
  });
58
53
  },
59
54
  async getUserInfo(token) {
@@ -141,6 +141,7 @@ declare const tiktok: (options: TiktokOptions) => {
141
141
  }): URL;
142
142
  validateAuthorizationCode: ({
143
143
  code,
144
+ codeVerifier,
144
145
  redirectURI
145
146
  }: {
146
147
  code: string;
@@ -6,6 +6,13 @@ import { betterFetch } from "@better-fetch/fetch";
6
6
  //#region src/social-providers/tiktok.ts
7
7
  const tiktok = (options) => {
8
8
  const tokenEndpoint = "https://open.tiktokapis.com/v2/oauth/token/";
9
+ const tokenEndpointAuth = {
10
+ method: "custom",
11
+ customizeRequest({ body }) {
12
+ body.set("client_key", options.clientKey);
13
+ body.set("client_secret", options.clientSecret);
14
+ }
15
+ };
9
16
  return {
10
17
  id: "tiktok",
11
18
  name: "TikTok",
@@ -27,24 +34,22 @@ const tiktok = (options) => {
27
34
  }
28
35
  return url;
29
36
  },
30
- validateAuthorizationCode: async ({ code, redirectURI }) => {
37
+ validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
31
38
  return validateAuthorizationCode({
32
39
  code,
40
+ codeVerifier,
33
41
  redirectURI: options.redirectURI || redirectURI,
34
- options: {
35
- clientKey: options.clientKey,
36
- clientSecret: options.clientSecret
37
- },
38
- tokenEndpoint
42
+ options: {},
43
+ tokenEndpoint,
44
+ tokenEndpointAuth
39
45
  });
40
46
  },
41
47
  refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
42
48
  return refreshAccessToken({
43
49
  refreshToken,
44
- options: { clientSecret: options.clientSecret },
50
+ options: {},
45
51
  tokenEndpoint,
46
- authentication: "post",
47
- extraParams: { client_key: options.clientKey }
52
+ tokenEndpointAuth
48
53
  });
49
54
  },
50
55
  async getUserInfo(token) {
@@ -8,6 +8,7 @@ import { Awaitable, LiteralString } from "./helper.mjs";
8
8
  import { BetterAuthPlugin } from "./plugin.mjs";
9
9
  import { BetterAuthOptions, BetterAuthRateLimitOptions, UserProvisioningSource } from "./init-options.mjs";
10
10
  import { Account, AccountKey } from "../db/schema/account.mjs";
11
+ import { SchemaCheck } from "../db/schema-check.mjs";
11
12
  import { BetterAuthCookie, BetterAuthCookies, CookieCachePayload } from "./cookie.mjs";
12
13
  import { SecretConfig } from "./secret.mjs";
13
14
  import { OAuthProvider } from "../oauth2/oauth-provider.mjs";
@@ -263,6 +264,16 @@ type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> = Plugin
263
264
  storage: "memory" | "database" | "secondary-storage";
264
265
  } & Omit<BetterAuthRateLimitOptions, "enabled" | "window" | "max" | "storage">;
265
266
  adapter: DBAdapter<Options>;
267
+ /**
268
+ * Confirms the database can hold what this configuration writes.
269
+ *
270
+ * Shared by initialization and requests for this adapter instance;
271
+ * returns nothing once the schema is known to be clean.
272
+ * Context construction does not await the verdict, so migration
273
+ * tooling can still use a context whose schema needs repair.
274
+ * Absent when the check is disabled or the adapter registers none.
275
+ */
276
+ checkSchema?: SchemaCheck | undefined;
266
277
  internalAdapter: InternalAdapter<Options>;
267
278
  createAuthCookie: CreateCookieGetterFn;
268
279
  secret: string;
@@ -387,6 +387,17 @@ type BetterAuthAdvancedOptions = {
387
387
  * @default false
388
388
  */
389
389
  joins?: boolean;
390
+ /**
391
+ * Validate the schema during initialization and report problems
392
+ * through the configured logger. Authentication requests await
393
+ * the same check and fail when the schema does not match.
394
+ * Kysely introspects the database; Drizzle and Prisma inspect
395
+ * local schema metadata without opening a connection.
396
+ * Set `false` to disable runtime schema validation.
397
+ *
398
+ * @default true
399
+ */
400
+ validateSchema?: boolean;
390
401
  } | undefined;
391
402
  /**
392
403
  * Trusted proxy headers
package/dist/utils/ip.mjs CHANGED
@@ -1,17 +1,19 @@
1
1
  import { isDevelopment, isTest } from "../env/env-impl.mjs";
2
2
  import * as z from "zod";
3
3
  //#region src/utils/ip.ts
4
+ const ipv4Schema = z.ipv4();
5
+ const ipv6Schema = z.ipv6();
4
6
  /**
5
7
  * Checks if an IP is valid IPv4 or IPv6
6
8
  */
7
9
  function isValidIP(ip) {
8
- return z.ipv4().safeParse(ip).success || z.ipv6().safeParse(ip).success;
10
+ return isIPv4(ip) || isIPv6(ip);
11
+ }
12
+ function isIPv4(ip) {
13
+ return z.validate(ipv4Schema, ip);
9
14
  }
10
- /**
11
- * Checks if an IP is IPv6
12
- */
13
15
  function isIPv6(ip) {
14
- return z.ipv6().safeParse(ip).success;
16
+ return z.validate(ipv6Schema, ip);
15
17
  }
16
18
  /**
17
19
  * Converts IPv4-mapped IPv6 address to IPv4
@@ -21,12 +23,12 @@ function extractIPv4FromMapped(ipv6) {
21
23
  const lower = ipv6.toLowerCase();
22
24
  if (lower.startsWith("::ffff:")) {
23
25
  const ipv4Part = lower.substring(7);
24
- if (z.ipv4().safeParse(ipv4Part).success) return ipv4Part;
26
+ if (isIPv4(ipv4Part)) return ipv4Part;
25
27
  }
26
28
  const parts = ipv6.split(":");
27
29
  if (parts.length === 7 && parts[5]?.toLowerCase() === "ffff") {
28
30
  const ipv4Part = parts[6];
29
- if (ipv4Part && z.ipv4().safeParse(ipv4Part).success) return ipv4Part;
31
+ if (ipv4Part && isIPv4(ipv4Part)) return ipv4Part;
30
32
  }
31
33
  if (lower.includes("::ffff:") || lower.includes(":ffff:")) {
32
34
  const groups = expandIPv6(ipv6);
@@ -96,7 +98,7 @@ function normalizeIPv6(ipv6, subnetPrefix) {
96
98
  * // -> "2001:0db8:0000:0000:0000:0000:0000:0000" (subnet /64)
97
99
  */
98
100
  function normalizeIP(ip, options = {}) {
99
- if (z.ipv4().safeParse(ip).success) return ip.toLowerCase();
101
+ if (isIPv4(ip)) return ip.toLowerCase();
100
102
  if (!isIPv6(ip)) return ip.toLowerCase();
101
103
  const ipv4 = extractIPv4FromMapped(ip);
102
104
  if (ipv4) return ipv4.toLowerCase();
@@ -106,7 +108,7 @@ function normalizeIP(ip, options = {}) {
106
108
  * Raw bytes of an IP for CIDR comparison. Returns `null` for an invalid IP.
107
109
  */
108
110
  function ipToBytes(ip) {
109
- if (z.ipv4().safeParse(ip).success) return Uint8Array.from(ip.split(".").map((octet) => Number(octet)));
111
+ if (isIPv4(ip)) return Uint8Array.from(ip.split(".").map((octet) => Number(octet)));
110
112
  if (!isIPv6(ip)) return null;
111
113
  const mapped = extractIPv4FromMapped(ip);
112
114
  if (mapped) return Uint8Array.from(mapped.split(".").map((octet) => Number(octet)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/core",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
4
4
  "description": "The most comprehensive authentication framework for TypeScript.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -157,7 +157,7 @@
157
157
  "dependencies": {
158
158
  "@opentelemetry/semantic-conventions": "^1.41.1",
159
159
  "@standard-schema/spec": "^1.1.0",
160
- "zod": "^4.3.6"
160
+ "zod": "^4.5.4"
161
161
  },
162
162
  "devDependencies": {
163
163
  "@better-auth/utils": "0.4.2",
@@ -1,6 +1,7 @@
1
1
  import type { AsyncLocalStorage } from "@better-auth/core/async_hooks";
2
2
  import { getAsyncLocalStorage } from "@better-auth/core/async_hooks";
3
3
  import type { DBAdapter, DBTransactionAdapter } from "../db/adapter";
4
+ import { schemaCheckFor } from "../db/schema-check";
4
5
  import type { BetterAuthOptions } from "../types";
5
6
  import { __getBetterAuthGlobal } from "./global";
6
7
 
@@ -114,6 +115,10 @@ export const runWithTransaction = async <
114
115
  if (store?.isTransactionActive) {
115
116
  return fn();
116
117
  }
118
+ // Settle the schema verdict before this transaction holds the
119
+ // connection a single-connection store would need for the lookup.
120
+ const pendingSchemaCheck = schemaCheckFor(adapter)?.();
121
+ if (pendingSchemaCheck) await pendingSchemaCheck;
117
122
  const pendingHooks: Array<() => Promise<void>> = [];
118
123
  let result: Awaited<R>;
119
124
  let error: unknown;