diff --git a/server/entity/User.ts b/server/entity/User.ts index 078fe15e..fd0162dd 100644 --- a/server/entity/User.ts +++ b/server/entity/User.ts @@ -8,7 +8,11 @@ import { RelationCount, AfterLoad, } from 'typeorm'; -import { Permission, hasPermission } from '../lib/permissions'; +import { + Permission, + hasPermission, + PermissionCheckOptions, +} from '../lib/permissions'; import { MediaRequest } from './MediaRequest'; import bcrypt from 'bcrypt'; import path from 'path'; @@ -85,8 +89,11 @@ export class User { return filtered; } - public hasPermission(permissions: Permission | Permission[]): boolean { - return !!hasPermission(permissions, this.permissions); + public hasPermission( + permissions: Permission | Permission[], + options?: PermissionCheckOptions + ): boolean { + return !!hasPermission(permissions, this.permissions, options); } public passwordMatch(password: string): Promise { diff --git a/server/lib/permissions.ts b/server/lib/permissions.ts index cfda793c..f0d45acd 100644 --- a/server/lib/permissions.ts +++ b/server/lib/permissions.ts @@ -13,6 +13,11 @@ export enum Permission { REQUEST_4K_MOVIE = 2048, REQUEST_4K_TV = 4096, REQUEST_ADVANCED = 8192, + REQUEST_VIEW = 16384, +} + +export interface PermissionCheckOptions { + type: 'and' | 'or'; } /** @@ -22,10 +27,12 @@ export enum Permission { * * @param permissions Single permission or array of permissions * @param value users current permission value + * @param options Extra options to control permission check behavior (mainly for arrays) */ export const hasPermission = ( permissions: Permission | Permission[], - value: number + value: number, + options: PermissionCheckOptions = { type: 'and' } ): boolean => { let total = 0; @@ -35,8 +42,15 @@ export const hasPermission = ( } if (Array.isArray(permissions)) { - // Combine all permission values into one - total = permissions.reduce((a, v) => a + v, 0); + if (value & Permission.ADMIN) { + return true; + } + switch (options.type) { + case 'and': + return permissions.every((permission) => !!(value & permission)); + case 'or': + return permissions.some((permission) => !!(value & permission)); + } } else { total = permissions; } diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts index 946a0b72..6d36bb2f 100644 --- a/server/middleware/auth.ts +++ b/server/middleware/auth.ts @@ -1,6 +1,6 @@ import { getRepository } from 'typeorm'; import { User } from '../entity/User'; -import { Permission } from '../lib/permissions'; +import { Permission, PermissionCheckOptions } from '../lib/permissions'; import { getSettings } from '../lib/settings'; export const checkUser: Middleware = async (req, _res, next) => { @@ -34,10 +34,11 @@ export const checkUser: Middleware = async (req, _res, next) => { }; export const isAuthenticated = ( - permissions?: Permission | Permission[] + permissions?: Permission | Permission[], + options?: PermissionCheckOptions ): Middleware => { const authMiddleware: Middleware = (req, res, next) => { - if (!req.user || !req.user.hasPermission(permissions ?? 0)) { + if (!req.user || !req.user.hasPermission(permissions ?? 0, options)) { res.status(403).json({ status: 403, error: 'You do not have permission to access this endpoint', diff --git a/server/routes/request.ts b/server/routes/request.ts index d932b1da..25ba9c0e 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -57,7 +57,8 @@ requestRoutes.get('/', async (req, res, next) => { } const [requests, requestCount] = req.user?.hasPermission( - Permission.MANAGE_REQUESTS + [Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW], + { type: 'or' } ) ? await requestRepository.findAndCount({ order: sortFilter, @@ -102,10 +103,10 @@ requestRoutes.post( if ( req.body.userId && - !( - req.user?.hasPermission(Permission.MANAGE_USERS) && - req.user?.hasPermission(Permission.MANAGE_REQUESTS) - ) + !req.user?.hasPermission([ + Permission.MANAGE_USERS, + Permission.MANAGE_REQUESTS, + ]) ) { return next({ status: 403, diff --git a/src/components/PermissionEdit/index.tsx b/src/components/PermissionEdit/index.tsx index 88f27810..58428b75 100644 --- a/src/components/PermissionEdit/index.tsx +++ b/src/components/PermissionEdit/index.tsx @@ -39,6 +39,8 @@ export const messages = defineMessages({ advancedrequest: 'Advanced Requests', advancedrequestDescription: 'Grants permission to use advanced request options. (Ex. Changing servers/profiles/paths)', + viewrequests: 'View Requests', + viewrequestsDescription: "Grants permission to view other user's requests.", }); interface PermissionEditProps { @@ -85,6 +87,12 @@ export const PermissionEdit: React.FC = ({ description: intl.formatMessage(messages.advancedrequestDescription), permission: Permission.REQUEST_ADVANCED, }, + { + id: 'viewrequests', + name: intl.formatMessage(messages.viewrequests), + description: intl.formatMessage(messages.viewrequestsDescription), + permission: Permission.REQUEST_VIEW, + }, ], }, { diff --git a/src/components/RequestModal/AdvancedRequester/index.tsx b/src/components/RequestModal/AdvancedRequester/index.tsx index 332ab4a4..c7db928b 100644 --- a/src/components/RequestModal/AdvancedRequester/index.tsx +++ b/src/components/RequestModal/AdvancedRequester/index.tsx @@ -322,8 +322,7 @@ const AdvancedRequester: React.FC = ({ )} - {hasPermission(Permission.MANAGE_REQUESTS) && - hasPermission(Permission.MANAGE_USERS) && + {hasPermission([Permission.MANAGE_REQUESTS, Permission.MANAGE_USERS]) && selectedUser && (
Promise; - hasPermission: (permission: Permission | Permission[]) => boolean; + hasPermission: ( + permission: Permission | Permission[], + options?: PermissionCheckOptions + ) => boolean; } export const useUser = ({ @@ -37,8 +44,11 @@ export const useUser = ({ } ); - const checkPermission = (permission: Permission | Permission[]): boolean => { - return hasPermission(permission, data?.permissions ?? 0); + const checkPermission = ( + permission: Permission | Permission[], + options?: PermissionCheckOptions + ): boolean => { + return hasPermission(permission, data?.permissions ?? 0, options); }; return { diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index f1311426..47554464 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -114,6 +114,8 @@ "components.PermissionEdit.settingsDescription": "Grants permission to modify all Overseerr settings. A user must have this permission to grant it to others.", "components.PermissionEdit.users": "Manage Users", "components.PermissionEdit.usersDescription": "Grants permission to manage Overseerr users. Users with this permission cannot modify users with Administrator privilege, or grant it.", + "components.PermissionEdit.viewrequests": "View Requests", + "components.PermissionEdit.viewrequestsDescription": "Grants permission to view other user's requests.", "components.PermissionEdit.vote": "Vote", "components.PermissionEdit.voteDescription": "Grants permission to vote on requests (voting not yet implemented)", "components.PersonDetails.appearsin": "Appears in",