studiocms
Version:
Astro Native CMS for AstroDB. Built from the ground up by the Astro community.
185 lines (184 loc) • 6.24 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 { 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
};