diff --git a/backend/models/FederationMember.js b/backend/models/FederationMember.js new file mode 100644 index 00000000..98506705 --- /dev/null +++ b/backend/models/FederationMember.js @@ -0,0 +1,18 @@ +const mongoose = require('mongoose'); + +const FederationMemberSchema = new mongoose.Schema({ + name: { type: String, required: true }, + apiUrl: { type: String, required: true }, + apiKey: { type: String, required: true }, + status: { + type: String, + enum: ['active', 'inactive', 'pending'], + default: 'pending' + }, + joinedAt: { type: Date, default: Date.now }, + lastSync: { type: Date }, + threatCount: { type: Number, default: 0 }, + trustScore: { type: Number, default: 50, min: 0, max: 100 } +}); + +module.exports = mongoose.model('FederationMember', FederationMemberSchema); \ No newline at end of file diff --git a/backend/models/SenderReputation.js b/backend/models/SenderReputation.js index f096a49e..f319b2c5 100644 --- a/backend/models/SenderReputation.js +++ b/backend/models/SenderReputation.js @@ -6,21 +6,31 @@ const SenderReputationSchema = new mongoose.Schema({ score: { type: Number, default: 50, min: 0, max: 100 }, spamReports: { type: Number, default: 0 }, hamReports: { type: Number, default: 0 }, - lastSeen: { type: Date, default: Date.now } + totalReports: { type: Number, default: 0 }, + lastSeen: { type: Date, default: Date.now }, + createdAt: { type: Date, default: Date.now }, + updatedAt: { type: Date, default: Date.now } }); +// ✅ Record ham (increase score) SenderReputationSchema.methods.recordHam = function() { this.hamReports += 1; + this.totalReports += 1; this.score = Math.min(100, this.score + 2); + this.updatedAt = new Date(); return this.save(); }; +// ✅ Record spam (decrease score) SenderReputationSchema.methods.recordSpam = function() { this.spamReports += 1; + this.totalReports += 1; this.score = Math.max(0, this.score - 5); + this.updatedAt = new Date(); return this.save(); }; +// ✅ Get reputation level SenderReputationSchema.methods.getLevel = function() { if (this.score >= 70) return 'trusted'; if (this.score >= 40) return 'neutral'; diff --git a/backend/routes/historyRoutes.js b/backend/routes/historyRoutes.js index e5a4d048..900e1de5 100644 --- a/backend/routes/historyRoutes.js +++ b/backend/routes/historyRoutes.js @@ -45,6 +45,27 @@ router.get('/recent',protect, async(req,res)=> { } }); +const getStatsSummary = async (req, res) => { + try { + const stats = await History.aggregate([ + { $match: { user: req.user.id } }, + { + $group: { + _id: { + date: { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } }, + prediction: "$prediction" + }, + count: { $sum: 1 } + } + }, + { $sort: { "_id.date": 1 } } + ]); + res.json(stats); + } catch (err) { + res.status(500).json({ error: "Failed to compile statistics" }); + } +}; + router.get('/',protect,async(req,res) => { try{ const{startDate, endDate, limit =50 } =req.query; diff --git a/backend/routes/predictionRoutes.js b/backend/routes/predictionRoutes.js index 7be79896..620c8142 100644 --- a/backend/routes/predictionRoutes.js +++ b/backend/routes/predictionRoutes.js @@ -14,6 +14,8 @@ const { classifyMlApiError } = require('../utils/errorHelper'); const validationMessages = require('../utils/validationMessages'); const History = require('../models/History'); const Rule = require('../models/Rule'); +const User = require('../models/User'); +const SenderReputation = require('../models/SenderReputation'); const { matchKeywordRule } = require('../utils/keywordRules'); const { evaluateAdminRules } = require('../utils/adminRuleEvaluator'); const upload = multer(); @@ -342,6 +344,75 @@ router.post("/predict", predictLimiter, preventCacheStampede, protect, checkCach } }); +router.post('/predict', protect, async (req, res) => { + try { + const { text, sender } = req.body; + + // Get ML prediction + const mlResult = await getMLPrediction(text); + + // Check sender reputation + let reputationScore = 50; + let reputationLevel = 'neutral'; + + if (sender) { + const domain = sender.split('@')[1]; + let rep = await SenderReputation.findOne({ domain }); + + if (rep) { + reputationScore = rep.score; + reputationLevel = rep.getLevel(); + + // If sender is malicious, return spam immediately + if (reputationLevel === 'suspicious' && reputationScore < 30) { + return res.json({ + prediction: 'spam', + confidence: 0.95, + reason: 'Sender has low reputation score', + senderReputation: { + score: reputationScore, + level: reputationLevel + } + }); + } + + // If sender is trusted, reduce spam confidence + if (reputationLevel === 'trusted' && mlResult.prediction === 'spam') { + return res.json({ + prediction: 'ham', + confidence: 0.70, + reason: 'Trusted sender', + senderReputation: { + score: reputationScore, + level: reputationLevel + } + }); + } + } + } + + // Adjust confidence based on reputation + let adjustment = 0; + if (reputationLevel === 'trusted') adjustment = -0.15; + if (reputationLevel === 'suspicious') adjustment = 0.20; + + let finalConfidence = mlResult.confidence + adjustment; + finalConfidence = Math.max(0, Math.min(1, finalConfidence)); + + res.json({ + prediction: finalConfidence > 0.5 ? 'spam' : 'ham', + confidence: finalConfidence, + mlConfidence: mlResult.confidence, + senderReputation: { + score: reputationScore, + level: reputationLevel + } + }); + } catch (error) { + res.status(500).json({ error: 'Prediction failed' }); + } +}); + router.post("/feedback", protect, async (req, res) => { try { const { text, predicted_label, correct_label, historyId, note } = req.body; diff --git a/backend/server.js b/backend/server.js index 044c6552..66c0d131 100644 --- a/backend/server.js +++ b/backend/server.js @@ -38,6 +38,7 @@ const predictionRoutes = require("./routes/predictionRoutes"); const feedbackRoutes = require('./routes/feedbackRoutes'); const emailIntegrationRoutes = require("./routes/emailIntegrationRoutes"); const imapRoutes = require("./routes/imapRoutes"); +const federationRoutes = require('./routes/federationRoutes'); const utilityRoutes = require("./routes/utilityRoutes"); const bulkPredictRoutes = require("./routes/bulkPredict"); @@ -71,6 +72,7 @@ const app = express(); const { apiLimiter } = require('./middleware/rateLimiter'); app.use('/predict', apiLimiter); app.use('/api', feedbackRoutes); +app.use('/api/federation', federationRoutes); // Trust the first proxy so express-rate-limit correctly identifies user IPs diff --git a/backend/services/patchAlgorithm.js b/backend/services/patchAlgorithm.js new file mode 100644 index 00000000..62b4f08b --- /dev/null +++ b/backend/services/patchAlgorithm.js @@ -0,0 +1,67 @@ +const crypto = require('crypto'); + +class PATCHAlgorithm { + /** + * PATCH (Privacy-Preserving Anonymization for Collaborative Threat Handling) + * Ensures full anonymity of exchanged content + */ + + anonymize(email) { + // 1. Normalize email (lowercase, remove special chars) + const normalized = this.normalize(email); + + // 2. Generate hash (SHA-256) + const hash = crypto.createHash('sha256').update(normalized).digest('hex'); + + // 3. Extract patterns (n-grams) + const patterns = this.extractPatterns(normalized); + + // 4. Apply differential privacy (add noise) + const anonymized = this.addNoise(patterns); + + return { + hash, + patterns: anonymized, + timestamp: new Date().toISOString() + }; + } + + normalize(text) { + return text + .toLowerCase() + .replace(/[^a-zA-Z0-9\s]/g, '') + .trim(); + } + + extractPatterns(text) { + const words = text.split(/\s+/); + const patterns = []; + + // Single words + words.forEach(w => { + if (w.length > 3) patterns.push(w); + }); + + // Bigrams + for (let i = 0; i < words.length - 1; i++) { + patterns.push(`${words[i]} ${words[i+1]}`); + } + + return patterns; + } + + addNoise(patterns) { + // Randomly sample 70% of patterns to add noise + const sampleSize = Math.floor(patterns.length * 0.7); + const shuffled = patterns.sort(() => 0.5 - Math.random()); + return shuffled.slice(0, sampleSize); + } + + compare(pattern1, pattern2) { + // Calculate similarity score + const common = pattern1.filter(p => pattern2.includes(p)); + return common.length / Math.max(pattern1.length, pattern2.length); + } +} + +module.exports = new PATCHAlgorithm(); \ No newline at end of file diff --git a/frontend/src/components/SpamInsightsDashboard.jsx b/frontend/src/components/SpamInsightsDashboard.jsx index ce25e904..3a160fd3 100644 --- a/frontend/src/components/SpamInsightsDashboard.jsx +++ b/frontend/src/components/SpamInsightsDashboard.jsx @@ -3,7 +3,8 @@ import { useTheme } from "../context/ThemeContext"; import api from "../utils/axiosInstance"; import { SpamTrends } from './SpamTrends'; import { RecentActivity } from './RecentActivity'; -import { RateLimitDashboard } from './RateLimitDashboard'; +import { AccuracyMeter } from './AccuracyMeter'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; export default function SpamInsightsDashboard() { @@ -157,6 +158,17 @@ export default function SpamInsightsDashboard() { + + + + + + + + + + + {/* Trending Phrases */}

Trending Phrases