diff --git a/web/server/server.js b/web/server/server.js index 98d345d..8388b13 100644 --- a/web/server/server.js +++ b/web/server/server.js @@ -21,6 +21,33 @@ const HOST = process.env.HOST || '0.0.0.0'; app.use(cors()); app.use(express.json({ limit: '5mb' })); +// ========== API Key Authentication ========== + +const API_KEY = process.env.API_KEY || ''; + +// Validate bearer token from Authorization header or ?key= query param +function extractApiKey(req) { + const auth = req.headers.authorization || ''; + if (auth.startsWith('Bearer ')) return auth.slice(7); + return req.query.key || ''; +} + +// Middleware: require API key on mutating endpoints +function requireAuth(req, res, next) { + if (!API_KEY) return next(); // Auth disabled when no key configured + const token = extractApiKey(req); + if (token === API_KEY) return next(); + return res.status(401).json({ error: 'Unauthorized — invalid or missing API key' }); +} + +// Apply auth to all POST/PUT/DELETE routes +app.use((req, res, next) => { + if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') { + return next(); // Read-only endpoints stay open + } + return requireAuth(req, res, next); +}); + // ========== State ========== const webClients = new Set(); const bridgeClients = new Set(); @@ -659,11 +686,33 @@ function updateStateFromBridge(data) { // ========== WebSocket Server ========== -const wss = new WebSocketServer({ server }); +const wss = new WebSocketServer({ noServer: true }); console.log(`🚀 Inventory Manager Web Server starting...`); console.log(`📡 HTTP Server: http://localhost:${PORT}`); console.log(`🔌 WebSocket Server: ws://localhost:${PORT}/ws`); +if (API_KEY) { + console.log('🔒 API key authentication enabled'); +} else { + console.log('⚠️ No API_KEY set \u2014 authentication disabled (open access)'); +} + +// Authenticate WebSocket upgrades +server.on('upgrade', (req, socket, head) => { + if (API_KEY) { + // Extract key from query string: /ws?key=... or /ws/bridge?key=... + const urlObj = new URL(req.url, `http://${req.headers.host}`); + const token = urlObj.searchParams.get('key') || ''; + if (token !== API_KEY) { + socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); + socket.destroy(); + return; + } + } + wss.handleUpgrade(req, socket, head, (ws) => { + wss.emit('connection', ws, req); + }); +}); wss.on('connection', (ws, req) => { const url = req.url || '';