New: Authentication is now required
This commit is contained in:
parent
570be88215
commit
d3018fb501
|
@ -4,6 +4,7 @@ import AppUpdatedModalConnector from 'App/AppUpdatedModalConnector';
|
||||||
import ColorImpairedContext from 'App/ColorImpairedContext';
|
import ColorImpairedContext from 'App/ColorImpairedContext';
|
||||||
import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
|
import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
|
||||||
import SignalRConnector from 'Components/SignalRConnector';
|
import SignalRConnector from 'Components/SignalRConnector';
|
||||||
|
import AuthenticationRequiredModal from 'FirstRun/AuthenticationRequiredModal';
|
||||||
import locationShape from 'Helpers/Props/Shapes/locationShape';
|
import locationShape from 'Helpers/Props/Shapes/locationShape';
|
||||||
import PageHeader from './Header/PageHeader';
|
import PageHeader from './Header/PageHeader';
|
||||||
import PageSidebar from './Sidebar/PageSidebar';
|
import PageSidebar from './Sidebar/PageSidebar';
|
||||||
|
@ -75,6 +76,7 @@ class Page extends Component {
|
||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
isSidebarVisible,
|
isSidebarVisible,
|
||||||
enableColorImpairedMode,
|
enableColorImpairedMode,
|
||||||
|
authenticationEnabled,
|
||||||
onSidebarToggle,
|
onSidebarToggle,
|
||||||
onSidebarVisibleChange
|
onSidebarVisibleChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -108,6 +110,10 @@ class Page extends Component {
|
||||||
isOpen={this.state.isConnectionLostModalOpen}
|
isOpen={this.state.isConnectionLostModalOpen}
|
||||||
onModalClose={this.onConnectionLostModalClose}
|
onModalClose={this.onConnectionLostModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AuthenticationRequiredModal
|
||||||
|
isOpen={!authenticationEnabled}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ColorImpairedContext.Provider>
|
</ColorImpairedContext.Provider>
|
||||||
);
|
);
|
||||||
|
@ -123,6 +129,7 @@ Page.propTypes = {
|
||||||
isUpdated: PropTypes.bool.isRequired,
|
isUpdated: PropTypes.bool.isRequired,
|
||||||
isDisconnected: PropTypes.bool.isRequired,
|
isDisconnected: PropTypes.bool.isRequired,
|
||||||
enableColorImpairedMode: PropTypes.bool.isRequired,
|
enableColorImpairedMode: PropTypes.bool.isRequired,
|
||||||
|
authenticationEnabled: PropTypes.bool.isRequired,
|
||||||
onResize: PropTypes.func.isRequired,
|
onResize: PropTypes.func.isRequired,
|
||||||
onSidebarToggle: PropTypes.func.isRequired,
|
onSidebarToggle: PropTypes.func.isRequired,
|
||||||
onSidebarVisibleChange: PropTypes.func.isRequired
|
onSidebarVisibleChange: PropTypes.func.isRequired
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { fetchImportLists, fetchLanguageProfiles, fetchQualityProfiles, fetchUIS
|
||||||
import { fetchStatus } from 'Store/Actions/systemActions';
|
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||||
import { fetchTags } from 'Store/Actions/tagActions';
|
import { fetchTags } from 'Store/Actions/tagActions';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||||
import ErrorPage from './ErrorPage';
|
import ErrorPage from './ErrorPage';
|
||||||
import LoadingPage from './LoadingPage';
|
import LoadingPage from './LoadingPage';
|
||||||
import Page from './Page';
|
import Page from './Page';
|
||||||
|
@ -125,18 +126,21 @@ function createMapStateToProps() {
|
||||||
selectErrors,
|
selectErrors,
|
||||||
selectAppProps,
|
selectAppProps,
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
|
createSystemStatusSelector(),
|
||||||
(
|
(
|
||||||
enableColorImpairedMode,
|
enableColorImpairedMode,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
errors,
|
errors,
|
||||||
app,
|
app,
|
||||||
dimensions
|
dimensions,
|
||||||
|
systemStatus
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
...app,
|
...app,
|
||||||
...errors,
|
...errors,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
isSmallScreen: dimensions.isSmallScreen,
|
isSmallScreen: dimensions.isSmallScreen,
|
||||||
|
authenticationEnabled: systemStatus.authentication !== 'none',
|
||||||
enableColorImpairedMode
|
enableColorImpairedMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
|
import AuthenticationRequiredModalContentConnector from './AuthenticationRequiredModalContentConnector';
|
||||||
|
|
||||||
|
function onModalClose() {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
function AuthenticationRequiredModal(props) {
|
||||||
|
const {
|
||||||
|
isOpen
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
size={sizes.MEDIUM}
|
||||||
|
isOpen={isOpen}
|
||||||
|
closeOnBackgroundClick={false}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<AuthenticationRequiredModalContentConnector
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationRequiredModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthenticationRequiredModal;
|
|
@ -0,0 +1,5 @@
|
||||||
|
.authRequiredAlert {
|
||||||
|
composes: alert from '~Components/Alert.css';
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import { inputTypes, kinds } from 'Helpers/Props';
|
||||||
|
import { authenticationMethodOptions, authenticationRequiredOptions, authenticationRequiredWarning } from 'Settings/General/SecuritySettings';
|
||||||
|
import styles from './AuthenticationRequiredModalContent.css';
|
||||||
|
|
||||||
|
function onModalClose() {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
function AuthenticationRequiredModalContent(props) {
|
||||||
|
const {
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
isSaving,
|
||||||
|
settings,
|
||||||
|
onInputChange,
|
||||||
|
onSavePress,
|
||||||
|
dispatchFetchStatus
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
authenticationMethod,
|
||||||
|
authenticationRequired,
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
} = settings;
|
||||||
|
|
||||||
|
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
|
||||||
|
|
||||||
|
const didMount = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isSaving && didMount.current) {
|
||||||
|
dispatchFetchStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
didMount.current = true;
|
||||||
|
}, [isSaving, dispatchFetchStatus]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent
|
||||||
|
showCloseButton={false}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<ModalHeader>
|
||||||
|
Authentication Required
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<Alert
|
||||||
|
className={styles.authRequiredAlert}
|
||||||
|
kind={kinds.WARNING}
|
||||||
|
>
|
||||||
|
{authenticationRequiredWarning}
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !error ?
|
||||||
|
<div>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Authentication</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="authenticationMethod"
|
||||||
|
values={authenticationMethodOptions}
|
||||||
|
helpText="Require Username and Password to access Sonarr"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...authenticationMethod}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
{
|
||||||
|
authenticationEnabled ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Authentication Required</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="authenticationRequired"
|
||||||
|
values={authenticationRequiredOptions}
|
||||||
|
helpText="Change which requests authentication is required for. Do not change unless you understand the risks."
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...authenticationRequired}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
authenticationEnabled ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Username</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TEXT}
|
||||||
|
name="username"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...username}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
authenticationEnabled ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Password</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.PASSWORD}
|
||||||
|
name="password"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...password}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</div> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isPopulated && !error ? <LoadingIndicator /> : null
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<SpinnerButton
|
||||||
|
kind={kinds.PRIMARY}
|
||||||
|
isSpinning={isSaving}
|
||||||
|
isDisabled={!authenticationEnabled}
|
||||||
|
onPress={onSavePress}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</SpinnerButton>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationRequiredModalContent.propTypes = {
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
saveError: PropTypes.object,
|
||||||
|
settings: PropTypes.object.isRequired,
|
||||||
|
onInputChange: PropTypes.func.isRequired,
|
||||||
|
onSavePress: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchStatus: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthenticationRequiredModalContent;
|
|
@ -0,0 +1,86 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
|
import { fetchGeneralSettings, saveGeneralSettings, setGeneralSettingsValue } from 'Store/Actions/settingsActions';
|
||||||
|
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||||
|
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||||
|
import AuthenticationRequiredModalContent from './AuthenticationRequiredModalContent';
|
||||||
|
|
||||||
|
const SECTION = 'general';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
createSettingsSectionSelector(SECTION),
|
||||||
|
(sectionSettings) => {
|
||||||
|
return {
|
||||||
|
...sectionSettings
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchClearPendingChanges: clearPendingChanges,
|
||||||
|
dispatchSetGeneralSettingsValue: setGeneralSettingsValue,
|
||||||
|
dispatchSaveGeneralSettings: saveGeneralSettings,
|
||||||
|
dispatchFetchGeneralSettings: fetchGeneralSettings,
|
||||||
|
dispatchFetchStatus: fetchStatus
|
||||||
|
};
|
||||||
|
|
||||||
|
class AuthenticationRequiredModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatchFetchGeneralSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.dispatchClearPendingChanges({ section: `settings.${SECTION}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onInputChange = ({ name, value }) => {
|
||||||
|
this.props.dispatchSetGeneralSettingsValue({ name, value });
|
||||||
|
};
|
||||||
|
|
||||||
|
onSavePress = () => {
|
||||||
|
this.props.dispatchSaveGeneralSettings();
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
dispatchClearPendingChanges,
|
||||||
|
dispatchFetchGeneralSettings,
|
||||||
|
dispatchSetGeneralSettingsValue,
|
||||||
|
dispatchSaveGeneralSettings,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthenticationRequiredModalContent
|
||||||
|
{...otherProps}
|
||||||
|
onInputChange={this.onInputChange}
|
||||||
|
onSavePress={this.onSavePress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationRequiredModalContentConnector.propTypes = {
|
||||||
|
dispatchClearPendingChanges: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchGeneralSettings: PropTypes.func.isRequired,
|
||||||
|
dispatchSetGeneralSettingsValue: PropTypes.func.isRequired,
|
||||||
|
dispatchSaveGeneralSettings: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchStatus: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(AuthenticationRequiredModalContentConnector);
|
|
@ -10,13 +10,15 @@ import ClipboardButton from 'Components/Link/ClipboardButton';
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
import { icons, inputTypes, kinds } from 'Helpers/Props';
|
import { icons, inputTypes, kinds } from 'Helpers/Props';
|
||||||
|
|
||||||
const authenticationMethodOptions = [
|
export const authenticationRequiredWarning = 'To prevent remote access without authentication, Sonarr now requires authentication to be enabled. You can optionally disable authentication from local addresses.';
|
||||||
{ key: 'none', value: 'None' },
|
|
||||||
|
export const authenticationMethodOptions = [
|
||||||
|
{ key: 'none', value: 'None', isDisabled: true },
|
||||||
{ key: 'basic', value: 'Basic (Browser Popup)' },
|
{ key: 'basic', value: 'Basic (Browser Popup)' },
|
||||||
{ key: 'forms', value: 'Forms (Login Page)' }
|
{ key: 'forms', value: 'Forms (Login Page)' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const authenticationRequiredOptions = [
|
export const authenticationRequiredOptions = [
|
||||||
{ key: 'enabled', value: 'Enabled' },
|
{ key: 'enabled', value: 'Enabled' },
|
||||||
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' }
|
{ key: 'disabledForLocalAddresses', value: 'Disabled for Local Addresses' }
|
||||||
];
|
];
|
||||||
|
@ -91,6 +93,7 @@ class SecuritySettings extends Component {
|
||||||
name="authenticationMethod"
|
name="authenticationMethod"
|
||||||
values={authenticationMethodOptions}
|
values={authenticationMethodOptions}
|
||||||
helpText="Require Username and Password to access Sonarr"
|
helpText="Require Username and Password to access Sonarr"
|
||||||
|
helpTextWarning={authenticationRequiredWarning}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...authenticationMethod}
|
{...authenticationMethod}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in New Issue