UNPKG

@paroicms/server

Version:
274 lines 9.54 kB
import { makeSecret } from "@paroicms/internal-server-lib"; import { ApiError } from "@paroicms/public-server-lib"; import { type } from "arktype"; import { formatAccount, formatAuthenticatedAccount, } from "../../common/data-format.js"; import { simpleI18n } from "../../context.js"; import { getDevAccount, getPlatformAdminAccount, isDevAccountId, parsePlatformAdminAccountId, } from "../../helpers/special-account.helpers.js"; import { executeHook } from "../../plugin-services/make-backend-plugin-service.js"; const AccountRowAT = type({ id: "number", email: "string", name: "string|null", preferences: "string|null", passwordResetToken: "string|null", "+": "reject", }).pipe((r) => ({ id: String(r.id), email: r.email, name: r.name ?? undefined, preferences: r.preferences ?? undefined, passwordResetToken: r.passwordResetToken ?? undefined, })); const FindAccountByIdAndEmailRowAT = type({ id: "number", email: "string", name: "string|null", preferences: "string|null", "+": "reject", }).pipe((r) => ({ id: String(r.id), email: r.email, name: r.name ?? undefined, preferences: r.preferences ?? undefined, })); export async function findAccountByIdAndEmail(siteContext, payload) { const account = await siteContext .cn("PaAccount as a") .select("a.id", "a.email", "a.name", "a.preferences") .where({ "a.id": payload.id, "a.email": payload.email, }) .first(); if (!account) throw new ApiError("Account not found", 404); return FindAccountByIdAndEmailRowAT.assert(account); } const FindAccountByEmailRowAT = type({ id: "number", email: "string", name: "string|null", preferences: "string|null", passwordHash: "string|null", "+": "reject", }).pipe((r) => ({ id: String(r.id), email: r.email, name: r.name ?? undefined, preferences: r.preferences ?? undefined, passwordHash: r.passwordHash ?? undefined, })); export async function findAccountByEmail(siteContext, email) { const account = await siteContext .cn("PaAccount as a") .select(["a.id", "a.email", "a.name", "a.preferences", "a.passwordHash"]) .where("a.email", email) .first(); if (!account) return; return FindAccountByEmailRowAT.assert(account); } const GetAccountRowAT = type({ id: "number", email: "string", name: "string|null", passwordResetToken: "string|null", "+": "reject", }).pipe((r) => ({ id: String(r.id), email: r.email, name: r.name ?? undefined, passwordResetToken: r.passwordResetToken ?? undefined, })); export async function getAccount(siteContext, id) { if (isDevAccountId(id)) return formatAccount(getDevAccount(id)); const parsedPlatformAccountId = parsePlatformAdminAccountId(id); if (parsedPlatformAccountId) { return formatAccount(getPlatformAdminAccount(parsedPlatformAccountId)); } const found = await siteContext .cn("PaAccount as a") .select(["a.id", "a.email", "a.name", "a.passwordResetToken"]) .where("a.id", id) .first(); if (!found) throw new ApiError("Account not found", 404); const account = GetAccountRowAT.assert(found); return formatAccount(account); } export async function getAuthenticatedAccount(siteContext, id) { if (isDevAccountId(id)) return formatAuthenticatedAccount(getDevAccount(id)); const parsedPlatformAccountId = parsePlatformAdminAccountId(id); if (parsedPlatformAccountId) { return formatAuthenticatedAccount(getPlatformAdminAccount(parsedPlatformAccountId)); } const found = await siteContext .cn("PaAccount as a") .select(["a.id", "a.email", "a.name", "a.preferences", "a.passwordResetToken"]) .where("a.id", id) .first(); if (!found) throw new ApiError("Account not found", 404); const account = AccountRowAT.assert(found); return formatAuthenticatedAccount(account); } export async function getAllAccounts(siteContext) { const accounts = await siteContext .cn("PaAccount as a") .select(["a.id", "a.email", "a.name", "a.preferences", "a.passwordResetToken"]); const parsedAccounts = accounts .map((account) => { return AccountRowAT.assert(account); }) .map((user) => formatAccount(user)); return parsedAccounts; } export async function setAccountPreferences(siteContext, accountId, values) { await siteContext .cn("PaAccount") .where({ id: accountId }) .update({ preferences: JSON.stringify(values) }); } const CreateAccountInsertedAT = type({ id: "number", "+": "reject", }).pipe((r) => ({ id: String(r.id), })); export async function createAccount(siteContext, payload) { const account = await findAccountByEmail(siteContext, payload.email); if (account) throw new ApiError("email already exists", 409); const [inserted] = await siteContext .cn("PaAccount") .insert({ email: payload.email, name: payload.name, preferences: JSON.stringify({ language: payload.language }), }) .returning("id"); const id = CreateAccountInsertedAT.assert(inserted).id; const newAccount = await findAccountById(siteContext, id); if (!newAccount) { throw new ApiError("Failed to get last insert account", 500); } if (payload.accountType === "google") { return await createGoogleAccount(siteContext, { payload, newAccount, }); } await resetAccountToken(siteContext, { id: newAccount.id, email: newAccount.email, language: payload.language, }); return formatAccount(newAccount); } async function createGoogleAccount(siteContext, { payload, newAccount, }) { const subject = simpleI18n.translate({ key: "accountCreation.subject", language: payload.language, }); const message = simpleI18n.translate({ key: "accountCreation.message", language: payload.language, args: [`${siteContext.siteUrl}/adm`], }); await executeHook(siteContext, "sendMail", { value: { subject: `${subject} - ${siteContext.fqdn}`, html: message, to: newAccount.email, }, }); return formatAccount(newAccount); } export async function updateAccount(siteContext, accountId, payload) { const account = await findAccountById(siteContext, accountId); if (!account) throw new ApiError("Account not found", 404); await siteContext.cn("PaAccount").where({ id: account.id }).update(payload); return formatAccount({ ...account, email: payload.email ?? account.email, name: payload.name ?? account.name, }); } export async function deleteAccount(siteContext, accountId) { const account = await findAccountById(siteContext, accountId); if (!account) throw new ApiError("Account not found", 404); await siteContext.cn("PaAccount").where({ id: account.id }).delete(); } export async function resetAccountPassword(siteContext, accountId) { const account = await findAccountById(siteContext, accountId); if (!account) throw new ApiError("Account not found", 404); const language = getAccountLanguage(siteContext, account); await resetAccountToken(siteContext, { id: account.id, email: account.email, language }); } const FindAccountByIdRowAT = type({ id: "number", email: "string", name: "string|null", preferences: "string|null", passwordHash: "string|null", passwordResetToken: "string|null", "+": "reject", }).pipe((data) => ({ id: String(data.id), email: data.email, name: data.name ?? undefined, preferences: data.preferences ?? undefined, passwordHash: data.passwordHash ?? undefined, passwordResetToken: data.passwordResetToken ?? undefined, })); async function findAccountById(siteContext, id) { const found = await siteContext .cn("PaAccount as a") .select([ "a.id", "a.email", "a.name", "a.preferences", "a.passwordHash", "a.passwordResetToken", ]) .where("a.id", id) .first(); if (!found) return; return FindAccountByIdRowAT.assert(found); } async function resetAccountToken(siteContext, account) { const passwordResetToken = makeSecret(60); await siteContext.cn("PaAccount").where({ id: account.id }).update({ passwordResetToken, passwordHash: null, }); const subject = simpleI18n.translate({ key: "accountPasswordReset.subject", language: account.language, }); const resetUrl = `${siteContext.siteUrl}/adm/?account=${account.id}&reset-password=${encodeURIComponent(passwordResetToken)}`; const message = simpleI18n.translate({ key: "accountPasswordReset.message", language: account.language, args: [resetUrl], }); await executeHook(siteContext, "sendMail", { value: { subject: `${subject} - ${siteContext.fqdn}`, html: message, to: account.email, }, }); } function getAccountLanguage(siteContext, account) { const preferences = account.preferences ? JSON.parse(account.preferences) : undefined; return preferences?.language ?? siteContext.siteSchema.defaultLanguage; } //# sourceMappingURL=account.queries.js.map