Adds the RBAC foundation the CRUD phases build on, and the first write module (users). The platform was read-only: every controller was guarded only by AuthenticatedGuard and UserRole was ADMIN|STAFF. The old PHP app stored level+role but enforced neither, so this is a fresh design. Permission model (server-authoritative): - UserRole expanded to an ordered rank ADMIN > MANAGER > STAFF > VIEWER. VIEWER is the read-only role; STAFF+ can write. - auth/abilities.ts: ROLE_RANK + ABILITY_MIN matrix + can()/abilitiesFor(). - @RequireAbility decorator + AbilityGuard enforce it on write routes; reads stay on AuthenticatedGuard so any logged-in user can read. - /auth/login and /auth/me now return the resolved abilities map, so the web gates its UI off one payload instead of duplicating the rules. User management (ADMIN-only, ability "user:manage"): - UsersService gains list/create/update/resetPassword (argon2), never returns passwordHash; blocks self-deactivation and self-demotion; maps duplicate email to 409. - UsersController: GET/POST /users, PATCH /users/:id, POST /users/:id/reset-password. - Every mutation logged via new AuditService over the existing ActivityLog model (global CommonModule). Web: - AuthContext + useAuth/useCan; AppShell provides the user and gates the new "Usuarios" nav entry on user:manage; shows the user's role. - /usuarios admin page: list + create/edit form + password reset + active toggle, Spanish-first, reusing existing card/table/field styles. Schema pushed to dev (enum only, non-destructive). Verified end-to-end against dev: admin CRUD works, VIEWER writes 403 while reads 200, self-lockout guards and duplicate-email 409 all hold. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
35 lines
1.0 KiB
TypeScript
35 lines
1.0 KiB
TypeScript
import { Injectable } from "@nestjs/common";
|
|
import { Prisma } from "@jorgecuadros/database";
|
|
import { PrismaService } from "../prisma/prisma.service";
|
|
|
|
/**
|
|
* Thin writer over the existing ActivityLog model. Every mutating route calls
|
|
* this so who-did-what is recorded — the structural replacement for the old
|
|
* PHP app's scattered Logger calls. Best-effort: a logging failure must never
|
|
* fail the underlying write, so callers `void audit.log(...)` without awaiting.
|
|
*/
|
|
@Injectable()
|
|
export class AuditService {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
async log(
|
|
userId: string | null | undefined,
|
|
event: string,
|
|
message?: Record<string, unknown>,
|
|
level = "info",
|
|
): Promise<void> {
|
|
try {
|
|
await this.prisma.activityLog.create({
|
|
data: {
|
|
userId: userId ?? undefined,
|
|
event,
|
|
level,
|
|
message: (message as Prisma.InputJsonValue) ?? undefined,
|
|
},
|
|
});
|
|
} catch {
|
|
/* never let audit logging break a real write */
|
|
}
|
|
}
|
|
}
|