webappAIO/VERIFICATION.md
2026-08-04 11:26:25 +03:00

50 lines
6.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 7-Point MCP Verification — Foundations + Users Management Module
Per `claude-project-prompt.md`'s `<mcp_rule>`, run after this implementation batch (plan: `imperative-toasting-tome.md`).
## 1. Architecture consistency
`backend/` is API-only (`routes/api.php`, versioned under `/v1`) — no server-rendered views for app data. `frontend/` is a Vite React SPA consuming it exclusively over `/api/v1/*` and `/sanctum/*`. Business logic lives in `app/Actions/*` (Login, VerifyTwoFactor, CreateUser, UpdateUser, DeleteUser, AssignRole), not in controllers. Controllers stay thin (validate via Form Requests → call an Action → fire an event → return a Resource).
## 2. Mobile-first compliance
`MobileShell` (`frontend/src/layouts/shell/MobileShell.tsx`) — top bar + bottom nav — and `DesktopShell` were built together in the same pass (task 7), not desktop-first with mobile bolted on. `AppShell` picks between them via `useIsMobile()` at a single decision point. Verified in-browser both directions: resizing/reloading at 375×812 renders `MobileShell`, at 1280×720 renders `DesktopShell`, with identical data and functionality (login, Users CRUD, dashboard widgets) in both.
## 3. Separate mobile/desktop view integrity
- `layouts/shell/{MobileShell,DesktopShell}.tsx` — distinct files, shared only via the `Outlet` contract.
- `features/users/{UsersListPage.tsx, UsersListPage.mobile.tsx}` — desktop uses a sortable/paginated `DataTable`; mobile uses a card list. Both share `useUsersRealtime()`, `usersApi.ts`, `UserFormModal`, `AssignRoleModal` — logic shared, markup separate, per the spec's explicit instruction.
- `UserFormModal` renders as a centered `Modal` on desktop and a bottom `Sheet` on mobile (same `UserFormFields` inside either).
## 4. Security compliance
- **CSRF**: Sanctum SPA cookie auth + `X-XSRF-TOKEN`; verified a stale token gets a 419, a fresh one succeeds.
- **Rate limiting**: login throttled 5/min/IP+identifier (verified: 6th attempt in a minute returns "Too many login attempts"); general API throttle 60/min (`RateLimiter::for('api', ...)`).
- **No user enumeration**: invalid login always returns the same generic message.
- **2FA**: TOTP (`pragmarx/google2fa`) + 10 single-use hashed recovery codes; verified full browser flow (enable → QR → confirm → recovery-code login) and that a used recovery code is consumed.
- **RBAC enforcement, 4 layers**: route middleware, `UserPolicy` (view/create/update/delete), API Resource `can_edit`/`can_delete` flags, frontend `usePermission()` nav/button gating. Verified a `user`-role account cannot see the Users nav item or any gated buttons; a `super-admin` can.
- **Self-delete guard**: `UserPolicy::delete` blocks it — **and this caught a real bug**: the `Gate::before` super-admin bypass initially overrode that guard, letting the Super Admin soft-delete their own account. Fixed by special-casing self-delete inside `Gate::before` itself (`app/Providers/AppServiceProvider.php`) so it applies even to the bypass; re-verified 403 after the fix, account restored.
- **Secure headers**: `SecurityHeaders` middleware (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, HSTS-on-HTTPS) appended to the `api` middleware group; verified present on successful `/api/v1/*` responses.
- **CORS**: `supports_credentials: true`, `allowed_origins` restricted to `FRONTEND_URL` (no wildcard).
- **Audit logging**: covers login, logout, 2FA enable/verify/disable, and every Users CRUD + role-assign action — verified via the dashboard's Recent Activity feed and directly via `/api/v1/dashboard/activity`.
- **Secrets**: DB/MQTT/Super-Admin credentials all via `.env`, generated with `openssl rand`, never hardcoded; Mosquitto `passwd`/`credentials.env` git-ignored.
- **File-upload hardening**: `FileUploadValidator` scaffolded (mime-sniffing, size cap, randomized filenames) ahead of any actual upload feature.
## 5. RBAC/permissions compliance
Roles `super-admin`/`admin`/`manager`/`user` seeded with `{module}.{action}`-named permissions. Verified via tinker that the seeded Super Admin holds all 8 permissions and the `Gate::before` bypass grants even an undefined permission. Verified via the Users module UI that role reassignment (manager → admin) immediately changes the permission set returned by the API and reflected in the UI. MQTT topic ACLs add a fifth enforcement layer (role-scoped topics — see point 6).
## 6. Real-time/MQTT compliance
Topic taxonomy (`private/user/{id}/notifications`, `role/{role}/alerts`, `module/{module}/events`, `broadcast/system`) enforced via Mosquitto's `acl.conf`. Verified end-to-end:
- `mosquitto_pub`/`sub` round-trip on both 1883 (MQTT) and confirmed 9001 (WebSocket) is reachable and used by `mqtt.js` in the browser.
- ACL denial verified directly: a `role_user` credential subscribes successfully (SUBACK) but receives zero messages published to `module/users/events`, which it isn't authorized for.
- Full Users CRUD cycle (create/update/assign-role/delete) each published a distinct MQTT event, captured live via `mosquitto_sub`.
- Browser-side: creating/editing/deleting a user through the UI produced a live toast and an up-to-date list/dashboard with **zero manual refresh**, via `useUsersRealtime()`/`useMqttTopic` invalidating the relevant react-query cache.
- Reconnect/token-refresh logic (`mqttClient.ts`) re-fetches a new broker credential on `close` before mqtt.js's automatic reconnect retries.
- **Documented limitation, not silently dropped**: per-role static Mosquitto credentials give role-level topic isolation only, not per-user; true per-user isolation needs a JWT-validating auth plugin (e.g. `mosquitto-go-auth`), deferred as an explicit follow-up per the approved plan.
## 7. Database portability
All migrations use the Eloquent schema builder exclusively — no raw SQL anywhere. `users` adds a `public_id` UUID (used for route-model binding and all API-facing IDs, never the numeric PK), soft deletes, and a self-referential `created_by` FK. `audit_logs` is append-only (`UPDATED_AT = null`, no soft deletes) with a polymorphic `auditable` relation. `spatie/laravel-permission` and Sanctum's own migrations are vendor-maintained and already Postgres-portable. Verified `migrate:fresh` succeeds cleanly against SQLite; nothing in the schema (indexes, FKs, JSON casts) is SQLite-specific.
---
## Known follow-ups (flagged, not silently dropped)
- Per-user MQTT topic isolation (JWT-based Mosquitto ACLs) — deferred per plan.
- Production TLS/reverse-proxy for the API and MQTT WebSocket listener — out of scope for this LXC-dev-focused pass, required before any non-local exposure.
- Frontend bundle exceeds Vite's 500KB chunk-size warning threshold (mqtt.js + react-query + axios); worth code-splitting later, not a functional issue now.