better-auth 1.7.0-rc.2 → 1.7.0-rc.4

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 (72) hide show
  1. package/dist/api/index.d.mts +38 -8
  2. package/dist/api/rate-limiter/index.mjs +3 -3
  3. package/dist/api/routes/account.d.mts +4 -4
  4. package/dist/api/routes/account.mjs +5 -5
  5. package/dist/api/routes/callback.mjs +2 -2
  6. package/dist/api/routes/password.mjs +1 -1
  7. package/dist/api/routes/session.mjs +3 -45
  8. package/dist/api/routes/sign-in.mjs +2 -2
  9. package/dist/api/routes/sign-out.d.mts +16 -0
  10. package/dist/api/routes/sign-out.mjs +68 -2
  11. package/dist/api/routes/sign-up.mjs +1 -1
  12. package/dist/api/routes/update-user.mjs +1 -1
  13. package/dist/client/path-to-object.d.mts +3 -2
  14. package/dist/client/session-atom.mjs +33 -44
  15. package/dist/client/solid/index.d.mts +1 -0
  16. package/dist/client/solid/index.mjs +4 -2
  17. package/dist/context/create-context.mjs +1 -12
  18. package/dist/cookies/cache.mjs +25 -0
  19. package/dist/cookies/index.d.mts +10 -12
  20. package/dist/cookies/index.mjs +69 -26
  21. package/dist/cookies/jwt.mjs +44 -0
  22. package/dist/cookies/session-store.d.mts +1 -1
  23. package/dist/db/internal-adapter.mjs +29 -21
  24. package/dist/db/schema.d.mts +1 -1
  25. package/dist/integrations/next-js.mjs +16 -2
  26. package/dist/oauth2/account-key.mjs +3 -3
  27. package/dist/oauth2/link-account.d.mts +1 -1
  28. package/dist/oauth2/link-account.mjs +9 -9
  29. package/dist/package.mjs +1 -1
  30. package/dist/plugins/admin/routes.mjs +2 -2
  31. package/dist/plugins/anonymous/index.mjs +0 -1
  32. package/dist/plugins/device-authorization/index.d.mts +41 -2
  33. package/dist/plugins/device-authorization/index.mjs +3 -3
  34. package/dist/plugins/device-authorization/routes.mjs +82 -11
  35. package/dist/plugins/device-authorization/schema.mjs +47 -39
  36. package/dist/plugins/email-otp/index.mjs +2 -2
  37. package/dist/plugins/email-otp/otp-token.mjs +1 -1
  38. package/dist/plugins/email-otp/routes.mjs +11 -6
  39. package/dist/plugins/generic-oauth/index.d.mts +1 -1
  40. package/dist/plugins/generic-oauth/index.mjs +22 -0
  41. package/dist/plugins/generic-oauth/providers/auth0.mjs +3 -0
  42. package/dist/plugins/generic-oauth/providers/gumroad.mjs +3 -0
  43. package/dist/plugins/generic-oauth/providers/hubspot.mjs +3 -0
  44. package/dist/plugins/generic-oauth/providers/keycloak.mjs +3 -0
  45. package/dist/plugins/generic-oauth/providers/line.mjs +3 -0
  46. package/dist/plugins/generic-oauth/providers/microsoft-entra-id.mjs +35 -9
  47. package/dist/plugins/generic-oauth/providers/okta.mjs +3 -0
  48. package/dist/plugins/generic-oauth/providers/patreon.mjs +3 -0
  49. package/dist/plugins/generic-oauth/providers/slack.mjs +3 -0
  50. package/dist/plugins/generic-oauth/providers/yandex.mjs +3 -0
  51. package/dist/plugins/generic-oauth/types.d.mts +27 -0
  52. package/dist/plugins/index.mjs +2 -2
  53. package/dist/plugins/jwt/adapter.mjs +7 -6
  54. package/dist/plugins/jwt/client.d.mts +4 -2
  55. package/dist/plugins/jwt/client.mjs +1 -1
  56. package/dist/plugins/jwt/cookie-cache.mjs +24 -64
  57. package/dist/plugins/jwt/index.d.mts +14 -0
  58. package/dist/plugins/jwt/index.mjs +16 -3
  59. package/dist/plugins/jwt/sign.mjs +1 -1
  60. package/dist/plugins/jwt/types.d.mts +9 -0
  61. package/dist/plugins/jwt/utils.mjs +2 -2
  62. package/dist/plugins/oauth-popup/index.mjs +1 -1
  63. package/dist/plugins/oauth-proxy/index.mjs +15 -5
  64. package/dist/plugins/one-tap/client.d.mts +4 -3
  65. package/dist/plugins/one-tap/client.mjs +1 -1
  66. package/dist/plugins/one-tap/index.mjs +2 -2
  67. package/dist/plugins/phone-number/routes.mjs +2 -2
  68. package/dist/plugins/siwe/index.mjs +2 -2
  69. package/dist/test-utils/http-test-instance.mjs +2 -1
  70. package/dist/test-utils/test-instance.d.mts +1 -0
  71. package/dist/test-utils/test-instance.mjs +4 -2
  72. package/package.json +21 -12
