Move security checks to users.js

This commit is contained in:
Cohee
2025-02-27 21:27:39 +02:00
parent d5056a5563
commit 60448f4ce8
4 changed files with 162 additions and 152 deletions

View File

@@ -120,6 +120,72 @@ export async function ensurePublicDirectoriesExist() {
return directoriesList;
}
/**
* Prints an error message and exits the process if necessary
* @param {string} message The error message to print
* @returns {void}
*/
function logSecurityAlert(message) {
const { basicAuthMode, whitelistMode } = globalThis.COMMAND_LINE_ARGS;
if (basicAuthMode || whitelistMode) return; // safe!
console.error(color.red(message));
if (getConfigValue('securityOverride', false, 'boolean')) {
console.warn(color.red('Security has been overridden. If it\'s not a trusted network, change the settings.'));
return;
}
process.exit(1);
}
/**
* Verifies the security settings and prints warnings if necessary
* @returns {Promise<void>}
*/
export async function verifySecuritySettings() {
const { listen, basicAuthMode } = globalThis.COMMAND_LINE_ARGS;
// Skip all security checks as listen is set to false
if (!listen) {
return;
}
if (!ENABLE_ACCOUNTS) {
logSecurityAlert('Your current SillyTavern configuration is insecure (listening to non-localhost). Enable whitelisting, basic authentication or user accounts.');
}
const users = await getAllEnabledUsers();
const unprotectedUsers = users.filter(x => !x.password);
const unprotectedAdminUsers = unprotectedUsers.filter(x => x.admin);
if (unprotectedUsers.length > 0) {
console.warn(color.blue('A friendly reminder that the following users are not password protected:'));
unprotectedUsers.map(x => `${color.yellow(x.handle)} ${color.red(x.admin ? '(admin)' : '')}`).forEach(x => console.warn(x));
console.log();
console.warn(`Consider setting a password in the admin panel or by using the ${color.blue('recover.js')} script.`);
console.log();
if (unprotectedAdminUsers.length > 0) {
logSecurityAlert('If you are not using basic authentication or whitelisting, you should set a password for all admin users.');
}
}
if (basicAuthMode) {
const perUserBasicAuth = getConfigValue('perUserBasicAuth', false, 'boolean');
if (perUserBasicAuth && !ENABLE_ACCOUNTS) {
console.error(color.red(
'Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.',
));
} else if (!perUserBasicAuth) {
const basicAuthUserName = getConfigValue('basicAuthUser.username', '');
const basicAuthUserPassword = getConfigValue('basicAuthUser.password', '');
if (!basicAuthUserName || !basicAuthUserPassword) {
console.warn(color.yellow(
'Basic Authentication is enabled, but username or password is not set or empty!',
));
}
}
}
}
export function cleanUploads() {
try {
const uploadsPath = path.join(globalThis.DATA_ROOT, UPLOADS_DIRECTORY);
@@ -817,6 +883,31 @@ export function requireLoginMiddleware(request, response, next) {
return next();
}
/**
* Middleware to host the login page.
* @param {import('express').Request} request Request object
* @param {import('express').Response} response Response object
*/
export async function loginPageMiddleware(request, response) {
if (!ENABLE_ACCOUNTS) {
console.log('User accounts are disabled. Redirecting to index page.');
return response.redirect('/');
}
try {
const { basicAuthMode } = globalThis.COMMAND_LINE_ARGS;
const autoLogin = await tryAutoLogin(request, basicAuthMode);
if (autoLogin) {
return response.redirect('/');
}
} catch (error) {
console.error('Error during auto-login:', error);
}
return response.sendFile('login.html', { root: path.join(process.cwd(), 'public') });
}
/**
* Creates a route handler for serving files from a specific directory.
* @param {(req: import('express').Request) => string} directoryFn A function that returns the directory path to serve files from