// @ts-check // Fix #200: Mehrere SettingsController-Endpoints (Logo, LLM-Test, // CSV-Bankimport-Formate) prüften Auth::requireAdmin() nicht, obwohl der // Bereich laut Code-Kommentar bei update() bewusst admin-pflichtig sein // soll. Ein normaler "user"-Account konnte damit Firmenlogo löschen, // Bankimport-Formate anlegen/ändern/löschen und den LLM-Test auslösen. // Dieser Test prüft die Durchsetzung auf HTTP-Ebene, die die PHP-Suite // nicht erreicht (Router-Guard, Auth::requireAdmin()). const { test, expect } = require('@playwright/test') const { uniqueTag } = require('../helpers/app') test.describe('Autorisierung: Settings-Verwaltungsendpoints nur für Admins (Fix #200)', () => { test('Nicht-Admin wird von Logo-, Bankimport- und LLM-Test-Endpoints ausgesperrt', async ({ page, browser }) => { const username = uniqueTag('noadmin').toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 64) const password = 'TempPass123!' await page.goto('/useradmin') await page.fill('#field-username', username) await page.fill('#field-password', password) await page.click('#submit-btn') await page.waitForURL('/useradmin') const row = page.locator('tr', { hasText: username }) await expect(row).toBeVisible() try { const context = await browser.newContext({ storageState: { cookies: [], origins: [] } }) const userPage = await context.newPage() await userPage.goto('/login') await userPage.fill('#username', username) await userPage.fill('#password', password) await userPage.click('form[action="/login"] button[type="submit"]') await userPage.waitForURL(url => !url.pathname.startsWith('/login')) await userPage.goto('/settings') const csrfToken = await userPage.evaluate(() => document.querySelector('meta[name="csrf-token"]')?.content || '') expect(csrfToken).not.toBe('') const headers = { 'X-CSRF-Token': csrfToken } const bankImportGet = await userPage.request.get('/settings/bankimport') expect(bankImportGet.status()).toBe(403) const deleteLogo = await userPage.request.post('/settings/logo/delete', { headers, form: {} }) expect(deleteLogo.status()).toBe(403) const llmTest = await userPage.request.post('/settings/llm-test', { headers, form: {} }) expect(llmTest.status()).toBe(403) const createFormat = await userPage.request.post('/settings/bankimport/create', { headers, form: { name: 'X', kuerzel: 'X' }, }) expect(createFormat.status()).toBe(403) const suggest = await userPage.request.post('/settings/bankimport/suggest', { headers, data: {}, }) expect(suggest.status()).toBe(403) const dismiss = await userPage.request.post('/settings/bankimport/dismiss-suggestion', { headers, form: {} }) expect(dismiss.status()).toBe(403) const updateField = await userPage.request.post('/settings/bankimport/1/field', { headers, form: { field: 'name', value: 'X' }, }) expect(updateField.status()).toBe(403) const deleteFormat = await userPage.request.post('/settings/bankimport/1/delete', { headers, form: {} }) expect(deleteFormat.status()).toBe(403) for (const response of [bankImportGet, deleteLogo, llmTest, createFormat, suggest, dismiss, updateField, deleteFormat]) { await expect(response.text()).resolves.toContain('Kein Zugriff') } await context.close() } finally { const delForm = row.locator('form[action$="/delete"]') if (await delForm.count() > 0) { await delForm.locator('button[type="submit"]').click() await page.waitForLoadState('networkidle') } } }) test('Admin darf /settings/bankimport weiterhin aufrufen (kein 403)', async ({ page }) => { const response = await page.goto('/settings/bankimport') expect(response.status()).not.toBe(403) }) })