enhance config interpolation and validation for admin user setup

This commit is contained in:
Jakub Trávník 2025-12-02 10:07:14 +01:00
parent 34123becd0
commit a4d1cc0791

View file

@ -28,7 +28,13 @@ export const startup = async () => {
function interpolate(obj) { function interpolate(obj) {
if (typeof obj === "string") { if (typeof obj === "string") {
return obj.replace(/\$\{([^}]+)\}/g, (_, v) => process.env[v] || ""); return obj.replace(/\$\{([^}]+)\}/g, (_, v) => {
if (process.env[v] === undefined) {
logger.warn(`Environment variable '${v}' is not defined. Replacing with empty string.`);
return "";
}
return process.env[v];
});
} else if (Array.isArray(obj)) { } else if (Array.isArray(obj)) {
return obj.map(interpolate); return obj.map(interpolate);
} else if (obj && typeof obj === "object") { } else if (obj && typeof obj === "object") {
@ -53,7 +59,15 @@ export const startup = async () => {
const fs = await import("node:fs/promises"); const fs = await import("node:fs/promises");
const { RESTIC_PASS_FILE } = await import("../../core/constants.js"); const { RESTIC_PASS_FILE } = await import("../../core/constants.js");
if (configFileAdmin && configFileAdmin.recoveryKey) { if (configFileAdmin && configFileAdmin.recoveryKey) {
await fs.writeFile(RESTIC_PASS_FILE, configFileAdmin.recoveryKey, { mode: 0o600 }); const recoveryKey = configFileAdmin.recoveryKey;
if (
typeof recoveryKey !== "string" ||
recoveryKey.length !== 64 ||
!/^[a-fA-F0-9]{64}$/.test(recoveryKey)
) {
throw new Error("Recovery key must be a 64-character hex string");
}
await fs.writeFile(RESTIC_PASS_FILE, recoveryKey, { mode: 0o600 });
logger.info(`Recovery key written from config to ${RESTIC_PASS_FILE}`); logger.info(`Recovery key written from config to ${RESTIC_PASS_FILE}`);
} }
} catch (err) { } catch (err) {
@ -177,6 +191,10 @@ export const startup = async () => {
try { try {
const { authService } = await import("../auth/auth.service"); const { authService } = await import("../auth/auth.service");
if (configFileAdmin && configFileAdmin.username && (configFileAdmin.password || configFileAdmin.passwordHash)) { if (configFileAdmin && configFileAdmin.username && (configFileAdmin.password || configFileAdmin.passwordHash)) {
if (configFileAdmin.password && configFileAdmin.passwordHash) {
logger.error("Config error: Both 'password' and 'passwordHash' provided for admin user. Use only one.");
throw new Error("Invalid admin configuration");
}
const hasUsers = await authService.hasUsers(); const hasUsers = await authService.hasUsers();
if (!hasUsers) { if (!hasUsers) {
let userId: number; let userId: number;
@ -198,7 +216,7 @@ export const startup = async () => {
await db.update(usersTable).set({ hasDownloadedResticPassword: true }).where(eq(usersTable.id, userId)); await db.update(usersTable).set({ hasDownloadedResticPassword: true }).where(eq(usersTable.id, userId));
} }
} }
} else { } else if (configFileAdmin) {
logger.warn("Admin config missing required fields (username, password or passwordHash). Skipping automated admin setup."); logger.warn("Admin config missing required fields (username, password or passwordHash). Skipping automated admin setup.");
} }
} catch (err) { } catch (err) {