mirror of
https://github.com/zoriya/flood.git
synced 2026-06-02 19:11:14 +00:00
server: allow Flood not to serve static assets
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Generated
-34
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"strict": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": "../",
|
||||
"paths": {
|
||||
"@shared/*": ["shared/*"]
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user