Recover gracefully when the site's security keys change and stored Bluesky tokens no longer decrypt#191
Open
pfefferle wants to merge 1 commit into
Open
Recover gracefully when the site's security keys change and stored Bluesky tokens no longer decrypt#191pfefferle wants to merge 1 commit into
pfefferle wants to merge 1 commit into
Conversation
…ecrypted Salt rotation (a security plugin's scheduled rotation, a migration, or a regenerated wp-config.php) changes the key the OAuth tokens are encrypted with, leaving them permanently unreadable. Previously every publish then failed with a retry hint that could never succeed (see the 'inconsistent post visibility' wp.org support topic). - Flag the connection needs_reauth on decrypt failure (race-guarded on the ciphertext field that actually failed), so publishing short-circuits and the admin reconnect notice appears. - Persist a domain-separated fingerprint of the encryption key with the connection and use it to tell 'security keys changed' apart from corrupted data, with cause-specific notice copy. - Swap the editor-panel error for a reconnect link when re-saving cannot help. - Support an ATMOSPHERE_ENCRYPTION_KEY constant that takes precedence over AUTH_KEY/AUTH_SALT for sites that rotate salts deliberately.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves OAuth token resilience by detecting when stored Bluesky credentials can no longer be decrypted (commonly after WordPress security key/salt rotation), then switching the site into a “needs reconnect” state with clearer UI messaging and an optional dedicated encryption key override.
Changes:
- Add key fingerprinting + decrypt-failure classification (
key_changedvsdecrypt_failed) and mark connections asneeds_reauthon decrypt failures to avoid futile retries. - Update admin notice + editor sidebar messaging to guide users to reconnect (instead of “update the post to try again”) for non-retryable auth/decrypt states.
- Document the new
ATMOSPHERE_ENCRYPTION_KEYescape hatch and add PHPUnit coverage for the new behavior.
Reviewed changes
Copilot reviewed 17 out of 19 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/phpunit/tests/oauth/class-test-encryption.php | Adds coverage for deterministic, non-leaking key fingerprints. |
| tests/phpunit/tests/oauth/class-test-client-decrypt.php | New tests covering decrypt-failure classification, needs_reauth stamping, and race-guard behavior. |
| tests/phpunit/tests/class-test-atmosphere.php | Ensures decrypt-related errors are treated as non-transient (not retried). |
| src/editor-plugin/plugin.js | Shows a reconnect link for non-recoverable connection/publish error codes. |
| src/config.js | Exposes SETTINGS_URL from localized PHP script data for editor UI links. |
| readme.txt | Adds an FAQ entry explaining reconnect prompts after key/salt changes. |
| includes/wp-admin/class-admin.php | Improves reauth admin notice copy based on reauth_reason (key changed vs generic decrypt failure). |
| includes/oauth/class-encryption.php | Adds ATMOSPHERE_ENCRYPTION_KEY support and a persistable key fingerprint helper. |
| includes/oauth/class-client.php | Flags decrypt failures as needs_reauth, classifies key-change vs corrupt ciphertext, and extracts guarded mark_needs_reauth(). |
| includes/class-block-editor.php | Localizes settingsUrl to JS to support reconnect links in the editor. |
| includes/class-atmosphere.php | Treats atmosphere_key_changed as a permanent publish error (no retry). |
| includes/class-api.php | Uses decrypt-failure flagging/classification when DPoP key decryption fails. |
| docs/developer-docs.md | Documents token encryption behavior and the ATMOSPHERE_ENCRYPTION_KEY constant. |
| build/pre-publish-panel/plugin.js | Rebuilt asset output reflecting updated shared config bundle. |
| build/pre-publish-panel/plugin.asset.php | Updates built asset version hash. |
| build/editor-plugin/plugin.js | Rebuilt editor sidebar JS bundle with reconnect-link behavior. |
| build/editor-plugin/plugin.asset.php | Updates built asset version hash. |
| .github/changelog/fix-decrypt-failure-recovery | Changelog entry for decrypt-failure recovery behavior. |
| .github/changelog/add-encryption-key-constant | Changelog entry for the new encryption key constant. |
Files not reviewed (2)
- build/editor-plugin/plugin.js: Generated file
- build/pre-publish-panel/plugin.js: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
11 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses the "Inconsistent post visibility" support topic ("Failed to decrypt refresh token").
Proposed changes:
OAuth tokens are encrypted at rest with a key derived from
AUTH_KEY . AUTH_SALT. When the salts change — a security plugin's scheduled rotation, a migration, a regeneratedwp-config.php— the stored tokens become permanently unreadable, and every publish failed with "Sharing to Bluesky failed. Update the post to try again.", which can never succeed. Three changes:needs_reauth(using the same race-guarded stamp as theinvalid_grantpath, extracted intoClient::mark_needs_reauth()), so publishing short-circuits, the global admin reconnect notice appears, and the editor panel shows a reconnect link instead of the futile retry hint.atmosphere_key_changed— the admin notice then says the security keys changed, instead of a generic failure. Both classifications are non-retryable, so the backoff ladder isn't burned on a deterministic failure.ATMOSPHERE_ENCRYPTION_KEYconstant takes precedence over the salts, for sites that rotate them deliberately. Documented indocs/developer-docs.md; a newreadme.txtFAQ entry answers "Why does ATmosphere say it needs to reconnect?".Other information:
Testing instructions:
npm run env-start) and connect a Bluesky account on Settings → ATmosphere.npm run wp-env run cli wp config shuffle-salts(or editAUTH_KEY/AUTH_SALT).define( 'ATMOSPHERE_ENCRYPTION_KEY', 'some-long-random-secret' );inwp-config.php, reconnect, then shuffle the salts again. Expected: sharing keeps working across the salt change.composer lintandnpm run env-testpass (920 tests).Changelog entry
Two entries are already committed on the branch (
.github/changelog/fix-decrypt-failure-recovery,.github/changelog/add-encryption-key-constant).Changelog Entry Details
Significance
Type
Message