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() {
+