From df8b952b81cd5f2fa7d5358adfc56a6ffcab9a00 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 04:05:17 +0000 Subject: [PATCH] Alte Mail-Links aufs Admin-Panel leiten ins Ticketsystem (Antrag direkt geoeffnet) Co-Authored-By: Claude Opus 5.5 --- server/index.js | 4 ++++ server/routes/legacyAdminRedirect.js | 21 +++++++++++++++++++++ test/adminPanelGone.test.js | 23 ++++++++++++++++++++++- 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 server/routes/legacyAdminRedirect.js diff --git a/server/index.js b/server/index.js index 85a7e61..53ff4fb 100644 --- a/server/index.js +++ b/server/index.js @@ -38,6 +38,7 @@ import adminAnalyseRoutes from './routes/admin/analyse.js' import { analyseLegalRouter } from './services/analyseLegal.js' import adminFinanzenRoutes from './routes/admin/finanzen.js' import teamRoutes from './routes/team/index.js' +import legacyAdminRedirect from './routes/legacyAdminRedirect.js' import { corsMiddleware } from './middleware/cors.js' import { kundenansichtGuard } from './middleware/kundenansichtGuard.js' @@ -104,6 +105,9 @@ app.use('/embed/', (_req, res, next) => { }) // Adressen ohne Fassung: aktuelle geprüfte Fassung der Analyse-Rechtstexte (vor express.static) app.use('/rechtliches', analyseLegalRouter()) +// Abgeschaltetes Admin-Panel und Rechnungsanalyse: Links aus alten Team-Mails ins Ticketsystem +app.use('/admin.html', legacyAdminRedirect.admin) +app.use('/kalkulation.html', legacyAdminRedirect.kalkulation) app.use(express.static(publicDir)) app.get('/dashboard.html', (req, res, next) => { diff --git a/server/routes/legacyAdminRedirect.js b/server/routes/legacyAdminRedirect.js new file mode 100644 index 0000000..2d0c803 --- /dev/null +++ b/server/routes/legacyAdminRedirect.js @@ -0,0 +1,21 @@ +import { config } from '../config.js' + +/** + * Admin-Panel (/admin.html) und Rechnungsanalyse (/kalkulation.html) sind abgeschaltet (Etappe 4). Links aus + * aelteren Team-Mails fuehren ins Ticketsystem; /admin.html?gutschrift= oeffnet den Antrag unter + * Kunden -> Gutschriften. + */ +const ANTRAG_ID = /^[A-Za-z0-9._-]{1,64}$/ + +const toTicketsystem = (pathFor) => (req, res, next) => { + if (req.method !== 'GET' && req.method !== 'HEAD') return next() + return res.redirect(302, `${config.womsBaseUrl}${pathFor(req)}`) +} + +export default { + admin: toTicketsystem((req) => { + const antrag = typeof req.query.gutschrift === 'string' ? req.query.gutschrift : '' + return ANTRAG_ID.test(antrag) ? `/customers/gutschriften?antrag=${encodeURIComponent(antrag)}` : '/customers' + }), + kalkulation: toTicketsystem(() => '/customers'), +} diff --git a/test/adminPanelGone.test.js b/test/adminPanelGone.test.js index 81c9112..da5b151 100644 --- a/test/adminPanelGone.test.js +++ b/test/adminPanelGone.test.js @@ -15,12 +15,13 @@ const { config } = await import('../server/config.js') const { startTestApp } = await import('./support/testApp.js') const { viewAsCookie } = await import('./support/fixtures.js') const { kundenansichtGuard } = await import('../server/middleware/kundenansichtGuard.js') +const { default: legacyAdminRedirect } = await import('../server/routes/legacyAdminRedirect.js') // Hinter dem Waechter: eine Lese- und eine Schreib-Route, die selbst immer 200 liefern const probe = Router() probe.get('/', (_req, res) => res.json({ ok: true })) probe.post('/', (_req, res) => res.json({ ok: true })) -const app = await startTestApp({ '/': kundenansichtGuard, '/api/projects': probe }) +const app = await startTestApp({ '/': kundenansichtGuard, '/api/projects': probe, '/admin.html': legacyAdminRedirect.admin, '/kalkulation.html': legacyAdminRedirect.kalkulation }) after(() => Promise.all([app.close(), aw.close(), st.close()])) const root = new URL('..', import.meta.url).pathname @@ -51,9 +52,14 @@ test('Admin-Panel und alte Admin-Schnittstellen sind weg', () => { }) test('keine Verweise mehr auf admin.html, kalkulation.html oder /api/portal-admin in server/ und public/', () => { + // einzige Ausnahme: die Weiterleitung alter Mail-Links ins Ticketsystem (Modul und seine zwei Mounts) + const allowed = [path.join(root, 'server/routes/legacyAdminRedirect.js')] + const allowedLines = ["app.use('/admin.html', legacyAdminRedirect.admin)", "app.use('/kalkulation.html', legacyAdminRedirect.kalkulation)"] const hits = [] for (const file of [...filesUnder(path.join(root, 'server')), ...filesUnder(path.join(root, 'public'))]) { + if (allowed.includes(file)) continue readFileSync(file, 'utf8').split('\n').forEach((line, i) => { + if (allowedLines.includes(line.trim())) return if (/admin\.html|kalkulation\.html|\/api\/portal-admin/.test(line)) hits.push(`${path.relative(root, file)}:${i + 1}`) }) } @@ -73,3 +79,18 @@ test('Alt-Ansicht ohne Kennung: Lesen und Schreiben 401 view_expired, Cookies ge assert.ok(clears(res, 'preview_session'), `${method}: Preview-Cookie geloescht`) } }) + +test('alte Mail-Links (/admin.html, /kalkulation.html) leiten ins Ticketsystem, Antrag direkt geoeffnet', async () => { + const location = async (p) => { + const res = await fetch(`${app.url}${p}`, { redirect: 'manual' }) + assert.equal(res.status, 302, p) + return res.headers.get('location') + } + assert.equal(await location('/admin.html?gutschrift=6f1c2d3e-aaaa-4bbb-8ccc-123456789abc'), 'https://ticket.webklar.com/customers/gutschriften?antrag=6f1c2d3e-aaaa-4bbb-8ccc-123456789abc') + assert.equal(await location('/admin.html'), 'https://ticket.webklar.com/customers') + assert.equal(await location('/admin.html?gutschrift=%3Cscript%3E'), 'https://ticket.webklar.com/customers') + assert.equal(await location('/kalkulation.html'), 'https://ticket.webklar.com/customers') + const index = readFileSync(path.join(root, 'server/index.js'), 'utf8') + assert.ok(index.includes("app.use('/admin.html', legacyAdminRedirect.admin)"), 'in server/index.js eingehaengt') + assert.ok(index.indexOf("app.use('/admin.html'") < index.indexOf('express.static(publicDir)'), 'vor den statischen Dateien') +})