Gift Moments: Privacy dei Dati via SQLite Crittografato

Mentre lo stato principale dell'applicazione risiede in soluzioni RDBMS standard, Gift Moments adotta un approccio specializzato per i repository di dati specifici dell'utente.

Il Vault Crittografato

Per i dati sensibili degli utenti, ho implementato un'architettura basata su SQLite Crittografato.

  • Sicurezza a Riposo: Il file del database stesso è crittografato utilizzando SQLCipher, garantendo che anche se si accede al file fisico, i dati rimangano illeggibili senza le chiavi specifiche.
  • Portabilità: L'uso di SQLite consente ai vault degli utenti di essere portabili e facilmente sottoposti a backup, comportandosi come documenti sicuri piuttosto che come semplici righe di database.

Dettagli Implementativi

La logica di crittografia è gestita in modo trasparente dal gestore delle connessioni. Quando viene richiesta una connessione, il sistema applica automaticamente la chiave di crittografia definita nella configurazione dell'ambiente.

// tc-be/src/common/services/sqlite/sqlite-connection-manager.ts

// Configure encryption if key is available
if (this.encryptionKey) {
    console.log(`Setting up encryption for database: ${dbPath}`);
    // Set to use standard SQLCipher encryption (compatible with DB Browser)
    db.pragma(`cipher='sqlcipher'`);
    db.pragma(`legacy=4`);
    
    // Check if this is a new database (doesn't exist or is empty)
    const isNewDb = !fs.existsSync(dbPath) || fs.statSync(dbPath).size === 0;
    
    if (isNewDb) {
        // For new database, set the key
        db.pragma(`key='${this.escapePragmaValue(this.encryptionKey)}'`);
    } else {
        // For existing database, try to open with key
        try {
            db.pragma(`key='${this.escapePragmaValue(this.encryptionKey)}'`);
            
            // Verify the database is readable (will throw if key is wrong)
            db.prepare('SELECT 1').get();
        } catch (error) {
            throw new Error(`Cannot decrypt database with provided key: ${dbPath}`);
        }
    }
}

Isolamento Utente

A ogni utente viene assegnato un file di database SQLite dedicato. Questa decisione architettonica garantisce un rigoroso isolamento dei dati; i dati di un utente sono fisicamente separati dagli altri, non solo logicamente filtrati da una clausola WHERE.

// tc-be/src/common/services/sqlite/sqlite.service.ts

// * Constructs the path to the user's database file
private getUserDbPath(userId: number): string {
    const userIdStr = String(userId).padStart(8, '0');
    // Each user gets their own isolated directory and database file
    return path.join(this.dbPath, `${userIdStr}/user_${userIdStr}.db`);
}

Questo approccio isola i contesti utente e aggiunge un livello critico di difesa per le informazioni personali, separato dalla logica principale dell'applicazione.