entitlement-verify
Source: entitlement-verify.js:9
Pure entitlement verification — plain Node, no Electron. src/auth.js owns the network calls, the safeStorage cache, and the IPC surface; this module owns the Ed25519 signature check and the cached-payload validity rules (expiry + clock-rollback), so the security-critical decisions run anywhere Node runs.
Instance Methods
publicKeyFromB64(rawKeyB64: string): crypto.KeyObject
Build a Node KeyObject from the server's raw 32-byte Ed25519 public key.
Parameters
rawKeyB64(string) — Raw 32-byte public key, base64-encoded.
Returns
crypto.KeyObject— Public key usable with crypto.verify.
canonicalJson(value: *): string
Serialize a value EXACTLY like the server's canonical_json(): keys sorted, no whitespace, raw unicode. Otherwise valid signatures won't verify.
Parameters
value(*) — Any JSON-serializable value.
Returns
string— Canonical JSON string.
verifyEntitlement(
response: Object,
verifyKey: crypto.KeyObject,
): boolean
Verify the Ed25519 signature on an entitlement response. The digest algorithm passed to crypto.verify is null because Ed25519 has no separate digest step.
Parameters
response(Object) — Entitlement response — signed payload plus base64 signature.verifyKey(crypto.KeyObject) — Public key to verify against.
Returns
boolean— True when the signature verifies; false for a missing payload or signature.
checkCachedEntitlement(
cached: Object,
verifyKey: crypto.KeyObject,
now: number,
): Object
Validity rules for a cached entitlement blob: the signature must still verify, the clock must not have been rolled back past the tolerance (to fake freshness), and valid_until must not have passed. An unparseable valid_until (Date.parse → NaN) fails closed: every comparison against NaN is false, which would otherwise read as "never expires", so a missing/garbled timestamp means the cache is not trustworthy.
Parameters
cached(Object) — Cached blob — the signed response plus the wall clock last seen when it was stored.verifyKey(crypto.KeyObject) — Public key to verify against.now(number) — Current wall clock, milliseconds since epoch.
Returns
Object— The payload when the cache is trustworthy, null otherwise.
nextRefreshDelayMs(
payload: Object,
now: number,
opts?: Object,
): number
When the entitlement should be re-fetched. Normally the 24h cadence (capMs), but if the payload itself says the subscription state will change sooner — a trial ending, a paid/canceled period lapsing — refresh just after that boundary (graceMs past it, so the server's read-time state machine has definitely flipped). Boundaries already in the past are ignored: the current payload was built after them, so they're old news. floorMs stops a just-passed boundary from hot-looping the scheduler.
Parameters
payload(Object) — Entitlement payload; trial_ends_at and current_period_end (ISO timestamps) are the boundaries considered.now(number) — Current wall clock, milliseconds since epoch.opts(Object, optional)opts.capMs(number, optional, default: 86400000) — Maximum delay — the normal 24h cadence.opts.floorMs(number, optional, default: 30000) — Minimum delay.opts.graceMs(number, optional, default: 60000) — Delay added past a boundary before refreshing.
Returns
number— Delay until the next refresh, in milliseconds.
Other
CLOCK_ROLLBACK_TOLERANCE_MS: number
How far backwards the wall clock may move, in milliseconds, before the cached entitlement is distrusted (crude clock-rollback defense — design §6: "not bulletproof").
SPKI_PREFIX: Buffer
Standard 12-byte SPKI header for Ed25519. Node wants a DER/SPKI key while the server hands out a raw 32-byte key; prefixing with this bridges the two.