Skip to content

Commit 057f534

Browse files
Archithclaude
andcommitted
feat: Add comprehensive fraud detection UI for candidate tracking
- Display prominent FRAUD RISK level (LOW/MEDIUM/HIGH) in Security tab - Show detailed fraud indicators for candidates only: • VPN/Proxy usage flagged as suspicious • Multiple login attempts highlighted as fraud warning • Account sharing detection - Color-coded fraud assessment panel - Detailed security alerts with location information - Candidate rows highlighted in red when fraud detected - Clear separation between tracked candidates and untracked interviewers Fraud Risk Calculation: - LOW: No indicators detected (green) - MEDIUM: 1-2 indicators (orange warning) - HIGH: 3+ indicators (red alert) All fraud data stored in Firebase under security_warnings for audit trail. Interviewers can now easily identify potential cheating or fraudulent activity. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 05263a1 commit 057f534

File tree

4 files changed

+264
-76
lines changed

4 files changed

+264
-76
lines changed

api/check-duplicate-login.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default async function handler(req, res) {
3434
}
3535

3636
try {
37-
const { userId, userName, sessionCode, action } = req.body;
37+
const { userId, userName, sessionCode, action, userType } = req.body;
3838

3939
if (!userId || !sessionCode) {
4040
return res.status(400).json({ error: 'Missing required fields' });
@@ -49,7 +49,16 @@ export default async function handler(req, res) {
4949
const hashedIP = hashIP(ip);
5050
const sessionKey = `${sessionCode}-${userId}`;
5151

52-
// Handle different actions
52+
// Skip all checks for interviewers
53+
if (userType === 'interviewer') {
54+
return res.status(200).json({
55+
success: true,
56+
skipped: true,
57+
message: 'No duplicate login checks for interviewers'
58+
});
59+
}
60+
61+
// Handle different actions (ONLY FOR CANDIDATES)
5362
if (action === 'login') {
5463
// Check if user is already logged in from different IP
5564
const existingSession = activeSessions.get(sessionKey);
@@ -59,11 +68,18 @@ export default async function handler(req, res) {
5968
const timeSinceLastActivity = Date.now() - existingSession.lastActivity;
6069

6170
if (timeSinceLastActivity < 5 * 60 * 1000) { // Active in last 5 minutes
62-
return res.status(403).json({
63-
error: 'Multiple login detected',
64-
message: 'You are already logged into this session from another location',
71+
// Log the suspicious activity for candidates
72+
console.log(`⚠️ Multiple login detected: candidate ${userName} from different IP`);
73+
console.log(`Existing: ${existingSession.location}, New: ${req.headers['cf-ipcountry'] || 'Unknown'}`);
74+
75+
// Return warning but still allow login (200 status, not 403)
76+
return res.status(200).json({
77+
success: true,
78+
warning: 'multiple_login_detected',
79+
message: 'Note: You appear to be logging in from a different location',
6580
existingLocation: existingSession.location,
6681
existingDevice: existingSession.device,
82+
newLocation: req.headers['cf-ipcountry'] || 'Unknown',
6783
lastActivity: new Date(existingSession.lastActivity).toISOString()
6884
});
6985
}
@@ -74,6 +90,7 @@ export default async function handler(req, res) {
7490
userId,
7591
userName,
7692
sessionCode,
93+
userType: userType || 'unknown',
7794
ip: hashedIP,
7895
location: req.headers['cf-ipcountry'] || 'Unknown', // Cloudflare geo header
7996
device: req.headers['user-agent']?.substring(0, 50),

api/track-session.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,15 @@ export default async function handler(req, res) {
144144
return res.status(400).json({ error: 'Missing required fields' });
145145
}
146146

147+
// Skip tracking for interviewers entirely
148+
if (metadata.userType === 'interviewer') {
149+
return res.status(200).json({
150+
success: true,
151+
skipped: true,
152+
message: 'Tracking skipped for interviewer'
153+
});
154+
}
155+
147156
// Get all IPs
148157
const allIPs = getAllIPs(req);
149158
const primaryIP = allIPs[0];

scripts/app.js

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
return;
7575
}
7676

77-
// Initialize session tracking (IP, device, duplicate login check)
77+
// Initialize session tracking for candidates only (IP, device, duplicate login check)
7878
if (window.SessionTracking) {
7979
candidateJoinBtn.textContent = 'Checking security...';
8080
const canProceed = await window.SessionTracking.initialize(sessionCode, 'candidate', name);
@@ -264,20 +264,7 @@
264264
`${interviewerName} (${currentUser.email})` :
265265
currentUser.email || 'Interviewer';
266266

267-
// Initialize session tracking (IP, device tracking)
268-
if (window.SessionTracking) {
269-
createSessionBtn.disabled = true;
270-
createSessionBtn.textContent = 'Initializing security...';
271-
const canProceed = await window.SessionTracking.initialize(sessionCode, 'interviewer', adminName);
272-
if (!canProceed) {
273-
createSessionBtn.disabled = false;
274-
createSessionBtn.textContent = 'Create New Session';
275-
// Re-show dashboard
276-
document.getElementById('adminDashboardModal').style.display = 'flex';
277-
document.getElementById('activeSession').style.display = 'none';
278-
return;
279-
}
280-
}
267+
// No tracking for interviewers creating sessions
281268

282269
// Start session - DON'T set sessionStarting here, let startSession handle it
283270
startSession(adminName, sessionCode, true);
@@ -295,18 +282,7 @@
295282
`${interviewerName} (${currentUser.email})` :
296283
currentUser.email || 'Interviewer';
297284

298-
// Initialize session tracking (IP, device, duplicate login check)
299-
if (window.SessionTracking) {
300-
adminJoinBtn.disabled = true;
301-
adminJoinBtn.textContent = 'Checking security...';
302-
const canProceed = await window.SessionTracking.initialize(sessionCode, 'interviewer', adminName);
303-
if (!canProceed) {
304-
adminJoinBtn.disabled = false;
305-
adminJoinBtn.textContent = 'Join Session';
306-
return;
307-
}
308-
adminJoinBtn.textContent = 'Joining...';
309-
}
285+
// No tracking for interviewers joining sessions
310286

311287
window.location.hash = sessionCode;
312288
startSession(adminName, sessionCode, false);

0 commit comments

Comments
 (0)