# License Admin Console

This guide covers all license-related pages in the unified admin console (`/admin/`).

---

## Getting Started

1. **Sign in**: Navigate to `/admin/login.php` and enter admin credentials.
2. **Sidebar**: The license module has these nav items:
   - **Licenses** — core license management
   - **Auths** — product authorizations (per-license feature gates)
   - **Plans** — license plans (Team, Enterprise, etc.)
   - **Auth Tokens** — per-product authorization tokens
   - **API Tokens** — API access tokens for programmatic access
   - **Certs** — X.509 client certificates
   - **Webhooks** — outbound event subscriptions
   - **Stats** — usage dashboard

3. **Common elements**:
   - Top bar shows your username; click to log out
   - Each page has filters (search, status, date range)
   - Bulk operations use the checkbox column
   - CSRF token required for all POST forms

---

## Licenses (`/admin/licenses.php`)

The core license management page. View all licenses, activate them, issue new ones, manage offline licenses.

### Top-level view

- Lists all licenses with: license key, holder, plan, status, expires_at
- Filters by holder name, batch number, license key
- Bulk actions: revoke, delete

### Operations

| Action | URL | What it does |
|---|---|---|
| Activate | `?page=activate` | Activate a license on a device, generate signed payload |
| Batch generate | `?page=batch` | Bulk-create licenses from a template |
| CSV import | `?page=import` | Import licenses from CSV file |
| Offline sign | `?page=offline` | Issue v2 offline license files |
| Activation logs | `?page=activation_logs` | Audit trail of all activation attempts |

### Activation form fields

- License key (dropdown of active licenses)
- Activation code (assigned to the license holder)
- Machine code (1-128 chars, `[A-Za-z0-9_-.]+`)
- Machine fingerprint (`sha256:<64 hex chars>`)
- Product (default: CoForen)
- Version (default: 0.8.1)

---

## Product Authorizations (`/admin/license_authorizations.php`)

Per-license, per-product feature grants. A license must be authorized for a product before clients can use product-specific features.

### Fields

- **License** (required) — select from active licenses
- **Product Slug** (required) — dropdown of known products (CoForen + custom)
- **Expires At** (optional) — when this authorization expires; leave blank for perpetual
- **Notes** (optional) — admin notes

### Operations

- Create new authorization
- Edit existing (extend expiry, toggle active)
- Revoke (soft delete via `is_active=0`)
- Delete (hard delete)
- Bulk delete

### Filters

- Search by license key / holder / slug
- Filter by product
- Filter by active status

---

## License Plans (`/admin/license_plans.php`)

CRUD for license plan definitions (Team, Enterprise, etc.). Plans determine features, member limits, and grace periods.

### Fields

- **Slug** (auto from name) — unique identifier (e.g., `team`, `enterprise`)
- **Name** — display name
- **Description** — free text
- **Max Members** — typical team size
- **Max Cases** — typical case load
- **Grace Days** — days after expiry during which the license still validates
- **Features** (checkboxes):
  - `case_map` — case mapping
  - `lan_collaboration` — multi-user LAN collaboration
  - `file_upload` — file upload
  - `graph_annotations` — graph annotations
  - `export` — export features
- **Sort Order** — display order
- **Active** — enable/disable

### Operations

- Create / Edit / Delete (with confirmation)
- Bulk activate / deactivate / delete

> Deletion fails with a clear error if any license still references the plan.

---

## Authorization Tokens (`/admin/license_tokens.php`)

Per-license, per-product authorization tokens. Useful for service-to-service auth where the calling party doesn't have an activation code.

### Issue a token

- **License** (required)
- **Product** (required)
- **Name** (required) — descriptive label
- **Valid Days** (1-3650, default 365)

After issue, the **plaintext token is shown ONCE**. Copy it immediately — it cannot be recovered.

### Operations

- View list (search by license, holder, name, product)
- Revoke (soft delete, sets `is_active=0`)
- Bulk revoke
- Bulk delete

---

## Active Admin User Setup

The Administrator admin user (login: `dynooob`, password: `Dy12345.`) is configured with full access. Password should be rotated periodically; use `/admin/system.php` → User Management to add additional admins.

---

## API Tokens (`/admin/license_api_tokens.php`)

Manage API tokens for programmatic access to License API endpoints.

### Generate

- **Name** (required) — descriptive label (e.g., "Production CoForen Client")
- **Scope** — fixed to `license`
- **Expires At** (optional) — leave blank for no expiry

After creation, the **plaintext token is shown ONCE** in a highlighted alert. Copy immediately.

### Operations

- View list (name, scope, expiry, last used)
- Regenerate — issue a new token, invalidates the old
- Revoke — disable without deleting
- Delete — hard delete

### Security best practices

- Use separate tokens for each integration (production, staging, dev)
- Set explicit expiry on test tokens
- Audit `last_used_at` for stale tokens
- Use the system page to monitor token activity

---

## X.509 Certificates (`/admin/license_certs.php`)

Manage client certificates for mTLS authentication.

### Generate certificate

- **Owner Name** (required) — Common Name in cert (e.g., "Zhang San")
- **Associated License** (optional) — link to a license record
- **Valid Days** (1-3650, default 365)

