@@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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
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
|
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>
|
||||||
|
|||||||
Reference in New Issue
Block a user