Merge pull request #51 from amhsirak/develop

feat: apply proxy config
This commit is contained in:
Karishma Shukla
2024-10-06 03:20:06 +05:30
committed by GitHub
6 changed files with 112 additions and 23 deletions

View File

@@ -1,6 +1,6 @@
import { Router, Request, Response } from 'express'; import { Router, Request, Response } from 'express';
import User from '../models/User'; import User from '../models/User';
import { hashPassword } from '../utils/auth'; import { encrypt, decrypt } from '../utils/auth';
import { requireSignIn } from '../middlewares/auth'; import { requireSignIn } from '../middlewares/auth';
export const router = Router(); export const router = Router();
@@ -30,19 +30,20 @@ router.post('/config', requireSignIn, async (req: AuthenticatedRequest, res: Res
return res.status(400).send('Proxy URL is required'); return res.status(400).send('Proxy URL is required');
} }
let hashedProxyUsername: string | null = null; const encryptedProxyUrl = encrypt(server_url);
let hashedProxyPassword: string | null = null; let encryptedProxyUsername: string | null = null;
let encryptedProxyPassword: string | null = null;
if (username && password) { if (username && password) {
hashedProxyUsername = await hashPassword(username); encryptedProxyUsername = encrypt(username);
hashedProxyPassword = await hashPassword(password); encryptedProxyPassword = encrypt(password);
} else if (username && !password) { } else if (username && !password) {
return res.status(400).send('Proxy password is required when proxy username is provided'); return res.status(400).send('Proxy password is required when proxy username is provided');
} }
user.proxy_url = server_url; user.proxy_url = encryptedProxyUrl;
user.proxy_username = hashedProxyUsername; user.proxy_username = encryptedProxyUsername;
user.proxy_password = hashedProxyPassword; user.proxy_password = encryptedProxyPassword;
await user.save(); await user.save();
@@ -51,3 +52,24 @@ router.post('/config', requireSignIn, async (req: AuthenticatedRequest, res: Res
res.status(500).send(`Could not save proxy configuration - ${error.message}`); res.status(500).send(`Could not save proxy configuration - ${error.message}`);
} }
}); });
// TODO: Move this from here
export const getDecryptedProxyConfig = async (userId: string) => {
const user = await User.findByPk(userId, {
attributes: ['proxy_url', 'proxy_username', 'proxy_password'],
});
if (!user) {
throw new Error('User not found');
}
const decryptedProxyUrl = user.proxy_url ? decrypt(user.proxy_url) : null;
const decryptedProxyUsername = user.proxy_username ? decrypt(user.proxy_username) : null;
const decryptedProxyPassword = user.proxy_password ? decrypt(user.proxy_password) : null;
return {
proxy_url: decryptedProxyUrl,
proxy_username: decryptedProxyUsername,
proxy_password: decryptedProxyPassword,
};
};

View File

