Fix critical security vulnerabilities and array access bugs
This commit addresses multiple high-priority security and reliability issues
found during comprehensive codebase analysis:
1. CRITICAL: Remove hardcoded TURN server credentials
- Removed dangerous default secret 'default-secret-change-me'
- Now throws error if TURN_SECRET env var not set
- Also requires TURN_SERVER_HOST and TURN_SERVER_PORT to be configured
- Prevents attackers from generating valid TURN credentials
- File: src/backend/services/callService.js
2. HIGH: Add array bounds checking before accessing database results
- Added validation before accessing rows[0] in multiple locations
- Prevents "Cannot read property of undefined" runtime crashes
- Provides clear error messages when records not found
- Files affected:
- src/backend/services/callService.js (conversation lookup)
- src/backend/services/messagingService.js (user lookup)
- src/backend/services/gameforgeIntegration.js (integration & conversation creation)
3. HIGH: Secure development mode authentication bypass
- Added second security layer requiring ALLOW_DEV_BYPASS='true'
- Prevents accidental auth bypass if NODE_ENV accidentally set to 'development'
- Added warning logs when bypass is active
- File: src/backend/middleware/auth.js
These fixes prevent:
- TURN server abuse via known secrets
- Runtime crashes from missing database records
- Accidental authentication bypass in production environments
This commit is contained in:
parent
41b03b88d5
commit
13d926a9c5
4 changed files with 44 additions and 12 deletions
|
|
@ -7,21 +7,28 @@ const jwt = require('jsonwebtoken');
|
|||
function authenticateUser(req, res, next) {
|
||||
try {
|
||||
// In development mode, allow requests without auth for testing
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// SECURITY: This bypass only works if BOTH conditions are met:
|
||||
// 1. NODE_ENV is 'development'
|
||||
// 2. ALLOW_DEV_BYPASS is explicitly set to 'true'
|
||||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||||
const allowDevBypass = process.env.ALLOW_DEV_BYPASS === 'true';
|
||||
|
||||
if (isDevelopment && allowDevBypass) {
|
||||
// Check for token, but if not present, use demo user
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
// Use demo user for development
|
||||
console.warn('⚠️ Development mode: Using demo user without authentication');
|
||||
req.user = {
|
||||
id: 'demo-user-123',
|
||||
email: 'demo@aethex.dev'
|
||||
};
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
|
||||
|
||||
// Try to verify, but don't fail if invalid
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
|
|
@ -31,15 +38,16 @@ function authenticateUser(req, res, next) {
|
|||
};
|
||||
} catch {
|
||||
// Use demo user if token invalid
|
||||
console.warn('⚠️ Development mode: Invalid token, using demo user');
|
||||
req.user = {
|
||||
id: 'demo-user-123',
|
||||
email: 'demo@aethex.dev'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
// Production: Strict auth required
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ class CallService {
|
|||
[conversationId]
|
||||
);
|
||||
|
||||
if (conversationResult.rows.length === 0) {
|
||||
throw new Error(`Conversation ${conversationId} not found`);
|
||||
}
|
||||
|
||||
const conversation = conversationResult.rows[0];
|
||||
|
||||
// Determine if group call
|
||||
|
|
@ -249,9 +253,12 @@ class CallService {
|
|||
async generateTURNCredentials(userId) {
|
||||
const timestamp = Math.floor(Date.now() / 1000) + 86400; // 24 hour TTL
|
||||
const username = `${timestamp}:${userId}`;
|
||||
|
||||
|
||||
// Generate credential using HMAC
|
||||
const turnSecret = process.env.TURN_SECRET || 'default-secret-change-me';
|
||||
const turnSecret = process.env.TURN_SECRET;
|
||||
if (!turnSecret) {
|
||||
throw new Error('TURN_SECRET environment variable must be set for TURN server authentication');
|
||||
}
|
||||
const hmac = crypto.createHmac('sha1', turnSecret);
|
||||
hmac.update(username);
|
||||
const credential = hmac.digest('base64');
|
||||
|
|
@ -265,8 +272,12 @@ class CallService {
|
|||
[userId, username, credential, timestamp]
|
||||
);
|
||||
|
||||
const turnHost = process.env.TURN_SERVER_HOST || 'turn.aethex.app';
|
||||
const turnPort = process.env.TURN_SERVER_PORT || '3478';
|
||||
const turnHost = process.env.TURN_SERVER_HOST;
|
||||
const turnPort = process.env.TURN_SERVER_PORT;
|
||||
|
||||
if (!turnHost || !turnPort) {
|
||||
throw new Error('TURN_SERVER_HOST and TURN_SERVER_PORT environment variables must be set');
|
||||
}
|
||||
|
||||
return {
|
||||
urls: [
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ class GameForgeIntegrationService {
|
|||
]
|
||||
);
|
||||
|
||||
if (integrationResult.rows.length === 0) {
|
||||
throw new Error('Failed to create GameForge integration');
|
||||
}
|
||||
|
||||
const integration = integrationResult.rows[0];
|
||||
|
||||
// Create channels
|
||||
|
|
@ -88,13 +92,17 @@ class GameForgeIntegrationService {
|
|||
const description = this.getChannelDescription(channelName);
|
||||
|
||||
const conversationResult = await db.query(
|
||||
`INSERT INTO conversations
|
||||
`INSERT INTO conversations
|
||||
(type, title, description, created_by, gameforge_project_id)
|
||||
VALUES ('group', $1, $2, $3, $4)
|
||||
RETURNING *`,
|
||||
[title, description, ownerId, projectId]
|
||||
);
|
||||
|
||||
if (conversationResult.rows.length === 0) {
|
||||
throw new Error('Failed to create conversation for GameForge channel');
|
||||
}
|
||||
|
||||
const conversation = conversationResult.rows[0];
|
||||
|
||||
// Add team members based on permissions
|
||||
|
|
|
|||
|
|
@ -287,8 +287,13 @@ class MessagingService {
|
|||
`SELECT username, verified_domain, avatar_url FROM users WHERE id = $1`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
throw new Error(`User ${userId} not found`);
|
||||
}
|
||||
|
||||
const user = userResult.rows[0];
|
||||
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
conversationId: message.conversation_id,
|
||||
|
|
|
|||
Loading…
Reference in a new issue