feat(clickTtHttpPageRoutes): enhance proxy functionality with session management and cookie handling
- Introduced session ID management to maintain user sessions across proxy requests, improving tracking and logging capabilities. - Added functions to extract and format cookies, enabling proper cookie handling for requests and responses. - Updated link and form action rewriting functions to include session IDs, ensuring seamless integration with the proxy. - Enhanced the proxy GET and POST endpoints to manage cookies effectively, improving the overall robustness of the proxy interactions.
This commit is contained in:
@@ -4,12 +4,42 @@
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import crypto from 'crypto';
|
||||
import clickTtHttpPageService from '../services/clickTtHttpPageService.js';
|
||||
import HttpPageFetchLog from '../models/HttpPageFetchLog.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Cookie-Store pro Session (sid) – Cookies vom Ursprungsserver werden hier gehalten und weitergeleitet
|
||||
const cookieStore = new Map();
|
||||
|
||||
function extractCookies(setCookieHeaders) {
|
||||
const cookies = {};
|
||||
if (setCookieHeaders) {
|
||||
(Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders]).forEach(cookie => {
|
||||
const [nameValue] = String(cookie).split(';');
|
||||
const eq = nameValue.indexOf('=');
|
||||
if (eq > 0) {
|
||||
const name = nameValue.substring(0, eq).trim();
|
||||
const value = nameValue.substring(eq + 1).trim();
|
||||
if (name) cookies[name] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
|
||||
function formatCookies(cookies) {
|
||||
return Object.entries(cookies)
|
||||
.map(([name, value]) => `${name}=${value}`)
|
||||
.join('; ');
|
||||
}
|
||||
|
||||
function getOrCreateSid(req) {
|
||||
return req.query.sid || crypto.randomBytes(16).toString('hex');
|
||||
}
|
||||
|
||||
/** Domains, deren Links durch den Proxy umgeleitet werden (für Folge-Logs) */
|
||||
const PROXY_DOMAINS = ['click-tt.de', 'httv.de'];
|
||||
|
||||
@@ -23,10 +53,18 @@ function shouldProxyUrl(href) {
|
||||
return PROXY_DOMAINS.some(d => h.includes(d));
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut die Proxy-URL inkl. Session-ID für Cookie-Weitergabe
|
||||
*/
|
||||
function buildProxyUrl(proxyBase, targetUrl, sid) {
|
||||
const sep = proxyBase.includes('?') ? '&' : '?';
|
||||
return `${proxyBase}${sep}url=${encodeURIComponent(targetUrl)}${sid ? `&sid=${sid}` : ''}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt Links im HTML um, sodass Klicks im iframe über unseren Proxy laufen (Folge-Logs).
|
||||
*/
|
||||
function rewriteLinksInHtml(html, proxyBaseUrl, pageBaseUrl) {
|
||||
function rewriteLinksInHtml(html, proxyBaseUrl, pageBaseUrl, sid) {
|
||||
if (!html || !proxyBaseUrl || !pageBaseUrl) return html;
|
||||
try {
|
||||
const base = new URL(pageBaseUrl);
|
||||
@@ -38,7 +76,7 @@ function rewriteLinksInHtml(html, proxyBaseUrl, pageBaseUrl) {
|
||||
absoluteUrl = new URL(href, base.origin + base.pathname).href;
|
||||
}
|
||||
if (!shouldProxyUrl(absoluteUrl)) return match;
|
||||
const proxyUrl = `${proxyBaseUrl}${proxyBaseUrl.includes('?') ? '&' : '?'}url=${encodeURIComponent(absoluteUrl)}`;
|
||||
const proxyUrl = buildProxyUrl(proxyBaseUrl, absoluteUrl, sid);
|
||||
return `<a ${before}href="${proxyUrl}"${after}>`;
|
||||
}
|
||||
);
|
||||
@@ -50,7 +88,7 @@ function rewriteLinksInHtml(html, proxyBaseUrl, pageBaseUrl) {
|
||||
/**
|
||||
* Schreibt Formular-Actions um, sodass Submissions über unseren Proxy laufen (Login etc. wird geloggt).
|
||||
*/
|
||||
function rewriteFormActionsInHtml(html, proxyBaseUrl, pageBaseUrl) {
|
||||
function rewriteFormActionsInHtml(html, proxyBaseUrl, pageBaseUrl, sid) {
|
||||
if (!html || !proxyBaseUrl || !pageBaseUrl) return html;
|
||||
try {
|
||||
const base = new URL(pageBaseUrl);
|
||||
@@ -64,7 +102,7 @@ function rewriteFormActionsInHtml(html, proxyBaseUrl, pageBaseUrl) {
|
||||
absoluteUrl = new URL(actionTrim, base.origin + base.pathname).href;
|
||||
}
|
||||
if (!shouldProxyUrl(absoluteUrl)) return match;
|
||||
const proxyUrl = `${proxyBaseUrl}${proxyBaseUrl.includes('?') ? '&' : '?'}url=${encodeURIComponent(absoluteUrl)}`;
|
||||
const proxyUrl = buildProxyUrl(proxyBaseUrl, absoluteUrl, sid);
|
||||
return `<form ${before}action="${proxyUrl}"${after}>`;
|
||||
}
|
||||
);
|
||||
@@ -82,6 +120,9 @@ function rewriteFormActionsInHtml(html, proxyBaseUrl, pageBaseUrl) {
|
||||
*/
|
||||
router.get('/proxy', async (req, res, next) => {
|
||||
try {
|
||||
const sid = getOrCreateSid(req);
|
||||
const cookies = cookieStore.get(sid) || {};
|
||||
|
||||
const { type, association, championship, clubId, url } = req.query;
|
||||
let targetUrl = null;
|
||||
let fetchType = 'proxy';
|
||||
@@ -121,8 +162,21 @@ router.get('/proxy', async (req, res, next) => {
|
||||
championship: championship || null,
|
||||
clubIdParam: clubId || null,
|
||||
userId: null,
|
||||
headers: Object.keys(cookies).length > 0 ? { Cookie: formatCookies(cookies) } : {},
|
||||
});
|
||||
|
||||
const responseHeaders = result.headers;
|
||||
if (responseHeaders) {
|
||||
const raw = responseHeaders.raw?.();
|
||||
const setCookies = raw?.['set-cookie'];
|
||||
if (setCookies) {
|
||||
const newCookies = extractCookies(setCookies);
|
||||
if (Object.keys(newCookies).length > 0) {
|
||||
cookieStore.set(sid, { ...cookies, ...newCookies });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let html = result.body;
|
||||
// CSP/X-Frame-Options entfernen für iframe-Einbettung
|
||||
html = (html || '')
|
||||
@@ -142,8 +196,8 @@ router.get('/proxy', async (req, res, next) => {
|
||||
const baseUrl = process.env.BACKEND_BASE_URL || process.env.BASE_URL
|
||||
|| `${req.protocol || 'http'}://${req.get('host') || 'localhost:' + (process.env.PORT || 3005)}`;
|
||||
const proxyBase = baseUrl.replace(/\/$/, '') + '/api/clicktt/proxy';
|
||||
html = rewriteLinksInHtml(html, proxyBase, targetUrl);
|
||||
html = rewriteFormActionsInHtml(html, proxyBase, targetUrl);
|
||||
html = rewriteLinksInHtml(html, proxyBase, targetUrl, sid);
|
||||
html = rewriteFormActionsInHtml(html, proxyBase, targetUrl, sid);
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
@@ -168,13 +222,15 @@ router.get('/proxy', async (req, res, next) => {
|
||||
*/
|
||||
router.post('/proxy', async (req, res, next) => {
|
||||
try {
|
||||
const sid = getOrCreateSid(req);
|
||||
const cookies = cookieStore.get(sid) || {};
|
||||
|
||||
const targetUrl = req.query.url;
|
||||
if (!targetUrl || (!targetUrl.includes('click-tt.de') && !targetUrl.includes('httv.de'))) {
|
||||
return res.status(400).send('<html><body><h1>Fehler</h1><p>Parameter url (click-tt.de oder httv.de) erforderlich.</p></body></html>');
|
||||
}
|
||||
|
||||
const contentType = req.get('content-type') || 'application/x-www-form-urlencoded';
|
||||
const cookie = req.get('cookie');
|
||||
const body = typeof req.body === 'string' ? req.body : (req.body && Object.keys(req.body).length
|
||||
? new URLSearchParams(req.body).toString()
|
||||
: null);
|
||||
@@ -186,7 +242,7 @@ router.post('/proxy', async (req, res, next) => {
|
||||
body: body || undefined,
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
...(cookie && { Cookie: cookie }),
|
||||
...(Object.keys(cookies).length > 0 && { Cookie: formatCookies(cookies) }),
|
||||
},
|
||||
userId: null,
|
||||
});
|
||||
@@ -196,6 +252,10 @@ router.post('/proxy', async (req, res, next) => {
|
||||
const raw = responseHeaders.raw?.();
|
||||
const setCookies = raw?.['set-cookie'] ?? responseHeaders.get?.('set-cookie') ?? responseHeaders['set-cookie'];
|
||||
if (setCookies) {
|
||||
const newCookies = extractCookies(setCookies);
|
||||
if (Object.keys(newCookies).length > 0) {
|
||||
cookieStore.set(sid, { ...cookies, ...newCookies });
|
||||
}
|
||||
(Array.isArray(setCookies) ? setCookies : [setCookies]).forEach(c => res.append('Set-Cookie', c));
|
||||
}
|
||||
const location = responseHeaders.get?.('location') ?? responseHeaders['location'];
|
||||
@@ -204,7 +264,7 @@ router.post('/proxy', async (req, res, next) => {
|
||||
|| `${req.protocol || 'http'}://${req.get('host') || 'localhost:' + (process.env.PORT || 3005)}`;
|
||||
const proxyBase = baseUrl.replace(/\/$/, '') + '/api/clicktt/proxy';
|
||||
if (shouldProxyUrl(location)) {
|
||||
res.redirect(302, `${proxyBase}?url=${encodeURIComponent(location)}`);
|
||||
res.redirect(302, buildProxyUrl(proxyBase, location, sid));
|
||||
return;
|
||||
}
|
||||
res.redirect(result.status, location);
|
||||
@@ -212,6 +272,24 @@ router.post('/proxy', async (req, res, next) => {
|
||||
}
|
||||
}
|
||||
|
||||
let body = result.body;
|
||||
const contentType = result.contentType || '';
|
||||
if (body && contentType.includes('text/html')) {
|
||||
const baseUrl = process.env.BACKEND_BASE_URL || process.env.BASE_URL
|
||||
|| `${req.protocol || 'http'}://${req.get('host') || 'localhost:' + (process.env.PORT || 3005)}`;
|
||||
const proxyBase = baseUrl.replace(/\/$/, '') + '/api/clicktt/proxy';
|
||||
body = body
|
||||
.replace(/<meta[^>]*http-equiv=["']content-security-policy["'][^>]*>/gi, '')
|
||||
.replace(/<meta[^>]*http-equiv=["']x-frame-options["'][^>]*>/gi, '');
|
||||
const pageOrigin = (() => { try { return new URL(targetUrl).origin + '/'; } catch { return null; } })();
|
||||
if (pageOrigin) {
|
||||
body = body.replace(/<base[^>]*>/gi, '');
|
||||
body = body.replace(/<head([^>]*)>/i, `<head$1><base href="${pageOrigin}">`);
|
||||
}
|
||||
body = rewriteLinksInHtml(body, proxyBase, targetUrl, sid);
|
||||
body = rewriteFormActionsInHtml(body, proxyBase, targetUrl, sid);
|
||||
}
|
||||
|
||||
res.set({
|
||||
'Content-Type': result.contentType || 'text/html; charset=utf-8',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
@@ -219,7 +297,7 @@ router.post('/proxy', async (req, res, next) => {
|
||||
'Content-Security-Policy': 'frame-ancestors *;',
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
});
|
||||
res.status(result.status).send(result.body);
|
||||
res.status(result.status).send(body);
|
||||
} catch (error) {
|
||||
console.error('ClickTT Proxy POST Fehler:', error);
|
||||
res.status(500).send(
|
||||
|
||||
Reference in New Issue
Block a user