Refactor license key validation and recheck logic to improve error handling and maintain cache integrity

This commit is contained in:
Owen
2026-08-17 09:25:27 -04:00
parent af515f1072
commit 7d6f3a2925
+53 -33
View File
@@ -104,11 +104,18 @@ LQIDAQAB
} }
public async forceRecheck() { public async forceRecheck() {
this.statusCache.flushAll();
this.licenseKeyCache.flushAll();
this.phoneHomeFailureCount = 0; this.phoneHomeFailureCount = 0;
return await this.check(); // Force a fresh check without discarding the last known good cache
// up front — check() only replaces the cache once it has a fresh
// result, so a failed recheck (e.g. a transient server error) won't
// leave listKeys()/status looking empty in the meantime.
this.doRecheck = true;
try {
return await this.check();
} finally {
this.doRecheck = false;
}
} }
public async isUnlocked(): Promise<boolean> { public async isUnlocked(): Promise<boolean> {
@@ -181,10 +188,15 @@ LQIDAQAB
} }
let foundHostKey = false; let foundHostKey = false;
// Keys that fully decrypted, to phone home with. A row that
// fails to decrypt (e.g. stored under a different server
// secret) is marked invalid below but excluded here, so it
// can't take down validation for every other key in the batch.
const keys: { licenseKey: string; instanceId: string }[] = [];
// Validate stored license keys // Validate stored license keys
for (const key of allKeysRes) { for (const key of allKeysRes) {
try { try {
// Decrypt the license key and token // Decrypt the license key, token, and instance ID
const decryptedKey = decrypt( const decryptedKey = decrypt(
key.licenseKeyId, key.licenseKeyId,
this.serverSecret this.serverSecret
@@ -193,6 +205,10 @@ LQIDAQAB
key.token, key.token,
this.serverSecret this.serverSecret
); );
const decryptedInstanceId = decrypt(
key.instanceId,
this.serverSecret
);
const payload = validateJWT<TokenPayload>( const payload = validateJWT<TokenPayload>(
decryptedToken, decryptedToken,
@@ -214,6 +230,11 @@ LQIDAQAB
if (payload.type === "host") { if (payload.type === "host") {
foundHostKey = true; foundHostKey = true;
} }
keys.push({
licenseKey: decryptedKey,
instanceId: decryptedInstanceId
});
} catch (e) { } catch (e) {
logger.error( logger.error(
`Error validating license key: ${key.licenseKeyId}` `Error validating license key: ${key.licenseKeyId}`
@@ -233,37 +254,36 @@ LQIDAQAB
status.isHostLicensed = false; status.isHostLicensed = false;
} }
const keys = allKeysRes.map((key) => ({
licenseKey: decrypt(key.licenseKeyId, this.serverSecret),
instanceId: decrypt(key.instanceId, this.serverSecret)
}));
let apiResponse: ValidateLicenseAPIResponse | undefined; let apiResponse: ValidateLicenseAPIResponse | undefined;
try { if (keys.length > 0) {
// Phone home to validate license keys try {
apiResponse = await this.phoneHome(keys, false); // Phone home to validate license keys
apiResponse = await this.phoneHome(keys, false);
if (!apiResponse?.success) { if (!apiResponse?.success) {
throw new Error(apiResponse?.error); throw new Error(apiResponse?.error);
} }
// Reset failure count on success // Reset failure count on success
this.phoneHomeFailureCount = 0; this.phoneHomeFailureCount = 0;
} catch (e) { } catch (e) {
this.phoneHomeFailureCount++; this.phoneHomeFailureCount++;
if (this.phoneHomeFailureCount === 1) { if (this.phoneHomeFailureCount === 1) {
// First failure: fail silently // First failure: fail silently
logger.error("Error communicating with license server:"); logger.error(
logger.error(e); "Error communicating with license server:"
logger.error( );
`Allowing failure. Will retry one more time at next run interval.` logger.error(e);
); logger.error(
// return last known good status `Allowing failure. Will retry one more time at next run interval.`
return this.statusCache.get( );
this.statusKey // return last known good status
) as LicenseStatus; return this.statusCache.get(
} else { this.statusKey
// Subsequent failures: fail abruptly ) as LicenseStatus;
throw e; } else {
// Subsequent failures: fail abruptly
throw e;
}
} }
} }