UNPKG

studiocms

Version:

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

185 lines (184 loc) 6.24 kB
import { Password, User } from "studiocms:auth/lib"; import { apiResponseLogger } from "studiocms:logger"; import { Notifications } from "studiocms:notifier"; import { SDKCore } from "studiocms:sdk"; import { z } from "astro/zod"; import { AllResponse, createEffectAPIRoutes, createJsonResponse, Effect, genLogger, OptionsResponse, parseAPIContextJson, Schema } from "../../../../../../effect.js"; import { verifyAuthTokenFromHeader } from "../../utils/auth-token.js"; class JSONData extends Schema.Class("JSONData")({ username: Schema.Union(Schema.String, Schema.Undefined), password: Schema.Union(Schema.String, Schema.Undefined), email: Schema.Union(Schema.String, Schema.Undefined), displayname: Schema.Union(Schema.String, Schema.Undefined), rank: Schema.Union( Schema.Literal("owner"), Schema.Literal("admin"), Schema.Literal("editor"), Schema.Literal("visitor"), Schema.Undefined ) }) { } const { ALL, GET, POST, OPTIONS } = createEffectAPIRoutes( { GET: (ctx) => genLogger("studioCMS:rest:v1:users:GET")(function* () { const [sdk, user] = yield* Effect.all([SDKCore, verifyAuthTokenFromHeader(ctx)]); if (user instanceof Response) { return user; } const { rank } = user; if (rank !== "owner" && rank !== "admin") { return apiResponseLogger(401, "Unauthorized"); } const users = yield* sdk.GET.users.all(); let data = users.map( ({ avatar, createdAt, email, id, name, permissionsData, updatedAt, url, username }) => ({ avatar, createdAt, email, id, name, rank: permissionsData?.rank ?? "unknown", updatedAt, url, username }) ); if (rank !== "owner") { data = data.filter((user2) => user2.rank !== "owner"); } const searchParams = ctx.url.searchParams; const rankFilter = searchParams.get("rank"); const usernameFilter = searchParams.get("username"); const nameFilter = searchParams.get("name"); const usernameFilterLower = usernameFilter?.toLowerCase(); const nameFilterLower = nameFilter?.toLowerCase(); let filteredData = data; if (rankFilter) { filteredData = filteredData.filter((u) => u.rank === rankFilter); } if (usernameFilterLower) { filteredData = filteredData.filter( (u) => (u.username ?? "").toLowerCase().includes(usernameFilterLower) ); } if (nameFilterLower) { filteredData = filteredData.filter( (u) => (u.name ?? "").toLowerCase().includes(nameFilterLower) ); } return createJsonResponse(filteredData); }), POST: (ctx) => genLogger("studioCMS:rest:v1:users:POST")(function* () { const [sdk, user, userUtils, passwordUtils, notifier] = yield* Effect.all([ SDKCore, verifyAuthTokenFromHeader(ctx), User, Password, Notifications ]); if (user instanceof Response) { return user; } const { rank } = user; if (rank !== "owner" && rank !== "admin") { return apiResponseLogger(401, "Unauthorized"); } let { username, password, email, displayname, rank: newUserRank } = yield* parseAPIContextJson(ctx, JSONData); if (!username) { return apiResponseLogger(400, "Missing field: Username is required"); } if (newUserRank === "owner" && rank !== "owner") { return apiResponseLogger(401, "Unauthorized"); } 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 (!newUserRank) { return apiResponseLogger(400, "Missing field: Rank is required"); } if (rank === "admin" && newUserRank === "owner") { return apiResponseLogger(403, "Forbidden: insufficient permission to assign owner rank"); } 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([ userUtils.verifyUsernameInput(username), passwordUtils.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"); } const newUser = yield* userUtils.createLocalUser( displayname, username, checkEmail.data, password ); const updateRank = yield* sdk.UPDATE.permissions({ user: newUser.id, rank: newUserRank }); yield* notifier.sendAdminNotification("new_user", newUser.username); return apiResponseLogger( 200, JSON.stringify({ username, email: checkEmail.data, displayname, rank: updateRank.rank, password }) ); }).pipe(Notifications.Provide), OPTIONS: () => Effect.try(() => OptionsResponse({ allowedMethods: ["GET", "POST"] })), ALL: () => Effect.try(() => AllResponse()) }, { cors: { methods: ["GET", "POST", "OPTIONS"] }, onError: (error) => { console.error("API Error:", error); return createJsonResponse({ error: "Something went wrong" }, { status: 500 }); } } ); export { ALL, GET, JSONData, OPTIONS, POST };