feat: version 2.7.2 -- tarjetas con icono en Contraseñas + fix borrado de password al editar
Listado de Contraseñas pasa de tabla a tarjetas: icono elegible ( catalogo fijo, sin servicios externos de favicon por privacidad) o inicial del nombre como fallback, junto con nombre y enlace. Fix real: editar una credencial sin revelar la contraseña antes la dejaba vacia al guardar -- ahora se conserva la que ya estaba si no se escribe una nueva. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { pool } from '../../config/db.js';
|
||||
|
||||
const LIST_SELECT = 'id, user_id, title, url, username, created_at, updated_at';
|
||||
const LIST_SELECT = 'id, user_id, title, url, username, icon_key, created_at, updated_at';
|
||||
const FULL_SELECT = `${LIST_SELECT}, password_enc, notes_enc`;
|
||||
|
||||
export async function findByUser(userId) {
|
||||
@@ -16,19 +16,19 @@ export async function findById(id) {
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function create(userId, { title, url, username, passwordEnc, notesEnc }) {
|
||||
export async function create(userId, { title, url, username, passwordEnc, notesEnc, iconKey }) {
|
||||
const [result] = await pool.query(
|
||||
`INSERT INTO vault_entries (user_id, title, url, username, password_enc, notes_enc)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[userId, title, url || null, username || null, passwordEnc || null, notesEnc || null]
|
||||
`INSERT INTO vault_entries (user_id, title, url, username, password_enc, notes_enc, icon_key)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[userId, title, url || null, username || null, passwordEnc || null, notesEnc || null, iconKey || null]
|
||||
);
|
||||
return findById(result.insertId);
|
||||
}
|
||||
|
||||
export async function update(id, { title, url, username, passwordEnc, notesEnc }) {
|
||||
export async function update(id, { title, url, username, passwordEnc, notesEnc, iconKey }) {
|
||||
await pool.query(
|
||||
`UPDATE vault_entries SET title = ?, url = ?, username = ?, password_enc = ?, notes_enc = ? WHERE id = ?`,
|
||||
[title, url || null, username || null, passwordEnc || null, notesEnc || null, id]
|
||||
`UPDATE vault_entries SET title = ?, url = ?, username = ?, password_enc = ?, notes_enc = ?, icon_key = ? WHERE id = ?`,
|
||||
[title, url || null, username || null, passwordEnc || null, notesEnc || null, iconKey || null, id]
|
||||
);
|
||||
return findById(id);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,16 @@ import { sendMail } from '../../utils/mailer.js';
|
||||
const PIN_RE = /^\d{4}$/;
|
||||
const VAULT_TOKEN_EXPIRES = '30m';
|
||||
|
||||
// Catalogo fijo (debe coincidir con el del frontend, ContrasenasPage.jsx#ICONS) -- nunca se
|
||||
// acepta un valor arbitrario, para no guardar basura ni abrir una via de inyeccion rara.
|
||||
const ICON_KEYS = new Set([
|
||||
'mail', 'globe', 'server', 'database', 'cloud', 'shield', 'lock', 'wifi',
|
||||
'card', 'building', 'printer', 'phone', 'key', 'disk', 'router', 'user',
|
||||
]);
|
||||
function sanitizeIconKey(key) {
|
||||
return ICON_KEYS.has(key) ? key : null;
|
||||
}
|
||||
|
||||
function newToken() {
|
||||
return crypto.randomBytes(32).toString('hex');
|
||||
}
|
||||
@@ -54,20 +64,24 @@ export async function createEntry(payload, currentUser) {
|
||||
username: payload.username?.trim(),
|
||||
passwordEnc: payload.password ? encrypt(payload.password) : null,
|
||||
notesEnc: payload.notes ? encrypt(payload.notes) : null,
|
||||
iconKey: sanitizeIconKey(payload.iconKey),
|
||||
});
|
||||
await logAudit({ userId: currentUser.id, action: 'create', entityType: 'vault_entry', entityId: entry.id });
|
||||
return stripSensitive(entry);
|
||||
}
|
||||
|
||||
export async function updateEntry(id, payload, currentUser) {
|
||||
await getOwnEntryOr404(id, currentUser);
|
||||
const entry = await getOwnEntryOr404(id, currentUser);
|
||||
if (!payload.title?.trim()) throw new HttpError(400, 'El nombre es obligatorio');
|
||||
// Si no viene password/notes nuevo (ej. se edito solo el nombre sin pasar por "Revelar"
|
||||
// primero), se conserva lo que ya estaba guardado -- nunca se borra por venir vacio.
|
||||
const updated = await repo.update(id, {
|
||||
title: payload.title.trim(),
|
||||
url: payload.url?.trim(),
|
||||
username: payload.username?.trim(),
|
||||
passwordEnc: payload.password ? encrypt(payload.password) : null,
|
||||
notesEnc: payload.notes ? encrypt(payload.notes) : null,
|
||||
passwordEnc: payload.password ? encrypt(payload.password) : entry.password_enc,
|
||||
notesEnc: payload.notes !== undefined && payload.notes !== '' ? encrypt(payload.notes) : entry.notes_enc,
|
||||
iconKey: sanitizeIconKey(payload.iconKey),
|
||||
});
|
||||
await logAudit({ userId: currentUser.id, action: 'update', entityType: 'vault_entry', entityId: id });
|
||||
return stripSensitive(updated);
|
||||
|
||||
Reference in New Issue
Block a user