feat: role-aware presence bar, WebSocket logging fixes
- findAdminByKey returns role from admins.json (defaults to 'admin') - JWT includes config-defined role instead of hardcoded 'admin' - PresenceBar split into "who's here?" (page admins) and "connected" (bot/utility services with icon+color badges) - Bot/utility roles appear in presence on all pages when connected - usePresence hook uses refs to avoid WS reconnect on navigation - WS auth log prints admin name instead of generic 'admin' - WS connection log reads X-Forwarded-For for real client IP - AuthContext stores adminRole from login response - Uncomment admins.json Docker volume mount, add SELinux :z flags Made-with: Cursor
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
[
|
||||
{ "name": "Alice", "key": "change-me-alice-key" },
|
||||
{ "name": "Bob", "key": "change-me-bob-key" }
|
||||
{ "name": "Alice", "role": "admin", "key": "change-me-alice-key" },
|
||||
{ "name": "Bob", "role": "bot", "key": "change-me-bob-key" },
|
||||
{ "name": "Charlie", "role": "utility", "key": "change-me-charlie-key" }
|
||||
]
|
||||
|
||||
@@ -62,7 +62,7 @@ const admins = loadAdmins();
|
||||
|
||||
function findAdminByKey(key) {
|
||||
const match = admins.find(a => a.key === key);
|
||||
return match ? { name: match.name } : null;
|
||||
return match ? { name: match.name, role: match.role || 'admin' } : null;
|
||||
}
|
||||
|
||||
module.exports = { findAdminByKey, admins };
|
||||
|
||||
@@ -18,7 +18,7 @@ router.post('/login', (req, res) => {
|
||||
}
|
||||
|
||||
const token = jwt.sign(
|
||||
{ role: 'admin', name: admin.name, timestamp: Date.now() },
|
||||
{ role: admin.role, name: admin.name, timestamp: Date.now() },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
@@ -26,6 +26,7 @@ router.post('/login', (req, res) => {
|
||||
res.json({
|
||||
token,
|
||||
name: admin.name,
|
||||
role: admin.role,
|
||||
message: 'Authentication successful',
|
||||
expiresIn: '24h'
|
||||
});
|
||||
|
||||
@@ -26,13 +26,17 @@ class WebSocketManager {
|
||||
* Handle new WebSocket connection
|
||||
*/
|
||||
handleConnection(ws, req) {
|
||||
console.log('[WebSocket] New connection from', req.socket.remoteAddress);
|
||||
const clientIp = req.headers['x-forwarded-for']?.split(',')[0]?.trim()
|
||||
|| req.headers['x-real-ip']
|
||||
|| req.socket.remoteAddress;
|
||||
console.log('[WebSocket] New connection from', clientIp);
|
||||
|
||||
// Initialize client info
|
||||
const clientInfo = {
|
||||
authenticated: false,
|
||||
userId: null,
|
||||
adminName: null,
|
||||
role: null,
|
||||
currentPage: null,
|
||||
subscribedSessions: new Set(),
|
||||
lastPing: Date.now()
|
||||
@@ -130,6 +134,7 @@ class WebSocketManager {
|
||||
clientInfo.authenticated = true;
|
||||
clientInfo.userId = decoded.role;
|
||||
clientInfo.adminName = decoded.name || null;
|
||||
clientInfo.role = decoded.role || 'admin';
|
||||
|
||||
if (!decoded.name) {
|
||||
this.sendError(ws, 'Token missing admin identity, please re-login', 'auth_error');
|
||||
@@ -141,7 +146,8 @@ class WebSocketManager {
|
||||
message: 'Authenticated successfully'
|
||||
});
|
||||
|
||||
console.log('[WebSocket] Client authenticated:', clientInfo.userId);
|
||||
console.log('[WebSocket] Client authenticated:', clientInfo.adminName);
|
||||
this.broadcastPresence();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[WebSocket] Authentication failed:', err.message);
|
||||
@@ -299,7 +305,7 @@ class WebSocketManager {
|
||||
});
|
||||
|
||||
this.clients.delete(ws);
|
||||
console.log('[WebSocket] Client disconnected and cleaned up');
|
||||
console.log('[WebSocket] Client disconnected:', clientInfo.adminName || 'unauthenticated');
|
||||
this.broadcastPresence();
|
||||
}
|
||||
}
|
||||
@@ -307,8 +313,12 @@ class WebSocketManager {
|
||||
broadcastPresence() {
|
||||
const admins = [];
|
||||
this.clients.forEach((info) => {
|
||||
if (info.authenticated && info.adminName && info.currentPage) {
|
||||
admins.push({ name: info.adminName, page: info.currentPage });
|
||||
if (!info.authenticated || !info.adminName) return;
|
||||
const role = info.role || 'admin';
|
||||
if (role === 'bot' || role === 'utility') {
|
||||
admins.push({ name: info.adminName, role, page: null });
|
||||
} else if (info.currentPage) {
|
||||
admins.push({ name: info.adminName, role, page: info.currentPage });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user