mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-29 18:31:33 +00:00
keep audit trails and recordings when a user is deleted (#1128)
audit_logs and session_recordings both referenced users with ON DELETE CASCADE, so removing an account erased everything it had ever done. An audit trail that disappears with the account it recorded cannot answer the question it exists for, and a recording is evidence about a host as much as about a person. Both foreign keys become ON DELETE SET NULL. audit_logs already denormalises username, so an entry still names who acted once the reference is gone. session_recordings did not, so the column is added and backfilled first — otherwise relaxing the constraint would only trade deleted evidence for anonymous evidence. SQLite cannot alter a foreign key in place, so existing databases are migrated by copy-and-swap, guarded by a PRAGMA check that makes it idempotent. Fresh databases are created in the target shape and skip it. Recordings still cascade from their host.
This commit is contained in:
@@ -8,6 +8,7 @@ import { DatabaseFileEncryption } from "../../utils/database-file-encryption.js"
|
||||
import { SystemCrypto } from "../../utils/system-crypto.js";
|
||||
import { DatabaseMigration } from "../../utils/database-migration.js";
|
||||
import { DatabaseSaveTrigger } from "../../utils/database-save-trigger.js";
|
||||
import { migrateAuditRetention } from "../../utils/audit-retention-migration.js";
|
||||
import {
|
||||
assertDataDirIsNotMisconfigured,
|
||||
DataDirMisconfiguredError,
|
||||
@@ -482,13 +483,14 @@ async function initializeCompleteDatabase(): Promise<void> {
|
||||
success INTEGER NOT NULL,
|
||||
error_message TEXT,
|
||||
timestamp TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session_recordings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
host_id INTEGER NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
user_id TEXT,
|
||||
username TEXT,
|
||||
access_id INTEGER,
|
||||
started_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ended_at TEXT,
|
||||
@@ -501,7 +503,7 @@ async function initializeCompleteDatabase(): Promise<void> {
|
||||
terminated_by_owner INTEGER DEFAULT 0,
|
||||
termination_reason TEXT,
|
||||
FOREIGN KEY (host_id) REFERENCES ssh_data (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (access_id) REFERENCES host_access (id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
@@ -1647,7 +1649,7 @@ const migrateSchema = () => {
|
||||
sqlite.exec(`
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
user_id TEXT,
|
||||
username TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
resource_type TEXT NOT NULL,
|
||||
@@ -1659,7 +1661,7 @@ const migrateSchema = () => {
|
||||
success INTEGER NOT NULL,
|
||||
error_message TEXT,
|
||||
timestamp TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL
|
||||
);
|
||||
`);
|
||||
} catch (createError) {
|
||||
@@ -2507,6 +2509,10 @@ const migrateSchema = () => {
|
||||
}
|
||||
// --- sync end ---
|
||||
|
||||
// Audit trails and session recordings used to be deleted along with the user
|
||||
// they referenced, which defeats the point of keeping them.
|
||||
migrateAuditRetention(sqlite);
|
||||
|
||||
databaseLogger.success("Schema migration completed", {
|
||||
operation: "schema_migration",
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user