Skip to content

Commit 05263a1

Browse files
Archithclaude
andcommitted
feat: Add security dashboard and Firebase integration
- Add Security tab to session details modal - Display real-time tracking data from Firebase - Show participant activity with IP, location, device info - Display VPN/proxy detection flags - Show security alerts and warnings - Store all tracking data in Firebase per session - Alert users when VPN is detected on join - Add comprehensive security analytics dashboard The security data is now: 1. Stored in Firebase under sessions/{code}/tracking/ 2. Visible in the Security tab of session details 3. Used to restrict duplicate logins from different IPs 4. Shows VPN usage, location, device fingerprinting 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 9858410 commit 05263a1

File tree

5 files changed

+280
-11
lines changed

5 files changed

+280
-11
lines changed

api/track-session.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,12 @@ export default async function handler(req, res) {
253253

254254
trackingData.securityFlags = securityFlags;
255255

256-
// Store in database (you'll need to set up Firebase Admin SDK)
257-
// For now, we'll just return the data
258-
// In production, store this in Firebase under sessions/${sessionCode}/tracking/${timestamp}
256+
// Store in Firebase
257+
// Store tracking data in Firebase
258+
const trackingRef = `sessions/${sessionCode}/tracking/${Date.now()}_${userId}`;
259+
// This would require Firebase Admin SDK setup on Vercel
260+
// For now, we'll send it back to the client to store
261+
trackingData.firebasePath = trackingRef;
259262

260263
// Return response
261264
res.status(200).json({

index.html

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ <h3>Session: <span id="detail-session-code"></span></h3>
374374
<button class="detail-tab active" data-tab="notes">📝 Notes</button>
375375
<button class="detail-tab" data-tab="code">💻 Code</button>
376376
<button class="detail-tab" data-tab="info">ℹ️ Info</button>
377+
<button class="detail-tab" data-tab="security">🔒 Security</button>
377378
</div>
378379

379380
<div class="tab-content" id="notes-tab" style="display: block;">
@@ -406,6 +407,41 @@ <h3>Session: <span id="detail-session-code"></span></h3>
406407
<p><strong>Status:</strong> <span id="display-status"></span></p>
407408
</div>
408409
</div>
410+
411+
<div class="tab-content" id="security-tab" style="display: none;">
412+
<div class="security-info-display">
413+
<h4>🔍 Session Security & Tracking</h4>
414+
<div id="security-tracking-data">
415+
<p class="loading-message">Loading security data...</p>
416+
</div>
417+
418+
<h4>👥 Participant Activity</h4>
419+
<div id="participant-activity">
420+
<table class="security-table">
421+
<thead>
422+
<tr>
423+
<th>User</th>
424+
<th>Type</th>
425+
<th>Location</th>
426+
<th>Device</th>
427+
<th>IP Hash</th>
428+
<th>Flags</th>
429+
</tr>
430+
</thead>
431+
<tbody id="participant-activity-body">
432+
<!-- Will be populated dynamically -->
433+
</tbody>
434+
</table>
435+
</div>
436+
437+
<h4>⚠️ Security Alerts</h4>
438+
<div id="security-alerts">
439+
<ul id="security-alerts-list">
440+
<!-- Will be populated dynamically -->
441+
</ul>
442+
</div>
443+
</div>
444+
</div>
409445
</div>
410446
</div>
411447
</div>

scripts/app.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1325,7 +1325,15 @@
13251325
this.classList.add('active');
13261326
const tabName = this.getAttribute('data-tab');
13271327
const tabContent = document.getElementById(`${tabName}-tab`);
1328-
if (tabContent) tabContent.style.display = 'block';
1328+
if (tabContent) {
1329+
tabContent.style.display = 'block';
1330+
1331+
// If security tab, load tracking data
1332+
if (tabName === 'security' && window.SessionTracking) {
1333+
const sessionCode = document.getElementById('detail-session-code').textContent;
1334+
window.SessionTracking.displaySecurityTab(sessionCode);
1335+
}
1336+
}
13291337
});
13301338
});
13311339

scripts/session-tracking.js

Lines changed: 129 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,24 @@
3535
if (data.success) {
3636
console.log('Session tracked:', data.tracked);
3737

38+
// Store tracking data in Firebase
39+
if (data.fullData && window.firebase) {
40+
const trackingPath = data.fullData.firebasePath || `sessions/${sessionCode}/tracking/${Date.now()}_${userId}`;
41+
try {
42+
await firebase.database().ref(trackingPath).set(data.fullData);
43+
console.log('Tracking data stored in Firebase');
44+
} catch (fbError) {
45+
console.error('Failed to store tracking in Firebase:', fbError);
46+
}
47+
}
48+
3849
// Show security warnings if any
3950
if (data.tracked.vpnDetected) {
4051
console.warn('VPN/Proxy detected');
52+
// Show alert to user if VPN is detected
53+
if (eventType === 'join') {
54+
alert('⚠️ VPN/Proxy detected. Your session is being monitored for security.');
55+
}
4156
}
4257

4358
if (data.tracked.securityFlags > 0) {
@@ -168,19 +183,126 @@
168183
// Get tracking summary for a session
169184
async getSessionTracking(sessionCode) {
170185
try {
171-
// This would fetch from Firebase or your database
172-
// For now, return mock data
186+
if (!window.firebase) {
187+
return null;
188+
}
189+
190+
// Fetch tracking data from Firebase
191+
const trackingRef = firebase.database().ref(`sessions/${sessionCode}/tracking`);
192+
const snapshot = await trackingRef.once('value');
193+
const trackingData = snapshot.val() || {};
194+
195+
// Process tracking entries
196+
const entries = Object.values(trackingData);
197+
const participants = new Map();
198+
const securityAlerts = [];
199+
let vpnCount = 0;
200+
201+
entries.forEach(entry => {
202+
// Group by user
203+
if (!participants.has(entry.userId)) {
204+
participants.set(entry.userId, {
205+
userName: entry.userName,
206+
userId: entry.userId,
207+
type: entry.metadata?.userType || 'unknown',
208+
location: entry.location ? `${entry.location.city}, ${entry.location.country}` : 'Unknown',
209+
device: entry.device.browser + ' on ' + entry.device.os,
210+
ipHash: entry.ip,
211+
vpn: entry.vpn.isVPN,
212+
flags: entry.securityFlags || [],
213+
joinTime: entry.timestamp,
214+
lastSeen: entry.timestamp
215+
});
216+
217+
if (entry.vpn.isVPN) {
218+
vpnCount++;
219+
}
220+
} else {
221+
// Update last seen
222+
const participant = participants.get(entry.userId);
223+
participant.lastSeen = Math.max(participant.lastSeen, entry.timestamp);
224+
}
225+
226+
// Collect security alerts
227+
if (entry.securityFlags && entry.securityFlags.length > 0) {
228+
entry.securityFlags.forEach(flag => {
229+
securityAlerts.push({
230+
...flag,
231+
userName: entry.userName,
232+
timestamp: entry.timestamp
233+
});
234+
});
235+
}
236+
});
237+
173238
return {
174-
totalParticipants: 0,
175-
vpnUsers: 0,
176-
locations: [],
177-
devices: [],
178-
securityFlags: []
239+
totalParticipants: participants.size,
240+
vpnUsers: vpnCount,
241+
participants: Array.from(participants.values()),
242+
securityAlerts: securityAlerts.sort((a, b) => b.timestamp - a.timestamp),
243+
rawData: entries
179244
};
180245
} catch (error) {
181246
console.error('Failed to get session tracking:', error);
182247
return null;
183248
}
249+
},
250+
251+
// Display tracking data in the security tab
252+
displaySecurityTab(sessionCode) {
253+
this.getSessionTracking(sessionCode).then(data => {
254+
if (!data) {
255+
document.getElementById('security-tracking-data').innerHTML =
256+
'<p class="loading-message">No tracking data available</p>';
257+
return;
258+
}
259+
260+
// Update summary
261+
document.getElementById('security-tracking-data').innerHTML = `
262+
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;">
263+
<div style="background: rgba(0,255,0,0.1); padding: 10px; border-radius: 5px;">
264+
<strong>Total Participants:</strong> ${data.totalParticipants}
265+
</div>
266+
<div style="background: ${data.vpnUsers > 0 ? 'rgba(255,0,0,0.1)' : 'rgba(0,255,0,0.1)'}; padding: 10px; border-radius: 5px;">
267+
<strong>VPN Users:</strong> ${data.vpnUsers}
268+
</div>
269+
<div style="background: ${data.securityAlerts.length > 0 ? 'rgba(255,255,0,0.1)' : 'rgba(0,255,0,0.1)'}; padding: 10px; border-radius: 5px;">
270+
<strong>Security Alerts:</strong> ${data.securityAlerts.length}
271+
</div>
272+
</div>
273+
`;
274+
275+
// Update participants table
276+
const tbody = document.getElementById('participant-activity-body');
277+
tbody.innerHTML = data.participants.map(p => `
278+
<tr>
279+
<td>${p.userName}</td>
280+
<td>${p.type}</td>
281+
<td>${p.location}</td>
282+
<td>${p.device}</td>
283+
<td style="font-family: monospace; font-size: 10px;">${p.ipHash.substring(0, 8)}...</td>
284+
<td>
285+
${p.vpn ? '<span class="security-flag vpn">VPN</span>' : ''}
286+
${p.flags.length > 0 ? p.flags.map(f =>
287+
`<span class="security-flag ${f.severity}">${f.type.replace(/_/g, ' ')}</span>`
288+
).join('') : '<span style="color: #00ff00;">✓ Clean</span>'}
289+
</td>
290+
</tr>
291+
`).join('');
292+
293+
// Update security alerts
294+
const alertsList = document.getElementById('security-alerts-list');
295+
if (data.securityAlerts.length === 0) {
296+
alertsList.innerHTML = '<li class="info">No security alerts detected</li>';
297+
} else {
298+
alertsList.innerHTML = data.securityAlerts.map(alert => `
299+
<li class="${alert.severity}">
300+
<strong>${alert.userName}:</strong> ${alert.detail}
301+
<br><small>${new Date(alert.timestamp).toLocaleString()}</small>
302+
</li>
303+
`).join('');
304+
}
305+
});
184306
}
185307
};
186308

styles/main.css

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2995,3 +2995,103 @@ a.powered-by-firepad {
29952995
font-size: 8px;
29962996
}
29972997
}
2998+
2999+
/* Security Tab Styles */
3000+
.security-info-display {
3001+
padding: 20px;
3002+
color: #fff;
3003+
}
3004+
3005+
.security-info-display h4 {
3006+
margin: 20px 0 10px;
3007+
color: #00ff00;
3008+
border-bottom: 1px solid #444;
3009+
padding-bottom: 5px;
3010+
}
3011+
3012+
.security-table {
3013+
width: 100%;
3014+
border-collapse: collapse;
3015+
margin: 10px 0;
3016+
}
3017+
3018+
.security-table th {
3019+
background: rgba(0, 255, 0, 0.1);
3020+
color: #00ff00;
3021+
padding: 10px;
3022+
text-align: left;
3023+
border: 1px solid #444;
3024+
font-size: 12px;
3025+
}
3026+
3027+
.security-table td {
3028+
padding: 8px;
3029+
border: 1px solid #444;
3030+
color: #fff;
3031+
font-size: 11px;
3032+
}
3033+
3034+
.security-table tbody tr:hover {
3035+
background: rgba(0, 255, 0, 0.05);
3036+
}
3037+
3038+
#security-alerts-list {
3039+
list-style: none;
3040+
padding: 0;
3041+
}
3042+
3043+
#security-alerts-list li {
3044+
background: rgba(255, 0, 0, 0.1);
3045+
border-left: 3px solid #ff0000;
3046+
padding: 10px;
3047+
margin: 5px 0;
3048+
color: #ffaaaa;
3049+
}
3050+
3051+
#security-alerts-list li.warning {
3052+
background: rgba(255, 255, 0, 0.1);
3053+
border-left-color: #ffff00;
3054+
color: #ffffaa;
3055+
}
3056+
3057+
#security-alerts-list li.info {
3058+
background: rgba(0, 100, 255, 0.1);
3059+
border-left-color: #0064ff;
3060+
color: #aaccff;
3061+
}
3062+
3063+
.loading-message {
3064+
color: #888;
3065+
font-style: italic;
3066+
text-align: center;
3067+
padding: 20px;
3068+
}
3069+
3070+
.security-flag {
3071+
display: inline-block;
3072+
padding: 2px 6px;
3073+
margin: 0 2px;
3074+
border-radius: 3px;
3075+
font-size: 10px;
3076+
font-weight: bold;
3077+
}
3078+
3079+
.security-flag.vpn {
3080+
background: #ff4444;
3081+
color: white;
3082+
}
3083+
3084+
.security-flag.proxy {
3085+
background: #ff8800;
3086+
color: white;
3087+
}
3088+
3089+
.security-flag.datacenter {
3090+
background: #ffaa00;
3091+
color: black;
3092+
}
3093+
3094+
.security-flag.suspicious {
3095+
background: #ff0000;
3096+
color: white;
3097+
}

0 commit comments

Comments
 (0)