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.
This commit is contained in:
Michael Thomas
2024-08-01 09:36:43 -04:00
parent 56a876f80d
commit be07f5cf49
2 changed files with 64 additions and 16 deletions

View File

@@ -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;

View File

@@ -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<string | null>(null);
@@ -149,6 +153,8 @@ const UserLinkedAccountsSettings = () => {
);
}
const enableMediaServerUnlink = user?.id !== 1 && passwordInfo?.hasPassword;
return (
<>
<PageTitle
@@ -179,11 +185,7 @@ const UserLinkedAccountsSettings = () => {
</div>
)}
</div>
{error && (
<Alert title={intl.formatMessage(globalMessages.failed)} type="error">
{error}
</Alert>
)}
{error && <Alert title={error} type="error" />}
{accounts.length ? (
<ul className="space-y-4">
{accounts.map((acct, i) => (
@@ -209,17 +211,19 @@ const UserLinkedAccountsSettings = () => {
</div>
</div>
<div className="flex-grow" />
<ConfirmButton
onClick={() => {
deleteRequest(
acct.type == LinkedAccountType.Plex ? 'plex' : 'jellyfin'
);
}}
confirmText={intl.formatMessage(globalMessages.areyousure)}
>
<TrashIcon />
<span>{intl.formatMessage(globalMessages.delete)}</span>
</ConfirmButton>
{enableMediaServerUnlink && (
<ConfirmButton
onClick={() => {
deleteRequest(
acct.type == LinkedAccountType.Plex ? 'plex' : 'jellyfin'
);
}}
confirmText={intl.formatMessage(globalMessages.areyousure)}
>
<TrashIcon />
<span>{intl.formatMessage(globalMessages.delete)}</span>
</ConfirmButton>
)}
</li>
))}
</ul>