From bfbed0f7096875c9dc7062a7ca9985bf950b2919 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sun, 17 Oct 2021 16:10:02 -0400 Subject: [PATCH] feat(login): allow Plex OAuth to be disabled when reqs are met --- docs/using-overseerr/settings/README.md | 8 +- docs/using-overseerr/users/README.md | 4 +- overseerr-api.yml | 3 + server/interfaces/api/settingsInterfaces.ts | 1 + server/lib/settings.ts | 4 + server/routes/auth.ts | 11 +- src/components/Login/index.tsx | 83 +++++---- .../NotificationType/index.tsx | 18 +- .../NotificationTypeSelector/index.tsx | 20 +- src/components/PermissionOption/index.tsx | 18 +- src/components/QuotaSelector/index.tsx | 4 +- .../Settings/SettingsUsers/index.tsx | 172 ++++++++++++++---- src/components/UserList/index.tsx | 11 +- .../UserGeneralSettings/index.tsx | 14 +- .../UserSettings/UserPasswordChange/index.tsx | 5 +- src/context/SettingsContext.tsx | 1 + src/i18n/locale/en.json | 21 ++- src/pages/_app.tsx | 1 + 18 files changed, 265 insertions(+), 134 deletions(-) diff --git a/docs/using-overseerr/settings/README.md b/docs/using-overseerr/settings/README.md index 82043073..3ff96877 100644 --- a/docs/using-overseerr/settings/README.md +++ b/docs/using-overseerr/settings/README.md @@ -64,13 +64,13 @@ This setting is **enabled** by default. ## Users -### Enable Local Sign-In +### Sign-In Methods -When enabled, users who have configured passwords will be allowed to sign in using their email address. +Select the sign-in methods you would like to allow. -When disabled, Plex OAuth becomes the only sign-in option, and any "local users" you have created will not be able to sign in to Overseerr. +In order to disable Plex OAuth, [email notifications](../notifications/email.md) must be enabled and the server owner must have a password configured for their account. -This setting is **enabled** by default. +Both Plex OAuth and password sign-in are enabled by default. ### Enable New Plex Sign-In diff --git a/docs/using-overseerr/users/README.md b/docs/using-overseerr/users/README.md index 139e935a..3dcc383c 100644 --- a/docs/using-overseerr/users/README.md +++ b/docs/using-overseerr/users/README.md @@ -12,11 +12,11 @@ There are currently two methods to add users to Overseerr: importing Plex users Clicking the **Import Plex Users** button on the **User List** page will fetch the list of users with access to the Plex server from [plex.tv](https://www.plex.tv/), and add them to Overseerr automatically. -Importing Plex users is not required, however. Any user with access to the Plex server can log in to Overseerr even if they have not been imported, and will be assigned the configured [default permissions](../settings/README.md#default-permissions) upon their first login. +Importing Plex users is not required, however. If the [Enable New Plex Sign-In](../settings/README.md#enable-new-plex-sign-in) setting is enabled, any user with access to the Plex server can log in to Overseerr even if they have not been imported. New users will be assigned the configured [default permissions](../settings/README.md#default-permissions) upon their first login. ### Creating Local Users -If you would like to grant Overseerr access to a user who doesn't have their own Plex account and/or access to the Plex server, you can manually add them by clicking the **Create Local User** button. +If you would like to grant Overseerr access to a user who doesn't have their own Plex account and/or access to the Plex server, you can manually add them by clicking the **Create User** button. #### Email Address diff --git a/overseerr-api.yml b/overseerr-api.yml index 25a24667..36536ea6 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -127,6 +127,9 @@ components: localLogin: type: boolean example: true + plexLogin: + type: boolean + example: true newPlexLogin: type: boolean example: true diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index 0e5ab45a..f2277598 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -26,6 +26,7 @@ export interface PublicSettingsResponse { applicationUrl: string; hideAvailable: boolean; localLogin: boolean; + plexLogin: boolean; movie4kEnabled: boolean; series4kEnabled: boolean; region: string; diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 5a2d2b8a..a9fb1e3f 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -95,6 +95,7 @@ export interface MainSettings { }; hideAvailable: boolean; localLogin: boolean; + plexLogin: boolean; newPlexLogin: boolean; region: string; originalLanguage: string; @@ -112,6 +113,7 @@ interface FullPublicSettings extends PublicSettings { applicationUrl: string; hideAvailable: boolean; localLogin: boolean; + plexLogin: boolean; movie4kEnabled: boolean; series4kEnabled: boolean; region: string; @@ -288,6 +290,7 @@ class Settings { }, hideAvailable: false, localLogin: true, + plexLogin: true, newPlexLogin: true, region: '', originalLanguage: '', @@ -480,6 +483,7 @@ class Settings { applicationUrl: this.data.main.applicationUrl, hideAvailable: this.data.main.hideAvailable, localLogin: this.data.main.localLogin, + plexLogin: this.data.main.plexLogin, movie4kEnabled: this.data.radarr.some( (radarr) => radarr.is4k && radarr.isDefault ), diff --git a/server/routes/auth.ts b/server/routes/auth.ts index cf4a4e86..68c9cb47 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -30,11 +30,12 @@ authRoutes.post('/plex', async (req, res, next) => { const userRepository = getRepository(User); const body = req.body as { authToken?: string }; - if (!body.authToken) { - return next({ - status: 500, - message: 'Authentication token required.', - }); + if (!settings.main.plexLogin) { + return res.status(500).json({ error: 'Plex sign-in is disabled.' }); + } else if (!body.authToken) { + return res + .status(500) + .json({ error: 'You must provide an authentication token' }); } try { // First we need to use this auth token to get the user's email from plex.tv diff --git a/src/components/Login/index.tsx b/src/components/Login/index.tsx index 2dc177a1..5c96d959 100644 --- a/src/components/Login/index.tsx +++ b/src/components/Login/index.tsx @@ -68,7 +68,7 @@ const Login = () => { }); return ( -
+
{ ) ?? [] } /> -
+
-
- Logo -

