unknownfriend00007 commited on
Commit
591c716
·
verified ·
1 Parent(s): 330d686

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +144 -65
server.js CHANGED
@@ -9,7 +9,7 @@ const app = express();
9
  // --- 1. SECURITY: TRUST PROXY ---
10
  app.set('trust proxy', 1);
11
 
12
- // --- 2. SECURITY: RATE LIMITING ---
13
  const limiter = rateLimit({
14
  windowMs: 15 * 60 * 1000,
15
  max: 100,
@@ -34,9 +34,71 @@ app.use(cors({
34
  }
35
  }));
36
 
 
 
 
 
 
 
 
 
 
 
 
37
  app.use(express.json());
38
 
39
- // --- 4. MULTI-INSTANCE CONFIGURATION ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  let INSTANCES = [];
41
  try {
42
  INSTANCES = JSON.parse(process.env.FLOWISE_INSTANCES || '[]');
@@ -44,92 +106,109 @@ try {
44
  console.error("CRITICAL ERROR: Could not parse FLOWISE_INSTANCES JSON", e);
45
  }
46
 
47
- let flowDirectory = {};
48
- let lastCacheUpdate = 0;
49
-
50
- // --- 5. DYNAMIC DISCOVERY ---
51
- async function refreshFlowDirectory() {
52
- if (Date.now() - lastCacheUpdate < 60000 && Object.keys(flowDirectory).length > 0) return;
53
 
54
- console.log(`[System] Scanning ${INSTANCES.length} Flowise Instances...`);
55
- const newDirectory = {};
56
 
57
- const promises = INSTANCES.map(async (inst) => {
58
- try {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  const headers = {};
60
- if (inst.key && inst.key.length > 0) {
61
- headers['Authorization'] = `Bearer ${inst.key}`;
62
  }
63
-
64
- const res = await fetch(`${inst.url}/api/v1/chatflows`, { headers });
65
 
66
- if(!res.ok) throw new Error(`Status ${res.status}`);
67
- const flows = await res.json();
68
 
69
- flows.forEach(flow => {
70
- const alias = flow.name.toLowerCase().replace(/\s+/g, '-');
71
- newDirectory[alias] = {
72
- id: flow.id,
73
- host: inst.url,
74
- key: inst.key
75
- };
76
- });
77
- } catch (err) {
78
- console.error(`[Error] Failed to fetch from ${inst.url}:`, err.message);
79
- }
80
- });
81
-
82
- await Promise.allSettled(promises);
83
-
84
- flowDirectory = newDirectory;
85
- lastCacheUpdate = Date.now();
86
- console.log(`[System] Directory Updated. Serving ${Object.keys(flowDirectory).length} bots.`);
87
- }
88
-
89
- // --- 6. THE ROUTE ---
90
- app.post('/api/v1/prediction/:botName', async (req, res) => {
91
- const botName = req.params.botName.toLowerCase();
92
-
93
- await refreshFlowDirectory();
94
-
95
- const target = flowDirectory[botName];
96
-
97
- if (!target) {
98
- lastCacheUpdate = 0;
99
- await refreshFlowDirectory();
100
- if (!flowDirectory[botName]) {
101
- return res.status(404).json({ error: `Bot '${botName}' not found.` });
102
  }
103
- }
104
-
105
- const finalTarget = flowDirectory[botName];
106
-
107
- try {
108
  const forwardHeaders = {
109
  'Content-Type': 'application/json',
110
  'HTTP-Referer': req.headers.origin || 'https://huggingface.co',
111
  'X-Title': 'FederatedProxy'
112
  };
113
-
114
- if (finalTarget.key && finalTarget.key.length > 0) {
115
- forwardHeaders['Authorization'] = `Bearer ${finalTarget.key}`;
116
  }
117
-
118
- const flowiseResponse = await fetch(`${finalTarget.host}/api/v1/prediction/${finalTarget.id}`, {
119
  method: 'POST',
120
  headers: forwardHeaders,
121
  body: JSON.stringify(req.body)
122
  });
123
-
124
  const data = await flowiseResponse.json();
125
  res.status(flowiseResponse.status).json(data);
126
-
127
  } catch (error) {
128
- console.error("Proxy Forwarding Error:", error);
129
- res.status(500).json({ error: 'Proxy forwarding failed.' });
 
 
 
130
  }
131
  });
132
 
133
  app.get('/', (req, res) => res.send('Federated Proxy Active'));
134
 
 
 
 
 
 
 
 
 
 
135
  app.listen(7860, '0.0.0.0', () => console.log('Federated Proxy running on port 7860'));
 
9
  // --- 1. SECURITY: TRUST PROXY ---
10
  app.set('trust proxy', 1);
11
 
12
+ // --- 2. SECURITY: RATE LIMITING (Per 15 Minutes) ---
13
  const limiter = rateLimit({
14
  windowMs: 15 * 60 * 1000,
15
  max: 100,
 
34
  }
35
  }));
