Node — @yetdapay/license-server
핵심 요약
Node 22 서버에서 라이선스 기반 실행 권한을 검사하는 SDK입니다. 서명된 사용권과 기기 증명을 검증하고 일회용 Capability를 처리합니다.
최소 서버 예제부터 시작하되, 실제 운영에는 영속적이며 접근 제어가 가능한 저장소를 준비하세요.
sdk/license/server/ — 셀러의 BYOS(bring-your-own-server) 배포를 위한 강제 호스트. Node 22, 런타임 의존성 제로 — node:crypto와 node:sqlite만 사용(engines.node >= 22). Entitlement와 DPoP 스타일 증명을 검증하고, 일회성 capability를 발급하고 소비하며, 저장소 오류에서 fail closed됩니다.
설정
import {
createLicenseServer, createMemoryStore, generateKeyBundle,
} from '@yetdapay/license-server';
// 또는 레퍼런스 영속 저장소:
import { createSqliteStore } from '@yetdapay/license-server/stores/sqlite';
const server = createLicenseServer({
store: createSqliteStore({ file: './.data/license.db' }),
keys: { load: async () => JSON.parse(process.env.DEVPAY_LICENSE_KEYS) },
issuer: 'devpay',
audience: 'app:my-product',
entitlementTtlSec: 120,
});2
3
4
5
6
7
8
9
10
11
12
13
필수 입력:
- 서명/검증 키 — ES256 P-256 번들, 시크릿 매니저/KMS에서 주입 (
resolveKeys,generateKeyBundle,keyBundleFromEnv헬퍼 존재). 절대 커밋하지 마세요. LicenseStore— 영속 계약 구현(아래).- 상품 기능 정의 —
products.features가 유일한 entitlement 진실 공급원; 라이선스 행 기능은 신뢰되지 않습니다.
API 표면
import {
createLicenseServer, createWorkerConfig, WORKER_ISSUER, DEFAULTS,
toHttpResponse, requestTargetFromUrl,
signEntitlement, verifyEntitlement,
verifyProof, verifyProofSignature, readEntitlementToken, readHeader,
createMemoryStore, createSqliteStore, // stores
LicenseError, ErrorCode, asLicenseError,
} from '@yetdapay/license-server';2
3
4
5
6
7
8
createLicenseServer(options) → { activate(), authorize(), issueCapability() }:
activate({key, product_id, public_jwk})— JWK 검증(on-curve), 라이선스 상태로 게이트, 디바이스 슬롯을jkt에 원자적으로 바인딩 (UNIQUE(lic_id,jkt),count < max_devices) →{entitlement, expires_in, features}.authorize(headers, requestTarget, rawBody)— 가드 체인: entitlement sig/iss/exp→ 라이브 라이선스 재조회 → 증명 sig/htm/htu/iat→jkt일치 →jti원자적 소비 → body 해시h→ 기능 인가. 거부 시 → 매핑된 오류이고 내부 사유는 절대 노출되지 않습니다.issueCapability({feature})— 60초 일회성 capability; 원자적 소비(UPDATE … WHERE consumed=0) → 재사용 →409 CAPABILITY_CONSUMED.
LicenseStore 계약
필수 메서드(src/index.d.ts 참조):
getProduct · findLicenseByKey · getLicense · bindDevice · countBindings · setLicenseStatus · consumeNonce · createCapability · getCapability · consumeCapability (+ 선택적 pruneNonces)
제공되는 어댑터:
createMemoryStore(seed?)— 인메모리, 테스트/개발용.createSqliteStore({file})(@yetdapay/license-server/stores/sqlite) —node:sqlite위의 레퍼런스 구현; D1/Postgres 포트의 템플릿.node:sqlite는 Node 22에서 실험적입니다(경고 출력) — 프로덕션 BYOS는 어댑터를 출시하기보다 포트해야 합니다.
저장소 실패는 INTERNAL로 표면화됩니다 — fail closed, DB 오류에서 절대 권한 부여 없음.
Worker 프리셋
import { createLicenseServer, createWorkerConfig } from '@yetdapay/license-server';
const server = createLicenseServer(createWorkerConfig({ store, keys }));2
호스티드 Worker의 토큰 형식에 맞춰 issuer: 'devpay-lic'(WORKER_ISSUER)를 설정하고 aud 요구를 제거합니다. 다른 옵션은 오버라이드로 통과됩니다.
오류 모델
LicenseError + ErrorCode는 실패를 {code,message} + HTTP 상태로 매핑합니다 — VALIDATION (400), INVALID_PUBLIC_JWK (400), NOT_FOUND (404), PRODUCT_MISMATCH/FORBIDDEN/REVOKED/DEVICE_LIMIT/INVALID_STATUS (403/409), UNAUTHORIZED (401 — 모든 인증 검사에 하나의 불투명 코드), CAPABILITY_CONSUMED (409), BODY_TOO_LARGE (413), INTERNAL/CONFIG (500). Worker-vs-SDK 델타는 계약 매트릭스 §2에 문서화되어 있습니다 — 버그가 아닌 의도적입니다.
TTL 및 윈도우
entitlement 120s · 증명 iat ±60s skew · capability 60s · 논스 TTL 600s — 모두 DEFAULTS/PROOF_IAT_WINDOW_SEC/CLAIM_CLOCK_SKEW_SEC에서 옵니다.