@@ -290,6 +290,11 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
290
290
  } | null>;
291
291
  readonly signOut: import("better-call").StrictEndpoint<"/sign-out", {
292
292
  method: "POST";
293
+ body: import("zod").ZodOptional<import("zod").ZodObject<{
294
+ callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
295
+ disableRedirect: import("zod").ZodOptional<import("zod").ZodBoolean>;
296
+ state: import("zod").ZodOptional<import("zod").ZodString>;
297
+ }, import("zod/v4/core").$strip>>;
293
298
  operationId: string;
294
299
  requireHeaders: true;
295
300
  metadata: {
@@ -307,6 +312,14 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
307
312
  success: {
308
313
  type: string;
309
314
  };
315
+ url: {
316
+ type: string;
317
+ description: string;
318
+ };
319
+ redirect: {
320
+ type: string;
321
+ description: string;
322
+ };
310
323
  };
311
324
  };
312
325
  };
@@ -317,6 +330,8 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
317
330
  };
318
331
  }, {
319
332
  success: boolean;
333
+ url: string | undefined;
334
+ redirect: boolean | undefined;
320
335
  }>;
321
336
  readonly signUpEmail: import("better-call").StrictEndpoint<"/sign-up/email", {
322
337
  method: "POST";
@@ -1704,7 +1719,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
1704
1719
  issuer: {
1705
1720
  type: string;
1706
1721
  };
1707
- providerAccountId: {
1722
+ accountId: {
1708
1723
  type: string;
1709
1724
  };
1710
1725
  userId: {
@@ -1734,7 +1749,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
1734
1749
  userId: string;
1735
1750
  providerId: string;
1736
1751
  issuer: string;
1737
- providerAccountId: string;
1752
+ accountId: string;
1738
1753
  }[]>;
1739
1754
  readonly deleteUserCallback: import("better-call").StrictEndpoint<"/delete-user/callback", {
1740
1755
  method: "GET";
@@ -1984,7 +1999,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
1984
1999
  issuer: {
1985
2000
  type: string;
1986
2001
  };
1987
- providerAccountId: {
2002
+ accountId: {
1988
2003
  type: string;
1989
2004
  };
1990
2005
  };
@@ -2018,7 +2033,7 @@ declare function getEndpoints<Option extends BetterAuthOptions>(ctx: Awaitable<A
2018
2033
  id: string;
2019
2034
  providerId: string;
2020
2035
  issuer: string;
2021
- providerAccountId: string;
2036
+ accountId: string;
2022
2037
  };
2023
2038
  user: import("@better-auth/core/oauth2").OAuth2UserInfo;
2024
2039
  data: object;
@@ -2297,6 +2312,11 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
2297
2312
  } | null>;
2298
2313
  readonly signOut: import("better-call").StrictEndpoint<"/sign-out", {
2299
2314
  method: "POST";
2315
+ body: import("zod").ZodOptional<import("zod").ZodObject<{
2316
+ callbackURL: import("zod").ZodOptional<import("zod").ZodString>;
2317
+ disableRedirect: import("zod").ZodOptional<import("zod").ZodBoolean>;
2318
+ state: import("zod").ZodOptional<import("zod").ZodString>;
2319
+ }, import("zod/v4/core").$strip>>;
2300
2320
  operationId: string;
2301
2321
  requireHeaders: true;
2302
2322
  metadata: {
@@ -2314,6 +2334,14 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
2314
2334
  success: {
2315
2335
  type: string;
2316
2336
  };
2337
+ url: {
2338
+ type: string;
2339
+ description: string;
2340
+ };
2341
+ redirect: {
2342
+ type: string;
2343
+ description: string;
2344
+ };
2317
2345
  };
2318
2346
  };
2319
2347
  };
@@ -2324,6 +2352,8 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
2324
2352
  };
