Connect License class to the real auroraplugins.com license server

- License.php now calls the aurora-licensing plugin's REST API
  (/wp-json/aurora-licensing/v1/validate|activate|deactivate) instead of
  treating any non-empty license_key as active. Result is cached in the
  aurora_admin_license_status option for 12 hours; is_active() and
  is_feature_available() read the cache instead of hitting the network on
  every admin page load.
- Hooks update_option_aurora_admin_settings to activate/deactivate
  immediately when the license_key setting actually changes, rather than
  waiting out the cache. Instantiated in AuroraAdmin.php so that hook
  registers.
- Settings page now surfaces the real status (active/invalid/unreachable,
  with the server's own message) as a status line under the License key
  field, instead of a field that silently does nothing.
- Verified live against the real server: a fake test key correctly came
  back locked with the server's own "License key not found." message.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
nixpanel
2026-07-03 20:26:00 -05:00
co-authored by Claude Sonnet 5
parent eb03d3dd6c
commit 540083e6e4
6 changed files with 264 additions and 30 deletions
+87 -22
View File
@@ -1,6 +1,6 @@
# Aurora Admin v2 — Project Status
_Last updated: 2026-07-03 (latest session — free/paid feature gating (licensing))_
_Last updated: 2026-07-04 (latest session — real license-server integration)_
## What this project is
@@ -65,6 +65,20 @@ get back up to speed instead of relying on prior chat history.
- **Git**: local repo config only (`user.name`/`user.email` set per-repo, not
`--global`). Commit after each user-confirmed-working change, not before.
- Site under test: `http://admin.local/wp-admin/`.
- **License server access**: `auroraplugins.com` runs on the same cPanel
box as the git remote, `auroradragon.studio` (hostname
`wyvern.auroradragon.studio`), but the git remote's SSH key
(`~/.ssh/id_ed25519`) is scoped to git-only — no shell. Root shell access
is a separate dedicated key, `~/.ssh/id_ed25519_auroradragon_root`
(`ssh -i ~/.ssh/id_ed25519_auroradragon_root [email protected]`).
The license server itself is the `aurora-licensing` WordPress plugin at
`/home/aurora/public_html/wp-content/plugins/aurora-licensing/aurora-licensing.php`
(also present under `/home/plugins/public_html/...` — a second copy on
the same box). **If SSH to port 22 suddenly times out**, something on the
box (fail2ban or similar) appears to re-trigger a short block on rapid
repeated connection attempts, including plain `nc`/probe connections —
it clears on its own after roughly 1-2 minutes; wait it out with one
careful attempt rather than retrying in a tight loop.
## Architecture
@@ -449,13 +463,56 @@ Creator, Admin Notices, Comments, and the Plugins page stay free.
- `admin/src/Licensing/License.php` is the single gate every paid feature
checks — `PAID_FEATURES` maps a feature id to a human label,
`is_active()` and `is_feature_available($feature)` are the two calls
everything else uses. **`is_active()` is a placeholder today**: any
non-empty `license_key` setting counts as licensed, no remote validation
at all. There's no license server yet — when one exists, only
`is_active()`'s internals need to change (ideally caching the remote
check in a transient so it isn't a network call on every admin page
load); every gated feature already funnels through this class rather than
reading the setting directly, so nothing else needs to change.
everything else uses.
- **Real license-server integration** (this session, supersedes the earlier
"any non-empty key counts as active" placeholder): validates against the
actual license server — the `aurora-licensing` WordPress plugin running
on `auroraplugins.com` (a cPanel box at `auroradragon.studio`, SSH-
accessible as root via a dedicated key,
`~/.ssh/id_ed25519_auroradragon_root` — the git-remote key is scoped
git-only and can't get a shell). REST namespace
`/wp-json/aurora-licensing/v1/`:
- `POST /validate` — `{license_key, domain, product_id?}` →
`{valid, status, plan, max_sites, active_sites, active_on_domain,
expires_at, product_id}` on success, `{valid:false, error, message}`
on failure (e.g. `invalid_key`).
- `POST /activate` — same request shape → `{activated, plan, max_sites,
expires_at}`, or `{activated:true, message:"Already activated..."}` if
already active on this domain, or a 403 `{valid:false,
error:"limit_reached", message}` if `max_sites` is exceeded.
- `POST /deactivate` — `{license_key, domain}` → `{deactivated, message}`.
- `domain` is normalized server-side (strips protocol/`www.`/trailing
slash) — `License::domain()` just sends the bare host from `home_url()`.
- `GET /my-licenses` (cookie-auth, lists all of a customer's licenses) is
the account-portal-facing endpoint, not used by the plugin itself.
- `License::status()` caches the `/validate` response in the
`aurora_admin_license_status` option for 12 hours
(`STATUS_TTL`) — `is_active()`/`is_feature_available()` read this
cache rather than hitting the network on every admin page load.
- `License::on_settings_updated()` hooks `update_option_aurora_admin_settings`
(same hook `ActivityLog.php` uses) and re-activates/deactivates
immediately whenever `license_key` actually changes, rather than
waiting out the cache TTL — deactivates the old key's slot on this
domain before activating the new one, so switching keys doesn't leak
an activation. Instantiated in `AuroraAdmin.php` (`new License();`)
so this hook actually registers — it wasn't needed when the class was
static-only.
- Network failures/non-JSON responses collapse into a synthetic
`{valid:false, status:"unreachable", ...}` shape (`License::call()`)
so callers only ever branch on response fields, never on `WP_Error`.
- **Settings UI feedback**: `SettingsPage.php` passes `License::status()`
to the Vue settings page as `licenseStatus`; `Settings.vue` forwards it
only to the `license_key` field; `SettingsField.vue` shows a colored
status line under the field (green "License active (plan) — expires
X", amber "unreachable, will retry", red the server's own error
message) — verified live against the real server with a fake test key
and got back the exact `{"valid":false,"error":"invalid_key","message":
"License key not found."}` shape.
- **Not yet done**: no real license key was available to test the
activate/site-limit-reached path end-to-end (only the invalid-key path
was verified against the live server) — worth a follow-up pass once a
real test key exists. No UI yet for the `limit_reached` case beyond
surfacing its message string.
- **Sub-apps** (`SubApps.php`): `PAGES` now has a `"feature"` key per page
(`null` for the 4 free ones). Locked pages still fully register
(`add_submenu_page`, real capability, real URL — same as before) so nav/
@@ -614,15 +671,22 @@ scrollbar with a large empty gap below the frame. Also made the sidebar's
native-menu sections a single-open accordion instead of letting multiple
stay expanded at once (pushed as commit `73591e0`).
Most recently: added the free/paid feature-gating system end-to-end (see
"Licensing" above) — Role Editor, Activity Log, Database Explorer,
Analytics, White Label, and the remote site switcher are now paid; Menu
Creator, Admin Notices, Comments, Plugins, and the whole shell/theming
product stay free. Verified both directions live (entering/clearing a
license key immediately locks/unlocks the sidebar badges, the sub-app
pages, the Dashboard's Analytics tab, and the underlying REST endpoints),
and fixed an unrelated pre-existing 500 in Role Editor's REST endpoint
along the way (pushed as commit `6d50260`). The distributable zip
An earlier pass this session added the free/paid feature-gating system
end-to-end (see "Licensing" above) — Role Editor, Activity Log, Database
Explorer, Analytics, White Label, and the remote site switcher are now
paid; Menu Creator, Admin Notices, Comments, Plugins, and the whole
shell/theming product stay free. That pass used a placeholder
`is_active()` (any non-empty key counted as licensed) and fixed an
unrelated pre-existing 500 in Role Editor's REST endpoint along the way
(pushed as commit `6d50260`).
Most recently: replaced that placeholder with real validation against the
license server at `auroraplugins.com` (see "Real license-server
integration" above) — `/validate`/`/activate`/`/deactivate` calls, 12-hour
cached status, immediate re-check on a license-key change, and a status
line in the Settings UI. Verified live end-to-end against the real server
with a fake test key, which correctly came back locked with the server's
own `"License key not found."` message. The distributable zip
(`dist/aurora-admin-v2.zip`) was rebuilt fresh from source and should be
re-verified/re-zipped again after this doc update if anything changed since.
@@ -632,8 +696,9 @@ hot-patched into
the repo's source files are the actual edits; that copy is just the
compiled build artifact, not a separate edit surface).
**Not yet built**: an actual license-purchase/checkout flow or remote
validation server — today entering *any* non-empty text in the License key
field unlocks every paid feature (see "Licensing" above). This is fine for
internal testing but must be replaced with real validation before this
ships to real users, or the paid tier isn't actually gated.
**Not yet done**: the activate/site-limit-reached path hasn't been tested
with a real, valid license key (only the invalid-key path was verified
against the live server) — worth a follow-up pass once a real test key is
available. No purchase/checkout flow lives in this repo (that's entirely
on the `auroraplugins.com`/WooCommerce side, which auto-generates keys on
order completion) — this repo only ever consumes the license API.