f19b50b2ad
Supports client migration from per-user UUID to machine fingerprint without consuming an extra maxDevices slot. Co-authored-by: Cursor <cursoragent@cursor.com>
77 lines
4.0 KiB
Markdown
77 lines
4.0 KiB
Markdown
# DndGamePlayerLicenseServer
|
||
|
||
Сервис лицензирования для **DNDGamePlayer**: активация по продуктовому ключу, выдача подписанного токена Ed25519, отзыв по `sub` (клиент опрашивает `GET /v1/status`, если задан `DND_LICENSE_STATUS_URL` в Electron main).
|
||
|
||
Репозиторий клиента: отдельный проект `dnd_player` — публичный ключ вшит в `app/shared/license/bundledPublicKey.ts` и должен соответствовать `LICENSE_PRIVATE_KEY_PEM` здесь.
|
||
|
||
## Переменные окружения
|
||
|
||
| Переменная | Описание |
|
||
|------------|----------|
|
||
| `LICENSE_PRIVATE_KEY_PEM` | Приватный ключ Ed25519 в PEM (PKCS#8), **обязательно** |
|
||
| `LICENSE_ADMIN_TOKEN` | Bearer-токен для `/v1/admin/*` (по умолчанию `change-me-admin`) |
|
||
| `DND_LICENSE_DATA_PATH` | Путь к `data.json` (по умолчанию `./data.json` в корне репозитория) |
|
||
| `PORT` | Порт (по умолчанию `3847`) |
|
||
|
||
## Подготовка
|
||
|
||
1. Скопируйте `data.example.json` → `data.json`.
|
||
2. Сгенерируйте пару Ed25519; **публичный** ключ (SPKI DER base64) вставьте в клиент в `bundledPublicKey.ts`.
|
||
|
||
```bash
|
||
node -e "const c=require('crypto');const kp=c.generateKeyPairSync('ed25519');console.log(kp.privateKey.export({type:'pkcs8',format:'pem'}));"
|
||
```
|
||
|
||
### Демо-ключ (только для разработки)
|
||
|
||
Публичный ключ по умолчанию в клиенте соответствует этому приватному PEM:
|
||
|
||
```
|
||
-----BEGIN PRIVATE KEY-----
|
||
MC4CAQAwBQYDK2VwBCIEIDNxA9U1VSG9zoOvcJ5uB+JUe25UD5m9UwMi6slXzW44
|
||
-----END PRIVATE KEY-----
|
||
```
|
||
|
||
## Запуск
|
||
|
||
На Windows перед запуском задайте переменную окружения `LICENSE_PRIVATE_KEY_PEM` (многострочное значение в кавычках или через `.env` и загрузчик по желанию).
|
||
|
||
```bash
|
||
npm start
|
||
```
|
||
|
||
## Продакшен
|
||
|
||
Публичный endpoint: **`https://license.ttrpgplayer.ru/`** (nginx → `127.0.0.1:3847` на VPS).
|
||
|
||
Проверка: `curl https://license.ttrpgplayer.ru/health` → `{"ok":true}`.
|
||
|
||
Код на сервере: `/var/www/license_mailib_ru`, PM2-процесс `dnd-license`.
|
||
|
||
## Тесты
|
||
|
||
Из корня репозитория:
|
||
|
||
```bash
|
||
npm test
|
||
```
|
||
|
||
Запускает модульные тесты Node (`node --test`, см. `package.json` → `test`).
|
||
|
||
## API
|
||
|
||
- `POST /v1/activate` — `{ "productKey": "...", "deviceId": "...", "retireDeviceId?": "..." }` → `{ token, sub }`. `retireDeviceId` (опционально) удаляет старый слот устройства при миграции (не занимает лишний `maxDevices`).
|
||
- `GET /v1/status?sub=...` → `{ revoked: boolean }`.
|
||
- `POST /v1/track/download` — `{ "platform": "windows"|"macos"|"linux" }` → учёт скачиваний в `data.json` → `downloadStats.byMonth`.
|
||
- `POST /v1/admin/revoke` — `Authorization: Bearer <LICENSE_ADMIN_TOKEN>`, тело `{ "key": "TTRPG-..." }` или `{ "sub": "..." }`.
|
||
- `POST /v1/admin/product-keys/delete` — hard delete ключа `{ "key": "..." }`.
|
||
- `POST /v1/admin/issue` — админская выдача (`sub`, `pid`, `iat`, `exp`, `did`).
|
||
- `GET /v1/admin/licenses` — список ключей (новые сверху), поля `createdAtSec`, `expiryMode`, `validDays`, …
|
||
- `GET /v1/admin/stats/downloads` — обзор `{ months, currentMonth }`; `?month=YYYY-MM` — статистика за месяц.
|
||
- `POST /v1/admin/product-keys` — создание ключа (`pid`, `maxDevices`, `validDays` или `expiresAtSec`, опционально `count` 1–100).
|
||
- `GET /health` — `{ ok: true }`.
|
||
|
||
## Клиент
|
||
|
||
`DND_LICENSE_STATUS_URL=http://localhost:3847/` (база URL для сборки пути `v1/status`).
|