Files
Webklar-Kundenbereich/test/vorschauLink.test.js
T
ClaudeandClaude Opus 5.5 2873638b1c Geheimer Vorschau-Link: /vorschau/<subdomain>/<token> setzt ein Cookie nur fuer diese Preview
- websiteProjects.shareToken (scripts/vorschau-link-setup.mjs)
- POST/DELETE /api/admin/website-projects/:id/vorschau-link fuer Mitarbeiter (neu erzeugen / abschalten)
- GET /api/vorschau-link/pruefen fuer die Login-Pruefung der Previews (preview-portal /auth/verify)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 17:59:23 +00:00

125 lines
5.4 KiB
JavaScript

// Geheimer Vorschau-Link: Link erzeugen, einloesen (Cookie + Weiterleitung), pruefen, abschalten
import { test, before, after } from 'node:test'
import assert from 'node:assert/strict'
import express from 'express'
import { startFakeAppwrite } from './helpers/fakeAppwrite.js'
const ready = { subdomain: 'kunde-a', projectType: 'preview', provisioningStatus: 'deployed', customerId: 'c1' }
let aw, server, baseUrl, svc
before(async () => {
aw = await startFakeAppwrite({
seed: {
websiteProjects: [
{ $id: 'p1', ...ready },
{ $id: 'p2', ...ready, subdomain: 'baut-noch', provisioningStatus: 'pending', shareToken: 'Pending_Token_123456789' },
{ $id: 'p3', projectName: 'ohne Subdomain', subdomain: '' },
{ $id: 'p4', ...ready, subdomain: 'archiv', archived: true, shareToken: 'Archiv_Token_1234567890' },
],
},
})
Object.assign(process.env, { APPWRITE_ENDPOINT: aw.endpoint, APPWRITE_API_KEY: 'test-key', PREVIEW_SESSION_SECRET: 'x'.repeat(40) })
svc = await import('../server/services/vorschauLink.js')
const { vorschauLinkSeite, vorschauLinkPruefung } = await import('../server/routes/vorschauLink.js')
const app = express()
app.use('/vorschau', vorschauLinkSeite())
app.use('/api/vorschau-link', vorschauLinkPruefung())
server = await new Promise((r) => { const s = app.listen(0, '127.0.0.1', () => r(s)) })
baseUrl = `http://127.0.0.1:${server.address().port}`
})
after(async () => {
server.closeAllConnections()
await new Promise((r) => server.close(r))
await aw.close()
})
const doc = (id) => aw.all('websiteProjects').find((d) => d.$id === id)
const pruefen = (sub, token) =>
fetch(`${baseUrl}/api/vorschau-link/pruefen?subdomain=${sub}`, { headers: token ? { 'x-vorschau-token': token } : {} })
test('Token: zufaellig, URL-tauglich, lang genug', () => {
const a = svc.newShareToken()
assert.match(a, /^[A-Za-z0-9_-]{24}$/)
assert.notEqual(a, svc.newShareToken())
assert.equal(svc.cleanToken(a), a)
assert.equal(svc.cleanToken('kurz'), '')
assert.equal(svc.cleanToken('../../etc/passwd/xxxxxxxxxxxxxxx'), '')
assert.equal(svc.cleanSubdomain('Kunde-A'), 'kunde-a')
assert.equal(svc.cleanSubdomain('-x'), '')
assert.equal(svc.cleanSubdomain('a.b'), '')
})
test('Link erzeugen speichert den Token und baut die URL', async () => {
const r = await svc.renewShareLink('p1')
assert.equal(doc('p1').shareToken, r.shareToken)
assert.equal(r.url, `https://project.webklar.com/vorschau/kunde-a/${r.shareToken}`)
})
test('Ohne Subdomain gibt es keinen Link (409), unbekanntes Projekt 404', async () => {
await assert.rejects(svc.renewShareLink('p3'), (e) => e.status === 409)
await assert.rejects(svc.renewShareLink('gibt-es-nicht'), (e) => e.status === 404)
})
test('Link einloesen: Cookie nur fuer diese Subdomain, Weiterleitung auf die Preview', async () => {
const { shareToken } = await svc.renewShareLink('p1')
const res = await fetch(`${baseUrl}/vorschau/kunde-a/${shareToken}`, { redirect: 'manual' })
assert.equal(res.status, 302)
assert.equal(res.headers.get('location'), 'https://kunde-a.project.webklar.com/')
const cookie = res.headers.get('set-cookie')
assert.match(cookie, new RegExp(`^wk_vorschau_kunde-a=${shareToken};`))
assert.match(cookie, /Domain=\.project\.webklar\.com/)
assert.match(cookie, /HttpOnly/)
assert.match(cookie, /SameSite=Lax/)
assert.equal(res.headers.get('cache-control'), 'no-store')
})
test('Falscher Token, fremde Subdomain oder Muell: 404-Seite ohne Cookie', async () => {
const { shareToken } = await svc.renewShareLink('p1')
for (const path of [
`/vorschau/kunde-a/${shareToken.slice(0, -1)}X`,
`/vorschau/archiv/${shareToken}`,
`/vorschau/kunde-a/kurz`,
`/vorschau/KUNDE-A.evil/${shareToken}`,
]) {
const res = await fetch(baseUrl + path, { redirect: 'manual' })
assert.equal(res.status, 404, path)
assert.equal(res.headers.get('set-cookie'), null, path)
assert.match(await res.text(), /nicht \(mehr\) gültig/)
}
})
test('Archiviertes Projekt: Link gilt nicht', async () => {
const res = await pruefen('archiv', 'Archiv_Token_1234567890')
assert.equal(res.status, 403)
})
test('Preview noch nicht fertig: 503-Hinweis, Pruefung sagt not_ready', async () => {
const res = await fetch(`${baseUrl}/vorschau/baut-noch/Pending_Token_123456789`, { redirect: 'manual' })
assert.equal(res.status, 503)
assert.equal(res.headers.get('set-cookie'), null)
const p = await pruefen('baut-noch', 'Pending_Token_123456789')
assert.deepEqual(await p.json(), { allowed: false, reason: 'not_ready' })
})
test('Pruefung: richtiger Token 200, sonst 403', async () => {
const { shareToken } = await svc.renewShareLink('p1')
let res = await pruefen('kunde-a', shareToken)
assert.equal(res.status, 200)
assert.equal((await res.json()).allowed, true)
res = await pruefen('kunde-a', svc.newShareToken())
assert.equal(res.status, 403)
res = await pruefen('kunde-a', '')
assert.equal(res.status, 403)
})
test('Neuer Link oder Abschalten macht den alten sofort ungueltig', async () => {
const alt = (await svc.renewShareLink('p1')).shareToken
assert.equal((await pruefen('kunde-a', alt)).status, 200)
const neu = (await svc.renewShareLink('p1')).shareToken
assert.equal((await pruefen('kunde-a', alt)).status, 403)
assert.equal((await pruefen('kunde-a', neu)).status, 200)
await svc.removeShareLink('p1')
assert.equal(doc('p1').shareToken, '')
assert.equal((await pruefen('kunde-a', neu)).status, 403)
})