UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

858 lines (856 loc) • 30.9 kB
import { getDate } from "../../utils/date.mjs"; import { parseUserOutput } from "../../db/schema.mjs"; import { deleteSessionCookie, setSessionCookie } from "../../cookies/index.mjs"; import { getSessionFromCtx } from "../../api/routes/session.mjs"; import { APIError } from "../../api/index.mjs"; import { hasPermission } from "./has-permission.mjs"; import { ADMIN_ERROR_CODES } from "./error-codes.mjs"; import { BASE_ERROR_CODES } from "@better-auth/core/error"; import * as z from "zod"; import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api"; //#region src/plugins/admin/routes.ts /** * Ensures a valid session, if not will throw. * Will also provide additional types on the user to include role types. */ const adminMiddleware = createAuthMiddleware(async (ctx) => { const session = await getSessionFromCtx(ctx); if (!session) throw new APIError("UNAUTHORIZED"); return { session }; }); function parseRoles(roles) { return Array.isArray(roles) ? roles.join(",") : roles; } const setRoleBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }), 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]`" }) }); /** * ### Endpoint * * POST `/admin/set-role` * * ### API Methods * * **server:** * `auth.api.setRole` * * **client:** * `authClient.admin.setRole` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-set-role) */ const setRole = (opts) => createAuthEndpoint("/admin/set-role", { method: "POST", body: setRoleBodySchema, requireHeaders: true, use: [adminMiddleware], metadata: { openapi: { operationId: "setUserRole", summary: "Set the role of a user", description: "Set the role of a user", responses: { 200: { description: "User role updated", content: { "application/json": { schema: { type: "object", properties: { user: { $ref: "#/components/schemas/User" } } } } } } } }, $Infer: { body: {} } } }, async (ctx) => { if (!hasPermission({ userId: ctx.context.session.user.id, role: ctx.context.session.user.role, options: opts, permissions: { user: ["set-role"] } })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE }); const roles = opts.roles; if (roles) { const inputRoles = Array.isArray(ctx.body.role) ? ctx.body.role : [ctx.body.role]; 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 }); } const updatedUser = await ctx.context.internalAdapter.updateUser(ctx.body.userId, { role: parseRoles(ctx.body.role) }); return ctx.json({ user: updatedUser }); }); const getUserQuerySchema = z.object({ id: z.string().meta({ description: "The id of the User" }) }); const getUser = (opts) => createAuthEndpoint("/admin/get-user", { method: "GET", query: getUserQuerySchema, use: [adminMiddleware], metadata: { openapi: { operationId: "getUser", summary: "Get an existing user", description: "Get an existing user", responses: { 200: { description: "User", content: { "application/json": { schema: { type: "object", properties: { user: { $ref: "#/components/schemas/User" } } } } } } } } } }, async (ctx) => { const { id } = ctx.query; if (!hasPermission({ userId: ctx.context.session.user.id, role: ctx.context.session.user.role, options: opts, permissions: { user: ["get"] } })) throw ctx.error("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_GET_USER, code: "YOU_ARE_NOT_ALLOWED_TO_GET_USER" }); const user = await ctx.context.internalAdapter.findUserById(id); if (!user) throw new APIError("NOT_FOUND", { message: BASE_ERROR_CODES.USER_NOT_FOUND }); return parseUserOutput(ctx.context.options, user); }); const createUserBodySchema = z.object({ email: z.string().meta({ description: "The email of the user" }), password: z.string().meta({ description: "The password of the user" }), name: z.string().meta({ description: "The name of the user" }), 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\"` }), data: z.record(z.string(), z.any()).optional().meta({ description: "Extra fields for the user. Including custom additional fields." }) }); /** * ### Endpoint * * POST `/admin/create-user` * * ### API Methods * * **server:** * `auth.api.createUser` * * **client:** * `authClient.admin.createUser` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-create-user) */ const createUser = (opts) => createAuthEndpoint("/admin/create-user", { method: "POST", body: createUserBodySchema, metadata: { openapi: { operationId: "createUser", summary: "Create a new user", description: "Create a new user", responses: { 200: { description: "User created", content: { "application/json": { schema: { type: "object", properties: { user: { $ref: "#/components/schemas/User" } } } } } } } }, $Infer: { body: {} } } }, async (ctx) => { const session = await getSessionFromCtx(ctx); if (!session && (ctx.request || ctx.headers)) throw ctx.error("UNAUTHORIZED"); if (session) { if (!hasPermission({ userId: session.user.id, role: session.user.role, options: opts, permissions: { user: ["create"] } })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS }); } const email = ctx.body.email.toLowerCase(); if (!z.email().safeParse(email).success) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.INVALID_EMAIL }); if (await ctx.context.internalAdapter.findUserByEmail(email)) throw new APIError("BAD_REQUEST", { message: ADMIN_ERROR_CODES.USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL }); const user = await ctx.context.internalAdapter.createUser({ email, name: ctx.body.name, role: (ctx.body.role && parseRoles(ctx.body.role)) ?? opts?.defaultRole ?? "user", ...ctx.body.data }); if (!user) throw new APIError("INTERNAL_SERVER_ERROR", { message: ADMIN_ERROR_CODES.FAILED_TO_CREATE_USER }); const hashedPassword = await ctx.context.password.hash(ctx.body.password); await ctx.context.internalAdapter.linkAccount({ accountId: user.id, providerId: "credential", password: hashedPassword, userId: user.id }); return ctx.json({ user }); }); const adminUpdateUserBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }), data: z.record(z.any(), z.any()).meta({ description: "The user data to update" }) }); /** * ### Endpoint * * POST `/admin/update-user` * * ### API Methods * * **server:** * `auth.api.adminUpdateUser` * * **client:** * `authClient.admin.updateUser` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-update-user) */ const adminUpdateUser = (opts) => createAuthEndpoint("/admin/update-user", { method: "POST", body: adminUpdateUserBodySchema, use: [adminMiddleware], metadata: { openapi: { operationId: "updateUser", summary: "Update a user", description: "Update a user's details", responses: { 200: { description: "User updated", content: { "application/json": { schema: { type: "object", properties: { user: { $ref: "#/components/schemas/User" } } } } } } } } } }, async (ctx) => { if (!hasPermission({ userId: ctx.context.session.user.id, role: ctx.context.session.user.role, options: opts, permissions: { user: ["update"] } })) throw ctx.error("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS, code: "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS" }); if (Object.keys(ctx.body.data).length === 0) throw new APIError("BAD_REQUEST", { message: ADMIN_ERROR_CODES.NO_DATA_TO_UPDATE }); if (Object.prototype.hasOwnProperty.call(ctx.body.data, "role")) { if (!hasPermission({ userId: ctx.context.session.user.id, role: ctx.context.session.user.role, options: opts, permissions: { user: ["set-role"] } })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE }); const roleValue = ctx.body.data.role; const inputRoles = Array.isArray(roleValue) ? roleValue : [roleValue]; for (const role of inputRoles) { if (typeof role !== "string") throw new APIError("BAD_REQUEST", { message: ADMIN_ERROR_CODES.INVALID_ROLE_TYPE }); if (opts.roles && !opts.roles[role]) throw new APIError("BAD_REQUEST", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE }); } ctx.body.data.role = parseRoles(inputRoles); } const updatedUser = await ctx.context.internalAdapter.updateUser(ctx.body.userId, ctx.body.data); return ctx.json(updatedUser); }); const listUsersQuerySchema = z.object({ searchValue: z.string().optional().meta({ description: "The value to search for. Eg: \"some name\"" }), searchField: z.enum(["email", "name"]).meta({ description: "The field to search in, defaults to email. Can be `email` or `name`. Eg: \"name\"" }).optional(), searchOperator: z.enum([ "contains", "starts_with", "ends_with" ]).meta({ description: "The operator to use for the search. Can be `contains`, `starts_with` or `ends_with`. Eg: \"contains\"" }).optional(), limit: z.string().meta({ description: "The number of users to return" }).or(z.number()).optional(), offset: z.string().meta({ description: "The offset to start from" }).or(z.number()).optional(), sortBy: z.string().meta({ description: "The field to sort by" }).optional(), sortDirection: z.enum(["asc", "desc"]).meta({ description: "The direction to sort by" }).optional(), filterField: z.string().meta({ description: "The field to filter by" }).optional(), filterValue: z.string().meta({ description: "The value to filter by" }).or(z.number()).or(z.boolean()).optional(), filterOperator: z.enum([ "eq", "ne", "lt", "lte", "gt", "gte", "contains" ]).meta({ description: "The operator to use for the filter" }).optional() }); const listUsers = (opts) => createAuthEndpoint("/admin/list-users", { method: "GET", use: [adminMiddleware], query: listUsersQuerySchema, metadata: { openapi: { operationId: "listUsers", summary: "List users", description: "List users", responses: { 200: { description: "List of users", content: { "application/json": { schema: { type: "object", properties: { users: { type: "array", items: { $ref: "#/components/schemas/User" } }, total: { type: "number" }, limit: { type: "number" }, offset: { type: "number" } }, required: ["users", "total"] } } } } } } } }, async (ctx) => { const session = ctx.context.session; if (!hasPermission({ userId: ctx.context.session.user.id, role: session.user.role, options: opts, permissions: { user: ["list"] } })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_LIST_USERS }); const where = []; if (ctx.query?.searchValue) where.push({ field: ctx.query.searchField || "email", operator: ctx.query.searchOperator || "contains", value: ctx.query.searchValue }); if (ctx.query?.filterValue) where.push({ field: ctx.query.filterField || "email", operator: ctx.query.filterOperator || "eq", value: ctx.query.filterValue }); try { const users = await ctx.context.internalAdapter.listUsers(Number(ctx.query?.limit) || void 0, Number(ctx.query?.offset) || void 0, ctx.query?.sortBy ? { field: ctx.query.sortBy, direction: ctx.query.sortDirection || "asc" } : void 0, where.length ? where : void 0); const total = await ctx.context.internalAdapter.countTotalUsers(where.length ? where : void 0); return ctx.json({ users, total, limit: Number(ctx.query?.limit) || void 0, offset: Number(ctx.query?.offset) || void 0 }); } catch { return ctx.json({ users: [], total: 0 }); } }); const listUserSessionsBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }) }); /** * ### Endpoint * * POST `/admin/list-user-sessions` * * ### API Methods * * **server:** * `auth.api.listUserSessions` * * **client:** * `authClient.admin.listUserSessions` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-list-user-sessions) */ const listUserSessions = (opts) => createAuthEndpoint("/admin/list-user-sessions", { method: "POST", use: [adminMiddleware], body: listUserSessionsBodySchema, metadata: { openapi: { operationId: "listUserSessions", summary: "List user sessions", description: "List user sessions", responses: { 200: { description: "List of user sessions", content: { "application/json": { schema: { type: "object", properties: { sessions: { type: "array", items: { $ref: "#/components/schemas/Session" } } } } } } } } } } }, async (ctx) => { const session = ctx.context.session; if (!hasPermission({ userId: ctx.context.session.user.id, role: session.user.role, options: opts, permissions: { session: ["list"] } })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS }); return { sessions: await ctx.context.internalAdapter.listSessions(ctx.body.userId) }; }); const unbanUserBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }) }); /** * ### Endpoint * * POST `/admin/unban-user` * * ### API Methods * * **server:** * `auth.api.unbanUser` * * **client:** * `authClient.admin.unbanUser` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-unban-user) */ const unbanUser = (opts) => createAuthEndpoint("/admin/unban-user", { method: "POST", body: unbanUserBodySchema, use: [adminMiddleware], metadata: { openapi: { operationId: "unbanUser", summary: "Unban a user", description: "Unban a user", responses: { 200: { description: "User unbanned", content: { "application/json": { schema: { type: "object", properties: { user: { $ref: "#/components/schemas/User" } } } } } } } } } }, async (ctx) => { const session = ctx.context.session; if (!hasPermission({ userId: ctx.context.session.user.id, role: session.user.role, options: opts, permissions: { user: ["ban"] } })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_BAN_USERS }); const user = await ctx.context.internalAdapter.updateUser(ctx.body.userId, { banned: false, banExpires: null, banReason: null, updatedAt: /* @__PURE__ */ new Date() }); return ctx.json({ user }); }); const banUserBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }), banReason: z.string().meta({ description: "The reason for the ban" }).optional(), banExpiresIn: z.number().meta({ description: "The number of seconds until the ban expires" }).optional() }); /** * ### Endpoint * * POST `/admin/ban-user` * * ### API Methods * * **server:** * `auth.api.banUser` * * **client:** * `authClient.admin.banUser` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-ban-user) */ const banUser = (opts) => createAuthEndpoint("/admin/ban-user", { method: "POST", body: banUserBodySchema, use: [adminMiddleware], metadata: { openapi: { operationId: "banUser", summary: "Ban a user", description: "Ban a user", responses: { 200: { description: "User banned", content: { "application/json": { schema: { type: "object", properties: { user: { $ref: "#/components/schemas/User" } } } } } } } } } }, async (ctx) => { const session = ctx.context.session; if (!hasPermission({ userId: ctx.context.session.user.id, role: session.user.role, options: opts, permissions: { user: ["ban"] } })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_BAN_USERS }); if (!await ctx.context.internalAdapter.findUserById(ctx.body.userId)) throw new APIError("NOT_FOUND", { message: BASE_ERROR_CODES.USER_NOT_FOUND }); if (ctx.body.userId === ctx.context.session.user.id) throw new APIError("BAD_REQUEST", { message: ADMIN_ERROR_CODES.YOU_CANNOT_BAN_YOURSELF }); const user = await ctx.context.internalAdapter.updateUser(ctx.body.userId, { banned: true, banReason: ctx.body.banReason || opts?.defaultBanReason || "No reason", banExpires: ctx.body.banExpiresIn ? getDate(ctx.body.banExpiresIn, "sec") : opts?.defaultBanExpiresIn ? getDate(opts.defaultBanExpiresIn, "sec") : void 0, updatedAt: /* @__PURE__ */ new Date() }); await ctx.context.internalAdapter.deleteSessions(ctx.body.userId); return ctx.json({ user }); }); const impersonateUserBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }) }); /** * ### Endpoint * * POST `/admin/impersonate-user` * * ### API Methods * * **server:** * `auth.api.impersonateUser` * * **client:** * `authClient.admin.impersonateUser` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-impersonate-user) */ const impersonateUser = (opts) => createAuthEndpoint("/admin/impersonate-user", { method: "POST", body: impersonateUserBodySchema, use: [adminMiddleware], metadata: { openapi: { operationId: "impersonateUser", summary: "Impersonate a user", description: "Impersonate a user", responses: { 200: { description: "Impersonation session created", content: { "application/json": { schema: { type: "object", properties: { session: { $ref: "#/components/schemas/Session" }, user: { $ref: "#/components/schemas/User" } } } } } } } } } }, async (ctx) => { if (!hasPermission({ userId: ctx.context.session.user.id, role: ctx.context.session.user.role, options: opts, permissions: { user: ["impersonate"] } })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS }); const targetUser = await ctx.context.internalAdapter.findUserById(ctx.body.userId); if (!targetUser) throw new APIError("NOT_FOUND", { message: "User not found" }); const adminRoles = (Array.isArray(opts.adminRoles) ? opts.adminRoles : opts.adminRoles?.split(",") || []).map((role) => role.trim()); const targetUserRole = (targetUser.role || opts.defaultRole || "user").split(","); if (opts.allowImpersonatingAdmins !== true && (targetUserRole.some((role) => adminRoles.includes(role)) || opts.adminUserIds?.includes(targetUser.id))) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_CANNOT_IMPERSONATE_ADMINS }); const session = await ctx.context.internalAdapter.createSession(targetUser.id, true, { impersonatedBy: ctx.context.session.user.id, expiresAt: opts?.impersonationSessionDuration ? getDate(opts.impersonationSessionDuration, "sec") : getDate(3600, "sec") }, true); if (!session) throw new APIError("INTERNAL_SERVER_ERROR", { message: ADMIN_ERROR_CODES.FAILED_TO_CREATE_USER }); const authCookies = ctx.context.authCookies; deleteSessionCookie(ctx); const dontRememberMeCookie = await ctx.getSignedCookie(ctx.context.authCookies.dontRememberToken.name, ctx.context.secret); const adminCookieProp = ctx.context.createAuthCookie("admin_session"); await ctx.setSignedCookie(adminCookieProp.name, `${ctx.context.session.session.token}:${dontRememberMeCookie || ""}`, ctx.context.secret, authCookies.sessionToken.options); await setSessionCookie(ctx, { session, user: targetUser }, true); return ctx.json({ session, user: targetUser }); }); /** * ### Endpoint * * POST `/admin/stop-impersonating` * * ### API Methods * * **server:** * `auth.api.stopImpersonating` * * **client:** * `authClient.admin.stopImpersonating` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-stop-impersonating) */ const stopImpersonating = () => createAuthEndpoint("/admin/stop-impersonating", { method: "POST", requireHeaders: true }, async (ctx) => { const session = await getSessionFromCtx(ctx); if (!session) throw new APIError("UNAUTHORIZED"); if (!session.session.impersonatedBy) throw new APIError("BAD_REQUEST", { message: "You are not impersonating anyone" }); const user = await ctx.context.internalAdapter.findUserById(session.session.impersonatedBy); if (!user) throw new APIError("INTERNAL_SERVER_ERROR", { message: "Failed to find user" }); const adminCookieName = ctx.context.createAuthCookie("admin_session").name; const adminCookie = await ctx.getSignedCookie(adminCookieName, ctx.context.secret); if (!adminCookie) throw new APIError("INTERNAL_SERVER_ERROR", { message: "Failed to find admin session" }); const [adminSessionToken, dontRememberMeCookie] = adminCookie?.split(":"); const adminSession = await ctx.context.internalAdapter.findSession(adminSessionToken); if (!adminSession || adminSession.session.userId !== user.id) throw new APIError("INTERNAL_SERVER_ERROR", { message: "Failed to find admin session" }); await ctx.context.internalAdapter.deleteSession(session.session.token); await setSessionCookie(ctx, adminSession, !!dontRememberMeCookie); await ctx.setSignedCookie(adminCookieName, "", ctx.context.secret, { ...ctx.context.authCookies.sessionToken.options, maxAge: 0 }); return ctx.json(adminSession); }); const revokeUserSessionBodySchema = z.object({ sessionToken: z.string().meta({ description: "The session token" }) }); /** * ### Endpoint * * POST `/admin/revoke-user-session` * * ### API Methods * * **server:** * `auth.api.revokeUserSession` * * **client:** * `authClient.admin.revokeUserSession` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-revoke-user-session) */ const revokeUserSession = (opts) => createAuthEndpoint("/admin/revoke-user-session", { method: "POST", body: revokeUserSessionBodySchema, use: [adminMiddleware], metadata: { openapi: { operationId: "revokeUserSession", summary: "Revoke a user session", description: "Revoke a user session", responses: { 200: { description: "Session revoked", content: { "application/json": { schema: { type: "object", properties: { success: { type: "boolean" } } } } } } } } } }, async (ctx) => { const session = ctx.context.session; if (!hasPermission({ userId: ctx.context.session.user.id, role: session.user.role, options: opts, permissions: { session: ["revoke"] } })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS }); await ctx.context.internalAdapter.deleteSession(ctx.body.sessionToken); return ctx.json({ success: true }); }); const revokeUserSessionsBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }) }); /** * ### Endpoint * * POST `/admin/revoke-user-sessions` * * ### API Methods * * **server:** * `auth.api.revokeUserSessions` * * **client:** * `authClient.admin.revokeUserSessions` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-revoke-user-sessions) */ const revokeUserSessions = (opts) => createAuthEndpoint("/admin/revoke-user-sessions", { method: "POST", body: revokeUserSessionsBodySchema, use: [adminMiddleware], metadata: { openapi: { operationId: "revokeUserSessions", summary: "Revoke all user sessions", description: "Revoke all user sessions", responses: { 200: { description: "Sessions revoked", content: { "application/json": { schema: { type: "object", properties: { success: { type: "boolean" } } } } } } } } } }, async (ctx) => { const session = ctx.context.session; if (!hasPermission({ userId: ctx.context.session.user.id, role: session.user.role, options: opts, permissions: { session: ["revoke"] } })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS }); await ctx.context.internalAdapter.deleteSessions(ctx.body.userId); return ctx.json({ success: true }); }); const removeUserBodySchema = z.object({ userId: z.coerce.string().meta({ description: "The user id" }) }); /** * ### Endpoint * * POST `/admin/remove-user` * * ### API Methods * * **server:** * `auth.api.removeUser` * * **client:** * `authClient.admin.removeUser` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-remove-user) */ const removeUser = (opts) => createAuthEndpoint("/admin/remove-user", { method: "POST", body: removeUserBodySchema, use: [adminMiddleware], metadata: { openapi: { operationId: "removeUser", summary: "Remove a user", description: "Delete a user and all their sessions and accounts. Cannot be undone.", responses: { 200: { description: "User removed", content: { "application/json": { schema: { type: "object", properties: { success: { type: "boolean" } } } } } } } } } }, async (ctx) => { const session = ctx.context.session; if (!hasPermission({ userId: ctx.context.session.user.id, role: session.user.role, options: opts, permissions: { user: ["delete"] } })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS }); if (ctx.body.userId === ctx.context.session.user.id) throw new APIError("BAD_REQUEST", { message: ADMIN_ERROR_CODES.YOU_CANNOT_REMOVE_YOURSELF }); if (!await ctx.context.internalAdapter.findUserById(ctx.body.userId)) throw new APIError("NOT_FOUND", { message: "User not found" }); await ctx.context.internalAdapter.deleteUser(ctx.body.userId); return ctx.json({ success: true }); }); const setUserPasswordBodySchema = z.object({ newPassword: z.string().nonempty("newPassword cannot be empty").meta({ description: "The new password" }), userId: z.coerce.string().nonempty("userId cannot be empty").meta({ description: "The user id" }) }); /** * ### Endpoint * * POST `/admin/set-user-password` * * ### API Methods * * **server:** * `auth.api.setUserPassword` * * **client:** * `authClient.admin.setUserPassword` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-set-user-password) */ const setUserPassword = (opts) => createAuthEndpoint("/admin/set-user-password", { method: "POST", body: setUserPasswordBodySchema, use: [adminMiddleware], metadata: { openapi: { operationId: "setUserPassword", summary: "Set a user's password", description: "Set a user's password", responses: { 200: { description: "Password set", content: { "application/json": { schema: { type: "object", properties: { status: { type: "boolean" } } } } } } } } } }, async (ctx) => { if (!hasPermission({ userId: ctx.context.session.user.id, role: ctx.context.session.user.role, options: opts, permissions: { user: ["set-password"] } })) throw new APIError("FORBIDDEN", { message: ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD }); const { newPassword, userId } = ctx.body; const minPasswordLength = ctx.context.password.config.minPasswordLength; if (newPassword.length < minPasswordLength) { ctx.context.logger.error("Password is too short"); throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.PASSWORD_TOO_SHORT }); } const maxPasswordLength = ctx.context.password.config.maxPasswordLength; if (newPassword.length > maxPasswordLength) { ctx.context.logger.error("Password is too long"); throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.PASSWORD_TOO_LONG }); } const hashedPassword = await ctx.context.password.hash(newPassword); await ctx.context.internalAdapter.updatePassword(userId, hashedPassword); return ctx.json({ status: true }); }); const userHasPermissionBodySchema = z.object({ userId: z.coerce.string().optional().meta({ description: `The user id. Eg: "user-id"` }), role: z.string().optional().meta({ description: `The role to check permission for. Eg: "admin"` }) }).and(z.union([z.object({ permission: z.record(z.string(), z.array(z.string())), permissions: z.undefined() }), z.object({ permission: z.undefined(), permissions: z.record(z.string(), z.array(z.string())) })])); /** * ### Endpoint * * POST `/admin/has-permission` * * ### API Methods * * **server:** * `auth.api.userHasPermission` * * **client:** * `authClient.admin.hasPermission` * * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/admin#api-method-admin-has-permission) */ const userHasPermission = (opts) => { return createAuthEndpoint("/admin/has-permission", { method: "POST", body: userHasPermissionBodySchema, metadata: { openapi: { description: "Check if the user has permission", requestBody: { content: { "application/json": { schema: { type: "object", properties: { permission: { type: "object", description: "The permission to check", deprecated: true }, permissions: { type: "object", description: "The permission to check" } }, required: ["permissions"] } } } }, responses: { "200": { description: "Success", content: { "application/json": { schema: { type: "object", properties: { error: { type: "string" }, success: { type: "boolean" } }, required: ["success"] } } } } } }, $Infer: { body: {} } } }, async (ctx) => { if (!ctx.body?.permission && !ctx.body?.permissions) throw new APIError("BAD_REQUEST", { message: "invalid permission check. no permission(s) were passed." }); const session = await getSessionFromCtx(ctx); if (!session && (ctx.request || ctx.headers)) throw new APIError("UNAUTHORIZED"); if (!session && !ctx.body.userId && !ctx.body.role) throw new APIError("BAD_REQUEST", { message: "user id or role is required" }); const user = session?.user || (ctx.body.role ? { id: ctx.body.userId || "", role: ctx.body.role } : null) || await ctx.context.internalAdapter.findUserById(ctx.body.userId); if (!user) throw new APIError("BAD_REQUEST", { message: "user not found" }); const result = hasPermission({ userId: user.id, role: user.role, options: opts, permissions: ctx.body.permissions ?? ctx.body.permission }); return ctx.json({ error: null, success: result }); }); }; //#endregion export { adminUpdateUser, banUser, createUser, getUser, impersonateUser, listUserSessions, listUsers, removeUser, revokeUserSession, revokeUserSessions, setRole, setUserPassword, stopImpersonating, unbanUser, userHasPermission }; //# sourceMappingURL=routes.mjs.map