@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
8
server/src/utils/env.ts
Normal 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;
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user