diff --git a/backend/scripts/testIntegrity.js b/backend/scripts/testIntegrity.js new file mode 100644 index 0000000..ca4b462 --- /dev/null +++ b/backend/scripts/testIntegrity.js @@ -0,0 +1,194 @@ +/** + * Integrity System Test Script + * + * This script demonstrates and tests the integrity system functionality. + * Run with: node backend/scripts/testIntegrity.js + */ + +import mongoose from "mongoose"; +import IntegrityService from "../src/services/integrity.service.js"; +import IntegrityJob from "../src/jobs/integrity.job.js"; +import User from "../src/models/user.model.js"; +import ActivityLog from "../src/models/activityLog.model.js"; +import CheatReport from "../src/models/cheatReport.model.js"; +import connectDB from "../src/config/db.js"; + +async function testIntegritySystem() { + console.log("๐Ÿš€ Starting Integrity System Tests...\n"); + + try { + // Connect to database + await connectDB(); + console.log("โœ… Connected to database\n"); + + // Test 1: Create test user + console.log("๐Ÿ“ Test 1: Creating test user..."); + let testUser = await User.findOne({ email: "integrity-test@example.com" }); + + if (!testUser) { + testUser = await User.create({ + name: "Integrity Test User", + email: "integrity-test@example.com", + password: "TestPassword123!", + }); + console.log(`โœ… Test user created: ${testUser._id}\n`); + } else { + console.log(`โœ… Using existing test user: ${testUser._id}\n`); + } + + // Test 2: Create suspicious activity pattern + console.log("๐Ÿ“ Test 2: Creating suspicious activity pattern..."); + const now = new Date(); + const activities = []; + + // Create rapid hard problem solving (velocity anomaly) + for (let i = 0; i < 5; i++) { + activities.push({ + userId: testUser._id, + platform: "leetcode", + action: "problem_solved", + count: 1, + difficulty: "hard", + date: new Date(now.getTime() + i * 30000), // 30 seconds apart + }); + } + + // Create impossible platform switching + activities.push( + { + userId: testUser._id, + platform: "codeforces", + action: "problem_solved", + count: 1, + difficulty: "medium", + date: new Date(now.getTime() + 150000), // 2.5 minutes + }, + { + userId: testUser._id, + platform: "codechef", + action: "problem_solved", + count: 1, + difficulty: "medium", + date: new Date(now.getTime() + 160000), // 10 seconds later + }, + { + userId: testUser._id, + platform: "leetcode", + action: "problem_solved", + count: 1, + difficulty: "easy", + date: new Date(now.getTime() + 165000), // 5 seconds later + } + ); + + await ActivityLog.insertMany(activities); + console.log(`โœ… Created ${activities.length} suspicious activities\n`); + + // Test 3: Analyze user + console.log("๐Ÿ“ Test 3: Analyzing user for violations..."); + const analysis = await IntegrityService.analyzeUser(testUser._id, 60); + + console.log("Analysis Results:"); + console.log(` - Suspicious: ${analysis.suspicious}`); + console.log(` - Flags found: ${analysis.flags.length}`); + console.log(` - Activities analyzed: ${analysis.activitiesAnalyzed}`); + + if (analysis.flags.length > 0) { + console.log("\n Detected violations:"); + analysis.flags.forEach((flag, index) => { + console.log(` ${index + 1}. ${flag.type}`); + console.log(` Severity: ${flag.severity}`); + console.log(` Confidence: ${flag.confidence}%`); + }); + } + console.log(); + + // Test 4: Create reports + console.log("๐Ÿ“ Test 4: Creating cheat reports..."); + const reports = []; + for (const flag of analysis.flags) { + const report = await IntegrityService.createReport( + testUser._id, + flag, + "automated" + ); + reports.push(report); + } + console.log(`โœ… Created ${reports.length} cheat reports\n`); + + // Test 5: Check shadow ban status + console.log("๐Ÿ“ Test 5: Checking shadow ban status..."); + const isShadowBanned = await IntegrityService.isUserShadowBanned(testUser._id); + const isTournamentBanned = await IntegrityService.isUserBannedFromTournaments( + testUser._id + ); + + console.log(` - Shadow Banned: ${isShadowBanned}`); + console.log(` - Tournament Banned: ${isTournamentBanned}\n`); + + // Test 6: Get user's reports + console.log("๐Ÿ“ Test 6: Retrieving user's reports..."); + const userReports = await IntegrityService.getUserReports(testUser._id); + console.log(`โœ… Found ${userReports.length} reports for user\n`); + + // Test 7: Get all reports (admin view) + console.log("๐Ÿ“ Test 7: Getting all reports..."); + const allReports = await IntegrityService.getAllReports({ + status: "investigating", + limit: 10, + }); + console.log(`โœ… Found ${allReports.length} reports in investigating status\n`); + + // Test 8: Run integrity job + console.log("๐Ÿ“ Test 8: Running automated integrity job..."); + const jobResult = await IntegrityJob.runIntegrityCheck({ + timeWindow: 60, + maxUsers: 10, + minActivityCount: 3, + }); + + console.log("Job Results:"); + console.log(` - Total users checked: ${jobResult.totalUsers}`); + console.log(` - Suspicious users: ${jobResult.suspicious}`); + console.log(` - Clean users: ${jobResult.clean}`); + console.log(` - Reports created: ${jobResult.reportsCreated}`); + console.log(` - Errors: ${jobResult.errors}\n`); + + // Test 9: Statistics + console.log("๐Ÿ“ Test 9: Getting integrity statistics..."); + const stats = await CheatReport.aggregate([ + { + $group: { + _id: "$status", + count: { $sum: 1 }, + }, + }, + ]); + + console.log("Statistics by status:"); + stats.forEach((stat) => { + console.log(` - ${stat._id}: ${stat.count}`); + }); + console.log(); + + console.log("โœ… All tests completed successfully!\n"); + + // Cleanup option + console.log("๐Ÿงน Cleanup: Removing test data..."); + await ActivityLog.deleteMany({ userId: testUser._id }); + await CheatReport.deleteMany({ userId: testUser._id }); + await User.deleteOne({ _id: testUser._id }); + console.log("โœ… Cleanup completed\n"); + + } catch (error) { + console.error("โŒ Test failed:", error.message); + console.error(error.stack); + } finally { + await mongoose.disconnect(); + console.log("๐Ÿ‘‹ Disconnected from database"); + process.exit(0); + } +} + +// Run tests +testIntegritySystem(); diff --git a/backend/src/middlewares/auth.middleware.js b/backend/src/middlewares/auth.middleware.js index 8cb9202..01bf780 100644 --- a/backend/src/middlewares/auth.middleware.js +++ b/backend/src/middlewares/auth.middleware.js @@ -1,91 +1,60 @@ -import jwt from 'jsonwebtoken'; -import { AppError } from '../utils/appError.js'; - -// Authentication bypass protection -export const authBypassProtection = (req, res, next) => { - const suspiciousPatterns = [ - /admin/i, - /root/i, - /bypass/i, - /\.\.\/\.\.\//, - /null/i, - /undefined/i, - /'or'1'='1'/i, - /union\s+select/i - ]; - - const checkValue = (value) => { - if (!value) return false; - return suspiciousPatterns.some(pattern => pattern.test(value)); - }; - - // Check headers - const authHeader = req.headers.authorization; - if (authHeader && checkValue(authHeader)) { - return next(new AppError('Suspicious authentication attempt detected', 401)); - } +import jwt from "jsonwebtoken"; +import { AppError, ERROR_CODES } from "../utils/appError.js"; +import { asyncMiddleware } from "../utils/asyncWrapper.js"; +import config from "../config/env.js"; +import User from "../models/user.model.js"; + +export const protect = asyncMiddleware(async (req, res, next) => { + let token; + + if ( + req.headers.authorization && + req.headers.authorization.startsWith("Bearer") + ) { + try { + token = req.headers.authorization.split(" ")[1]; + + const decoded = jwt.verify(token, config.JWT_SECRET); + + // Fetch user with role + const user = await User.findById(decoded.id).select("role"); + if (!user) { + throw new AppError("User not found", 401, ERROR_CODES.INVALID_TOKEN); + } + + req.user = { id: decoded.id, role: user.role }; - // Check query parameters - for (const [key, value] of Object.entries(req.query)) { - if (checkValue(key) || checkValue(value)) { - return next(new AppError('Suspicious query parameter detected', 400)); + next(); + } catch (error) { + throw new AppError("Not authorized, token failed", 401, ERROR_CODES.INVALID_TOKEN); } } else { throw new AppError("Not authorized, no token", 401, ERROR_CODES.INVALID_TOKEN); } - - // Check body parameters - if (req.body && typeof req.body === 'object') { - const checkObject = (obj) => { - for (const [key, value] of Object.entries(obj)) { - if (checkValue(key) || (typeof value === 'string' && checkValue(value))) { - return true; - } - if (typeof value === 'object' && value !== null && checkObject(value)) { - return true; - } - } - return false; - }; - - if (checkObject(req.body)) { - return next(new AppError('Suspicious request body detected', 400)); +}); + +/** + * Restrict access to specific roles + * Usage: restrictTo('admin', 'moderator') + */ +export const restrictTo = (...roles) => { + return (req, res, next) => { + if (!req.user || !req.user.role) { + throw new AppError( + "You are not authorized to access this resource", + 403, + ERROR_CODES.FORBIDDEN + ); } - } - next(); -}; - -// JWT token validation -export const validateToken = (req, res, next) => { - const token = req.headers.authorization?.split(' ')[1]; - - if (!token) { - return next(); - } - - try { - // Basic token format validation - if (token.length < 10 || token.length > 2048) { - return next(new AppError('Invalid token format', 401)); + if (!roles.includes(req.user.role)) { + throw new AppError( + `Access denied. Required role: ${roles.join(" or ")}`, + 403, + ERROR_CODES.FORBIDDEN + ); } - // Check for suspicious token patterns - const suspiciousTokenPatterns = [ - /^null$/i, - /^undefined$/i, - /^admin$/i, - /^test$/i, - /^debug$/i - ]; - - if (suspiciousTokenPatterns.some(pattern => pattern.test(token))) { - return next(new AppError('Suspicious token detected', 401)); - } - - req.token = token; next(); - } catch (error) { - next(new AppError('Token validation failed', 401)); - } + }; }; diff --git a/backend/src/server.js b/backend/src/server.js index 93f65ce..4fd4472 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -47,20 +47,23 @@ import advancedCacheRoutes from './routes/advancedCache.routes.js'; import notificationRoutes from './routes/notification.routes.js'; import analyticsRoutes from './routes/analytics.routes.js'; import securityRoutes from './routes/security.routes.js'; -import healthRoutes from './routes/health.routes.js'; -import { secureLogger, secureErrorHandler } from './middlewares/secureLogging.middleware.js'; -import { validateEnvironment } from './config/environment.js'; -import { connectionManager } from './utils/connectionManager.js'; -import { memoryMonitor } from './services/memoryMonitor.service.js'; -import { cpuMonitor } from './services/cpuMonitor.service.js'; -import { bandwidthMonitor } from './services/bandwidthMonitor.service.js'; -import { processLimiter } from './utils/processLimiter.js'; -import { cacheManager } from './utils/cacheManager.js'; -import { gracefulShutdown } from './utils/shutdown.util.js'; -import { authBypassProtection, validateToken } from './middlewares/auth.middleware.js'; -import { fileUploadSecurity, validateFileExtensions, detectEncodedFiles } from './middlewares/fileUpload.middleware.js'; -import { apiVersionSecurity, deprecationWarning, validateApiEndpoint, versionRateLimit } from './middlewares/apiVersion.middleware.js'; -import { csrfProtection, csrfTokenEndpoint } from './middlewares/csrf.middleware.js'; +import databaseRoutes from './routes/database.routes.js'; +import websocketRoutes from './routes/websocket.routes.js'; +import quotaRoutes from './routes/quota.routes.js'; +import jobsRoutes from './routes/jobs.routes.js'; +import monitoringRoutes from './routes/monitoring.routes.js'; +import grindRoomRoutes from './routes/grindRoom.routes.js'; +import tournamentRoutes from './routes/tournament.routes.js'; +import duelRoutes from './routes/duel.routes.js'; +import mentorshipRoutes from './routes/mentorship.routes.js'; + +import monitoringRoutes from './routes/monitoring.routes.js'; +// Import secure logger to prevent JWT exposure + +import './utils/secureLogger.js'; +// Import constants +import { HTTP_STATUS, ENVIRONMENTS } from './constants/app.constants.js'; +import Logger from './utils/logger.js'; // Set default NODE_ENV if not provided if (!process.env.NODE_ENV) { @@ -215,47 +218,65 @@ app.get('/api/leetcode/:username', data, traceId: req.traceId }); - }) -); - -app.get('/api/codeforces/:username', - scrapingBodyLimit, - scrapingSizeLimit, - scrapingTimeout, - heavyOperationProtection, - csrfProtection, - validateUsername, - asyncHandler(async (req, res) => { - const { username } = req.params; - const raw = await backpressureManager.process(() => - withTrace(req.traceId, "codeforces.scrape", () => - fetchCodeforcesStats(username) - ) - ); - const normalized = normalizeCodeforces({ ...raw, username }); - res.json({ success: true, data: normalized, traceId: req.traceId }); - }) -); + } catch (error) { + Logger.error('Health check failed', { error: error.message }); + res.status(503).json({ + success: false, + message: 'Server unhealthy', + error: error.message, + timestamp: new Date().toISOString(), + }); + } +}); -app.get('/api/codechef/:username', - scrapingBodyLimit, - scrapingSizeLimit, - scrapingTimeout, - heavyOperationProtection, - csrfProtection, - validateUsername, - asyncHandler(async (req, res) => { - const { username } = req.params; - const raw = await backpressureManager.process(() => - withTrace(req.traceId, "codechef.scrape", () => - fetchCodeChefStats(username) - ) - ); - const normalized = normalizeCodeChef({ ...raw, username }); - res.json({ success: true, data: normalized, traceId: req.traceId }); - }) -); +// API routes +app.use('/api/scrape', scrapeRoutes); +app.use('/api/auth', authRoutes); +app.use('/api/cache', cacheRoutes); +app.use('/api/advanced-cache', advancedCacheRoutes); +app.use('/api/notifications', notificationRoutes); +app.use('/api/analytics', analyticsRoutes); +app.use('/api/security', securityRoutes); +app.use('/api/database', databaseRoutes); +app.use('/api/websocket', websocketRoutes); +app.use('/api/quota', quotaRoutes); +app.use('/api/upload', fileUploadRoutes); +app.use('/api/job-monitoring', jobMonitoringRoutes); +app.use('/api/monitoring', monitoringRoutes); +app.use('/api/rooms', grindRoomRoutes); +app.use('/api/tournaments', tournamentRoutes); +app.use('/api/duels', duelRoutes); +app.use('/api/mentorship', mentorshipRoutes); + +// API documentation endpoint +app.get('/api', (req, res) => { + res.status(HTTP_STATUS.OK).json({ + success: true, + message: 'GrindMap API v1.0', + documentation: '/api/docs', + endpoints: { + scraping: '/api/scrape', + authentication: '/api/auth', + cache: '/api/cache', + advancedCache: '/api/advanced-cache', + notifications: '/api/notifications', + analytics: '/api/analytics', + websocket: '/ws', + websocketAPI: '/api/websocket', + quota: '/api/quota', + jobs: '/api/jobs', + monitoring: '/api/monitoring', + tournaments: '/api/tournaments', + duels: '/api/duels', + mentorship: '/api/mentorship', + health: '/health', + database: '/api/database', + }, + correlationId: req.correlationId, + }); +}); +// 404 handler for undefined routes app.use(notFound); app.use(secureErrorHandler); app.use(errorHandler);