From ad474f1ac117f391b6be9dd96da12d63f21d41d6 Mon Sep 17 00:00:00 2001 From: null Date: Thu, 2 Jul 2026 21:48:05 -0500 Subject: [PATCH] test(qa): admin/status authorization probe + B10/B11/B12 coverage notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - api.probe: assert a regular user is 403 on /api/admin/*, /api/status, /api/about-admin (read + write) — B1/B11 authorization - confirmed (static): settings PUT whitelists USER_SETTING_KEYS (no mass-assignment), notifications route splits requireAdmin/requireUser - docs: mark B10/B11/B12 probed Co-Authored-By: Claude Opus 4.8 --- docs/QA_PLAN.md | 6 +++--- e2e/api.probe.spec.js | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/QA_PLAN.md b/docs/QA_PLAN.md index b2e184d..3df346e 100644 --- a/docs/QA_PLAN.md +++ b/docs/QA_PLAN.md @@ -97,9 +97,9 @@ before cross-cutting; regression last). Update **Status** and **Findings** every | B7 | Debt planning (math) | `/snowball`, `/payoff` APR/amortization vs hand-calc | edge (APR=0, $0 debt) | 🔄 | 0 / 2 | | B8 | Banking & bank sync | `/bank-transactions`, SimpleFIN sync, matching, merchant/store, advisory filter | seeded txns | ⬜ | 0 / 0 | | B9 | Data lifecycle | `/data` import (XLSX/CSV/SQLite), export, ICS feed, backups round-trip | empty + seeded | 🔄 | 0 / 1 | -| B10 | Notifications & workers | email + ntfy/Gotify/Discord/Telegram, reminders, cron workers | seeded | ⬜ | 0 / 0 | -| B11 | Admin panel | users, login mode, auth methods, backups, cleanup, status, onboarding | admin | ⬜ | 0 / 0 | -| B12 | Settings, Profile & global UI | `/settings`, `/profile`, static pages, command palette, sidebar/nav | any | ⬜ | 0 / 0 | +| B10 | Notifications & workers | email + ntfy/Gotify/Discord/Telegram, reminders, cron workers | seeded | 🔄 | 0 / 0 | +| B11 | Admin panel | users, login mode, auth methods, backups, cleanup, status, onboarding | admin | 🔄 | 0 / 0 | +| B12 | Settings, Profile & global UI | `/settings`, `/profile`, static pages, command palette, sidebar/nav | any | 🔄 | 0 / 0 | | B13 | API / backend direct | all `/api/*`: auth, CSRF, validation, rate limits, error shape, IDOR, cents | via HTTP client | 🔄 | 0 / 1 | | B14 | Non-functional | a11y, performance, PWA/offline, XSS/secrets, timezone/DST | large + adversarial | 🔄 | 0 / 3 | | B15 | Regression & sign-off | full smoke on **production build**, exit criteria | seeded | 🔄 | 0 / 0 | diff --git a/e2e/api.probe.spec.js b/e2e/api.probe.spec.js index b60b711..b1d51b4 100644 --- a/e2e/api.probe.spec.js +++ b/e2e/api.probe.spec.js @@ -71,6 +71,23 @@ test('data-isolation — cannot touch another user\'s bill (B1/IDOR)', async ({ expect.soft(del.status(), 'DELETE user B bill must be blocked (>=400)').toBeGreaterThanOrEqual(400); }); +test('authorization — a regular user is blocked from admin + status APIs (B1/B11)', async ({ request }) => { + const token = await csrf(request); + // Read endpoints → 403 (requireAdmin); user A is a regular 'user'. + for (const url of ['/api/admin/users', '/api/admin/has-users', '/api/status', '/api/about-admin']) { + const res = await request.get(url); + console.log(`[authz] GET ${url} -> ${res.status()}`); + expect.soft(res.status(), `${url} must be admin-only (403)`).toBe(403); + } + // Write endpoint → 403 too (not a silent success). + const create = await request.post('/api/admin/users', { + headers: { 'x-csrf-token': token }, + data: { username: 'sneaky_admin', password: 'password123', role: 'admin' }, + }); + console.log(`[authz] POST /api/admin/users -> ${create.status()}`); + expect.soft(create.status(), 'creating a user as a non-admin must be 403').toBe(403); +}); + test('bad / nonexistent id — structured, not a 500 (B13)', async ({ request }) => { for (const id of ['99999999', 'not-a-number', '0', '-1']) { const res = await request.get(`/api/bills/${id}`);