first commit
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/middleware/auth.middleware.js
|
||||
|
||||
const { verifyToken } = require("../utils/jwt.util");
|
||||
const { getEffectivePermissions } = require("../services/permission.service");
|
||||
|
||||
const authenticate = async (req, res, next) => {
|
||||
try {
|
||||
let token = null;
|
||||
|
||||
// Get token from cookie
|
||||
if (req.cookies?.access_token) {
|
||||
token = req.cookies.access_token;
|
||||
}
|
||||
|
||||
// Fallback to Bearer token
|
||||
if (!token && req.headers.authorization?.startsWith("Bearer ")) {
|
||||
token = req.headers.authorization.split(" ")[1];
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: "Unauthorized",
|
||||
});
|
||||
}
|
||||
|
||||
// Verify token
|
||||
const decoded = verifyToken(token);
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("DECODED:", decoded);
|
||||
}
|
||||
|
||||
const userId = decoded.sub || decoded.id;
|
||||
|
||||
if (!userId) {
|
||||
throw new Error("User ID missing in token");
|
||||
}
|
||||
|
||||
// Load permissions
|
||||
const permissions = await getEffectivePermissions(userId);
|
||||
|
||||
req.user = {
|
||||
...decoded,
|
||||
id: userId,
|
||||
permissions,
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
console.error("AUTH ERROR:", err);
|
||||
|
||||
// Clear invalid/expired cookie
|
||||
res.clearCookie("access_token", {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: process.env.NODE_ENV === "production" ? "None" : "Lax",
|
||||
});
|
||||
|
||||
// Token expired
|
||||
if (err.name === "TokenExpiredError") {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: "Session expired. Please login again.",
|
||||
});
|
||||
}
|
||||
|
||||
// Invalid token
|
||||
if (err.name === "JsonWebTokenError") {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: "Invalid token",
|
||||
});
|
||||
}
|
||||
|
||||
// Default
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: "Authentication failed",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { authenticate };
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/middleware/docsSession.middleware.js
|
||||
|
||||
module.exports = (req, res, next) => {
|
||||
if (req.cookies && req.cookies.docsAuth === "true") {
|
||||
return next();
|
||||
}
|
||||
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: "Unauthorized: Please login to access docs",
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/middleware/permission.middleware.js
|
||||
|
||||
const hasPermission = (userPermissions, requiredPermission) => {
|
||||
return userPermissions.some(p => {
|
||||
if (p === requiredPermission) return true;
|
||||
|
||||
// wildcard support
|
||||
if (p.endsWith(".*")) {
|
||||
const prefix = p.slice(0, -2);
|
||||
return requiredPermission.startsWith(prefix);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
const methodToAction = {
|
||||
GET: "view",
|
||||
POST: "create",
|
||||
PUT: "update",
|
||||
PATCH: "update",
|
||||
DELETE: "delete"
|
||||
};
|
||||
|
||||
const checkPermission = (baseOrFull, options = {}) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: "Unauthorized"
|
||||
});
|
||||
}
|
||||
|
||||
// admin bypass
|
||||
if (req.user.accountType === "admin") {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (typeof baseOrFull !== "string") {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Permission must be a string"
|
||||
});
|
||||
}
|
||||
|
||||
let requiredPermission;
|
||||
|
||||
if (options.custom) {
|
||||
requiredPermission = baseOrFull;
|
||||
} else {
|
||||
const action = methodToAction[req.method];
|
||||
|
||||
if (!action) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Unknown HTTP method"
|
||||
});
|
||||
}
|
||||
|
||||
requiredPermission = `${baseOrFull}.${action}`;
|
||||
}
|
||||
|
||||
const userPermissions = req.user.permissions || [];
|
||||
|
||||
const hasPermission =
|
||||
userPermissions.includes(requiredPermission) ||
|
||||
userPermissions.includes(`${baseOrFull}.*`);
|
||||
|
||||
if (!hasPermission) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: `Forbidden - Missing ${requiredPermission}`
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
const authorizedAccountType = (allowedTypes) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.user || !allowedTypes.includes(req.user.accountType)) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ success: false, message: "Forbidden" });
|
||||
}
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {authorizedAccountType, checkPermission};
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/middlewares/upload.middleware.js
|
||||
|
||||
const multer = require("multer");
|
||||
|
||||
// Use memory storage (required for S3 upload)
|
||||
const storage = multer.memoryStorage();
|
||||
|
||||
// File filter (only images + PDFs)
|
||||
const fileFilter = (req, file, cb) => {
|
||||
const allowedTypes = [
|
||||
"image/",
|
||||
"application/pdf",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // .xlsx
|
||||
"application/vnd.ms-excel" // .xls
|
||||
];
|
||||
|
||||
const isValid =
|
||||
file.mimetype.startsWith("image/") ||
|
||||
file.mimetype === "application/pdf" ||
|
||||
file.mimetype === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ||
|
||||
file.mimetype === "application/vnd.ms-excel";
|
||||
|
||||
if (isValid) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error("Only images, PDFs, and Excel files are allowed"), false);
|
||||
}
|
||||
};
|
||||
|
||||
// Multer instance
|
||||
const upload = multer({
|
||||
storage,
|
||||
fileFilter,
|
||||
limits: {
|
||||
fileSize: 5 * 1024 * 1024, // max 5MB (global limit)
|
||||
},
|
||||
});
|
||||
|
||||
// Middleware wrappers
|
||||
|
||||
// Single file upload
|
||||
const uploadSingle = (fieldName) => upload.single(fieldName);
|
||||
|
||||
// Multiple files (same field)
|
||||
const uploadMultiple = (fieldName, maxCount = 5) =>
|
||||
upload.array(fieldName, maxCount);
|
||||
|
||||
// Multiple fields (advanced)
|
||||
const uploadFields = (fields) => upload.fields(fields);
|
||||
|
||||
module.exports = {
|
||||
uploadSingle,
|
||||
uploadMultiple,
|
||||
uploadFields,
|
||||
};
|
||||
Reference in New Issue
Block a user