fix(encryption): align stale account encrypted flags with plaintext names

A handful of legacy accounts are still flagged encrypted=true while their
name is stored in plaintext (name_iv is null). The client-side decrypt-
migration only clears the flag for accounts with an actually-encrypted name,
so these stale flags never resolve and keep the user perpetually counted as
"has encrypted accounts". Backfill the flag to match reality.

Transaction decryption is unaffected: it is driven by each transaction's
description_iv / notes_iv, not by this account flag.
This commit is contained in:
Víctor Falcón 2026-06-20 12:58:06 +02:00
parent 51c4f1fdc5
commit b581206a35
2 changed files with 56 additions and 0 deletions

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Some legacy accounts are still flagged `encrypted = true` even though
* their name is stored in plaintext (`name_iv` is null). The client-side
* decrypt-migration only clears the flag for accounts whose name is
* actually encrypted, so these stale flags would never resolve on their
* own and keep the user perpetually counted as "has encrypted accounts".
* Align the flag with reality: an account with no `name_iv` is not
* encrypted. Transaction decryption is unaffected it is driven by each
* transaction's `description_iv` / `notes_iv`, not by this flag.
*/
public function up(): void
{
DB::table('accounts')
->where('encrypted', true)
->whereNull('name_iv')
->update(['encrypted' => false]);
}
/**
* Irreversible: once flipped, a migration-corrected account is
* indistinguishable from an account that was always plaintext, so blanket
* re-flagging would wrongly encrypt legitimately unencrypted accounts.
*/
public function down(): void
{
//
}
};

View File

@ -0,0 +1,21 @@
<?php
use App\Models\Account;
function runAlignEncryptedFlagMigration(): void
{
$migration = require database_path('migrations/2026_06_20_105609_align_accounts_encrypted_flag_with_plaintext_names.php');
$migration->up();
}
it('clears the encrypted flag only for accounts whose name is plaintext', function () {
$staleFlag = Account::factory()->create(['encrypted' => true, 'name_iv' => null]);
$encryptedName = Account::factory()->create(['encrypted' => true, 'name_iv' => str_repeat('a', 16)]);
$alreadyPlaintext = Account::factory()->create(['encrypted' => false, 'name_iv' => null]);
runAlignEncryptedFlagMigration();
expect($staleFlag->fresh()->encrypted)->toBeFalse()
->and($encryptedName->fresh()->encrypted)->toBeTrue()
->and($alreadyPlaintext->fresh()->encrypted)->toBeFalse();
});