admin hardening - #126
Conversation
|
@Jumongweb fix your CI |
EmeditWeb
left a comment
There was a problem hiding this comment.
❌ Automated Audit: does_not_solve
The PR builds a competent DB-backed AdminGuard, but the diff never modifies any @UseGuards decorator: per the issue, audit.controller.ts line 12 was @UseGuards(JwtAuthGuard) and no hunk touches it, so the new guard is imported and registered in the module yet never applied to any route — providing a guard in NestJS providers does not activate it, leaving GET /admin/audit-logs readable by any authenticated wallet. The unit tests invoke guard.canActivate() directly, bypassing the HTTP stack, so they would stay green even with the guard unwired (violating the standard that regression tests fail on pre-fix code), and only one controller file is touched despite the issue requiring coverage of the entire /admin tree. Additionally, denied attempts are emitted via a Logger masquerading under the name 'AuditInterceptor' rather than persisted through the audit pipeline.
Gaps identified:
- No @UseGuards(JwtAuthGuard, AdminGuard) change on audit.controller.ts (or APP_GUARD registration); the guard is dead code on the request path
- Guard not demonstrably applied across the whole /admin controller tree
- No route-level/integration tests proving 403/401/200 over HTTP; existing unit tests cannot detect the unwired-guard failure mode
- Denied attempts only logged (with a spoofed 'AuditInterceptor' logger context), not recorded via the audit service/trail
- Migration and DTO fold 'admin' into the self-service one-time role-choice vocabulary (PATCH /users/me/role) with no shown server-side block preventing a user from self-selecting admin — potential privilege-escalation path; no admin provisioning mechanism defined
Audited by stepfi-audit-bot 🤖
Closes #115
🔖 Title
Harden admin audit-log access with server-side authorization
📝 Description
This PR closes a critical authorization gap on the
/admincontroller tree. It adds a server-sideAdminGuardthat checks the latestusersrow from Supabase on every request, so access decisions no longer rely only on JWT claims.🔄 Changes Made
src/modules/admin/admin.guard.tsto fetchroleandstatusfresh from Supabase and deny by default unless the user is an active admin.GET /admin/audit-logsand documented the 401/403 responses in Swagger.users.roleconstraint to allow a realadminvalue in the database and updated the user profile DTO accordingly.🗒️ Additional Notes
The guard intentionally trusts the database over the JWT role claim so role grants and revocations take effect immediately on the next request, even before token refresh.