+
+ Logo +

{intl.formatMessage(messages.signinheader)}

@@ -102,10 +102,10 @@ const Login = () => { leaveFrom="opacity-100" leaveTo="opacity-0" > -
+
- +

@@ -118,33 +118,45 @@ const Login = () => { {({ openIndexes, handleClick, AccordionContent }) => ( <> - - -
- setAuthToken(authToken)} - /> -
-
- {settings.currentSettings.localLogin && ( -
+ {settings.currentSettings.plexLogin && ( + <> + +
+ setAuthToken(authToken)} + /> +
+
+ + )} + {settings.currentSettings.localLogin && ( + <> + - +
-
+ )} )} diff --git a/src/components/NotificationTypeSelector/NotificationType/index.tsx b/src/components/NotificationTypeSelector/NotificationType/index.tsx index f0e6cb05..da247429 100644 --- a/src/components/NotificationTypeSelector/NotificationType/index.tsx +++ b/src/components/NotificationTypeSelector/NotificationType/index.tsx @@ -45,16 +45,14 @@ const NotificationType = ({ } />

-
- -
+
{(option.children ?? []).map((child) => (
diff --git a/src/components/NotificationTypeSelector/index.tsx b/src/components/NotificationTypeSelector/index.tsx index 149c2757..e9d4398b 100644 --- a/src/components/NotificationTypeSelector/index.tsx +++ b/src/components/NotificationTypeSelector/index.tsx @@ -381,17 +381,15 @@ const NotificationTypeSelector = ({ {intl.formatMessage(messages.notificationTypes)} {!user && *} -
-
- {availableTypes.map((type) => ( - - ))} -
+
+ {availableTypes.map((type) => ( + + ))} {error &&
{error}
}
diff --git a/src/components/PermissionOption/index.tsx b/src/components/PermissionOption/index.tsx index 43d5128d..3205cc19 100644 --- a/src/components/PermissionOption/index.tsx +++ b/src/components/PermissionOption/index.tsx @@ -122,16 +122,14 @@ const PermissionOption = ({ checked={checked} />
-
- -
+
{(option.children ?? []).map((child) => (
diff --git a/src/components/QuotaSelector/index.tsx b/src/components/QuotaSelector/index.tsx index 7240dbc2..59b79144 100644 --- a/src/components/QuotaSelector/index.tsx +++ b/src/components/QuotaSelector/index.tsx @@ -50,7 +50,7 @@ const QuotaSelector = ({ }, [limitFieldName, onChange, quotaLimit]); return ( -
+ {intl.formatMessage( mediaType === 'movie' ? messages.movieRequests : messages.tvRequests, { @@ -97,7 +97,7 @@ const QuotaSelector = ({ }, } )} -
+ ); }; diff --git a/src/components/Settings/SettingsUsers/index.tsx b/src/components/Settings/SettingsUsers/index.tsx index fb01ba48..b7771a3e 100644 --- a/src/components/Settings/SettingsUsers/index.tsx +++ b/src/components/Settings/SettingsUsers/index.tsx @@ -3,6 +3,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import PageTitle from '@app/components/Common/PageTitle'; import PermissionEdit from '@app/components/PermissionEdit'; import QuotaSelector from '@app/components/QuotaSelector'; +import useSettings from '@app/hooks/useSettings'; import globalMessages from '@app/i18n/globalMessages'; import { SaveIcon } from '@heroicons/react/outline'; import type { MainSettings } from '@server/lib/settings'; @@ -11,6 +12,7 @@ import { Field, Form, Formik } from 'formik'; import { defineMessages, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR, { mutate } from 'swr'; +import * as Yup from 'yup'; const messages = defineMessages({ users: 'Users', @@ -18,30 +20,64 @@ const messages = defineMessages({ userSettingsDescription: 'Configure global and default user settings.', toastSettingsSuccess: 'User settings saved successfully!', toastSettingsFailure: 'Something went wrong while saving settings.', - localLogin: 'Enable Local Sign-In', - localLoginTip: - 'Allow users to sign in using their email address and password, instead of Plex OAuth', - newPlexLogin: 'Enable New Plex Sign-In', - newPlexLoginTip: 'Allow Plex users to sign in without first being imported', + newPlexLogin: 'Enable New Plex Sign-Ins', + newPlexLoginTip: + 'Allow Plex users with access to the media server to sign in without being imported', movieRequestLimitLabel: 'Global Movie Request Limit', tvRequestLimitLabel: 'Global Series Request Limit', defaultPermissions: 'Default Permissions', defaultPermissionsTip: 'Initial permissions assigned to new users', + signinMethods: 'Sign-In Methods', + plexSigninHoverTip: + 'To disable Plex OAuth, email notifications must be enabled and the server owner must have a password configured for their account.', + passwordSignin: '{applicationTitle} Password', + validationSigninMethods: 'At least one sign-in method must be selected', }); const SettingsUsers = () => { const { addToast } = useToasts(); const intl = useIntl(); + const settings = useSettings(); const { data, error, mutate: revalidate, } = useSWR('/api/v1/settings/main'); + const { data: ownerData } = useSWR<{ hasPassword: boolean }>( + '/api/v1/user/1/settings/password' + ); + + const SettingsUsersSchema = Yup.object().shape( + { + plexLogin: Yup.boolean().when('localLogin', { + is: false, + then: Yup.boolean().oneOf( + [true], + intl.formatMessage(messages.validationSigninMethods) + ), + otherwise: Yup.boolean(), + }), + localLogin: Yup.boolean().when('plexLogin', { + is: false, + then: Yup.boolean().oneOf( + [true], + intl.formatMessage(messages.validationSigninMethods) + ), + otherwise: Yup.boolean(), + }), + }, + [['plexLogin', 'localLogin']] + ); if (!data && !error) { return ; } + const allowPlexSigninDisable = + ownerData?.hasPassword && + settings.currentSettings.applicationUrl && + settings.currentSettings.emailEnabled; + return ( <> { { defaultPermissions: data?.defaultPermissions ?? 0, }} enableReinitialize + validationSchema={SettingsUsersSchema} onSubmit={async (values) => { try { await axios.post('/api/v1/settings/main', { localLogin: values.localLogin, + plexLogin: values.plexLogin, newPlexLogin: values.newPlexLogin, defaultQuotas: { movie: { @@ -101,28 +140,97 @@ const SettingsUsers = () => { } }} > - {({ isSubmitting, values, setFieldValue }) => { + {({ + isSubmitting, + values, + setFieldValue, + errors, + touched, + isValid, + }) => { return (
-
-