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

6.7 KiB
Raw Permalink Blame History

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.