Skip to content

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
trunkfrom
fix/decrypt-failure-recovery
Open

Recover gracefully when the site's security keys change and stored Bluesky tokens no longer decrypt#191
pfefferle wants to merge 1 commit into
trunkfrom
fix/decrypt-failure-recovery

Conversation

@pfefferle

Copy link
Copy Markdown
Member

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 regenerated wp-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:

  • Graceful recovery. A decrypt failure now flags the connection needs_reauth (using the same race-guarded stamp as the invalid_grant path, extracted into Client::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.
  • Precise detection. A domain-separated fingerprint of the encryption key is stored with the connection (at connect time, backfilled on each successful refresh for existing connections). On decrypt failure, a fingerprint mismatch classifies the error as 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.
  • Escape hatch. A new optional ATMOSPHERE_ENCRYPTION_KEY constant takes precedence over the salts, for sites that rotate them deliberately. Documented in docs/developer-docs.md; a new readme.txt FAQ entry answers "Why does ATmosphere say it needs to reconnect?".

Other information:

  • Have you written new tests for your changes, if applicable?

Testing instructions:

  • Start the environment (npm run env-start) and connect a Bluesky account on Settings → ATmosphere.
  • Simulate a salt rotation: npm run wp-env run cli wp config shuffle-salts (or edit AUTH_KEY/AUTH_SALT).
  • Publish a post. Expected: the post save succeeds, sharing fails once, the connection is flagged — a global admin notice explains that the security keys changed and links to the settings page, and the post's ATmosphere editor panel shows "Reconnect on the settings page" instead of "Update the post to try again".
  • Reconnect on the settings page. Expected: the notice disappears and sharing works again.
  • Optional: define define( 'ATMOSPHERE_ENCRYPTION_KEY', 'some-long-random-secret' ); in wp-config.php, reconnect, then shuffle the salts again. Expected: sharing keeps working across the salt change.
  • composer lint and npm run env-test pass (920 tests).

Changelog entry

Two entries are already committed on the branch (.github/changelog/fix-decrypt-failure-recovery, .github/changelog/add-encryption-key-constant).

  • Automatically create a changelog entry from the details below.
Changelog Entry Details

Significance

  • Patch
  • Minor
  • Major

Type

  • Added - for new features
  • Changed - for changes in existing functionality
  • Deprecated - for soon-to-be removed features
  • Removed - for now removed features
  • Fixed - for any bug fixes
  • Security - in case of vulnerabilities

Message

…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.
Copilot AI review requested due to automatic review settings July 10, 2026 20:02
@pfefferle pfefferle self-assigned this Jul 10, 2026
@pfefferle pfefferle requested a review from a team July 10, 2026 20:02
@github-actions github-actions Bot added [Feature] API PDS API client [Feature] OAuth OAuth flow and authentication [Feature] WP Admin Admin UI and settings [Tests] Includes Tests PR includes test changes Docs labels Jul 10, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_changed vs decrypt_failed) and mark connections as needs_reauth on 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_KEY escape 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Docs [Feature] API PDS API client [Feature] OAuth OAuth flow and authentication [Feature] WP Admin Admin UI and settings [Tests] Includes Tests PR includes test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants