@zdevzone/keyforge-client 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +133 -2
- package/dist/client.d.ts +105 -0
- package/dist/client.js +563 -0
- package/dist/client.js.map +1 -0
- package/dist/device-signals-node.d.ts +22 -0
- package/dist/device-signals-node.js +130 -0
- package/dist/device-signals-node.js.map +1 -0
- package/dist/device-signals.d.ts +13 -0
- package/dist/device-signals.js +17 -0
- package/dist/device-signals.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/linux.d.ts +8 -0
- package/dist/linux.js +35 -0
- package/dist/linux.js.map +1 -0
- package/dist/macos.d.ts +7 -0
- package/dist/macos.js +27 -0
- package/dist/macos.js.map +1 -0
- package/dist/tokens.d.ts +63 -0
- package/dist/tokens.js +113 -0
- package/dist/tokens.js.map +1 -0
- package/dist/types.d.ts +136 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/windows.d.ts +8 -0
- package/dist/windows.js +27 -0
- package/dist/windows.js.map +1 -0
- package/package.json +49 -3
- package/skill/keyforge-integrate/SKILL.md +26 -0
- package/skill/keyforge-integrate/references/integration.md +51 -0
package/dist/windows.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Optional Node-only Windows adapter. Never imported by the framework-independent entry point. */
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createSignalProvider, executeQuery } from "./device-signals-node.js";
|
|
4
|
+
export { DeviceSignalQueryError as WindowsQueryError, hashDeviceIdentifier as hashWindowsIdentifier, normalizeDeviceIdentifier as normalizeWindowsIdentifier, } from "./device-signals-node.js";
|
|
5
|
+
const kinds = ["machine-guid", "smbios-uuid", "volume-serial"];
|
|
6
|
+
const queries = {
|
|
7
|
+
"machine-guid": "(Get-ItemProperty -LiteralPath 'HKLM:\\SOFTWARE\\Microsoft\\Cryptography' -Name MachineGuid -ErrorAction Stop).MachineGuid",
|
|
8
|
+
"smbios-uuid": "(Get-CimInstance -ClassName Win32_ComputerSystemProduct -ErrorAction Stop).UUID",
|
|
9
|
+
"volume-serial": "(Get-CimInstance -ClassName Win32_LogicalDisk -Filter ('DeviceID=' + [char]39 + $env:SystemDrive + [char]39) -ErrorAction Stop).VolumeSerialNumber",
|
|
10
|
+
};
|
|
11
|
+
function systemQuery(kind) {
|
|
12
|
+
const executable = join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
13
|
+
const command = "$ErrorActionPreference='Stop'; try { " +
|
|
14
|
+
queries[kind] +
|
|
15
|
+
" } catch { if ($_.Exception -is [System.UnauthorizedAccessException] -or $_.FullyQualifiedErrorId -match 'AccessDenied|Unauthorized') { exit 5 }; exit 1 }";
|
|
16
|
+
return executeQuery(executable, [
|
|
17
|
+
"-NoLogo",
|
|
18
|
+
"-NoProfile",
|
|
19
|
+
"-NonInteractive",
|
|
20
|
+
"-Command",
|
|
21
|
+
command,
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
24
|
+
export function createWindowsDeviceSignalsProvider(options = {}) {
|
|
25
|
+
return createSignalProvider("win32", kinds, options, systemQuery);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=windows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows.js","sourceRoot":"","sources":["../src/windows.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE9E,OAAO,EACL,sBAAsB,IAAI,iBAAiB,EAC3C,oBAAoB,IAAI,qBAAqB,EAC7C,yBAAyB,IAAI,0BAA0B,GACxD,MAAM,0BAA0B,CAAC;AAClC,MAAM,KAAK,GAAG,CAAC,cAAc,EAAE,aAAa,EAAE,eAAe,CAAU,CAAC;AACxE,MAAM,OAAO,GAA2B;IACtC,cAAc,EACZ,4HAA4H;IAC9H,aAAa,EAAE,iFAAiF;IAChG,eAAe,EACb,oJAAoJ;CACvJ,CAAC;AAOF,SAAS,WAAW,CAAC,IAAsB;IACzC,MAAM,UAAU,GAAG,IAAI,CACrB,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,aAAa,EACvC,UAAU,EACV,mBAAmB,EACnB,MAAM,EACN,gBAAgB,CACjB,CAAC;IACF,MAAM,OAAO,GACX,uCAAuC;QACvC,OAAO,CAAC,IAAI,CAAC;QACb,4JAA4J,CAAC;IAC/J,OAAO,YAAY,CAAC,UAAU,EAAE;QAC9B,SAAS;QACT,YAAY;QACZ,iBAAiB;QACjB,UAAU;QACV,OAAO;KACR,CAAC,CAAC;AACL,CAAC;AACD,MAAM,UAAU,kCAAkC,CAChD,OAAO,GAAgC,EAAE;IAEzC,OAAO,oBAAoB,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;AACpE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zdevzone/keyforge-client",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Typed KeyForge installation client and signed offline authorization verifier",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/zdevzone/keyforge.git",
|
|
9
|
+
"directory": "packages/keyforge-client"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"skill"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./windows": {
|
|
23
|
+
"types": "./dist/windows.d.ts",
|
|
24
|
+
"import": "./dist/windows.js"
|
|
25
|
+
},
|
|
26
|
+
"./linux": {
|
|
27
|
+
"types": "./dist/linux.d.ts",
|
|
28
|
+
"import": "./dist/linux.js"
|
|
29
|
+
},
|
|
30
|
+
"./macos": {
|
|
31
|
+
"types": "./dist/macos.d.ts",
|
|
32
|
+
"import": "./dist/macos.js"
|
|
33
|
+
},
|
|
34
|
+
"./device-signals": {
|
|
35
|
+
"types": "./dist/device-signals.d.ts",
|
|
36
|
+
"import": "./dist/device-signals.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"zod": "^4.6.5"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=22.18.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc -p tsconfig.json",
|
|
50
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
51
|
+
}
|
|
6
52
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keyforge-integrate
|
|
3
|
+
description: Integrate an existing desktop or service app with KeyForge using its TypeScript client SDK, online license/trial onboarding, secure installation storage, offline authorization and optional customer messaging. Use for dependent-app integration, not license-server administration or payment recording.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Integrate KeyForge into a dependent app
|
|
7
|
+
|
|
8
|
+
Inspect the target application's language, runtime, onboarding, protected operations, storage and test workflow first. Use `@zdevzone/keyforge-client` where its Fetch/Web Crypto runtime requirements fit; do not assume Electron, invent a backend, or add a second auth system. Ask for the deployment URL, stable product ID and trusted public keys when unavailable. Product/license keys are customer inputs; the SDK automatically creates the separate installation secret.
|
|
9
|
+
|
|
10
|
+
Read [the integration contract](references/integration.md) before changing licensing. Prefer the installed SDK README/types and this contract; when the KeyForge source repository is available, consult `docs/dependent-app-integration.md`, `docs/verify-token.md`, `docs/client-openapi.json` and `examples/keyforge-node.ts`. Do not infer API fields from old unscoped routes or JWT libraries.
|
|
11
|
+
|
|
12
|
+
## Implement the host integration
|
|
13
|
+
|
|
14
|
+
- Add the publishable SDK archive/workspace dependency using the target repository's package manager. The package may not yet be on a public registry; obtain the built archive/source rather than installing an unrelated similarly named package. Respect target framework/version and existing UI components.
|
|
15
|
+
- Implement and test a platform-appropriate `SecureStorage` adapter. Keep one SDK owner in a trusted process, expose only required status/actions to UI, and atomically persist a coherent state record. Use the existing credential vault or a vault-held encryption key plus atomic encrypted-file replacement. Never silently fall back to plaintext persistence or `MemoryStorage` in production.
|
|
16
|
+
- Offer online key activation and provider-permitted trial onboarding. Skipping leaves the app unlicensed with only explicitly free app features. Enforce access at actual protected operations, including backend/IPC boundaries where present, rather than only hiding buttons.
|
|
17
|
+
- Refresh while online ahead of the signed deadline; evaluate cached authorization on startup and at protected operations. Handle expired/revoked/reconnect states visibly. Keep app-specific trial usage durable and separate from token refresh. Preserve the target app's work/data when authorization ends.
|
|
18
|
+
- For optional Windows recognition, import `createWindowsDeviceSignalsProvider` from `@zdevzone/keyforge-client/windows` in the trusted Node process and supply `deviceSignalsProvider`. Keep Node imports out of browser/mobile bundles. Do not collect CPU IDs, MAC/IP addresses or send raw identifiers. Read the SDK's Windows diagnostics and recovery guidance; partial/unsupported reports must leave installation-based licensing usable. Backfill existing installations with `updateDeviceSignals()`, never a new registration request. Preserve secure storage and the original registration body across restarts. Explain URL/product scoping, legacy backfill and recognition limitations; do not describe this as physical-device enforcement.
|
|
19
|
+
- For Windows/Linux/macOS targets, prefer the automatic `createDeviceSignalsProvider()` from `@zdevzone/keyforge-client/device-signals`; explicit `/linux` and `/macos` factories also exist. Never request sudo to read Linux DMI identifiers. Honor missing/permission-denied signals and OS-specific complete sets (Mac two; Windows/Linux three). Browser/mobile sandbox limitations remain. For non-JS/TS apps, use the HTTP/OpenAPI and exact hash/token contracts; do not claim a native SDK exists or introduce Node just to wrap REST. Consult `docs/device-identity.md` when available and the packaged README; choose language-specific ports only for a real target's needs.
|
|
20
|
+
- For optional inbox/notifications, use public customer methods and polling/backoff. Add a durable app outbox for support messages; keep UUID/body unchanged after timeouts. Do not imply that email delivery or customer portal accounts exist.
|
|
21
|
+
|
|
22
|
+
Avoid handling signing secrets, admin tokens, financial operations or trusted metering credentials in the client. Never print keys, tokens, installation state, request-link secrets or authorization headers in tools/logs. Reading public key material is sufficient for verification. Installing this skill does not authorize modifying live operator/license data.
|
|
23
|
+
|
|
24
|
+
## Verify the integration
|
|
25
|
+
|
|
26
|
+
Test against an isolated KeyForge fixture or provider-approved test product. Verify online activation, offline restart within grace, reconnect after grace, altered/wrong-context tokens, signed HTTP-200 revocation, interrupted registration, exhausted seats, disabled/spent trials and retained consumption. Test the host storage adapter for atomic-save failure/restart and single-owner serialization. For communication, verify exact message replay and isolation from other installations/internal notes. Run the target repository's checks and explain runtime/storage limitations. Do not reset the server or issue live licenses merely to exercise the integration.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Current KeyForge client contract
|
|
2
|
+
|
|
3
|
+
The SDK supports Node 22.18+ as its reference runtime and needs Fetch, Web Crypto Ed25519, TextEncoder/TextDecoder, base64 helpers and secure randomness. It contains no UI or framework-specific credential vault. Keep installation identity in the desktop trusted/main process. For another language, implement against the server's OpenAPI and token contracts with shared test vectors; do not import Node-specific assumptions.
|
|
4
|
+
|
|
5
|
+
## Setup and state
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { KeyForgeClient, type SecureStorage } from "@zdevzone/keyforge-client";
|
|
9
|
+
declare const storage: SecureStorage;
|
|
10
|
+
declare const publicKeys: Record<string, string>;
|
|
11
|
+
const client = new KeyForgeClient({
|
|
12
|
+
serverUrl: "https://licenses.example.com",
|
|
13
|
+
productId: "your-product",
|
|
14
|
+
publicKeys,
|
|
15
|
+
storage,
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`SecureStorage.load()` returns a snapshot of `ClientState | null`; `save(state)` atomically replaces it. Scope a record to server URL, product and subject kind. Preserve identity, pending registration UUID/body, tokens, consumed trial usage, denial and clock checkpoint across restarts. Only a truly new installation returns null. Lost credentials need operator-assisted seat recovery, not silent state deletion. `MemoryStorage` is test-only/ephemeral. One SDK owner serializes calls; the host must prevent multiple writers across processes.
|
|
20
|
+
|
|
21
|
+
## Online onboarding and offline usage
|
|
22
|
+
|
|
23
|
+
- `activate(key)` registers if needed, activates online, verifies and saves the returned license, then returns authorization.
|
|
24
|
+
- `startTrial()` requires online access and explicitly selects the previously obtained/server-issued trial. Registration alone does not select it; skipping onboarding grants no paid features.
|
|
25
|
+
- `register()` persists identity and exact request before networking. Its bearer is a generated installation secret, not the product key or an administrator credential. Retry the same body/UUID after an ambiguous timeout. Do not change pending registration metadata.
|
|
26
|
+
- `validate(key)` is online inspection; it does not bind a seat or create offline authorization.
|
|
27
|
+
- `getAuthorization()` needs no network. Allow paid operations only for `state === "licensed"` and the exact required signed entitlement. Trial behavior must be defined by the app; trial grants do not contain paid plan entitlements.
|
|
28
|
+
- `refresh()` requires a bound license and internet. Schedule before the returned paid `deadline`; the SDK does not run background workers. Transport retries bounded network/429/502/503/504 failures. A transient failure leaves an otherwise usable cached grant. Explicit binding/identity denials block fallback.
|
|
29
|
+
- `deactivate()` releases the seat online, clears local paid use after success, and does not silently enable trial use.
|
|
30
|
+
- States: `unlicensed`, `trial`, `licensed`, `reconnect_required`, `expired`, `revoked`. Reason codes explain failures. Correct a rolled-back system clock before reconnecting.
|
|
31
|
+
- `consumeTrial(units)` persists cumulative consumption independently of grants and returns post-consumption state. Check available units before allowing work. No refresh/restart may reset consumption. Local counters are not authoritative on compromised devices.
|
|
32
|
+
|
|
33
|
+
## Verification invariants
|
|
34
|
+
|
|
35
|
+
`createDeviceSignalsProvider()` from `@zdevzone/keyforge-client/device-signals` automatically selects Windows, Linux or macOS in a trusted Node process. Explicit `/linux` and `/macos` exports are available; the `/windows` contract remains compatible. Consult the packaged README or `docs/device-identity.md` for per-OS sources and hash vectors. Never elevate to obtain DMI signals. Unsupported/sandboxed platforms still use installation credentials, not fabricated fingerprints. Only JS/TS has an official SDK; other languages implement the same HTTPS, secure-storage, retry and signed-token contracts. OpenAPI generation covers HTTP plumbing, not offline authorization.
|
|
36
|
+
|
|
37
|
+
For opt-in Windows recognition, use `@zdevzone/keyforge-client/windows` only in a trusted Node process. Follow the SDK README's exact v1 normalization/hash scope; never invent an all-platform fingerprint. Preserve pending registration signals before networking. Use `getDeviceSignalsDiagnostics()` to explain partial/unsupported collection and `updateDeviceSignals()` for existing authenticated identities. Backfill does not register, reset trial consumption, change meters/seats or merge groups. Test permission/timeout/placeholder cases, interrupted registration with changed hardware, legacy backfill and wrong-product credentials. URL/product changes require coordinated state migration, not silent identity replacement. Hardware signals are forgeable hints; do not promise physical-device enforcement or offline seat transfer.
|
|
38
|
+
|
|
39
|
+
License v4/trial v2 tokens are `base64url(JSON bytes).base64url(Ed25519 signature)`. The signature covers **decoded UTF-8 JSON bytes**, without reserialization. These are not JWTs. `verifySignedToken` authenticates payload/context even for negative statuses; it alone does not grant access. Prefer `getAuthorization` for permission.
|
|
40
|
+
|
|
41
|
+
Pin approved SPKI public PEMs by `kid`. Never automatically trust keys fetched beside a token. Reject malformed payloads, unknown versions/kids, wrong issuer/product/installation/subject, implausible future issuance and invalid signatures. Paid access ends at the earlier of expiry and offline grace. A signed revoked/expired refresh can be HTTP 200: save it before evaluating access. Trial `spent`, allowance exhaustion and expiry fail closed. Trials use their own nullable expiry/allowance contract, not the paid offline-grace field. Offline revocation is bounded by the signed deadline; no client can promise immediate offline revocation or prevent all local tampering.
|
|
42
|
+
|
|
43
|
+
## Optional communication
|
|
44
|
+
|
|
45
|
+
Register first, then use `getNotifications()`, `setNotificationPreferences({messages, expirations})`, and `markNotificationRead(eventId)`. Poll while online, pause when backgrounded/offline, and back off on failures. There is no email/realtime delivery; collections are currently unpaginated at the server.
|
|
46
|
+
|
|
47
|
+
`collectLicense(licenseId)` retrieves a key targeted to this installation. It does not activate it. Construct collection through the SDK instead of following arbitrary `collectPath` URLs with a credential.
|
|
48
|
+
|
|
49
|
+
Use `listSupportThreads()`/`getSupportThread(id)`. `createSupportThread` takes `requestId`, `licenseId`, `category`, `urgency`, `subject`, `message`, `createdAt` and optional reporter/app metadata. The SDK sets `Idempotency-Key` to requestId. Technical/feature/other categories require an active binding; licensing/billing also permit existing expired/released bindings; trial-only installations cannot create cases.
|
|
50
|
+
|
|
51
|
+
`replyToSupportThread(id, {requestId, revision, message})` requires the latest revision. Persist exact request/body in a durable host outbox before sending. On revision conflict reload and reconcile; on ambiguous timeout replay exactly. Customer replies reopen resolved/waiting cases, while closed cases require a new case. Internal notes and assignments remain server-private. Request-link secrets for preinstallation customers are separate from installation credentials. Usage metering uses a trusted backend's scoped service credential and is intentionally absent from this desktop SDK.
|