diff --git a/backend/src/index.js b/backend/src/index.js
index efe7970..ec06481 100644
--- a/backend/src/index.js
+++ b/backend/src/index.js
@@ -15,6 +15,7 @@ const authRoutes = require('./routes/auth');
const fileRoutes = require('./routes/file');
const searchRoutes = require('./routes/search');
const sqlRoutes = require('./routes/sql');
+const proxyRoutes = require('./routes/proxy');
const pexelsRoutes = require('./routes/pexels');
const openaiRoutes = require('./routes/openai');
@@ -88,6 +89,7 @@ app.use(cors({origin: true}));
require('./auth/auth');
app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: true }));
app.use('/api/auth', authRoutes);
app.use('/api/file', fileRoutes);
@@ -96,6 +98,7 @@ app.enable('trust proxy');
app.use('/api/users', passport.authenticate('jwt', {session: false}), usersRoutes);
+app.use('/api/proxy', proxyRoutes);
app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoutes);
diff --git a/backend/src/routes/proxy.js b/backend/src/routes/proxy.js
new file mode 100644
index 0000000..c4de94d
--- /dev/null
+++ b/backend/src/routes/proxy.js
@@ -0,0 +1,132 @@
+const express = require('express');
+const router = express.Router();
+const axios = require('axios');
+const Helpers = require('../helpers');
+
+router.all('/', Helpers.wrapAsync(async (req, res) => {
+ const targetUrl = req.query.url;
+ if (!targetUrl) return res.status(400).send('URL is required');
+
+ try {
+ const method = req.method;
+ const headers = { ...req.headers };
+
+ // Remove host and other headers that might cause issues
+ delete headers.host;
+ delete headers.origin;
+ delete headers.referer;
+
+ // Set a common User-Agent
+ headers['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36';
+
+ const response = await axios({
+ url: targetUrl,
+ method: method,
+ headers: headers,
+ data: req.body,
+ params: req.params,
+ responseType: 'arraybuffer',
+ validateStatus: () => true,
+ maxRedirects: 10,
+ timeout: 15000,
+ });
+
+ const contentType = response.headers['content-type'] || '';
+ const finalUrl = response.request.res.responseUrl || targetUrl;
+
+ // Copy headers but strip security and encoding ones
+ Object.keys(response.headers).forEach(key => {
+ const lowerKey = key.toLowerCase();
+ if (![
+ 'x-frame-options',
+ 'content-security-policy',
+ 'content-security-policy-report-only',
+ 'content-length',
+ 'transfer-encoding',
+ 'connection',
+ 'strict-transport-security',
+ 'x-content-type-options'
+ ].includes(lowerKey)) {
+ res.setHeader(key, response.headers[key]);
+ }
+ });
+
+ // Ensure we don't have caching issues during dev
+ res.setHeader('Cache-Control', 'no-store');
+
+ let data = response.data;
+
+ if (contentType.includes('text/html')) {
+ let html = data.toString('utf-8');
+
+ const parsedUrl = new URL(finalUrl);
+ const origin = parsedUrl.origin;
+ const baseUrl = origin + parsedUrl.pathname;
+
+ // Inject