Skip to content

Commit

Permalink
chore: permission check for add member fn
Browse files Browse the repository at this point in the history
  • Loading branch information
aseerkt committed Jul 8, 2024
1 parent ba31168 commit 6b3a93a
Show file tree
Hide file tree
Showing 16 changed files with 86 additions and 19 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions server/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@ interface UserPayload {
declare namespace Express {
export interface Request {
user?: UserPayload
group?: {
id: number
role: 'owner' | 'admin' | 'member'
}
}
}
11 changes: 10 additions & 1 deletion server/src/middlewares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 9 additions & 1 deletion server/src/modules/groups/groups.routes.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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'),
Expand Down
8 changes: 8 additions & 0 deletions server/src/modules/members/members.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
7 changes: 5 additions & 2 deletions server/src/modules/members/members.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
4 changes: 2 additions & 2 deletions server/src/socket/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -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) => {
Expand All @@ -27,13 +26,7 @@ export const GroupInfo = (props: MembersListProps) => {
</button>
<h3 className='font-semibold'>Group info</h3>
</header>
<MemberList
key={props.isOpen ? 'true' : 'false'}
groupId={props.groupId}
/>
<div className='w-full p-3'>
<AddMembers />
</div>
{props.children}
</div>
) : null
}
1 change: 1 addition & 0 deletions web/src/features/chat/layouts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GroupInfo } from './GroupInfo'
3 changes: 2 additions & 1 deletion web/src/features/member/components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { GroupInfo as MembersSidebar } from './GroupInfo'
export { AddMembers } from './AddMembers'
export { MemberList } from './MemberList'
1 change: 1 addition & 0 deletions web/src/features/member/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useCurrentMember'
24 changes: 24 additions & 0 deletions web/src/features/member/hooks/useCurrentMember.ts
Original file line number Diff line number Diff line change
@@ -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 }
}
5 changes: 5 additions & 0 deletions web/src/features/member/member.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
13 changes: 11 additions & 2 deletions web/src/pages/ChatRoom.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -22,6 +24,8 @@ export const Component = () => {
}
}, [groupId])

const { hasPermission } = useCurrentMember(groupId, isOpen)

if (!groupId) return null

return (
Expand All @@ -37,7 +41,12 @@ export const Component = () => {
<TypingIndicator />
<MessageComposer groupId={groupId} />
</div>
<MembersSidebar isOpen={isOpen} onClose={toggle} groupId={groupId} />
<GroupInfo isOpen={isOpen} onClose={toggle} groupId={groupId}>
<MemberList groupId={groupId} />
<div className='w-full p-3'>
{hasPermission('admin') && <AddMembers />}
</div>
</GroupInfo>
</>
)
}
Expand Down
2 changes: 1 addition & 1 deletion web/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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') },
Expand Down

0 comments on commit 6b3a93a

Please sign in to comment.