CertifiEd

Быстрый старт

Сквозной путь: компания → шаблон → версия → выпуск лицензии → интеграция SDK. Команды проверены против API на http://localhost:5100 с seed-оператором admin@certified.local / changeme.

0. Предпосылки

  • CertifiEd API запущен и поднят с CERTIFIED_MASTER_KEY (base64, 32 байта).
  • Установлены curl и — для HMAC-подписи heartbeat — openssl.
bash
BASE=http://localhost:5100
curl -s $BASE/health# {"status":"ok", ...}

1. Логин оператора

Panel API работает по HttpOnly-cookie certified.auth; сохраняем её в cookie-jar cj.txt.

bash
curl -s -c cj.txt -X POST $BASE/api/v1/panel/auth/login \  -H 'Content-Type: application/json' \  -d '{"email":"admin@certified.local","password":"changeme"}'
200 OK
{  "id": "019f69e4-2a5d-7c3c-bb39-93d6829d92a5",  "email": "admin@certified.local",  "displayName": "Operator",  "isOperator": true}

Дальше во все panel-запросы добавляем -b cj.txt.

2. Компания-лицензиат

Корневую компанию создаёт оператор (parentId: null).

bash
curl -s -b cj.txt -X POST $BASE/api/v1/panel/companies \  -H 'Content-Type: application/json' \  -d '{"name":"Globex Inc","slug":"globex-inc","parentId":null,       "contactEmail":"it@globex.example"}'
201 Created
{  "id": "019f6b45-927c-7c2b-978c-91e7689fe7f3",  "name": "Globex Inc",  "slug": "globex-inc",  "parentId": null,  "path": "c019f6b45927c7c2b978c91e7689fe7f3",  "depth": 0,  "status": "Active",  "contactEmail": "it@globex.example",  "createdAt": "2026-07-16T14:12:29.43+00:00"}

Запомним id как COID. slug — 1–64 символа [a-z0-9-], не начинается и не заканчивается дефисом, уникален среди сиблингов.

3. Шаблон продукта

Создание шаблона автоматически генерирует первую активную Ed25519 signing-key.

bash
curl -s -b cj.txt -X POST $BASE/api/v1/panel/templates \  -H 'Content-Type: application/json' \  -d '{"name":"Curl Demo","productCode":"curl-demo","description":"demo",       "defaultOfflineDays":7,"defaultValidityDays":365}'
201 Created
{  "id": "019f6b45-92bf-758a-8955-131b7c9cc364",  "name": "Curl Demo",  "productCode": "curl-demo",  "defaultOfflineDays": 7,  "defaultValidityDays": 365,  "status": "Active",  "currentVersionId": null,  "createdAt": "2026-07-16T14:12:29.50+00:00"}

Запомним id как TPLID. currentVersionId пока null — версии ещё нет.

4. Версия шаблона

Версия несёт JSON-схему конфигурации и defaults. configSchema и defaultsстроки JSON (внутренние кавычки экранируются). Созданная версия сразу становится текущей.

bash
curl -s -b cj.txt -X POST $BASE/api/v1/panel/templates/$TPLID/versions \  -H 'Content-Type: application/json' \  -d '{"configSchema":"{\"type\":\"object\"}",       "defaults":"{\"features\":[]}","changelog":"v1"}'
201 Created
{  "id": "019f6b45-92f8-71e5-bd7a-aa95cf5644a1",  "templateId": "019f6b45-92bf-758a-8955-131b7c9cc364",  "version": 1,  "configSchema": "{\"type\":\"object\"}",  "defaults": "{\"features\":[]}",  "signingKeyId": "019f6b45-92c3-7a09-8bdf-24b8ef0b8ba5",  "changelog": "v1",  "createdAt": "2026-07-16T14:12:29.56+00:00"}

5. Выпуск лицензии

Ключевой момент: поле config — это строка JSON, которая встраивается в подписанный токен. Именно её потом читают HasFeature() и GetConfig<T>(). Здесь кладём фичи export / api и лимит limits.maxSeats = 25.

