feat: Implement service-to-service authentication, centralize environment configuration, and harden Docker security.

This commit is contained in:
2026-01-29 19:41:59 -03:00
parent 5d85dc0714
commit 5075a2440d
6 changed files with 169 additions and 12 deletions

View File

@@ -8,7 +8,17 @@ const { Server } = require('socket.io');
const app = express();
const prisma = new PrismaClient();
app.use(cors());
// SECURITY: Configure CORS with specific origins
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS
? process.env.ALLOWED_ORIGINS.split(',')
: ['http://localhost:5173', 'http://127.0.0.1:5173'];
app.use(cors({
origin: ALLOWED_ORIGINS,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Service-Key']
}));
app.use(express.json());
// Rate limiting simple para /api/detect (G)
@@ -44,8 +54,9 @@ setInterval(() => {
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
origin: ALLOWED_ORIGINS,
methods: ["GET", "POST"],
credentials: true
}
});
@@ -270,8 +281,26 @@ app.post('/api/people/bulk-approve', authenticateToken, isAdmin, async (req, res
}
});
// Detection Endpoint (from Python) with Rate Limiting (G)
app.post('/api/detect', async (req, res) => {
// Detection Endpoint (from Python) with Rate Limiting and Service Auth
// SECURITY: Requires X-Service-Key header for service-to-service auth
const validateServiceKey = (req, res, next) => {
const serviceKey = process.env.SERVICE_API_KEY;
// If no key configured, allow (development mode) but warn
if (!serviceKey) {
console.warn('⚠️ SERVICE_API_KEY not configured - /api/detect is unprotected!');
return next();
}
const providedKey = req.headers['x-service-key'];
if (providedKey !== serviceKey) {
console.warn(`🔒 Rejected /api/detect request - invalid service key from ${req.ip}`);
return res.status(401).json({ error: 'Invalid service key' });
}
next();
};
app.post('/api/detect', validateServiceKey, async (req, res) => {
const clientIp = req.ip || req.connection.remoteAddress;
// Check rate limit
@@ -368,7 +397,18 @@ server.listen(PORT, async () => {
const userCount = await prisma.user.count();
if (userCount === 0) {
console.log('No users found. Creating default admin user...');
const hashedPassword = await bcrypt.hash('admin123', 10);
// SECURITY: Use env var or generate random password
let adminPassword = process.env.ADMIN_PASSWORD;
let isGenerated = false;
if (!adminPassword) {
// Generate a secure random password
adminPassword = require('crypto').randomBytes(12).toString('base64url');
isGenerated = true;
}
const hashedPassword = await bcrypt.hash(adminPassword, 10);
await prisma.user.create({
data: {
username: 'admin',
@@ -376,7 +416,18 @@ server.listen(PORT, async () => {
role: 'ADMIN'
}
});
console.log('Default admin created: admin / admin123');
console.log('═'.repeat(50));
console.log('🔐 ADMIN USER CREATED');
console.log(' Username: admin');
if (isGenerated) {
console.log(` Password: ${adminPassword}`);
console.log(' ⚠️ SAVE THIS PASSWORD - it won\'t be shown again!');
console.log(' 💡 Set ADMIN_PASSWORD env var to use a custom password');
} else {
console.log(' Password: [from ADMIN_PASSWORD env var]');
}
console.log('═'.repeat(50));
}
} catch (err) {
console.error('Error seeding admin user:', err);

View File

@@ -1,6 +1,12 @@
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-this';
// SECURITY: JWT_SECRET must be configured via environment variable
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) {
console.error('❌ FATAL: JWT_SECRET environment variable is required');
console.error(' Generate one with: node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"');
process.exit(1);
}
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];