better-auth 1.4.6-beta.2 → 1.4.6-beta.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 (187) hide show
  1. package/dist/{access-vPNO48H4.mjs → access-BktEfzR6.mjs} +1 -1
  2. package/dist/{access-h-uttVNZ.mjs → access-DZRRE6Tq.mjs} +1 -1
  3. package/dist/adapters/drizzle-adapter/index.mjs +1 -6
  4. package/dist/adapters/index.d.mts +2 -237
  5. package/dist/adapters/index.mjs +1 -6
  6. package/dist/adapters/kysely-adapter/index.d.mts +1 -1
  7. package/dist/adapters/kysely-adapter/index.mjs +2 -7
  8. package/dist/adapters/memory-adapter/index.mjs +1 -6
  9. package/dist/adapters/mongodb-adapter/index.d.mts +1 -1
  10. package/dist/adapters/mongodb-adapter/index.mjs +1 -6
  11. package/dist/adapters/prisma-adapter/index.mjs +1 -6
  12. package/dist/adapters/test.mjs +2 -3
  13. package/dist/admin-6fpWm8vW.mjs +971 -0
  14. package/dist/{anonymous-DxoIOUPc.mjs → anonymous-BvvfPHFI.mjs} +5 -6
  15. package/dist/api/index.d.mts +2 -4
  16. package/dist/api/index.mjs +10 -13
  17. package/dist/{api-CkmycQ2x.mjs → api-B67AcvJ9.mjs} +67 -91
  18. package/dist/auth/minimal.d.mts +3 -4
  19. package/dist/auth/minimal.mjs +12 -15
  20. package/dist/{auth-DfR8_5w3.mjs → auth-D5QxgEcO.mjs} +3 -3
  21. package/dist/{base-CiRMFqet.mjs → base-CtTlmMzu.mjs} +40 -10
  22. package/dist/{bearer-C6RSCXv_.mjs → bearer-GIQo0kPs.mjs} +1 -1
  23. package/dist/{captcha-Dbvurc2Z.mjs → captcha-av_BAGRz.mjs} +1 -1
  24. package/dist/{chunk-DbI2OXuS.mjs → chunk-DieNfLhd.mjs} +2 -4
  25. package/dist/client/index.d.mts +40 -21
  26. package/dist/client/index.mjs +4 -5
  27. package/dist/client/lynx/index.d.mts +4 -6
  28. package/dist/client/lynx/index.mjs +5 -7
  29. package/dist/client/plugins/index.d.mts +45 -46
  30. package/dist/client/plugins/index.mjs +10 -11
  31. package/dist/client/react/index.d.mts +17 -19
  32. package/dist/client/react/index.mjs +5 -7
  33. package/dist/client/solid/index.d.mts +3 -4
  34. package/dist/client/solid/index.mjs +4 -4
  35. package/dist/client/svelte/index.d.mts +18 -19
  36. package/dist/client/svelte/index.mjs +4 -4
  37. package/dist/client/vue/index.d.mts +18 -19
  38. package/dist/client/vue/index.mjs +4 -4
  39. package/dist/{client-BN9e9KX1.mjs → client-BUwkFYja.mjs} +2 -2
  40. package/dist/cookies/index.d.mts +3 -5
  41. package/dist/cookies/index.mjs +4 -5
  42. package/dist/{cookies-D72PbWdz.mjs → cookies-DmQmKltR.mjs} +4 -4
  43. package/dist/crypto/index.mjs +1 -1
  44. package/dist/{custom-session-CaUbM_of.mjs → custom-session-BB-BwDl8.mjs} +2 -2
  45. package/dist/db/index.d.mts +3 -5
  46. package/dist/db/index.mjs +5 -10
  47. package/dist/device-authorization-Bo7zd0O8.mjs +593 -0
  48. package/dist/{dialect-D9ZUZA4J.mjs → dialect-BHuPIP4Z.mjs} +2 -2
  49. package/dist/email-otp-BDqXTWoL.mjs +736 -0
  50. package/dist/{esm-BXhcK_bt.mjs → esm-CyZgw_uF.mjs} +83 -83
  51. package/dist/{generic-oauth-BGbWWUh9.mjs → generic-oauth-DdO9dcA6.mjs} +385 -346
  52. package/dist/{get-migration-Bf0TuCzm.mjs → get-migration--xV8I5XU.mjs} +36 -8
  53. package/dist/{has-permission-qCjQqGds.mjs → has-permission-BxveqtYZ.mjs} +1 -1
  54. package/dist/{haveibeenpwned-BSsFUFTi.mjs → haveibeenpwned-DLiHZcSj.mjs} +1 -1
  55. package/dist/{index-RbmRVtaI.d.mts → index-1pOfmpZV.d.mts} +3 -3
  56. package/dist/{index-BvZWUEoC.d.mts → index-7pFhhF0x.d.mts} +6 -6
  57. package/dist/{index-rnNTrfA7.d.mts → index-B1fASdrI.d.mts} +1 -1
  58. package/dist/{index-BcnaxwZM.d.mts → index-BDCxDMd9.d.mts} +36 -80
  59. package/dist/{index-hBQ1qSDX.d.mts → index-BP8GKS1P.d.mts} +4 -4
  60. package/dist/{index-DlOKn_yt.d.mts → index-BfGjzoFj.d.mts} +24 -24
  61. package/dist/{index-pQ7I9EOb.d.mts → index-BgCLcoV7.d.mts} +2 -2
  62. package/dist/{index-CfdutUAu.d.mts → index-BgRi_ahI.d.mts} +6 -6
  63. package/dist/{index-D0UwTS5_.d.mts → index-Bzvy2fZc.d.mts} +15 -15
  64. package/dist/{index-Bqj2Kztr.d.mts → index-C-MbAsf4.d.mts} +9 -9
  65. package/dist/{index--dkKT2P2.d.mts → index-CVZJwI8R.d.mts} +5 -5
  66. package/dist/{index-73Iin-Jq.d.mts → index-CoRq_As_.d.mts} +6 -6
  67. package/dist/{index-Be0syNaw.d.mts → index-D8JLGqFA.d.mts} +85 -181
  68. package/dist/{index-DA5jLHwS.d.mts → index-DfQ1cHMu.d.mts} +2 -2
  69. package/dist/{index-Bt92FfJd.d.mts → index-GPso9wJG.d.mts} +33 -33
  70. package/dist/{index-CLll1XPI.d.mts → index-HVSLuFvF.d.mts} +8 -8
  71. package/dist/{index-BevOkArW.d.mts → index-ItLtiMMN.d.mts} +249 -257
  72. package/dist/{index-CGKooORC.d.mts → index-MexUZ7RH.d.mts} +4 -4
  73. package/dist/{index-akAuJUJt.d.mts → index-P-wYkAD5.d.mts} +170 -197
  74. package/dist/{index-Dqjfj0PE.d.mts → index-V081dNa-.d.mts} +808 -1024
  75. package/dist/{index-BVpFCwyn.d.mts → index-ZA57__3E.d.mts} +1 -1
  76. package/dist/{index-CWcnHbjn.d.mts → index-fnLzWr2C.d.mts} +2 -2
  77. package/dist/{index-BfHctM9K.d.mts → index-veGVCQDP.d.mts} +7 -7
  78. package/dist/index.d.mts +5 -6
  79. package/dist/index.mjs +15 -18
  80. package/dist/integrations/next-js.d.mts +4 -4
  81. package/dist/integrations/next-js.mjs +4 -5
  82. package/dist/integrations/node.d.mts +2 -4
  83. package/dist/integrations/svelte-kit.d.mts +4 -6
  84. package/dist/integrations/svelte-kit.mjs +4 -5
  85. package/dist/integrations/tanstack-start.d.mts +4 -4
  86. package/dist/integrations/tanstack-start.mjs +4 -5
  87. package/dist/{jwt-Ar1sciI2.mjs → jwt-YwDmNjLC.mjs} +18 -15
  88. package/dist/{magic-link-QdHvtdfs.mjs → magic-link-DHqYwrFb.mjs} +19 -17
  89. package/dist/{multi-session-CbEYz_wJ.mjs → multi-session-DFQQVetu.mjs} +7 -5
  90. package/dist/{oauth-proxy-Dz1E1SVN.mjs → oauth-proxy-EqrMlSDE.mjs} +10 -9
  91. package/dist/oauth2/index.d.mts +2 -4
  92. package/dist/oauth2/index.mjs +10 -13
  93. package/dist/{oidc-provider-B9SsN23J.mjs → oidc-provider-iL1I_C0p.mjs} +56 -51
  94. package/dist/{one-tap-Bz8Q39Od.mjs → one-tap-DL0Yr6_1.mjs} +5 -4
  95. package/dist/{one-time-token-C8YQxf38.mjs → one-time-token-B14NUG5P.mjs} +5 -4
  96. package/dist/{open-api-DZG02vyi.mjs → open-api-DFxa4uca.mjs} +3 -3
  97. package/dist/{organization-BdJSRNgM.mjs → organization-BHI3Ry6r.mjs} +139 -112
  98. package/dist/phone-number-D4JdoQf_.mjs +565 -0
  99. package/dist/plugins/access/index.d.mts +1 -2
  100. package/dist/plugins/access/index.mjs +1 -1
  101. package/dist/plugins/admin/access/index.d.mts +23 -25
  102. package/dist/plugins/admin/access/index.mjs +2 -2
  103. package/dist/plugins/admin/index.d.mts +22 -25
  104. package/dist/plugins/admin/index.mjs +15 -18
  105. package/dist/plugins/anonymous/index.d.mts +3 -5
  106. package/dist/plugins/anonymous/index.mjs +11 -14
  107. package/dist/plugins/bearer/index.d.mts +1 -1
  108. package/dist/plugins/bearer/index.mjs +5 -6
  109. package/dist/plugins/captcha/index.d.mts +1 -1
  110. package/dist/plugins/captcha/index.mjs +2 -2
  111. package/dist/plugins/custom-session/index.d.mts +3 -5
  112. package/dist/plugins/custom-session/index.mjs +12 -15
  113. package/dist/plugins/device-authorization/index.d.mts +1 -1
  114. package/dist/plugins/device-authorization/index.mjs +9 -13
  115. package/dist/plugins/email-otp/index.d.mts +1 -1
  116. package/dist/plugins/email-otp/index.mjs +12 -15
  117. package/dist/plugins/generic-oauth/index.d.mts +1 -5
  118. package/dist/plugins/generic-oauth/index.mjs +11 -14
  119. package/dist/plugins/haveibeenpwned/index.d.mts +1 -1
  120. package/dist/plugins/haveibeenpwned/index.mjs +11 -14
  121. package/dist/plugins/index.d.mts +23 -25
  122. package/dist/plugins/index.mjs +41 -45
  123. package/dist/plugins/jwt/index.d.mts +3 -5
  124. package/dist/plugins/jwt/index.mjs +11 -14
  125. package/dist/plugins/magic-link/index.d.mts +1 -1
  126. package/dist/plugins/magic-link/index.mjs +11 -14
  127. package/dist/plugins/multi-session/index.d.mts +1 -1
  128. package/dist/plugins/multi-session/index.mjs +11 -14
  129. package/dist/plugins/oauth-proxy/index.d.mts +1 -1
  130. package/dist/plugins/oauth-proxy/index.mjs +12 -15
  131. package/dist/plugins/oidc-provider/index.d.mts +22 -25
  132. package/dist/plugins/oidc-provider/index.mjs +12 -15
  133. package/dist/plugins/one-tap/index.d.mts +1 -1
  134. package/dist/plugins/one-tap/index.mjs +11 -14
  135. package/dist/plugins/one-time-token/index.d.mts +3 -5
  136. package/dist/plugins/one-time-token/index.mjs +11 -14
  137. package/dist/plugins/open-api/index.d.mts +1 -2
  138. package/dist/plugins/open-api/index.mjs +11 -14
  139. package/dist/plugins/organization/access/index.d.mts +22 -25
  140. package/dist/plugins/organization/access/index.mjs +2 -2
  141. package/dist/plugins/organization/index.d.mts +22 -25
  142. package/dist/plugins/organization/index.mjs +14 -18
  143. package/dist/plugins/phone-number/index.d.mts +3 -5
  144. package/dist/plugins/phone-number/index.mjs +11 -14
  145. package/dist/plugins/siwe/index.d.mts +3 -5
  146. package/dist/plugins/siwe/index.mjs +11 -14
  147. package/dist/plugins/two-factor/index.d.mts +3 -5
  148. package/dist/plugins/two-factor/index.mjs +13 -16
  149. package/dist/plugins/username/index.d.mts +2 -3
  150. package/dist/plugins/username/index.mjs +11 -14
  151. package/dist/{plugins-Dmw3tk5H.d.mts → plugins-DLdyc73z.d.mts} +1 -1
  152. package/dist/{plugins-DgSTpOzm.mjs → plugins-qDvRRRie.mjs} +89 -83
  153. package/dist/{promise-B1BZ0y5h.mjs → promise-CL99vzc3.mjs} +245 -243
  154. package/dist/{proxy-DplNCOES.mjs → proxy-ehujpdXc.mjs} +2 -2
  155. package/dist/{schema-Bb7wzeK_.mjs → schema-dfOF7vRb.mjs} +4 -1
  156. package/dist/{session-AaRl3_x-.mjs → session-Dpj2Z_b_.mjs} +5 -5
  157. package/dist/{siwe-D81Y4fkp.mjs → siwe-CAoLY7AB.mjs} +10 -9
  158. package/dist/test-utils/index.d.mts +1154 -1155
  159. package/dist/test-utils/index.mjs +47 -52
  160. package/dist/{two-factor-BDQvVILL.mjs → two-factor-BjnAESdO.mjs} +41 -32
  161. package/dist/types/index.d.mts +3 -4
  162. package/dist/types/index.mjs +0 -2
  163. package/dist/{username-C_wmkXwt.mjs → username-BcWNDdCk.mjs} +13 -11
  164. package/dist/{utils-db7gNqd-.mjs → utils-D4_n6vJh.mjs} +4 -10
  165. package/package.json +8 -8
  166. package/dist/adapter-factory-HF3JB9cT.mjs +0 -820
  167. package/dist/admin-D-OMdNIc.mjs +0 -742
  168. package/dist/device-authorization-9_f1Up5D.mjs +0 -579
  169. package/dist/email-otp-CiznqFUN.mjs +0 -614
  170. package/dist/get-model-name-D4DUV7S2.mjs +0 -366
  171. package/dist/json-CnHxKYpj.mjs +0 -24
  172. package/dist/misc-BwNc0MKr.mjs +0 -7
  173. package/dist/phone-number-wU9XYnr8.mjs +0 -507
  174. package/dist/types-BOTDmeLz.mjs +0 -1
  175. /package/dist/{access-Dx8TLKnw.mjs → access-BCQibqkF.mjs} +0 -0
  176. /package/dist/{bun-sqlite-dialect-C3P_ISb5.mjs → bun-sqlite-dialect-BGIIaWxx.mjs} +0 -0
  177. /package/dist/{client-CMPEe5-e.mjs → client-7xkXfvW4.mjs} +0 -0
  178. /package/dist/{crypto-CFUhAR9W.mjs → crypto-DgVHxgLL.mjs} +0 -0
  179. /package/dist/{get-request-ip-D6st-mto.mjs → get-request-ip-G2Tcmzbb.mjs} +0 -0
  180. /package/dist/{helper-DFzV6jvx.d.mts → helper-BBvhhJRX.d.mts} +0 -0
  181. /package/dist/{node-sqlite-dialect-D6w8Ekdz.mjs → node-sqlite-dialect-DL3qojbZ.mjs} +0 -0
  182. /package/dist/{parser-pHp5yoAv.mjs → parser-CQYBJKoR.mjs} +0 -0
  183. /package/dist/{password-BFQK0cLg.mjs → password-BRmR7rWA.mjs} +0 -0
  184. /package/dist/{permission-JwliMugl.mjs → permission-BZUPzNK6.mjs} +0 -0
  185. /package/dist/{plugin-helper-zFdFWLgL.mjs → plugin-helper-BneBaGtD.mjs} +0 -0
  186. /package/dist/{types-CEepZ-RG.d.mts → types-Bde2wFm4.d.mts} +0 -0
  187. /package/dist/{url-CB8xCwz-.mjs → url-B7VXiggp.mjs} +0 -0
