better-auth 1.7.10 → 1.7.12

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.
@@ -88,7 +88,7 @@ function createDatabaseStorageWrapper(ctx) {
88
88
  value: key
89
89
  }]
90
90
  }))[0];
91
- if (typeof data?.lastRequest === "bigint") data.lastRequest = Number(data.lastRequest);
91
+ if (data) data.lastRequest = Number(data.lastRequest);
92
92
  return data;
93
93
  };
94
94
  const consume = async (key, rule) => {
@@ -1,13 +1,12 @@
1
1
  import { isAPIError } from "../../utils/is-api-error.mjs";
2
2
  import { setSessionCookie } from "../../cookies/index.mjs";
3
3
  import { getAwaitableValue } from "../../context/helpers.mjs";
4
- import { resolveOAuthAccountKey, toOAuthProfileRecord } from "../../oauth2/account-key.mjs";
5
4
  import { OAUTH_CALLBACK_ERROR_CODES, missingEmailLogMessage } from "../../oauth2/errors.mjs";
6
5
  import { getOAuthCallbackPath } from "../../oauth2/utils.mjs";
7
6
  import { handleOAuthUserInfo, linkOAuthAccount } from "../../oauth2/link-account.mjs";
8
7
  import { generateIdTokenNonce, generateState, parseState } from "../../oauth2/state.mjs";
8
+ import { exchangeOAuthCallback } from "../../oauth2/callback.mjs";
9
9
  import { HIDE_METADATA } from "../../utils/hide-metadata.mjs";
10
- import { safeJSONParse } from "@better-auth/core/utils/json";
11
10
  import { appendQueryParams } from "@better-auth/core/utils/url";
12
11
  import { createAuthEndpoint } from "@better-auth/core/api";
13
12
  import * as z from "zod";
@@ -92,56 +91,16 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
92
91
  c.context.logger.warn("OAuth provider not found", { providerId: c.params.id });
93
92
  throw redirectOnError(OAUTH_CALLBACK_ERROR_CODES.PROVIDER_NOT_FOUND);
94
93
  }
95
- if (iss && provider.issuer && iss !== provider.issuer) {
96
- c.context.logger.error("OAuth issuer mismatch", {
97
- expected: provider.issuer,
98
- received: iss
99
- });
100
- throw redirectOnError(OAUTH_CALLBACK_ERROR_CODES.ISSUER_MISMATCH);
101
- }
102
- if (provider.requiresIdTokenNonce && !idTokenNonce) {
103
- c.context.logger.error("OAuth id_token nonce binding required but no expected nonce was found in state", { providerId: provider.id });
104
- throw redirectOnError(OAUTH_CALLBACK_ERROR_CODES.NONCE_BINDING_MISSING);
105
- }
106
- let tokens;
107
- try {
108
- tokens = await provider.validateAuthorizationCode({
109
- code,
110
- codeVerifier,
111
- deviceId: device_id,
112
- redirectURI: `${c.context.baseURL}${getOAuthCallbackPath(provider)}`
113
- });
114
- } catch (e) {
115
- c.context.logger.error("", e);
116
- throw redirectOnError(OAUTH_CALLBACK_ERROR_CODES.INVALID_CODE);
117
- }
118
- if (!tokens) throw redirectOnError(OAUTH_CALLBACK_ERROR_CODES.INVALID_CODE);
119
- const parsedUserData = userData ? safeJSONParse(userData) : null;
120
- const providerResult = await provider.getUserInfo({
121
- ...tokens,
122
- ...idTokenNonce ? { expectedIdTokenNonce: idTokenNonce } : {},
123
- /**
124
- * The user object from the provider
125
- * This is only available for some providers like Apple
126
- */
127
- user: parsedUserData ?? void 0
94
+ const exchanged = await exchangeOAuthCallback(c, provider, {
95
+ code,
96
+ codeVerifier,
97
+ iss,
98
+ deviceId: device_id,
99
+ user: userData,
100
+ idTokenNonce
128
101
  });
129
- if (!providerResult?.user) {
130
- c.context.logger.error("Unable to get user info");
131
- return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.UNABLE_TO_GET_USER_INFO);
132
- }
133
- const userInfo = providerResult.user;
134
- const providerProfile = toOAuthProfileRecord(providerResult.data);
135
- let accountKey;
136
- try {
137
- accountKey = await resolveOAuthAccountKey(provider, tokens, providerResult.data);
138
- } catch (error) {
139
- c.context.logger.error("Unable to derive provider account identity", {
140
- providerId: provider.id,
141
- error
142
- });
143
- return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.UNABLE_TO_GET_USER_INFO);
144
- }
102
+ if (exchanged.error) throw redirectOnError(exchanged.error);
103
+ const { tokens, userInfo, providerProfile, accountKey } = exchanged;
145
104
  const { accountId } = accountKey;
