chore: license server updates and tests

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-05-11 22:20:21 +08:00
parent 4a0523f309
commit d595f4ca5a
6 changed files with 66 additions and 18 deletions
+11 -1
View File
@@ -34,11 +34,21 @@ MC4CAQAwBQYDK2VwBCIEIDNxA9U1VSG9zoOvcJ5uB+JUe25UD5m9UwMi6slXzW44
## Запуск ## Запуск
На Windows перед запуском задайте переменную окружения `LICENSE_PRIVATE_KEY_PEM` (многострочное значение в кавычках или через `.env` и загрузчик по желанию).
```bash ```bash
npm start npm start
``` ```
На Windows перед запуском задайте переменную окружения `LICENSE_PRIVATE_KEY_PEM` (многострочное значение в кавычках или через `.env` и загрузчик по желанию). ## Тесты
Из корня репозитория:
```bash
npm test
```
Запускает модульные тесты Node (`node --test`, см. `package.json``test`).
## API ## API
+18
View File
@@ -0,0 +1,18 @@
import { createPrivateKey, sign } from 'node:crypto';
import { canonicalJson } from './canonicalJson.mjs';
export function b64url(buf) {
return Buffer.from(buf)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/u, '');
}
export function signPayload(payload, privateKeyPem) {
const key = createPrivateKey(privateKeyPem);
const body = canonicalJson(payload);
const sig = sign(null, Buffer.from(body, 'utf8'), key);
return `${b64url(Buffer.from(body, 'utf8'))}.${b64url(sig)}`;
}
+2 -1
View File
@@ -5,7 +5,8 @@
"type": "module", "type": "module",
"description": "Сервис выдачи и отзыва лицензий DNDGamePlayer (Ed25519)", "description": "Сервис выдачи и отзыва лицензий DNDGamePlayer (Ed25519)",
"scripts": { "scripts": {
"start": "node src/server.mjs" "start": "node src/server.mjs",
"test": "node --test test/*.mjs"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
+2 -16
View File
@@ -1,10 +1,11 @@
import { createPrivateKey, randomUUID, sign } from 'node:crypto'; import { randomUUID } from 'node:crypto';
import fs from 'node:fs'; import fs from 'node:fs';
import http from 'node:http'; import http from 'node:http';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { canonicalJson } from '../lib/canonicalJson.mjs'; import { canonicalJson } from '../lib/canonicalJson.mjs';
import { signPayload } from '../lib/signPayload.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(__dirname, '..'); const root = path.resolve(__dirname, '..');
@@ -20,21 +21,6 @@ function writeData(data) {
fs.writeFileSync(p, `${JSON.stringify(data, null, 2)}\n`, 'utf8'); fs.writeFileSync(p, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
} }
function b64url(buf) {
return Buffer.from(buf)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/u, '');
}
function signPayload(payload, privateKeyPem) {
const key = createPrivateKey(privateKeyPem);
const body = canonicalJson(payload);
const sig = sign(null, Buffer.from(body, 'utf8'), key);
return `${b64url(Buffer.from(body, 'utf8'))}.${b64url(sig)}`;
}
function json(res, code, obj) { function json(res, code, obj) {
const body = JSON.stringify(obj); const body = JSON.stringify(obj);
res.writeHead(code, { res.writeHead(code, {
+15
View File
@@ -0,0 +1,15 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { canonicalJson } from '../lib/canonicalJson.mjs';
void test('canonicalJson: stable key order', () => {
const a = canonicalJson({ b: 2, a: 1 });
const b = canonicalJson({ a: 1, b: 2 });
assert.equal(a, b);
assert.equal(a, '{"a":1,"b":2}');
});
void test('canonicalJson: rejects non-finite number', () => {
assert.throws(() => canonicalJson({ x: NaN }), TypeError);
});
+18
View File
@@ -0,0 +1,18 @@
import assert from 'node:assert/strict';
import { generateKeyPairSync, verify } from 'node:crypto';
import test from 'node:test';
import { signPayload } from '../lib/signPayload.mjs';
void test('signPayload produces verifiable Ed25519 token', () => {
const { privateKey, publicKey } = generateKeyPairSync('ed25519');
const pem = privateKey.export({ type: 'pkcs8', format: 'pem' });
const payload = { v: 1, sub: 's1', pid: 'p', iat: 1, exp: 2, did: 'd1' };
const token = signPayload(payload, pem);
const [bodyB64, sigB64] = token.split('.');
assert.ok(bodyB64 && sigB64);
const body = Buffer.from(bodyB64, 'base64url');
const sig = Buffer.from(sigB64, 'base64url');
const ok = verify(null, body, publicKey, sig);
assert.equal(ok, true);
});