UNPKG

studiocms

Version:

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

145 lines (144 loc) 5.05 kB
import { Password, User } from "studiocms:auth/lib"; import { apiResponseLogger } from "studiocms:logger"; import { Notifications } from "studiocms:notifier"; import { SDKCore } from "studiocms:sdk"; import { UserPermissionLevel } from "@withstudiocms/auth-kit/types"; import { z } from "astro/zod"; import { ValidRanks } from "../../../consts.js"; import { AllResponse, createEffectAPIRoutes, createJsonResponse, Effect, genLogger, OptionsResponse, readAPIContextJson } from "../../../effect.js"; const { POST, OPTIONS, ALL } = createEffectAPIRoutes( { POST: (ctx) => genLogger("studiocms/routes/api/dashboard/create-user.POST")(function* () { const [pass, userHelper, notify, sdk] = yield* Effect.all([ Password, 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"); } let { username, password, email, displayname, rank } = yield* readAPIContextJson(ctx); if (!username) { return apiResponseLogger(400, "Missing field: Username is required"); } if (!password) { password = yield* sdk.generateRandomPassword(12); } if (!email) { return apiResponseLogger(400, "Missing field: Email is required"); } if (!displayname) { return apiResponseLogger(400, "Missing field: Display name is required"); } if (!rank) { return apiResponseLogger(400, "Missing field: Rank is required"); } if (!ValidRanks.has(rank) || rank === "unknown") { return apiResponseLogger(400, "Invalid rank"); } const callerPerm = yield* userHelper.getUserPermissionLevel(userData); const rankToPerm = (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 targetPerm = rankToPerm(rank); const permWeight = (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; } }; if (rank === "owner" && callerPerm !== UserPermissionLevel.owner) { return createJsonResponse({ error: "Forbidden" }, { status: 403 }); } if (permWeight(callerPerm) < permWeight(targetPerm)) { return createJsonResponse({ error: "Forbidden" }, { status: 403 }); } const checkEmail = z.coerce.string().email({ message: "Email address is invalid" }).safeParse(email); if (!checkEmail.success) { return apiResponseLogger(400, `Invalid email: ${checkEmail.error.message}`); } const [verifyUsernameResponse, verifyPasswordResponse, { usernameSearch, emailSearch }] = yield* Effect.all([ userHelper.verifyUsernameInput(username), pass.verifyPasswordStrength(password), sdk.AUTH.user.searchUsersForUsernameOrEmail(username, checkEmail.data) ]); if (verifyUsernameResponse !== true) { return apiResponseLogger(400, verifyUsernameResponse); } if (verifyPasswordResponse !== true) { return apiResponseLogger(400, verifyPasswordResponse); } if (usernameSearch.length > 0) { return apiResponseLogger(400, "Invalid username: Username is already in use"); } if (emailSearch.length > 0) { return apiResponseLogger(400, "Invalid email: Email is already in use"); } yield* userHelper.createLocalUser(displayname, username, email, password).pipe( Effect.flatMap( (newUser) => sdk.UPDATE.permissions({ user: newUser.id, rank }) ), Effect.tap(() => notify.sendAdminNotification("new_user", username)) ); return apiResponseLogger( 200, JSON.stringify({ username, email, displayname, rank, password }) ); }).pipe(Notifications.Provide), OPTIONS: () => Effect.try(() => OptionsResponse({ allowedMethods: ["POST"] })), ALL: () => Effect.try(() => AllResponse()) }, { cors: { methods: ["POST", "OPTIONS"] }, onError: (error) => { console.error("API Error:", error); return createJsonResponse( { error: "Internal Server Error" }, { status: 500 } ); } } ); export { ALL, OPTIONS, POST };