@@ -0,0 +1,971 @@
1
+ import { l as parseUserOutput, t as mergeSchema, u as getDate } from "./schema-dfOF7vRb.mjs";
2
+ import { t as APIError } from "./api-B67AcvJ9.mjs";
3
+ import { c as setSessionCookie, n as deleteSessionCookie } from "./cookies-DmQmKltR.mjs";
4
+ import { r as getSessionFromCtx } from "./session-Dpj2Z_b_.mjs";
5
+ import { t as hasPermission } from "./has-permission-BxveqtYZ.mjs";
6
+ import { t as getEndpointResponse } from "./plugin-helper-BneBaGtD.mjs";
7
+ import { BASE_ERROR_CODES } from "@better-auth/core/error";
8
+ import { defineErrorCodes } from "@better-auth/core/utils";
9
+ import * as z from "zod";
10
+ import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
11
+
12
+ //#region src/plugins/admin/error-codes.ts
13
+ const ADMIN_ERROR_CODES = defineErrorCodes({
14
+ FAILED_TO_CREATE_USER: "Failed to create user",
15
+ USER_ALREADY_EXISTS: "User already exists.",
16
+ USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL: "User already exists. Use another email.",
17
+ YOU_CANNOT_BAN_YOURSELF: "You cannot ban yourself",
18
+ YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE: "You are not allowed to change users role",
19
+ YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS: "You are not allowed to create users",
20
+ YOU_ARE_NOT_ALLOWED_TO_LIST_USERS: "You are not allowed to list users",
21
+ YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS: "You are not allowed to list users sessions",
22
+ YOU_ARE_NOT_ALLOWED_TO_BAN_USERS: "You are not allowed to ban users",
23
+ YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS: "You are not allowed to impersonate users",
24
+ YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS: "You are not allowed to revoke users sessions",
25
+ YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS: "You are not allowed to delete users",
26
+ YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD: "You are not allowed to set users password",
27
+ BANNED_USER: "You have been banned from this application",
28
+ YOU_ARE_NOT_ALLOWED_TO_GET_USER: "You are not allowed to get user",
29
+ NO_DATA_TO_UPDATE: "No data to update",
30
+ YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS: "You are not allowed to update users",
31
+ YOU_CANNOT_REMOVE_YOURSELF: "You cannot remove yourself",
32
+ YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE: "You are not allowed to set a non-existent role value"
33
+ });
34
+
35
+ //#endregion
36
+ //#region src/plugins/admin/routes.ts
37
+ /**
38
+ * Ensures a valid session, if not will throw.
39
+ * Will also provide additional types on the user to include role types.
40
+ */
41
+ const adminMiddleware = createAuthMiddleware(async (ctx) => {
42
+ const session = await getSessionFromCtx(ctx);
43
+ if (!session) throw new APIError("UNAUTHORIZED");
44
+ return { session };
45
+ });
46
+ function parseRoles(roles) {
47
+ return Array.isArray(roles) ? roles.join(",") : roles;
48
+ }
49
+ const setRoleBodySchema = z.object({
50
+ userId: z.coerce.string().meta({ description: "The user id" }),
51
+ role: z.union([z.string().meta({ description: "The role to set. `admin` or `user` by default" }), z.array(z.string().meta({ description: "The roles to set. `admin` or `user` by default" }))]).meta({ description: "The role to set, this can be a string or an array of strings. Eg: `admin` or `[admin, user]`" })
52
+ });
53
+ /**
54
+ * ### Endpoint
55
+ *
56
+ * POST `/admin/set-role`
57
+ *
58
+ * ### API Methods
59
+ *
60
+ * **server:**
61
+ * `auth.api.setRole`
62
+ *
63
+ * **client:**
64
+ * `authClient.admin.setRole`
65
+ *
66
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-set-role)
67
+ */
68
+ const setRole = (opts) => createAuthEndpoint("/admin/set-role", {
69
+ method: "POST",
70
+ body: setRoleBodySchema,
71
+ requireHeaders: true,
72
+ use: [adminMiddleware],
73
+ metadata: {
74
+ openapi: {
75
+ operationId: "setUserRole",
76
+ summary: "Set the role of a user",
77
+ description: "Set the role of a user",
78
+ responses: { 200: {
79
+ description: "User role updated",
80
+ content: { "application/json": { schema: {
81
+ type: "object",
82
+ properties: { user: { $ref: "#/components/schemas/User" } }
83
+ } } }
84
+ } }
85
+ },
86
+ $Infer: { body: {} }
87
+ }
88
+ }, async (ctx) => {
89
+ if (!hasPermission({
90
+ userId: ctx.context.session.user.id,
91
+ role: ctx.context.session.user.role,
92
+ options: opts,
93
+ permissions: { user: ["set-role"] }
94
+ })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE });
95
+ const roles = opts.roles;
96
+ if (roles) {
97
+ const inputRoles = Array.isArray(ctx.body.role) ? ctx.body.role : [ctx.body.role];
98
+ for (const role of inputRoles) if (!roles[role]) throw new APIError("BAD_REQUEST", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE });
99
+ }
100
+ const updatedUser = await ctx.context.internalAdapter.updateUser(ctx.body.userId, { role: parseRoles(ctx.body.role) });
101
+ return ctx.json({ user: updatedUser });
102
+ });
103
+ const getUserQuerySchema = z.object({ id: z.string().meta({ description: "The id of the User" }) });
104
+ const getUser = (opts) => createAuthEndpoint("/admin/get-user", {
105
+ method: "GET",
106
+ query: getUserQuerySchema,
107
+ use: [adminMiddleware],
108
+ metadata: { openapi: {
109
+ operationId: "getUser",
110
+ summary: "Get an existing user",
111
+ description: "Get an existing user",
112
+ responses: { 200: {
113
+ description: "User",
114
+ content: { "application/json": { schema: {
115
+ type: "object",
116
+ properties: { user: { $ref: "#/components/schemas/User" } }
117
+ } } }
118
+ } }
119
+ } }
120
+ }, async (ctx) => {
121
+ const { id } = ctx.query;
122
+ if (!hasPermission({
123
+ userId: ctx.context.session.user.id,
124
+ role: ctx.context.session.user.role,
125
+ options: opts,
126
+ permissions: { user: ["get"] }
127
+ })) throw ctx.error("FORBIDDEN", {
128
+ message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_GET_USER,
129
+ code: "YOU_ARE_NOT_ALLOWED_TO_GET_USER"
130
+ });
131
+ const user = await ctx.context.internalAdapter.findUserById(id);
132
+ if (!user) throw new APIError("NOT_FOUND", { message: BASE_ERROR_CODES.USER_NOT_FOUND });
133
+ return parseUserOutput(ctx.context.options, user);
134
+ });
135
+ const createUserBodySchema = z.object({
136
+ email: z.string().meta({ description: "The email of the user" }),
137
+ password: z.string().meta({ description: "The password of the user" }),
138
+ name: z.string().meta({ description: "The name of the user" }),
139
+ role: z.union([z.string().meta({ description: "The role of the user" }), z.array(z.string().meta({ description: "The roles of user" }))]).optional().meta({ description: `A string or array of strings representing the roles to apply to the new user. Eg: \"user\"` }),
140
+ data: z.record(z.string(), z.any()).optional().meta({ description: "Extra fields for the user. Including custom additional fields." })
141
+ });
142
+ /**
143
+ * ### Endpoint
144
+ *
145
+ * POST `/admin/create-user`
146
+ *
147
+ * ### API Methods
148
+ *
149
+ * **server:**
150
+ * `auth.api.createUser`
151
+ *
152
+ * **client:**
153
+ * `authClient.admin.createUser`
154
+ *
155
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-create-user)
156
+ */
157
+ const createUser = (opts) => createAuthEndpoint("/admin/create-user", {
158
+ method: "POST",
159
+ body: createUserBodySchema,
160
+ metadata: {
161
+ openapi: {
162
+ operationId: "createUser",
163
+ summary: "Create a new user",
164
+ description: "Create a new user",
165
+ responses: { 200: {
166
+ description: "User created",
167
+ content: { "application/json": { schema: {
168
+ type: "object",
169
+ properties: { user: { $ref: "#/components/schemas/User" } }
170
+ } } }
171
+ } }
172
+ },
173
+ $Infer: { body: {} }
174
+ }
175
+ }, async (ctx) => {
176
+ const session = await getSessionFromCtx(ctx);
177
+ if (!session && (ctx.request || ctx.headers)) throw ctx.error("UNAUTHORIZED");
178
+ if (session) {
179
+ if (!hasPermission({
180
+ userId: session.user.id,
181
+ role: session.user.role,
182
+ options: opts,
183
+ permissions: { user: ["create"] }
184
+ })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS });
185
+ }
186
+ const email = ctx.body.email.toLowerCase();
187
+ if (!z.email().safeParse(email).success) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.INVALID_EMAIL });
188
+ if (await ctx.context.internalAdapter.findUserByEmail(email)) throw new APIError("BAD_REQUEST", { message: ADMIN_ERROR_CODES.USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL });
189
+ const user = await ctx.context.internalAdapter.createUser({
190
+ email,
191
+ name: ctx.body.name,
192
+ role: (ctx.body.role && parseRoles(ctx.body.role)) ?? opts?.defaultRole ?? "user",
193
+ ...ctx.body.data
194
+ });
195
+ if (!user) throw new APIError("INTERNAL_SERVER_ERROR", { message: ADMIN_ERROR_CODES.FAILED_TO_CREATE_USER });
196
+ const hashedPassword = await ctx.context.password.hash(ctx.body.password);
197
+ await ctx.context.internalAdapter.linkAccount({
198
+ accountId: user.id,
199
+ providerId: "credential",
200
+ password: hashedPassword,
201
+ userId: user.id
202
+ });
203
+ return ctx.json({ user });
204
+ });
205
+ const adminUpdateUserBodySchema = z.object({
206
+ userId: z.coerce.string().meta({ description: "The user id" }),
207
+ data: z.record(z.any(), z.any()).meta({ description: "The user data to update" })
208
+ });
209
+ /**
210
+ * ### Endpoint
211
+ *
212
+ * POST `/admin/update-user`
213
+ *
214
+ * ### API Methods
215
+ *
216
+ * **server:**
217
+ * `auth.api.adminUpdateUser`
218
+ *
219
+ * **client:**
220
+ * `authClient.admin.updateUser`
221
+ *
222
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-update-user)
223
+ */
224
+ const adminUpdateUser = (opts) => createAuthEndpoint("/admin/update-user", {
225
+ method: "POST",
226
+ body: adminUpdateUserBodySchema,
227
+ use: [adminMiddleware],
228
+ metadata: { openapi: {
229
+ operationId: "updateUser",
230
+ summary: "Update a user",
231
+ description: "Update a user's details",
232
+ responses: { 200: {
233
+ description: "User updated",
234
+ content: { "application/json": { schema: {
235
+ type: "object",
236
+ properties: { user: { $ref: "#/components/schemas/User" } }
237
+ } } }
238
+ } }
239
+ } }
240
+ }, async (ctx) => {
241
+ if (!hasPermission({
242
+ userId: ctx.context.session.user.id,
243
+ role: ctx.context.session.user.role,
244
+ options: opts,
245
+ permissions: { user: ["update"] }
246
+ })) throw ctx.error("FORBIDDEN", {
247
+ message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS,
248
+ code: "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS"
249
+ });
250
+ if (Object.keys(ctx.body.data).length === 0) throw new APIError("BAD_REQUEST", { message: ADMIN_ERROR_CODES.NO_DATA_TO_UPDATE });
251
+ if (ctx.body.data?.role) ctx.body.data.role = parseRoles(ctx.body.data.role);
252
+ const updatedUser = await ctx.context.internalAdapter.updateUser(ctx.body.userId, ctx.body.data);
253
+ return ctx.json(updatedUser);
254
+ });
255
+ const listUsersQuerySchema = z.object({
256
+ searchValue: z.string().optional().meta({ description: "The value to search for. Eg: \"some name\"" }),
257
+ searchField: z.enum(["email", "name"]).meta({ description: "The field to search in, defaults to email. Can be `email` or `name`. Eg: \"name\"" }).optional(),
258
+ searchOperator: z.enum([
259
+ "contains",
260
+ "starts_with",
261
+ "ends_with"
262
+ ]).meta({ description: "The operator to use for the search. Can be `contains`, `starts_with` or `ends_with`. Eg: \"contains\"" }).optional(),
263
+ limit: z.string().meta({ description: "The number of users to return" }).or(z.number()).optional(),
264
+ offset: z.string().meta({ description: "The offset to start from" }).or(z.number()).optional(),
265
+ sortBy: z.string().meta({ description: "The field to sort by" }).optional(),
266
+ sortDirection: z.enum(["asc", "desc"]).meta({ description: "The direction to sort by" }).optional(),
267
+ filterField: z.string().meta({ description: "The field to filter by" }).optional(),
268
+ filterValue: z.string().meta({ description: "The value to filter by" }).or(z.number()).or(z.boolean()).optional(),
269
+ filterOperator: z.enum([
270
+ "eq",
271
+ "ne",
272
+ "lt",
273
+ "lte",
274
+ "gt",
275
+ "gte",
276
+ "contains"
277
+ ]).meta({ description: "The operator to use for the filter" }).optional()
278
+ });
279
+ const listUsers = (opts) => createAuthEndpoint("/admin/list-users", {
280
+ method: "GET",
281
+ use: [adminMiddleware],
282
+ query: listUsersQuerySchema,
283
+ metadata: { openapi: {
284
+ operationId: "listUsers",
285
+ summary: "List users",
286
+ description: "List users",
287
+ responses: { 200: {
288
+ description: "List of users",
289
+ content: { "application/json": { schema: {
290
+ type: "object",
291
+ properties: {
292
+ users: {
293
+ type: "array",
294
+ items: { $ref: "#/components/schemas/User" }
295
+ },
296
+ total: { type: "number" },
297
+ limit: { type: "number" },
298
+ offset: { type: "number" }
299
+ },
300
+ required: ["users", "total"]
301
+ } } }
302
+ } }
303
+ } }
304
+ }, async (ctx) => {
305
+ const session = ctx.context.session;
306
+ if (!hasPermission({
307
+ userId: ctx.context.session.user.id,
308
+ role: session.user.role,
309
+ options: opts,
310
+ permissions: { user: ["list"] }
311
+ })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_LIST_USERS });
312
+ const where = [];
313
+ if (ctx.query?.searchValue) where.push({
314
+ field: ctx.query.searchField || "email",
315
+ operator: ctx.query.searchOperator || "contains",
316
+ value: ctx.query.searchValue
317
+ });
318
+ if (ctx.query?.filterValue) where.push({
319
+ field: ctx.query.filterField || "email",
320
+ operator: ctx.query.filterOperator || "eq",
321
+ value: ctx.query.filterValue
322
+ });
323
+ try {
324
+ const users = await ctx.context.internalAdapter.listUsers(Number(ctx.query?.limit) || void 0, Number(ctx.query?.offset) || void 0, ctx.query?.sortBy ? {
325
+ field: ctx.query.sortBy,
326
+ direction: ctx.query.sortDirection || "asc"
327
+ } : void 0, where.length ? where : void 0);
328
+ const total = await ctx.context.internalAdapter.countTotalUsers(where.length ? where : void 0);
329
+ return ctx.json({
330
+ users,
331
+ total,
332
+ limit: Number(ctx.query?.limit) || void 0,
333
+ offset: Number(ctx.query?.offset) || void 0
334
+ });
335
+ } catch (e) {
336
+ return ctx.json({
337
+ users: [],
338
+ total: 0
339
+ });
340
+ }
341
+ });
342
+ const listUserSessionsBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }) });
343
+ /**
344
+ * ### Endpoint
345
+ *
346
+ * POST `/admin/list-user-sessions`
347
+ *
348
+ * ### API Methods
349
+ *
350
+ * **server:**
351
+ * `auth.api.listUserSessions`
352
+ *
353
+ * **client:**
354
+ * `authClient.admin.listUserSessions`
355
+ *
356
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-list-user-sessions)
357
+ */
358
+ const listUserSessions = (opts) => createAuthEndpoint("/admin/list-user-sessions", {
359
+ method: "POST",
360
+ use: [adminMiddleware],
361
+ body: listUserSessionsBodySchema,
362
+ metadata: { openapi: {
363
+ operationId: "listUserSessions",
364
+ summary: "List user sessions",
365
+ description: "List user sessions",
366
+ responses: { 200: {
367
+ description: "List of user sessions",
368
+ content: { "application/json": { schema: {
369
+ type: "object",
370
+ properties: { sessions: {
371
+ type: "array",
372
+ items: { $ref: "#/components/schemas/Session" }
373
+ } }
374
+ } } }
375
+ } }
376
+ } }
377
+ }, async (ctx) => {
378
+ const session = ctx.context.session;
379
+ if (!hasPermission({
380
+ userId: ctx.context.session.user.id,
381
+ role: session.user.role,
382
+ options: opts,
383
+ permissions: { session: ["list"] }
384
+ })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS });
385
+ return { sessions: await ctx.context.internalAdapter.listSessions(ctx.body.userId) };
386
+ });
387
+ const unbanUserBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }) });
388
+ /**
389
+ * ### Endpoint
390
+ *
391
+ * POST `/admin/unban-user`
392
+ *
393
+ * ### API Methods
394
+ *
395
+ * **server:**
396
+ * `auth.api.unbanUser`
397
+ *
398
+ * **client:**
399
+ * `authClient.admin.unbanUser`
400
+ *
401
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-unban-user)
402
+ */
403
+ const unbanUser = (opts) => createAuthEndpoint("/admin/unban-user", {
404
+ method: "POST",
405
+ body: unbanUserBodySchema,
406
+ use: [adminMiddleware],
407
+ metadata: { openapi: {
408
+ operationId: "unbanUser",
409
+ summary: "Unban a user",
410
+ description: "Unban a user",
411
+ responses: { 200: {
412
+ description: "User unbanned",
413
+ content: { "application/json": { schema: {
414
+ type: "object",
415
+ properties: { user: { $ref: "#/components/schemas/User" } }
416
+ } } }
417
+ } }
418
+ } }
419
+ }, async (ctx) => {
420
+ const session = ctx.context.session;
421
+ if (!hasPermission({
422
+ userId: ctx.context.session.user.id,
423
+ role: session.user.role,
424
+ options: opts,
425
+ permissions: { user: ["ban"] }
426
+ })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_BAN_USERS });
427
+ const user = await ctx.context.internalAdapter.updateUser(ctx.body.userId, {
428
+ banned: false,
429
+ banExpires: null,
430
+ banReason: null,
431
+ updatedAt: /* @__PURE__ */ new Date()
432
+ });
433
+ return ctx.json({ user });
434
+ });
435
+ const banUserBodySchema = z.object({
436
+ userId: z.coerce.string().meta({ description: "The user id" }),
437
+ banReason: z.string().meta({ description: "The reason for the ban" }).optional(),
438
+ banExpiresIn: z.number().meta({ description: "The number of seconds until the ban expires" }).optional()
439
+ });
440
+ /**
441
+ * ### Endpoint
442
+ *
443
+ * POST `/admin/ban-user`
444
+ *
445
+ * ### API Methods
446
+ *
447
+ * **server:**
448
+ * `auth.api.banUser`
449
+ *
450
+ * **client:**
451
+ * `authClient.admin.banUser`
452
+ *
453
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-ban-user)
454
+ */
455
+ const banUser = (opts) => createAuthEndpoint("/admin/ban-user", {
456
+ method: "POST",
457
+ body: banUserBodySchema,
458
+ use: [adminMiddleware],
459
+ metadata: { openapi: {
460
+ operationId: "banUser",
461
+ summary: "Ban a user",
462
+ description: "Ban a user",
463
+ responses: { 200: {
464
+ description: "User banned",
465
+ content: { "application/json": { schema: {
466
+ type: "object",
467
+ properties: { user: { $ref: "#/components/schemas/User" } }
468
+ } } }
469
+ } }
470
+ } }
471
+ }, async (ctx) => {
472
+ const session = ctx.context.session;
473
+ if (!hasPermission({
474
+ userId: ctx.context.session.user.id,
475
+ role: session.user.role,
476
+ options: opts,
477
+ permissions: { user: ["ban"] }
478
+ })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_BAN_USERS });
479
+ if (!await ctx.context.internalAdapter.findUserById(ctx.body.userId)) throw new APIError("NOT_FOUND", { message: BASE_ERROR_CODES.USER_NOT_FOUND });
480
+ if (ctx.body.userId === ctx.context.session.user.id) throw new APIError("BAD_REQUEST", { message: ADMIN_ERROR_CODES.YOU_CANNOT_BAN_YOURSELF });
481
+ const user = await ctx.context.internalAdapter.updateUser(ctx.body.userId, {
482
+ banned: true,
483
+ banReason: ctx.body.banReason || opts?.defaultBanReason || "No reason",
484
+ banExpires: ctx.body.banExpiresIn ? getDate(ctx.body.banExpiresIn, "sec") : opts?.defaultBanExpiresIn ? getDate(opts.defaultBanExpiresIn, "sec") : void 0,
485
+ updatedAt: /* @__PURE__ */ new Date()
486
+ });
487
+ await ctx.context.internalAdapter.deleteSessions(ctx.body.userId);
488
+ return ctx.json({ user });
489
+ });
490
+ const impersonateUserBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }) });
491
+ /**
492
+ * ### Endpoint
493
+ *
494
+ * POST `/admin/impersonate-user`
495
+ *
496
+ * ### API Methods
497
+ *
498
+ * **server:**
499
+ * `auth.api.impersonateUser`
500
+ *
501
+ * **client:**
502
+ * `authClient.admin.impersonateUser`
503
+ *
504
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-impersonate-user)
505
+ */
506
+ const impersonateUser = (opts) => createAuthEndpoint("/admin/impersonate-user", {
507
+ method: "POST",
508
+ body: impersonateUserBodySchema,
509
+ use: [adminMiddleware],
510
+ metadata: { openapi: {
511
+ operationId: "impersonateUser",
512
+ summary: "Impersonate a user",
513
+ description: "Impersonate a user",
514
+ responses: { 200: {
515
+ description: "Impersonation session created",
516
+ content: { "application/json": { schema: {
517
+ type: "object",
518
+ properties: {
519
+ session: { $ref: "#/components/schemas/Session" },
520
+ user: { $ref: "#/components/schemas/User" }
521
+ }
522
+ } } }
523
+ } }
524
+ } }
525
+ }, async (ctx) => {
526
+ if (!hasPermission({
527
+ userId: ctx.context.session.user.id,
528
+ role: ctx.context.session.user.role,
529
+ options: opts,
530
+ permissions: { user: ["impersonate"] }
531
+ })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS });
532
+ const targetUser = await ctx.context.internalAdapter.findUserById(ctx.body.userId);
533
+ if (!targetUser) throw new APIError("NOT_FOUND", { message: "User not found" });
534
+ const session = await ctx.context.internalAdapter.createSession(targetUser.id, true, {
535
+ impersonatedBy: ctx.context.session.user.id,
536
+ expiresAt: opts?.impersonationSessionDuration ? getDate(opts.impersonationSessionDuration, "sec") : getDate(3600, "sec")
537
+ }, true);
538
+ if (!session) throw new APIError("INTERNAL_SERVER_ERROR", { message: ADMIN_ERROR_CODES.FAILED_TO_CREATE_USER });
539
+ const authCookies = ctx.context.authCookies;
540
+ deleteSessionCookie(ctx);
541
+ const dontRememberMeCookie = await ctx.getSignedCookie(ctx.context.authCookies.dontRememberToken.name, ctx.context.secret);
542
+ const adminCookieProp = ctx.context.createAuthCookie("admin_session");
543
+ await ctx.setSignedCookie(adminCookieProp.name, `${ctx.context.session.session.token}:${dontRememberMeCookie || ""}`, ctx.context.secret, authCookies.sessionToken.options);
544
+ await setSessionCookie(ctx, {
545
+ session,
546
+ user: targetUser
547
+ }, true);
548
+ return ctx.json({
549
+ session,
550
+ user: targetUser
551
+ });
552
+ });
553
+ /**
554
+ * ### Endpoint
555
+ *
556
+ * POST `/admin/stop-impersonating`
557
+ *
558
+ * ### API Methods
559
+ *
560
+ * **server:**
561
+ * `auth.api.stopImpersonating`
562
+ *
563
+ * **client:**
564
+ * `authClient.admin.stopImpersonating`
565
+ *
566
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-stop-impersonating)
567
+ */
568
+ const stopImpersonating = () => createAuthEndpoint("/admin/stop-impersonating", {
569
+ method: "POST",
570
+ requireHeaders: true
571
+ }, async (ctx) => {
572
+ const session = await getSessionFromCtx(ctx);
573
+ if (!session) throw new APIError("UNAUTHORIZED");
574
+ if (!session.session.impersonatedBy) throw new APIError("BAD_REQUEST", { message: "You are not impersonating anyone" });
575
+ const user = await ctx.context.internalAdapter.findUserById(session.session.impersonatedBy);
576
+ if (!user) throw new APIError("INTERNAL_SERVER_ERROR", { message: "Failed to find user" });
577
+ const adminCookieName = ctx.context.createAuthCookie("admin_session").name;
578
+ const adminCookie = await ctx.getSignedCookie(adminCookieName, ctx.context.secret);
579
+ if (!adminCookie) throw new APIError("INTERNAL_SERVER_ERROR", { message: "Failed to find admin session" });
580
+ const [adminSessionToken, dontRememberMeCookie] = adminCookie?.split(":");
581
+ const adminSession = await ctx.context.internalAdapter.findSession(adminSessionToken);
582
+ if (!adminSession || adminSession.session.userId !== user.id) throw new APIError("INTERNAL_SERVER_ERROR", { message: "Failed to find admin session" });
583
+ await ctx.context.internalAdapter.deleteSession(session.session.token);
584
+ await setSessionCookie(ctx, adminSession, !!dontRememberMeCookie);
585
+ return ctx.json(adminSession);
586
+ });
587
+ const revokeUserSessionBodySchema = z.object({ sessionToken: z.string().meta({ description: "The session token" }) });
588
+ /**
589
+ * ### Endpoint
590
+ *
591
+ * POST `/admin/revoke-user-session`
592
+ *
593
+ * ### API Methods
594
+ *
595
+ * **server:**
596
+ * `auth.api.revokeUserSession`
597
+ *
598
+ * **client:**
599
+ * `authClient.admin.revokeUserSession`
600
+ *
601
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-revoke-user-session)
602
+ */
603
+ const revokeUserSession = (opts) => createAuthEndpoint("/admin/revoke-user-session", {
604
+ method: "POST",
605
+ body: revokeUserSessionBodySchema,
606
+ use: [adminMiddleware],
607
+ metadata: { openapi: {
608
+ operationId: "revokeUserSession",
609
+ summary: "Revoke a user session",
610
+ description: "Revoke a user session",
611
+ responses: { 200: {
612
+ description: "Session revoked",
613
+ content: { "application/json": { schema: {
614
+ type: "object",
615
+ properties: { success: { type: "boolean" } }
616
+ } } }
617
+ } }
618
+ } }
619
+ }, async (ctx) => {
620
+ const session = ctx.context.session;
621
+ if (!hasPermission({
622
+ userId: ctx.context.session.user.id,
623
+ role: session.user.role,
624
+ options: opts,
625
+ permissions: { session: ["revoke"] }
626
+ })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS });
627
+ await ctx.context.internalAdapter.deleteSession(ctx.body.sessionToken);
628
+ return ctx.json({ success: true });
629
+ });
630
+ const revokeUserSessionsBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }) });
631
+ /**
632
+ * ### Endpoint
633
+ *
634
+ * POST `/admin/revoke-user-sessions`
635
+ *
636
+ * ### API Methods
637
+ *
638
+ * **server:**
639
+ * `auth.api.revokeUserSessions`
640
+ *
641
+ * **client:**
642
+ * `authClient.admin.revokeUserSessions`
643
+ *
644
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-revoke-user-sessions)
645
+ */
646
+ const revokeUserSessions = (opts) => createAuthEndpoint("/admin/revoke-user-sessions", {
647
+ method: "POST",
648
+ body: revokeUserSessionsBodySchema,
649
+ use: [adminMiddleware],
650
+ metadata: { openapi: {
651
+ operationId: "revokeUserSessions",
652
+ summary: "Revoke all user sessions",
653
+ description: "Revoke all user sessions",
654
+ responses: { 200: {
655
+ description: "Sessions revoked",
656
+ content: { "application/json": { schema: {
657
+ type: "object",
658
+ properties: { success: { type: "boolean" } }
659
+ } } }
660
+ } }
661
+ } }
662
+ }, async (ctx) => {
663
+ const session = ctx.context.session;
664
+ if (!hasPermission({
665
+ userId: ctx.context.session.user.id,
666
+ role: session.user.role,
667
+ options: opts,
668
+ permissions: { session: ["revoke"] }
669
+ })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS });
670
+ await ctx.context.internalAdapter.deleteSessions(ctx.body.userId);
671
+ return ctx.json({ success: true });
672
+ });
673
+ const removeUserBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }) });
674
+ /**
675
+ * ### Endpoint
676
+ *
677
+ * POST `/admin/remove-user`
678
+ *
679
+ * ### API Methods
680
+ *
681
+ * **server:**
682
+ * `auth.api.removeUser`
683
+ *
684
+ * **client:**
685
+ * `authClient.admin.removeUser`
686
+ *
687
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-remove-user)
688
+ */
689
+ const removeUser = (opts) => createAuthEndpoint("/admin/remove-user", {
690
+ method: "POST",
691
+ body: removeUserBodySchema,
692
+ use: [adminMiddleware],
693
+ metadata: { openapi: {
694
+ operationId: "removeUser",
695
+ summary: "Remove a user",
696
+ description: "Delete a user and all their sessions and accounts. Cannot be undone.",
697
+ responses: { 200: {
698
+ description: "User removed",
699
+ content: { "application/json": { schema: {
700
+ type: "object",
701
+ properties: { success: { type: "boolean" } }
702
+ } } }
703
+ } }
704
+ } }
705
+ }, async (ctx) => {
706
+ const session = ctx.context.session;
707
+ if (!hasPermission({
708
+ userId: ctx.context.session.user.id,
709
+ role: session.user.role,
710
+ options: opts,
711
+ permissions: { user: ["delete"] }
712
+ })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS });
713
+ if (ctx.body.userId === ctx.context.session.user.id) throw new APIError("BAD_REQUEST", { message: ADMIN_ERROR_CODES.YOU_CANNOT_REMOVE_YOURSELF });
714
+ if (!await ctx.context.internalAdapter.findUserById(ctx.body.userId)) throw new APIError("NOT_FOUND", { message: "User not found" });
715
+ await ctx.context.internalAdapter.deleteUser(ctx.body.userId);
716
+ return ctx.json({ success: true });
717
+ });
718
+ const setUserPasswordBodySchema = z.object({
719
+ newPassword: z.string().nonempty("newPassword cannot be empty").meta({ description: "The new password" }),
720
+ userId: z.coerce.string().nonempty("userId cannot be empty").meta({ description: "The user id" })
721
+ });
722
+ /**
723
+ * ### Endpoint
724
+ *
725
+ * POST `/admin/set-user-password`
726
+ *
727
+ * ### API Methods
728
+ *
729
+ * **server:**
730
+ * `auth.api.setUserPassword`
731
+ *
732
+ * **client:**
733
+ * `authClient.admin.setUserPassword`
734
+ *
735
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-set-user-password)
736
+ */
737
+ const setUserPassword = (opts) => createAuthEndpoint("/admin/set-user-password", {
738
+ method: "POST",
739
+ body: setUserPasswordBodySchema,
740
+ use: [adminMiddleware],
741
+ metadata: { openapi: {
742
+ operationId: "setUserPassword",
743
+ summary: "Set a user's password",
744
+ description: "Set a user's password",
745
+ responses: { 200: {
746
+ description: "Password set",
747
+ content: { "application/json": { schema: {
748
+ type: "object",
749
+ properties: { status: { type: "boolean" } }
750
+ } } }
751
+ } }
752
+ } }
753
+ }, async (ctx) => {
754
+ if (!hasPermission({
755
+ userId: ctx.context.session.user.id,
756
+ role: ctx.context.session.user.role,
757
+ options: opts,
758
+ permissions: { user: ["set-password"] }
759
+ })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD });
760
+ const { newPassword, userId } = ctx.body;
761
+ const minPasswordLength = ctx.context.password.config.minPasswordLength;
762
+ if (newPassword.length < minPasswordLength) {
763
+ ctx.context.logger.error("Password is too short");
764
+ throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.PASSWORD_TOO_SHORT });
765
+ }
766
+ const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
767
+ if (newPassword.length > maxPasswordLength) {
768
+ ctx.context.logger.error("Password is too long");
769
+ throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.PASSWORD_TOO_LONG });
770
+ }
771
+ const hashedPassword = await ctx.context.password.hash(newPassword);
772
+ await ctx.context.internalAdapter.updatePassword(userId, hashedPassword);
773
+ return ctx.json({ status: true });
774
+ });
775
+ const userHasPermissionBodySchema = z.object({
776
+ userId: z.coerce.string().optional().meta({ description: `The user id. Eg: "user-id"` }),
777
+ role: z.string().optional().meta({ description: `The role to check permission for. Eg: "admin"` })
778
+ }).and(z.union([z.object({
779
+ permission: z.record(z.string(), z.array(z.string())),
780
+ permissions: z.undefined()
781
+ }), z.object({
782
+ permission: z.undefined(),
783
+ permissions: z.record(z.string(), z.array(z.string()))
784
+ })]));
785
+ /**
786
+ * ### Endpoint
787
+ *
788
+ * POST `/admin/has-permission`
789
+ *
790
+ * ### API Methods
791
+ *
792
+ * **server:**
793
+ * `auth.api.userHasPermission`
794
+ *
795
+ * **client:**
796
+ * `authClient.admin.hasPermission`
797
+ *
798
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-has-permission)
799
+ */
800
+ const userHasPermission = (opts) => {
801
+ return createAuthEndpoint("/admin/has-permission", {
802
+ method: "POST",
803
+ body: userHasPermissionBodySchema,
804
+ metadata: {
805
+ openapi: {
806
+ description: "Check if the user has permission",
807
+ requestBody: { content: { "application/json": { schema: {
808
+ type: "object",
809
+ properties: {
810
+ permission: {
811
+ type: "object",
812
+ description: "The permission to check",
813
+ deprecated: true
814
+ },
815
+ permissions: {
816
+ type: "object",
817
+ description: "The permission to check"
818
+ }
819
+ },
820
+ required: ["permissions"]
821
+ } } } },
822
+ responses: { "200": {
823
+ description: "Success",
824
+ content: { "application/json": { schema: {
825
+ type: "object",
826
+ properties: {
827
+ error: { type: "string" },
828
+ success: { type: "boolean" }
829
+ },
830
+ required: ["success"]
831
+ } } }
832
+ } }
833
+ },
834
+ $Infer: { body: {} }
835
+ }
836
+ }, async (ctx) => {
837
+ if (!ctx.body?.permission && !ctx.body?.permissions) throw new APIError("BAD_REQUEST", { message: "invalid permission check. no permission(s) were passed." });
838
+ const session = await getSessionFromCtx(ctx);
839
+ if (!session && (ctx.request || ctx.headers)) throw new APIError("UNAUTHORIZED");
840
+ if (!session && !ctx.body.userId && !ctx.body.role) throw new APIError("BAD_REQUEST", { message: "user id or role is required" });
841
+ const user = session?.user || (ctx.body.role ? {
842
+ id: ctx.body.userId || "",
843
+ role: ctx.body.role
844
+ } : null) || await ctx.context.internalAdapter.findUserById(ctx.body.userId);
845
+ if (!user) throw new APIError("BAD_REQUEST", { message: "user not found" });
846
+ const result = hasPermission({
847
+ userId: user.id,
848
+ role: user.role,
849
+ options: opts,
850
+ permissions: ctx.body.permissions ?? ctx.body.permission
851
+ });
852
+ return ctx.json({
853
+ error: null,
854
+ success: result
855
+ });
856
+ });
857
+ };
858
+
859
+ //#endregion
860
+ //#region src/plugins/admin/schema.ts
861
+ const schema = {
862
+ user: { fields: {
863
+ role: {
864
+ type: "string",
865
+ required: false,
866
+ input: false
867
+ },
868
+ banned: {
869
+ type: "boolean",
870
+ defaultValue: false,
871
+ required: false,
872
+ input: false
873
+ },
874
+ banReason: {
875
+ type: "string",
876
+ required: false,
877
+ input: false
878
+ },
879
+ banExpires: {
880
+ type: "date",
881
+ required: false,
882
+ input: false
883
+ }
884
+ } },
885
+ session: { fields: { impersonatedBy: {
886
+ type: "string",
887
+ required: false
888
+ } } }
889
+ };
890
+
891
+ //#endregion
892
+ //#region src/plugins/admin/admin.ts
893
+ const admin = (options) => {
894
+ const opts = {
895
+ defaultRole: options?.defaultRole ?? "user",
896
+ adminRoles: options?.adminRoles ?? ["admin"],
897
+ bannedUserMessage: options?.bannedUserMessage ?? "You have been banned from this application. Please contact support if you believe this is an error.",
898
+ ...options
899
+ };
900
+ return {
901
+ id: "admin",
902
+ init() {
903
+ return { options: { databaseHooks: {
904
+ user: { create: { async before(user) {
905
+ return { data: {
906
+ role: options?.defaultRole ?? "user",
907
+ ...user
908
+ } };
909
+ } } },
910
+ session: { create: { async before(session, ctx) {
911
+ if (!ctx) return;
912
+ const user = await ctx.context.internalAdapter.findUserById(session.userId);
913
+ if (user.banned) {
914
+ if (user.banExpires && new Date(user.banExpires).getTime() < Date.now()) {
915
+ await ctx.context.internalAdapter.updateUser(session.userId, {
916
+ banned: false,
917
+ banReason: null,
918
+ banExpires: null
919
+ });
920
+ return;
921
+ }
922
+ if (ctx && (ctx.path.startsWith("/callback") || ctx.path.startsWith("/oauth2/callback"))) {
923
+ const redirectURI = ctx.context.options.onAPIError?.errorURL || `${ctx.context.baseURL}/error`;
924
+ throw ctx.redirect(`${redirectURI}?error=banned&error_description=${opts.bannedUserMessage}`);
925
+ }
926
+ throw new APIError("FORBIDDEN", {
927
+ message: opts.bannedUserMessage,
928
+ code: "BANNED_USER"
929
+ });
930
+ }
931
+ } } }
932
+ } } };
933
+ },
934
+ hooks: { after: [{
935
+ matcher(context) {
936
+ return context.path === "/list-sessions";
937
+ },
938
+ handler: createAuthMiddleware(async (ctx) => {
939
+ const response = await getEndpointResponse(ctx);
940
+ if (!response) return;
941
+ const newJson = response.filter((session) => {
942
+ return !session.impersonatedBy;
943
+ });
944
+ return ctx.json(newJson);
945
+ })
946
+ }] },
947
+ endpoints: {
948
+ setRole: setRole(opts),
949
+ getUser: getUser(opts),
950
+ createUser: createUser(opts),
951
+ adminUpdateUser: adminUpdateUser(opts),
952
+ listUsers: listUsers(opts),
953
+ listUserSessions: listUserSessions(opts),
954
+ unbanUser: unbanUser(opts),
955
+ banUser: banUser(opts),
956
+ impersonateUser: impersonateUser(opts),
957
+ stopImpersonating: stopImpersonating(),
958
+ revokeUserSession: revokeUserSession(opts),
959
+ revokeUserSessions: revokeUserSessions(opts),
960
+ removeUser: removeUser(opts),
961
+ setUserPassword: setUserPassword(opts),
962
+ userHasPermission: userHasPermission(opts)
963
+ },
964
+ $ERROR_CODES: ADMIN_ERROR_CODES,
965
+ schema: mergeSchema(schema, opts.schema),
966
+ options
967
+ };
968
+ };
969
+
970
+ //#endregion
971
+ export { admin as t };