After generation, the **PEM certificate** is shown with a one-time download URL for the private key. The download URL is valid for 30 minutes and single-use.

### View certificate

Shows full X.509 details:
- Serial number
- Subject/Issuer DN
- Validity period
- Full PEM (with copy button)

### Revoke certificate

Marks the certificate as revoked. Revoked certificates are added to the CRL ([`/license/api/coforen/certs/crl/`]).

### Operations

- View list (filter by status: active, revoked, expired)
- Revoke single
- Bulk revoke
- Bulk delete (irreversible)
- Export PEM

---

## Webhooks (`/admin/license_webhooks.php`)

Subscribe to license events. The server sends HTTPS POST requests when registered events occur.

### Register

- **URL** — must be valid HTTPS URL
- **Scope** — `all` for everything, or specific event types like `license.activated`, `license.renewed`, `license.revoked`

After registration, a **shared secret** is shown ONCE. Use this to verify webhook authenticity via HMAC-SHA256 of the request body.

### Failed events panel

Failed deliveries are queued with exponential backoff and retry up to 5 times. Use the **Retry** button to manually requeue.

### Operations

- Toggle active (disable without deleting)
- Delete
- Bulk delete
- Manual retry of failed events

### Signature verification

Each webhook delivery includes:

```
X-License-Webhook-Signature: sha256=<hmac-sha256-hex>
X-License-Event: license.activated
Content-Type: application/json
```

Verify on your server:

```python
expected = hmac.new(secret.encode(), request.body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature_header.removeprefix("sha256=")):
    return 401
```

---

## Stats Dashboard (`/admin/license_stats.php`)

Read-only dashboard. Shows:

### Overview cards

- Total licenses
- Active licenses (green)
- Expired licenses (red)
- Expiring soon — within 7 days (yellow)
- In grace period
- Active devices
- Total activations
- Success rate (%)

### Charts
- Daily activations bar chart (filterable by period)
- License plan distribution (color-coded)

### Tables

- Top license holders (by device count)
- Activations by product

### Filters

- Date range (`from`, `to`)
- Product filter

---

## Common Tasks

### Issue a new license

1. Go to **Licenses** → **+ New License**
2. Fill in: holder, plan, product, expiry
3. Click **Create**
4. Note the generated `license_key`

### Authorize a license for a new product

1. Go to **Auths** → **+ New Authorization**
2. Select license + product
3. Optionally set expiry
4. Click **Create**

### Issue an API token for a partner

1. Go to **API Tokens** → **+ Generate**
2. Name it (e.g., "Partner X Integration")
4. Copy the plaintext immediately
5. Send via secure channel to the partner

### Issue a client certificate

1. Go to **Certs** → **+ Generate**
3. Fill in owner name + license (optional)
4. Click **Generate**
5. Copy certificate PEM
6. Click the private key download URL (within 30 minutes) to get the key
7. Distribute both to the client securely

### Set up a webhook

1. Go to **Webhooks** → **+ Register**
2. Enter the consumer URL
3. Choose scope (`all` recommended initially)
4. Click **Register**
5. Copy the secret for HMAC verification
6. Configure your endpoint to verify signatures

### Enable two-factor authentication

1. Go to **System** → **Security** tab
2. Click **Setup 2FA** — a 32-character base32 secret is generated
3. Add it to an authenticator app (Google Authenticator, 1Password, Authy):
   - type the secret manually, or
   - scan the `otpauth://` URI
4. Enter the 6-digit code shown in the app → **Confirm**
5. **Save the 8 backup codes** — they are shown once and cannot be retrieved later

Backup code format is `XXXX-XXXX-XXXX-XXXX`, e.g. `K7QP-3RWM-9XHT-B2ND`.
The alphabet excludes ambiguous characters (no `0/O`, `1/I`, `L`, `U`, `V`).
Entry is case-insensitive and dashes/spaces are ignored. Each code works once.

Use **重新签发备用码** to issue a fresh set; this invalidates all unused
previous codes. Disabling 2FA deletes every code for the account.

To disable 2FA, you must supply a currently valid 6-digit code.

### Review and revoke admin sessions

**System** → **Security** → Sessions panel lists every active session for your
account (IP, user agent, created, last active). Use **Revoke** on a specific
session, or **Revoke Others** to kill every session except the current one.

Sessions are stored in the `admin_sessions` table. Revoked rows keep a
`revoked_at` timestamp for audit purposes.

---

## Security Notes

- All admin pages require authentication (`admin_current_user()`)
- All POST forms require CSRF token (`_csrf` hidden field)
- All actions are logged in `admin_audit_logs` table
- Failed login attempts are logged with username
- 5 consecutive failed logins lock the account for 15 minutes
- New admin passwords must be ≥12 characters with upper case, lower case,
  a digit, and a special character
- TOTP secrets are stored AES-256-GCM encrypted, never in plaintext
- See [SECURITY.md](SECURITY.md) for the full security model

## See Also

- [LICENSE_API.md](LICENSE_API.md) — Programmatic API
- [SECURITY.md](SECURITY.md) — Security model
- [DATABASE.md](DATABASE.md) — Database schema