Initial local backup snapshot
This commit is contained in:
commit
acd9e14ba5
367 changed files with 118038 additions and 0 deletions
256
server/mailSettings.ts
Normal file
256
server/mailSettings.ts
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
|
||||
import * as db from "./db";
|
||||
import { ENV } from "./_core/env";
|
||||
import { providerDefaults, resolveMailConfig, type MailEnvInput, type SupportedMailProvider } from "@shared/mailProviders";
|
||||
|
||||
export const MAIL_SETTINGS_KEY = "system.mail.settings";
|
||||
|
||||
type StoredMailSettings = {
|
||||
smtpProvider: SupportedMailProvider;
|
||||
smtpHost?: string;
|
||||
smtpPort?: string;
|
||||
smtpUser?: string;
|
||||
smtpFrom?: string;
|
||||
smtpSecure?: boolean;
|
||||
smtpRequireTls?: boolean;
|
||||
smtpPassEncrypted?: string;
|
||||
updatedByUserId?: number;
|
||||
updatedByUserName?: string | null;
|
||||
};
|
||||
|
||||
export type MailSettingsFormInput = {
|
||||
smtpProvider: SupportedMailProvider;
|
||||
smtpHost?: string;
|
||||
smtpPort?: string;
|
||||
smtpUser?: string;
|
||||
smtpFrom?: string;
|
||||
smtpSecure?: boolean;
|
||||
smtpRequireTls?: boolean;
|
||||
smtpPass?: string;
|
||||
};
|
||||
|
||||
export type AdminMailSettings = {
|
||||
smtpProvider: SupportedMailProvider;
|
||||
smtpHost: string;
|
||||
smtpPort: string;
|
||||
smtpUser: string;
|
||||
smtpFrom: string;
|
||||
smtpSecure: boolean;
|
||||
smtpRequireTls: boolean;
|
||||
smtpPassConfigured: boolean;
|
||||
ready: boolean;
|
||||
missing: string[];
|
||||
source: "environment" | "admin" | "admin+environment";
|
||||
hasSavedConfig: boolean;
|
||||
updatedAt: string | null;
|
||||
updatedByUserId: number | null;
|
||||
updatedByUserName: string | null;
|
||||
};
|
||||
|
||||
function buildCipherKey() {
|
||||
return createHash("sha256")
|
||||
.update(ENV.cookieSecret || "local-dev-mail-settings")
|
||||
.digest();
|
||||
}
|
||||
|
||||
function encryptSecret(value: string) {
|
||||
const iv = randomBytes(12);
|
||||
const key = buildCipherKey();
|
||||
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
||||
const encrypted = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
||||
const tag = cipher.getAuthTag();
|
||||
return `${iv.toString("base64")}.${tag.toString("base64")}.${encrypted.toString("base64")}`;
|
||||
}
|
||||
|
||||
function decryptSecret(value?: string) {
|
||||
if (!value) return "";
|
||||
|
||||
const [ivBase64, tagBase64, encryptedBase64] = value.split(".");
|
||||
if (!ivBase64 || !tagBase64 || !encryptedBase64) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
const key = buildCipherKey();
|
||||
const decipher = createDecipheriv(
|
||||
"aes-256-gcm",
|
||||
key,
|
||||
Buffer.from(ivBase64, "base64")
|
||||
);
|
||||
decipher.setAuthTag(Buffer.from(tagBase64, "base64"));
|
||||
const decrypted = Buffer.concat([
|
||||
decipher.update(Buffer.from(encryptedBase64, "base64")),
|
||||
decipher.final(),
|
||||
]);
|
||||
return decrypted.toString("utf8");
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function trimOrEmpty(value?: string | null) {
|
||||
return String(value || "").trim();
|
||||
}
|
||||
|
||||
function normalizeStoredSettings(input: Partial<StoredMailSettings> | null | undefined): StoredMailSettings | null {
|
||||
if (!input?.smtpProvider) return null;
|
||||
|
||||
return {
|
||||
smtpProvider: input.smtpProvider,
|
||||
smtpHost: trimOrEmpty(input.smtpHost),
|
||||
smtpPort: trimOrEmpty(input.smtpPort),
|
||||
smtpUser: trimOrEmpty(input.smtpUser),
|
||||
smtpFrom: trimOrEmpty(input.smtpFrom),
|
||||
smtpSecure: Boolean(input.smtpSecure),
|
||||
smtpRequireTls: Boolean(input.smtpRequireTls),
|
||||
smtpPassEncrypted: trimOrEmpty(input.smtpPassEncrypted),
|
||||
updatedByUserId: input.updatedByUserId ? Number(input.updatedByUserId) : undefined,
|
||||
updatedByUserName: input.updatedByUserName?.trim() || null,
|
||||
};
|
||||
}
|
||||
|
||||
async function getStoredMailSettingsRecord() {
|
||||
const setting = await db.getPortalSettingRecord(MAIL_SETTINGS_KEY);
|
||||
if (!setting?.valeur) {
|
||||
return { setting: null, payload: null as StoredMailSettings | null };
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(setting.valeur);
|
||||
return {
|
||||
setting,
|
||||
payload: normalizeStoredSettings(parsed),
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
setting,
|
||||
payload: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function mergeMailInput(stored: StoredMailSettings | null) {
|
||||
const decryptedPassword = decryptSecret(stored?.smtpPassEncrypted);
|
||||
const sourceEntries: Array<keyof MailEnvInput> = [
|
||||
"smtpProvider",
|
||||
"smtpHost",
|
||||
"smtpPort",
|
||||
"smtpUser",
|
||||
"smtpPass",
|
||||
"smtpFrom",
|
||||
"smtpSecure",
|
||||
"smtpRequireTls",
|
||||
];
|
||||
|
||||
const storedInput: MailEnvInput = {
|
||||
smtpProvider: stored?.smtpProvider,
|
||||
smtpHost: stored?.smtpHost,
|
||||
smtpPort: stored?.smtpPort,
|
||||
smtpUser: stored?.smtpUser,
|
||||
smtpPass: decryptedPassword,
|
||||
smtpFrom: stored?.smtpFrom,
|
||||
smtpSecure: stored ? String(Boolean(stored.smtpSecure)) : "",
|
||||
smtpRequireTls: stored ? String(Boolean(stored.smtpRequireTls)) : "",
|
||||
};
|
||||
|
||||
const envInput: MailEnvInput = {
|
||||
smtpProvider: ENV.smtpProvider,
|
||||
smtpHost: ENV.smtpHost,
|
||||
smtpPort: ENV.smtpPort,
|
||||
smtpUser: ENV.smtpUser,
|
||||
smtpPass: ENV.smtpPass,
|
||||
smtpFrom: ENV.smtpFrom,
|
||||
smtpSecure: ENV.smtpSecure,
|
||||
smtpRequireTls: ENV.smtpRequireTls,
|
||||
};
|
||||
|
||||
const merged: MailEnvInput = {};
|
||||
let usesEnvironmentFallback = false;
|
||||
for (const key of sourceEntries) {
|
||||
const storedValue = trimOrEmpty(storedInput[key]);
|
||||
const envValue = trimOrEmpty(envInput[key]);
|
||||
if (storedValue) {
|
||||
merged[key] = storedInput[key];
|
||||
} else if (envValue) {
|
||||
merged[key] = envInput[key];
|
||||
if (stored) {
|
||||
usesEnvironmentFallback = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const source: AdminMailSettings["source"] = stored
|
||||
? usesEnvironmentFallback
|
||||
? "admin+environment"
|
||||
: "admin"
|
||||
: "environment";
|
||||
|
||||
return { merged, source };
|
||||
}
|
||||
|
||||
export async function getResolvedMailConfiguration() {
|
||||
const { setting, payload } = await getStoredMailSettingsRecord();
|
||||
const { merged, source } = mergeMailInput(payload);
|
||||
return {
|
||||
config: resolveMailConfig(merged),
|
||||
source,
|
||||
stored: payload,
|
||||
updatedAt: setting?.updatedAt?.toISOString?.() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAdminMailSettings(): Promise<AdminMailSettings> {
|
||||
const { config, source, stored, updatedAt } = await getResolvedMailConfiguration();
|
||||
const defaults = stored?.smtpProvider && stored.smtpProvider !== "custom"
|
||||
? providerDefaults[stored.smtpProvider]
|
||||
: null;
|
||||
|
||||
return {
|
||||
smtpProvider: config.provider,
|
||||
smtpHost: stored?.smtpHost || config.host || defaults?.host || "",
|
||||
smtpPort: stored?.smtpPort || (config.port ? String(config.port) : defaults?.port ? String(defaults.port) : ""),
|
||||
smtpUser: stored?.smtpUser || config.user || "",
|
||||
smtpFrom: stored?.smtpFrom || config.from || "",
|
||||
smtpSecure: stored ? Boolean(stored.smtpSecure) : config.secure,
|
||||
smtpRequireTls: stored ? Boolean(stored.smtpRequireTls) : config.requireTLS,
|
||||
smtpPassConfigured: Boolean(stored?.smtpPassEncrypted || ENV.smtpPass),
|
||||
ready: config.ready,
|
||||
missing: config.missing,
|
||||
source,
|
||||
hasSavedConfig: Boolean(stored),
|
||||
updatedAt,
|
||||
updatedByUserId: stored?.updatedByUserId ?? null,
|
||||
updatedByUserName: stored?.updatedByUserName ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function saveAdminMailSettings(
|
||||
input: MailSettingsFormInput,
|
||||
actor?: { id: number; name?: string | null }
|
||||
) {
|
||||
const existing = await getStoredMailSettingsRecord();
|
||||
const nextPassword = trimOrEmpty(input.smtpPass)
|
||||
? encryptSecret(trimOrEmpty(input.smtpPass))
|
||||
: existing.payload?.smtpPassEncrypted || "";
|
||||
|
||||
const payload: StoredMailSettings = {
|
||||
smtpProvider: input.smtpProvider,
|
||||
smtpHost: trimOrEmpty(input.smtpHost),
|
||||
smtpPort: trimOrEmpty(input.smtpPort),
|
||||
smtpUser: trimOrEmpty(input.smtpUser),
|
||||
smtpFrom: trimOrEmpty(input.smtpFrom),
|
||||
smtpSecure: Boolean(input.smtpSecure),
|
||||
smtpRequireTls: Boolean(input.smtpRequireTls),
|
||||
smtpPassEncrypted: nextPassword,
|
||||
updatedByUserId: actor?.id,
|
||||
updatedByUserName: actor?.name?.trim() || null,
|
||||
};
|
||||
|
||||
await db.setPortalSetting(
|
||||
MAIL_SETTINGS_KEY,
|
||||
JSON.stringify(payload),
|
||||
"Configuration SMTP et messagerie du portail"
|
||||
);
|
||||
|
||||
return getAdminMailSettings();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue