server: allow Flood not to serve static assets

This commit is contained in:
Jesse Chan
2020-12-10 01:22:49 +08:00
parent 055673738a
commit 559ecaa0d8
13 changed files with 65 additions and 87 deletions
-2
View File
@@ -30,8 +30,6 @@ jobs:
- run: sudo apt-get install -y rtorrent qbittorrent-nox transmission-daemon
- run: npm ci --no-optional
- run: npm run build
- run: npm run start -- --help
- run: npm test || npm test || npm test
- uses: codecov/codecov-action@v1
+7
View File
@@ -104,6 +104,12 @@ const {argv} = require('yargs')
describe: 'Allowed path for file operations, can be called multiple times',
type: 'string',
})
.option('assets', {
default: true,
describe: 'ADVANCED: Serve static assets',
hidden: true,
type: 'boolean',
})
.option('dbclean', {
default: 1000 * 60 * 60,
describe: 'ADVANCED: Interval between database purge',
@@ -285,6 +291,7 @@ const CONFIG = {
sslKey: argv.sslkey || path.resolve(path.join(argv.rundir, 'key.pem')),
sslCert: argv.sslcert || path.resolve(path.join(argv.rundir, 'fullchain.pem')),
allowedPaths: allowedPaths.length > 0 ? allowedPaths : undefined,
serveAssets: argv.assets,
};
module.exports = CONFIG;
-34
View File
@@ -38,7 +38,6 @@
"@types/feedsub": "^0.7.1",
"@types/fs-extra": "^9.0.4",
"@types/geoip-country": "^4.0.0",
"@types/glob": "^7.1.3",
"@types/http-errors": "^1.8.0",
"@types/jest": "^26.0.17",
"@types/jsonwebtoken": "^8.5.0",
@@ -105,7 +104,6 @@
"frontmatter-markdown-loader": "^3.6.2",
"fs-extra": "^9.0.1",
"get-user-locale": "^1.4.0",
"glob": "^7.1.6",
"hash-wasm": "^4.4.1",
"html-webpack-plugin": "^5.0.0-alpha.10",
"http-errors": "^1.8.0",
@@ -2792,16 +2790,6 @@
"integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==",
"dev": true
},
"node_modules/@types/glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==",
"dev": true,
"dependencies": {
"@types/minimatch": "*",
"@types/node": "*"
}
},
"node_modules/@types/graceful-fs": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz",
@@ -2937,12 +2925,6 @@
"integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==",
"dev": true
},
"node_modules/@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
"dev": true
},
"node_modules/@types/minipass": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@types/minipass/-/minipass-2.2.0.tgz",
@@ -27211,16 +27193,6 @@
"integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==",
"dev": true
},
"@types/glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==",
"dev": true,
"requires": {
"@types/minimatch": "*",
"@types/node": "*"
}
},
"@types/graceful-fs": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz",
@@ -27355,12 +27327,6 @@
"integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==",
"dev": true
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
"dev": true
},
"@types/minipass": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@types/minipass/-/minipass-2.2.0.tgz",
-2
View File
@@ -87,7 +87,6 @@
"@types/feedsub": "^0.7.1",
"@types/fs-extra": "^9.0.4",
"@types/geoip-country": "^4.0.0",
"@types/glob": "^7.1.3",
"@types/http-errors": "^1.8.0",
"@types/jest": "^26.0.17",
"@types/jsonwebtoken": "^8.5.0",
@@ -154,7 +153,6 @@
"frontmatter-markdown-loader": "^3.6.2",
"fs-extra": "^9.0.1",
"get-user-locale": "^1.4.0",
"glob": "^7.1.6",
"hash-wasm": "^4.4.1",
"html-webpack-plugin": "^5.0.0-alpha.10",
"http-errors": "^1.8.0",
+1
View File
@@ -8,6 +8,7 @@ const temporaryRuntimeDirectory = path.resolve(os.tmpdir(), `flood.test.${crypto
process.argv = ['node', 'flood'];
process.argv.push('--rundir', temporaryRuntimeDirectory);
process.argv.push('--auth', 'default');
process.argv.push('--assets', 'false');
afterAll(() => {
if (process.env.CI !== 'true') {
+1
View File
@@ -26,6 +26,7 @@ process.argv.push('--auth', 'none');
process.argv.push('--qburl', `http://127.0.0.1:${qbtPort}`);
process.argv.push('--qbuser', 'admin');
process.argv.push('--qbpass', 'adminadmin');
process.argv.push('--assets', 'false');
afterAll(() => {
qBittorrentDaemon.kill('SIGKILL');
+1
View File
@@ -27,6 +27,7 @@ process.argv.push('--allowedpath', temporaryRuntimeDirectory);
process.argv.push('--rtorrent');
process.argv.push('--rtconfig', `${temporaryRuntimeDirectory}/rtorrent.rc`);
process.argv.push('--test');
process.argv.push('--assets', 'false');
afterAll((done) => {
process.kill(Number(fs.readFileSync(`${temporaryRuntimeDirectory}/rtorrent.pid`).toString()));
+1
View File
@@ -39,6 +39,7 @@ process.argv.push('--auth', 'none');
process.argv.push('--trurl', `http://127.0.0.1:${rpcPort}/transmission/rpc`);
process.argv.push('--truser', 'transmission');
process.argv.push('--trpass', 'transmission');
process.argv.push('--assets', 'false');
afterAll((done) => {
transmissionProcess.on('close', () => {
+38 -22
View File
@@ -10,6 +10,7 @@ import path from 'path';
import type {UserInDatabase} from '@shared/schema/Auth';
import apiRoutes from './routes/api';
import config from '../config';
import passportConfig from './config/passport';
import paths from '../shared/config/paths';
import Users from './models/Users';
@@ -32,12 +33,47 @@ if (process.env.NODE_ENV !== 'development') {
}
app.set('strict routing', true);
app.set('etag', false);
app.set('trust proxy', 'loopback');
app.use(morgan('dev'));
if (config.serveAssets !== false) {
// Disable ETag
app.set('etag', false);
// Enable compression
app.use(compression());
// Static assets
app.use(paths.servedPath, express.static(paths.appDist));
// Client app routes, serve index.html and client js will figure it out
const html = fs.readFileSync(path.join(paths.appDist, 'index.html'), {
encoding: 'utf8',
});
app.get(`${paths.servedPath}login`, (_req, res) => {
res.send(html);
});
app.get(`${paths.servedPath}register`, (_req, res) => {
res.send(html);
});
app.get(`${paths.servedPath}overview`, (_req, res) => {
res.send(html);
});
} else {
// no-op res.flush() as compression is not handled by Express
app.use((_req, res, next) => {
res.flush = () => {
// do nothing.
};
next();
});
}
app.use(passport.initialize());
app.use(compression());
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded({extended: false, limit: '50mb'}));
app.use(cookieParser());
@@ -46,24 +82,4 @@ passportConfig(passport);
app.use(`${paths.servedPath}api`, apiRoutes);
// After routes, look for static assets.
app.use(paths.servedPath, express.static(paths.appDist));
// Client app routes, serve index.html and client js will figure it out
const html = fs.readFileSync(path.join(paths.appDist, 'index.html'), {
encoding: 'utf8',
});
app.get(`${paths.servedPath}login`, (_req, res) => {
res.send(html);
});
app.get(`${paths.servedPath}register`, (_req, res) => {
res.send(html);
});
app.get(`${paths.servedPath}overview`, (_req, res) => {
res.send(html);
});
export default app;
+3 -23
View File
@@ -1,5 +1,4 @@
import fs from 'fs';
import glob from 'glob';
import path from 'path';
import {appDist} from '../../shared/config/paths';
@@ -20,27 +19,8 @@ const doFilesExist = (files: Array<string>) => {
}
};
const grepRecursive = (folder: string, match: string) => {
return glob.sync(folder.concat('/**/*')).some((file) => {
try {
if (!fs.lstatSync(file).isDirectory()) {
return fs.readFileSync(file, {encoding: 'utf8'}).includes(match);
}
return false;
} catch (error) {
console.error(`Error reading file: ${file}\n${error}`);
return false;
}
});
};
const enforcePrerequisites = () =>
new Promise<void>((resolve, reject: (error: Error) => void) => {
if (!doFilesExist(staticAssets)) {
reject(new Error(`Static assets (index.html) are missing.`));
return;
}
// Ensures that WebAssembly support is present
if (typeof WebAssembly === 'undefined') {
reject(new Error('WebAssembly is not supported in this environment!'));
@@ -55,9 +35,9 @@ const enforcePrerequisites = () =>
return;
}
// Ensures that server secret is not served to user
if (grepRecursive(appDist, config.secret)) {
reject(new Error(`Secret is included in static assets. Please ensure that secret is unique.`));
// Ensure static assets exist if they need to be served
if (!doFilesExist(staticAssets) && config.serveAssets !== false) {
reject(new Error(`Static assets are missing.`));
return;
}
+6 -4
View File
@@ -6,6 +6,7 @@ import spdy from 'spdy';
import app from '../app';
import config from '../../config';
import packageJSON from '../../package.json';
const debugFloodServer = debug('flood:server');
@@ -25,7 +26,6 @@ const normalizePort = (val: string | number): string | number => {
console.error('Unexpected port or pipe');
process.exit(1);
return 0;
};
const startWebServer = () => {
@@ -68,11 +68,9 @@ const startWebServer = () => {
case 'EACCES':
console.error(`${bind} requires elevated privileges`);
process.exit(1);
break;
case 'EADDRINUSE':
console.error(`${bind} is already in use`);
process.exit(1);
break;
default:
throw error;
}
@@ -101,11 +99,15 @@ const startWebServer = () => {
const address = chalk.underline(`${useSSL ? 'https' : 'http'}://${host}:${port}`);
console.log(chalk.green(`Flood server starting on ${address}.\n`));
console.log(chalk.green(`Flood server ${packageJSON.version} starting on ${address}\n`));
if (config.authMethod === 'none') {
console.log(chalk.yellow('Starting without builtin authentication\n'));
}
if (config.serveAssets === false) {
console.log(chalk.blue('Static assets not served\n'));
}
};
export default startWebServer;
+1
View File
@@ -8,6 +8,7 @@
"strict": true,
"isolatedModules": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"baseUrl": "../",
"paths": {
"@shared/*": ["shared/*"]
+6
View File
@@ -124,6 +124,12 @@ export const configSchema = object({
// CLI argument: --allowedpath, can be called multiple times
// Allowed paths for file operations. "undefined" means everything. [default: undefined]
allowedPaths: array(string()).optional(),
// CLI argument: --assets
// Whether Flood should serve static assets.
// Users may prefer to serve static assets via a "professional" web server such as nginx to
// increase performance or have more flexibility on compression or other options. [default: true]
serveAssets: boolean().optional(),
})
.refine(
(config) => {