diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..13566b81b --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 000000000..ff5d00b02 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000..79ee123c2 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 000000000..a12bac1c9 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/config/db/db.sqlite3 + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml new file mode 100644 index 000000000..02b915b85 --- /dev/null +++ b/.idea/git_toolbox_prj.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jellyseerr.iml b/.idea/jellyseerr.iml new file mode 100644 index 000000000..0c8867d7e --- /dev/null +++ b/.idea/jellyseerr.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..bb19e753c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..94a25f7f4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index a25d16fdc..7d5bb637d 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "country-flag-icons": "^1.4.21", "csurf": "^1.11.0", "email-templates": "^8.0.10", + "email-validator": "^2.0.4", "express": "^4.17.3", "express-openapi-validator": "^4.13.6", "express-rate-limit": "^6.3.0", @@ -84,6 +85,7 @@ "@babel/cli": "^7.17.6", "@commitlint/cli": "^16.2.1", "@commitlint/config-conventional": "^16.2.1", + "@next/eslint-plugin-next": "^12.1.6", "@semantic-release/changelog": "^6.0.1", "@semantic-release/commit-analyzer": "^9.0.2", "@semantic-release/exec": "^6.0.3", diff --git a/server/entity/User.ts b/server/entity/User.ts index 157e7f24f..7fa6dc67d 100644 --- a/server/entity/User.ts +++ b/server/entity/User.ts @@ -137,6 +137,8 @@ export class User { @UpdateDateColumn() public updatedAt: Date; + public warnings: string[] = []; + constructor(init?: Partial) { Object.assign(this, init); } diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 6b167d7c4..953fc3d02 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -134,6 +134,7 @@ interface FullPublicSettings extends PublicSettings { enablePushRegistration: boolean; locale: string; emailEnabled: boolean; + userEmailRequired: boolean; newPlexLogin: boolean; } @@ -159,6 +160,7 @@ export interface NotificationAgentSlack extends NotificationAgentConfig { export interface NotificationAgentEmail extends NotificationAgentConfig { options: { + userEmailRequired: boolean; emailFrom: string; smtpHost: string; smtpPort: number; @@ -335,6 +337,7 @@ class Settings { email: { enabled: false, options: { + userEmailRequired: false, emailFrom: '', smtpHost: '', smtpPort: 587, @@ -529,6 +532,8 @@ class Settings { enablePushRegistration: this.data.notifications.agents.webpush.enabled, locale: this.data.main.locale, emailEnabled: this.data.notifications.agents.email.enabled, + userEmailRequired: + this.data.notifications.agents.email.options.userEmailRequired, newPlexLogin: this.data.main.newPlexLogin, }; } diff --git a/server/routes/auth.ts b/server/routes/auth.ts index 7c92db627..209451511 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -9,6 +9,7 @@ import { Permission } from '../lib/permissions'; import { getSettings } from '../lib/settings'; import logger from '../logger'; import { isAuthenticated } from '../middleware/auth'; +import * as EmailValidator from 'email-validator'; const authRoutes = Router(); @@ -24,6 +25,16 @@ authRoutes.get('/me', isAuthenticated(), async (req, res) => { where: { id: req.user.id }, }); + // check if email is required in settings and if user has an valid email + const settings = await getSettings(); + if ( + settings.notifications.agents.email.options.userEmailRequired && + !EmailValidator.validate(user.email) + ) { + user.warnings.push('userEmailRequired'); + logger.warn(`User ${user.username} has no valid email address`); + } + return res.status(200).json(user); }); diff --git a/src/components/Layout/Sidebar/index.tsx b/src/components/Layout/Sidebar/index.tsx index 45716eeb5..821a3d2dc 100644 --- a/src/components/Layout/Sidebar/index.tsx +++ b/src/components/Layout/Sidebar/index.tsx @@ -14,6 +14,7 @@ import useClickOutside from '../../../hooks/useClickOutside'; import { Permission, useUser } from '../../../hooks/useUser'; import Transition from '../../Transition'; import VersionStatus from '../VersionStatus'; +import UserWarnings from '../UserWarnings'; const messages = defineMessages({ dashboard: 'Discover', @@ -177,6 +178,10 @@ const Sidebar: React.FC = ({ open, setClosed }) => { ); })} +
+ setClosed()} /> +
+ {hasPermission(Permission.ADMIN) && (
setClosed()} /> @@ -236,6 +241,9 @@ const Sidebar: React.FC = ({ open, setClosed }) => { ); })} +
+ +
{hasPermission(Permission.ADMIN) && (
diff --git a/src/components/Layout/UserWarnings/index.tsx b/src/components/Layout/UserWarnings/index.tsx new file mode 100644 index 000000000..fe621d2a9 --- /dev/null +++ b/src/components/Layout/UserWarnings/index.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import Link from 'next/link'; +import { ExclamationIcon } from '@heroicons/react/outline'; +import { defineMessages, useIntl } from 'react-intl'; +import { useUser } from '../../../hooks/useUser'; + +const messages = defineMessages({ + emailRequired: 'An email address is required.', + emailInvalid: 'Email address is invalid.', + passwordRequired: 'A password is required.', +}); + +interface UserWarningsProps { + onClick?: () => void; +} + +const UserWarnings: React.FC = ({ onClick }) => { + const intl = useIntl(); + const { user } = useUser(); + if (!user) { + return null; + } + + let res = null; + + //check if a user has warnings + if (user.warnings.length > 0) { + user.warnings.forEach((warning) => { + let link = ''; + let warningText = ''; + let warningTitle = ''; + switch (warning) { + case 'userEmailRequired': + link = '/profile/settings/'; + warningTitle = 'Profile is incomplete'; + warningText = intl.formatMessage(messages.emailRequired); + } + + res = ( + + { + if (e.key === 'Enter' && onClick) { + onClick(); + } + }} + role="button" + tabIndex={0} + className="mx-2 mb-2 flex items-center rounded-lg bg-yellow-500 p-2 text-xs text-white ring-1 ring-gray-700 transition duration-300 hover:bg-yellow-400" + > + +
+ {warningTitle} + {warningText} +
+
+ + ); + }); + } + + return res; +}; + +export default UserWarnings; diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index bde592778..b560c66e8 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -50,6 +50,7 @@ const Layout: React.FC = ({ children }) => {
+ setSidebarOpen(false)} />
diff --git a/src/components/Settings/Notifications/NotificationsEmail.tsx b/src/components/Settings/Notifications/NotificationsEmail.tsx index 403083903..e20a13055 100644 --- a/src/components/Settings/Notifications/NotificationsEmail.tsx +++ b/src/components/Settings/Notifications/NotificationsEmail.tsx @@ -16,6 +16,7 @@ const messages = defineMessages({ validationSmtpHostRequired: 'You must provide a valid hostname or IP address', validationSmtpPortRequired: 'You must provide a valid port number', agentenabled: 'Enable Agent', + userEmailRequired: 'Require user email', emailsender: 'Sender Address', smtpHost: 'SMTP Host', smtpPort: 'SMTP Port', @@ -125,6 +126,7 @@ const NotificationsEmail: React.FC = () => { { await axios.post('/api/v1/settings/notifications/email', { enabled: values.enabled, options: { + userEmailRequired: values.userEmailRequired, emailFrom: values.emailFrom, smtpHost: values.smtpHost, smtpPort: Number(values.smtpPort), @@ -241,6 +244,18 @@ const NotificationsEmail: React.FC = () => {
+
+ +
+ +
+
{
@@ -258,7 +261,12 @@ const UserGeneralSettings: React.FC = () => { id="email" name="email" type="text" - placeholder={user?.email} + placeholder="example@domain.com" + className={ + user?.warnings.find((w) => w === 'userEmailRequired') + ? 'border-2 border-red-400 focus:border-blue-600' + : '' + } />
{errors.email && touched.email && ( diff --git a/src/hooks/useUser.ts b/src/hooks/useUser.ts index 223cea326..4880a6315 100644 --- a/src/hooks/useUser.ts +++ b/src/hooks/useUser.ts @@ -13,6 +13,7 @@ export type { PermissionCheckOptions }; export interface User { id: number; + warnings: string[]; plexUsername?: string; username?: string; displayName: string; diff --git a/tailwind.config.js b/tailwind.config.js index d1d023c01..aeb8b7f4f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,6 +2,7 @@ const defaultTheme = require('tailwindcss/defaultTheme'); module.exports = { + important: true, mode: 'jit', content: ['./src/pages/**/*.{ts,tsx}', './src/components/**/*.{ts,tsx}'], theme: { diff --git a/yarn.lock b/yarn.lock index b32edc909..064253471 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1622,6 +1622,13 @@ dependencies: glob "7.1.7" +"@next/eslint-plugin-next@^12.1.6": + version "12.1.6" + resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-12.1.6.tgz#dde3f98831f15923b25244588d924c716956292e" + integrity sha512-yNUtJ90NEiYFT6TJnNyofKMPYqirKDwpahcbxBgSIuABwYOdkGwzos1ZkYD51Qf0diYwpQZBeVqElTk7Q2WNqw== + dependencies: + glob "7.1.7" + "@next/swc-android-arm64@12.1.0": version "12.1.0" resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.1.0.tgz#865ba3a9afc204ff2bdeea49dd64d58705007a39" @@ -4780,6 +4787,11 @@ email-templates@^8.0.10: nodemailer "^6.7.2" preview-email "^3.0.5" +email-validator@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed" + integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ== + emoji-regex@^10.0.0: version "10.0.1" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.0.1.tgz#77180edb279b99510a21b79b19e1dc283d8f3991"