@@ -14,6 +14,7 @@ import {
import { chromium } from 'playwright-extra'; import { chromium } from 'playwright-extra';
import stealthPlugin from 'puppeteer-extra-plugin-stealth'; import stealthPlugin from 'puppeteer-extra-plugin-stealth';
import logger from "../logger"; import logger from "../logger";
import { getDecryptedProxyConfig } from './proxy';
export const router = Router(); export const router = Router();
chromium.use(stealthPlugin()); chromium.use(stealthPlugin());
@@ -30,11 +31,27 @@ router.all('/', (req, res, next) => {
* GET endpoint for starting the remote browser recording session. * GET endpoint for starting the remote browser recording session.
* returns session's id * returns session's id
*/ */
router.get('/start', (req, res) => { router.get('/start', async (req, res) => {
const proxyConfig = await getDecryptedProxyConfig(req.user.id);
// Prepare the proxy options dynamically based on the user's proxy configuration
let proxyOptions: any = {}; // Default to no proxy
if (proxyConfig.proxy_url) {
// Set the server, and if username & password exist, set those as well
proxyOptions = {
server: proxyConfig.proxy_url,
...(proxyConfig.proxy_username && proxyConfig.proxy_password && {
username: proxyConfig.proxy_username,
password: proxyConfig.proxy_password,
}),
};
}
const id = initializeRemoteBrowserForRecording({ const id = initializeRemoteBrowserForRecording({
browser: chromium, browser: chromium,
launchOptions: { launchOptions: {
headless: true, headless: true,
proxy: proxyOptions.server ? proxyOptions : undefined,
} }
}); });
return res.send(id); return res.send(id);

View File

@@ -10,10 +10,11 @@ import { chromium } from "playwright";
import { browserPool } from "../server"; import { browserPool } from "../server";
import fs from "fs"; import fs from "fs";
import { uuid } from "uuidv4"; import { uuid } from "uuidv4";
import { workflowQueue } from '../workflow-management/scheduler'; // import { workflowQueue } from '../workflow-management/scheduler';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import cron from 'node-cron'; import cron from 'node-cron';
import { googleSheetUpdateTasks, processGoogleSheetUpdates } from '../workflow-management/integrations/gsheet'; import { googleSheetUpdateTasks, processGoogleSheetUpdates } from '../workflow-management/integrations/gsheet';
import { getDecryptedProxyConfig } from './proxy';
export const router = Router(); export const router = Router();
@@ -85,9 +86,25 @@ router.delete('/runs/:fileName', async (req, res) => {
*/ */
router.put('/runs/:fileName', async (req, res) => { router.put('/runs/:fileName', async (req, res) => {
try { try {
const proxyConfig = await getDecryptedProxyConfig(req.user.id);
let proxyOptions: any = {};
if (proxyConfig.proxy_url) {
proxyOptions = {
server: proxyConfig.proxy_url,
...(proxyConfig.proxy_username && proxyConfig.proxy_password && {
username: proxyConfig.proxy_username,
password: proxyConfig.proxy_password,
}),
};
}
const id = createRemoteBrowserForRun({ const id = createRemoteBrowserForRun({
browser: chromium, browser: chromium,
launchOptions: { headless: true } launchOptions: {
headless: true,
proxy: proxyOptions.server ? proxyOptions : undefined,
}
}); });
const runId = uuid(); const runId = uuid();
@@ -262,16 +279,16 @@ router.put('/schedule/:fileName/', async (req, res) => {
const runId = uuid(); const runId = uuid();
await workflowQueue.add( // await workflowQueue.add(
'run workflow', // 'run workflow',
{ fileName, runId }, // { fileName, runId },
{ // {
repeat: { // repeat: {
pattern: cronExpression, // pattern: cronExpression,
tz: timezone // tz: timezone
} // }
} // }
); // );
res.status(200).json({ res.status(200).json({
message: 'success', message: 'success',

View File

@@ -1,4 +1,6 @@
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import crypto from 'crypto';
import { getEnvVariable } from './env';
export const hashPassword = (password: string): Promise<string> => { export const hashPassword = (password: string): Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -19,4 +21,25 @@ export const hashPassword = (password: string): Promise<string> => {
// password from frontend and hash from database // password from frontend and hash from database
export const comparePassword = (password: string, hash: string): Promise<boolean> => { export const comparePassword = (password: string, hash: string): Promise<boolean> => {
return bcrypt.compare(password, hash) return bcrypt.compare(password, hash)
} }
export const encrypt = (text: string): string => {
const ivLength = parseInt(getEnvVariable('IV_LENGTH'), 10);
const iv = crypto.randomBytes(ivLength);
const algorithm = getEnvVariable('ALGORITHM');
const key = Buffer.from(getEnvVariable('ENCRYPTION_KEY'), 'hex');
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return `${iv.toString('hex')}:${encrypted}`;
};
export const decrypt = (encryptedText: string): string => {
const [iv, encrypted] = encryptedText.split(':');
const algorithm = getEnvVariable('ALGORITHM');
const key = Buffer.from(getEnvVariable('ENCRYPTION_KEY'), 'hex');
const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(iv, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
};

8
server/src/utils/env.ts Normal file
View File

@@ -0,0 +1,8 @@
// Helper function to get environment variables and throw an error if they are not set
export const getEnvVariable = (key: string, defaultValue?: string): string => {
const value = process.env[key] || defaultValue;
if (!value) {
throw new Error(`Environment variable ${key} is not defined`);
}
return value;
};

View File

@@ -98,7 +98,9 @@ const ProxyForm: React.FC = () => {
fullWidth fullWidth
required required
error={!!errors.server_url} error={!!errors.server_url}
helperText={errors.server_url || 'e.g., http://proxy-server.com:8080'} helperText={errors.server_url || `Proxy to be used for all requests.
HTTP and SOCKS proxies are supported, for example http://myproxy.com:3128 or
socks5://myproxy.com:3128. Short form myproxy.com:3128 is considered an HTTP proxy.`}
/> />
</FormControl> </FormControl>
<FormControl> <FormControl>