mirror of
https://github.com/zoriya/flood.git
synced 2025-12-06 07:16:18 +00:00
config: switch to authMethod config for auth method selection
This commit is contained in:
@@ -22,7 +22,7 @@ const AppWrapper: React.FC<AppWrapperProps> = (props: AppWrapperProps) => {
|
||||
overlay = <LoadingOverlay dependencies={UIStore.dependencies} />;
|
||||
}
|
||||
|
||||
if (AuthStore.isAuthenticated && !ClientStatusStore.isConnected && !ConfigStore.disableAuth) {
|
||||
if (AuthStore.isAuthenticated && !ClientStatusStore.isConnected && ConfigStore.authMethod !== 'none') {
|
||||
overlay = (
|
||||
<div className="application__loading-overlay">
|
||||
<div className="application__entry-barrier">
|
||||
|
||||
@@ -128,7 +128,7 @@ class SettingsModal extends Component<WrappedComponentProps, SettingsModalStates
|
||||
id: 'settings.tabs.resources',
|
||||
}),
|
||||
},
|
||||
...(!ConfigStore.disableAuth
|
||||
...(ConfigStore.authMethod !== 'none'
|
||||
? {
|
||||
authentication: {
|
||||
content: AuthTab,
|
||||
|
||||
@@ -6,7 +6,7 @@ import Logout from '../icons/Logout';
|
||||
import Tooltip from '../general/Tooltip';
|
||||
|
||||
const LogoutButton = () => {
|
||||
if (ConfigStore.disableAuth) {
|
||||
if (ConfigStore.authMethod === 'none') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import {makeAutoObservable} from 'mobx';
|
||||
|
||||
import type {AuthMethod} from '@shared/schema/Auth';
|
||||
import type {AuthVerificationPreloadConfigs} from '@shared/schema/api/auth';
|
||||
|
||||
class ConfigStore {
|
||||
baseURI = window.location.pathname.substr(0, window.location.pathname.lastIndexOf('/') + 1);
|
||||
disableAuth = false;
|
||||
authMethod: AuthMethod = 'default';
|
||||
pollInterval = 2000;
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
handlePreloadConfigs({disableAuth, pollInterval}: AuthVerificationPreloadConfigs) {
|
||||
this.disableAuth = disableAuth != null ? disableAuth : false;
|
||||
handlePreloadConfigs({authMethod, pollInterval}: AuthVerificationPreloadConfigs) {
|
||||
this.authMethod = authMethod || 'default';
|
||||
this.pollInterval = pollInterval || 2000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,40 +30,47 @@ const {argv} = require('yargs')
|
||||
})
|
||||
.option('secret', {
|
||||
alias: 's',
|
||||
hidden: true,
|
||||
describe: 'A unique secret, a random one will be generated if not provided',
|
||||
type: 'string',
|
||||
})
|
||||
.option('auth', {
|
||||
describe: 'Access control and user management method',
|
||||
choices: ['default', 'none'],
|
||||
})
|
||||
.option('noauth', {
|
||||
alias: 'n',
|
||||
hidden: true,
|
||||
default: false,
|
||||
describe: "Disable Flood's builtin access control system, needs rthost+rtport OR rtsocket.",
|
||||
describe: "Disable Flood's builtin access control system, deprecated, use auth=none instead",
|
||||
type: 'boolean',
|
||||
})
|
||||
.option('rthost', {
|
||||
describe: "Depends on noauth: Host of rTorrent's SCGI interface",
|
||||
describe: "Host of rTorrent's SCGI interface",
|
||||
type: 'string',
|
||||
})
|
||||
.option('rtport', {
|
||||
describe: "Depends on noauth: Port of rTorrent's SCGI interface",
|
||||
describe: "Port of rTorrent's SCGI interface",
|
||||
type: 'number',
|
||||
})
|
||||
.option('rtsocket', {
|
||||
conflicts: ['rthost', 'rtport'],
|
||||
describe: "Depends on noauth: Path to rTorrent's SCGI unix socket",
|
||||
describe: "Path to rTorrent's SCGI unix socket",
|
||||
type: 'string',
|
||||
})
|
||||
.option('qburl', {
|
||||
describe: 'Depends on noauth: URL to qBittorrent Web API',
|
||||
describe: 'URL to qBittorrent Web API',
|
||||
type: 'string',
|
||||
})
|
||||
.option('qbuser', {
|
||||
describe: 'Depends on noauth: Username of qBittorrent Web API',
|
||||
describe: 'Username of qBittorrent Web API',
|
||||
type: 'string',
|
||||
})
|
||||
.option('qbpass', {
|
||||
describe: 'Depends on noauth: Password of qBittorrent Web API',
|
||||
describe: 'Password of qBittorrent Web API',
|
||||
type: 'string',
|
||||
})
|
||||
.group(['rthost', 'rtport', 'rtsocket', 'qburl', 'qbuser', 'qbpass'], 'When auth=none:')
|
||||
.option('ssl', {
|
||||
default: false,
|
||||
describe: 'Enable SSL, key.pem and fullchain.pem needed in runtime directory',
|
||||
@@ -195,12 +202,17 @@ if (argv.rtsocket != null || argv.rthost != null) {
|
||||
};
|
||||
}
|
||||
|
||||
let authMethod = 'default';
|
||||
if (argv.noauth || argv.auth === 'none') {
|
||||
authMethod = 'none';
|
||||
}
|
||||
|
||||
const CONFIG = {
|
||||
baseURI: argv.baseuri,
|
||||
dbCleanInterval: argv.dbclean,
|
||||
dbPath: path.resolve(path.join(argv.rundir, 'db')),
|
||||
tempPath: path.resolve(path.join(argv.rundir, 'temp')),
|
||||
disableUsersAndAuth: argv.noauth,
|
||||
authMethod,
|
||||
configUser: connectionSettings,
|
||||
floodServerHost: argv.host,
|
||||
floodServerPort: argv.port,
|
||||
|
||||
3
config.d.ts
vendored
3
config.d.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import type {AuthMethod} from '@shared/schema/Auth';
|
||||
import type {ClientConnectionSettings} from '@shared/schema/ClientConnectionSettings';
|
||||
|
||||
declare const CONFIG: {
|
||||
@@ -5,7 +6,7 @@ declare const CONFIG: {
|
||||
dbCleanInterval: number;
|
||||
dbPath: string;
|
||||
tempPath: string;
|
||||
disableUsersAndAuth: boolean;
|
||||
authMethod: AuthMethod;
|
||||
configUser: ClientConnectionSettings;
|
||||
floodServerHost: string;
|
||||
floodServerPort: number;
|
||||
|
||||
@@ -24,11 +24,23 @@ const CONFIG = {
|
||||
// Where to store Flood's temporary files
|
||||
tempPath: './run/temp/',
|
||||
|
||||
// If this is true, there will be no users and no attempt to
|
||||
// authenticate or password-protect any page. In that case,
|
||||
// instead of per-user config, the following configUser settings
|
||||
// will be used.
|
||||
disableUsersAndAuth: false,
|
||||
//
|
||||
// Authentication and user management method:
|
||||
//
|
||||
// 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: 'default',
|
||||
|
||||
// Settings for the no-user configuration.
|
||||
configUser: {
|
||||
|
||||
@@ -7,7 +7,7 @@ const temporaryRuntimeDirectory = path.resolve(os.tmpdir(), `flood.test.${crypto
|
||||
|
||||
process.argv = ['node', 'flood'];
|
||||
process.argv.push('--rundir', temporaryRuntimeDirectory);
|
||||
process.argv.push('--noauth', 'false');
|
||||
process.argv.push('--auth', 'default');
|
||||
|
||||
afterAll(() => {
|
||||
if (process.env.CI !== 'true') {
|
||||
|
||||
@@ -21,7 +21,7 @@ const qBittorrentDaemon = spawn(
|
||||
|
||||
process.argv = ['node', 'flood'];
|
||||
process.argv.push('--rundir', temporaryRuntimeDirectory);
|
||||
process.argv.push('--noauth');
|
||||
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');
|
||||
|
||||
@@ -32,7 +32,7 @@ const rTorrentProcess = spawn(
|
||||
|
||||
process.argv = ['node', 'flood'];
|
||||
process.argv.push('--rundir', temporaryRuntimeDirectory);
|
||||
process.argv.push('--noauth');
|
||||
process.argv.push('--auth', 'none');
|
||||
process.argv.push('--rtsocket', rTorrentSocket);
|
||||
process.argv.push('--allowedpath', temporaryRuntimeDirectory);
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ const startWebServer = () => {
|
||||
|
||||
console.log(chalk.green(`Flood server starting on ${address}.\n`));
|
||||
|
||||
if (config.disableUsersAndAuth) {
|
||||
if (config.authMethod === 'none') {
|
||||
console.log(chalk.yellow('Starting without builtin authentication\n'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -169,7 +169,7 @@ class Users {
|
||||
}
|
||||
|
||||
lookupUser(username: string, callback: (err: Error | null, user?: UserInDatabase) => void): void {
|
||||
if (config.disableUsersAndAuth) {
|
||||
if (config.authMethod === 'none') {
|
||||
return callback(null, this.getConfigUser());
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ class Users {
|
||||
}
|
||||
|
||||
listUsers(callback: (users: Array<UserInDatabase> | null, err?: Error) => void): void {
|
||||
if (config.disableUsersAndAuth) {
|
||||
if (config.authMethod === 'none') {
|
||||
return callback([this.getConfigUser()]);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ const validationError = (res: Response, err: Error) => {
|
||||
};
|
||||
|
||||
const preloadConfigs: AuthVerificationPreloadConfigs = {
|
||||
disableAuth: config.disableUsersAndAuth,
|
||||
authMethod: config.authMethod,
|
||||
pollInterval: config.torrentClientPollInterval,
|
||||
};
|
||||
|
||||
@@ -98,7 +98,7 @@ router.use('/users', passport.authenticate('jwt', {session: false}), requireAdmi
|
||||
* @return {AuthAuthenticationResponse} 200 - success response - application/json
|
||||
*/
|
||||
router.post<unknown, unknown, AuthAuthenticationOptions>('/authenticate', (req, res) => {
|
||||
if (config.disableUsersAndAuth) {
|
||||
if (config.authMethod === 'none') {
|
||||
sendAuthenticationResponse(res, Users.getConfigUser());
|
||||
return;
|
||||
}
|
||||
@@ -163,8 +163,8 @@ router.use('/register', (req, res, next) => {
|
||||
* @return {AuthAuthenticationResponse} 200 - success response - application/json
|
||||
*/
|
||||
router.post<unknown, unknown, AuthRegistrationOptions, {cookie: string}>('/register', (req, res) => {
|
||||
// No user can be registered when disableUsersAndAuth is true
|
||||
if (config.disableUsersAndAuth) {
|
||||
// No user can be registered when authMethod is none
|
||||
if (config.authMethod === 'none') {
|
||||
// Return 404
|
||||
res.status(404).send('Not found');
|
||||
return;
|
||||
@@ -200,7 +200,7 @@ router.post<unknown, unknown, AuthRegistrationOptions, {cookie: string}>('/regis
|
||||
// Allow unauthenticated verification if no users are currently registered.
|
||||
router.use('/verify', (req, res, next) => {
|
||||
// Unconditionally provide a token if auth is disabled
|
||||
if (config.disableUsersAndAuth) {
|
||||
if (config.authMethod === 'none') {
|
||||
const {username, level} = Users.getConfigUser();
|
||||
|
||||
getAuthToken(username, res);
|
||||
@@ -284,8 +284,8 @@ router.get('/logout', (_req, res) => {
|
||||
router.use('/', requireAdmin);
|
||||
|
||||
router.use('/users', (_req, res, next) => {
|
||||
// No operation on user when disableUsersAndAuth is true
|
||||
if (config.disableUsersAndAuth) {
|
||||
// No operation on user when authMethod is none
|
||||
if (config.authMethod === 'none') {
|
||||
// Return 404
|
||||
res.status(404).send('Not found');
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import type {infer as zodInfer} from 'zod';
|
||||
import {AccessLevel} from './constants/Auth';
|
||||
import {clientConnectionSettingsSchema} from './ClientConnectionSettings';
|
||||
|
||||
export type AuthMethod = 'default' | 'none';
|
||||
|
||||
export const credentialsSchema = object({
|
||||
username: string(),
|
||||
password: string(),
|
||||
|
||||
@@ -3,6 +3,8 @@ import type {infer as zodInfer} from 'zod';
|
||||
import {AccessLevel} from '../constants/Auth';
|
||||
import {credentialsSchema} from '../Auth';
|
||||
|
||||
import type {AuthMethod} from '../Auth';
|
||||
|
||||
// All auth requests are schema validated to ensure security.
|
||||
|
||||
// POST /api/auth/authenticate
|
||||
@@ -26,7 +28,7 @@ export type AuthUpdateUserOptions = zodInfer<typeof authUpdateUserSchema>;
|
||||
|
||||
// GET /api/auth/verify - preload configurations
|
||||
export interface AuthVerificationPreloadConfigs {
|
||||
disableAuth: boolean;
|
||||
authMethod: AuthMethod;
|
||||
pollInterval: number;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user