Skip to content

Commit 17de288

Browse files
authored
Merge pull request #30 from indrasuthar07/main
feat(admin): implement CRUD endpoints for roles and permissions
2 parents f9252e5 + 24718ee commit 17de288

File tree

8 files changed

+271
-33
lines changed

8 files changed

+271
-33
lines changed

src/app.js

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,36 @@
1-
import express from 'express';
2-
import cors from 'cors';
3-
import cookieparser from 'cookie-parser';
4-
import authRoutes from './routes/authRoutes.js';
1+
import express from "express";
2+
import cors from "cors";
3+
import cookieParser from "cookie-parser";
4+
import authRoutes from "./routes/authRoutes.js"
55
import rbacRoutes from './routes/rbacRoutes.js';
6+
import dotenv from "dotenv";
7+
import roleRoutes from "./routes/role.routes.js";
8+
import permissionRoutes from "./routes/permission.routes.js";
9+
dotenv.config();
610

711
const app = express();
812

9-
app.use(cors({
10-
origin: process.env.CORS_URL,
11-
credentials: true
12-
}));
13+
// Middleware setup
14+
app.use(
15+
cors({
16+
origin: process.env.CORS_URL || "*",
17+
credentials: true,
18+
})
19+
);
1320

14-
app.use(express.json({ limit: '16kb' }));
15-
app.use(express.urlencoded({ extended: true, limit: '16kb' }));
16-
app.use(express.static('public'));
17-
app.use(cookieparser());
21+
app.use(express.json({ limit: "16kb" }));
22+
app.use(express.urlencoded({ extended: true, limit: "16kb" }));
23+
app.use(express.static("public"));
24+
app.use(cookieParser());
1825

19-
//routes
20-
app.use('/api/auth', authRoutes);
26+
// Routes
27+
app.use("/api/roles", roleRoutes);
28+
app.use("/api/permissions", permissionRoutes);
29+
app.use("/api/auth", authRoutes)
2130
app.use('/api/rbac-test', rbacRoutes);
31+
// Root route
32+
app.get("/", (req, res) => {
33+
res.send("RBAC is running...");
34+
});
2235

2336
export { app };
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as permissionService from "../services/permission.service.js";
2+
3+
export const createPermission = async (req, res) => {
4+
try {
5+
const { name, description } = req.body;
6+
const perm = await permissionService.createPermission(name, description);
7+
res.status(201).json(perm);
8+
} catch (err) {
9+
res.status(400).json({ message: err.message });
10+
}
11+
};
12+
13+
export const getPermissions = async (req, res) => {
14+
try {
15+
const perms = await permissionService.getPermissions();
16+
res.json(perms);
17+
} catch (err) {
18+
res.status(500).json({ message: err.message });
19+
}
20+
};
21+
22+
export const getPermissionById = async (req, res) => {
23+
try {
24+
const perm = await permissionService.getPermissionById(req.params.id);
25+
if (!perm) return res.status(404).json({ message: "Permission not found" });
26+
res.json(perm);
27+
} catch (err) {
28+
res.status(500).json({ message: err.message });
29+
}
30+
};
31+
32+
export const updatePermission = async (req, res) => {
33+
try {
34+
const perm = await permissionService.updatePermission(req.params.id, req.body);
35+
if (!perm) return res.status(404).json({ message: "Permission not found" });
36+
res.json(perm);
37+
} catch (err) {
38+
res.status(400).json({ message: err.message });
39+
}
40+
};
41+
42+
export const deletePermission = async (req, res) => {
43+
try {
44+
const deleted = await permissionService.deletePermission(req.params.id);
45+
if (!deleted) return res.status(404).json({ message: "Permission not found" });
46+
res.json({ message: "Permission deleted successfully" });
47+
} catch (err) {
48+
res.status(500).json({ message: err.message });
49+
}
50+
};

src/controllers/role.controller.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as roleService from "../services/role.service.js";
2+
3+
export const createRole = async (req, res) => {
4+
try {
5+
const { name, permissions } = req.body;
6+
const role = await roleService.createRole(name, permissions);
7+
res.status(201).json(role);
8+
} catch (err) {
9+
res.status(400).json({ message: err.message });
10+
}
11+
};
12+
13+
export const getRoles = async (req, res) => {
14+
try {
15+
const roles = await roleService.getRoles();
16+
res.json(roles);
17+
} catch (err) {
18+
res.status(500).json({ message: err.message });
19+
}
20+
};
21+
22+
export const getRoleById = async (req, res) => {
23+
try {
24+
const role = await roleService.getRoleById(req.params.id);
25+
if (!role) return res.status(404).json({ message: "Role not found" });
26+
res.json(role);
27+
} catch (err) {
28+
res.status(500).json({ message: err.message });
29+
}
30+
};
31+
32+
export const updateRole = async (req, res) => {
33+
try {
34+
const role = await roleService.updateRole(req.params.id, req.body);
35+
if (!role) return res.status(404).json({ message: "Role not found" });
36+
res.json(role);
37+
} catch (err) {
38+
res.status(400).json({ message: err.message });
39+
}
40+
};
41+
42+
export const deleteRole = async (req, res) => {
43+
try {
44+
const deleted = await roleService.deleteRole(req.params.id);
45+
if (!deleted) return res.status(404).json({ message: "Role not found" });
46+
res.json({ message: "Role deleted successfully" });
47+
} catch (err) {
48+
res.status(500).json({ message: err.message });
49+
}
50+
};
51+
52+
export const assignPermissions = async (req, res) => {
53+
try {
54+
const { permissions } = req.body;
55+
if (!Array.isArray(permissions)) {
56+
return res.status(400).json({ message: "permissions must be an array of permission IDs" });
57+
}
58+
const role = await roleService.assignPermissions(req.params.id, permissions);
59+
if (!role) return res.status(404).json({ message: "Role not found" });
60+
// populate permissions before returning
61+
const populated = await roleService.getRoleById(role._id);
62+
res.json(populated);
63+
} catch (err) {
64+
res.status(400).json({ message: err.message });
65+
}
66+
};

