unknownfriend00007 commited on
Commit
e9ec81c
·
verified ·
1 Parent(s): efc33bf

Upload 3 files

Browse files
Files changed (2) hide show
  1. package.json +1 -0
  2. server.js +42 -75
package.json CHANGED
@@ -12,6 +12,7 @@
12
  "express": "^4.21.2",
13
  "express-rate-limit": "^7.1.5",
14
  "helmet": "^7.1.0",
 
15
  "node-fetch": "^2.7.0"
16
  },
17
  "engines": {
 
12
  "express": "^4.21.2",
13
  "express-rate-limit": "^7.1.5",
14
  "helmet": "^7.1.0",
15
+ "jsonwebtoken": "^9.0.2",
16
  "node-fetch": "^2.7.0"
17
  },
18
  "engines": {
server.js CHANGED
@@ -4,6 +4,7 @@ const cors = require('cors');
4
  const rateLimit = require('express-rate-limit');
5
  const helmet = require('helmet');
6
  const crypto = require('crypto');
 
7
  require('dotenv').config();
8
 
9
  const app = express();
@@ -253,17 +254,6 @@ function hasValidApiKey(req) {
253
  return typeof apiKey === 'string' && API_KEYS.some((configuredKey) => timingSafeEquals(configuredKey, apiKey));
254
  }
255
 
256
- function base64UrlEncode(value) {
257
- const buffer = Buffer.isBuffer(value) ? value : Buffer.from(value);
258
- return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
259
- }
260
-
261
- function base64UrlDecode(value) {
262
- const normalized = value.replace(/-/g, '+').replace(/_/g, '/');
263
- const padLength = (4 - (normalized.length % 4)) % 4;
264
- return Buffer.from(normalized + '='.repeat(padLength), 'base64');
265
- }
266
-
267
  function normalizeBotName(rawBotName) {
268
  if (typeof rawBotName !== 'string') return '';
269
  return rawBotName.toLowerCase().replace(/\s+/g, '-').substring(0, 100);
@@ -289,60 +279,30 @@ function hashUserAgent(value) {
289
  return crypto.createHash('sha256').update(userAgent).digest('hex');
290
  }
291
 
292
- function createChatAccessToken(payload) {
293
- const header = { alg: 'HS256', typ: 'JWT' };
294
- const encodedHeader = base64UrlEncode(JSON.stringify(header));
295
- const encodedPayload = base64UrlEncode(JSON.stringify(payload));
296
- const unsigned = `${encodedHeader}.${encodedPayload}`;
297
- const signature = crypto.createHmac('sha256', CHAT_TOKEN_SECRET).update(unsigned).digest();
298
- return `${unsigned}.${base64UrlEncode(signature)}`;
299
- }
300
-
301
  function verifyChatAccessToken(token) {
302
  if (typeof token !== 'string' || token.length < 20) {
303
  return { valid: false, reason: 'missing-token' };
304
  }
305
 
306
- const parts = token.split('.');
307
- if (parts.length !== 3) {
308
- return { valid: false, reason: 'invalid-format' };
309
- }
310
-
311
- const [encodedHeader, encodedPayload, encodedSignature] = parts;
312
- let header;
313
  let payload;
314
 
315
  try {
316
- header = JSON.parse(base64UrlDecode(encodedHeader).toString('utf8'));
317
- payload = JSON.parse(base64UrlDecode(encodedPayload).toString('utf8'));
318
- } catch {
319
- return { valid: false, reason: 'invalid-encoding' };
320
- }
321
-
322
- if (!header || header.alg !== 'HS256' || header.typ !== 'JWT') {
323
- return { valid: false, reason: 'invalid-header' };
324
- }
325
-
326
- const unsigned = `${encodedHeader}.${encodedPayload}`;
327
- const expectedSignature = base64UrlEncode(
328
- crypto.createHmac('sha256', CHAT_TOKEN_SECRET).update(unsigned).digest()
329
- );
330
-
331
- if (!timingSafeEquals(expectedSignature, encodedSignature)) {
332
- return { valid: false, reason: 'invalid-signature' };
333
- }
334
-
335
- const nowSeconds = Math.floor(Date.now() / 1000);
336
- if (typeof payload.exp !== 'number' || payload.exp + CHAT_TOKEN_CLOCK_SKEW_SECONDS < nowSeconds) {
337
- return { valid: false, reason: 'expired' };
338
- }
339
-
340
- if (typeof payload.iat !== 'number' || payload.iat - CHAT_TOKEN_CLOCK_SKEW_SECONDS > nowSeconds) {
341
- return { valid: false, reason: 'invalid-issued-at' };
342
  }
343
 
344
- if (payload.iss !== CHAT_TOKEN_ISSUER) {
345
- return { valid: false, reason: 'invalid-issuer' };
346
  }
347
 
348
  if (!Number.isInteger(payload.instanceNum) || payload.instanceNum < 1) {
@@ -632,14 +592,21 @@ setInterval(() => {
632
  }, 10 * 60 * 1000);
633
 
634
  // --- FETCH WITH TIMEOUT ---
635
- async function fetchWithTimeout(url, options, timeout = 10000) {
636
- return Promise.race([
637
- fetch(url, options),
638
- new Promise((_, reject) =>
639
- setTimeout(() => reject(new Error('Request timeout')), timeout)
640
- )
641
- ]);
642
- }
 
 
 
 
 
 
 
643
 
644
  // --- RESOLVE CHATFLOW ID ---
645
  async function resolveChatflowId(instanceNum, botName) {
@@ -727,12 +694,12 @@ async function handleStreamingResponse(flowiseResponse, clientRes) {
727
  }
728
  }, 5000);
729
 
730
- flowiseResponse.body.on('data', (chunk) => {
731
- clearTimeout(timeoutCheck);
732
- streamStarted = true;
733
- dataReceived = true;
734
- lastDataTime = Date.now();
735
- totalBytes += chunk.length;
736
 
737
  logSensitive(`[Streaming] Received chunk: ${chunk.length} bytes (total: ${totalBytes})`);
738
  clientRes.write(chunk);
@@ -837,12 +804,7 @@ app.post('/auth/chat-token', tokenIssueLimiter, async (req, res) => {
837
  });
838
  }
839
 
840
- const nowSeconds = Math.floor(Date.now() / 1000);
841
  const claims = {
842
- iss: CHAT_TOKEN_ISSUER,
843
- iat: nowSeconds,
844
- exp: nowSeconds + CHAT_TOKEN_TTL_SECONDS,
845
- jti: crypto.randomBytes(12).toString('hex'),
846
  instanceNum: target.instanceNum,
847
  botName: target.botName
848
  };
@@ -859,7 +821,12 @@ app.post('/auth/chat-token', tokenIssueLimiter, async (req, res) => {
859
  claims.uaHash = hashUserAgent(req.headers['user-agent'] || '');
860
  }
861
 
862
- const token = createChatAccessToken(claims);
 
 
 
 
 
863
  res.status(200).json({
864
  token,
865
  tokenType: 'Bearer',
 
4
  const rateLimit = require('express-rate-limit');
5
  const helmet = require('helmet');
6
  const crypto = require('crypto');
7
+ const jwt = require('jsonwebtoken');
8
  require('dotenv').config();
9
 
10
  const app = express();
 
254
  return typeof apiKey === 'string' && API_KEYS.some((configuredKey) => timingSafeEquals(configuredKey, apiKey));
255
  }
256
 
 
 
 
 
 
 
 
 
 
 
 
257
  function normalizeBotName(rawBotName) {
258
  if (typeof rawBotName !== 'string') return '';
259
  return rawBotName.toLowerCase().replace(/\s+/g, '-').substring(0, 100);
 
279
  return crypto.createHash('sha256').update(userAgent).digest('hex');
280
  }
281
 
 
 
 
 
 
 
 
 
 
282
  function verifyChatAccessToken(token) {
283
  if (typeof token !== 'string' || token.length < 20) {
284
  return { valid: false, reason: 'missing-token' };
285
  }
286
 
 
 
 
 
 
 
 
287
  let payload;
288
 
289
  try {
290
+ payload = jwt.verify(token, CHAT_TOKEN_SECRET, {
291
+ algorithms: ['HS256'],
292
+ issuer: CHAT_TOKEN_ISSUER,
293
+ clockTolerance: CHAT_TOKEN_CLOCK_SKEW_SECONDS
294
+ });
295
+ } catch (error) {
296
+ const message = String(error && error.message ? error.message : '').toLowerCase();
297
+ if (message.includes('jwt expired')) return { valid: false, reason: 'expired' };
298
+ if (message.includes('invalid issuer')) return { valid: false, reason: 'invalid-issuer' };
299
+ if (message.includes('invalid signature')) return { valid: false, reason: 'invalid-signature' };
300
+ if (message.includes('jwt malformed') || message.includes('invalid token')) return { valid: false, reason: 'invalid-format' };
301
+ return { valid: false, reason: 'invalid-token' };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  }
303
 
304
+ if (!payload || typeof payload !== 'object') {
305
+ return { valid: false, reason: 'invalid-token' };
306
  }
307
 
308
  if (!Number.isInteger(payload.instanceNum) || payload.instanceNum < 1) {
 
592
  }, 10 * 60 * 1000);
593
 
594
  // --- FETCH WITH TIMEOUT ---
595
+ async function fetchWithTimeout(url, options, timeout = 10000) {
596
+ const controller = new AbortController();
597
+ const timer = setTimeout(() => controller.abort(), timeout);
598
+
599
+ try {
600
+ return await fetch(url, { ...options, signal: controller.signal });
601
+ } catch (error) {
602
+ if (error && error.name === 'AbortError') {
603
+ throw new Error('Request timeout');
604
+ }
605
+ throw error;
606
+ } finally {
607
+ clearTimeout(timer);
608
+ }
609
+ }
610
 
611
  // --- RESOLVE CHATFLOW ID ---
612
  async function resolveChatflowId(instanceNum, botName) {
 
694
  }
695
  }, 5000);
696
 
697
+ flowiseResponse.body.on('data', (chunk) => {
698
+ clearInterval(timeoutCheck);
699
+ streamStarted = true;
700
+ dataReceived = true;
701
+ lastDataTime = Date.now();
702
+ totalBytes += chunk.length;
703
 
704
  logSensitive(`[Streaming] Received chunk: ${chunk.length} bytes (total: ${totalBytes})`);
705
  clientRes.write(chunk);
 
804
  });
805
  }
806
 
 
807
  const claims = {
 
 
 
 
808
  instanceNum: target.instanceNum,
809
  botName: target.botName
810
  };
 
821
  claims.uaHash = hashUserAgent(req.headers['user-agent'] || '');
822
  }
823
 
824
+ const token = jwt.sign(claims, CHAT_TOKEN_SECRET, {
825
+ algorithm: 'HS256',
826
+ issuer: CHAT_TOKEN_ISSUER,
827
+ expiresIn: CHAT_TOKEN_TTL_SECONDS,
828
+ jwtid: crypto.randomBytes(12).toString('hex')
829
+ });
830
  res.status(200).json({
831
  token,
832
  tokenType: 'Bearer',