better-auth 1.7.0-beta.7 → 1.7.0-beta.8

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 (35) hide show
  1. package/dist/api/index.d.mts +8 -26
  2. package/dist/api/routes/account.d.mts +4 -11
  3. package/dist/api/routes/account.mjs +81 -64
  4. package/dist/api/routes/callback.mjs +35 -30
  5. package/dist/api/routes/sign-in.d.mts +0 -2
  6. package/dist/api/routes/sign-in.mjs +13 -21
  7. package/dist/cookies/session-store.d.mts +1 -1
  8. package/dist/db/get-migration.mjs +1 -34
  9. package/dist/db/internal-adapter.mjs +20 -0
  10. package/dist/db/schema.d.mts +1 -1
  11. package/dist/oauth2/index.d.mts +3 -5
  12. package/dist/oauth2/index.mjs +3 -5
  13. package/dist/oauth2/link-account.d.mts +74 -0
  14. package/dist/oauth2/link-account.mjs +222 -0
  15. package/dist/oauth2/state.d.mts +0 -11
  16. package/dist/oauth2/state.mjs +1 -2
  17. package/dist/oauth2/utils.d.mts +11 -0
  18. package/dist/oauth2/{token-encryption.mjs → utils.mjs} +6 -2
  19. package/dist/package.mjs +1 -1
  20. package/dist/plugins/generic-oauth/index.d.mts +2 -2
  21. package/dist/plugins/generic-oauth/index.mjs +0 -1
  22. package/dist/plugins/oauth-popup/index.mjs +2 -2
  23. package/dist/plugins/oauth-proxy/index.mjs +6 -20
  24. package/dist/plugins/one-tap/index.mjs +6 -10
  25. package/dist/state.d.mts +1 -10
  26. package/dist/state.mjs +1 -2
  27. package/dist/test-utils/http-test-instance.d.mts +0 -20
  28. package/package.json +8 -8
  29. package/dist/oauth2/persist-account.d.mts +0 -80
  30. package/dist/oauth2/persist-account.mjs +0 -84
  31. package/dist/oauth2/resolve-account.d.mts +0 -126
  32. package/dist/oauth2/resolve-account.mjs +0 -128
  33. package/dist/oauth2/sign-in-with-oauth-identity.d.mts +0 -83
  34. package/dist/oauth2/sign-in-with-oauth-identity.mjs +0 -133
  35. package/dist/oauth2/token-encryption.d.mts +0 -7
@@ -100,7 +100,6 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
100
100
  accessToken: zod.ZodOptional<zod.ZodString>;
101
101
  refreshToken: zod.ZodOptional<zod.ZodString>;
102
102
  expiresAt: zod.ZodOptional<zod.ZodNumber>;
103
- scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
104
103
  user: zod.ZodOptional<zod.ZodObject<{
105
104
  name: zod.ZodOptional<zod.ZodObject<{
106
105
  firstName: zod.ZodOptional<zod.ZodString>;
@@ -129,7 +128,6 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
129
128
  accessToken: zod.ZodOptional<zod.ZodString>;
130
129
  refreshToken: zod.ZodOptional<zod.ZodString>;
131
130
  expiresAt: zod.ZodOptional<zod.ZodNumber>;
132
- scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
133
131
  user: zod.ZodOptional<zod.ZodObject<{
134
132
  name: zod.ZodOptional<zod.ZodObject<{
135
133
  firstName: zod.ZodOptional<zod.ZodString>;
@@ -1584,7 +1582,6 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
1584
1582
  nonce: zod.ZodOptional<zod.ZodString>;
1585
1583
  accessToken: zod.ZodOptional<zod.ZodString>;
1586
1584
  refreshToken: zod.ZodOptional<zod.ZodString>;
1587
- scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
1588
1585
  }, zod_v4_core0.$strip>>;
1589
1586
  requestSignUp: zod.ZodOptional<zod.ZodBoolean>;
1590
1587
  scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
@@ -1712,7 +1709,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
1712
1709
  userId: {
1713
1710
  type: string;
1714
1711
  };
1715
- grantedScopes: {
1712
+ scopes: {
1716
1713
  type: string;
1717
1714
  items: {
1718
1715
  type: string;
@@ -1729,7 +1726,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
1729
1726
  };
1730
1727
  };
1731
1728
  }, {
1732
- grantedScopes: string[];
1729
+ scopes: string[];
1733
1730
  id: string;
1734
1731
  createdAt: Date;
1735
1732
  updatedAt: Date;
@@ -1869,12 +1866,6 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
1869
1866
  type: string;
1870
1867
  format: string;
1871
1868
  };
1872
- grantedScopes: {
1873
- type: string;
1874
- items: {
1875
- type: string;
1876
- };
1877
- };
1878
1869
  };
1879
1870
  };
1880
1871
  };
