Skip to content

feat: one-click Cloud connect, digest consent, entitlements sync - #42

Merged
cport1 merged 3 commits into
mainfrom
feat/cloud-connect
Jul 23, 2026
Merged

cport1 merged 3 commits into
mainfrom
feat/cloud-connect

Conversation

@cport1

@cport1 cport1 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What this does

Implements the WordPress side of one-click Cloud connect (#40) and the entitlements sync + Free Connected UI state (#41), per the authoritative connect API contract in both issues.

The flow (issue #40)

  1. Connect click — In the WebDecoy Cloud settings tab, a primary Connect to WebDecoy Cloud button sits above a Send me a monthly security report consent checkbox (default unchecked). Clicking posts to admin-post.php (nonce-protected). The handler mints a 64-hex CSPRNG nonce, stores it in a 15-minute site transient, and redirects the browser to:
    https://app.webdecoy.com/connect/wordpress?site_url=…&site_name=…&nonce=<64-hex>&digest={0|1}&return_url=<enc admin URL>
    
  2. Return handler — On the settings page return (?page=webdecoy&tab=cloud&wd_connect=1&wd_connect_token=…), the plugin verifies manage_options + that the stored transient nonce still exists, then server-side wp_remote_posts the token to https://api.webdecoy.com/api/v1/connect/wordpress/exchange with {connect_token, site_url, nonce}. On 200 it persists api_key, site_key, organization_id, organization_name, plan, burns the transient, and shows a success notice. Denial (wd_connect_error=denied) or any error surfaces a clean notice with no state change.
  3. New class includes/class-webdecoy-cloud-connect.php owns the whole flow, wired into webdecoy.php beside the sibling feature classes.

The manual API key / site key fields are unchanged — they just move into a collapsed Advanced: manual configuration <details> fold.

Entitlements + connected UI (issue #41)

  • Sync — After a successful connect and then twice daily via cron (webdecoy_sync_entitlements), the plugin GETs https://ingest.webdecoy.com/api/v1/sdk/entitlements authenticated exactly like the violation reporter — the API key as an Authorization: Bearer <api_key> header. The normalized response is cached in the webdecoy_entitlements option with a fetched_at stamp; entries older than 12h are flagged stale but still served; any error fails open to free (all features false).
  • AccessorWebDecoy_Cloud_Connect::get_entitlements() always returns a complete, typed array.
  • Connected UI — org name, plan badge (Free Connected for free_connected, else the humanized plan name), monthly-report status line, Upgrade link to https://app.webdecoy.com/billing, and a nonce-protected Disconnect button that clears keys/entitlements/org locally (no remote call, per P0).

Zero-external-calls guarantee

The reviewed 2.2.3 property holds: no external HTTP call happens until the user clicks Connect. On a fresh/unconnected install, handle_connect never fires, the return handler returns early, the entitlements cron is never scheduled, and sync_entitlements bails when no API key is present. The exchange POST and entitlements GET only ever run after an explicit, user-initiated connect.

Notes

  • PHP 7.4-safe (no 8.0+ syntax); class constants use literals so the file loads without WP constants (enables dependency-free tests).
  • Plugin Check clean: all input sanitized/unslashed, actions nonce-verified, output escaped; phpcs.xml.dist security ruleset and phpstan (level 3) both pass on touched files with zero new errors. No inline <style>/<script> — styling lives in the enqueued admin stylesheet.
  • Connect/disconnect use two standalone forms outside the settings form (no nested forms); the tab controls target them via the HTML5 form="" attribute.

Tests

tests/CloudConnectTest.php added to the dependency-free suite (php tests/run.php, now 48 passed, 0 failed): entitlements normalization (fail-open, feature coercion, 12h staleness boundary, garbage-shape safety), is_hex nonce validation, connect-token sanitization + length cap, and plan labelling.

Closes #40, Closes #41. Part of WebDecoy/app#306.

cport1 added 3 commits July 23, 2026 07:39
Add WebDecoy_Cloud_Connect owning the user-initiated connect handshake and
the entitlements sync:

- handle_connect: mint a 64-hex nonce (15-min transient), redirect the browser
  to app.webdecoy.com/connect/wordpress with site details, digest-consent bit,
  and a return URL. First point the plugin ever leaves the site, and only on an
  explicit click.
- maybe_handle_return: on the settings-page return, verify manage_options + the
  one-time transient nonce, then server-side POST the token to
  api.webdecoy.com/.../exchange and persist api_key/site_key/org/plan.
- sync_entitlements: GET ingest.webdecoy.com/.../sdk/entitlements with the API
  key as a Bearer token (same auth scheme as WebDecoy_Violation_Reporter),
  cache the normalized result with a fetched_at stamp, fail open to free on any
  error. Runs after connect and twice daily via cron.
- handle_disconnect: clear keys/org/entitlements locally, no remote call.

Wire into webdecoy.php: store_cloud_credentials/clear_cloud_credentials
(API key encrypted at rest like the manual path; sanitizer bypassed for these
pre-shaped writes), org/plan defaults, and sanitize_options carry-forward so a
settings save never wipes connection metadata. Clean up the entitlements option
+ cron on deactivate/uninstall.

Zero external HTTP calls until the admin clicks Connect.

Part of WebDecoy/app#306
Rework the WebDecoy Cloud settings tab:

- Disconnected: primary "Connect to WebDecoy Cloud" button with a "Send me a
  monthly security report" consent checkbox (default off) above it; the manual
  API key / site key fields (behavior unchanged) move into a collapsed
  "Advanced: manual configuration" <details> fold.
- Connected: org name, plan badge ("Free Connected" for free_connected, else
  the humanized plan name), monthly-report status line, Upgrade link to
  app.webdecoy.com/billing, and a nonce-protected Disconnect button.

Connect/disconnect post to admin-post.php via two standalone forms placed
outside the settings form (no nested forms); the tab controls target them with
the HTML5 form="" attribute. Settings page honors ?tab=cloud so the connect
return lands on this tab without the JS hash router. Styling added to the
enqueued admin stylesheet (no inline CSS).
Add tests/CloudConnectTest.php to the dependency-free suite (php tests/run.php):
entitlements normalization (fail-open to free, feature coercion, 12h staleness
boundary, garbage-shape safety), is_hex nonce validation, connect-token
sanitization + length cap, and plan labelling.
@cport1
cport1 merged commit 21f5630 into main Jul 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Entitlements sync + Free Connected UI state One-click Cloud connect flow (+ digest consent)

1 participant