unknownfriend00007 commited on
Commit
21efc4c
·
verified ·
1 Parent(s): 3b3a317

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +66 -19
server.js CHANGED
@@ -7,7 +7,6 @@ require('dotenv').config();
7
 
8
  const app = express();
9
 
10
- // --- SECURITY HEADERS ---
11
  app.use(helmet({
12
  contentSecurityPolicy: false,
13
  crossOriginEmbedderPolicy: false
@@ -16,14 +15,48 @@ app.use(helmet({
16
  app.set('trust proxy', 1);
17
  app.use(express.json({ limit: '1mb' }));
18
 
19
- // --- CORS PROTECTION ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  const allowedOrigins = process.env.ALLOWED_ORIGINS
21
  ? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim())
22
  : [];
23
 
24
  app.use(cors({
25
  origin: function (origin, callback) {
26
- if (!origin || allowedOrigins.length === 0) return callback(null, true);
 
 
 
27
  if (allowedOrigins.includes(origin)) {
28
  callback(null, true);
29
  } else {
@@ -41,7 +74,27 @@ app.use((err, req, res, next) => {
41
  next(err);
42
  });
43
 
44
- // --- RATE LIMITING (15 minutes) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  const limiter = rateLimit({
46
  windowMs: 15 * 60 * 1000,
47
  max: 100,
@@ -56,8 +109,9 @@ app.use(limiter);
56
  // --- REQUEST LOGGING ---
57
  app.use((req, res, next) => {
58
  const ip = (req.ip || 'unknown').replace(/:\d+[^:]*$/, '');
59
- const origin = (req.headers.origin || 'unknown').substring(0, 100);
60
- console.log(`[${new Date().toISOString()}] ${ip} -> ${req.method} ${req.path} from ${origin}`);
 
61
  next();
62
  });
63
 
@@ -92,7 +146,6 @@ app.use((req, res, next) => {
92
 
93
  dailyUsage.set(ip, count + 1);
94
 
95
- // FIXED: Prevent unbounded growth
96
  if (dailyUsage.size > 10000) {
97
  console.warn('[System] Daily usage map too large, clearing oldest entries');
98
  const entries = Array.from(dailyUsage.entries()).slice(0, 1000);
@@ -104,7 +157,6 @@ app.use((req, res, next) => {
104
 
105
  // --- BOT DETECTION ---
106
  app.use((req, res, next) => {
107
- // FIXED: Check all POST requests, not just /prediction/
108
  if (req.method !== 'POST') {
109
  return next();
110
  }
@@ -112,6 +164,11 @@ app.use((req, res, next) => {
112
  const userAgent = (req.headers['user-agent'] || '').toLowerCase();
113
  const suspiciousBots = ['python-requests', 'curl/', 'wget/', 'scrapy', 'crawler'];
114
 
 
 
 
 
 
115
  const isBot = suspiciousBots.some(bot => userAgent.includes(bot));
116
 
117
  if (isBot) {
@@ -142,7 +199,6 @@ const flowCache = new Map();
142
  setInterval(() => {
143
  const now = Date.now();
144
  for (const [key, value] of flowCache.entries()) {
145
- // FIXED: Safety check for timestamp
146
  if (value.timestamp && now - value.timestamp > 10 * 60 * 1000) {
147
  flowCache.delete(key);
148
  }
@@ -232,7 +288,6 @@ async function handleStreamingResponse(flowiseResponse, clientRes) {
232
  flowiseResponse.body.on('error', (err) => {
233
  console.error('[Streaming Error]', err.message);
234
 
235
- // FIXED: Send error event if stream already started
236
  if (streamStarted) {
237
  clientRes.write(`\n\nevent: error\ndata: {"error": "Stream interrupted"}\n\n`);
238
  }
@@ -240,7 +295,7 @@ async function handleStreamingResponse(flowiseResponse, clientRes) {
240
  });
241
  }
242
 
243
- // --- ROUTE 1: PREDICTION (WITH STREAMING SUPPORT) ---
244
  app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
245
  try {
246
  const instanceNum = parseInt(req.params.instanceNum);
@@ -288,13 +343,11 @@ app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
288
 
289
  const contentType = response.headers.get('content-type') || '';
290
 
291
- // STREAMING RESPONSE
292
  if (contentType.includes('text/event-stream')) {
293
  console.log('[Streaming] Detected SSE response');
294
  return handleStreamingResponse(response, res);
295
  }
296
 
297
- // NON-STREAMING RESPONSE
298
  console.log('[Non-streaming] Parsing JSON response');
299
  const text = await response.text();
300
 
@@ -315,7 +368,6 @@ app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
315
  }
316
  });
317
 
318
- // --- ROUTE 2: CHATBOT CONFIG ---
319
  app.get('/api/v1/public-chatbotConfig/:instanceNum/:botName', async (req, res) => {
320
  try {
321
  const instanceNum = parseInt(req.params.instanceNum);
@@ -347,7 +399,6 @@ app.get('/api/v1/public-chatbotConfig/:instanceNum/:botName', async (req, res) =
347
  }
348
  });
349
 
350
- // --- ROUTE 3: STREAMING CHECK ---
351
  app.get('/api/v1/chatflows-streaming/:instanceNum/:botName', async (req, res) => {
352
  try {
353
  const instanceNum = parseInt(req.params.instanceNum);
@@ -379,7 +430,6 @@ app.get('/api/v1/chatflows-streaming/:instanceNum/:botName', async (req, res) =>
379
  }
380
  });
381
 
382
- // --- HEALTH CHECK ---
383
  app.get('/', (req, res) => res.send('Federated Proxy Active'));
384
 
385
  app.get('/health', (req, res) => {
@@ -392,18 +442,15 @@ app.get('/health', (req, res) => {
392
  });
393
  });
394
 
395
- // --- 404 HANDLER ---
396
  app.use((req, res) => {
397
  res.status(404).json({ error: 'Route not found' });
398
  });
399
 
400
- // --- GLOBAL ERROR HANDLER ---
401
  app.use((err, req, res, next) => {
402
  console.error('[Error] Unhandled error:', err);
403
  res.status(500).json({ error: 'Internal server error' });
404
  });
405
 
406
- // --- GRACEFUL SHUTDOWN ---
407
  const server = app.listen(7860, '0.0.0.0', () => {
408
  console.log('Federated Proxy running on port 7860');
409
  });
 
7
 
8
  const app = express();
9
 
 
10
  app.use(helmet({
11
  contentSecurityPolicy: false,
12
  crossOriginEmbedderPolicy: false
 
15
  app.set('trust proxy', 1);
16
  app.use(express.json({ limit: '1mb' }));
17
 
18
+ // --- API KEY AUTHENTICATION ---
19
+ const API_KEYS = process.env.API_KEYS
20
+ ? process.env.API_KEYS.split(',').map(k => k.trim())
21
+ : [];
22
+
23
+ function authenticateRequest(req) {
24
+ const origin = req.headers.origin;
25
+ const apiKey = req.headers['x-api-key'];
26
+
27
+ // CASE 1: Browser request (has Origin)
28
+ if (origin) {
29
+ if (allowedOrigins.length === 0) return { valid: true, source: 'open-mode' };
30
+ return {
31
+ valid: allowedOrigins.includes(origin),
32
+ source: origin
33
+ };
34
+ }
35
+
36
+ // CASE 2: Backend request (no Origin) - MUST have API key
37
+ if (apiKey) {
38
+ if (API_KEYS.length === 0) return { valid: true, source: 'no-keys-configured' };
39
+ return {
40
+ valid: API_KEYS.includes(apiKey),
41
+ source: 'api-key'
42
+ };
43
+ }
44
+
45
+ // CASE 3: No Origin, No API Key - BLOCKED
46
+ return { valid: false, source: 'unauthorized' };
47
+ }
48
+
49
+ // --- CORS (For browsers) ---
50
  const allowedOrigins = process.env.ALLOWED_ORIGINS
51
  ? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim())
52
  : [];
53
 
54
  app.use(cors({
55
  origin: function (origin, callback) {
56
+ if (!origin) {
57
+ return callback(null, true);
58
+ }
59
+ if (allowedOrigins.length === 0) return callback(null, true);
60
  if (allowedOrigins.includes(origin)) {
61
  callback(null, true);
62
  } else {
 
74
  next(err);
75
  });
76
 
77
+ // --- API KEY AUTHENTICATION MIDDLEWARE ---
78
+ app.use((req, res, next) => {
79
+ if (req.path === '/' || req.path === '/health') {
80
+ return next();
81
+ }
82
+
83
+ const auth = authenticateRequest(req);
84
+
85
+ if (!auth.valid) {
86
+ console.log(`[Security] Blocked unauthorized request from ${req.ip} (${auth.source})`);
87
+ return res.status(401).json({
88
+ error: 'Unauthorized',
89
+ message: 'Valid origin or API key required'
90
+ });
91
+ }
92
+
93
+ console.log(`[Auth] Request authorized from: ${auth.source}`);
94
+ next();
95
+ });
96
+
97
+ // --- RATE LIMITING ---
98
  const limiter = rateLimit({
99
  windowMs: 15 * 60 * 1000,
100
  max: 100,
 
109
  // --- REQUEST LOGGING ---
110
  app.use((req, res, next) => {
111
  const ip = (req.ip || 'unknown').replace(/:\d+[^:]*$/, '');
112
+ const origin = req.headers.origin || 'no-origin';
113
+ const apiKey = req.headers['x-api-key'] ? '***' : 'none';
114
+ console.log(`[${new Date().toISOString()}] ${ip} -> ${req.method} ${req.path} | Origin: ${origin} | Key: ${apiKey}`);
115
  next();
116
  });
117
 
 
146
 
147
  dailyUsage.set(ip, count + 1);
148
 
 
149
  if (dailyUsage.size > 10000) {
150
  console.warn('[System] Daily usage map too large, clearing oldest entries');
151
  const entries = Array.from(dailyUsage.entries()).slice(0, 1000);
 
157
 
158
  // --- BOT DETECTION ---
159
  app.use((req, res, next) => {
 
160
  if (req.method !== 'POST') {
161
  return next();
162
  }
 
164
  const userAgent = (req.headers['user-agent'] || '').toLowerCase();
165
  const suspiciousBots = ['python-requests', 'curl/', 'wget/', 'scrapy', 'crawler'];
166
 
167
+ const hasValidApiKey = req.headers['x-api-key'] && API_KEYS.includes(req.headers['x-api-key']);
168
+ if (hasValidApiKey) {
169
+ return next();
170
+ }
171
+
172
  const isBot = suspiciousBots.some(bot => userAgent.includes(bot));
173
 
174
  if (isBot) {
 
199
  setInterval(() => {
200
  const now = Date.now();
201
  for (const [key, value] of flowCache.entries()) {
 
202
  if (value.timestamp && now - value.timestamp > 10 * 60 * 1000) {
203
  flowCache.delete(key);
204
  }
 
288
  flowiseResponse.body.on('error', (err) => {
289
  console.error('[Streaming Error]', err.message);
290
 
 
291
  if (streamStarted) {
292
  clientRes.write(`\n\nevent: error\ndata: {"error": "Stream interrupted"}\n\n`);
293
  }
 
295
  });
296
  }
297
 
298
+ // --- ROUTES ---
299
  app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
300
  try {
301
  const instanceNum = parseInt(req.params.instanceNum);
 
343
 
344
  const contentType = response.headers.get('content-type') || '';
345
 
 
346
  if (contentType.includes('text/event-stream')) {
347
  console.log('[Streaming] Detected SSE response');
348
  return handleStreamingResponse(response, res);
349
  }
350
 
 
351
  console.log('[Non-streaming] Parsing JSON response');
352
  const text = await response.text();
353
 
 
368
  }
369
  });
370
 
 
371
  app.get('/api/v1/public-chatbotConfig/:instanceNum/:botName', async (req, res) => {
372
  try {
373
  const instanceNum = parseInt(req.params.instanceNum);
 
399
  }
400
  });
401
 
 
402
  app.get('/api/v1/chatflows-streaming/:instanceNum/:botName', async (req, res) => {
403
  try {
404
  const instanceNum = parseInt(req.params.instanceNum);
 
430
  }
431
  });
432
 
 
433
  app.get('/', (req, res) => res.send('Federated Proxy Active'));
434
 
435
  app.get('/health', (req, res) => {
 
442
  });
443
  });
444
 
 
445
  app.use((req, res) => {
446
  res.status(404).json({ error: 'Route not found' });
447
  });
448
 
 
449
  app.use((err, req, res, next) => {
450
  console.error('[Error] Unhandled error:', err);
451
  res.status(500).json({ error: 'Internal server error' });
452
  });
453
 
 
454
  const server = app.listen(7860, '0.0.0.0', () => {
455
  console.log('Federated Proxy running on port 7860');
456
  });