feat: version 2.13.0 -- grupos y compartir en Contraseñas

Grupos de un solo nivel (tarjetas, clic entra a ver sus credenciales) y compartir
una credencial individual de solo lectura con otro usuario registrado (puede
revelar/copiar la contraseña real, nunca editar/borrar/compartir/mover de grupo).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-14 16:14:36 -05:00
parent 971de05773
commit 3da652c136
13 changed files with 570 additions and 43 deletions
@@ -0,0 +1,45 @@
-- Grupos de un solo nivel para Contraseñas (ej. "Correos" agrupa Gmail, Outlook...) y compartir
-- una entrada individual de solo lectura con otro usuario ya registrado. Idempotente.
CREATE TABLE IF NOT EXISTS vault_groups (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
name VARCHAR(80) NOT NULL,
icon_key VARCHAR(40) NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_vault_groups_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE INDEX IF NOT EXISTS idx_vault_groups_user ON vault_groups(user_id);
-- Borrar un grupo desagrupa sus entradas (ON DELETE SET NULL) -- nunca las borra.
ALTER TABLE vault_entries
ADD COLUMN IF NOT EXISTS group_id INT NULL AFTER user_id;
CREATE INDEX IF NOT EXISTS idx_vault_entries_group ON vault_entries(group_id);
-- MariaDB no soporta "ADD CONSTRAINT IF NOT EXISTS ... FOREIGN KEY" (error de sintaxis en
-- 10.11) -- se emula la idempotencia a mano consultando information_schema antes de ejecutar el
-- ALTER via PREPARE/EXECUTE (migrate-db.js corre TODOS los .sql en cada deploy, sin tabla de
-- control de versiones, asi que esto debe poder correr muchas veces sin fallar).
SET @fk_exists := (
SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE() AND TABLE_NAME = 'vault_entries' AND CONSTRAINT_NAME = 'fk_vault_entries_group'
);
SET @fk_ddl := IF(@fk_exists = 0,
'ALTER TABLE vault_entries ADD CONSTRAINT fk_vault_entries_group FOREIGN KEY (group_id) REFERENCES vault_groups(id) ON DELETE SET NULL',
'SELECT 1');
PREPARE stmt FROM @fk_ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Compartir SOLO por entrada (nunca por grupo completo), de solo lectura: el receptor puede
-- revelar la contraseña real (ver vault/service.js#getReadableEntryOr404) pero nunca
-- editar/borrar/compartir/mover de grupo la entrada ajena.
CREATE TABLE IF NOT EXISTS vault_entry_shares (
entry_id INT NOT NULL,
user_id INT NOT NULL,
shared_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (entry_id, user_id),
CONSTRAINT fk_vault_shares_entry FOREIGN KEY (entry_id) REFERENCES vault_entries(id) ON DELETE CASCADE,
CONSTRAINT fk_vault_shares_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;