diff --git a/RentalCar/server/controllers/documentController.js b/RentalCar/server/controllers/documentController.js index 43f653c..b3d44c1 100644 --- a/RentalCar/server/controllers/documentController.js +++ b/RentalCar/server/controllers/documentController.js @@ -2,6 +2,7 @@ import imagekit from "../configs/imageKit.js"; import Document from "../models/Document.js"; import User from "../models/User.js"; import { getExtensionFromMime } from "../middleware/multer.js"; +import { logSecurityEvent } from "../services/securityLogger.js"; const ALLOWED_DOCUMENT_TYPES = [ "DRIVING_LICENSE", @@ -61,6 +62,13 @@ export const uploadDocument = async (req, res) => { $addToSet: { documents: doc._id }, }); + await logSecurityEvent({ + event: "DOCUMENT_UPLOAD", + userId: req.user?._id, + req, + status: 201, + details: "Document uploaded" + }); return res.status(201).json({ success: true, message: "Document uploaded successfully", diff --git a/RentalCar/server/controllers/userController.js b/RentalCar/server/controllers/userController.js index d9a9d01..58d4602 100644 --- a/RentalCar/server/controllers/userController.js +++ b/RentalCar/server/controllers/userController.js @@ -2,6 +2,7 @@ import User from "../models/User.js"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import Car from "../models/Car.js"; +import { logSecurityEvent } from "../services/securityLogger.js"; const MAX_LOGIN_ATTEMPTS = 5; const LOCK_TIME_MS = 15 * 60 * 1000; // 15 minuta @@ -75,8 +76,16 @@ export const loginUser = async (req, res) => { }; if (!user) { - return res.status(401).json(invalidMessage); - } + await logSecurityEvent({ + event: "LOGIN_FAILED", + email, + req, + status: 401, + details: "User not found" + }); + + return res.status(401).json(invalidMessage); +} if (user.lockUntil && user.lockUntil > new Date()) { return res.status(423).json({ @@ -94,6 +103,15 @@ export const loginUser = async (req, res) => { const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { + + await logSecurityEvent({ + event: "LOGIN_FAILED", + email, + req, + status: 401, + details: "Invalid password" + }); + user.loginAttempts += 1; if (user.loginAttempts >= MAX_LOGIN_ATTEMPTS) { @@ -117,6 +135,14 @@ export const loginUser = async (req, res) => { const token = generateToken(user); + await logSecurityEvent({ + event: "LOGIN_SUCCESS", + userId: user._id, + email: user.email, + req, + status: 200 + }); + return res.status(200).json({ success: true, token, diff --git a/RentalCar/server/middleware/roles.js b/RentalCar/server/middleware/roles.js index 3ed45cb..1dc72cb 100644 --- a/RentalCar/server/middleware/roles.js +++ b/RentalCar/server/middleware/roles.js @@ -1,6 +1,9 @@ +import { logSecurityEvent } from "../services/securityLogger.js"; + export const requireRole = (...allowedRoles) => - (req, res, next) => { + async (req, res, next) => { + const role = req.user?.role; if (!role) { @@ -8,6 +11,15 @@ export const requireRole = } if (!allowedRoles.includes(role)) { + + await logSecurityEvent({ + event: "ACCESS_DENIED", + userId: req.user?._id, + req, + status: 403, + details: "RBAC role violation" + }); + return res.status(403).json({ success: false, message: "forbidden (insufficient role)", diff --git a/RentalCar/server/middleware/securityAudit.js b/RentalCar/server/middleware/securityAudit.js new file mode 100644 index 0000000..32e2556 --- /dev/null +++ b/RentalCar/server/middleware/securityAudit.js @@ -0,0 +1,17 @@ +import { logSecurityEvent } from "../services/securityLogger.js"; + +export const securityAudit = async (req, res, next) => { + try { + await logSecurityEvent({ + event: "REQUEST_AUDIT", + userId: req.user?._id || null, + req, + status: 200, + details: "API request" + }); + } catch (err) { + console.error("Audit log failed:", err.message); + } + + next(); +}; \ No newline at end of file diff --git a/RentalCar/server/models/SecurityLog.js b/RentalCar/server/models/SecurityLog.js new file mode 100644 index 0000000..6f788dd --- /dev/null +++ b/RentalCar/server/models/SecurityLog.js @@ -0,0 +1,19 @@ +import mongoose from "mongoose"; + +const securityLogSchema = new mongoose.Schema({ + event: { type: String, required: true }, + user: { type: mongoose.Schema.Types.ObjectId, ref: "User", default: null }, + ip: String, + path: String, + method: String, + status: Number, + details: String, + createdAt: { + type: Date, + default: Date.now + } +}); + +const SecurityLog = mongoose.model("SecurityLog", securityLogSchema); + +export default SecurityLog; \ No newline at end of file diff --git a/RentalCar/server/server.js b/RentalCar/server/server.js index 84b6b86..a15856f 100644 --- a/RentalCar/server/server.js +++ b/RentalCar/server/server.js @@ -20,6 +20,8 @@ import documentRouter from "./routes/documentRoutes.js"; import paymentRouter from "./routes/paymentRoutes.js"; import integrationsRouter from "./routes/integrationsRoutes.js"; +import { securityAudit } from "./middleware/securityAudit.js"; + // Initialize Express App const app = express(); @@ -87,6 +89,8 @@ app.use( app.use(xss()); +app.use(securityAudit); + // Health app.get("/", (req, res) => res.send("Server is running")); diff --git a/RentalCar/server/services/securityLogger.js b/RentalCar/server/services/securityLogger.js new file mode 100644 index 0000000..4553c16 --- /dev/null +++ b/RentalCar/server/services/securityLogger.js @@ -0,0 +1,23 @@ +import SecurityLog from "../models/SecurityLog.js"; + +export const logSecurityEvent = async ({ + event, + userId = null, + req = null, + status = null, + details = null +}) => { + try { + await SecurityLog.create({ + event, + user: userId, + ip: req?.ip || null, + path: req?.originalUrl || null, + method: req?.method || null, + status, + details + }); + } catch (err) { + console.error("Security log failed:", err.message); + } +}; \ No newline at end of file