Skip to content

Commit 91b2baa

Browse files
andrii-trushclaude
andcommitted
revert: remove CP toast notification on decrypt failure
The toast delivery proved unreliable across all tested paths: - AddToasts/Axios interceptor path fails on page refresh because Listing.vue's AbortController cancels the first request after the server has already consumed the dedup cache key, so the second (actually-read) response carries no _toasts. - session()->flash() path shows only on the subsequent page load and produced confusing UX. Remove all associated code and tests: - Drop Toast, Cache, Statamic imports and NOTIFY_TTL constant from FieldEncryptor; decrypt() now just logs a warning and returns the raw value on failure. - Remove the $context parameter from decrypt() and update both repository callers. - Remove the decrypt_failure_toast lang string. - Remove two now-stale FieldEncryptorTest cases (they tested absence of Toast calls, which passed trivially after the code was gone). - Update OVERVIEW.md and PLAN.md to reflect the simplified behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c6a07c5 commit 91b2baa

7 files changed

Lines changed: 5 additions & 72 deletions

File tree

docs/OVERVIEW.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ tests/
6060
- Checks the current user's permission (`view decrypted sensitive fields`).
6161
- **Authorized**: strips `enc:v1:` prefix and decrypts the value.
6262
- **Unauthorized**: replaces the value with the mask string (default `••••••`).
63-
4. If decryption fails (e.g. key rotation), returns raw ciphertext, logs a warning, and dispatches a CP error toast to the current user (HTTP context only; deduplicated to once per form per hour via `Cache::add`).
63+
4. If decryption fails (e.g. key rotation), returns raw ciphertext and logs a warning.
6464

6565
### Addon Settings
6666

docs/PLAN.md

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,7 @@ Edition is detected via the **Statamic Editions API**: `Addon::edition()` reads
180180
5. isEncrypted detects prefix
181181
6. mask returns configured value
182182
7. decrypt returns non-encrypted as-is
183-
8. Failed decrypt does not dispatch toast in console context
184-
9. Failed decrypt without context does not dispatch toast in console context
183+
8. Failed decrypt returns raw value and logs warning (no side-effects)
185184

