) {
- e.preventDefault();
- setErrors({});
-
- if (password.length < 12) {
- setErrors({
- password: 'Encryption password must be at least 12 characters',
- });
- return;
- }
-
- if (password !== confirmPassword) {
- setErrors({
- confirmPassword: 'Passwords do not match',
- });
- return;
- }
-
- setProcessing(true);
-
- try {
- const salt = generateSalt();
-
- const pbkdfKey = await getKeyFromPassword(password);
-
- const aesKey = await getAESKeyFromPBKDF(pbkdfKey, salt);
-
- const { encrypted, iv } = await encrypt('Hello, world', aesKey);
-
- const exportedKey = await exportKey(aesKey);
-
- await axios.post('/api/encryption/setup', {
- salt: bufferToBase64(salt),
- encrypted_content: encrypted,
- iv: iv,
- });
-
- storeKey(exportedKey, storagePreference === 'persistent');
- refreshKeyState();
-
- router.visit(dashboard().url);
- } catch (error) {
- console.error('Encryption setup error:', error);
- setErrors({
- general:
- 'Failed to setup encryption. Please try again or contact support.',
- });
- setProcessing(false);
- }
- }
-
- return (
-
-
-
-
- );
-}
diff --git a/resources/js/pages/transactions/index.tsx b/resources/js/pages/transactions/index.tsx
index ddfed952..db04b364 100644
--- a/resources/js/pages/transactions/index.tsx
+++ b/resources/js/pages/transactions/index.tsx
@@ -1172,7 +1172,6 @@ export default function Transactions({
categories={categories}
labels={labels}
accounts={accounts}
- isKeySet={true}
enableSavedFilters={true}
actions={
diff --git a/resources/js/services/transaction-sync.ts b/resources/js/services/transaction-sync.ts
index 81c30865..3e395f29 100644
--- a/resources/js/services/transaction-sync.ts
+++ b/resources/js/services/transaction-sync.ts
@@ -1,6 +1,4 @@
-import { importKey } from '@/lib/crypto';
import { db } from '@/lib/dexie-db';
-import { getStoredKey } from '@/lib/key-storage';
import { TransactionSyncManager } from '@/lib/sync-manager';
import type { Transaction } from '@/types/transaction';
import type { UUID } from '@/types/uuid';
@@ -233,42 +231,14 @@ class TransactionSyncService {
return txDate >= minDate && txDate <= maxDate;
});
- const keyString = getStoredKey();
- const key = keyString ? await importKey(keyString) : null;
-
- const decryptedTransactions = await Promise.all(
- transactionsInRange.map(async (t) => {
- try {
- let decryptedDescription: string;
- if (t.description_iv && key) {
- const { decrypt } = await import('@/lib/crypto');
- decryptedDescription = await decrypt(
- t.description,
- key,
- t.description_iv,
- );
- } else if (t.description_iv && !key) {
- return null;
- } else {
- decryptedDescription = t.description;
- }
- return {
- transaction_date: normalizeDate(t.transaction_date),
- amount: parseFloat(t.amount),
- description: decryptedDescription
- .toLowerCase()
- .trim()
- .replace(/\s+/g, ' '),
- };
- } catch {
- return null;
- }
- }),
- );
-
- const validDecryptedTransactions = decryptedTransactions.filter(
- (t) => t !== null,
- );
+ const existingTransactions = transactionsInRange.map((t) => ({
+ transaction_date: normalizeDate(t.transaction_date),
+ amount: parseFloat(t.amount),
+ description: t.description
+ .toLowerCase()
+ .trim()
+ .replace(/\s+/g, ' '),
+ }));
return transactions.map((importingTx) => {
const normalizedDescription = importingTx.description
@@ -276,7 +246,7 @@ class TransactionSyncService {
.trim()
.replace(/\s+/g, ' ');
- return validDecryptedTransactions.some(
+ return existingTransactions.some(
(existing) =>
existing.transaction_date ===
importingTx.transaction_date &&
diff --git a/routes/api.php b/routes/api.php
index 9e100920..5f9bca04 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -14,8 +14,7 @@ use App\Http\Controllers\Sync\TransactionSyncController;
use Illuminate\Support\Facades\Route;
Route::middleware(['web', 'auth'])->group(function () {
- // Encryption
- Route::post('encryption/setup', [EncryptionController::class, 'setup']);
+ // Encryption (legacy decrypt-migration support only)
Route::get('encryption/message', [EncryptionController::class, 'getMessage']);
// Import Data (for import drawers)
diff --git a/tests/Feature/AlignAccountsEncryptedFlagMigrationTest.php b/tests/Feature/AlignAccountsEncryptedFlagMigrationTest.php
new file mode 100644
index 00000000..70bd3566
--- /dev/null
+++ b/tests/Feature/AlignAccountsEncryptedFlagMigrationTest.php
@@ -0,0 +1,21 @@
+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();
+});
diff --git a/tests/Feature/DecryptTransactionsTest.php b/tests/Feature/DecryptTransactionsTest.php
index a4045f8f..875ab477 100644
--- a/tests/Feature/DecryptTransactionsTest.php
+++ b/tests/Feature/DecryptTransactionsTest.php
@@ -27,23 +27,6 @@ test('encrypted transactions endpoint returns only encrypted transactions', func
expect($data[0]['id'])->toBe($encrypted->id);
});
-test('plaintext filter returns only plaintext transactions', function () {
- Transaction::factory()->create([
- 'user_id' => $this->user->id,
- 'description_iv' => 'some-iv',
- ]);
- $plaintext = Transaction::factory()->plaintext()->create([
- 'user_id' => $this->user->id,
- ]);
-
- $response = $this->getJson('/api/transactions?encrypted=false');
-
- $response->assertOk();
- $data = $response->json('data');
- expect($data)->toHaveCount(1);
- expect($data[0]['id'])->toBe($plaintext->id);
-});
-
test('encrypted transactions endpoint paginates correctly', function () {
$account = Account::factory()->create(['user_id' => $this->user->id]);
$category = Category::factory()->create(['user_id' => $this->user->id]);
diff --git a/tests/Feature/EncryptionTest.php b/tests/Feature/EncryptionTest.php
index e17d77a3..ab1818fc 100644
--- a/tests/Feature/EncryptionTest.php
+++ b/tests/Feature/EncryptionTest.php
@@ -4,7 +4,6 @@ use App\Models\EncryptedMessage;
use App\Models\User;
use function Pest\Laravel\actingAs;
-use function Pest\Laravel\assertDatabaseHas;
test('authenticated user without encryption salt can access setup page', function () {
$user = User::factory()->create(['encryption_salt' => null]);
@@ -14,54 +13,6 @@ test('authenticated user without encryption salt can access setup page', functio
$response->assertSuccessful();
});
-test('user can setup encryption', function () {
- $user = User::factory()->create(['encryption_salt' => null]);
-
- $response = actingAs($user)->postJson('/api/encryption/setup', [
- 'salt' => str_repeat('a', 24),
- 'encrypted_content' => 'encrypted_test_content',
- 'iv' => str_repeat('b', 16),
- ]);
-
- $response->assertSuccessful();
-
- $user->refresh();
-
- expect($user->encryption_salt)->toBe(str_repeat('a', 24));
-
- assertDatabaseHas('encrypted_messages', [
- 'user_id' => $user->id,
- 'encrypted_content' => 'encrypted_test_content',
- 'iv' => str_repeat('b', 16),
- ]);
-});
-
-test('encryption setup requires valid salt', function () {
- $user = User::factory()->create(['encryption_salt' => null]);
-
- $response = actingAs($user)->postJson('/api/encryption/setup', [
- 'salt' => 'invalid',
- 'encrypted_content' => 'encrypted_test_content',
- 'iv' => str_repeat('b', 16),
- ]);
-
- $response->assertUnprocessable();
- $response->assertJsonValidationErrors(['salt']);
-});
-
-test('encryption setup requires valid iv', function () {
- $user = User::factory()->create(['encryption_salt' => null]);
-
- $response = actingAs($user)->postJson('/api/encryption/setup', [
- 'salt' => str_repeat('a', 24),
- 'encrypted_content' => 'encrypted_test_content',
- 'iv' => 'invalid',
- ]);
-
- $response->assertUnprocessable();
- $response->assertJsonValidationErrors(['iv']);
-});
-
test('user can retrieve encrypted message', function () {
$user = User::factory()->create([
'encryption_salt' => str_repeat('a', 24),