Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion server/controllers/memberControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export const getMemberRoleById = async (
throw new ConflictError("The user is not a member of the project");

next(
new SuccessResponse("Member role has been updated successfully.", {
new SuccessResponse("Member role has been fetched successfully.", {
role: member.members[0].role,
})
);
Expand Down
23 changes: 16 additions & 7 deletions server/controllers/projectController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import User from "@root/models/userModel.ts";
import SuccessResponse from "@root/success/SuccessResponse.ts";
import CreatedResponse from "@root/success/CreatedResponse.ts";
import Changelog from "@root/models/changelogModel.ts";
import { ValidatedRoleRequest } from "@root/types/authTypes.ts";

/**
* Get projects based on query parameters.
Expand Down Expand Up @@ -80,20 +81,28 @@ export const getAllProjects = async (

// Get a project by ID
export const getProjectById = async (
req: Request,
req: ValidatedRoleRequest,
res: Response,
next: NextFunction
) => {
try {
const userId = req.userId

// Find the project by ID
const project = await Project.findById(req.params.id).select('-members');
const project = await Project.findById(req.params.projectId);

// Check if the project exists
if (!project) throw new NotFoundError("Project not found.");

const member = project.members.find((user) => user.userId.toString() === userId)
const userRole = member ? member.role : null

const { members, ...projectData} = project.toJSON()

next(
new SuccessResponse("Successfully fetched the project.", {
project,
project: projectData,
userRole
})
);
} catch (err: any) {
Expand All @@ -109,14 +118,14 @@ export const editProject = async (
) => {
try {
// Find the project by ID
const project = await Project.findById(req.params.id);
const project = await Project.findById(req.params.projectId);

// Check if the project exists
if (!project) throw new NotFoundError("Project not found.");

// Update the project
const updatedProject = await Project.findByIdAndUpdate(
req.params.id,
req.params.projectId,
req.body,
{ new: true }
).select('-data -members');
Expand Down Expand Up @@ -184,7 +193,7 @@ export const saveProject = async (
) => {
try {
// Find the project by ID
const project = await Project.findById(req.params.id).select('-members');
const project = await Project.findById(req.params.projectId).select('-members');
const { data, members = [] } = req.body;

// Check if the project exists
Expand Down Expand Up @@ -238,7 +247,7 @@ export const deleteProject = async (
next: NextFunction
) => {
try {
const project = await Project.findByIdAndDelete(req.params.id);
const project = await Project.findByIdAndDelete(req.params.projectId);

if (!project) throw new NotFoundError("Project not found.");

Expand Down
55 changes: 39 additions & 16 deletions server/middleware/validators/memberRoleValidator.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,58 @@
import NotFoundError from "@root/errors/NotFoundError";
import ValidationError from "@root/errors/ValidationError";
import Project from "@root/models/projectModel"
import User from "@root/models/userModel";
import { Response, NextFunction } from "express";


const getMemberByEmail = async (email: string) => {
const user = await User.findOne(
{ email: email }
)

if (!user) {
console.log("User not found")
throw new NotFoundError("User not found");
}

return user._id.toString()
}

const getMemberRole = async (projectId: string, userId: string) => {
const project = await Project.findOne(
{ _id: projectId, "members.userId": userId },
{ "members.$": 1 }
);
const project = await Project.findById(projectId).select("members generalAccess");

console.log(projectId, userId)
if (!project) {
console.log("Project not found")
throw new NotFoundError("Project not found");
}

const member = project.members.find((user) => user.userId.toString() === userId);
const memberRole = member ? member.role : null;
const generalRole = project.generalAccess?.role || null;
const accessType = project.generalAccess?.accessType || "Restricted";

if (!project || project.members.length === 0) {
console.log("Project or member not found")
return null;
if (accessType === "Restricted" && !memberRole) {
console.log("Access denied: User is not a member of the project.");
throw new NotFoundError("Access denied: User is not a member.");
}

console.log("Got the member role")
return project.members[0].role;
return { memberRole, generalRole };
}

export const checkRole = (allowedRoles: string[]) => async (req: any, res: Response, next: NextFunction) => {
try {
const { projectId } = req.params;
const userId = req.passport;

console.log(req.user)
const userId = await getMemberByEmail(req.user.email);

const role = await getMemberRole(projectId, userId)
const { memberRole, generalRole } = await getMemberRole(projectId, userId)

if (!role || !allowedRoles.includes(role))
throw new ValidationError("Access denied")
const hasAccess = (memberRole && allowedRoles.includes(memberRole)) || (generalRole && allowedRoles.includes(generalRole));

if (!hasAccess) {
throw new ValidationError("Access denied: Insufficient permissions.");
}

req.userId = userId;

next()
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion server/middleware/validators/projectValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const validate = (req: Request, res: Response, next: NextFunction) => {
next();
};

export const validateProjectId = param("id")
export const validateProjectId = param("projectId")
.trim()
.isMongoId()
.withMessage("The project ID must be a valid MongoDB ID.");
Expand Down
10 changes: 6 additions & 4 deletions server/routes/memberRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const router = Router();
router.get(
"/:projectId/members",
[validateToken, validateProjectId, validate],
checkRole(["Viewer", "Editor", "Admin", "Owner"]),
getMembersByProjectId
);
router.post(
Expand All @@ -34,7 +35,7 @@ router.post(
validateRoleOptional,
validate,
],
// checkRole(["Editor", "Admin", "Owner"]),
checkRole(["Editor", "Admin", "Owner"]),
addMember
);
router.patch(
Expand All @@ -46,7 +47,7 @@ router.patch(
validateRoleRequired,
validate,
],
// checkRole(["Admin", "Owner"]),
checkRole(["Admin", "Owner"]),
editMemberRole
);
router.get(
Expand All @@ -57,12 +58,13 @@ router.get(
validateUserId,
validate,
],
checkRole(["Viewer", "Editor", "Admin", "Owner"]),
getMemberRoleById
);
router.delete(
"/:projectId/members/:userId",
[validateToken, validateProjectId, validateUserId, validate],
// checkRole(["Admin", "Owner"]),
checkRole(["Admin", "Owner"]),
removeMember
);
router.patch(
Expand All @@ -74,7 +76,7 @@ router.patch(
validateAccessRole,
validate
],
// checkRole(["Admin", "Owner"]),
checkRole(["Admin", "Owner"]),
editGeneralAccess
)

Expand Down
15 changes: 11 additions & 4 deletions server/routes/projectRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
validateOnlyDataField,
} from "@root/middleware/validators/projectValidator";
import { validateToken } from "@root/middleware/validators/authValidator";
import { checkRole } from "@root/middleware/validators/memberRoleValidator";

const router = express.Router();

Expand All @@ -44,7 +45,12 @@ router.get("", [validateToken, validateUserIdQuery, validate], getProjects);
* - validateProjectId: Ensures the provided project ID is valid.
* - validate: General validation middleware.
*/
router.get("/:id", [validateToken, validateProjectId, validate], getProjectById);
router.get(
"/:projectId",
[validateToken, validateProjectId, validate],
checkRole(["Viewer", "Editor", "Admin", "Owner"]),
getProjectById
);

/**
* PATCH /projects/:id
Expand All @@ -57,7 +63,7 @@ router.get("/:id", [validateToken, validateProjectId, validate], getProjectById)
* - validate: General validation middleware.
*/
router.patch(
"/:id",
"/:projectId",
[
validateToken,
validateProjectId,
Expand All @@ -80,14 +86,15 @@ router.patch(
*
*/
router.patch(
"/:id/data",
"/:projectId/data",
[
validateToken,
validateProjectId,
validateProjectData,
validateOnlyDataField,
validate,
],
checkRole(["Viewer", "Editor", "Admin", "Owner"]),
saveProject
);

Expand All @@ -113,7 +120,7 @@ router.post(
* - validate: General validation middleware.
*/
router.delete(
"/:id",
"/:projectId",
[validateToken, validateProjectId, validate],
deleteProject
);
Expand Down
5 changes: 5 additions & 0 deletions server/types/authTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Request } from "express";
import { Session } from "express-session";


export interface ValidatedRoleRequest extends Request {
userId?: string
}

export interface AuthRequest<P = {}, B = {}> extends Request<P, {}, B> {
session: CustomSession;
user?: any;
Expand Down
34 changes: 26 additions & 8 deletions src/data/repo/useDataInitializer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom";
import { useEmojiStore } from "../../store/globalStore";
import { getProjectsApi } from "../api/projectsApi";
import { db } from "../db/db";
Expand All @@ -9,13 +9,16 @@ import useHistoryRepo from "./useHistoryRepo";
import useProjectRepo from "./useProjectRepo";
import { useSettingsRepo } from "./useSettingsRepo";
import useUserRepo from "./useUserRepo";
import CustomNotification from "../../components/ui/CustomNotification";
import { AxiosError } from "axios";

export const useDataInitializer = () => {
const { user } = useUserRepo();
const { setProjectList, selectProject } = useProjectRepo();
const { loadChangelogs } = useChangelogRepo();
const { fetchUserSettings } = useSettingsRepo();
const { resetHistory } = useHistoryRepo();
const navigate = useNavigate();

// Router
const params = useParams();
Expand All @@ -35,19 +38,34 @@ export const useDataInitializer = () => {
}, []);

useEffect(() => {
if (id && user) {
selectProject(id, user.id);
loadChangelogs(id);
resetHistory();
const loadSelectedProject = async () => {
try {
if (id && user && isLoaded) {
const res = await selectProject(id);
resetHistory();
if (res) {
loadChangelogs(id);
}

}
} catch (err) {
if (err instanceof AxiosError) {
CustomNotification({
status: "error",
title: "Failed to Load Project",
message: err.response?.data.message,
});
}
navigate("/", { replace: true })
}
}

loadSelectedProject()
}, [id, isLoaded]);

const loadProjects = async () => {
console.log("Loading projects from local storage");

// Dexie fetching of projects
// NOTE: Not needed anymore
// const projectList = await db.projects.toArray();
if (!user) return;

const getProjectList = await getProjectsApi(user?.id);
Expand Down
2 changes: 1 addition & 1 deletion src/data/repo/useMemberRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const useMemberRepo = () => {
try {
setLoading(true);
setError(null);

console.log("Get member")
const response = await getProjectMembersApi(projectId);

if (response.success) {
Expand Down
Loading