146
105
  if (!callbackURL) {
147
106
  c.context.logger.error("No callback URL found");
@@ -229,7 +229,7 @@ const getSession = () => createAuthEndpoint("/get-session", {
229
229
  } catch (error) {
230
230
  if (isAPIError(error)) throw error;
231
231
  ctx.context.logger.error("INTERNAL_SERVER_ERROR", error);
232
- throw APIError.from("INTERNAL_SERVER_ERROR", BASE_ERROR_CODES.FAILED_TO_GET_SESSION);
232
+ throw APIError.from("INTERNAL_SERVER_ERROR", BASE_ERROR_CODES.FAILED_TO_GET_SESSION, { cause: error });
233
233
  }
234
234
  });
235
235
  /**
@@ -16,6 +16,7 @@ const map = {
16
16
  ],
17
17
  number: [
18
18
  "int4",
19
+ "int8",
19
20
  "integer",
20
21
  "bigint",
21
22
  "smallint",
@@ -0,0 +1,68 @@
1
+ import { resolveOAuthAccountKey, toOAuthProfileRecord } from "./account-key.mjs";
2
+ import { OAUTH_CALLBACK_ERROR_CODES } from "./errors.mjs";
3
+ import { getOAuthCallbackPath } from "./utils.mjs";
4
+ import { safeJSONParse } from "@better-auth/core/utils/json";
5
+ //#region src/oauth2/callback.ts
6
+ /**
7
+ * Turns a provider callback into a verified identity. It checks that the
8
+ * callback answers this authorization request, exchanges the code, and
9
+ * resolves the user and account key.
10
+ *
11
+ * Every route that completes a provider callback goes through here, so the
12
+ * routes run the same checks.
13
+ */
14
+ async function exchangeOAuthCallback(ctx, provider, callback) {
15
+ if (callback.iss && provider.issuer && callback.iss !== provider.issuer) {
16
+ ctx.context.logger.error("OAuth issuer mismatch", {
17
+ expected: provider.issuer,
18
+ received: callback.iss
19
+ });
20
+ return { error: OAUTH_CALLBACK_ERROR_CODES.ISSUER_MISMATCH };
21
+ }
22
+ if (provider.requiresIdTokenNonce && !callback.idTokenNonce) {
23
+ ctx.context.logger.error("OAuth id_token nonce binding required but no expected nonce was found in state", { providerId: provider.id });
24
+ return { error: OAUTH_CALLBACK_ERROR_CODES.NONCE_BINDING_MISSING };
25
+ }
26
+ let tokens;
27
+ try {
28
+ tokens = await provider.validateAuthorizationCode({
29
+ code: callback.code,
30
+ codeVerifier: callback.codeVerifier,
31
+ deviceId: callback.deviceId,
32
+ redirectURI: `${ctx.context.baseURL}${getOAuthCallbackPath(provider)}`
33
+ });
34
+ } catch (error) {
35
+ ctx.context.logger.error("Failed to validate authorization code", error);
36
+ return { error: OAUTH_CALLBACK_ERROR_CODES.INVALID_CODE };
37
+ }
38
+ if (!tokens) return { error: OAUTH_CALLBACK_ERROR_CODES.INVALID_CODE };
39
+ const result = await provider.getUserInfo({
40
+ ...tokens,
41
+ expectedIdTokenNonce: callback.idTokenNonce,
42
+ /**
43
+ * The user object from the provider
44
+ * This is only available for some providers like Apple
45
+ */
46
+ user: safeJSONParse(callback.user) ?? void 0
47
+ });
48
+ if (!result?.user) {
49
+ ctx.context.logger.error("Unable to get user info");
50
+ return { error: OAUTH_CALLBACK_ERROR_CODES.UNABLE_TO_GET_USER_INFO };
51
+ }
52
+ const accountKey = await resolveOAuthAccountKey(provider, tokens, result.data).catch((error) => {
53
+ ctx.context.logger.error("Unable to derive provider account identity", {
54
+ providerId: provider.id,
55
+ error
56
+ });
57
+ return null;
58
+ });
59
+ if (!accountKey) return { error: OAUTH_CALLBACK_ERROR_CODES.UNABLE_TO_GET_USER_INFO };
60
+ return {
61
+ tokens,
62
+ userInfo: result.user,
63
+ providerProfile: toOAuthProfileRecord(result.data),
64
+ accountKey
65
+ };
66
+ }
67
+ //#endregion
68
+ export { exchangeOAuthCallback };
package/dist/package.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.7.10";
2
+ var version = "1.7.12";
3
3
  //#endregion
