feat: version 2.15.0 -- instalador del agente GLPI, filtro de componentes y token recuperable
Instalador .bat generado en el navegador, casilla para ocultar actualizaciones/drivers/componentes en Software, buscador por pestaña en el detalle, GUIDs como componentes y tokens de ingesta cifrados (migraciones 024 y 025). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,5 +15,6 @@ export const getRaw = asyncHandler(async (req, res) => {
|
||||
|
||||
export const getTokens = asyncHandler(async (req, res) => ok(res, await service.listTokens()));
|
||||
export const postToken = asyncHandler(async (req, res) => ok(res, await service.createToken(req.body.label, req.currentUser), 201));
|
||||
export const getTokenSecret = asyncHandler(async (req, res) => ok(res, await service.revealToken(Number(req.params.id), req.currentUser)));
|
||||
export const patchToken = asyncHandler(async (req, res) => ok(res, await service.setTokenActive(Number(req.params.id), !!req.body.isActive, req.currentUser)));
|
||||
export const deleteToken = asyncHandler(async (req, res) => ok(res, await service.removeToken(Number(req.params.id), req.currentUser)));
|
||||
|
||||
@@ -68,7 +68,7 @@ const COMPONENT_NAME = new RegExp([
|
||||
'windowsappruntime', 'ui xaml', 'advertising sdk', 'vclibs', 'driver', 'controlador', 'firmware', 'chipset',
|
||||
'management engine', 'rapid storage', 'serial io', 'hardware accelerated', 'compatibility fix', 'subsystem for linux',
|
||||
'webview2', 'update health tools', 'pc health', 'comprobaci[oó]n de estado', 'desinstalar impresora', 'printer uninstall',
|
||||
'^microsoftwindows\\.', '^[0-9a-f]{8}-[0-9a-f]{4}-', '^windows sdk', 'software development kit.*(windows|arm|x64|x86)',
|
||||
'^microsoftwindows\\.', '^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-', '^windows sdk', 'software development kit.*(windows|arm|x64|x86)',
|
||||
].join('|'), 'i');
|
||||
function isComponent(name, category) {
|
||||
return COMPONENT_CATEGORIES.has((category || '').toLowerCase()) || COMPONENT_NAME.test(name || '');
|
||||
|
||||
@@ -27,17 +27,22 @@ export const touchToken = (id) => pool.query('UPDATE inventory_tokens SET last_u
|
||||
|
||||
export async function listTokens() {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT t.id, t.label, t.is_active, t.last_used_at, t.created_at, u.name AS created_by_name
|
||||
`SELECT t.id, t.label, t.is_active, t.last_used_at, t.created_at, u.name AS created_by_name, (t.token_enc IS NOT NULL) AS has_secret
|
||||
FROM inventory_tokens t LEFT JOIN users u ON u.id = t.created_by ORDER BY t.id DESC`
|
||||
);
|
||||
return rows;
|
||||
return rows.map((r) => ({ ...r, has_secret: !!r.has_secret }));
|
||||
}
|
||||
|
||||
export async function createToken(label, hash, userId) {
|
||||
const [r] = await pool.query('INSERT INTO inventory_tokens (label, token_hash, created_by) VALUES (?, ?, ?)', [label, hash, userId]);
|
||||
export async function createToken(label, hash, enc, userId) {
|
||||
const [r] = await pool.query('INSERT INTO inventory_tokens (label, token_hash, token_enc, created_by) VALUES (?, ?, ?, ?)', [label, hash, enc, userId]);
|
||||
return r.insertId;
|
||||
}
|
||||
|
||||
export async function findTokenSecret(id) {
|
||||
const [[row]] = await pool.query('SELECT token_enc FROM inventory_tokens WHERE id = ?', [id]);
|
||||
return row?.token_enc || null;
|
||||
}
|
||||
|
||||
export const setTokenActive = (id, active) => pool.query('UPDATE inventory_tokens SET is_active = ? WHERE id = ?', [active ? 1 : 0, id]);
|
||||
export const deleteToken = (id) => pool.query('DELETE FROM inventory_tokens WHERE id = ?', [id]);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Router } from 'express';
|
||||
import { requireAuth, requireRole, ROLES } from '../../middleware/auth.js';
|
||||
import {
|
||||
getDevices, getDevice, putDevice, getDashboard, getSoftware, getSoftwareDevices, getRaw,
|
||||
getTokens, postToken, patchToken, deleteToken,
|
||||
getTokens, postToken, patchToken, deleteToken, getTokenSecret,
|
||||
} from './controller.js';
|
||||
|
||||
const router = Router();
|
||||
@@ -17,6 +17,7 @@ router.get('/software', getSoftware);
|
||||
router.get('/software/devices', getSoftwareDevices);
|
||||
router.get('/tokens', requireRole(...CAN_MANAGE_TOKENS), getTokens);
|
||||
router.post('/tokens', requireRole(...CAN_MANAGE_TOKENS), postToken);
|
||||
router.get('/tokens/:id/secret', requireRole(...CAN_MANAGE_TOKENS), getTokenSecret);
|
||||
router.patch('/tokens/:id', requireRole(...CAN_MANAGE_TOKENS), patchToken);
|
||||
router.delete('/tokens/:id', requireRole(...CAN_MANAGE_TOKENS), deleteToken);
|
||||
router.get('/', getDevices);
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from 'node:path';
|
||||
import { env } from '../../config/env.js';
|
||||
import { HttpError } from '../../utils/http.js';
|
||||
import { logAudit } from '../../utils/audit.js';
|
||||
import { encrypt, decrypt } from '../../utils/crypto.js';
|
||||
import * as repo from './repository.js';
|
||||
|
||||
const STATUSES = ['active', 'retired'];
|
||||
@@ -57,11 +58,19 @@ export async function createToken(label, actor) {
|
||||
const clean = String(label || '').trim();
|
||||
if (!clean) throw new HttpError(400, 'La etiqueta es obligatoria');
|
||||
const token = crypto.randomBytes(24).toString('hex');
|
||||
const id = await repo.createToken(clean.slice(0, 120), crypto.createHash('sha256').update(token).digest('hex'), actor.id);
|
||||
const id = await repo.createToken(clean.slice(0, 120), crypto.createHash('sha256').update(token).digest('hex'), encrypt(token), actor.id);
|
||||
await logAudit({ userId: actor.id, action: 'create', entityType: 'inventory_token', entityId: id, detail: { label: clean } });
|
||||
return { id, label: clean, token };
|
||||
}
|
||||
|
||||
// Devuelve el token en claro a admin/superadmin para armar el instalador. Queda en auditoria.
|
||||
export async function revealToken(id, actor) {
|
||||
const enc = await repo.findTokenSecret(id);
|
||||
if (!enc) throw new HttpError(404, 'Este token se creo antes de poder recuperarse: genera uno nuevo');
|
||||
await logAudit({ userId: actor.id, action: 'reveal', entityType: 'inventory_token', entityId: id });
|
||||
return { token: decrypt(enc) };
|
||||
}
|
||||
|
||||
export async function setTokenActive(id, active, actor) {
|
||||
await repo.setTokenActive(id, active);
|
||||
await logAudit({ userId: actor.id, action: active ? 'enable' : 'disable', entityType: 'inventory_token', entityId: id });
|
||||
|
||||
Reference in New Issue
Block a user