UNPKG

studiocms

Version:

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

158 lines (157 loc) 5.7 kB
import { User } from "studiocms:auth/lib"; import { apiResponseLogger } from "studiocms:logger"; import { Mailer } from "studiocms:mailer"; import getTemplate from "studiocms:mailer/templates"; import { Notifications } from "studiocms:notifier"; import { SDKCore } from "studiocms:sdk"; import { z } from "astro/zod"; import { AllResponse, appendSearchParamsToUrl, createEffectAPIRoutes, createJsonResponse, Effect, genLogger, OptionsResponse, pipe, readAPIContextJson } from "../../../effect.js"; const generateResetUrl = ({ locals: { StudioCMS: { routeMap: { mainLinks: { dashboardIndex } } } } }, baseUrl, { id, userId, token }) => { const resetURL = new URL(`${dashboardIndex}/password-reset`, baseUrl); return pipe( resetURL, appendSearchParamsToUrl("userid", userId), appendSearchParamsToUrl("token", token), appendSearchParamsToUrl("id", id) ); }; const noMailerError = (message, resetLink) => `Failed to send email: ${message}. You can provide the following Reset link to your User: ${resetLink}`; const { POST, OPTIONS, ALL } = createEffectAPIRoutes( { POST: (ctx) => genLogger("studiocms/routes/api/dashboard/create-user-invite.POST")(function* () { const [userHelper, mailer, notify, sdk] = yield* Effect.all([ User, Mailer, Notifications, SDKCore ]); const siteConfig = ctx.locals.StudioCMS.siteConfig.data; if (!siteConfig) { return apiResponseLogger(500, "Failed to get site config"); } 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 { username, email, displayname, rank, originalUrl } = yield* readAPIContextJson(ctx); if (!username) { return apiResponseLogger(400, "Missing field: Username is required"); } 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"); } const userPerms = ctx.locals.StudioCMS.security?.userPermissionLevel; if (rank === "owner" && !userPerms?.isOwner) { return apiResponseLogger(403, "Unauthorized"); } 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, { usernameSearch, emailSearch }] = yield* Effect.all([ userHelper.verifyUsernameInput(username), sdk.AUTH.user.searchUsersForUsernameOrEmail(username, checkEmail.data) ]); if (verifyUsernameResponse !== true) { return apiResponseLogger(400, verifyUsernameResponse); } 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 token = yield* sdk.AUTH.user.create( { username, email: checkEmail.data, name: displayname, createdAt: /* @__PURE__ */ new Date(), id: crypto.randomUUID() }, rank ).pipe(Effect.flatMap((newUser) => sdk.resetTokenBucket.new(newUser.id))); if (!token) { return apiResponseLogger(500, "Failed to create reset token"); } const resetLink = generateResetUrl(ctx, originalUrl, token); yield* notify.sendAdminNotification("new_user", username); if (siteConfig.enableMailer) { const checkMailConnection = yield* mailer.verifyMailConnection; if (!checkMailConnection) { return apiResponseLogger( 500, noMailerError("Failed to connect to mail server", resetLink) ); } if ("error" in checkMailConnection) { return apiResponseLogger( 500, noMailerError("Failed to connect to mail server", resetLink) ); } const htmlTemplate = getTemplate("userInvite"); const mailResponse = yield* mailer.sendMail({ to: checkEmail.data, subject: `You have been invited to join ${siteConfig.title}!`, html: htmlTemplate({ title: siteConfig.title, link: resetLink }) }); if (!mailResponse) { return apiResponseLogger(500, noMailerError("Failed to send email", resetLink)); } if ("error" in mailResponse) { return apiResponseLogger(500, noMailerError(mailResponse.error, resetLink)); } return apiResponseLogger(200, "User invite created and email sent"); } return apiResponseLogger(200, resetLink.toString()); }).pipe(Mailer.Provide, 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 };