UNPKG

studiocms

Version:

Astro Native CMS for AstroDB. Built from the ground up by the Astro community.

164 lines (163 loc) 6.41 kB
import { User } from "studiocms:auth/lib"; import { developerConfig } from "studiocms:config"; import { apiResponseLogger } from "studiocms:logger"; import { Notifications } from "studiocms:notifier"; import { SDKCore } from "studiocms:sdk"; import { UserPermissionLevel } from "@withstudiocms/auth-kit/types"; import { ValidRanks } from "../../../consts.js"; import { AllResponse, createEffectAPIRoutes, createJsonResponse, Effect, genLogger, OptionsResponse, readAPIContextJson } from "../../../effect.js"; const { POST, DELETE, OPTIONS, ALL } = createEffectAPIRoutes( { POST: (ctx) => genLogger("studiocms/routes/api/dashboard/users.POST")(function* () { const [userHelper, notifications, sdk] = yield* Effect.all([User, Notifications, SDKCore]); const userData = ctx.locals.StudioCMS.security?.userSessionData; if (!userData?.isLoggedIn) { return apiResponseLogger(403, "Unauthorized"); } const isAuthorized = ctx.locals.StudioCMS.security?.userPermissionLevel.isAdmin; if (!isAuthorized) { return apiResponseLogger(403, "Unauthorized"); } const { id, rank, emailVerified } = yield* readAPIContextJson(ctx); if (!id || !rank) { return apiResponseLogger(400, "Invalid request"); } if (!ValidRanks.has(rank) || rank === "unknown") { return apiResponseLogger(400, "Invalid rank supplied"); } const insertData = { user: id, rank }; const user = yield* sdk.GET.users.byId(id); if (!user) { return apiResponseLogger(404, "User not found"); } const userPermissionLevel = yield* userHelper.getUserPermissionLevel(userData); const toLevel = (r) => { switch (r) { case "owner": return UserPermissionLevel.owner; case "admin": return UserPermissionLevel.admin; case "editor": return UserPermissionLevel.editor; case "visitor": return UserPermissionLevel.visitor; default: return UserPermissionLevel.unknown; } }; const targetCurrentLevel = toLevel( user.permissionsData?.rank ); const targetNewLevel = toLevel(rank); const weight = (lvl) => { switch (lvl) { case UserPermissionLevel.owner: return 4; case UserPermissionLevel.admin: return 3; case UserPermissionLevel.editor: return 2; case UserPermissionLevel.visitor: return 1; default: return 0; } }; const isAllowedToUpdateRank = weight(userPermissionLevel) > weight(targetCurrentLevel) && weight(userPermissionLevel) >= weight(targetNewLevel) && (rank !== "owner" || userPermissionLevel === UserPermissionLevel.owner); if (!isAllowedToUpdateRank) { return apiResponseLogger(403, "Unauthorized"); } const updatedData = yield* sdk.UPDATE.permissions(insertData); if (!updatedData) { return apiResponseLogger(400, "Failed to update user rank"); } if (typeof emailVerified === "boolean") { yield* sdk.AUTH.user.update(id, { emailVerified }); } yield* Effect.all([ notifications.sendUserNotification("account_updated", id), notifications.sendAdminNotification("user_updated", user.username) ]); return apiResponseLogger(200, "User rank updated successfully"); }).pipe(Notifications.Provide), DELETE: (ctx) => genLogger("studiocms/routes/api/dashboard/users.DELETE")(function* () { const [notifications, sdk] = yield* Effect.all([Notifications, SDKCore]); if (developerConfig.demoMode !== false) { return apiResponseLogger(403, "Demo mode is enabled, this action is not allowed."); } const userData = ctx.locals.StudioCMS.security?.userSessionData; if (!userData?.isLoggedIn) { return apiResponseLogger(403, "Unauthorized"); } const isAuthorized = ctx.locals.StudioCMS.security?.userPermissionLevel.isAdmin; if (!isAuthorized) { return apiResponseLogger(403, "Unauthorized"); } const { userId, username, usernameConfirm } = yield* readAPIContextJson(ctx); if (!userId || !username || !usernameConfirm) { return apiResponseLogger(400, "Invalid request"); } if (username !== usernameConfirm) { return apiResponseLogger(400, "Username does not match"); } const targetUser = yield* sdk.GET.users.byId(userId); if (!targetUser) { return apiResponseLogger(404, "User not found"); } if (targetUser.username !== username) { return apiResponseLogger(400, "Username confirmation does not match target user"); } if (userData.user?.id && userData.user.id === userId) { return apiResponseLogger(403, "You cannot delete your own account"); } const actorPerm = ctx.locals.StudioCMS.security?.userPermissionLevel; if (targetUser.permissionsData?.rank === "owner" && !actorPerm?.isOwner) { return apiResponseLogger(403, "Insufficient privileges to delete an owner account"); } if (targetUser.permissionsData?.rank === "owner") { const allUsers = yield* sdk.GET.users.all(); const ownerCount = allUsers.filter((u) => u.permissionsData?.rank === "owner").length; if (ownerCount <= 1) { return apiResponseLogger(403, "Cannot delete the last owner account"); } } const response = yield* sdk.DELETE.user(userId); if (!response) { return apiResponseLogger(400, "Failed to delete user"); } if (response.status === "error") { return apiResponseLogger(400, response.message); } yield* notifications.sendAdminNotification("user_deleted", username); return apiResponseLogger(200, response.message); }).pipe(Notifications.Provide), OPTIONS: () => Effect.try(() => OptionsResponse({ allowedMethods: ["POST", "DELETE"] })), ALL: () => Effect.try(() => AllResponse()) }, { cors: { methods: ["POST", "DELETE", "OPTIONS"] }, onError: (error) => { console.error("API Error:", error); return createJsonResponse({ error: "Internal Server Error" }, { status: 500 }); } } ); export { ALL, DELETE, OPTIONS, POST };