36
 
37
+ // JSON error handler for CORS failures
38
+ app.use((err, req, res, next) => {
39
+ if (err.message === 'Not allowed by CORS') {
40
+ return res.status(403).json({
41
+ error: 'Access denied',
42
+ message: 'This chatbot can only be used on authorized websites.'
43
+ });
44
+ }
45
+ next(err);
46
+ });
47
+
48
  app.use(express.json());
49
 
50
+ // --- FEATURE 1: REQUEST LOGGING ---
51
+ app.use((req, res, next) => {
52
+ const ip = req.ip || req.connection.remoteAddress;
53
+ console.log(`[${new Date().toISOString()}] ${ip} -> ${req.method} ${req.path} from ${req.headers.origin || 'unknown'}`);
54
+ next();
55
+ });
56
+
57
+ // --- FEATURE 3: DAILY USAGE CAPS ---
58
+ const dailyUsage = new Map();
59
+
60
+ // Reset daily counts at midnight IST
61
+ setInterval(() => {
62
+ dailyUsage.clear();
63
+ console.log('[System] Daily usage counters reset');
64
+ }, 24 * 60 * 60 * 1000);
65
+
66
+ // Daily limit middleware
67
+ app.use((req, res, next) => {
68
+ if (req.method === 'POST' && req.path.includes('/prediction/')) {
69
+ const ip = req.ip;
70
+ const today = new Date().toDateString();
71
+ const key = `${ip}-${today}`;
72
+
73
+ const count = dailyUsage.get(key) || 0;
74
+ if (count >= 200) { // 200 requests per day per IP
75
+ return res.status(429).json({
76
+ error: 'Daily limit reached',
77
+ message: 'You have reached your daily usage limit. Try again tomorrow.'
78
+ });
79
+ }
80
+
81
+ dailyUsage.set(key, count + 1);
82
+ }
83
+ next();
84
+ });
85
+
86
+ // --- FEATURE 5: BOT DETECTION ---
87
+ app.use((req, res, next) => {
88
+ const userAgent = req.headers['user-agent'] || '';
89
+ const suspiciousBots = ['python-requests', 'curl', 'wget', 'scrapy', 'bot', 'crawler'];
90
+
91
+ if (req.path.includes('/prediction/') && suspiciousBots.some(bot => userAgent.toLowerCase().includes(bot))) {
92
+ console.log(`[Security] Blocked bot: ${userAgent} from ${req.ip}`);
93
+ return res.status(403).json({
94
+ error: 'Automated access detected',
95
+ message: 'This service is for web browsers only.'
96
+ });
97
+ }
98
+ next();
99
+ });
100
+
101
+ // --- INSTANCES CONFIGURATION ---
102
  let INSTANCES = [];
103
  try {
104
  INSTANCES = JSON.parse(process.env.FLOWISE_INSTANCES || '[]');
 
106
  console.error("CRITICAL ERROR: Could not parse FLOWISE_INSTANCES JSON", e);
107
  }
108
 
109
+ console.log(`[System] Loaded ${INSTANCES.length} instances`);
 
 
 
 
 
110
 
111
+ // Cache for chatflow lookups: "instanceNum-botName" -> chatflowId
112
+ let flowCache = new Map();
113
 
