Niansuh commited on
Commit
9be9fcf
1 Parent(s): a3c440e

Create app.js

Browse files
Files changed (1) hide show
  1. app.js +537 -0
app.js ADDED
@@ -0,0 +1,537 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // app.js
2
+
3
+ const express = require('express');
4
+ const bodyParser = require('body-parser');
5
+ const axios = require('axios');
6
+ const path = require('path');
7
+ const mysql = require('mysql2');
8
+ const morgan = require('morgan'); // For logging HTTP requests
9
+ const helmet = require('helmet'); // For securing HTTP headers
10
+ const { body, validationResult } = require('express-validator'); // For input validation
11
+ require('dotenv').config(); // For environment variables
12
+
13
+ const app = express();
14
+ const port = process.env.PORT || 3000;
15
+
16
+ // Middleware Setup
17
+ app.use(bodyParser.urlencoded({ extended: true }));
18
+ app.use(bodyParser.json());
19
+ app.use(helmet());
20
+ app.use(morgan('combined'));
21
+ app.use(express.static('public')); // Serve static files from 'public' directory
22
+
23
+ // Database Connection Configuration with Connection Pooling
24
+ const pool = mysql.createPool({
25
+ host: process.env.DB_HOST,
26
+ port: process.env.DB_PORT || 3306,
27
+ user: process.env.DB_USER,
28
+ password: process.env.DB_PASSWORD,
29
+ database: process.env.DB_NAME,
30
+ waitForConnections: true,
31
+ connectionLimit: 10, // Adjust based on your requirements
32
+ queueLimit: 0
33
+ });
34
+
35
+ // Promisify for async/await usage
36
+ const promisePool = pool.promise();
37
+
38
+ // Function to Initialize Database Tables
39
+ async function initializeDatabase() {
40
+ try {
41
+ // Create redemption_codes Table
42
+ const createRedemptionCodesTable = `
43
+ CREATE TABLE IF NOT EXISTS redemption_codes (
44
+ id INT AUTO_INCREMENT PRIMARY KEY,
45
+ code TEXT NOT NULL,
46
+ name VARCHAR(255),
47
+ quota INT,
48
+ count INT,
49
+ user_ip VARCHAR(45),
50
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
51
+ )
52
+ `;
53
+
54
+ await promisePool.query(createRedemptionCodesTable);
55
+ console.log('Redemption codes table is ready.');
56
+
57
+ // Create session_cookies Table
58
+ const createSessionCookiesTable = `
59
+ CREATE TABLE IF NOT EXISTS session_cookies (
60
+ id INT AUTO_INCREMENT PRIMARY KEY,
61
+ username VARCHAR(255) NOT NULL,
62
+ session_cookies TEXT NOT NULL,
63
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
64
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
65
+ )
66
+ `;
67
+
68
+ await promisePool.query(createSessionCookiesTable);
69
+ console.log('Session_cookies table is ready.');
70
+
71
+ // Create tokens Table with renamed `token_key` column
72
+ const createTokensTable = `
73
+ CREATE TABLE IF NOT EXISTS tokens (
74
+ id INT AUTO_INCREMENT PRIMARY KEY,
75
+ name VARCHAR(255) NOT NULL,
76
+ token TEXT NOT NULL,
77
+ token_key VARCHAR(255) NOT NULL,
78
+ remain_quota INT NOT NULL,
79
+ expired_time INT NOT NULL,
80
+ unlimited_quota BOOLEAN NOT NULL,
81
+ model_limits_enabled BOOLEAN NOT NULL,
82
+ model_limits TEXT,
83
+ allow_ips TEXT,
84
+ group_name VARCHAR(255),
85
+ user_ip VARCHAR(45),
86
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
87
+ )
88
+ `;
89
+
90
+ await promisePool.query(createTokensTable);
91
+ console.log('Tokens table is ready.');
92
+ } catch (err) {
93
+ console.error('Error initializing database tables:', err);
94
+ process.exit(1);
95
+ }
96
+ }
97
+
98
+ // Initialize Database Tables
99
+ initializeDatabase();
100
+
101
+ // Serve the HTML Form (Assuming you have an index.html in the 'views' directory)
102
+ app.get('/', (req, res) => {
103
+ res.sendFile(path.join(__dirname, 'views', 'index.html'));
104
+ });
105
+
106
+ // Utility Function to Get User's IP Address
107
+ function getUserIP(req) {
108
+ const xForwardedFor = req.headers['x-forwarded-for'];
109
+ if (xForwardedFor) {
110
+ const ips = xForwardedFor.split(',').map(ip => ip.trim());
111
+ return ips[0];
112
+ }
113
+ return req.socket.remoteAddress;
114
+ }
115
+
116
+ // Function to Save Session Cookies to the Database
117
+ async function saveSessionCookies(username, sessionCookies) {
118
+ try {
119
+ const insertQuery = `
120
+ INSERT INTO session_cookies (username, session_cookies)
121
+ VALUES (?, ?)
122
+ `;
123
+
124
+ await promisePool.query(insertQuery, [username, sessionCookies]);
125
+ console.log(`New session cookies for user '${username}' saved successfully.`);
126
+ } catch (err) {
127
+ console.error(`Error saving session cookies for user '${username}':`, err);
128
+ throw err;
129
+ }
130
+ }
131
+
132
+ // Function to Perform Login and Retrieve Session Cookies
133
+ async function performLogin() {
134
+ const loginUrl = `${process.env.BASE_URL}/api/user/login?turnstile=`;
135
+
136
+ const headers = {
137
+ 'Accept': 'application/json, text/plain, */*',
138
+ 'Accept-Language': 'en-US,en;q=0.9',
139
+ 'Connection': 'keep-alive',
140
+ 'Content-Type': 'application/json',
141
+ 'Origin': process.env.BASE_URL,
142
+ 'Referer': `${process.env.BASE_URL}/login`,
143
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
144
+ 'VoApi-User': '-1'
145
+ };
146
+
147
+ const data = {
148
+ username: process.env.ADMIN_USERNAME,
149
+ password: process.env.ADMIN_PASSWORD
150
+ };
151
+
152
+ try {
153
+ const response = await axios.post(loginUrl, data, {
154
+ headers: headers,
155
+ withCredentials: true,
156
+ httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) // Equivalent to --insecure
157
+ });
158
+
159
+ // Extract all cookies from response headers
160
+ const setCookieHeader = response.headers['set-cookie'];
161
+ if (setCookieHeader) {
162
+ // Parse the set-cookie header to extract all cookies
163
+ const cookies = setCookieHeader.map(cookie => cookie.split(';')[0]);
164
+ const cookieString = cookies.join('; '); // Join all cookies into a single string
165
+
166
+ if (cookieString) {
167
+ console.log('Session cookies obtained from login.');
168
+ return cookieString;
169
+ }
170
+ }
171
+
172
+ throw new Error('No session cookies received from login response.');
173
+ } catch (error) {
174
+ if (error.response) {
175
+ throw new Error(`Login API Error: ${JSON.stringify(error.response.data)}`);
176
+ } else if (error.request) {
177
+ throw new Error('Login API Error: No response received from the login service.');
178
+ } else {
179
+ throw new Error(`Login Error: ${error.message}`);
180
+ }
181
+ }
182
+ }
183
+
184
+ // Function to Generate Redemption Code
185
+ async function generateRedemptionCode(name, quota, count, sessionCookies) {
186
+ console.log(`Using session cookies: ${sessionCookies}`); // Log the cookies being used
187
+
188
+ const url = `${process.env.BASE_URL}/api/redemption/`;
189
+
190
+ const headers = {
191
+ 'Accept': 'application/json, text/plain, */*',
192
+ 'Accept-Language': 'en-US,en;q=0.9',
193
+ 'Connection': 'keep-alive',
194
+ 'Content-Type': 'application/json',
195
+ 'Origin': process.env.BASE_URL,
196
+ 'Referer': `${process.env.BASE_URL}/redemption`,
197
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
198
+ 'VoApi-User': '1',
199
+ 'Cookie': sessionCookies // Use all stored cookies
200
+ };
201
+
202
+ const data = {
203
+ name: name,
204
+ quota: parseInt(quota),
205
+ count: parseInt(count)
206
+ };
207
+
208
+ try {
209
+ const response = await axios.post(url, data, {
210
+ headers: headers,
211
+ httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) // Equivalent to --insecure
212
+ });
213
+
214
+ console.log('Redemption code generated successfully.');
215
+ return response.data;
216
+ } catch (error) {
217
+ if (error.response) {
218
+ throw new Error(`API Error: ${JSON.stringify(error.response.data)}`);
219
+ } else if (error.request) {
220
+ throw new Error('API Error: No response received from the redemption service.');
221
+ } else {
222
+ throw new Error(`Error: ${error.message}`);
223
+ }
224
+ }
225
+ }
226
+
227
+ // Function to Save Redemption Code to the Database
228
+ async function saveRedemptionCode(codeData, name, quota, count, userIP) {
229
+ try {
230
+ // Assuming the API returns the code in codeData.code
231
+ const code = codeData.code || JSON.stringify(codeData);
232
+
233
+ const insertQuery = `
234
+ INSERT INTO redemption_codes (code, name, quota, count, user_ip)
235
+ VALUES (?, ?, ?, ?, ?)
236
+ `;
237
+
238
+ await promisePool.query(insertQuery, [code, name, quota, count, userIP]);
239
+ console.log('Redemption code saved to database.');
240
+ } catch (err) {
241
+ console.error('Error saving redemption code to database:', err);
242
+ throw err;
243
+ }
244
+ }
245
+
246
+ // Function to Create Token
247
+ async function createToken(name, remain_quota, expired_time, unlimited_quota, model_limits_enabled, model_limits, allow_ips, group_name, sessionCookies) {
248
+ console.log(`Using session cookies for token creation: ${sessionCookies}`); // Log the cookies being used
249
+
250
+ const url = `${process.env.BASE_URL}/api/token/`;
251
+
252
+ const headers = {
253
+ 'Accept': 'application/json, text/plain, */*',
254
+ 'Accept-Language': 'en-US,en;q=0.9,ru;q=0.8',
255
+ 'Connection': 'keep-alive',
256
+ 'Content-Type': 'application/json',
257
+ 'Origin': process.env.BASE_URL,
258
+ 'Referer': `${process.env.BASE_URL}/token`,
259
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
260
+ 'VoApi-User': '1',
261
+ 'Cookie': sessionCookies // Use the new session cookie
262
+ };
263
+
264
+ const data = {
265
+ name: name,
266
+ remain_quota: parseInt(remain_quota),
267
+ expired_time: parseInt(expired_time),
268
+ unlimited_quota: unlimited_quota,
269
+ model_limits_enabled: model_limits_enabled,
270
+ model_limits: model_limits || '',
271
+ allow_ips: allow_ips || '',
272
+ group: group_name || ''
273
+ };
274
+
275
+ try {
276
+ const response = await axios.post(url, data, {
277
+ headers: headers,
278
+ httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) // Equivalent to --insecure
279
+ });
280
+
281
+ console.log('Token creation API call successful.');
282
+ return response.data;
283
+ } catch (error) {
284
+ if (error.response) {
285
+ throw new Error(`Token API Error: ${JSON.stringify(error.response.data)}`);
286
+ } else if (error.request) {
287
+ throw new Error('Token API Error: No response received from the token service.');
288
+ } else {
289
+ throw new Error(`Error: ${error.message}`);
290
+ }
291
+ }
292
+ }
293
+
294
+ // Function to List Tokens
295
+ async function listTokens(sessionCookies, p = 0, size = 100) {
296
+ console.log(`Listing tokens with session cookies: ${sessionCookies}`);
297
+
298
+ const url = `${process.env.BASE_URL}/api/token/?p=${p}&size=${size}`;
299
+
300
+ const headers = {
301
+ 'Accept': 'application/json, text/plain, */*',
302
+ 'Accept-Language': 'en-US,en;q=0.9,ru;q=0.8',
303
+ 'Connection': 'keep-alive',
304
+ 'Referer': `${process.env.BASE_URL}/token`,
305
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
306
+ 'VoApi-User': '1',
307
+ 'Cookie': sessionCookies // Use the session cookie
308
+ };
309
+
310
+ try {
311
+ const response = await axios.get(url, {
312
+ headers: headers,
313
+ httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) // Equivalent to --insecure
314
+ });
315
+
316
+ console.log('Tokens list retrieved successfully.');
317
+ return response.data;
318
+ } catch (error) {
319
+ if (error.response) {
320
+ throw new Error(`List Tokens API Error: ${JSON.stringify(error.response.data)}`);
321
+ } else if (error.request) {
322
+ throw new Error('List Tokens API Error: No response received from the token service.');
323
+ } else {
324
+ throw new Error(`Error: ${error.message}`);
325
+ }
326
+ }
327
+ }
328
+
329
+ // Function to Save Token to the Database with 'sk-' Prefix
330
+ async function saveToken(tokenData, name, remain_quota, expired_time, unlimited_quota, model_limits_enabled, model_limits, allow_ips, group_name, userIP) {
331
+ try {
332
+ // Extract the 'key' from tokenData and add 'sk-' prefix
333
+ const key = tokenData.key ? `sk-${tokenData.key}` : null;
334
+
335
+ if (!key) {
336
+ throw new Error('Token key not found in the response.');
337
+ }
338
+
339
+ // Save the token along with its prefixed key
340
+ const insertQuery = `
341
+ INSERT INTO tokens (name, token, token_key, remain_quota, expired_time, unlimited_quota, model_limits_enabled, model_limits, allow_ips, group_name, user_ip)
342
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
343
+ `;
344
+
345
+ const [result] = await promisePool.query(insertQuery, [
346
+ name,
347
+ JSON.stringify(tokenData), // Store the entire token data as JSON
348
+ key, // Prefixed key
349
+ parseInt(remain_quota),
350
+ parseInt(expired_time),
351
+ unlimited_quota,
352
+ model_limits_enabled,
353
+ model_limits || '',
354
+ allow_ips || '',
355
+ group_name || '',
356
+ userIP
357
+ ]);
358
+
359
+ console.log(`Token saved to database with ID: ${result.insertId} and key: ${key}`);
360
+ } catch (err) {
361
+ console.error('Error saving token to database:', err);
362
+ throw err;
363
+ }
364
+ }
365
+
366
+ // Route: POST /api/create
367
+ app.post('/api/create', [
368
+ body('username').isString().trim().notEmpty(),
369
+ body('name').isString().trim().notEmpty(),
370
+ body('quota').isInt({ min: 1 }),
371
+ body('count').isInt({ min: 1 })
372
+ ], async (req, res) => {
373
+ // Validate Input
374
+ const errors = validationResult(req);
375
+ if (!errors.isEmpty()) {
376
+ return res.status(400).json({ success: false, errors: errors.array() });
377
+ }
378
+
379
+ const { username, name, quota, count } = req.body;
380
+ const userIP = getUserIP(req);
381
+ const apiKey = req.headers['x-api-key'];
382
+
383
+ // Check API Key
384
+ if (apiKey !== process.env.API_KEY) {
385
+ return res.status(401).json({
386
+ success: false,
387
+ error: 'Unauthorized: Invalid API key'
388
+ });
389
+ }
390
+
391
+ try {
392
+ // Perform Login to Obtain New Session Cookies
393
+ console.log('Performing login to obtain new session cookies...');
394
+ const sessionCookies = await performLogin();
395
+
396
+ // Save the New Session Cookies to the Database
397
+ await saveSessionCookies(username, sessionCookies);
398
+ console.log('New session cookies saved to the database.');
399
+
400
+ // Generate Redemption Code Using the New Session Cookies
401
+ const redemptionCode = await generateRedemptionCode(name, quota, count, sessionCookies);
402
+
403
+ // Save Redemption Code to Database
404
+ await saveRedemptionCode(redemptionCode, name, quota, count, userIP);
405
+
406
+ res.json({
407
+ success: true,
408
+ data: redemptionCode
409
+ });
410
+ } catch (error) {
411
+ console.error('Error:', error.message);
412
+ res.status(500).json({
413
+ success: false,
414
+ error: error.message
415
+ });
416
+ }
417
+ });
418
+
419
+ // Route: POST /api/token
420
+ app.post('/api/token', [
421
+ body('name').isString().trim().notEmpty(),
422
+ body('remain_quota').isInt({ min: 1 }),
423
+ body('expired_time').isInt(), // Assuming negative values are allowed
424
+ body('unlimited_quota').isBoolean(),
425
+ body('model_limits_enabled').isBoolean(),
426
+ body('model_limits').optional().isString(),
427
+ body('allow_ips').optional().isString(),
428
+ body('group').optional().isString()
429
+ ], async (req, res) => {
430
+ // Validate Input
431
+ const errors = validationResult(req);
432
+ if (!errors.isEmpty()) {
433
+ return res.status(400).json({ success: false, errors: errors.array() });
434
+ }
435
+
436
+ const {
437
+ name,
438
+ remain_quota,
439
+ expired_time,
440
+ unlimited_quota,
441
+ model_limits_enabled,
442
+ model_limits,
443
+ allow_ips,
444
+ group
445
+ } = req.body;
446
+
447
+ const userIP = getUserIP(req);
448
+ const apiKey = req.headers['x-api-key'];
449
+
450
+ // Check API Key
451
+ if (apiKey !== process.env.API_KEY) {
452
+ return res.status(401).json({
453
+ success: false,
454
+ error: 'Unauthorized: Invalid API key'
455
+ });
456
+ }
457
+
458
+ try {
459
+ // Perform Login to Obtain New Session Cookies
460
+ console.log('Performing login to obtain new session cookies for token creation...');
461
+ const sessionCookies = await performLogin();
462
+
463
+ // Save the New Session Cookies to the Database with Correct Username
464
+ await saveSessionCookies(process.env.ADMIN_USERNAME, sessionCookies); // Use ADMIN_USERNAME instead of 'name'
465
+ console.log('New session cookies saved to the database.');
466
+
467
+ // Create Token Using the New Session Cookies
468
+ const tokenData = await createToken(
469
+ name,
470
+ remain_quota,
471
+ expired_time,
472
+ unlimited_quota,
473
+ model_limits_enabled,
474
+ model_limits,
475
+ allow_ips,
476
+ group,
477
+ sessionCookies
478
+ );
479
+
480
+ // If the token creation API does not return the 'key', fetch it using the listTokens function
481
+ let tokenKey = tokenData.key; // Attempt to get the key directly
482
+ if (!tokenKey) {
483
+ console.log('Token key not found in creation response. Fetching from tokens list...');
484
+ const tokensList = await listTokens(sessionCookies, 0, 100);
485
+
486
+ if (tokensList.success && Array.isArray(tokensList.data) && tokensList.data.length > 0) {
487
+ // Assuming the latest token is the first in the list
488
+ const latestToken = tokensList.data[0];
489
+ tokenKey = latestToken.key;
490
+
491
+ if (!tokenKey) {
492
+ throw new Error('Token key not found in the tokens list response.');
493
+ }
494
+ } else {
495
+ throw new Error('Failed to retrieve tokens list or no tokens available.');
496
+ }
497
+ }
498
+
499
+ // Update the tokenData with the retrieved key and add 'sk-' prefix
500
+ tokenData.key = `sk-${tokenKey}`;
501
+
502
+ // Save Token to Database
503
+ await saveToken(
504
+ tokenData,
505
+ name,
506
+ remain_quota,
507
+ expired_time,
508
+ unlimited_quota,
509
+ model_limits_enabled,
510
+ model_limits,
511
+ allow_ips,
512
+ group,
513
+ userIP
514
+ );
515
+
516
+ res.json({
517
+ success: true,
518
+ data: tokenData.key // Return the 'token_key' with 'sk-' prefix
519
+ });
520
+ } catch (error) {
521
+ console.error('Error:', error.message);
522
+ res.status(500).json({
523
+ success: false,
524
+ error: error.message
525
+ });
526
+ }
527
+ });
528
+
529
+ // Health Check Endpoint
530
+ app.get('/health', (req, res) => {
531
+ res.status(200).json({ status: 'OK' });
532
+ });
533
+
534
+ // Start the Server
535
+ app.listen(port, () => {
536
+ console.log(`App is running at http://localhost:${port}`);
537
+ });