CRUD de Usuários e Papéis (role weight, permissões, gitignore de worktrees)
- apps/api/src/users/: invite (senha temporária, mesma entropia do legado), PATCH (role/isActive, com checagem de peso), reset de senha administrativo, troca de senha self-service, grant/revoke de acesso a aplicação — cada ação sensível auditada. - apps/api/src/roles/: CRUD de papéis e gestão de role_permissions, exclusivo super_admin (SuperAdminGuard), mirando o requireSuperAdmin do legado — deliberadamente não generalizado para o modelo de permissão resource+action+scope, por ser a raiz do próprio sistema de autorização (documentado em roles.service.ts). - apps/api/src/auth/role-weight.ts: assertCanAssignRole/assertCanActOnUser (eden.md §1.6) — um ator nunca atribui papel nem mexe em usuário com peso maior que o seu; super_admin sempre passa. - .gitignore: .claude/worktrees/ (worktrees temporários de subagentes paralelos nunca devem virar submódulo do repo principal). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,10 +5,11 @@ import { PasswordService } from "./password.service.js";
|
||||
import { TokenService } from "./token.service.js";
|
||||
import { JwtAuthGuard } from "./jwt-auth.guard.js";
|
||||
import { PermissionGuard } from "./permission.guard.js";
|
||||
import { SuperAdminGuard } from "./super-admin.guard.js";
|
||||
|
||||
@Module({
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, PasswordService, TokenService, JwtAuthGuard, PermissionGuard],
|
||||
exports: [PasswordService, TokenService, JwtAuthGuard, PermissionGuard],
|
||||
providers: [AuthService, PasswordService, TokenService, JwtAuthGuard, PermissionGuard, SuperAdminGuard],
|
||||
exports: [PasswordService, TokenService, JwtAuthGuard, PermissionGuard, SuperAdminGuard],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
41
apps/api/src/auth/role-weight.ts
Normal file
41
apps/api/src/auth/role-weight.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ForbiddenException } from "@nestjs/common";
|
||||
import { rolesRepo } from "@eden/database";
|
||||
import { SUPER_ADMIN_ROLE } from "./permissions.js";
|
||||
|
||||
/**
|
||||
* Role weight (eden.md §1.6, ADR-0004): an actor can never assign a role
|
||||
* heavier than their own, nor act on a user whose *current* role is
|
||||
* heavier than the actor's — orthogonal to the resource/action/scope
|
||||
* permission check. super_admin (weight 100, hardcoded ceiling) always
|
||||
* passes both checks.
|
||||
*/
|
||||
async function getWeight(roleKey: string): Promise<number> {
|
||||
const role = await rolesRepo.getRoleByKey(roleKey);
|
||||
if (!role) throw new ForbiddenException("Papel inválido");
|
||||
return role.weight;
|
||||
}
|
||||
|
||||
export async function assertCanAssignRole(actorRoleKey: string, targetRoleKey: string): Promise<void> {
|
||||
if (actorRoleKey === SUPER_ADMIN_ROLE) return;
|
||||
const [actorWeight, targetWeight] = await Promise.all([
|
||||
getWeight(actorRoleKey),
|
||||
getWeight(targetRoleKey),
|
||||
]);
|
||||
if (targetWeight > actorWeight) {
|
||||
throw new ForbiddenException("Você não pode atribuir um papel com peso maior que o seu.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function assertCanActOnUser(
|
||||
actorRoleKey: string,
|
||||
targetUserRoleKey: string,
|
||||
): Promise<void> {
|
||||
if (actorRoleKey === SUPER_ADMIN_ROLE) return;
|
||||
const [actorWeight, targetWeight] = await Promise.all([
|
||||
getWeight(actorRoleKey),
|
||||
getWeight(targetUserRoleKey),
|
||||
]);
|
||||
if (targetWeight > actorWeight) {
|
||||
throw new ForbiddenException("Você não pode alterar um usuário com papel de peso maior que o seu.");
|
||||
}
|
||||
}
|
||||
23
apps/api/src/auth/super-admin.guard.ts
Normal file
23
apps/api/src/auth/super-admin.guard.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from "@nestjs/common";
|
||||
import type { Request } from "express";
|
||||
import { SUPER_ADMIN_ROLE } from "./permissions.js";
|
||||
|
||||
/**
|
||||
* Role definition and the permission matrix itself (role_permissions) are
|
||||
* the root of the whole authorization system — deliberately kept
|
||||
* super_admin-only and hardcoded, exactly like the legacy's
|
||||
* `requireSuperAdmin` (eden.md §8), rather than generalized to the
|
||||
* resource/action/scope model. Generalizing config-of-the-authorization-
|
||||
* system-itself would let a misconfigured permission accidentally grant
|
||||
* permission-configuration rights — not worth the flexibility.
|
||||
*/
|
||||
@Injectable()
|
||||
export class SuperAdminGuard implements CanActivate {
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
if (request.edenUser?.roleKey !== SUPER_ADMIN_ROLE) {
|
||||
throw new ForbiddenException("Exclusivo do super_admin");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user