From e7ee85c29b5d25c6bff58717eae5e62de4dcef0c Mon Sep 17 00:00:00 2001 From: sct Date: Mon, 21 Dec 2020 18:47:55 +0900 Subject: [PATCH] feat: default user permissions added to settings closes #388 --- overseerr-api.yml | 3 + server/lib/settings.ts | 3 + server/routes/auth.ts | 4 +- server/routes/settings.ts | 6 +- src/components/Settings/SettingsMain.tsx | 164 +++++++++++++++++++++-- src/components/UserEdit/index.tsx | 30 ++--- src/i18n/locale/en.json | 1 + 7 files changed, 179 insertions(+), 32 deletions(-) diff --git a/overseerr-api.yml b/overseerr-api.yml index b52527acf..bcb97771a 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -58,6 +58,9 @@ components: applicationUrl: type: string example: https://os.example.com + defaultPermissions: + type: number + example: 32 PlexLibrary: type: object properties: diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 02e6b46be..4b075af85 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -2,6 +2,7 @@ import fs from 'fs'; import path from 'path'; import { merge } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; +import { Permission } from './permissions'; export interface Library { id: string; @@ -47,6 +48,7 @@ export interface SonarrSettings extends DVRSettings { export interface MainSettings { apiKey: string; applicationUrl: string; + defaultPermissions: number; } interface PublicSettings { @@ -105,6 +107,7 @@ class Settings { main: { apiKey: '', applicationUrl: '', + defaultPermissions: Permission.REQUEST, }, plex: { name: '', diff --git a/server/routes/auth.ts b/server/routes/auth.ts index c734e7020..cda86d5bf 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -5,6 +5,7 @@ import PlexTvAPI from '../api/plextv'; import { isAuthenticated } from '../middleware/auth'; import { Permission } from '../lib/permissions'; import logger from '../logger'; +import { getSettings } from '../lib/settings'; const authRoutes = Router(); @@ -25,6 +26,7 @@ authRoutes.get('/me', isAuthenticated(), async (req, res) => { }); authRoutes.post('/login', async (req, res, next) => { + const settings = getSettings(); const userRepository = getRepository(User); const body = req.body as { authToken?: string }; @@ -82,7 +84,7 @@ authRoutes.post('/login', async (req, res, next) => { username: account.username, plexId: account.id, plexToken: account.authToken, - permissions: Permission.REQUEST, + permissions: settings.main.defaultPermissions, avatar: account.thumb, }); await userRepository.save(user); diff --git a/server/routes/settings.ts b/server/routes/settings.ts index a55b861fa..a01722210 100644 --- a/server/routes/settings.ts +++ b/server/routes/settings.ts @@ -16,7 +16,7 @@ import logger from '../logger'; import { scheduledJobs } from '../job/schedule'; import { Permission } from '../lib/permissions'; import { isAuthenticated } from '../middleware/auth'; -import { merge } from 'lodash'; +import { merge, omit } from 'lodash'; import Media from '../entity/Media'; import { MediaRequest } from '../entity/MediaRequest'; import { getAppVersion } from '../utils/appVersion'; @@ -32,9 +32,7 @@ const filteredMainSettings = ( main: MainSettings ): Partial => { if (!user?.hasPermission(Permission.ADMIN)) { - return { - applicationUrl: main.applicationUrl, - }; + return omit(main, 'apiKey'); } return main; diff --git a/src/components/Settings/SettingsMain.tsx b/src/components/Settings/SettingsMain.tsx index 7d5ea2e77..b5d163aff 100644 --- a/src/components/Settings/SettingsMain.tsx +++ b/src/components/Settings/SettingsMain.tsx @@ -9,6 +9,8 @@ import Button from '../Common/Button'; import { defineMessages, useIntl } from 'react-intl'; import { useUser, Permission } from '../../hooks/useUser'; import { useToasts } from 'react-toast-notifications'; +import { messages as permissionMessages } from '../UserEdit'; +import { hasPermission } from '../../../server/lib/permissions'; const messages = defineMessages({ generalsettings: 'General Settings', @@ -22,11 +24,19 @@ const messages = defineMessages({ toastApiKeyFailure: 'Something went wrong generating a new API Key.', toastSettingsSuccess: 'Settings saved.', toastSettingsFailure: 'Something went wrong saving settings.', + defaultPermissions: 'Default User Permissions', }); +interface PermissionOption { + id: string; + name: string; + description: string; + permission: Permission; +} + const SettingsMain: React.FC = () => { const { addToast } = useToasts(); - const { hasPermission } = useUser(); + const { hasPermission: userHasPermission } = useUser(); const intl = useIntl(); const { data, error, revalidate } = useSWR( '/api/v1/settings/main' @@ -53,13 +63,62 @@ const SettingsMain: React.FC = () => { return ; } + const permissionList: PermissionOption[] = [ + { + id: 'admin', + name: intl.formatMessage(permissionMessages.admin), + description: intl.formatMessage(permissionMessages.adminDescription), + permission: Permission.ADMIN, + }, + { + id: 'settings', + name: intl.formatMessage(permissionMessages.settings), + description: intl.formatMessage(permissionMessages.settingsDescription), + permission: Permission.MANAGE_SETTINGS, + }, + { + id: 'users', + name: intl.formatMessage(permissionMessages.users), + description: intl.formatMessage(permissionMessages.usersDescription), + permission: Permission.MANAGE_USERS, + }, + { + id: 'managerequest', + name: intl.formatMessage(permissionMessages.managerequests), + description: intl.formatMessage( + permissionMessages.managerequestsDescription + ), + permission: Permission.MANAGE_REQUESTS, + }, + { + id: 'request', + name: intl.formatMessage(permissionMessages.request), + description: intl.formatMessage(permissionMessages.requestDescription), + permission: Permission.REQUEST, + }, + { + id: 'vote', + name: intl.formatMessage(permissionMessages.vote), + description: intl.formatMessage(permissionMessages.voteDescription), + permission: Permission.VOTE, + }, + { + id: 'autoapprove', + name: intl.formatMessage(permissionMessages.autoapprove), + description: intl.formatMessage( + permissionMessages.autoapproveDescription + ), + permission: Permission.AUTO_APPROVE, + }, + ]; + return ( <>
-

