73 lines
2.2 KiB
TypeScript
73 lines
2.2 KiB
TypeScript
import { isIP } from 'node:net';
|
|
import { ConflictException } from '@nestjs/common';
|
|
import { EntityManager } from 'typeorm';
|
|
import type {
|
|
AuthPrincipal,
|
|
RequestWithContext,
|
|
} from '../../common/http/request-context';
|
|
import { AuditSource } from '../../database/entities';
|
|
|
|
const REQUIRED_RECOVERY_PERMISSIONS = [
|
|
'roles.manage',
|
|
'users.assign_roles',
|
|
] as const;
|
|
|
|
export function administrationAuditContext(
|
|
principal: AuthPrincipal,
|
|
request: RequestWithContext,
|
|
) {
|
|
const candidateIp = request.ip || request.socket.remoteAddress || '';
|
|
const rawUserAgent = request.header('user-agent')?.trim();
|
|
|
|
return {
|
|
actorUserId: principal.userId,
|
|
actorUsername: principal.username,
|
|
requestId: request.requestId,
|
|
source: AuditSource.WEB,
|
|
ip: isIP(candidateIp) ? candidateIp : null,
|
|
userAgent: rawUserAgent ? rawUserAgent.slice(0, 2048) : null,
|
|
};
|
|
}
|
|
|
|
export async function assertAdministrativeRecoveryRemains(
|
|
manager: EntityManager,
|
|
): Promise<void> {
|
|
const [row] = (await manager.query(
|
|
`
|
|
SELECT COUNT(*)::integer AS count
|
|
FROM (
|
|
SELECT user_account.id
|
|
FROM users user_account
|
|
INNER JOIN user_roles user_role
|
|
ON user_role.user_id = user_account.id
|
|
INNER JOIN role_permissions role_permission
|
|
ON role_permission.role_id = user_role.role_id
|
|
INNER JOIN permissions permission
|
|
ON permission.id = role_permission.permission_id
|
|
WHERE user_account.status = 'ACTIVE'
|
|
AND permission.code = ANY($1::varchar[])
|
|
GROUP BY user_account.id
|
|
HAVING COUNT(DISTINCT permission.code) = $2
|
|
) administrators
|
|
`,
|
|
[REQUIRED_RECOVERY_PERMISSIONS, REQUIRED_RECOVERY_PERMISSIONS.length],
|
|
)) as Array<{ count: number }>;
|
|
|
|
if (!row || Number(row.count) < 1) {
|
|
throw new ConflictException({
|
|
code: 'LAST_ADMINISTRATOR_PROTECTED',
|
|
message:
|
|
'Debe permanecer al menos un usuario activo capaz de administrar roles y asignaciones',
|
|
});
|
|
}
|
|
}
|
|
|
|
export function isUniqueViolation(error: unknown): boolean {
|
|
return (
|
|
typeof error === 'object' &&
|
|
error !== null &&
|
|
'code' in error &&
|
|
(error as { code?: unknown }).code === '23505'
|
|
);
|
|
}
|