From 6b3a93a52378b6ea0960b615d537077553b1ae9d Mon Sep 17 00:00:00 2001 From: Aseer KT Date: Mon, 8 Jul 2024 17:16:10 +0530 Subject: [PATCH] chore: permission check for add member fn --- README.md | 1 + server/global.d.ts | 4 ++++ server/src/middlewares.ts | 11 ++++++++- server/src/modules/groups/groups.routes.ts | 10 +++++++- .../src/modules/members/members.controller.ts | 8 +++++++ server/src/modules/members/members.service.ts | 7 ++++-- server/src/socket/events.ts | 4 ++-- .../chat}/layouts/ChatLayout.tsx | 0 .../components => chat/layouts}/GroupInfo.tsx | 11 ++------- web/src/features/chat/layouts/index.ts | 1 + web/src/features/member/components/index.ts | 3 ++- web/src/features/member/hooks/index.ts | 1 + .../features/member/hooks/useCurrentMember.ts | 24 +++++++++++++++++++ web/src/features/member/member.service.ts | 5 ++++ web/src/pages/ChatRoom.tsx | 13 ++++++++-- web/src/router.tsx | 2 +- 16 files changed, 86 insertions(+), 19 deletions(-) rename web/src/{ => features/chat}/layouts/ChatLayout.tsx (100%) rename web/src/features/{member/components => chat/layouts}/GroupInfo.tsx (76%) create mode 100644 web/src/features/chat/layouts/index.ts create mode 100644 web/src/features/member/hooks/index.ts create mode 100644 web/src/features/member/hooks/useCurrentMember.ts diff --git a/README.md b/README.md index 9656c73..778407b 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ pnpm --filter web test - [x] realtime member list update - [x] infinite scroll cursor pagination (messages/groups/members) - [x] tanstack react-query integration +- [x] add members - [ ] alert component - [ ] confirm dialog - [ ] delete group diff --git a/server/global.d.ts b/server/global.d.ts index 8d5c471..cb6c082 100644 --- a/server/global.d.ts +++ b/server/global.d.ts @@ -7,5 +7,9 @@ interface UserPayload { declare namespace Express { export interface Request { user?: UserPayload + group?: { + id: number + role: 'owner' | 'admin' | 'member' + } } } diff --git a/server/src/middlewares.ts b/server/src/middlewares.ts index 3e7dbb9..82a2cdf 100644 --- a/server/src/middlewares.ts +++ b/server/src/middlewares.ts @@ -44,12 +44,21 @@ export const hasGroupPermission = return notAuthorized(res) } - const isAllowed = await checkPermission(groupId, req.user!.id, role) + const { isAllowed, memberRole } = await checkPermission( + groupId, + req.user!.id, + role, + ) if (!isAllowed) { return notAuthorized(res) } + req.group = { + id: groupId, + role: memberRole!, + } + next() } catch (error) { notAuthorized(res) diff --git a/server/src/modules/groups/groups.routes.ts b/server/src/modules/groups/groups.routes.ts index b2cb57a..69bdbf7 100644 --- a/server/src/modules/groups/groups.routes.ts +++ b/server/src/modules/groups/groups.routes.ts @@ -1,6 +1,9 @@ import { hasGroupPermission } from '@/middlewares' import { Router } from 'express' -import { getGroupMembers } from '../members/members.controller' +import { + getCurrentMember, + getGroupMembers, +} from '../members/members.controller' import { createMessage, listMessages } from '../messages/messages.controller' import { addGroupMembers, @@ -21,6 +24,11 @@ router.delete('/:groupId', hasGroupPermission('owner'), deleteGroup) router.post('/:groupId/members', hasGroupPermission('admin'), addGroupMembers) router.get('/:groupId/members', hasGroupPermission('member'), getGroupMembers) +router.get( + '/:groupId/members/current', + hasGroupPermission('member'), + getCurrentMember, +) router.get( '/:groupId/non-members', hasGroupPermission('admin'), diff --git a/server/src/modules/members/members.controller.ts b/server/src/modules/members/members.controller.ts index 2413c81..4d700ff 100644 --- a/server/src/modules/members/members.controller.ts +++ b/server/src/modules/members/members.controller.ts @@ -79,3 +79,11 @@ export const getGroupMembers: RequestHandler = async (req, res, next) => { next(error) } } + +export const getCurrentMember: RequestHandler = (req, res, next) => { + try { + return res.json(req.group) + } catch (error) { + next(error) + } +} diff --git a/server/src/modules/members/members.service.ts b/server/src/modules/members/members.service.ts index 601d16a..2bc0bd7 100644 --- a/server/src/modules/members/members.service.ts +++ b/server/src/modules/members/members.service.ts @@ -25,14 +25,17 @@ export const checkPermission = async ( .limit(1) if (!member) { - return false + return { isAllowed: false } } await setMemberRolesForAGroup(groupId, { [userId]: member.role }) memberRole = member.role } - return memberRoles.indexOf(memberRole) >= memberRoles.indexOf(role) + return { + isAllowed: memberRoles.indexOf(memberRole) >= memberRoles.indexOf(role), + memberRole, + } } export const addMembers = async ( diff --git a/server/src/socket/events.ts b/server/src/socket/events.ts index 9826972..9261d57 100644 --- a/server/src/socket/events.ts +++ b/server/src/socket/events.ts @@ -55,12 +55,12 @@ export const registerSocketEvents = (io: TypedIOServer) => { socket.on('createMessage', async ({ groupId, text }, cb) => { try { - const hasPermission = await checkPermission( + const { isAllowed } = await checkPermission( groupId, socket.data.user!.id, 'member', ) - if (!hasPermission) { + if (!isAllowed) { throw new Error('createMessage: Not authorized') } diff --git a/web/src/layouts/ChatLayout.tsx b/web/src/features/chat/layouts/ChatLayout.tsx similarity index 100% rename from web/src/layouts/ChatLayout.tsx rename to web/src/features/chat/layouts/ChatLayout.tsx diff --git a/web/src/features/member/components/GroupInfo.tsx b/web/src/features/chat/layouts/GroupInfo.tsx similarity index 76% rename from web/src/features/member/components/GroupInfo.tsx rename to web/src/features/chat/layouts/GroupInfo.tsx index ec07c46..2c2c7cf 100644 --- a/web/src/features/member/components/GroupInfo.tsx +++ b/web/src/features/chat/layouts/GroupInfo.tsx @@ -1,11 +1,10 @@ import backArrow from '@/assets/back-svgrepo-com.svg' -import { AddMembers } from './AddMembers' -import { MemberList } from './MemberList' interface MembersListProps { isOpen: boolean onClose: () => void groupId: number + children: React.ReactNode } export const GroupInfo = (props: MembersListProps) => { @@ -27,13 +26,7 @@ export const GroupInfo = (props: MembersListProps) => {

Group info

- -
- -
+ {props.children} ) : null } diff --git a/web/src/features/chat/layouts/index.ts b/web/src/features/chat/layouts/index.ts new file mode 100644 index 0000000..77cf67d --- /dev/null +++ b/web/src/features/chat/layouts/index.ts @@ -0,0 +1 @@ +export { GroupInfo } from './GroupInfo' diff --git a/web/src/features/member/components/index.ts b/web/src/features/member/components/index.ts index 21a0775..21d9b93 100644 --- a/web/src/features/member/components/index.ts +++ b/web/src/features/member/components/index.ts @@ -1 +1,2 @@ -export { GroupInfo as MembersSidebar } from './GroupInfo' +export { AddMembers } from './AddMembers' +export { MemberList } from './MemberList' diff --git a/web/src/features/member/hooks/index.ts b/web/src/features/member/hooks/index.ts new file mode 100644 index 0000000..9650bc5 --- /dev/null +++ b/web/src/features/member/hooks/index.ts @@ -0,0 +1 @@ +export * from './useCurrentMember' diff --git a/web/src/features/member/hooks/useCurrentMember.ts b/web/src/features/member/hooks/useCurrentMember.ts new file mode 100644 index 0000000..ba6fd78 --- /dev/null +++ b/web/src/features/member/hooks/useCurrentMember.ts @@ -0,0 +1,24 @@ +import { useQuery } from '@tanstack/react-query' +import { useCallback } from 'react' +import { IMember } from '../member.interface' +import { getCurrentMember } from '../member.service' + +const memberRoles = ['member', 'admin', 'owner'] + +export const useCurrentMember = (groupId: number, enabled = true) => { + const { data } = useQuery({ + queryKey: ['members', groupId, 'current'], + queryFn: ({ queryKey }) => getCurrentMember(queryKey[1] as number), + enabled, + }) + + const hasPermission = useCallback( + (role: IMember['role']) => + data?.role + ? memberRoles.indexOf(data.role) >= memberRoles.indexOf(role) + : false, + [data], + ) + + return { member: data, hasPermission } +} diff --git a/web/src/features/member/member.service.ts b/web/src/features/member/member.service.ts index 5fa4775..58d104d 100644 --- a/web/src/features/member/member.service.ts +++ b/web/src/features/member/member.service.ts @@ -20,3 +20,8 @@ export const addGroupMembers = async ({ method: 'POST', body: JSON.stringify({ memberIds }), }) + +export const getCurrentMember = async ( + groupId: number, +): Promise<{ id: number; role: IMember['role'] }> => + fetcher(`groups/${groupId}/members/current`) diff --git a/web/src/pages/ChatRoom.tsx b/web/src/pages/ChatRoom.tsx index 322c7c1..e9817df 100644 --- a/web/src/pages/ChatRoom.tsx +++ b/web/src/pages/ChatRoom.tsx @@ -1,6 +1,8 @@ import { TypingIndicator } from '@/features/chat/components' +import { GroupInfo } from '@/features/chat/layouts' import { GroupHeader } from '@/features/group/components' -import { MembersSidebar } from '@/features/member/components' +import { AddMembers, MemberList } from '@/features/member/components' +import { useCurrentMember } from '@/features/member/hooks' import { MessageComposer, MessageList } from '@/features/message/components' import { useDisclosure } from '@/hooks/useDisclosure' import { getSocketIO } from '@/utils/socket' @@ -22,6 +24,8 @@ export const Component = () => { } }, [groupId]) + const { hasPermission } = useCurrentMember(groupId, isOpen) + if (!groupId) return null return ( @@ -37,7 +41,12 @@ export const Component = () => { - + + +
+ {hasPermission('admin') && } +
+
) } diff --git a/web/src/router.tsx b/web/src/router.tsx index 00f8556..7dd8e3b 100644 --- a/web/src/router.tsx +++ b/web/src/router.tsx @@ -14,7 +14,7 @@ export const router = createBrowserRouter([ { path: '/signup', lazy: () => import('./pages/SignUp') }, { path: '/chat', - Component: lazy(() => import('./layouts/ChatLayout')), + Component: lazy(() => import('./features/chat/layouts/ChatLayout')), children: [ { path: '', lazy: () => import('./pages/ChatHome') }, { path: ':groupId', lazy: () => import('./pages/ChatRoom') },