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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,5 @@ dist
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vite/

Temp folder/
31 changes: 31 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@types/socket.io": "^3.0.1",
"bcryptjs": "^3.0.2",
"cors": "^2.8.5",
"cookie-parser": "^1.4.6",
"dotenv": "^17.2.3",
"express": "^5.1.0",
"express-rate-limit": "^8.2.0",
Expand All @@ -44,6 +45,7 @@
"supertest": "^7.1.4",
"ts-jest": "^29.4.4",
"ts-node-dev": "^2.0.0",
"@types/cookie-parser": "^1.4.3",
"typescript": "^5.9.2"
}
}
11 changes: 10 additions & 1 deletion backend/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import dotenv from "dotenv";
dotenv.config();

import express from "express";
import cookieParser from 'cookie-parser';
import { createServer } from "http";
import cors from "cors";
import {
Expand All @@ -7,7 +11,8 @@
generalLimiter,
speedLimiter,
securityHeaders,
requestSizeLimiter
requestSizeLimiter,
csrfProtection
} from "./middleware/security";
import healthRoutes from "./routes/health";
import authRoutes from "./routes/auth";
Expand All @@ -30,6 +35,10 @@
app.use(helmetConfig); // Security headers
app.use(securityHeaders); // Additional custom security headers
app.use(corsConfig); // CORS policy
// Cookie parser (needed for refresh token cookie parsing)
app.use(cookieParser());
Comment thread Fixed

Check failure

Code scanning / CodeQL

Missing CSRF middleware High

This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.

Copilot Autofix

AI 6 months ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

// CSRF Protection (defense in depth with SameSite cookies)
app.use(csrfProtection);
app.use(requestSizeLimiter); // Request size limiting
// Note: Rate limiting now applied per-route for better control

Expand Down
61 changes: 60 additions & 1 deletion backend/src/middleware/security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ export const authLimiter = createRateLimit(
'Too many authentication attempts, please try again later'
);

export const refreshLimiter = createRateLimit(
15 * 60 * 1000, // 15 minutes
30, // 30 refresh attempts per window
'Too many token refresh attempts, please try again later'
);

export const generalLimiter = createRateLimit(
15 * 60 * 1000, // 15 minutes
500, // 500 requests per window (increased for better UX)
Expand Down Expand Up @@ -201,4 +207,57 @@ export const securityHeaders = (req: Request, res: Response, next: NextFunction)
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');

next();
};
};

// CSRF Protection via Origin/Referer Validation (defense in depth with SameSite cookies)
export const csrfProtection = (req: Request, res: Response, next: NextFunction) => {
// Skip CSRF for GET, HEAD, OPTIONS (safe methods)
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}

// Get origin or referer
const origin = req.get('origin');
const referer = req.get('referer');

// In development, be more lenient
if (process.env.NODE_ENV === 'development') {
return next();
}

// Build allowed origins
const envList = (process.env.FRONTEND_URLS || process.env.FRONTEND_URL || '')
.split(',')
.map(s => s.trim())
.filter(Boolean);

const allowedOrigins = [
'http://localhost:3000',
'http://localhost:5173',
'https://collab-code-review.vercel.app',
'https://collab-code-review-ram-prasads-projects-12031425.vercel.app',
...envList
];

// Check origin
if (origin) {
const isAllowed = allowedOrigins.some(allowed => origin === allowed) ||
/https?:\/\/[a-z0-9-]+\.vercel\.app$/i.test(origin);
if (isAllowed) {
return next();
}
}

// Check referer as fallback
if (referer) {
const isAllowed = allowedOrigins.some(allowed => referer.startsWith(allowed)) ||
/https?:\/\/[a-z0-9-]+\.vercel\.app/i.test(referer);
if (isAllowed) {
return next();
}
}

// If no origin/referer or they don't match, reject
console.warn(`CSRF protection blocked request from origin: ${origin}, referer: ${referer}`);
res.status(403).json({ error: 'CSRF validation failed' });
};
30 changes: 30 additions & 0 deletions backend/src/models/RefreshToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import mongoose, { Schema, Document } from 'mongoose';

export interface IRefreshToken extends Document {
user: mongoose.Types.ObjectId;
tokenHash: string;
expiresAt: Date;
createdAt: Date;
createdByIp?: string;
revoked?: boolean;
revokedAt?: Date;
revokedByIp?: string;
replacedByToken?: string;
}

const refreshTokenSchema = new Schema<IRefreshToken>(
{
user: { type: Schema.Types.ObjectId, ref: 'User', required: true },
tokenHash: { type: String, required: true, index: true },
expiresAt: { type: Date, required: true },
createdAt: { type: Date, default: Date.now },
createdByIp: { type: String },
revoked: { type: Boolean, default: false },
revokedAt: { type: Date },
revokedByIp: { type: String },
replacedByToken: { type: String }
},
{ timestamps: false }
);

export default mongoose.model<IRefreshToken>('RefreshToken', refreshTokenSchema);
Loading
Loading