test(qa): admin/status authorization probe + B10/B11/B12 coverage notes
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
3c1d000bab
commit
ad474f1ac1
|
|
@ -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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| B15 | Regression & sign-off | full smoke on **production build**, exit criteria | seeded | 🔄 | 0 / 0 |
|
||||||
|
|
|
||||||
|
|
@ -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);
|
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 }) => {
|
test('bad / nonexistent id — structured, not a 500 (B13)', async ({ request }) => {
|
||||||
for (const id of ['99999999', 'not-a-number', '0', '-1']) {
|
for (const id of ['99999999', 'not-a-number', '0', '-1']) {
|
||||||
const res = await request.get(`/api/bills/${id}`);
|
const res = await request.get(`/api/bills/${id}`);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue