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 User from '../models/User';
import { hashPassword } from '../utils/auth';
import { encrypt, decrypt } from '../utils/auth';
import { requireSignIn } from '../middlewares/auth';
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');
}
let hashedProxyUsername: string | null = null;
let hashedProxyPassword: string | null = null;
const encryptedProxyUrl = encrypt(server_url);
let encryptedProxyUsername: string | null = null;
let encryptedProxyPassword: string | null = null;
if (username && password) {
hashedProxyUsername = await hashPassword(username);
hashedProxyPassword = await hashPassword(password);
encryptedProxyUsername = encrypt(username);
encryptedProxyPassword = encrypt(password);
} else if (username && !password) {
return res.status(400).send('Proxy password is required when proxy username is provided');
}
user.proxy_url = server_url;
user.proxy_username = hashedProxyUsername;
user.proxy_password = hashedProxyPassword;
user.proxy_url = encryptedProxyUrl;
user.proxy_username = encryptedProxyUsername;
user.proxy_password = encryptedProxyPassword;
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}`);
}
});
// 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 stealthPlugin from 'puppeteer-extra-plugin-stealth';
import logger from "../logger";
import { getDecryptedProxyConfig } from './proxy';
export const router = Router();
chromium.use(stealthPlugin());
@@ -30,11 +31,27 @@ router.all('/', (req, res, next) => {
* GET endpoint for starting the remote browser recording session.
* 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({
browser: chromium,
launchOptions: {
headless: true,
proxy: proxyOptions.server ? proxyOptions : undefined,
}
});
return res.send(id);

View File

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

View File

@@ -1,4 +1,6 @@
import bcrypt from "bcrypt";
import crypto from 'crypto';
import { getEnvVariable } from './env';
export const hashPassword = (password: string): Promise<string> => {
return new Promise((resolve, reject) => {
@@ -19,4 +21,25 @@ export const hashPassword = (password: string): Promise<string> => {
// password from frontend and hash from database
export const comparePassword = (password: string, hash: string): Promise<boolean> => {
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
required
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>