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
41 changes: 27 additions & 14 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
import express from 'express';
import cors from 'cors';
import cookieparser from 'cookie-parser';
import authRoutes from './routes/authRoutes.js';
import express from "express";
import cors from "cors";
import cookieParser from "cookie-parser";
import authRoutes from "./routes/authRoutes.js"
import rbacRoutes from './routes/rbacRoutes.js';
import dotenv from "dotenv";
import roleRoutes from "./routes/role.routes.js";
import permissionRoutes from "./routes/permission.routes.js";
dotenv.config();

const app = express();

app.use(cors({
origin: process.env.CORS_URL,
credentials: true
}));
// Middleware setup
app.use(
cors({
origin: process.env.CORS_URL || "*",
credentials: true,
})
);

app.use(express.json({ limit: '16kb' }));
app.use(express.urlencoded({ extended: true, limit: '16kb' }));
app.use(express.static('public'));
app.use(cookieparser());
app.use(express.json({ limit: "16kb" }));
app.use(express.urlencoded({ extended: true, limit: "16kb" }));
app.use(express.static("public"));
app.use(cookieParser());

//routes
app.use('/api/auth', authRoutes);
// Routes
app.use("/api/roles", roleRoutes);
app.use("/api/permissions", permissionRoutes);
app.use("/api/auth", authRoutes)
app.use('/api/rbac-test', rbacRoutes);
// Root route
app.get("/", (req, res) => {
res.send("RBAC is running...");
});

export { app };
50 changes: 50 additions & 0 deletions src/controllers/permission.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as permissionService from "../services/permission.service.js";

export const createPermission = async (req, res) => {
try {
const { name, description } = req.body;
const perm = await permissionService.createPermission(name, description);
res.status(201).json(perm);
} catch (err) {
res.status(400).json({ message: err.message });
}
};

export const getPermissions = async (req, res) => {
try {
const perms = await permissionService.getPermissions();
res.json(perms);
} catch (err) {
res.status(500).json({ message: err.message });
}
};

export const getPermissionById = async (req, res) => {
try {
const perm = await permissionService.getPermissionById(req.params.id);
if (!perm) return res.status(404).json({ message: "Permission not found" });
res.json(perm);
} catch (err) {
res.status(500).json({ message: err.message });
}
};

export const updatePermission = async (req, res) => {
try {
const perm = await permissionService.updatePermission(req.params.id, req.body);
if (!perm) return res.status(404).json({ message: "Permission not found" });
res.json(perm);
} catch (err) {
res.status(400).json({ message: err.message });
}
};

export const deletePermission = async (req, res) => {
try {
const deleted = await permissionService.deletePermission(req.params.id);
if (!deleted) return res.status(404).json({ message: "Permission not found" });
res.json({ message: "Permission deleted successfully" });
} catch (err) {
res.status(500).json({ message: err.message });
}
};
66 changes: 66 additions & 0 deletions src/controllers/role.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as roleService from "../services/role.service.js";

export const createRole = async (req, res) => {
try {
const { name, permissions } = req.body;
const role = await roleService.createRole(name, permissions);
res.status(201).json(role);
} catch (err) {
res.status(400).json({ message: err.message });
}
};

export const getRoles = async (req, res) => {
try {
const roles = await roleService.getRoles();
res.json(roles);
} catch (err) {
res.status(500).json({ message: err.message });
}
};

export const getRoleById = async (req, res) => {
try {
const role = await roleService.getRoleById(req.params.id);
if (!role) return res.status(404).json({ message: "Role not found" });
res.json(role);
} catch (err) {
res.status(500).json({ message: err.message });
}
};

export const updateRole = async (req, res) => {
try {
const role = await roleService.updateRole(req.params.id, req.body);
if (!role) return res.status(404).json({ message: "Role not found" });
res.json(role);
} catch (err) {
res.status(400).json({ message: err.message });
}
};

export const deleteRole = async (req, res) => {
try {
const deleted = await roleService.deleteRole(req.params.id);
if (!deleted) return res.status(404).json({ message: "Role not found" });
res.json({ message: "Role deleted successfully" });
} catch (err) {
res.status(500).json({ message: err.message });
}
};

