# Vectra Phase 5 Device Authorization — Server Deployment Notes

## Scope

This package adds the DRM-server side of Phase 5 Account / Device Authorization on top of the accepted Phase 4 granular purchase sync baseline.

New public endpoints:

```text
POST /api/vectra/sync_start.php
POST /api/vectra/sync_poll.php
POST /api/vectra/sync_approve.php
```

Friendly rewrite routes are also added:

```text
/api/vectra/sync/start
/api/vectra/sync/poll
/api/vectra/sync/approve
```

The implementation does **not** add OAuth/OIDC, username/password login, subscription logic, public add-on catalog logic, read-path flip, live bridge mode, or historical backfill.

## Deploy order

1. Back up the staging database.
2. Deploy the changed PHP files.
3. Add private config values described below.
4. Run the migration:

   ```bash
   mysql --default-character-set=utf8mb4 -u DB_USER -p DB_NAME < migrations/2026_05_11_phase5_sync_sessions.sql
   ```

5. Run fixture checks:

   ```bash
   /opt/alt/python39/bin/python3 tests/test_phase5_device_authorization_fixtures.py
   /opt/alt/python39/bin/python3 tests/test_phase4_granular_purchase_sync_fixtures.py
   ```

6. Confirm HTTPS is active for `/sync_approve.php`. The approval endpoint rejects non-HTTPS requests.
7. Configure WordPress to call `/api/vectra/sync_approve.php` with the HMAC headers and payload from the Phase 5 spec.

## Required private config

Place secrets in the private `vectra_env.php`, or provide the equivalent environment variables.

```php
'sync_sessions' => [
    'hash_secret' => 'different-long-random-secret-per-environment',
    'ttl_seconds' => 600,
    'poll_interval_seconds' => 5,
    'max_failed_approval_attempts' => 5,
    'retention_days' => 14,
    'start_ip_limit_per_hour' => 30,
    'start_machine_limit_per_hour' => 10,
    'verification_url_base' => 'https://audionerdz.net/my-account/vectra-connect',
    'approval' => [
        'issuer' => 'audionerdz-wordpress',
        'audience' => 'vectra-drm-staging',
        'scope' => 'vectra.sync.approve',
        'kid' => 'staging-YYYY-MM',
        'secret' => 'different-long-random-approval-secret',
        'keys' => [
            'staging-YYYY-MM' => 'different-long-random-approval-secret',
        ],
        'timestamp_window_seconds' => 300,
    ],
],
```

Environment-variable equivalents:

```text
VECTRA_SYNC_SESSION_HASH_SECRET
VECTRA_SYNC_VERIFICATION_URL_BASE
VECTRA_SYNC_APPROVAL_SECRET
VECTRA_SYNC_APPROVAL_KID
VECTRA_SYNC_APPROVAL_ISSUER
VECTRA_SYNC_APPROVAL_AUDIENCE
VECTRA_SYNC_APPROVAL_SCOPE
VECTRA_SYNC_APPROVAL_KEYS_JSON
```

Staging and production must use different values for `sync_sessions.hash_secret`, approval HMAC secret(s), claim-token secret, and webhook secret.

## Endpoint behavior

### `sync_start.php`

Unauthenticated by design. Requires POST JSON:

```json
{
  "product": "Vectra",
  "pluginVersion": "1.0.0",
  "machineFingerprint": "<stable fingerprint JSON>",
  "platform": "macOS",
  "osVersion": "14.x"
}
```

Stores only HMAC hashes for the device code, user code, IP, and machine fingerprint. The full machine fingerprint is retained only while the session is pending/approved and is nulled on completion, expiry, or denial.

### `sync_poll.php`

Requires POST JSON:

```json
{
  "syncSessionId": "sess_...",
  "deviceCode": "dev_..."
}
```

The first valid poll after approval reuses Phase 4 license construction/signing:

```text
Free baseline assertion
→ active SKU loading
→ entitlement derivation
→ signed V2 license response
```

That poll marks the session `completed`, stores only `license_fingerprint_hash`, clears `machine_fingerprint_json`, and returns the signed license once. Later polls do not regenerate or return a license.

### `sync_approve.php`

Called server-to-server by WordPress after a logged-in user explicitly approves or denies.

Required headers:

```text
X-Vectra-Approval-Timestamp
X-Vectra-Approval-Signature
X-Vectra-Approval-Key-Id
```

Signature message:

```text
timestamp + "." + raw_json_body
```

The endpoint validates timestamp freshness, key ID, raw-body HMAC-SHA256 signature with constant-time comparison, issuer, audience, scope, `issuedAt`, positive `wpUserId`, nonce replay, user-code mapping, expiry, and pending state.

## Operational notes

- `sync_approve.php` requires HTTPS and POST.
- `deviceCode` is never placed in the browser URL.
- `userCode` is a human 40-bit code (`XXXX-XXXX`) using `23456789ABCDEFGHJKLMNPQRSTUVWXYZ`.
- Active pending user-code collisions are checked transactionally with `FOR UPDATE` and retried.
- Failed approval attempts are counted; after the configured limit the session is denied and the full machine fingerprint is removed.
- Opportunistic cleanup expires stale pending/approved sessions and deletes terminal sessions after the configured retention period.

## Rollback

This change is additive. To roll back the Phase 5 server path, remove the three new rewrite routes and stop routing WordPress/client traffic to the new endpoints. Existing Phase 4 `license_refresh.php`, manual token fallback, Free mode, and legacy activation endpoints are not mutated by this package.
