73 lines
2.6 KiB
JavaScript
73 lines
2.6 KiB
JavaScript
/**
|
|
* This file exports a function, used by the write config endpoint.
|
|
* It will make a backup of the users conf.yml file
|
|
* and then write their new config into the main conf.yml file.
|
|
* Finally, it will call a function with the status message
|
|
*/
|
|
const fsPromises = require('fs').promises;
|
|
const path = require('path');
|
|
|
|
const MAX_CONFIG_BYTES = 256 * 1024;
|
|
|
|
// Disallow paths having path separators, control chars (NUL/CR/LF), or ..
|
|
const SAFE_FILENAME = /^(?!\.+$)[^\\/\0\r\n]+\.ya?ml$/i;
|
|
|
|
module.exports = async (newConfig, render) => {
|
|
const respond = (success, message) => render(JSON.stringify({ success, message }));
|
|
|
|
// Validate request body
|
|
if (!newConfig || typeof newConfig.config !== 'string' || newConfig.config.length === 0) {
|
|
respond(false, "Request body is missing or has an invalid 'config' field");
|
|
return;
|
|
}
|
|
if (newConfig.config.length > MAX_CONFIG_BYTES) {
|
|
respond(false, `Config exceeds maximum size of ${MAX_CONFIG_BYTES / 1024} KB`);
|
|
return;
|
|
}
|
|
|
|
// If `filename` (for sub-pages) is specified validate and set it
|
|
let usersFileName;
|
|
if (typeof newConfig.filename === 'string' && newConfig.filename) {
|
|
const base = path.basename(newConfig.filename);
|
|
if (!SAFE_FILENAME.test(base)) {
|
|
respond(false, 'Invalid filename: must be a basename ending in .yml or .yaml');
|
|
return;
|
|
}
|
|
usersFileName = base;
|
|
}
|
|
|
|
// Resolve paths
|
|
const userDataDirectory = process.env.USER_DATA_DIR || './user-data/';
|
|
const backupLocation = process.env.BACKUP_DIR || path.join(userDataDirectory, 'config-backups');
|
|
const targetFile = usersFileName || 'conf.yml';
|
|
const targetFilePath = path.join(userDataDirectory, targetFile);
|
|
|
|
const backupBase = targetFile.replace(/\.ya?ml$/i, '');
|
|
const backupFilePath = path.join(backupLocation, `${backupBase}-${Date.now()}.backup.yml`);
|
|
|
|
// Backup current config before proceeding
|
|
try {
|
|
await fsPromises.mkdir(backupLocation, { recursive: true });
|
|
await fsPromises.copyFile(targetFilePath, backupFilePath);
|
|
} catch (error) {
|
|
if (error.code !== 'ENOENT') {
|
|
respond(false, `Unable to backup ${targetFile}: ${error}`);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Write the new config
|
|
try {
|
|
await fsPromises.writeFile(targetFilePath, newConfig.config, { encoding: 'utf8' });
|
|
} catch (error) {
|
|
respond(false, `Unable to write to ${targetFile}: ${error}`);
|
|
return;
|
|
}
|
|
|
|
// If successful, then render hasn't yet been called- call it
|
|
respond(
|
|
true,
|
|
`Successfully backed up ${targetFile} to ${backupFilePath}, `
|
|
+ `and updated the contents of ${targetFilePath}`,
|
|
);
|
|
};
|