141 lines
4.0 KiB
JavaScript
141 lines
4.0 KiB
JavaScript
import net from "node:net";
|
|
import tls from "node:tls";
|
|
import { db, setSetting } from "./db.mjs";
|
|
import { requirePermission } from "./auth.mjs";
|
|
import { json, readBody } from "./http.mjs";
|
|
import { permissions } from "./permissions.mjs";
|
|
|
|
function getSettingValue(key, fallback = "") {
|
|
return db.prepare("SELECT value FROM system_settings WHERE key = ?").get(key)?.value ?? fallback;
|
|
}
|
|
|
|
function testSmtpConnection({ host, port }) {
|
|
return new Promise((resolve, reject) => {
|
|
const socketFactory = Number(port) === 465 ? tls.connect : net.connect;
|
|
const socket = socketFactory({
|
|
host,
|
|
port: Number(port),
|
|
servername: host,
|
|
timeout: 10000,
|
|
});
|
|
let buffer = "";
|
|
|
|
const finish = (result) => {
|
|
socket.removeAllListeners();
|
|
socket.end();
|
|
resolve(result);
|
|
};
|
|
|
|
socket.on("connect", () => {
|
|
if (Number(port) === 465) {
|
|
socket.write("EHLO vitepress-cms.local\r\n");
|
|
}
|
|
});
|
|
|
|
socket.on("secureConnect", () => {
|
|
socket.write("EHLO vitepress-cms.local\r\n");
|
|
});
|
|
|
|
socket.on("data", (chunk) => {
|
|
buffer += chunk.toString("utf-8");
|
|
|
|
if (buffer.startsWith("220")) {
|
|
socket.write("EHLO vitepress-cms.local\r\n");
|
|
}
|
|
|
|
if (buffer.includes("\n250 ") || buffer.includes("\n250-") || buffer.startsWith("250")) {
|
|
socket.write("QUIT\r\n");
|
|
finish({
|
|
ok: true,
|
|
greeting: buffer.split(/\r?\n/).filter(Boolean).slice(0, 3).join(" / "),
|
|
});
|
|
}
|
|
});
|
|
|
|
socket.on("timeout", () => {
|
|
socket.destroy();
|
|
reject(new Error("SMTP connection timed out"));
|
|
});
|
|
|
|
socket.on("error", reject);
|
|
});
|
|
}
|
|
|
|
function publicSetting(row) {
|
|
return {
|
|
key: row.key,
|
|
value: row.encrypted ? "" : row.value,
|
|
encrypted: Boolean(row.encrypted),
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
async function handleGetSettings(request, response) {
|
|
if (!requirePermission(request, response, permissions.settingsManage)) return;
|
|
|
|
const rows = db
|
|
.prepare("SELECT key, value, encrypted, updated_at FROM system_settings ORDER BY key")
|
|
.all();
|
|
|
|
json(response, 200, { settings: rows.map(publicSetting) });
|
|
}
|
|
|
|
async function handleSaveSettings(request, response) {
|
|
if (!requirePermission(request, response, permissions.settingsManage)) return;
|
|
|
|
const body = JSON.parse(await readBody(request) || "{}");
|
|
const settings = Array.isArray(body.settings) ? body.settings : [];
|
|
|
|
settings.forEach((setting) => {
|
|
if (!setting.key) return;
|
|
const existing = db.prepare("SELECT encrypted FROM system_settings WHERE key = ?").get(String(setting.key));
|
|
const shouldKeepExistingSecret = Boolean(existing?.encrypted) && Boolean(setting.encrypted) && !String(setting.value ?? "");
|
|
if (shouldKeepExistingSecret) return;
|
|
|
|
setSetting(String(setting.key), String(setting.value ?? ""), Boolean(setting.encrypted));
|
|
});
|
|
|
|
json(response, 200, { ok: true });
|
|
}
|
|
|
|
async function handleTestSmtp(request, response) {
|
|
if (!requirePermission(request, response, permissions.settingsManage)) return;
|
|
|
|
const host = getSettingValue("smtp.host");
|
|
const port = getSettingValue("smtp.port", "587");
|
|
|
|
if (!host) {
|
|
json(response, 400, { error: "SMTP host is required" });
|
|
return;
|
|
}
|
|
|
|
const result = await testSmtpConnection({ host, port });
|
|
json(response, 200, result);
|
|
}
|
|
|
|
export async function handleSettingsApi(request, response, url) {
|
|
try {
|
|
if (url.pathname === "/api/admin/settings" && request.method === "GET") {
|
|
await handleGetSettings(request, response);
|
|
return true;
|
|
}
|
|
|
|
if (url.pathname === "/api/admin/settings" && request.method === "PUT") {
|
|
await handleSaveSettings(request, response);
|
|
return true;
|
|
}
|
|
|
|
if (url.pathname === "/api/admin/settings/test-smtp" && request.method === "POST") {
|
|
await handleTestSmtp(request, response);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} catch (error) {
|
|
json(response, 500, {
|
|
error: error instanceof Error ? error.message : "Unknown settings API error",
|
|
});
|
|
return true;
|
|
}
|
|
}
|