studiocms
Version:
Astro Native CMS for AstroDB. Built from the ground up by the Astro community.
145 lines (144 loc) • 5.05 kB
JavaScript
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
};