diff --git a/server/constants/error.ts b/server/constants/error.ts index ac18c3ec8..96fafdc97 100644 --- a/server/constants/error.ts +++ b/server/constants/error.ts @@ -2,6 +2,7 @@ export enum ApiErrorCode { InvalidUrl = 'INVALID_URL', InvalidCredentials = 'INVALID_CREDENTIALS', InvalidAuthToken = 'INVALID_AUTH_TOKEN', + InvalidEmail = 'INVALID_EMAIL', NotAdmin = 'NOT_ADMIN', SyncErrorGroupedFolders = 'SYNC_ERROR_GROUPED_FOLDERS', SyncErrorNoLibraries = 'SYNC_ERROR_NO_LIBRARIES', diff --git a/server/routes/user/usersettings.ts b/server/routes/user/usersettings.ts index 9669cb186..11cbd666f 100644 --- a/server/routes/user/usersettings.ts +++ b/server/routes/user/usersettings.ts @@ -1,3 +1,4 @@ +import { ApiErrorCode } from '@server/constants/error'; import { getRepository } from '@server/datasource'; import { User } from '@server/entity/User'; import { UserSettings } from '@server/entity/UserSettings'; @@ -9,6 +10,7 @@ import { Permission } from '@server/lib/permissions'; import { getSettings } from '@server/lib/settings'; import logger from '@server/logger'; import { isAuthenticated } from '@server/middleware/auth'; +import { ApiError } from '@server/types/error'; import { Router } from 'express'; import { canMakePermissionsChange } from '.'; @@ -98,10 +100,18 @@ userSettingsRoutes.post< } user.username = req.body.username; + const oldEmail = user.email; if (user.jellyfinUsername) { user.email = req.body.email || user.jellyfinUsername || user.email; } + const existingUser = await userRepository.findOne({ + where: { email: user.email }, + }); + if (oldEmail !== user.email && existingUser) { + throw new ApiError(400, ApiErrorCode.InvalidEmail); + } + // Update quota values only if the user has the correct permissions if ( !user.hasPermission(Permission.MANAGE_USERS) && @@ -145,7 +155,14 @@ userSettingsRoutes.post< email: savedUser.email, }); } catch (e) { - next({ status: 500, message: e.message }); + if (e.errorCode) { + return next({ + status: e.statusCode, + message: e.errorCode, + }); + } else { + return next({ status: 500, message: e.message }); + } } }); diff --git a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx index 15d960714..502a9d84f 100644 --- a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx +++ b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx @@ -14,6 +14,7 @@ import globalMessages from '@app/i18n/globalMessages'; import ErrorPage from '@app/pages/_error'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; +import { ApiErrorCode } from '@server/constants/error'; import type { UserSettingsGeneralResponse } from '@server/interfaces/api/userSettingsInterfaces'; import { Field, Form, Formik } from 'formik'; import { useRouter } from 'next/router'; @@ -42,6 +43,7 @@ const messages = defineMessages( user: 'User', toastSettingsSuccess: 'Settings saved successfully!', toastSettingsFailure: 'Something went wrong while saving settings.', + toastSettingsFailureEmail: 'This email is already taken!', region: 'Discover Region', regionTip: 'Filter content by regional availability', originallanguage: 'Discover Language', @@ -178,7 +180,7 @@ const UserGeneralSettings = () => { watchlistSyncTv: values.watchlistSyncTv, }), }); - if (!res.ok) throw new Error(); + if (!res.ok) throw new Error(res.statusText, { cause: res }); if (currentUser?.id === user?.id && setLocale) { setLocale( @@ -193,10 +195,24 @@ const UserGeneralSettings = () => { appearance: 'success', }); } catch (e) { - addToast(intl.formatMessage(messages.toastSettingsFailure), { - autoDismiss: true, - appearance: 'error', - }); + let errorData; + try { + errorData = await e.cause?.text(); + errorData = JSON.parse(errorData); + } catch { + /* empty */ + } + if (errorData?.message === ApiErrorCode.InvalidEmail) { + addToast(intl.formatMessage(messages.toastSettingsFailureEmail), { + autoDismiss: true, + appearance: 'error', + }); + } else { + addToast(intl.formatMessage(messages.toastSettingsFailure), { + autoDismiss: true, + appearance: 'error', + }); + } } finally { revalidate(); revalidateUser();