Skip to content

Commit

Permalink
[BUG]: users permissions to delete boards (#408)
Browse files Browse the repository at this point in the history
* test: user role and ownership of boards when deleting

* test: user roles and admin permissions to delete boards

* test: change typeof to boolean on usersuper admin

* feat: delete permissions for users

* feat: delete permissions for users frontend

* fix: pr suggestions

Co-authored-by: Catia Barroco <104831678+CatiaBarroco-xgeeks@users.noreply.github.com>
  • Loading branch information
f-morgado and CatiaBarroco-xgeeks committed Aug 22, 2022
1 parent 30a12a4 commit 91bfa2d
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 32 deletions.
1 change: 0 additions & 1 deletion backend/src/modules/azure/controller/azure.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ export default class AzureController {
@HttpCode(200)
@Post('/')
loginOrRegistetruerAzureToken(@Body() azureToken: AzureToken) {
console.log('AZURE', azureToken);
return this.authAzureApp.registerOrLogin(azureToken.token);
}

Expand Down
98 changes: 77 additions & 21 deletions backend/src/modules/boards/services/delete.board.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { Injectable } from '@nestjs/common';
import { Inject, Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { ClientSession, Model, ObjectId } from 'mongoose';
import { ClientSession, LeanDocument, Model, ObjectId } from 'mongoose';

import { TeamRoles } from 'libs/enum/team.roles';
import { DELETE_FAILED } from 'libs/exceptions/messages';
import isEmpty from 'libs/utils/isEmpty';
import { GetTeamServiceInterface } from 'modules/teams/interfaces/services/get.team.service.interface';
import * as Teams from 'modules/teams/interfaces/types';
import { TeamUserDocument } from 'modules/teams/schemas/team.user.schema';
import { UserDocument } from 'modules/users/schemas/user.schema';

import { DeleteBoardService } from '../interfaces/services/delete.board.service.interface';
import Board, { BoardDocument } from '../schemas/board.schema';
Expand All @@ -12,11 +17,45 @@ import BoardUser, { BoardUserDocument } from '../schemas/board.user.schema';
@Injectable()
export default class DeleteBoardServiceImpl implements DeleteBoardService {
constructor(
@InjectModel(Board.name) private boardModel: Model<BoardDocument>,
@InjectModel(Board.name)
private boardModel: Model<BoardDocument>,
@Inject(Teams.TYPES.services.GetTeamService)
private getTeamService: GetTeamServiceInterface,
@InjectModel(BoardUser.name)
private boardUserModel: Model<BoardUserDocument>
) {}

private async getTeamUser(
userId: string,
teamId: string
): Promise<LeanDocument<TeamUserDocument>> {
const teamUser = await this.getTeamService.getTeamUser(userId, teamId);
if (!teamUser) {
throw new NotFoundException('User not found on this team!');
}
return teamUser;
}

private async isUserSAdmin(
userId: string,
teamUsers: LeanDocument<TeamUserDocument>[]
): Promise<boolean> {
const myUser = teamUsers.find(
(user) => String((user.user as UserDocument)?._id) === String(userId)
);
const isUserSAdmin = (myUser?.user as UserDocument).isSAdmin;
return isUserSAdmin;
}

private async getUsersOfTeam(teamId: string): Promise<LeanDocument<TeamUserDocument>[]> {
const users = await this.getTeamService.getUsersOfTeam(teamId);
if (!users) {
throw new NotFoundException('User not found list of users!');
}

return users;
}

async deleteSubBoards(dividedBoards: Board[] | ObjectId[], boardSession: ClientSession) {
const { deletedCount } = await this.boardModel
.deleteMany({ _id: { $in: dividedBoards } }, { session: boardSession })
Expand Down Expand Up @@ -50,28 +89,45 @@ export default class DeleteBoardServiceImpl implements DeleteBoardService {

async delete(boardId: string, userId: string) {
const boardSession = await this.boardModel.db.startSession();
const boardUserSession = await this.boardUserModel.db.startSession();
boardSession.startTransaction();
boardUserSession.startTransaction();
const board = await this.boardModel.findById(boardId).exec();
if (!board) {
throw new NotFoundException('Board not found!');
}
const { team, createdBy } = board;
const teamUser = await this.getTeamUser(userId, String(team));
const users = await this.getUsersOfTeam(String(team));

const userIsSAdmin = await this.isUserSAdmin(userId, users);

try {
const { _id, dividedBoards } = await this.deleteBoard(boardId, userId, boardSession);
const isAdminOrStakeholder = [TeamRoles.STAKEHOLDER, TeamRoles.ADMIN].includes(
teamUser.role as TeamRoles
);

if (!isEmpty(dividedBoards)) {
await this.deleteSubBoards(dividedBoards, boardSession);
// Validate if the logged user are the owner
const isOwner = String(userId) === String(createdBy);

await this.deleteBoardUsers(dividedBoards, boardUserSession, _id);
}
if (isOwner || isAdminOrStakeholder || userIsSAdmin) {
const boardUserSession = await this.boardUserModel.db.startSession();
boardSession.startTransaction();
boardUserSession.startTransaction();
try {
const { _id, dividedBoards } = await this.deleteBoard(boardId, userId, boardSession);

await boardSession.commitTransaction();
await boardUserSession.commitTransaction();
return true;
} catch (e) {
await boardSession.abortTransaction();
await boardUserSession.abortTransaction();
} finally {
await boardSession.endSession();
await boardUserSession.endSession();
if (!isEmpty(dividedBoards)) {
await this.deleteSubBoards(dividedBoards, boardSession);

await this.deleteBoardUsers(dividedBoards, boardUserSession, _id);
}
await boardSession.commitTransaction();
await boardUserSession.commitTransaction();
return true;
} catch (e) {
await boardSession.abortTransaction();
await boardUserSession.abortTransaction();
} finally {
await boardSession.endSession();
await boardUserSession.endSession();
}
}

return false;
Expand Down
2 changes: 1 addition & 1 deletion backend/src/modules/teams/services/get.team.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default class GetTeamService implements GetTeamServiceInterface {
.find({ team: teamId })
.populate({
path: 'user',
select: '_id firstName lastName email'
select: '_id firstName lastName email isSAdmin'
})
.lean()
.exec();
Expand Down
20 changes: 14 additions & 6 deletions frontend/src/components/CardBoard/CardBody/CardBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,20 @@ const CardBody = React.memo<CardBodyProps>(
return !!users.find((user) => user.user._id === userId);
}, [users, userId]);

const userIsAdmin = useMemo(() => {
if (isSAdmin) return true;
if (team) {
return !!team.users.find((user) => user.role === 'admin');
const userIsAdminOrStakeholder = useMemo(() => {
if (isSAdmin) {
return true;
}
const myUser = team.users.find((user) => String(user.user._id) === String(userId));
const myUserIsOwner = board.createdBy._id === userId;
if (
team &&
(myUser?.role === 'admin' || myUser?.role === 'stakeholder' || myUserIsOwner)
) {
return true;
}
return !!users.find((user) => user.role === 'owner');
}, [isSAdmin, team, users]);
}, [isSAdmin, team, board.createdBy._id, userId, users]);

const handleOpenSubBoards = (e: ClickEvent<HTMLDivElement, MouseEvent>) => {
e.preventDefault();
Expand Down Expand Up @@ -225,7 +232,8 @@ const CardBody = React.memo<CardBodyProps>(
isDashboard={isDashboard}
isSubBoard={isSubBoard}
userId={userId}
userIsAdmin={userIsAdmin}
userIsAdminOrStakeholder={userIsAdminOrStakeholder}
userIsParticipating={userIsParticipating}
userSAdmin={isSAdmin}
/>
</InnerContainer>
Expand Down
15 changes: 12 additions & 3 deletions frontend/src/components/CardBoard/CardBody/CardEnd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,22 @@ type CardEndProps = {
isDashboard: boolean;
isSubBoard: boolean | undefined;
index: number | undefined;
userIsAdmin: boolean;
userIsAdminOrStakeholder: boolean;
userId: string;
userSAdmin?: boolean;
userIsParticipating: boolean;
};

const CardEnd: React.FC<CardEndProps> = React.memo(
({ board, isDashboard, isSubBoard, index, userIsAdmin, userId, userSAdmin = undefined }) => {
({
board,
isDashboard,
isSubBoard,
index,
userIsAdminOrStakeholder,
userId,
userSAdmin = undefined
}) => {
CardEnd.defaultProps = {
userSAdmin: undefined
};
Expand Down Expand Up @@ -82,7 +91,7 @@ const CardEnd: React.FC<CardEndProps> = React.memo(
</Flex>
)}
<CountCards columns={columns} />
{(userIsAdmin || userSAdmin) && !isSubBoard && (
{(userIsAdminOrStakeholder || userSAdmin) && !isSubBoard && (
<Flex align="center" css={{ ml: '$24' }} gap="24">
<Separator
orientation="vertical"
Expand Down

0 comments on commit 91bfa2d

Please sign in to comment.