mirror of
https://github.com/zoriya/flood.git
synced 2026-06-01 18:47:44 +00:00
server: schema validate configurations
Unfortunately there are still many people who prefer static config file. As such, schema validate the configurations to ensure that the failure happens loud and early when the config.js is broken. Also enforces that the length of secret must be larger than 30 as the JWT secret can be brute forced locally without interaction with the server. This ensures that we always have proper configurations and avoids unnecessary and bad defensive programming practices.
This commit is contained in:
@@ -6,13 +6,6 @@
|
||||
|
||||
Flood is a monitoring service for various torrent clients. It's a Node.js service that communicates with your favorite torrent client and serves a decent web UI for administration. This project is based on the [original Flood project](https://github.com/Flood-UI/flood).
|
||||
|
||||
#### Supported Clients
|
||||
| Client | Support |
|
||||
|--------------------------------------------------------------|--------------------------------------|
|
||||
| [rTorrent](https://github.com/rakshasa/rtorrent) | Stable and Tested :white_check_mark: |
|
||||
| [qBittorrent](https://github.com/qbittorrent/qBittorrent) | Experimental :alembic: |
|
||||
| [Transmission](https://github.com/transmission/transmission) | Experimental :alembic: |
|
||||
|
||||
#### Feedback
|
||||
|
||||
If you have a specific issue or bug, please file a [GitHub issue](https://github.com/jesec/flood/issues). Please join the [Flood Discord server](https://discord.gg/Z7yR5Uf) to discuss feature requests and implementation details.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Flood
|
||||
|
||||
[](https://flood.js.org)
|
||||
[](https://flood.js.org)
|
||||
|
||||
[](https://github.com/jesec/flood/actions) [](https://crowdin.com/project/flood) [](https://discord.gg/Z7yR5Uf)
|
||||
|
||||
@@ -44,7 +44,7 @@ By default, Flood uses a command line configuration interface. If you installed
|
||||
|
||||
Run `flood --help`, `npx flood --help` or `npm run start -- --help` to get help about command line arguments.
|
||||
|
||||
If you want to know more about configurations, check `config.d.ts`.
|
||||
If you want to know more about configurations, check [shared/schema/Config.ts](https://github.com/jesec/flood/blob/master/shared/schema/Config.ts).
|
||||
|
||||
When Flood's builtin user management is enabled (default), you will be prompted to configure the connection to rTorrent when loading the web interface.
|
||||
|
||||
|
||||
Vendored
+3
-122
@@ -1,126 +1,7 @@
|
||||
// This is the config schema for Flood, a React-based frontend for various BitTorrent clients.
|
||||
// This file provides the detailed documentation for config options.
|
||||
//
|
||||
// By default, <rundir> is ~/.local/share/flood.
|
||||
//
|
||||
// You may use this schema to create a static configuration. However, it is not recommended.
|
||||
// Stability of this schema can NOT be guaranteed. The reason is that other files import
|
||||
// config.js and use its variables directly and it would involve unnecessary and duplicative
|
||||
// conditionals in EVERY related file in order to retain compatibility for older config files.
|
||||
// Plus, such duplications and conditionals are error-prone.
|
||||
//
|
||||
// Use CLI if you don't want to check and change the config.js whenever Flood is updated.
|
||||
// CLI also supports passing through arguments via environment variables. For example,
|
||||
// env variable FLOOD_OPTION_port=80 is equivalent to argument --port 80. Use ',' to split
|
||||
// for arguments that take multiple inputs such as --allowedpath.
|
||||
// @see shared/schema/Config
|
||||
|
||||
import type {AuthMethod} from '@shared/schema/Auth';
|
||||
import type {ClientConnectionSettings} from '@shared/schema/ClientConnectionSettings';
|
||||
import type {Config} from '@shared/schema/Config';
|
||||
|
||||
declare const CONFIG: {
|
||||
// CLI argument: --baseuri
|
||||
// This URI will prefix all of Flood's HTTP requests.
|
||||
// For example, if you intend to serve from http://example.com/flood, set this to
|
||||
// '/flood' and configure your web server to pass _all_ requests from `/flood` to
|
||||
// the `/flood` of Flood's web server. [default: '/']
|
||||
baseURI: string;
|
||||
|
||||
// CLI argument: --dbclean
|
||||
// Flood uses a local nedb database to keep track of users, torrents, and activity. The
|
||||
// database is regularly purged to remove outdated data. This value dictates how old data
|
||||
// is, in milliseconds, before being purged. [default: 1000 * 60 * 60]
|
||||
dbCleanInterval: number;
|
||||
|
||||
// CLI argument: --rundir / -d
|
||||
// Where to store the local nedb database. [default: '<rundir>/db']
|
||||
dbPath: string;
|
||||
|
||||
// CLI argument: --rundir / -d
|
||||
// Where to store Flood's temporary files [default: '<rundir>/temp']
|
||||
tempPath: string;
|
||||
|
||||
// CLI argument: --auth (--noauth implies --auth=none)
|
||||
// Authentication and user management method: [default: 'default']
|
||||
//
|
||||
// default:
|
||||
// Flood uses its own authentication and user management system. Users are authenticated
|
||||
// by password and will be prompted to configure the connection to torrent client in the
|
||||
// web interface. On successful authentication via /authenticate API endpoint, Flood will
|
||||
// send a cookie with token to user. Users with admin privileges may create additional
|
||||
// users with different password and torrent client configurations.
|
||||
//
|
||||
// none:
|
||||
// There is no per-user config and no attempt to authenticate. An auth cookie with token is
|
||||
// still needed to access API endpoints. This allows us to utilize browser's protections
|
||||
// against session hijacking. The cookie with token will be sent unconditionally when
|
||||
// /authenticate or /verify endpoints are accessed. Instead of per-user config, the
|
||||
// configUser settings will be used.
|
||||
authMethod: AuthMethod;
|
||||
|
||||
// CLI arguments: "When auth=none:" group
|
||||
// Settings for the no-user configuration.
|
||||
configUser: ClientConnectionSettings;
|
||||
|
||||
// CLI argument: --host / -h
|
||||
// The host that Flood should listen for web connections on.
|
||||
// To listen on all interfaces, change to `floodServerHost: '0.0.0.0'`. [default: '127.0.0.1']
|
||||
floodServerHost: string;
|
||||
|
||||
// CLI argument: --port / -p
|
||||
// The port that Flood should listen for web connections on. [default: 3000]
|
||||
floodServerPort: number;
|
||||
|
||||
// CLI argument: --proxy
|
||||
// Used for development only. Not used in production.
|
||||
// See the "Local Development" section of README.md for detail.
|
||||
floodServerProxy: string;
|
||||
|
||||
// CLI argument: --maxhistorystates
|
||||
// Flood keeps a history of torrent download and upload speeds.
|
||||
// This value dictates the number of individual records per period to keep.
|
||||
maxHistoryStates: number;
|
||||
|
||||
// CLI argument: --clientpoll
|
||||
// How often (in milliseconds) Flood will request the torrent list. This value affects how
|
||||
// often values are updated when a user is present. {torrentClientPollIntervalIdle} will be
|
||||
// used when no user is present. Note that poll intervals only affect activity stream. API
|
||||
// requests like "GET /api/torrents" always trigger fresh torrent list fetch. [default: 1000 * 2]
|
||||
torrentClientPollInterval: number;
|
||||
|
||||
// CLI argument: --clientpollidle
|
||||
// How often (in milliseconds) Flood will request the torrent list when no user is present.
|
||||
// {torrentClientPollInterval} will be used when at least one user is present. This value
|
||||
// usually affects some automations such as notification of download completion. Automations
|
||||
// that rely on torrent properties may be delayed within the interval. [default: 1000 * 60 * 15]
|
||||
torrentClientPollIntervalIdle: number;
|
||||
|
||||
// CLI argument: --secret / -s
|
||||
// A unique secret for signing messages with JWT (see https://jwt.io).
|
||||
// Change this to something unique and hard to guess.
|
||||
// You can use 'uuidgen' or 'cat /proc/sys/kernel/random/uuid' or 'uuidgenerator.net'.
|
||||
// By default, this is a 72-character string randomly generated at the first launch.
|
||||
// Generated secret is stored to "<rundir>/flood.secret" with 0600 permissions.
|
||||
secret: string;
|
||||
|
||||
// CLI argument: --ssl
|
||||
// Configuration for SSL, if using SSL with the Flood service directly. [default: false]
|
||||
ssl: boolean;
|
||||
|
||||
// CLI argument: --sslkey
|
||||
// Path to the SSL private key. [default: '<rundir>/key.pem']
|
||||
sslKey: string;
|
||||
|
||||
// CLI argument: --sslcert
|
||||
// Path to the SSL fullchain certificate. [default: '<rundir>/fullchain.pem']
|
||||
sslCert: string;
|
||||
|
||||
// Assign desired mounts to include. Refer to "Mounted on" column of `df -P`
|
||||
// "undefined" means all possible mounts. [default: undefined]
|
||||
watchMountPoints?: Array<string>;
|
||||
|
||||
// CLI argument: --allowedpath, can be called multiple times
|
||||
// Allowed paths for file operations. "undefined" means everything. [default: undefined]
|
||||
allowedPaths?: Array<string>;
|
||||
};
|
||||
declare const CONFIG: Config;
|
||||
|
||||
export = CONFIG;
|
||||
|
||||
@@ -2,8 +2,9 @@ import fs from 'fs';
|
||||
import glob from 'glob';
|
||||
import path from 'path';
|
||||
|
||||
import {secret} from '../../config';
|
||||
import {appDist} from '../../shared/config/paths';
|
||||
import config from '../../config';
|
||||
import {configSchema} from '../../shared/schema/Config';
|
||||
|
||||
const staticAssets = [path.join(appDist, 'index.html')];
|
||||
|
||||
@@ -46,8 +47,16 @@ const enforcePrerequisites = () =>
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensures that there is a proper configuration
|
||||
const result = configSchema.safeParse(config);
|
||||
if (!result.success) {
|
||||
console.error(result.error.message);
|
||||
reject(new Error('Invalid configuration.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensures that server secret is not served to user
|
||||
if (grepRecursive(appDist, secret)) {
|
||||
if (grepRecursive(appDist, config.secret)) {
|
||||
reject(new Error(`Secret is included in static assets. Please ensure that secret is unique.`));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,19 +5,19 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import {AccessLevel} from '../../shared/schema/constants/Auth';
|
||||
|
||||
import type {Credentials, UserInDatabase} from '../../shared/schema/Auth';
|
||||
|
||||
import config from '../../config';
|
||||
import services from '../services';
|
||||
|
||||
import type {ClientConnectionSettings} from '../../shared/schema/ClientConnectionSettings';
|
||||
import type {Credentials, UserInDatabase} from '../../shared/schema/Auth';
|
||||
|
||||
class Users {
|
||||
db = Users.loadDatabase();
|
||||
configUser: UserInDatabase = {
|
||||
_id: '_config',
|
||||
username: '_config',
|
||||
password: '',
|
||||
client: config.configUser,
|
||||
client: config.configUser as ClientConnectionSettings,
|
||||
level: AccessLevel.ADMINISTRATOR,
|
||||
};
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ class HistoryService extends BaseService<HistoryServiceEvents> {
|
||||
};
|
||||
}
|
||||
|
||||
deferFetchTransferSummary(interval = config.torrentClientPollInterval || 2000) {
|
||||
deferFetchTransferSummary(interval = config.torrentClientPollInterval) {
|
||||
this.pollTimeout = setTimeout(this.fetchCurrentTransferSummary, interval);
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ class HistoryService extends BaseService<HistoryServiceEvents> {
|
||||
};
|
||||
|
||||
handleFetchTransferSummaryError = () => {
|
||||
let nextInterval = config.torrentClientPollInterval || 2000;
|
||||
let nextInterval = config.torrentClientPollInterval;
|
||||
|
||||
// If more than 2 consecutive errors have occurred, then we delay the next request.
|
||||
this.errorCount += 1;
|
||||
|
||||
@@ -53,7 +53,7 @@ class TorrentService extends BaseService<TorrentServiceEvents> {
|
||||
}
|
||||
|
||||
deferFetchTorrentList() {
|
||||
this.pollTimeout = setTimeout(this.fetchTorrentList, this.pollInterval || 2000);
|
||||
this.pollTimeout = setTimeout(this.fetchTorrentList, this.pollInterval);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import {nativeEnum, object, string} from 'zod';
|
||||
import {literal, nativeEnum, object, string, union} from 'zod';
|
||||
import type {infer as zodInfer} from 'zod';
|
||||
|
||||
import {AccessLevel} from './constants/Auth';
|
||||
import {clientConnectionSettingsSchema} from './ClientConnectionSettings';
|
||||
|
||||
export type AuthMethod = 'default' | 'none';
|
||||
export const authMethodSchema = union([literal('default'), literal('none')]);
|
||||
|
||||
export type AuthMethod = zodInfer<typeof authMethodSchema>;
|
||||
|
||||
export const credentialsSchema = object({
|
||||
username: string(),
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
// This is the config schema for Flood, a React-based frontend for various BitTorrent clients.
|
||||
// This file provides the detailed documentation for config options.
|
||||
//
|
||||
// By default, <rundir> is ~/.local/share/flood.
|
||||
//
|
||||
// You may use this schema to create a static configuration. However, it is not recommended.
|
||||
// Stability of this schema can NOT be guaranteed. The reason is that other files import
|
||||
// config.js and use its variables directly and it would involve unnecessary and duplicative
|
||||
// conditionals in EVERY related file in order to retain compatibility for older config files.
|
||||
// Plus, such duplications and conditionals are error-prone.
|
||||
//
|
||||
// Use CLI if you don't want to check and change the config.js whenever Flood is updated.
|
||||
// CLI also supports passing through arguments via environment variables. For example,
|
||||
// env variable FLOOD_OPTION_port=80 is equivalent to argument --port 80. Use ',' to split
|
||||
// for arguments that take multiple inputs such as --allowedpath.
|
||||
|
||||
import {array, boolean, number, object, string} from 'zod';
|
||||
import type {infer as zodInfer} from 'zod';
|
||||
|
||||
import {authMethodSchema} from './Auth';
|
||||
import {clientConnectionSettingsSchema} from './ClientConnectionSettings';
|
||||
|
||||
export const configSchema = object({
|
||||
// CLI argument: --baseuri
|
||||
// This URI will prefix all of Flood's HTTP requests.
|
||||
// For example, if you intend to serve from http://example.com/flood, set this to
|
||||
// '/flood' and configure your web server to pass _all_ requests from `/flood` to
|
||||
// the `/flood` of Flood's web server. [default: '/']
|
||||
baseURI: string(),
|
||||
|
||||
// CLI argument: --dbclean
|
||||
// Flood uses a local nedb database to keep track of users, torrents, and activity. The
|
||||
// database is regularly purged to remove outdated data. This value dictates how old data
|
||||
// is, in milliseconds, before being purged. [default: 1000 * 60 * 60]
|
||||
dbCleanInterval: number().min(1000),
|
||||
|
||||
// CLI argument: --rundir / -d
|
||||
// Where to store the local nedb database. [default: '<rundir>/db']
|
||||
dbPath: string(),
|
||||
|
||||
// CLI argument: --rundir / -d
|
||||
// Where to store Flood's temporary files [default: '<rundir>/temp']
|
||||
tempPath: string(),
|
||||
|
||||
// CLI argument: --auth (--noauth implies --auth=none)
|
||||
// Authentication and user management method: [default: 'default']
|
||||
//
|
||||
// default:
|
||||
// Flood uses its own authentication and user management system. Users are authenticated
|
||||
// by password and will be prompted to configure the connection to torrent client in the
|
||||
// web interface. On successful authentication via /authenticate API endpoint, Flood will
|
||||
// send a cookie with token to user. Users with admin privileges may create additional
|
||||
// users with different password and torrent client configurations.
|
||||
//
|
||||
// none:
|
||||
// There is no per-user config and no attempt to authenticate. An auth cookie with token is
|
||||
// still needed to access API endpoints. This allows us to utilize browser's protections
|
||||
// against session hijacking. The cookie with token will be sent unconditionally when
|
||||
// /authenticate or /verify endpoints are accessed. Instead of per-user config, the
|
||||
// configUser settings will be used.
|
||||
authMethod: authMethodSchema,
|
||||
|
||||
// CLI arguments: "When auth=none:" group
|
||||
// Settings for the no-user configuration.
|
||||
configUser: clientConnectionSettingsSchema.optional(),
|
||||
|
||||
// CLI argument: --host / -h
|
||||
// The host that Flood should listen for web connections on.
|
||||
// To listen on all interfaces, change to `floodServerHost: '0.0.0.0'`. [default: '127.0.0.1']
|
||||
floodServerHost: string(),
|
||||
|
||||
// CLI argument: --port / -p
|
||||
// The port that Flood should listen for web connections on. [default: 3000]
|
||||
floodServerPort: number().int().positive(),
|
||||
|
||||
// CLI argument: --proxy
|
||||
// Used for development only. Not used in production.
|
||||
// See the "Local Development" section of README.md for detail.
|
||||
floodServerProxy: string().url(),
|
||||
|
||||
// CLI argument: --maxhistorystates
|
||||
// Flood keeps a history of torrent download and upload speeds.
|
||||
// This value dictates the number of individual records per period to keep.
|
||||
maxHistoryStates: number().int().positive(),
|
||||
|
||||
// CLI argument: --clientpoll
|
||||
// How often (in milliseconds) Flood will request the torrent list. This value affects how
|
||||
// often values are updated when a user is present. {torrentClientPollIntervalIdle} will be
|
||||
// used when no user is present. Note that poll intervals only affect activity stream. API
|
||||
// requests like "GET /api/torrents" always trigger fresh torrent list fetch. [default: 1000 * 2]
|
||||
torrentClientPollInterval: number().positive(),
|
||||
|
||||
// CLI argument: --clientpollidle
|
||||
// How often (in milliseconds) Flood will request the torrent list when no user is present.
|
||||
// {torrentClientPollInterval} will be used when at least one user is present. This value
|
||||
// usually affects some automations such as notification of download completion. Automations
|
||||
// that rely on torrent properties may be delayed within the interval. [default: 1000 * 60 * 15]
|
||||
torrentClientPollIntervalIdle: number().positive(),
|
||||
|
||||
// CLI argument: --secret / -s
|
||||
// A unique secret for signing messages with JWT (see https://jwt.io).
|
||||
// Change this to something unique and hard to guess.
|
||||
// You can use 'uuidgen' or 'cat /proc/sys/kernel/random/uuid' or 'uuidgenerator.net'.
|
||||
// By default, this is a 72-character string randomly generated at the first launch.
|
||||
// Generated secret is stored to "<rundir>/flood.secret" with 0600 permissions.
|
||||
secret: string().min(30),
|
||||
|
||||
// CLI argument: --ssl
|
||||
// Configuration for SSL, if using SSL with the Flood service directly. [default: false]
|
||||
ssl: boolean(),
|
||||
|
||||
// CLI argument: --sslkey
|
||||
// Path to the SSL private key. [default: '<rundir>/key.pem']
|
||||
sslKey: string().optional(),
|
||||
|
||||
// CLI argument: --sslcert
|
||||
// Path to the SSL fullchain certificate. [default: '<rundir>/fullchain.pem']
|
||||
sslCert: string().optional(),
|
||||
|
||||
// Assign desired mounts to include. Refer to "Mounted on" column of `df -P`
|
||||
// "undefined" means all possible mounts. [default: undefined]
|
||||
watchMountPoints: array(string()).optional(),
|
||||
|
||||
// CLI argument: --allowedpath, can be called multiple times
|
||||
// Allowed paths for file operations. "undefined" means everything. [default: undefined]
|
||||
allowedPaths: array(string()).optional(),
|
||||
})
|
||||
.refine(
|
||||
(config) => {
|
||||
if (config.authMethod === 'none') {
|
||||
return config.configUser != null;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: 'Empty client connection settings.',
|
||||
path: ['configUser'],
|
||||
},
|
||||
)
|
||||
.refine(
|
||||
(config) => {
|
||||
if (config.ssl) {
|
||||
return config.sslKey != null && config.sslCert != null;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: 'SSL key or certificate not specified.',
|
||||
path: ['ssl'],
|
||||
},
|
||||
);
|
||||
|
||||
export type Config = zodInfer<typeof configSchema>;
|
||||
Reference in New Issue
Block a user