186185
### Feature (SensitiveFieldsTest, 12 tests)
187186
1. Sensitive field stored encrypted
@@ -244,12 +243,3 @@ Larger teams need per-form control (e.g. HR form vs. contact form handled by dif
244243
- Both `DecryptingSubmissionRepository` and `DecryptingSubmissionQueryBuilder` check global then per-form permission via `isAuthorizedForForm(string $formHandle)`.
245244

246245
---
247-
248-
### [FREE/PRO] CP notification on decrypt failure — Implemented
249-
250-
Decryption failures are now surfaced in the CP as an error toast in addition to the existing `Log::warning`.
251-
252-
- `FieldEncryptor::decrypt()` accepts an optional `string $context` parameter (form handle) for deduplication.
253-
- In HTTP context, `Cache::add('sffields.decrypt_failure_notified.{context}', true, 3600)` is used as an atomic set-if-not-exists guard — at most one toast per form per hour.
254-
- `Toast::error()` is skipped entirely when `app()->runningInConsole()` is true (commands, queue workers).
255-
- `DecryptingSubmissionRepository` passes the form handle as `$context` when calling `decrypt()`.

lang/en/messages.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
'permission_form_label' => 'View Decrypted Sensitive Fields',
1313
'permission_form_description' => 'Allow viewing decrypted values of sensitive fields in this form only',
1414

15-
'decrypt_failure_toast' => 'One or more sensitive field values could not be decrypted. Your APP_KEY may have changed. Use sensitive-fields:rekey (Pro) to recover.',
16-
1715
'settings_enabled_display' => 'Enabled',
1816
'settings_enabled_instructions' => 'Enable or disable field encryption.',
1917
'settings_mask_display' => 'Mask String',

src/Encryption/FieldEncryptor.php

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,16 @@
44

55
namespace Isapp\SensitiveFormFields\Encryption;
66

7-
use Illuminate\Support\Facades\Cache;
87
use Illuminate\Support\Facades\Crypt;
98
use Illuminate\Support\Facades\Log;
109
use Statamic\Addons\Addon;
11-
use Statamic\Facades\CP\Toast;
12-
use Statamic\Statamic;
1310

1411
class FieldEncryptor
1512
{
1613
// Versioned prefix prepended to every ciphertext.
1714
// Allows detecting already-encrypted values and future format migrations.
1815
protected const PREFIX = 'enc:v1:';
1916

20-
// Suppress repeated CP toasts for the same form within this window (seconds).
21-
protected const NOTIFY_TTL = 3600;
22-
2317
public function __construct(
2418
protected Addon $addon,
2519
) {}
@@ -37,12 +31,7 @@ public function encrypt(string $value): string
3731
// Returns the value unchanged (with a log warning) on decryption failure,
3832
// e.g. after an APP_KEY rotation. Callers should treat the raw ciphertext
3933
// as an opaque fallback rather than a fatal error.
40-
//
41-
// $context is an optional form handle used to deduplicate CP toasts so that
42-
// bulk failures (e.g. many submissions after an APP_KEY rotation) produce at
43-
// most one toast per form per hour. Pass the form handle from repository callers;
44-
// omit from CLI callers (toast is suppressed in console context anyway).
45-
public function decrypt(string $value, string $context = ''): string
34+
public function decrypt(string $value): string
4635
{
4736
if (! $this->isEncrypted($value)) {
4837
return $value;
@@ -53,29 +42,6 @@ public function decrypt(string $value, string $context = ''): string
5342
} catch (\Throwable $e) {
5443
Log::warning('Failed to decrypt sensitive field value: ' . $e->getMessage());
5544

56-
// Only dispatch CP toasts on actual CP requests — not API or frontend
57-
// requests, which could consume the dedup window before a CP user visits.
58-
// Cache::add() is atomic set-if-not-exists; the key is rolled back via
59-
// Cache::forget() when toast delivery fails so the dedup window is not
60-
// consumed without a toast being shown. Wrapped in try/catch so that
61-
// cache or session failures never break the graceful fallback.
62-
try {
63-
if (Statamic::isCpRoute()) {
64-
$cacheKey = 'sffields.decrypt_failure_notified.' . ($context ?: 'unknown');
65-
if (Cache::add($cacheKey, true, self::NOTIFY_TTL)) {
66-
try {
67-
Toast::error(__('statamic-sensitive-form-fields::messages.decrypt_failure_toast'));
68-
} catch (\Throwable) {
69-
// Roll back the dedup key so a future request with an active
70-
// CP session can still deliver the toast.
71-
Cache::forget($cacheKey);
72-
}
73-
}
74-
}
75-
} catch (\Throwable) {
76-
// Cache failure must never convert a recoverable decrypt error into a fatal one.
77-
}
78-
7945
return $value;
8046
}
8147
}

src/Repositories/DecryptingSubmissionQueryBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ protected function decryptSubmission(Submission $submission): void
8181
}
8282

8383
if ($canDecrypt) {
84-
$submission->set($handle, $this->encryptor->decrypt($value, $submission->form()->handle()));
84+
$submission->set($handle, $this->encryptor->decrypt($value));
8585
} else {
8686
$submission->set($handle, $this->encryptor->mask());
8787
}

src/Repositories/DecryptingSubmissionRepository.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ protected function decryptSubmission(Submission $submission): void
104104
}
105105

106106
if ($canDecrypt) {
107-
$submission->set($handle, $this->encryptor->decrypt($value, $submission->form()->handle()));
107+
$submission->set($handle, $this->encryptor->decrypt($value));
108108
} else {
109109
$submission->set($handle, $this->encryptor->mask());
110110
}

tests/Unit/FieldEncryptorTest.php

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -66,25 +66,4 @@ public function test_decrypt_returns_non_encrypted_value_as_is()
6666
{
6767
$this->assertSame('plaintext', $this->encryptor->decrypt('plaintext'));
6868
}
69-
70-
public function test_failed_decrypt_does_not_dispatch_toast_in_console_context()
71-
{
72-
// The test suite runs in console context (app()->runningInConsole() === true),
73-
// so the Toast must never be called — verified by spying on the facade.
74-
\Statamic\Facades\CP\Toast::spy();
75-
76-
$this->encryptor->decrypt('enc:v1:corrupted-ciphertext', 'contact');
77-
78-
\Statamic\Facades\CP\Toast::shouldNotHaveReceived('error');
79-
}
80-
81-
public function test_failed_decrypt_without_context_does_not_dispatch_toast_in_console_context()
82-
{
83-
\Statamic\Facades\CP\Toast::spy();
84-
85-
// Backward-compatible: context parameter is optional.
86-
$this->encryptor->decrypt('enc:v1:corrupted-ciphertext');
87-
88-
\Statamic\Facades\CP\Toast::shouldNotHaveReceived('error');
89-
}
9069
}

0 commit comments

Comments
 (0)