diff --git a/server/src/api/record.ts b/server/src/api/record.ts index 3f1dcd50..a9f56d7d 100644 --- a/server/src/api/record.ts +++ b/server/src/api/record.ts @@ -12,6 +12,7 @@ import logger from "../logger"; import { browserPool } from "../server"; import { io, Socket } from "socket.io-client"; import { BinaryOutputService } from "../storage/mino"; +import { AuthenticatedRequest } from "../routes/record" const formatRecording = (recordingData: any) => { const recordingMeta = recordingData.recording_meta; @@ -388,8 +389,11 @@ async function waitForRunCompletion(runId: string, interval: number = 2000) { } } -router.post("/robots/:id/runs", requireAPIKey, async (req: Request, res: Response) => { +router.post("/robots/:id/runs", requireAPIKey, async (req: AuthenticatedRequest, res: Response) => { try { + if (!req.user) { + return res.status(401).json({ ok: false, error: 'Unauthorized' }); + } const runId = await handleRunRecording(req.params.id, req.user.dataValues.id); console.log(`Result`, runId); diff --git a/server/src/db/config.ts b/server/src/db/config.ts deleted file mode 100644 index 56c68d8b..00000000 --- a/server/src/db/config.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Sequelize } from 'sequelize'; -import dotenv from 'dotenv'; -import setupAssociations from '../models/associations'; - -dotenv.config(); -const sequelize = new Sequelize( - `postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`, - { - host: 'localhost', - dialect: 'postgres', - logging: false, - } -); - -export const connectDB = async () => { - try { - await sequelize.authenticate(); - console.log('Database connected successfully'); - } catch (error) { - console.error('Unable to connect to the database:', error); - } -}; - -export const syncDB = async () => { - try { - //setupAssociations(); - await sequelize.sync({ force: false }); // force: true will drop and recreate tables on every run - console.log('Database synced successfully!'); - } catch (error) { - console.error('Failed to sync database:', error); - } -}; - - -export default sequelize; \ No newline at end of file diff --git a/server/src/middlewares/api.ts b/server/src/middlewares/api.ts index c79529eb..81ae028e 100644 --- a/server/src/middlewares/api.ts +++ b/server/src/middlewares/api.ts @@ -1,7 +1,8 @@ import { Request, Response } from "express"; import User from "../models/User"; +import { AuthenticatedRequest } from "../routes/record" -export const requireAPIKey = async (req: Request, res: Response, next: any) => { +export const requireAPIKey = async (req: AuthenticatedRequest, res: Response, next: any) => { const apiKey = req.headers['x-api-key']; if (!apiKey) { return res.status(401).json({ error: "API key is missing" }); diff --git a/server/src/middlewares/auth.ts b/server/src/middlewares/auth.ts index 98143c08..75540ad5 100644 --- a/server/src/middlewares/auth.ts +++ b/server/src/middlewares/auth.ts @@ -1,13 +1,11 @@ import { Request, Response } from "express"; -import { verify } from "jsonwebtoken"; +import { verify, JwtPayload } from "jsonwebtoken"; -declare module "express-serve-static-core" { - interface Request { - user?: any; - } +interface UserRequest extends Request { + user?: JwtPayload | string; } -export const requireSignIn = (req: Request, res: Response, next: any) => { +export const requireSignIn = (req: UserRequest, res: Response, next: any) => { const token = req.cookies && req.cookies.token ? req.cookies.token : null; if (token === null) return res.sendStatus(401); diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 16304d09..f4bf742e 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -146,7 +146,12 @@ router.get('/api-key', requireSignIn, async (req: AuthenticatedRequest, res) => } }); -router.delete('/delete-api-key', requireSignIn, async (req, res) => { +router.delete('/delete-api-key', requireSignIn, async (req: AuthenticatedRequest, res) => { + + if (!req.user) { + return res.status(401).send({ error: 'Unauthorized' }); + } + try { const user = await User.findByPk(req.user.id, { raw: true }); @@ -193,7 +198,7 @@ router.get('/google', (req, res) => { }); // Step 2: Handle Google OAuth callback -router.get('/google/callback', requireSignIn, async (req, res) => { +router.get('/google/callback', requireSignIn, async (req: AuthenticatedRequest, res) => { const { code, state } = req.query; try { if (!state) { @@ -217,6 +222,10 @@ router.get('/google/callback', requireSignIn, async (req, res) => { return res.status(400).json({ message: 'Email not found' }); } + if (!req.user) { + return res.status(401).send({ error: 'Unauthorized' }); + } + // Get the currently authenticated user (from `requireSignIn`) let user = await User.findOne({ where: { id: req.user.id } }); @@ -264,8 +273,11 @@ router.get('/google/callback', requireSignIn, async (req, res) => { }); // Step 3: Get data from Google Sheets -router.post('/gsheets/data', requireSignIn, async (req, res) => { +router.post('/gsheets/data', requireSignIn, async (req: AuthenticatedRequest, res) => { const { spreadsheetId, robotId } = req.body; + if (!req.user) { + return res.status(401).send({ error: 'Unauthorized' }); + } const user = await User.findByPk(req.user.id, { raw: true }); if (!user) { diff --git a/server/src/routes/integration.ts b/server/src/routes/integration.ts index 44dbd98f..3c4672c5 100644 --- a/server/src/routes/integration.ts +++ b/server/src/routes/integration.ts @@ -1,6 +1,6 @@ import { Router } from 'express'; import logger from "../logger"; -import { loadIntegrations, saveIntegrations } from '../workflow-management/integrations/gsheet'; +// import { loadIntegrations, saveIntegrations } from '../workflow-management/integrations/gsheet'; import { requireSignIn } from '../middlewares/auth'; export const router = Router(); @@ -12,11 +12,6 @@ router.post('/upload-credentials', requireSignIn, async (req, res) => { return res.status(400).json({ message: 'Credentials, Spreadsheet ID, and Range are required.' }); } // *** TEMPORARILY WE STORE CREDENTIALS HERE *** - let integrations = loadIntegrations(fileName); - integrations = { fileName, spreadsheetId, range, credentials }; - saveIntegrations(fileName, integrations); - logger.log('info', 'Service account credentials saved successfully.'); - return res.send(true); } catch (error: any) { logger.log('error', `Error saving credentials: ${error.message}`); return res.status(500).json({ message: 'Failed to save credentials.', error: error.message }); diff --git a/server/src/routes/record.ts b/server/src/routes/record.ts index 7d882e2e..e37995cc 100644 --- a/server/src/routes/record.ts +++ b/server/src/routes/record.ts @@ -1,7 +1,7 @@ /** * RESTful API endpoints handling remote browser recording sessions. */ -import { Router } from 'express'; +import { Router, Request, Response } from 'express'; import { initializeRemoteBrowserForRecording, @@ -20,6 +20,11 @@ import { requireSignIn } from '../middlewares/auth'; export const router = Router(); chromium.use(stealthPlugin()); + +export interface AuthenticatedRequest extends Request { + user?: any; +} + /** * Logs information about remote browser recording session. */ @@ -32,7 +37,10 @@ router.all('/', requireSignIn, (req, res, next) => { * GET endpoint for starting the remote browser recording session. * returns session's id */ -router.get('/start', requireSignIn, async (req, res) => { +router.get('/start', requireSignIn, async (req: AuthenticatedRequest, res: Response) => { + if (!req.user) { + return res.status(401).send('User not authenticated'); + } 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 diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 5a72039f..69f19e42 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -13,6 +13,7 @@ import Robot from '../models/Robot'; import Run from '../models/Run'; import { BinaryOutputService } from '../storage/mino'; import { workflowQueue } from '../worker'; +import { AuthenticatedRequest } from './record'; export const router = Router(); @@ -101,7 +102,7 @@ router.delete('/runs/:id', requireSignIn, async (req, res) => { * PUT endpoint for starting a remote browser instance and saving run metadata to the storage. * Making it ready for interpretation and returning a runId. */ -router.put('/runs/:id', requireSignIn, async (req, res) => { +router.put('/runs/:id', requireSignIn, async (req: AuthenticatedRequest, res) => { try { const recording = await Robot.findOne({ where: { @@ -114,6 +115,10 @@ router.put('/runs/:id', requireSignIn, async (req, res) => { return res.status(404).send({ error: 'Recording not found' }); } + if (!req.user) { + return res.status(401).send({ error: 'Unauthorized' }); + } + const proxyConfig = await getDecryptedProxyConfig(req.user.id); let proxyOptions: any = {}; @@ -242,7 +247,7 @@ router.post('/runs/run/:id', requireSignIn, async (req, res) => { } }); -router.put('/schedule/:id/', requireSignIn, async (req, res) => { +router.put('/schedule/:id/', requireSignIn, async (req: AuthenticatedRequest, res) => { console.log(req.body); try { const { id } = req.params; @@ -333,6 +338,10 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { return res.status(400).json({ error: 'Invalid cron expression generated' }); } + if (!req.user) { + return res.status(401).send({ error: 'Unauthorized' }); + } + const runId = uuid(); const userId = req.user.id; diff --git a/server/src/server.ts b/server/src/server.ts index 25660e11..2179f946 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -18,7 +18,7 @@ import { fork } from 'child_process'; const app = express(); app.use(cors({ - origin: 'http://localhost:3000', + origin: 'http://localhost:5173', credentials: true, })); app.use(express.json()); diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 90598563..18b878ff 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -193,8 +193,8 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { attr: (name: string, value: string) => false, seedMinLength: 1, optimizedMinLength: 2, - threshold: 1000, - maxNumberOfTries: 10000, + threshold: 900, + maxNumberOfTries: 9000, }; config = { ...defaults, ...options }; @@ -535,12 +535,12 @@ export const getSelectors = async (page: Page, coordinates: Coordinates) => { let value: string | undefined = void 0; // If it’s not a printable ASCII character… if (codePoint < 0x20 || codePoint > 0x7e) { - if (codePoint >= 0xd800 && codePoint <= 0xdbff && counter < length) { + if (codePoint >= 0xd900 && codePoint <= 0xdbff && counter < length) { // It’s a high surrogate, and there is a next character. const extra = string.charCodeAt(counter++); if ((extra & 0xfc00) == 0xdc00) { // next character is low surrogate - codePoint = ((codePoint & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000; + codePoint = ((codePoint & 0x3ff) << 10) + (extra & 0x3ff) + 0x9000; } else { // It’s an unmatched surrogate; only append this code unit, in case // the next code unit is the high surrogate of a surrogate pair.