bash
curl -s -b cj.txt -X POST $BASE/api/v1/panel/licenses \  -H 'Content-Type: application/json' \  -d '{        "companyId": "'"$COID"'",        "templateId": "'"$TPLID"'",        "config": "{\"features\":[\"export\",\"api\"],\"limits\":{\"maxSeats\":25}}",        "offlineDays": 7      }'
201 Created
{  "id": "019f6b45-9312-7c41-b1fb-2e2539eaac9d",  "licenseKey": "CFED-3NCR-4ZER-BAEA-YREY",  "token": "eyJhbGciOiJFZDI1NTE5Iiwia2lkIjoi...<three segments>...",  "publicKey": "67619cd9aac91085cb5be3b93a9fbca97e5b06c21fcef6a7fdf3b182e16487d8"}

expiresAt не задали → срок = now + defaultValidityDays шаблона. publicKey отдаётся в hex. Запомним licenseKey как KEY, id — как LICID.

6. Скачать .ced и публичный ключ

bash
# License file: JSON envelope { licenseKey, token, publicKey }curl -s -b cj.txt $BASE/api/v1/panel/licenses/$LICID/download -o license.ced
# Public key on its own (hex)curl -s -b cj.txt $BASE/api/v1/panel/licenses/$LICID/public-key# {"publicKey":"67619cd9aac9...87d8"}

license.ced можно отдать клиентскому приложению как есть — SDK читает и JSON-конверт, и «голый» токен одной строкой.

7. Клиентская сторона: SDK

Обычно проверкой занимается SDK — он делает всё из шагов 8–9 самостоятельно.

C#
using CertifiEd.Client;
var options = new CertifiEdClientOptions{    ServerUrl       = "http://localhost:5100",    LicenseFilePath = "license.ced",    PublicKey       = Convert.FromHexString(        "67619cd9aac91085cb5be3b93a9fbca97e5b06c21fcef6a7fdf3b182e16487d8"),};
await using var license = new CertifiEdLicenseClient(options);if (!await license.InitializeAsync())    return;                                       // missing file / bad signature / expired
Console.WriteLine(license.Status);                // ActiveConsole.WriteLine(license.HasFeature("export"));  // TrueConsole.WriteLine(license.GetConfig<int>("limits.maxSeats")); // 25

Детали — в разделе Клиентский SDK. Ниже — те же клиентские вызовы «руками», через client API.

8. Активация (client API, без HMAC)

activate использует сам license key как общий секрет — HMAC-подпись не нужна.

bash
curl -s -X POST $BASE/api/v1/client/activate \  -H 'Content-Type: application/json' \  -d '{"licenseKey":"'"$KEY"'","hwFingerprint":"demo-fp-001",       "machineName":"demo-host"}'
200 OK
{  "activationId": "019f6b45-9362-78f1-aec3-1e3e105a3bf8",  "heartbeatToken": "eyJ0eXAiOiJjZXJ0aWZpZWQt...",  "heartbeatIntervalSeconds": 3600,  "maxOfflineDays": 7}

heartbeatToken — подписанный offline-маркер; SDK кэширует его рядом с лицензией как license.ced.hb. Запомним activationId как ACTID.

9. Heartbeat (HMAC-подпись)

heartbeat и deactivate подписываются: X-CertifiEd-Signature: v1=hex(HMAC_SHA256(key = licenseKey, msg = timestamp)), где timestamp — Unix-секунды, окно ±5 минут.

bash
TS=$(date +%s)SIG=$(printf '%s' "$TS" | openssl dgst -sha256 -hmac "$KEY" -hex | sed 's/^.*= //')
curl -s -X POST $BASE/api/v1/client/heartbeat \  -H 'Content-Type: application/json' \  -H "X-CertifiEd-Timestamp: $TS" \  -H "X-CertifiEd-Signature: v1=$SIG" \  -d '{"licenseKey":"'"$KEY"'","activationId":"'"$ACTID"'"}'
200 OK
{ "heartbeatToken": "eyJ0eXAiOiJjZXJ0aWZpZWQt..." }

HTTP 200 и свежий маркер. Деактивация выполняется так же, но через POST /api/v1/client/deactivate — вернёт 204.

Проверка со стороны панели

bash
curl -s -b cj.txt $BASE/api/v1/panel/licenses/$LICID/activationscurl -s -b cj.txt "$BASE/api/v1/panel/licenses/$LICID/heartbeats?limit=10"

Что дальше