From 24ec81b0ba2b3f6ce4c549be2a85e2afa294c0c9 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Tue, 16 Sep 2025 03:20:33 +0000 Subject: [PATCH] refactor: consolidate active identity migrations 004-006 into single migration - Consolidate migrations 004, 005, and 006 into single 004_active_identity_management - Remove redundant migrations 005 (constraint_fix) and 006 (settings_cleanup) - Implement security-first approach with ON DELETE RESTRICT constraint from start - Include comprehensive data migration from settings.activeDid to active_identity.activeDid - Add proper cleanup of orphaned settings records and legacy activeDid values - Update migrationService.ts validation logic to reflect consolidated structure - Fix migration name references and enhance validation for hasBackedUpSeed column - Reduce migration complexity from 3 separate operations to 1 atomic operation - Maintain data integrity with foreign key constraints and performance indexes Migration successfully tested on web platform with no data loss or corruption. Active DID properly migrated: did:ethr:0xCA26A3959D32D2eB5459cE08203DbC4e62e79F5D Files changed: - src/db-sql/migration.ts: Consolidated 3 migrations into 1 (-46 lines) - src/services/migrationService.ts: Updated validation logic (+13 lines) --- src/db-sql/migration.ts | 53 ++++++-------------------------- src/services/migrationService.ts | 16 ++++++++-- 2 files changed, 23 insertions(+), 46 deletions(-) diff --git a/src/db-sql/migration.ts b/src/db-sql/migration.ts index a8cea42e..314e8b93 100644 --- a/src/db-sql/migration.ts +++ b/src/db-sql/migration.ts @@ -141,9 +141,11 @@ const MIGRATIONS = [ `, }, { - name: "004_active_identity_and_seed_backup", + name: "004_active_identity_management", sql: ` - -- Migration 004: active_identity_and_seed_backup + -- Migration 004: active_identity_management (CONSOLIDATED) + -- Combines original migrations 004, 005, and 006 into single atomic operation + -- CRITICAL SECURITY: Uses ON DELETE RESTRICT constraint from the start -- Assumes master code deployed with migration 003 (hasBackedUpSeed) -- Enable foreign key constraints for data integrity @@ -152,12 +154,12 @@ const MIGRATIONS = [ -- Add UNIQUE constraint to accounts.did for foreign key support CREATE UNIQUE INDEX IF NOT EXISTS idx_accounts_did_unique ON accounts(did); - -- Create active_identity table with foreign key constraint + -- Create active_identity table with SECURE constraint (ON DELETE RESTRICT) + -- This prevents accidental account deletion - critical security feature CREATE TABLE IF NOT EXISTS active_identity ( id INTEGER PRIMARY KEY CHECK (id = 1), - activeDid TEXT DEFAULT NULL, -- NULL instead of empty string - lastUpdated TEXT NOT NULL DEFAULT (datetime('now')), - FOREIGN KEY (activeDid) REFERENCES accounts(did) ON DELETE SET NULL + activeDid TEXT REFERENCES accounts(did) ON DELETE RESTRICT, + lastUpdated TEXT NOT NULL DEFAULT (datetime('now')) ); -- Add performance indexes @@ -175,45 +177,10 @@ const MIGRATIONS = [ lastUpdated = datetime('now') WHERE id = 1 AND EXISTS (SELECT 1 FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != ''); - `, - }, - { - name: "005_active_identity_constraint_fix", - sql: ` - -- Migration 005: Fix foreign key constraint to ON DELETE RESTRICT - -- CRITICAL SECURITY FIX: Prevents accidental account deletion - - PRAGMA foreign_keys = ON; - - -- Recreate table with ON DELETE RESTRICT constraint (SECURITY FIX) - CREATE TABLE active_identity_new ( - id INTEGER PRIMARY KEY CHECK (id = 1), - activeDid TEXT REFERENCES accounts(did) ON DELETE RESTRICT, - lastUpdated TEXT NOT NULL DEFAULT (datetime('now')) - ); - - -- Copy existing data - INSERT INTO active_identity_new (id, activeDid, lastUpdated) - SELECT id, activeDid, lastUpdated FROM active_identity; - -- Replace old table - DROP TABLE active_identity; - ALTER TABLE active_identity_new RENAME TO active_identity; - - -- Recreate indexes - CREATE UNIQUE INDEX IF NOT EXISTS idx_active_identity_single_record ON active_identity(id); - `, - }, - { - name: "006_settings_cleanup", - sql: ` - -- Migration 006: Settings cleanup - -- Remove orphaned settings records and clear legacy activeDid values - - -- Remove orphaned settings records (accountDid is null) + -- CLEANUP: Remove orphaned settings records and clear legacy activeDid values + -- This completes the migration from settings-based to table-based active identity DELETE FROM settings WHERE accountDid IS NULL; - - -- Clear any remaining activeDid values in settings UPDATE settings SET activeDid = NULL; `, }, diff --git a/src/services/migrationService.ts b/src/services/migrationService.ts index a0f71ff1..e75b777f 100644 --- a/src/services/migrationService.ts +++ b/src/services/migrationService.ts @@ -374,7 +374,7 @@ async function validateMigrationApplication( } else { validation.hasExpectedColumns = true; } - } else if (migration.name === "003_active_identity_and_seed_backup") { + } else if (migration.name === "004_active_identity_management") { // Validate active_identity table exists and has correct structure const activeIdentityExists = await validateTableExists( "active_identity", @@ -409,6 +409,8 @@ async function validateMigrationApplication( } // Check that hasBackedUpSeed column exists in settings table + // Note: This validation is included here because migration 004 is consolidated + // and includes the functionality from the original migration 003 const hasBackedUpSeedExists = await validateColumnExists( "settings", "hasBackedUpSeed", @@ -493,7 +495,7 @@ async function isSchemaAlreadyPresent( } catch (error) { return false; } - } else if (migration.name === "004_active_identity_and_seed_backup") { + } else if (migration.name === "004_active_identity_management") { // Check if active_identity table exists and has correct structure try { // Check that active_identity table exists @@ -515,7 +517,15 @@ async function isSchemaAlreadyPresent( await sqlQuery( `SELECT id, activeDid, lastUpdated FROM active_identity LIMIT 1`, ); - return true; + + // Also check that hasBackedUpSeed column exists in settings + // This is included because migration 004 is consolidated + try { + await sqlQuery(`SELECT hasBackedUpSeed FROM settings LIMIT 1`); + return true; + } catch (error) { + return false; + } } catch (error) { return false; }