@@ -1891,7 +1882,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
1891
1882
  refreshToken: string;
1892
1883
  accessTokenExpiresAt: Date | undefined;
1893
1884
  refreshTokenExpiresAt: Date | null | undefined;
1894
- grantedScopes: string[];
1885
+ scope: string | null | undefined;
1895
1886
  idToken: string | null | undefined;
1896
1887
  providerId: string;
1897
1888
  accountId: string;
@@ -1941,7 +1932,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
1941
1932
  }, {
1942
1933
  accessToken: string;
1943
1934
  accessTokenExpiresAt: Date | undefined;
1944
- grantedScopes: string[];
1935
+ scopes: string[];
1945
1936
  idToken: string | undefined;
1946
1937
  }>;
1947
1938
  readonly accountInfo: better_call0.StrictEndpoint<"/account-info", {
@@ -2084,7 +2075,6 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
2084
2075
  accessToken: zod.ZodOptional<zod.ZodString>;
2085
2076
  refreshToken: zod.ZodOptional<zod.ZodString>;
2086
2077
  expiresAt: zod.ZodOptional<zod.ZodNumber>;
2087
- scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
2088
2078
  user: zod.ZodOptional<zod.ZodObject<{
2089
2079
  name: zod.ZodOptional<zod.ZodObject<{
2090
2080
  firstName: zod.ZodOptional<zod.ZodString>;
@@ -2113,7 +2103,6 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
2113
2103
  accessToken: zod.ZodOptional<zod.ZodString>;
2114
2104
  refreshToken: zod.ZodOptional<zod.ZodString>;
2115
2105
  expiresAt: zod.ZodOptional<zod.ZodNumber>;
2116
- scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
2117
2106
  user: zod.ZodOptional<zod.ZodObject<{
2118
2107
  name: zod.ZodOptional<zod.ZodObject<{
2119
2108
  firstName: zod.ZodOptional<zod.ZodString>;
@@ -3568,7 +3557,6 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
3568
3557
  nonce: zod.ZodOptional<zod.ZodString>;
3569
3558
  accessToken: zod.ZodOptional<zod.ZodString>;
3570
3559
  refreshToken: zod.ZodOptional<zod.ZodString>;
3571
- scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
3572
3560
  }, zod_v4_core0.$strip>>;
3573
3561
  requestSignUp: zod.ZodOptional<zod.ZodBoolean>;
3574
3562
  scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString>>;
@@ -3696,7 +3684,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
3696
3684
  userId: {
3697
3685
  type: string;
3698
3686
  };
3699
- grantedScopes: {
3687
+ scopes: {
3700
3688
  type: string;
3701
3689
  items: {
3702
3690
  type: string;
@@ -3713,7 +3701,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
3713
3701
  };
3714
3702
  };
3715
3703
  }, {
3716
- grantedScopes: string[];
3704
+ scopes: string[];
3717
3705
  id: string;
3718
3706
  createdAt: Date;
3719
3707
  updatedAt: Date;
@@ -3853,12 +3841,6 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
3853
3841
  type: string;
3854
3842
  format: string;
3855
3843
  };
3856
- grantedScopes: {
3857
- type: string;
3858
- items: {
3859
- type: string;
3860
- };
3861
- };
3862
3844
  };
3863
3845
  };
3864
3846
  };
@@ -3875,7 +3857,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
3875
3857
  refreshToken: string;
3876
3858
  accessTokenExpiresAt: Date | undefined;
3877
3859
  refreshTokenExpiresAt: Date | null | undefined;
3878
- grantedScopes: string[];
3860
+ scope: string | null | undefined;
3879
3861
  idToken: string | null | undefined;
3880
3862
  providerId: string;
3881
3863
  accountId: string;
@@ -3925,7 +3907,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
3925
3907
  }, {
3926
3908
  accessToken: string;
3927
3909
  accessTokenExpiresAt: Date | undefined;
3928
- grantedScopes: string[];
3910
+ scopes: string[];
3929
3911
  idToken: string | undefined;
3930
3912
  }>;