4
4
  export { version };
@@ -1,5 +1,16 @@
1
1
  import { getCurrentAdapter } from "@better-auth/core/context";
2
2
  //#region src/plugins/jwt/adapter.ts
3
+ /**
4
+ * Algorithm of the key the plugin generates when `jwks.keyPairConfig` is unset.
5
+ */
6
+ const DEFAULT_JWK_ALG = "EdDSA";
7
+ /**
8
+ * Algorithm for new keys, and for stored keys saved before the `alg` column
9
+ * existed.
10
+ */
11
+ function getDefaultKeyAlg(options) {
12
+ return options?.jwks?.keyPairConfig?.alg ?? "EdDSA";
13
+ }
3
14
  const getJwksAdapter = (baseAdapter, options) => {
4
15
  return {
5
16
  getAllKeys: async (ctx) => {
@@ -38,7 +49,7 @@ const getJwksAdapter = (baseAdapter, options) => {
38
49
  *
39
50
  * Legacy rows persisted before the `alg` column existed have
40
51
  * `alg: null`. Per `schema.ts`, those rows are treated as the
41
- * configured default alg (`options.jwks.keyPairConfig.alg ?? "EdDSA"`),
52
+ * configured default alg ({@link getDefaultKeyAlg}),
42
53
  * so deployments that have only legacy rows can still satisfy an
43
54
  * audience-pinned `signingAlgorithm` matching the default.
44
55
  *
@@ -48,7 +59,7 @@ const getJwksAdapter = (baseAdapter, options) => {
48
59
  getLatestKeyByAlg: async (ctx, alg) => {
49
60
  const candidates = options?.adapter?.getJwks ? await options.adapter.getJwks(ctx) : await (await getCurrentAdapter(baseAdapter)).findMany({ model: "jwks" });
50
61
  if (!candidates) return void 0;
51
- const configAlg = options?.jwks?.keyPairConfig?.alg ?? "EdDSA";
62
+ const configAlg = getDefaultKeyAlg(options);
52
63
  const now = /* @__PURE__ */ new Date();
53
64
  return candidates.filter((k) => k.alg === alg || k.alg == null && configAlg === alg).filter((k) => !k.expiresAt || k.expiresAt > now).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())[0];
54
65
  },
@@ -65,4 +76,4 @@ const getJwksAdapter = (baseAdapter, options) => {
65
76
  };
66
77
  };
67
78
  //#endregion
68
- export { getJwksAdapter };
79
+ export { DEFAULT_JWK_ALG, getDefaultKeyAlg, getJwksAdapter };
@@ -1,5 +1,5 @@
1
1
  import { SESSION_COOKIE_JWT_AUDIENCE, SESSION_COOKIE_JWT_TYPE, getSessionCookieJwtVerifyOptions, parseSessionCookieJwtPayload } from "../../cookies/jwt.mjs";
2
- import { getJwksAdapter } from "./adapter.mjs";
2
+ import { getDefaultKeyAlg, getJwksAdapter } from "./adapter.mjs";
3
3
  import { resolveSigningKey } from "./sign.mjs";
4
4
  import { BetterAuthError } from "@better-auth/core/error";
5
5
  import { SignJWT, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
@@ -9,8 +9,7 @@ function getCookieCacheJwtIssuer(ctx) {
9
9
  return typeof baseURL === "string" ? baseURL || "better-auth:session-cache" : ctx.context.baseURL || "better-auth:session-cache";
10
10
  }
11
11
  async function importLocalPublicKey(ctx, token, options) {
12
- const header = decodeProtectedHeader(token);
13
- const kid = header.kid;
12
+ const kid = decodeProtectedHeader(token).kid;
14
13
  if (!kid) {
15
14
  ctx.context.logger.debug("Cookie-cache JWT missing kid in header");
16
15
  return null;
@@ -25,11 +24,7 @@ async function importLocalPublicKey(ctx, token, options) {
25
24
  ctx.context.logger.debug(`No JWKS key found for cookie-cache JWT kid: ${kid}`);
26
25
  return null;
27
26
  }
28
- const alg = key.alg ?? options?.jwks?.keyPairConfig?.alg ?? header.alg;
29
- if (!alg) {
30
- ctx.context.logger.debug(`No JWT algorithm available for cookie-cache JWT kid: ${kid}`);
31
- return null;
32
- }
27
+ const alg = key.alg ?? getDefaultKeyAlg(options);
33
28
  return {
34
29
  alg,
35
30
  publicKey: await importJWK(JSON.parse(key.publicKey), alg)
@@ -2,7 +2,7 @@ import { mergeSchema } from "../../db/schema.mjs";
2
2
  import { sessionMiddleware } from "../../api/routes/session.mjs";
3
3
  import { APIError as APIError$1 } from "../../api/index.mjs";
4
4
  import { PACKAGE_VERSION } from "../../version.mjs";
5
- import { getJwksAdapter } from "./adapter.mjs";
5
+ import { getDefaultKeyAlg, getJwksAdapter } from "./adapter.mjs";
6
6
  import { createJwk, generateExportedKeyPair, toExpJWT } from "./utils.mjs";
7
7
  import { getJwtToken, resolveSigningKey, signJWT } from "./sign.mjs";
8
8
  import { createCookieCacheSigner } from "./cookie-cache.mjs";
@@ -131,7 +131,7 @@ const jwt = (options) => {
131
131
  const defaultCrv = keyPairConfig ? "crv" in keyPairConfig ? keyPairConfig.crv : void 0 : void 0;
132
132
  return ctx.json({ keys: keys.map((keySet) => {
133
133
  return {
134
- alg: keySet.alg ?? options?.jwks?.keyPairConfig?.alg ?? "EdDSA",
134
+ alg: keySet.alg ?? getDefaultKeyAlg(options),
135
135
  crv: keySet.crv ?? defaultCrv,
136
136
  ...JSON.parse(keySet.publicKey),
137
137
  kid: keySet.id
@@ -1,5 +1,5 @@
1
1
  import { symmetricDecrypt } from "../../crypto/index.mjs";
2
- import { getJwksAdapter } from "./adapter.mjs";
2
+ import { getDefaultKeyAlg, getJwksAdapter } from "./adapter.mjs";
3
3
  import { createJwk, toExpJWT } from "./utils.mjs";
4
4
  import { BetterAuthError } from "@better-auth/core/error";
5
5
  import { SignJWT, importJWK } from "jose";
@@ -29,13 +29,13 @@ async function resolveSigningKey(ctx, options, overrides) {
29
29
  key = await adapter.getKeyById(ctx, overrides.signingKeyId);
30
30
  if (!key) throw new BetterAuthError(`signJWT: signingKeyId "${overrides.signingKeyId}" not found in JWKS. The key must be provisioned before it can be referenced.`);
31
31
  if (overrides.signingAlgorithm !== void 0) {
32
- const configAlg = options?.jwks?.keyPairConfig?.alg ?? "EdDSA";
32
+ const configAlg = getDefaultKeyAlg(options);
33
33
  if ((key.alg ?? configAlg) !== overrides.signingAlgorithm) throw new BetterAuthError(`signJWT: signingKeyId "${overrides.signingKeyId}" has alg "${key.alg ?? `unset (inherits keyPairConfig.alg "${configAlg}")`}" but signingAlgorithm was set to "${overrides.signingAlgorithm}".`);
34
34
  }
35
35
  } else if (overrides?.signingAlgorithm !== void 0) {
36
36
  key = await adapter.getLatestKeyByAlg(ctx, overrides.signingAlgorithm);
37
37
  if (!key) {
38
- const primaryAlg = options?.jwks?.keyPairConfig?.alg ?? "EdDSA";
38
+ const primaryAlg = getDefaultKeyAlg(options);
39
39
  const preconfig = options?.jwks?.keyPairConfigs?.find((c) => c.alg === overrides.signingAlgorithm);
40
40
  const isPrimary = primaryAlg === overrides.signingAlgorithm;
41
41
  if (preconfig || isPrimary) key = await createJwk(ctx, {
@@ -51,7 +51,7 @@ async function resolveSigningKey(ctx, options, overrides) {
51
51
  }
52
52
  }
53
53
  } else {
54
- const primaryAlg = options?.jwks?.keyPairConfig?.alg ?? "EdDSA";
54
+ const primaryAlg = getDefaultKeyAlg(options);
55
55
  key = await adapter.getLatestKeyByAlg(ctx, primaryAlg) ?? await adapter.getLatestKey(ctx);
56
56
  }
57
57
  if (!key || key.expiresAt && key.expiresAt < /* @__PURE__ */ new Date()) {
@@ -64,7 +64,7 @@ async function resolveSigningKey(ctx, options, overrides) {
64
64
  }).catch(() => {
65
65
  throw new BetterAuthError("Failed to decrypt private key. Make sure the secret currently in use is the same as the one used to encrypt the private key. If you are using a different secret, either clean up your JWKS or disable private key encryption.");
66
66
  }) : key.privateKey;
67
- const alg = key.alg ?? options?.jwks?.keyPairConfig?.alg ?? "EdDSA";
67
+ const alg = key.alg ?? getDefaultKeyAlg(options);
68
68
  const privateKey = await importJWK(JSON.parse(privateWebKey), alg);
69
69
  return {
70
70
  alg,
@@ -86,6 +86,7 @@ async function signJWT(ctx, config) {
86
86
  const defaultIss = options?.jwt?.issuer ?? baseURLOrigin;
87
87
  const aud = payload.aud;
88
88
  const defaultAud = options?.jwt?.audience ?? baseURLOrigin;
89
+ if (!(iss ?? defaultIss) || !(aud ?? defaultAud)) ctx.context.logger.warn("[better-auth] The JWT plugin signed a JWT without an issuer or audience because no base URL was resolved for this call. Set `baseURL`, or `jwt.issuer` and `jwt.audience` in the JWT plugin options.");
89
90
  if (options?.jwt?.sign) {
90
91
  const jwtPayload = {
91
92
  ...payload,
@@ -1,4 +1,4 @@
1
- import { getJwksAdapter } from "./adapter.mjs";
1
+ import { getDefaultKeyAlg, getJwksAdapter } from "./adapter.mjs";
2
2
  import { getCurrentAuthEndpointContext } from "@better-auth/core/context";
3
3
  import { base64 } from "@better-auth/utils/base64";
4
4
  import { importJWK, jwtVerify } from "jose";
@@ -28,11 +28,17 @@ async function verifyJWT(token, options) {
28
28
  ctx.context.logger.debug(`No JWKS key found for kid: ${kid}`);
29
29
  return null;
30
30
  }
31
- const cryptoKey = await importJWK(JSON.parse(key.publicKey), key.alg ?? options?.jwks?.keyPairConfig?.alg ?? "EdDSA");
31
+ const publicKey = JSON.parse(key.publicKey);
32
+ const alg = key.alg ?? getDefaultKeyAlg(options);
33
+ const cryptoKey = await importJWK(publicKey, alg);
32
34
  const baseURLOrigin = typeof ctx.context.options.baseURL === "string" ? ctx.context.options.baseURL : void 0;
35
+ const issuer = options?.jwt?.issuer ?? baseURLOrigin;
36
+ const audience = options?.jwt?.audience ?? baseURLOrigin;
37
+ if (!issuer || !audience) ctx.context.logger.warn("[better-auth] The JWT plugin is verifying a JWT without checking its issuer or audience because no base URL was resolved for this call. Set `baseURL`, or `jwt.issuer` and `jwt.audience` in the JWT plugin options.");
33
38
  const { payload } = await jwtVerify(token, cryptoKey, {
34
- issuer: options?.jwt?.issuer ?? baseURLOrigin,
35
- audience: options?.jwt?.audience ?? baseURLOrigin
39
+ issuer,
40
+ audience,
41
+ algorithms: [alg]
36
42
  });
37
43
  if (!payload.sub || !payload.aud) return null;
38
44
  return payload;
@@ -4,18 +4,16 @@ import { originCheck } from "../../api/middlewares/origin-check.mjs";
4
4
  import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
5
5
  import { symmetricDecrypt, symmetricEncrypt } from "../../crypto/index.mjs";
6
6
  import { setSessionCookie } from "../../cookies/index.mjs";
7
- import { resolveOAuthAccountKey, toOAuthProfileRecord } from "../../oauth2/account-key.mjs";
8
7
  import { redirectOnError } from "../../oauth2/errors.mjs";
9
- import { getOAuthCallbackPath } from "../../oauth2/utils.mjs";
10
8
  import { handleOAuthUserInfo, linkOAuthAccount } from "../../oauth2/link-account.mjs";
11
9
  import { setOAuthState } from "../../api/state/oauth.mjs";
12
10
  import { derivePurposeKey } from "../../crypto/purpose.mjs";
13
11
  import { getAuthStateVerificationIdentifier, parseGenericState } from "../../state.mjs";
12
+ import { exchangeOAuthCallback } from "../../oauth2/callback.mjs";
14
13
  import { PACKAGE_VERSION } from "../../version.mjs";
15
14
  import { parseJSON } from "../../client/parser.mjs";
16
15
  import { checkSkipProxy, resolveCurrentURL, stripTrailingSlash } from "./utils.mjs";
17
16
  import { accountSchema, userSchema } from "@better-auth/core/db";
18
- import { safeJSONParse } from "@better-auth/core/utils/json";
19
17
  import { defu } from "defu";
20
18
  import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
21
19
  import * as z from "zod";
@@ -63,7 +61,9 @@ const oauthProxyQuerySchema = z.object({
63
61
  const oauthCallbackQuerySchema = z.object({
64
62
  code: z.string().optional(),
65
63
  error: z.string().optional(),
66
- user: z.string().optional()
64
+ user: z.string().optional(),
65
+ iss: z.string().optional(),
66
+ device_id: z.string().optional()
67
67
  });
68
68
  const oAuthProxy = (opts) => {
69
69
  const maxAge = opts?.maxAge ?? 60;
@@ -242,7 +242,7 @@ const oAuthProxy = (opts) => {
242
242
  ctx.context.logger.warn("Invalid OAuth callback query", query.error);
243
243
  return;
244
244
  }
245
- const { code, error, user: userData } = query.data;
245
+ const { code, error, user: userData, iss, device_id: deviceId } = query.data;
246
246
  let stateData;
247
247
  try {
248
248
  stateData = parseJSON(await symmetricDecrypt({
@@ -269,47 +269,20 @@ const oAuthProxy = (opts) => {
269
269
  ctx.context.logger.warn("OAuth provider not found", { providerId });
270
270
  throw redirectOnError(ctx, errorURL, "oauth_provider_not_found");
271
271
  }
272
- let tokens;
273
- try {
274
- tokens = await provider.validateAuthorizationCode({
275
- code,
276
- codeVerifier: stateData.codeVerifier,
277
- redirectURI: `${ctx.context.baseURL}${getOAuthCallbackPath(provider)}`
278
- });
279
- } catch (e) {
280
- ctx.context.logger.error("Failed to validate authorization code", e);
281
- throw redirectOnError(ctx, errorURL, "invalid_code");
282
- }
283
- if (!tokens) throw redirectOnError(ctx, errorURL, "invalid_code");
284
- const parsedUserData = userData ? safeJSONParse(userData) : null;
285
- const userInfoResult = await provider.getUserInfo({
286
- ...tokens,
287
- /**
288
- * The user object from the provider
289
- * This is only available for some providers like Apple
290
- */
291
- user: parsedUserData ?? void 0
272
+ const exchanged = await exchangeOAuthCallback(ctx, provider, {
273
+ code,
274
+ codeVerifier: stateData.codeVerifier,
275
+ iss,
276
+ deviceId,
277
+ user: userData,
278
+ idTokenNonce: stateData.idTokenNonce
292
279
  });
293
- const userInfo = userInfoResult?.user;
294
- if (!userInfo) {
295
- ctx.context.logger.error("Unable to get user info from provider");
296
- throw redirectOnError(ctx, errorURL, "unable_to_get_user_info");
297
- }
280
+ if (exchanged.error) throw redirectOnError(ctx, errorURL, exchanged.error);
281
+ const { tokens, userInfo, providerProfile, accountKey } = exchanged;
298
282
  if (!userInfo.email) {
299
283
  ctx.context.logger.error("Provider did not return email");
300
284
  throw redirectOnError(ctx, errorURL, "email_not_found");
301
285
  }
302
- let accountKey;
303
- try {
304
- accountKey = await resolveOAuthAccountKey(provider, tokens, userInfoResult.data);
305
- } catch (error) {
306
- ctx.context.logger.error("Unable to derive provider account identity", {
307
- providerId: provider.id,
308
- error
309
- });
310
- throw redirectOnError(ctx, errorURL, "unable_to_get_user_info");
311
- }
312
- const providerProfile = toOAuthProfileRecord(userInfoResult.data);
313
286
  const proxyCallbackURL = new URL(stateData.callbackURL);
314
287
  const finalCallbackURL = proxyCallbackURL.searchParams.get("callbackURL") || stateData.callbackURL;
315
288
  const payload = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-auth",
3
- "version": "1.7.10",
3
+ "version": "1.7.12",
4
4
  "description": "The most comprehensive authentication framework for TypeScript.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -497,19 +497,19 @@
497
497
  "@better-fetch/fetch": "1.3.2",
498
498
  "@noble/ciphers": "^2.2.0",
499
499
  "@noble/hashes": "^2.2.0",
500
- "better-call": "1.4.2",
500
+ "better-call": "1.4.4",
501
501
  "defu": "^6.1.4",
502
502
  "jose": "^6.2.3",
503
503
  "kysely": "^0.28.17 || ^0.29.0",
504
504
  "nanostores": "^1.3.0",
505
505
  "zod": "^4.5.4",
506
- "@better-auth/core": "1.7.10",
507
- "@better-auth/drizzle-adapter": "1.7.10",
508
- "@better-auth/kysely-adapter": "1.7.10",
509
- "@better-auth/memory-adapter": "1.7.10",
510
- "@better-auth/mongo-adapter": "1.7.10",
511
- "@better-auth/prisma-adapter": "1.7.10",
512
- "@better-auth/telemetry": "1.7.10"
506
+ "@better-auth/core": "1.7.12",
507
+ "@better-auth/drizzle-adapter": "1.7.12",
508
+ "@better-auth/kysely-adapter": "1.7.12",
509
+ "@better-auth/memory-adapter": "1.7.12",
510
+ "@better-auth/mongo-adapter": "1.7.12",
511
+ "@better-auth/prisma-adapter": "1.7.12",
512
+ "@better-auth/telemetry": "1.7.12"
513
513
  },
514
514
  "devDependencies": {
515
515
  "@lynx-js/react": "^0.121.2",