src/middlewares/auth.middleware.js

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,45 @@
1-
import jwt from 'jsonwebtoken';
1+
import jwt from "jsonwebtoken";
2+
import { User } from "../models/user.model.js";
3+
import Role from "../models/Role.model.js";
24

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

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

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

14-
try {
15-
const decoded = jwt.verify(token, process.env.JWT_SECRET);
16-
17-
req.user = decoded;
18-
next();
19-
} catch (error) {
20-
console.error("Error: ", error.message);
21-
return res.status(401).json({ message: "Unauthorized: Invalid token" });
22-
}
23-
}
16+
req.user = user;
17+
next();
18+
} catch (error) {
19+
return res.status(401).json({ message: "Invalid or expired token" });
20+
}
21+
};
2422

23+
export const rbacMiddleware = (requiredRole) => {
24+
return async (req, res, next) => {
25+
try {
26+
if (!req.user?.role) {
27+
return res.status(403).json({ message: "Role not assigned" });
28+
}
29+
let userRole;
30+
if (typeof req.user.role === "object" && req.user.role !== null) {
31+
userRole = req.user.role;
32+
} else {
33+
userRole = await Role.findById(req.user.role);
34+
}
2535

36+
if (!userRole || userRole.name !== requiredRole) {
37+
return res.status(403).json({ message: "Access denied" });
38+
}
39+
40+
next();
41+
} catch (error) {
42+
return res.status(500).json({ message: "Error in RBAC middleware" });
43+
}
44+
};
45+
};

src/routes/permission.routes.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import express from "express";
2+
import {
3+
createPermission,
4+
getPermissions,
5+
getPermissionById,
6+
updatePermission,
7+
deletePermission
8+
} from "../controllers/permission.controller.js";
9+
10+
const router = express.Router();
11+
12+
router.post("/", createPermission);
13+
router.get("/", getPermissions);
14+
router.get("/:id", getPermissionById);
15+
router.put("/:id", updatePermission);
16+
router.delete("/:id", deletePermission);
17+
18+
export default router;

src/routes/role.routes.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import express from "express";
2+
import {
3+
createRole,
4+
getRoles,
5+
getRoleById,
6+
updateRole,
7+
deleteRole,
8+
assignPermissions,
9+
} from "../controllers/role.controller.js";
10+
11+
const router = express.Router();
12+
13+
router.post("/", createRole);
14+
router.get("/", getRoles);
15+
router.get("/:id", getRoleById);
16+
router.put("/:id", updateRole);
17+
router.delete("/:id", deleteRole);
18+
router.put("/:id/permissions", assignPermissions);
19+
20+
export default router;

src/services/permission.service.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Permission from "../models/Permission.model.js";
2+
3+
export const createPermission = async (name, description) => {
4+
const perm = new Permission({ name, description });
5+
return await perm.save();
6+
};
7+
8+
export const getPermissions = async () => {
9+
return await Permission.find();
10+
};
11+
12+
export const getPermissionById = async (id) => {
13+
return await Permission.findById(id);
14+
};
15+
16+
export const updatePermission = async (id, data) => {
17+
return await Permission.findByIdAndUpdate(id, data, { new: true });
18+
};
19+
20+
export const deletePermission = async (id) => {
21+
return await Permission.findByIdAndDelete(id);
22+
};

src/services/role.service.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Role from "../models/Role.model.js";
2+
import Permission from "../models/Permission.model.js";
3+
4+
export const createRole = async (name, permissions = []) => {
5+
const role = new Role({ name, permissions });
6+
return await role.save();
7+
};
8+
9+
export const getRoles = async () => {
10+
return await Role.find().populate("permissions");
11+
};
12+
13+
export const getRoleById = async (id) => {
14+
return await Role.findById(id).populate("permissions");
15+
};
16+
17+
export const updateRole = async (id, data) => {
18+
return await Role.findByIdAndUpdate(id, data, { new: true });
19+
};
20+
21+
export const deleteRole = async (id) => {
22+
return await Role.findByIdAndDelete(id);
23+
};
24+
25+
export const assignPermissions = async (roleId, permissionIds) => {
26+
const role = await Role.findById(roleId);
27+
role.permissions = permissionIds;
28+
return await role.save();
29+
};

0 commit comments

Comments
 (0)