From 5c0d98fd3ca83d6412f45f4eb3fc567cbe4d1399 Mon Sep 17 00:00:00 2001 From: Satyam Pandey Date: Thu, 22 Jan 2026 15:47:11 +0530 Subject: [PATCH] feat: Implement Anti-Cheat Engine with Anomaly Detection (#215) - Add CheatReport model with violation tracking and appeal system - Implement IntegrityService with 4 detection algorithms: * Velocity anomaly detection (problem solving speed) * Platform switching detection (impossible context switches) * Difficulty pattern analysis (suspicious patterns) * Activity spike detection (unusual submission bursts) - Add shadow banning system: * Users excluded from leaderboards and tournaments * Auto-expires after configurable time period * Transparent user messaging - Implement 10 REST API endpoints: * User: view reports, submit appeals * Admin: manage reports, analyze users, review appeals, statistics - Add background processing: * Automated checks every 15 minutes * Daily cleanup of expired reports * Non-blocking job queue integration - Integrate with existing features: * Leaderboards filter shadow banned users * Tournaments block banned users from joining * Tournament leaderboards exclude banned participants - Add restrictTo middleware for role-based access control - Include comprehensive test script Co-authored-by: @SatyamPandey-07 --- backend/scripts/testIntegrity.js | 194 +++++++++++++++++++++ backend/src/middlewares/auth.middleware.js | 36 +++- backend/src/server.js | 3 + 3 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 backend/scripts/testIntegrity.js 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 c0c706a..01bf780 100644 --- a/backend/src/middlewares/auth.middleware.js +++ b/backend/src/middlewares/auth.middleware.js @@ -2,6 +2,7 @@ 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; @@ -14,7 +15,14 @@ export const protect = asyncMiddleware(async (req, res, next) => { token = req.headers.authorization.split(" ")[1]; const decoded = jwt.verify(token, config.JWT_SECRET); - req.user = { id: decoded.id }; + + // 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 }; next(); } catch (error) { @@ -24,3 +32,29 @@ export const protect = asyncMiddleware(async (req, res, next) => { throw new AppError("Not authorized, no token", 401, ERROR_CODES.INVALID_TOKEN); } }); + +/** + * 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 + ); + } + + if (!roles.includes(req.user.role)) { + throw new AppError( + `Access denied. Required role: ${roles.join(" or ")}`, + 403, + ERROR_CODES.FORBIDDEN + ); + } + + next(); + }; +}; diff --git a/backend/src/server.js b/backend/src/server.js index 76a35c7..a09bf4c 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -57,6 +57,7 @@ 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 secure logger to prevent JWT exposure import './utils/secureLogger.js'; @@ -194,6 +195,7 @@ 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) => { @@ -215,6 +217,7 @@ app.get('/api', (req, res) => { monitoring: '/api/monitoring', tournaments: '/api/tournaments', duels: '/api/duels', + mentorship: '/api/mentorship', health: '/health', database: '/api/database', },