Skip to content
8 changes: 8 additions & 0 deletions RentalCar/server/controllers/documentController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
30 changes: 28 additions & 2 deletions RentalCar/server/controllers/userController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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({
Expand All @@ -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) {
Expand All @@ -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,
Expand Down
14 changes: 13 additions & 1 deletion RentalCar/server/middleware/roles.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { logSecurityEvent } from "../services/securityLogger.js";

export const requireRole =
(...allowedRoles) =>
(req, res, next) => {
async (req, res, next) => {

const role = req.user?.role;

if (!role) {
return res.status(401).json({ success: false, message: "not authorized" });
}

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)",
Expand Down
17 changes: 17 additions & 0 deletions RentalCar/server/middleware/securityAudit.js
Original file line number Diff line number Diff line change
@@ -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();
};
19 changes: 19 additions & 0 deletions RentalCar/server/models/SecurityLog.js
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 4 additions & 0 deletions RentalCar/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -87,6 +89,8 @@ app.use(

app.use(xss());

app.use(securityAudit);

// Health
app.get("/", (req, res) => res.send("Server is running"));

Expand Down
23 changes: 23 additions & 0 deletions RentalCar/server/services/securityLogger.js
Original file line number Diff line number Diff line change
@@ -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);
}
};
Loading