From 3f6e5cc84a81f003ae2f8e464058e9f2fab51ce2 Mon Sep 17 00:00:00 2001 From: Michael Thomas Date: Sat, 18 Jan 2025 14:18:42 -0500 Subject: [PATCH] feat: support disabling jellyfin login --- docs/using-jellyseerr/settings/users.md | 8 ++ server/interfaces/api/settingsInterfaces.ts | 1 + server/lib/settings/index.ts | 4 + server/routes/auth.ts | 14 ++- .../Common/LabeledCheckbox/index.tsx | 43 +++++++ .../Settings/SettingsUsers/index.tsx | 105 ++++++++++++++---- src/context/SettingsContext.tsx | 1 + src/i18n/locale/en.json | 7 +- src/pages/_app.tsx | 1 + 9 files changed, 158 insertions(+), 26 deletions(-) create mode 100644 src/components/Common/LabeledCheckbox/index.tsx diff --git a/docs/using-jellyseerr/settings/users.md b/docs/using-jellyseerr/settings/users.md index ebe547efc..0fdeb7db3 100644 --- a/docs/using-jellyseerr/settings/users.md +++ b/docs/using-jellyseerr/settings/users.md @@ -14,6 +14,14 @@ When disabled, your mediaserver OAuth becomes the only sign-in option, and any " This setting is **enabled** by default. +## Enable Jellyfin/Emby/Plex Sign-In + +When enabled, users will be able to sign in to Jellyseerr using their Jellyfin/Emby/Plex credentials, provided they have linked their media server accounts. + +When disabled, users will only be able to sign in using their email address. Users without a password set will not be able to sign in to Jellyseerr. + +This setting is **enabled** by default. + ## Enable New Jellyfin/Emby/Plex Sign-In When enabled, users with access to your media server will be able to sign in to Jellyseerr even if they have not yet been imported. Users will be automatically assigned the permissions configured in the [Default Permissions](#default-permissions) setting upon first sign-in. diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index 017eef856..0e97c2bf4 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -30,6 +30,7 @@ export interface PublicSettingsResponse { applicationUrl: string; hideAvailable: boolean; localLogin: boolean; + mediaServerLogin: boolean; movie4kEnabled: boolean; series4kEnabled: boolean; discoverRegion: string; diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 343c01e2f..d29f329ea 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -124,6 +124,7 @@ export interface MainSettings { }; hideAvailable: boolean; localLogin: boolean; + mediaServerLogin: boolean; newPlexLogin: boolean; discoverRegion: string; streamingRegion: string; @@ -147,6 +148,7 @@ interface FullPublicSettings extends PublicSettings { applicationUrl: string; hideAvailable: boolean; localLogin: boolean; + mediaServerLogin: boolean; movie4kEnabled: boolean; series4kEnabled: boolean; discoverRegion: string; @@ -340,6 +342,7 @@ class Settings { }, hideAvailable: false, localLogin: true, + mediaServerLogin: true, newPlexLogin: true, discoverRegion: '', streamingRegion: '', @@ -582,6 +585,7 @@ class Settings { applicationUrl: this.data.main.applicationUrl, hideAvailable: this.data.main.hideAvailable, localLogin: this.data.main.localLogin, + mediaServerLogin: this.data.main.mediaServerLogin, jellyfinForgotPasswordUrl: this.data.jellyfin.jellyfinForgotPasswordUrl, movie4kEnabled: this.data.radarr.some( (radarr) => radarr.is4k && radarr.isDefault diff --git a/server/routes/auth.ts b/server/routes/auth.ts index cbfbc3f79..31c846adc 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -56,8 +56,9 @@ authRoutes.post('/plex', async (req, res, next) => { } if ( - settings.main.mediaServerType != MediaServerType.PLEX && - settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED + settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED && + (settings.main.mediaServerLogin === false || + settings.main.mediaServerType != MediaServerType.PLEX) ) { return res.status(500).json({ error: 'Plex login is disabled' }); } @@ -231,10 +232,13 @@ authRoutes.post('/jellyfin', async (req, res, next) => { //Make sure jellyfin login is enabled, but only if jellyfin && Emby is not already configured if ( - settings.main.mediaServerType !== MediaServerType.JELLYFIN && - settings.main.mediaServerType !== MediaServerType.EMBY && + // media server not configured, allow login for setup settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED && - settings.jellyfin.ip !== '' + (settings.main.mediaServerLogin === false || + // media server is neither jellyfin or emby + (settings.main.mediaServerType !== MediaServerType.JELLYFIN && + settings.main.mediaServerType !== MediaServerType.EMBY && + settings.jellyfin.ip !== '')) ) { return res.status(500).json({ error: 'Jellyfin login is disabled' }); } diff --git a/src/components/Common/LabeledCheckbox/index.tsx b/src/components/Common/LabeledCheckbox/index.tsx new file mode 100644 index 000000000..da949e233 --- /dev/null +++ b/src/components/Common/LabeledCheckbox/index.tsx @@ -0,0 +1,43 @@ +import { Field } from 'formik'; + +interface LabeledCheckboxProps { + id: string; + className?: string; + label: string; + description: string; + onChange: () => void; + children?: React.ReactNode; +} + +const LabeledCheckbox: React.FC = ({ + id, + className, + label, + description, + onChange, + children, +}) => { + return ( + <> +
+
+ +
+
+ +
+
+ { + /* can hold child checkboxes */ + children &&
{children}
+ } + + ); +}; + +export default LabeledCheckbox; diff --git a/src/components/Settings/SettingsUsers/index.tsx b/src/components/Settings/SettingsUsers/index.tsx index 7f6fa1fcf..8203360bd 100644 --- a/src/components/Settings/SettingsUsers/index.tsx +++ b/src/components/Settings/SettingsUsers/index.tsx @@ -1,4 +1,5 @@ import Button from '@app/components/Common/Button'; +import LabeledCheckbox from '@app/components/Common/LabeledCheckbox'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import PageTitle from '@app/components/Common/PageTitle'; import PermissionEdit from '@app/components/PermissionEdit'; @@ -13,6 +14,7 @@ import { Field, Form, Formik } from 'formik'; import { useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR, { mutate } from 'swr'; +import * as yup from 'yup'; const messages = defineMessages('components.Settings.SettingsUsers', { users: 'Users', @@ -20,9 +22,15 @@ const messages = defineMessages('components.Settings.SettingsUsers', { userSettingsDescription: 'Configure global and default user settings.', toastSettingsSuccess: 'User settings saved successfully!', toastSettingsFailure: 'Something went wrong while saving settings.', + loginMethods: 'Login Methods', + loginMethodsTip: 'Configure login methods for users.', localLogin: 'Enable Local Sign-In', localLoginTip: - 'Allow users to sign in using their email address and password, instead of {mediaServerName} OAuth', + 'Allow users to sign in using their email address and password', + mediaServerLogin: 'Enable {mediaServerName} Sign-In', + mediaServerLoginTip: + 'Allow users to sign in using their {mediaServerName} account', + atLeastOneAuth: 'At least one authentication method must be selected.', newPlexLogin: 'Enable New {mediaServerName} Sign-In', newPlexLoginTip: 'Allow {mediaServerName} users to sign in without first being imported', @@ -42,6 +50,27 @@ const SettingsUsers = () => { } = useSWR('/api/v1/settings/main'); const settings = useSettings(); + const schema = yup + .object() + .shape({ + localLogin: yup.boolean(), + mediaServerLogin: yup.boolean(), + }) + .test({ + name: 'atLeastOneAuth', + test: function (values) { + const isValid = ['localLogin', 'mediaServerLogin'].some( + (field) => !!values[field] + ); + + if (isValid) return true; + return this.createError({ + path: 'localLogin | mediaServerLogin', + message: intl.formatMessage(messages.atLeastOneAuth), + }); + }, + }); + if (!data && !error) { return ; } @@ -52,6 +81,8 @@ const SettingsUsers = () => { ? 'Jellyfin' : settings.currentSettings.mediaServerType === MediaServerType.EMBY ? 'Emby' + : settings.currentSettings.mediaServerType === MediaServerType.PLEX + ? 'Plex' : undefined, }; @@ -73,6 +104,7 @@ const SettingsUsers = () => { { tvQuotaDays: data?.defaultQuotas.tv.quotaDays ?? 7, defaultPermissions: data?.defaultPermissions ?? 0, }} + validationSchema={schema} enableReinitialize onSubmit={async (values) => { try { @@ -90,6 +123,7 @@ const SettingsUsers = () => { }, body: JSON.stringify({ localLogin: values.localLogin, + mediaServerLogin: values.mediaServerLogin, newPlexLogin: values.newPlexLogin, defaultQuotas: { movie: { @@ -121,30 +155,61 @@ const SettingsUsers = () => { } }} > - {({ isSubmitting, values, setFieldValue }) => { + {({ isSubmitting, isValid, values, errors, setFieldValue }) => { return (
-
-