2325
2353
  }, {
2326
2354
  success: boolean;
2355
+ url: string | undefined;
2356
+ redirect: boolean | undefined;
2327
2357
  }>;
2328
2358
  readonly signUpEmail: import("better-call").StrictEndpoint<"/sign-up/email", {
2329
2359
  method: "POST";
@@ -3711,7 +3741,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
3711
3741
  issuer: {
3712
3742
  type: string;
3713
3743
  };
3714
- providerAccountId: {
3744
+ accountId: {
3715
3745
  type: string;
3716
3746
  };
3717
3747
  userId: {
@@ -3741,7 +3771,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
3741
3771
  userId: string;
3742
3772
  providerId: string;
3743
3773
  issuer: string;
3744
- providerAccountId: string;
3774
+ accountId: string;
3745
3775
  }[]>;
3746
3776
  readonly deleteUserCallback: import("better-call").StrictEndpoint<"/delete-user/callback", {
3747
3777
  method: "GET";
@@ -3991,7 +4021,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
3991
4021
  issuer: {
3992
4022
  type: string;
3993
4023
  };
3994
- providerAccountId: {
4024
+ accountId: {
3995
4025
  type: string;
3996
4026
  };
3997
4027
  };
@@ -4025,7 +4055,7 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
4025
4055
  id: string;
4026
4056
  providerId: string;
4027
4057
  issuer: string;
4028
- providerAccountId: string;
4058
+ accountId: string;
4029
4059
  };
4030
4060
  user: import("@better-auth/core/oauth2").OAuth2UserInfo;
4031
4061
  data: object;
@@ -127,7 +127,7 @@ function createDatabaseStorageWrapper(ctx) {
127
127
  lastRequest: now
128
128
  }
129
129
  })) {
130
- deleteExpiredRows(now);
130
+ await deleteExpiredRows(now);
131
131
  return {
132
132
  allowed: true,
133
133
  retryAfter: null
@@ -168,9 +168,9 @@ function createDatabaseStorageWrapper(ctx) {
168
168
  retryAfter: getRetryAfter(fresh.lastRequest, rule.window)
169
169
  };
170
170
  };
171
- const deleteExpiredRows = (now) => {
171
+ const deleteExpiredRows = async (now) => {
172
172
  const cutoff = now - longestObservedWindow * 1e3;
173
- ctx.runInBackground(db.deleteMany({
173
+ await ctx.runInBackgroundOrAwait(db.deleteMany({
174
174
  model,
175
175
  where: [{
176
176
  field: "lastRequest",
@@ -56,7 +56,7 @@ declare const listUserAccounts: import("better-call").StrictEndpoint<"/list-acco
56
56
  issuer: {
57
57
  type: string;
58
58
  };
59
- providerAccountId: {
59
+ accountId: {
60
60
  type: string;
61
61
  };
62
62
  userId: {
@@ -86,7 +86,7 @@ declare const listUserAccounts: import("better-call").StrictEndpoint<"/list-acco
86
86
  userId: string;
87
87
  providerId: string;
88
88
  issuer: string;
89
- providerAccountId: string;
89
+ accountId: string;
90
90
  }[]>;
91
91
  declare const linkSocialAccount: import("better-call").StrictEndpoint<"/link-social", {
92
92
  method: "POST";
@@ -375,7 +375,7 @@ declare const accountInfo: import("better-call").StrictEndpoint<"/account-info",
375
375
  issuer: {
376
376
  type: string;
377
377
  };
378
- providerAccountId: {
378
+ accountId: {
379
379
  type: string;
380
380
  };
381
381
  };
@@ -409,7 +409,7 @@ declare const accountInfo: import("better-call").StrictEndpoint<"/account-info",
409
409
  id: string;
410
410
  providerId: string;
411
411
  issuer: string;
412
- providerAccountId: string;
412
+ accountId: string;
413
413
  };
414
414
  user: import("@better-auth/core/oauth2").OAuth2UserInfo;
415
415
  data: object;
@@ -42,7 +42,7 @@ const listUserAccounts = createAuthEndpoint("/list-accounts", {
42
42
  format: "date-time"
43
43
  },
44
44
  issuer: { type: "string" },
45
- providerAccountId: { type: "string" },
45
+ accountId: { type: "string" },
46
46
  userId: { type: "string" },
47
47
  scopes: {
48
48
  type: "array",
@@ -55,7 +55,7 @@ const listUserAccounts = createAuthEndpoint("/list-accounts", {
55
55
  "createdAt",
56
56
  "updatedAt",
57
57
  "issuer",
58
- "providerAccountId",
58
+ "accountId",
59
59
  "userId",
60
60
  "scopes"
61
61
  ]
@@ -559,13 +559,13 @@ const accountInfo = createAuthEndpoint("/account-info", {
559
559
  id: { type: "string" },
560
560
  providerId: { type: "string" },
561
561
  issuer: { type: "string" },
562
- providerAccountId: { type: "string" }
562
+ accountId: { type: "string" }
563
563
  },
564
564
  required: [
565
565
  "id",
566
566
  "providerId",
567
567
  "issuer",
568
- "providerAccountId"
568
+ "accountId"
569
569
  ],
570
570
  additionalProperties: false
571
571
  },
@@ -617,7 +617,7 @@ const accountInfo = createAuthEndpoint("/account-info", {
617
617
  id: account.id,
618
618
  providerId: account.providerId,
619
619
  issuer: account.issuer,
620
- providerAccountId: account.providerAccountId
620
+ accountId: account.accountId
621
621
  }
622
622
  });
623
623
  });
@@ -142,7 +142,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
142
142
  });
143
143
  return redirectOnError(OAUTH_CALLBACK_ERROR_CODES.UNABLE_TO_GET_USER_INFO);
144
144
  }
145
- const { providerAccountId } = accountKey;
145
+ const { accountId } = accountKey;
146
146
  if (!callbackURL) {
147
147
  c.context.logger.error("No callback URL found");
148
148
  throw redirectOnError(OAUTH_CALLBACK_ERROR_CODES.NO_CALLBACK_URL);
@@ -220,7 +220,7 @@ const callbackOAuth = createAuthEndpoint("/callback/:id", {
220
220
  result = await handleOAuthUserInfo(c, {
221
221
  userInfo: {
222
222
  ...userInfo,
223
- id: providerAccountId,
223
+ id: accountId,
224
224
  email: userInfo.email,
225
225
  name: userInfo.name || ""
226
226
  },
@@ -165,7 +165,7 @@ const resetPassword = createAuthEndpoint("/reset-password", {
165
165
  userId,
166
166
  providerId: "credential",
167
167
  issuer: createLocalAccountIssuer("credential"),
168
- providerAccountId: user.id,
168
+ accountId: user.id,
169
169
  password: hashedPassword
170
170
  });
171
171
  else await ctx.context.internalAdapter.updatePassword(userId, hashedPassword);
@@ -1,19 +1,13 @@
1
1
  import { isAPIError } from "../../utils/is-api-error.mjs";
2
2
  import { hasServerSessionStore } from "../../context/store-capabilities.mjs";
3
- import { symmetricDecodeJWT, verifyJWT } from "../../crypto/jwt.mjs";
4
3
  import { parseSessionOutput, parseUserOutput } from "../../db/schema.mjs";
5
- import { getCookieCacheJwtSigningKey, verifyCookieCacheJWT } from "../../plugins/jwt/cookie-cache.mjs";
6
4
  import { getDate } from "../../utils/date.mjs";
7
5
  import { getChunkedCookie, getSessionQuerySchema } from "../../cookies/session-store.mjs";
8
- import { deleteSessionCookie, expireCookie, setCookieCache, setSessionCookie } from "../../cookies/index.mjs";
6
+ import { decodeCookieCache, deleteSessionCookie, expireCookie, setCookieCache, setSessionCookie } from "../../cookies/index.mjs";
9
7
  import { getShouldSkipSessionRefresh } from "../state/should-session-refresh.mjs";
10
8
  import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
11
- import { safeJSONParse } from "@better-auth/core/utils/json";
12
9
  import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
13
10
  import * as z from "zod";
14
- import { base64Url } from "@better-auth/utils/base64";
15
- import { binary } from "@better-auth/utils/binary";
16
- import { createHMAC } from "@better-auth/utils/hmac";
17
11
  //#region src/api/routes/session.ts
18
12
  const getSession = () => createAuthEndpoint("/get-session", {
19
13
  method: ["GET", "POST"],
@@ -45,44 +39,8 @@ const getSession = () => createAuthEndpoint("/get-session", {
45
39
  const sessionCookieToken = await ctx.getSignedCookie(ctx.context.authCookies.sessionToken.name, ctx.context.secret);
46
40
  if (!sessionCookieToken) return null;
47
41
  const sessionDataCookie = getChunkedCookie(ctx, ctx.context.authCookies.sessionData.name);
48
- let sessionDataPayload = null;
49
- if (sessionDataCookie) {
50
- const strategy = ctx.context.options.session?.cookieCache?.strategy || "compact";
51
- const jwtSigningKey = getCookieCacheJwtSigningKey(ctx.context.options);
52
- if (strategy === "jwe") {
53
- const payload = await symmetricDecodeJWT(sessionDataCookie, ctx.context.secretConfig, "better-auth-session");
54
- if (payload && payload.session && payload.user) sessionDataPayload = {
55
- session: {
56
- session: payload.session,
57
- user: payload.user,
58
- updatedAt: payload.updatedAt,
59
- version: payload.version
60
- },
61
- expiresAt: payload.exp ? payload.exp * 1e3 : Date.now()
62
- };
63
- else expireCookie(ctx, ctx.context.authCookies.sessionData);
64
- } else if (strategy === "jwt") {
65
- const payload = jwtSigningKey === "jwt-plugin" ? await verifyCookieCacheJWT(ctx, sessionDataCookie) : await verifyJWT(sessionDataCookie, ctx.context.secret);
66
- if (payload && payload.session && payload.user) sessionDataPayload = {
67
- session: {
68
- session: payload.session,
69
- user: payload.user,
70
- updatedAt: payload.updatedAt,
71
- version: payload.version
72
- },
73
- expiresAt: payload.exp ? payload.exp * 1e3 : Date.now()
74
- };
75
- else expireCookie(ctx, ctx.context.authCookies.sessionData);
76
- } else {
77
- const parsed = safeJSONParse(binary.decode(base64Url.decode(sessionDataCookie)));
78
- if (parsed) if (await createHMAC("SHA-256", "base64urlnopad").verify(ctx.context.secret, JSON.stringify({
79
- ...parsed.session,
80
- expiresAt: parsed.expiresAt
81
- }), parsed.signature)) sessionDataPayload = parsed;
82
- else expireCookie(ctx, ctx.context.authCookies.sessionData);
83
- else expireCookie(ctx, ctx.context.authCookies.sessionData);
84
- }
85
- }
42
+ const sessionDataPayload = sessionDataCookie ? await decodeCookieCache(ctx, sessionDataCookie) : null;
43
+ if (sessionDataCookie && !sessionDataPayload) expireCookie(ctx, ctx.context.authCookies.sessionData);
86
44
  const dontRememberMe = await ctx.getSignedCookie(ctx.context.authCookies.dontRememberToken.name, ctx.context.secret);
87
45
  /**
88
46
  * If session data is present in the cookie, check if it should be used or refreshed
@@ -182,7 +182,7 @@ const signInSocial = () => createAuthEndpoint("/sign-in/social", {
182
182
  userInfo: {
183
183
  ...userInfo.user,
184
184
  email: userInfo.user.email,
185
- id: accountKey.providerAccountId,
185
+ id: accountKey.accountId,
186
186
  name: userInfo.user.name || "",
187
187
  image: userInfo.user.image,
188
188
  emailVerified: userInfo.user.emailVerified || false
@@ -316,7 +316,7 @@ const signInEmail = () => createAuthEndpoint("/sign-in/email", {
316
316
  if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_EMAIL);
317
317
  const userRecord = await ctx.context.internalAdapter.findUserByEmail(email.toLowerCase(), { includeAccounts: true });
318
318
  const credentialIssuer = createLocalAccountIssuer("credential");
319
- const credentialAccount = userRecord?.accounts.find((account) => account.providerId === "credential" && account.issuer === credentialIssuer && account.providerAccountId === userRecord.user.id);
319
+ const credentialAccount = userRecord?.accounts.find((account) => account.providerId === "credential" && account.issuer === credentialIssuer && account.accountId === userRecord.user.id);
320
320
  if (!userRecord || !credentialAccount) {
321
321
  await ctx.context.password.hash(password);
322
322
  ctx.context.logger.warn("User not found");
@@ -1,6 +1,12 @@
1
+ import * as z from "zod";
1
2
  //#region src/api/routes/sign-out.d.ts
2
3
  declare const signOut: import("better-call").StrictEndpoint<"/sign-out", {
3
4
  method: "POST";
5
+ body: z.ZodOptional<z.ZodObject<{
6
+ callbackURL: z.ZodOptional<z.ZodString>;
7
+ disableRedirect: z.ZodOptional<z.ZodBoolean>;
8
+ state: z.ZodOptional<z.ZodString>;
9
+ }, z.core.$strip>>;
4
10
  operationId: string;
5
11
  requireHeaders: true;
6
12
  metadata: {
@@ -18,6 +24,14 @@ declare const signOut: import("better-call").StrictEndpoint<"/sign-out", {
18
24
  success: {
19
25
  type: string;
20
26
  };
27
+ url: {
28
+ type: string;
29
+ description: string;
30
+ };
31
+ redirect: {
32
+ type: string;
33
+ description: string;
34
+ };
21
35
  };
22
36
  };
23
37
  };
@@ -28,6 +42,8 @@ declare const signOut: import("better-call").StrictEndpoint<"/sign-out", {
28
42
  };
29
43
  }, {
30
44
  success: boolean;
45
+ url: string | undefined;
46
+ redirect: boolean | undefined;
31
47
  }>;
32
48
  //#endregion
33
49
  export { signOut };
@@ -1,8 +1,20 @@
1
1
  import { deleteSessionCookie } from "../../cookies/index.mjs";
2
2
  import { createAuthEndpoint } from "@better-auth/core/api";
3
+ import * as z from "zod";
3
4
  //#region src/api/routes/sign-out.ts
5
+ const signOutBodySchema = z.object({
6
+ callbackURL: z.string().meta({ description: "The URL to redirect to after provider logout" }).optional(),
7
+ disableRedirect: z.boolean().meta({ description: "Return the provider logout URL without redirecting" }).optional(),
8
+ state: z.string().meta({ description: "State to pass to the provider logout endpoint" }).optional()
9
+ }).optional();
10
+ const signOutResponse = (providerLogout) => ({
11
+ success: true,
12
+ url: providerLogout?.url,
13
+ redirect: providerLogout?.redirect
14
+ });
4
15
  const signOut = createAuthEndpoint("/sign-out", {
5
16
  method: "POST",
17
+ body: signOutBodySchema,
6
18
  operationId: "signOut",
7
19
  requireHeaders: true,
8
20
  metadata: { openapi: {
@@ -12,19 +24,73 @@ const signOut = createAuthEndpoint("/sign-out", {
12
24
  description: "Success",
13
25
  content: { "application/json": { schema: {
14
26
  type: "object",
15
- properties: { success: { type: "boolean" } }
27
+ properties: {
28
+ success: { type: "boolean" },
29
+ url: {
30
+ type: "string",
31
+ description: "Provider logout URL when RP-initiated logout is available"
32
+ },
33
+ redirect: {
34
+ type: "boolean",
35
+ description: "Whether the client should redirect to the provider logout URL"
36
+ }
37
+ }
16
38
  } } }
17
39
  } }
18
40
  } }
19
41
  }, async (ctx) => {
20
42
  const sessionCookieToken = await ctx.getSignedCookie(ctx.context.authCookies.sessionToken.name, ctx.context.secret);
43
+ let currentSession = null;
44
+ if (sessionCookieToken) try {
45
+ currentSession = await ctx.context.internalAdapter.findSession(sessionCookieToken);
46
+ } catch (e) {
47
+ ctx.context.logger.error("Failed to read session from database", e);
48
+ }
21
49
  if (sessionCookieToken) try {
22
50
  await ctx.context.internalAdapter.deleteSession(sessionCookieToken);
23
51
  } catch (e) {
24
52
  ctx.context.logger.error("Failed to delete session from database", e);
25
53
  }
26
54
  deleteSessionCookie(ctx);
27
- return ctx.json({ success: true });
55
+ const providerLogoutResult = await (async () => {
56
+ try {
57
+ if (!currentSession) return null;
58
+ const accounts = await ctx.context.internalAdapter.findAccounts(currentSession.user.id);
59
+ const providersById = new Map(ctx.context.socialProviders.map((provider) => [provider.id, provider]));
60
+ const logoutAccounts = accounts.filter((account) => Boolean(providersById.get(account.providerId)?.createEndSessionURL)).sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
61
+ if (logoutAccounts.length === 0) return null;
62
+ const seenProviderIds = /* @__PURE__ */ new Set();
63
+ const postLogoutRedirectURI = ctx.body?.callbackURL ? new URL(ctx.body.callbackURL, ctx.context.baseURL).toString() : void 0;
64
+ for (const account of logoutAccounts) {
65
+ if (seenProviderIds.has(account.providerId)) continue;
66
+ seenProviderIds.add(account.providerId);
67
+ const provider = providersById.get(account.providerId);
68
+ try {
69
+ const url = await provider?.createEndSessionURL?.({
70
+ idToken: account.idToken,
71
+ postLogoutRedirectURI,
72
+ state: ctx.body?.state
73
+ });
74
+ if (url) return url.toString();
75
+ } catch (e) {
76
+ ctx.context.logger.error(`Failed to create logout URL for provider "${account.providerId}"`, e);
77
+ }
78
+ }
79
+ return null;
80
+ } catch (e) {
81
+ ctx.context.logger.error("Failed to create provider logout URL", e);
82
+ return null;
83
+ }
84
+ })();
85
+ if (providerLogoutResult) {
86
+ const shouldRedirect = !ctx.body?.disableRedirect;
87
+ if (shouldRedirect) ctx.setHeader("Location", providerLogoutResult);
88
+ return ctx.json(signOutResponse({
89
+ url: providerLogoutResult,
90
+ redirect: shouldRedirect
91
+ }));
92
+ }
93
+ return ctx.json(signOutResponse());
28
94
  });
29
95
  //#endregion
30
96
  export { signOut };
@@ -244,7 +244,7 @@ const signUpEmail = () => createAuthEndpoint("/sign-up/email", {
244
244
  userId: createdUser.id,
245
245
  providerId: "credential",
246
246
  issuer: createLocalAccountIssuer("credential"),
247
- providerAccountId: createdUser.id,
247
+ accountId: createdUser.id,
248
248
  password: hash
249
249
  });
250
250
  if (ctx.context.options.emailVerification?.sendOnSignUp ?? ctx.context.options.emailAndPassword.requireEmailVerification) {
@@ -221,7 +221,7 @@ newPassword: z.string().meta({ description: "The new password to set is required
221
221
  userId: session.user.id,
222
222
  providerId: "credential",
223
223
  issuer: createLocalAccountIssuer("credential"),
224
- providerAccountId: session.user.id,
224
+ accountId: session.user.id,
225
225
  password: passwordHash
226
226
  });
227
227
  return ctx.json({ status: true });
@@ -77,7 +77,8 @@ type InferBodyCtx<Body, FetchOptions extends ClientFetchOption> = Body extends R
77
77
  } : never;
78
78
  type PrettifyUnion<T> = T extends unknown ? Prettify$1<T> : never;
79
79
  type HasRequiredKeysInUnion<T> = true extends (T extends unknown ? HasRequiredKeys<T> : never) ? true : false;
80
- type InferCtx<C extends InputContext<any, any>, FetchOptions extends ClientFetchOption> = IsAny<C["body"]> extends true ? InferCtxQuery<C, FetchOptions> : C["body"] extends Record<string, any> ? InferBodyCtx<C["body"], FetchOptions> : InferCtxQuery<C, FetchOptions>;
80
+ type HasRequiredCtx<C extends InputContext<any, any>, FetchOptions extends ClientFetchOption> = IsAny<C["body"]> extends true ? HasRequiredKeysInUnion<InferCtx<C, FetchOptions>> : undefined extends C["body"] ? HasRequiredKeysInUnion<InferCtxQuery<C, FetchOptions>> : HasRequiredKeysInUnion<InferCtx<C, FetchOptions>>;
81
+ type InferCtx<C extends InputContext<any, any>, FetchOptions extends ClientFetchOption> = IsAny<C["body"]> extends true ? InferCtxQuery<C, FetchOptions> : [NonNullable<C["body"]>] extends [never] ? InferCtxQuery<C, FetchOptions> : NonNullable<C["body"]> extends Record<string, any> ? InferBodyCtx<NonNullable<C["body"]>, FetchOptions> : InferCtxQuery<C, FetchOptions>;
81
82
  type MergeRoutes<T> = UnionToIntersection<T>;
82
83
  type InferRoute<API, COpts extends BetterAuthClientOptions> = API extends Record<string, infer T> ? T extends Endpoint ? T["options"]["metadata"] extends {
83
84
  isAction: false;
@@ -87,7 +88,7 @@ type InferRoute<API, COpts extends BetterAuthClientOptions> = API extends Record
87
88
  scope: "http";
88
89
  } | {
89
90
  scope: "server";
90
- } ? {} : PathToObject<T["path"], T extends ((ctx: infer C) => infer R) ? C extends InputContext<any, any> ? <FetchOptions extends ClientFetchOption<Partial<C["body"]> & Record<string, any>, Partial<C["query"]> & Record<string, any>, C["params"]>>(...data: HasRequiredKeysInUnion<InferCtx<C, FetchOptions>> extends true ? [PrettifyUnion<T["path"] extends `/sign-up/email` ? InferSignUpEmailCtx<COpts, FetchOptions> : T["path"] extends `/sign-in/social` ? Omit<InferCtx<C, FetchOptions>, "provider"> & {
91
+ } ? {} : PathToObject<T["path"], T extends ((ctx: infer C) => infer R) ? C extends InputContext<any, any> ? <FetchOptions extends ClientFetchOption<Partial<C["body"]> & Record<string, any>, Partial<C["query"]> & Record<string, any>, C["params"]>>(...data: HasRequiredCtx<C, FetchOptions> extends true ? [PrettifyUnion<T["path"] extends `/sign-up/email` ? InferSignUpEmailCtx<COpts, FetchOptions> : T["path"] extends `/sign-in/social` ? Omit<InferCtx<C, FetchOptions>, "provider"> & {
91
92
  provider: SocialProviderList[number] | InferGenericOAuthProviderIds<COpts> | (string & {});
92
93
  fetchOptions?: FetchOptions | undefined;
93
94
  } : InferCtx<C, FetchOptions>>, FetchOptions?] : [PrettifyUnion<T["path"] extends `/update-user` ? InferUserUpdateCtx<COpts, FetchOptions> : T["path"] extends `/update-session` ? InferSessionUpdateCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>?, FetchOptions?]) => Promise<BetterFetchResponse<T["options"]["metadata"] extends {