114
+ // --- INSTANCE-SPECIFIC ROUTE ---
115
+ app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
116
+ const instanceNum = parseInt(req.params.instanceNum);
117
+ const botName = req.params.botName.toLowerCase();
118
+
119
+ // FEATURE 2: MESSAGE LENGTH LIMIT
120
+ if (req.body.question && req.body.question.length > 2000) {
121
+ return res.status(400).json({
122
+ error: 'Message too long',
123
+ message: 'Please keep messages under 2000 characters.'
124
+ });
125
+ }
126
+
127
+ // Validate instance number
128
+ if (isNaN(instanceNum) || instanceNum < 1 || instanceNum > INSTANCES.length) {
129
+ return res.status(404).json({
130
+ error: 'Invalid instance',
131
+ message: `Instance ${instanceNum} does not exist. Valid instances: 1-${INSTANCES.length}`
132
+ });
133
+ }
134
+
135
+ const instance = INSTANCES[instanceNum - 1];
136
+ const cacheKey = `${instanceNum}-${botName}`;
137
+
138
+ try {
139
+ // Check cache first
140
+ let chatflowId = flowCache.get(cacheKey);
141
+
142
+ // If not cached, fetch from instance
143
+ if (!chatflowId) {
144
+ console.log(`[System] Looking up '${botName}' in instance ${instanceNum}...`);
145
+
146
  const headers = {};
147
+ if (instance.key && instance.key.length > 0) {
148
+ headers['Authorization'] = `Bearer ${instance.key}`;
149
  }
 
 
150
 
151
+ const listResponse = await fetch(`${instance.url}/api/v1/chatflows`, { headers });
 
152
 
153
+ if (!listResponse.ok) {
154
+ throw new Error(`Instance ${instanceNum} returned status ${listResponse.status}`);
155
+ }
156
+
157
+ const flows = await listResponse.json();
158
+ const matchedFlow = flows.find(f => f.name.toLowerCase().replace(/\s+/g, '-') === botName);
159
+
160
+ if (!matchedFlow) {
161
+ return res.status(404).json({
162
+ error: 'Bot not found',
163
+ message: `Bot '${botName}' not found in instance ${instanceNum}`
164
+ });
165
+ }
166
+
167
+ chatflowId = matchedFlow.id;
168
+ flowCache.set(cacheKey, chatflowId);
169
+ setTimeout(() => flowCache.delete(cacheKey), 5 * 60 * 1000);
170
+
171
+ console.log(`[System] Found '${botName}' -> ${chatflowId}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  }
173
+
174
+ // Forward request to Flowise
 
 
 
175
  const forwardHeaders = {
176
  'Content-Type': 'application/json',
177
  'HTTP-Referer': req.headers.origin || 'https://huggingface.co',
178
  'X-Title': 'FederatedProxy'
179
  };
180
+
181
+ if (instance.key && instance.key.length > 0) {
182
+ forwardHeaders['Authorization'] = `Bearer ${instance.key}`;
183
  }
184
+
185
+ const flowiseResponse = await fetch(`${instance.url}/api/v1/prediction/${chatflowId}`, {
186
  method: 'POST',
187
  headers: forwardHeaders,
188
  body: JSON.stringify(req.body)
189
  });
190
+
191
  const data = await flowiseResponse.json();
192
  res.status(flowiseResponse.status).json(data);
193
+
194
  } catch (error) {
195
+ console.error(`[Error] Instance ${instanceNum} request failed:`, error.message);
196
+ res.status(500).json({
197
+ error: 'Proxy forwarding failed',
198
+ message: error.message
199
+ });
200
  }
201
  });
202
 
203
  app.get('/', (req, res) => res.send('Federated Proxy Active'));
204
 
205
+ app.get('/health', (req, res) => {
206
+ res.json({
207
+ status: 'healthy',
208
+ instances: INSTANCES.length,
209
+ cached_bots: flowCache.size,
210
+ daily_active_ips: dailyUsage.size
211
+ });
212
+ });
213
+
214
  app.listen(7860, '0.0.0.0', () => console.log('Federated Proxy running on port 7860'));