# 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": "..." }` → `{ token, sub }`. - `GET /v1/status?sub=...` → `{ revoked: boolean }`. - `POST /v1/track/download` — `{ "platform": "windows"|"macos"|"linux" }` → учёт скачиваний в `data.json` → `downloadStats.byMonth`. - `POST /v1/admin/revoke` — `Authorization: Bearer `, тело `{ "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`).