No exceptions on missing error webpages - Create a `safeReadFileSync()` function in `src/utils.js` to wrap around `fs.readFileSync()` - Migrate error-webpage loads to use `safeReadFileSync()`, with default values of an empty string - Move the 404 error middleware to explicitly only be called *after* extensions are registered

6099ffece134bcde9197c1be51a0b23f885465b5

Spappz <34202141+Spappz@users.noreply.github.com>

Signed
4 files changed, +29 -11Showing whitespace changes
server.js+12 -6
@@ -67,6 +67,7 @@ import {
67 forwardFetchResponse,67 forwardFetchResponse,
68 removeColorFormatting,68 removeColorFormatting,
69 getSeparator,69 getSeparator,
70 safeReadFileSync,
70} from './src/util.js';71} from './src/util.js';
71import { UPLOADS_DIRECTORY } from './src/constants.js';72import { UPLOADS_DIRECTORY } from './src/constants.js';
72import { ensureThumbnailCache } from './src/endpoints/thumbnails.js';73import { ensureThumbnailCache } from './src/endpoints/thumbnails.js';
@@ -615,12 +616,6 @@ app.use('/api/backends/scale-alt', scaleAltRouter);
615app.use('/api/speech', speechRouter);616app.use('/api/speech', speechRouter);
616app.use('/api/azure', azureRouter);617app.use('/api/azure', azureRouter);
617618
618// If all other middlewares fail, send 404 error.
619const notFoundWebpage = fs.readFileSync('./public/error/url-not-found.html', { encoding: 'utf-8' });
620app.use((req, res, next) => {
621 res.status(404).send(notFoundWebpage);
622});
623
624const tavernUrlV6 = new URL(619const tavernUrlV6 = new URL(
625 (cliArguments.ssl ? 'https://' : 'http://') +620 (cliArguments.ssl ? 'https://' : 'http://') +
626 (listen ? '[::]' : '[::1]') +621 (listen ? '[::]' : '[::1]') +
@@ -927,6 +922,16 @@ async function verifySecuritySettings() {
927 }922 }
928}923}
929924
925/**
926 * Registers a not-found error response if a not-found error page exists. Should only be called after all other middlewares have been registered.
927 */
928function apply404Middleware() {
929 const notFoundWebpage = safeReadFileSync('./public/error/url-not-found.html') ?? '';
930 app.use((req, res) => {
931 res.status(404).send(notFoundWebpage);
932 });
933}
934
930// User storage module needs to be initialized before starting the server935// User storage module needs to be initialized before starting the server
931initUserStorage(dataRoot)936initUserStorage(dataRoot)
932 .then(ensurePublicDirectoriesExist)937 .then(ensurePublicDirectoriesExist)
@@ -934,4 +939,5 @@ initUserStorage(dataRoot)
934 .then(migrateSystemPrompts)939 .then(migrateSystemPrompts)
935 .then(verifySecuritySettings)940 .then(verifySecuritySettings)
936 .then(preSetupTasks)941 .then(preSetupTasks)
942 .then(apply404Middleware)
937 .finally(startServer);943 .finally(startServer);
src/middleware/basicAuth.js+2 -3
@@ -5,13 +5,12 @@
5import { Buffer } from 'node:buffer';5import { Buffer } from 'node:buffer';
6import storage from 'node-persist';6import storage from 'node-persist';
7import { getAllUserHandles, toKey, getPasswordHash } from '../users.js';7import { getAllUserHandles, toKey, getPasswordHash } from '../users.js';
8import { getConfig, getConfigValue } from '../util.js';8import { getConfig, getConfigValue, safeReadFileSync } from '../util.js';
9import { readFileSync } from 'node:fs';
109
11const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);10const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
12const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);11const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
1312
14const unauthorizedWebpage = readFileSync('./public/error/unauthorized.html', { encoding: 'utf-8' });13const unauthorizedWebpage = safeReadFileSync('./public/error/unauthorized.html') ?? '';
15const unauthorizedResponse = (res) => {14const unauthorizedResponse = (res) => {
16 res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"');15 res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"');
17 return res.status(401).send(unauthorizedWebpage);16 return res.status(401).send(unauthorizedWebpage);
src/middleware/whitelist.js+4 -2
@@ -5,13 +5,15 @@ import Handlebars from 'handlebars';
5import ipMatching from 'ip-matching';5import ipMatching from 'ip-matching';
66
7import { getIpFromRequest } from '../express-common.js';7import { getIpFromRequest } from '../express-common.js';
8import { color, getConfigValue } from '../util.js';8import { color, getConfigValue, safeReadFileSync } from '../util.js';
99
10const whitelistPath = path.join(process.cwd(), './whitelist.txt');10const whitelistPath = path.join(process.cwd(), './whitelist.txt');
11const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false);11const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false);
12let whitelist = getConfigValue('whitelist', []);12let whitelist = getConfigValue('whitelist', []);
13let knownIPs = new Set();13let knownIPs = new Set();
14const forbiddenWebpage = Handlebars.compile(fs.readFileSync('./public/error/forbidden-by-whitelist.html', 'utf-8'));14const forbiddenWebpage = Handlebars.compile(
15 safeReadFileSync('./public/error/forbidden-by-whitelist.html') ?? '',
16);
1517
16if (fs.existsSync(whitelistPath)) {18if (fs.existsSync(whitelistPath)) {
17 try {19 try {
src/util.js+11 -0
@@ -871,3 +871,14 @@ export class MemoryLimitedMap {
871 return this.map[Symbol.iterator]();871 return this.map[Symbol.iterator]();
872 }872 }
873}873}
874
875/**
876 * A 'safe' version of `fs.readFileSync()`. Returns the contents of a file if it exists, falling back to a default value if not.
877 * @param {string} filePath Path of the file to be read.
878 * @param {Parameters<typeof fs.readFileSync>[1]} options Options object to pass through to `fs.readFileSync()` (default: `{ encoding: 'utf-8' }`).
879 * @returns The contents at `filePath` if it exists, or `null` if not.
880 */
881export function safeReadFileSync(filePath, options = { encoding: 'utf-8' }) {
882 if (fs.existsSync(filePath)) return fs.readFileSync(filePath, options);
883 return null;
884}