+

{intl.formatMessage(messages.generalsettings)}

-

+

{intl.formatMessage(messages.generalsettingsDescription)}

@@ -67,11 +126,14 @@ const SettingsMain: React.FC = () => { { try { await axios.post('/api/v1/settings/main', { applicationUrl: values.applicationUrl, + defaultPermissions: values.defaultPermissions, }); addToast(intl.formatMessage(messages.toastSettingsSuccess), { @@ -88,10 +150,10 @@ const SettingsMain: React.FC = () => { } }} > - {({ isSubmitting }) => { + {({ isSubmitting, values, setFieldValue }) => { return (
- {hasPermission(Permission.ADMIN) && ( + {userHasPermission(Permission.ADMIN) && (
-
+
@@ -117,7 +179,7 @@ const SettingsMain: React.FC = () => { e.preventDefault(); regenerate(); }} - className="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-500 text-sm leading-5 font-medium rounded-r-md text-white bg-indigo-500 hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150" + className="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-500 border border-gray-500 rounded-r-md hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700" > { {intl.formatMessage(messages.applicationurl)}
-
+
-
+
+
+
+
+
+ {intl.formatMessage(messages.defaultPermissions)} +
+
+
+
+ {permissionList.map((permissionOption) => ( +
+
+ { + setFieldValue( + 'defaultPermissions', + hasPermission( + permissionOption.permission, + values.defaultPermissions + ) + ? values.defaultPermissions - + permissionOption.permission + : values.defaultPermissions + + permissionOption.permission + ); + }} + checked={hasPermission( + permissionOption.permission, + values.defaultPermissions + )} + /> +
+
+ +

+ {permissionOption.description} +

+
+
+ ))} +
+
+
+
+
+
- +