export const assignPermissions = async (req, res) => {
try {
const { permissions } = req.body;
if (!Array.isArray(permissions)) {
return res.status(400).json({ message: "permissions must be an array of permission IDs" });
}
const role = await roleService.assignPermissions(req.params.id, permissions);
if (!role) return res.status(404).json({ message: "Role not found" });
// populate permissions before returning
const populated = await roleService.getRoleById(role._id);
res.json(populated);
} catch (err) {
res.status(400).json({ message: err.message });
}
};
58 changes: 39 additions & 19 deletions src/middlewares/auth.middleware.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
import jwt from 'jsonwebtoken';
import jwt from "jsonwebtoken";
import { User } from "../models/user.model.js";
import Role from "../models/Role.model.js";

export const authMiddleware = (req, res, next) => {
const authHeader = req.headers.authorization;
export const authMiddleware = async (req, res, next) => {
try {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ message: "No token provided" });

if (!authHeader || !authHeader.startsWith('Bearer')) {
return res.status(401).json({
message: "Unauthorize : No token provided"
});
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// populate role for convenience
const user = await User.findById(decoded._id).populate("role");

const token = authHeader.split(" ")[1];
if (!user) return res.status(404).json({ message: "User not found" });

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);

req.user = decoded;
next();
} catch (error) {
console.error("Error: ", error.message);
return res.status(401).json({ message: "Unauthorized: Invalid token" });
}
}
req.user = user;
next();
} catch (error) {
return res.status(401).json({ message: "Invalid or expired token" });
}
};

export const rbacMiddleware = (requiredRole) => {
return async (req, res, next) => {
try {
if (!req.user?.role) {
return res.status(403).json({ message: "Role not assigned" });
}
let userRole;
if (typeof req.user.role === "object" && req.user.role !== null) {
userRole = req.user.role;
} else {
userRole = await Role.findById(req.user.role);
}

if (!userRole || userRole.name !== requiredRole) {
return res.status(403).json({ message: "Access denied" });
}

next();
} catch (error) {
return res.status(500).json({ message: "Error in RBAC middleware" });
}
};
};
18 changes: 18 additions & 0 deletions src/routes/permission.routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import express from "express";
import {
createPermission,
getPermissions,
getPermissionById,
updatePermission,
deletePermission
} from "../controllers/permission.controller.js";

const router = express.Router();

router.post("/", createPermission);
router.get("/", getPermissions);
router.get("/:id", getPermissionById);
router.put("/:id", updatePermission);
router.delete("/:id", deletePermission);

export default router;
20 changes: 20 additions & 0 deletions src/routes/role.routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import express from "express";
import {
createRole,
getRoles,
getRoleById,
updateRole,
deleteRole,
assignPermissions,
} from "../controllers/role.controller.js";

const router = express.Router();

router.post("/", createRole);
router.get("/", getRoles);
router.get("/:id", getRoleById);
router.put("/:id", updateRole);
router.delete("/:id", deleteRole);
router.put("/:id/permissions", assignPermissions);

export default router;
22 changes: 22 additions & 0 deletions src/services/permission.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Permission from "../models/Permission.model.js";

export const createPermission = async (name, description) => {
const perm = new Permission({ name, description });
return await perm.save();
};

export const getPermissions = async () => {
return await Permission.find();
};

export const getPermissionById = async (id) => {
return await Permission.findById(id);
};

export const updatePermission = async (id, data) => {
return await Permission.findByIdAndUpdate(id, data, { new: true });
};

export const deletePermission = async (id) => {
return await Permission.findByIdAndDelete(id);
};
29 changes: 29 additions & 0 deletions src/services/role.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Role from "../models/Role.model.js";
import Permission from "../models/Permission.model.js";

export const createRole = async (name, permissions = []) => {
const role = new Role({ name, permissions });
return await role.save();
};

export const getRoles = async () => {
return await Role.find().populate("permissions");
};

export const getRoleById = async (id) => {
return await Role.findById(id).populate("permissions");
};

export const updateRole = async (id, data) => {
return await Role.findByIdAndUpdate(id, data, { new: true });
};

export const deleteRole = async (id) => {
return await Role.findByIdAndDelete(id);
};

export const assignPermissions = async (roleId, permissionIds) => {
const role = await Role.findById(roleId);
role.permissions = permissionIds;
return await role.save();
};
Loading