From be07f5cf49d4218088540073f39132661c375b72 Mon Sep 17 00:00:00 2001 From: Michael Thomas Date: Thu, 1 Aug 2024 09:36:43 -0400 Subject: [PATCH] fix(linked-accounts): probibit unlinking accounts in certain cases Prevents the primary administrator from unlinking their media server account (which would break sync). Additionally, prevents users without a configured local email and password from unlinking their accounts, which would render them unable to log in. --- server/routes/user/usersettings.ts | 44 +++++++++++++++++++ .../UserLinkedAccountsSettings/index.tsx | 36 ++++++++------- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/server/routes/user/usersettings.ts b/server/routes/user/usersettings.ts index 1b211b7c..3b246005 100644 --- a/server/routes/user/usersettings.ts +++ b/server/routes/user/usersettings.ts @@ -369,6 +369,28 @@ userSettingsRoutes.delete<{ id: string }>( return next({ status: 404, message: 'User not found.' }); } + if (user.id === 1) { + return next({ + status: 400, + message: + 'Cannot unlink media server accounts for the primary administrator.', + }); + } + + const hasPassword = !!( + await userRepository.findOne({ + where: { id: user.id }, + select: ['id', 'password'], + }) + )?.password; + + if (!user.email || !hasPassword) { + return next({ + status: 400, + message: 'User does not have a local email or password set.', + }); + } + user.userType = UserType.LOCAL; user.plexId = null; user.plexUsername = null; @@ -489,6 +511,28 @@ userSettingsRoutes.delete<{ id: string }>( return next({ status: 404, message: 'User not found.' }); } + if (user.id === 1) { + return next({ + status: 400, + message: + 'Cannot unlink media server accounts for the primary administrator.', + }); + } + + const hasPassword = !!( + await userRepository.findOne({ + where: { id: user.id }, + select: ['id', 'password'], + }) + )?.password; + + if (!user.email || !hasPassword) { + return next({ + status: 400, + message: 'User does not have a local email or password set.', + }); + } + user.userType = UserType.LOCAL; user.jellyfinUserId = null; user.jellyfinUsername = null; diff --git a/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx b/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx index 8b7cb03b..9754a1ec 100644 --- a/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx +++ b/src/components/UserProfile/UserSettings/UserLinkedAccountsSettings/index.tsx @@ -15,6 +15,7 @@ import { MediaServerType } from '@server/constants/server'; import { useRouter } from 'next/router'; import { useState } from 'react'; import { useIntl } from 'react-intl'; +import useSWR from 'swr'; import LinkJellyfinModal from './LinkJellyfinModal'; const messages = defineMessages( @@ -56,6 +57,9 @@ const UserLinkedAccountsSettings = () => { hasPermission, revalidate: revalidateUser, } = useUser({ id: Number(router.query.userId) }); + const { data: passwordInfo } = useSWR<{ hasPassword: boolean }>( + user ? `/api/v1/user/${user?.id}/settings/password` : null + ); const [showJellyfinModal, setShowJellyfinModal] = useState(false); const [error, setError] = useState(null); @@ -149,6 +153,8 @@ const UserLinkedAccountsSettings = () => { ); } + const enableMediaServerUnlink = user?.id !== 1 && passwordInfo?.hasPassword; + return ( <> { )} - {error && ( - - {error} - - )} + {error && } {accounts.length ? (
    {accounts.map((acct, i) => ( @@ -209,17 +211,19 @@ const UserLinkedAccountsSettings = () => {
    - { - deleteRequest( - acct.type == LinkedAccountType.Plex ? 'plex' : 'jellyfin' - ); - }} - confirmText={intl.formatMessage(globalMessages.areyousure)} - > - - {intl.formatMessage(globalMessages.delete)} - + {enableMediaServerUnlink && ( + { + deleteRequest( + acct.type == LinkedAccountType.Plex ? 'plex' : 'jellyfin' + ); + }} + confirmText={intl.formatMessage(globalMessages.areyousure)} + > + + {intl.formatMessage(globalMessages.delete)} + + )} ))}