3931
3913
  readonly accountInfo: better_call0.StrictEndpoint<"/account-info", {
@@ -62,7 +62,7 @@ declare const listUserAccounts: better_call0.StrictEndpoint<"/list-accounts", {
62
62
  userId: {
63
63
  type: string;
64
64
  };
65
- grantedScopes: {
65
+ scopes: {
66
66
  type: string;
67
67
  items: {
68
68
  type: string;
@@ -79,7 +79,7 @@ declare const listUserAccounts: better_call0.StrictEndpoint<"/list-accounts", {
79
79
  };
80
80
  };
81
81
  }, {
82
- grantedScopes: string[];
82
+ scopes: string[];
83
83
  id: string;
84
84
  createdAt: Date;
85
85
  updatedAt: Date;
@@ -98,7 +98,6 @@ declare const linkSocialAccount: better_call0.StrictEndpoint<"/link-social", {
98
98
  nonce: z.ZodOptional<z.ZodString>;
99
99
  accessToken: z.ZodOptional<z.ZodString>;
100
100
  refreshToken: z.ZodOptional<z.ZodString>;
101
- scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
102
101
  }, z.core.$strip>>;
103
102
  requestSignUp: z.ZodOptional<z.ZodBoolean>;
104
103
  scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -266,7 +265,7 @@ declare const getAccessToken: better_call0.StrictEndpoint<"/get-access-token", {
266
265
  }, {
267
266
  accessToken: string;
268
267
  accessTokenExpiresAt: Date | undefined;
269
- grantedScopes: string[];
268
+ scopes: string[];
270
269
  idToken: string | undefined;
271
270
  }>;
272
271
  declare const refreshToken: better_call0.StrictEndpoint<"/refresh-token", {
@@ -307,12 +306,6 @@ declare const refreshToken: better_call0.StrictEndpoint<"/refresh-token", {
307
306
  type: string;
308
307
  format: string;
309
308
  };
310
- grantedScopes: {
311
- type: string;
312
- items: {
313
- type: string;
314
- };
315
- };
316
309
  };
317
310
  };
318
311
  };
@@ -329,7 +322,7 @@ declare const refreshToken: better_call0.StrictEndpoint<"/refresh-token", {
329
322
  refreshToken: string;
330
323
  accessTokenExpiresAt: Date | undefined;
331
324
  refreshTokenExpiresAt: Date | null | undefined;
332
- grantedScopes: string[];
325
+ scope: string | null | undefined;
333
326
  idToken: string | null | undefined;
334
327
  providerId: string;
335
328
  accountId: string;
@@ -1,20 +1,22 @@
1
1
  import { shouldBindAccountCookieToSessionUser } from "../../context/store-capabilities.mjs";
2
2
  import { parseAccountOutput } from "../../db/schema.mjs";
3
- import { generateRandomString } from "../../crypto/random.mjs";
4
- import { getAccountCookie } from "../../cookies/session-store.mjs";
3
+ import { getAccountCookie, setAccountCookie } from "../../cookies/session-store.mjs";
5
4
  import { getAwaitableValue } from "../../context/helpers.mjs";
6
5
  import { missingEmailLogMessage } from "../../oauth2/errors.mjs";
7
- import { decryptOAuthToken } from "../../oauth2/token-encryption.mjs";
8
- import { persistOAuthAccount } from "../../oauth2/persist-account.mjs";
9
- import { applyUpdateUserInfoOnLink } from "../../oauth2/resolve-account.mjs";
6
+ import { decryptOAuthToken, getOAuthCallbackPath, setTokenUtil } from "../../oauth2/utils.mjs";
7
+ import { applyUpdateUserInfoOnLink } from "../../oauth2/link-account.mjs";
10
8
  import { generateIdTokenNonce, generateState } from "../../oauth2/state.mjs";
11
9
  import { freshSessionMiddleware, getSessionFromCtx, isStateful, sessionMiddleware } from "./session.mjs";
12
10
  import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
13
- import { additionalAuthorizationParamsSchema, readGrantedScopes, supportsIdTokenSignIn, verifyProviderIdToken } from "@better-auth/core/oauth2";
11
+ import { additionalAuthorizationParamsSchema, supportsIdTokenSignIn, verifyProviderIdToken } from "@better-auth/core/oauth2";
14
12
  import { SocialProviderListEnum } from "@better-auth/core/social-providers";
15
13
  import { createAuthEndpoint } from "@better-auth/core/api";
16
14
  import * as z from "zod";
17
15
  //#region src/api/routes/account.ts
16
+ function parseStoredScopes(scope) {
17
+ if (!scope) return [];
18
+ return scope.split(",").map((s) => s.trim()).filter(Boolean);
19
+ }
18
20
  const listUserAccounts = createAuthEndpoint("/list-accounts", {
19
21
  method: "GET",
20
22
  use: [sessionMiddleware],
@@ -40,7 +42,7 @@ const listUserAccounts = createAuthEndpoint("/list-accounts", {
40
42
  },
41
43
  accountId: { type: "string" },
42
44
  userId: { type: "string" },
43
- grantedScopes: {
45
+ scopes: {
44
46
  type: "array",
45
47
  items: { type: "string" }
46
48
  }
@@ -52,7 +54,7 @@ const listUserAccounts = createAuthEndpoint("/list-accounts", {
52
54
  "updatedAt",
53
55
  "accountId",
54
56
  "userId",
55
- "grantedScopes"
57
+ "scopes"
56
58
  ]
57
59
  }
58
60
  } } }
@@ -62,10 +64,10 @@ const listUserAccounts = createAuthEndpoint("/list-accounts", {
62
64
  const session = c.context.session;
63
65
  const accounts = await c.context.internalAdapter.findAccounts(session.user.id);
64
66
  return c.json(accounts.map((a) => {
65
- const { grantedScopes, ...parsed } = parseAccountOutput(c.context.options, a);
67
+ const { scope, ...parsed } = parseAccountOutput(c.context.options, a);
66
68
  return {
67
69
  ...parsed,
68
- grantedScopes: readGrantedScopes(grantedScopes)
70
+ scopes: parseStoredScopes(scope)
69
71
  };
70
72
  }));
71
73
  });
@@ -79,8 +81,7 @@ const linkSocialAccount = createAuthEndpoint("/link-social", {
79
81
  token: z.string(),
80
82
  nonce: z.string().optional(),
81
83
  accessToken: z.string().optional(),
82
- refreshToken: z.string().optional(),
83
- scopes: z.array(z.string()).optional()
84
+ refreshToken: z.string().optional()
84
85
  }).optional(),
85
86
  requestSignUp: z.boolean().optional(),
86
87
  scopes: z.array(z.string()).meta({ description: "Additional scopes to request from the provider" }).optional(),
@@ -158,18 +159,15 @@ const linkSocialAccount = createAuthEndpoint("/link-social", {
158
159
  code: "LINKING_DIFFERENT_EMAILS_NOT_ALLOWED"
159
160
  });
160
161
  try {
161
- await persistOAuthAccount(c, {
162
+ await c.context.internalAdapter.createAccount({
162
163
  userId: session.user.id,
163
164
  providerId: provider.id,
164
165
  accountId: linkingUserId,
165
- tokens: {
166
- accessToken: c.body.idToken.accessToken,
167
- refreshToken: c.body.idToken.refreshToken,
168
- idToken: token
169
- },
170
- mode: "link"
166
+ accessToken: c.body.idToken.accessToken,
167
+ idToken: token,
168
+ refreshToken: c.body.idToken.refreshToken
171
169
  });
172
- } catch (_e) {
170
+ } catch {
173
171
  throw APIError.from("EXPECTATION_FAILED", {
174
172
  message: "Account not linked - unable to create account",
175
173
  code: "LINKING_FAILED"
@@ -182,29 +180,24 @@ const linkSocialAccount = createAuthEndpoint("/link-social", {
182
180
  redirect: false
183
181
  });
184
182
  }
185
- const stateNonce = generateRandomString(32);
186
- const codeVerifier = generateRandomString(128);
187
183
  const idTokenNonce = generateIdTokenNonce(provider);
188
- const { url, requestedScopes } = await provider.createAuthorizationURL({
189
- state: stateNonce,
190
- codeVerifier,
191
- idTokenNonce,
192
- redirectURI: `${c.context.baseURL}${provider.callbackPath}`,
193
- scopes: c.body.scopes,
194
- loginHint: c.body.loginHint,
195
- additionalParams: c.body.additionalParams
196
- });
197
- await generateState(c, {
184
+ const state = await generateState(c, {
198
185
  link: {
199
186
  userId: session.user.id,
200
187
  email: session.user.email
201
188
  },
202
189
  additionalData: c.body.additionalData,
203
- requestedScopes,
204
- state: stateNonce,
205
- codeVerifier,
206
190
  idTokenNonce
207
191
  });
192
+ const url = await provider.createAuthorizationURL({
193
+ state: state.state,
194
+ codeVerifier: state.codeVerifier,
195
+ idTokenNonce,
196
+ redirectURI: `${c.context.baseURL}${getOAuthCallbackPath(provider)}`,
197
+ scopes: c.body.scopes,
198
+ loginHint: c.body.loginHint,
199
+ additionalParams: c.body.additionalParams
200
+ });
208
201
  if (!c.body.disableRedirect) c.setHeader("Location", url.toString());
209
202
  return c.json({
210
203
  url: url.toString(),
@@ -276,7 +269,7 @@ async function getValidAccessToken(ctx, { resolvedUserId, providerId, accountId,
276
269
  const provider = await getAwaitableValue(ctx.context.socialProviders, { value: providerId });
277
270
  if (!provider) throw APIError.from("BAD_REQUEST", {
278
271
  message: `Provider ${providerId} is not supported.`,
279
- code: BASE_ERROR_CODES.PROVIDER_NOT_SUPPORTED.code
272
+ code: "PROVIDER_NOT_SUPPORTED"
280
273
  });
281
274
  let account = resolvedAccount;
282
275
  if (!account) {
@@ -295,12 +288,18 @@ async function getValidAccessToken(ctx, { resolvedUserId, providerId, accountId,
295
288
  if (account.refreshToken && accessTokenExpired && provider.refreshAccessToken) {
296
289
  const refreshToken = await decryptOAuthToken(account.refreshToken, ctx.context);
297
290
  newTokens = await provider.refreshAccessToken(refreshToken, ctx);
298
- await persistOAuthAccount(ctx, {
299
- userId: account.userId,
300
- providerId: account.providerId,
301
- accountId: account.accountId,
302
- tokens: newTokens,
303
- mode: "refresh"
291
+ const updatedData = {
292
+ accessToken: await setTokenUtil(newTokens?.accessToken, ctx.context),
293
+ accessTokenExpiresAt: newTokens?.accessTokenExpiresAt,
294
+ refreshToken: newTokens?.refreshToken ? await setTokenUtil(newTokens.refreshToken, ctx.context) : account.refreshToken,
295
+ refreshTokenExpiresAt: newTokens?.refreshTokenExpiresAt ?? account.refreshTokenExpiresAt,
296
+ idToken: newTokens?.idToken || account.idToken
297
+ };
298
+ let updatedAccount = null;
299
+ if (account.id) updatedAccount = await ctx.context.internalAdapter.updateAccount(account.id, updatedData);
300
+ if (ctx.context.options.account?.storeAccountCookie) await setAccountCookie(ctx, {
301
+ ...account,
302
+ ...updatedAccount ?? updatedData
304
303
  });
305
304
  }
306
305
  const accessTokenExpiresAt = (() => {
@@ -316,12 +315,14 @@ async function getValidAccessToken(ctx, { resolvedUserId, providerId, accountId,
316
315
  return {
317
316
  accessToken: newTokens?.accessToken ?? await decryptOAuthToken(account.accessToken ?? "", ctx.context),
318
317
  accessTokenExpiresAt,
319
- grantedScopes: readGrantedScopes(account.grantedScopes),
318
+ scopes: parseStoredScopes(account.scope),
320
319
  idToken: newTokens?.idToken ?? account.idToken ?? void 0
321
320
  };
322
- } catch (error) {
323
- ctx.context.logger.error("Failed to get a valid access token", error);
324
- throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.FAILED_TO_GET_ACCESS_TOKEN);
321
+ } catch (_error) {
322
+ throw APIError.from("BAD_REQUEST", {
323
+ message: "Failed to get a valid access token",
324
+ code: "FAILED_TO_GET_ACCESS_TOKEN"
325
+ });
325
326
  }
326
327
  }
327
328
  const getAccessToken = createAuthEndpoint("/get-access-token", {
@@ -387,10 +388,6 @@ const refreshToken = createAuthEndpoint("/refresh-token", {
387
388
  refreshTokenExpiresAt: {
388
389
  type: "string",
389
390
  format: "date-time"
390
- },
391
- grantedScopes: {
392
- type: "array",
393
- items: { type: "string" }
394
391
  }
395
392
  }
396
393
  } } }
@@ -404,11 +401,11 @@ const refreshToken = createAuthEndpoint("/refresh-token", {
404
401
  const provider = await getAwaitableValue(ctx.context.socialProviders, { value: providerId });
405
402
  if (!provider) throw APIError.from("BAD_REQUEST", {
406
403
  message: `Provider ${providerId} is not supported.`,
407
- code: BASE_ERROR_CODES.PROVIDER_NOT_SUPPORTED.code
404
+ code: "PROVIDER_NOT_SUPPORTED"
408
405
  });
409
406
  if (!provider.refreshAccessToken) throw APIError.from("BAD_REQUEST", {
410
407
  message: `Provider ${providerId} does not support token refreshing.`,
411
- code: BASE_ERROR_CODES.TOKEN_REFRESH_NOT_SUPPORTED.code
408
+ code: "TOKEN_REFRESH_NOT_SUPPORTED"
412
409
  });
413
410
  let account = void 0;
414
411
  const accountData = await getAccountCookie(ctx);
@@ -420,30 +417,50 @@ const refreshToken = createAuthEndpoint("/refresh-token", {
420
417
  else account = (await ctx.context.internalAdapter.findAccounts(resolvedUserId)).find((acc) => accountId ? acc.accountId === accountId && acc.providerId === providerId : acc.providerId === providerId);
421
418
  if (!account) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.ACCOUNT_NOT_FOUND);
422
419
  const refreshToken = account.refreshToken ?? void 0;
423
- if (!refreshToken) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.REFRESH_TOKEN_NOT_FOUND);
420
+ if (!refreshToken) throw APIError.from("BAD_REQUEST", {
421
+ message: "Refresh token not found",
422
+ code: "REFRESH_TOKEN_NOT_FOUND"
423
+ });
424
424
  try {
425
425
  const decryptedRefreshToken = await decryptOAuthToken(refreshToken, ctx.context);
426
426
  const tokens = await provider.refreshAccessToken(decryptedRefreshToken, ctx);
427
- await persistOAuthAccount(ctx, {
428
- userId: account.userId,
429
- providerId: account.providerId,
430
- accountId: account.accountId,
431
- tokens,
432
- mode: "refresh"
427
+ const resolvedRefreshToken = tokens.refreshToken ? await setTokenUtil(tokens.refreshToken, ctx.context) : refreshToken;
428
+ const resolvedRefreshTokenExpiresAt = tokens.refreshTokenExpiresAt ?? account.refreshTokenExpiresAt;
429
+ const updatedTokenData = {
430
+ accessToken: await setTokenUtil(tokens.accessToken, ctx.context),
431
+ refreshToken: resolvedRefreshToken,
432
+ accessTokenExpiresAt: tokens.accessTokenExpiresAt,
433
+ refreshTokenExpiresAt: resolvedRefreshTokenExpiresAt,
434
+ idToken: tokens.idToken || account.idToken
435
+ };
436
+ let updatedAccount = null;
437
+ if (account.id)
438
+ /**
439
+ * `scope` intentionally omitted. Refresh response may be narrower.
440
+ *
441
+ * @see {@link Account.scope}
442
+ */
443
+ updatedAccount = await ctx.context.internalAdapter.updateAccount(account.id, updatedTokenData);
444
+ if (accountData && providerId === accountData.providerId && ctx.context.options.account?.storeAccountCookie) await setAccountCookie(ctx, {
445
+ ...accountData,
446
+ ...updatedAccount ?? updatedTokenData
433
447
  });
448
+ const responseScope = updatedAccount?.scope ?? account.scope;
434
449
  return ctx.json({
435
450
  accessToken: tokens.accessToken,
436
451
  refreshToken: tokens.refreshToken ?? decryptedRefreshToken,
437
452
  accessTokenExpiresAt: tokens.accessTokenExpiresAt,
438
- refreshTokenExpiresAt: tokens.refreshTokenExpiresAt ?? account.refreshTokenExpiresAt,
439
- grantedScopes: readGrantedScopes(account.grantedScopes),
453
+ refreshTokenExpiresAt: resolvedRefreshTokenExpiresAt,
454
+ scope: responseScope,
440
455
  idToken: tokens.idToken || account.idToken,
441
456
  providerId: account.providerId,
442
457
  accountId: account.accountId
443
458
  });
444
- } catch (error) {
445
- ctx.context.logger.error("Failed to refresh access token", error);
446
- throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.FAILED_TO_REFRESH_ACCESS_TOKEN);
459
+ } catch (_error) {
460
+ throw APIError.from("BAD_REQUEST", {
461
+ message: "Failed to refresh access token",
462
+ code: "FAILED_TO_REFRESH_ACCESS_TOKEN"
463
+ });
447
464
  }
448
465
  });
449
466
  const accountInfoQuerySchema = z.optional(z.object({
@@ -1,14 +1,13 @@
1
1
  import { isAPIError } from "../../utils/is-api-error.mjs";
2
- import { generateRandomString } from "../../crypto/random.mjs";
3
2
  import { setSessionCookie } from "../../cookies/index.mjs";
4
3
  import { assertValidUserInfo } from "../../utils/validate-user-info.mjs";
5
4
  import { getAwaitableValue } from "../../context/helpers.mjs";
6
5
  import { OAUTH_CALLBACK_ERROR_CODES, missingEmailLogMessage } from "../../oauth2/errors.mjs";
7
- import { persistOAuthAccount } from "../../oauth2/persist-account.mjs";
8
- import { applyUpdateUserInfoOnLink } from "../../oauth2/resolve-account.mjs";
6
+ import { getOAuthCallbackPath, setTokenUtil } from "../../oauth2/utils.mjs";
7
+ import { applyUpdateUserInfoOnLink, handleOAuthUserInfo } from "../../oauth2/link-account.mjs";
9
8
  import { generateIdTokenNonce, generateState, parseState } from "../../oauth2/state.mjs";
10
- import { signInWithOAuthIdentity } from "../../oauth2/sign-in-with-oauth-identity.mjs";
11
9
  import { HIDE_METADATA } from "../../utils/hide-metadata.mjs";
10
+ import { mergeScopes } from "@better-auth/core/oauth2";
12
11
  import { safeJSONParse } from "@better-auth/core/utils/json";
13
12
  import { createAuthEndpoint } from "@better-auth/core/api";
14
13
  import * as z from "zod";
@@ -58,20 +57,13 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
58
57
  if (state === void 0 && code) {
59
58
  const provider = await getAwaitableValue(c.context.socialProviders, { value: c.params.id });
60
59
  if (provider?.allowIdpInitiated) {
61
- const state = generateRandomString(32);
62
- const codeVerifier = generateRandomString(128);
63
60
  const idTokenNonce = generateIdTokenNonce(provider);
64
- const { url: authUrl, requestedScopes } = await provider.createAuthorizationURL({
65
- state,
61
+ const { state: freshState, codeVerifier } = await generateState(c, { idTokenNonce });
62
+ const authUrl = await provider.createAuthorizationURL({
63
+ state: freshState,
66
64
  codeVerifier,
67
65
  idTokenNonce,
68
- redirectURI: `${c.context.baseURL}${provider.callbackPath}`
69
- });
70
- await generateState(c, {
71
- requestedScopes,
72
- state,
73
- codeVerifier,
74
- idTokenNonce
66
+ redirectURI: `${c.context.baseURL}${getOAuthCallbackPath(provider)}`
75
67
  });
76
68
  throw c.redirect(authUrl.toString());
77
69
  }
@@ -81,7 +73,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
81
73
  const url = `${defaultErrorURL}${defaultErrorURL.includes("?") ? "&" : "?"}error=state_not_found`;
82
74
  throw c.redirect(url);
83
75
  }
84
- const { codeVerifier, callbackURL, link, errorURL, newUserURL, requestSignUp, requestedScopes, idTokenNonce } = await parseState(c);
76
+ const { codeVerifier, callbackURL, link, errorURL, newUserURL, requestSignUp, idTokenNonce } = await parseState(c);
85
77
  function redirectOnError(error, description) {
86
78
  const baseURL = errorURL ?? defaultErrorURL;
87
79
  const params = new URLSearchParams({ error });
@@ -116,7 +108,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
116
108
  code,
117
109
  codeVerifier,
118
110
  deviceId: device_id,
119
- redirectURI: `${c.context.baseURL}${provider.callbackPath}`
111
+ redirectURI: `${c.context.baseURL}${getOAuthCallbackPath(provider)}`
120
112
  });
121
113
  } catch (e) {
122
114
  c.context.logger.error("", e);
@@ -166,15 +158,26 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
166
158
  }
167
159
  if (userInfo.email?.toLowerCase() !== link.email.toLowerCase() && c.context.options.account?.accountLinking?.allowDifferentEmails !== true) return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.EMAIL_DOES_NOT_MATCH);
168
160
  const existingAccount = await c.context.internalAdapter.findAccountByProviderId(providerAccountId, provider.id);
169
- if (existingAccount && existingAccount.userId.toString() !== link.userId.toString()) return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.ACCOUNT_ALREADY_LINKED_TO_DIFFERENT_USER);
170
- if (!await persistOAuthAccount(c, {
161
+ if (existingAccount) {
162
+ if (existingAccount.userId.toString() !== link.userId.toString()) return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.ACCOUNT_ALREADY_LINKED_TO_DIFFERENT_USER);
163
+ const mergedScope = mergeScopes(existingAccount.scope, tokens.scopes);
164
+ const updateData = Object.fromEntries(Object.entries({
165
+ accessToken: await setTokenUtil(tokens.accessToken, c.context),
166
+ refreshToken: await setTokenUtil(tokens.refreshToken, c.context),
167
+ idToken: tokens.idToken,
168
+ accessTokenExpiresAt: tokens.accessTokenExpiresAt,
169
+ refreshTokenExpiresAt: tokens.refreshTokenExpiresAt,
170
+ scope: mergedScope || void 0
171
+ }).filter(([_, value]) => value !== void 0));
172
+ await c.context.internalAdapter.updateAccount(existingAccount.id, updateData);
173
+ } else if (!await c.context.internalAdapter.createAccount({
171
174
  userId: link.userId,
172
175
  providerId: provider.id,
173
176
  accountId: providerAccountId,
174
- tokens,
175
- requestedScopes,
176
- mode: "link",
177
- grantAuthority: provider.grantAuthority
177
+ ...tokens,
178
+ accessToken: await setTokenUtil(tokens.accessToken, c.context),
179
+ refreshToken: await setTokenUtil(tokens.refreshToken, c.context),
180
+ scope: tokens.scopes?.join(",")
178
181
  })) return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.UNABLE_TO_LINK_ACCOUNT);
179
182
  await applyUpdateUserInfoOnLink(c, link.userId, userInfo);
180
183
  let toRedirectTo;
@@ -189,19 +192,22 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
189
192
  c.context.logger.error(missingEmailLogMessage(provider.id));
190
193
  return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.EMAIL_NOT_FOUND);
191
194
  }
195
+ const accountData = {
196
+ providerId: provider.id,
197
+ accountId: providerAccountId,
198
+ ...tokens,
199
+ scope: tokens.scopes?.join(",")
200
+ };
192
201
  let result;
193
202
  try {
194
- result = await signInWithOAuthIdentity(c, {
203
+ result = await handleOAuthUserInfo(c, {
195
204
  userInfo: {
196
205
  ...userInfo,
197
206
  id: providerAccountId,
198
207
  email: userInfo.email,
199
208
  name: userInfo.name || ""
200
209
  },
201
- providerId: provider.id,
202
- accountId: providerAccountId,
203
- tokens,
204
- requestedScopes,
210
+ account: accountData,
205
211
  callbackURL,
206
212
  disableSignUp: provider.disableImplicitSignUp && !requestSignUp || provider.options?.disableSignUp,
207
213
  overrideUserInfo: provider.options?.overrideUserInfoOnSignIn,
@@ -211,8 +217,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
211
217
  providerId: provider.id,
212
218
  profile: providerResult.data
213
219
  }
214
- },
215
- grantAuthority: provider.grantAuthority
220
+ }
216
221
  });
217
222
  } catch (e) {
218
223
  if (isAPIError(e) && e.body?.code) redirectOnError(e.body.code, e.body.message);
@@ -16,7 +16,6 @@ declare const socialSignInBodySchema: z.ZodObject<{
16
16
  accessToken: z.ZodOptional<z.ZodString>;
17
17
  refreshToken: z.ZodOptional<z.ZodString>;
18
18
  expiresAt: z.ZodOptional<z.ZodNumber>;
19
- scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
20
19
  user: z.ZodOptional<z.ZodObject<{
21
20
  name: z.ZodOptional<z.ZodObject<{
22
21
  firstName: z.ZodOptional<z.ZodString>;
@@ -46,7 +45,6 @@ declare const signInSocial: <O extends BetterAuthOptions>() => better_call0.Stri
46
45
  accessToken: z.ZodOptional<z.ZodString>;
47
46
  refreshToken: z.ZodOptional<z.ZodString>;
48
47
  expiresAt: z.ZodOptional<z.ZodNumber>;
49
- scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
50
48
  user: z.ZodOptional<z.ZodObject<{
51
49
  name: z.ZodOptional<z.ZodObject<{
52
50
  firstName: z.ZodOptional<z.ZodString>;