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
+29 -9
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;
// 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(); 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,12 +254,8 @@ 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;
if (keys.length > 0) {
try { try {
// Phone home to validate license keys // Phone home to validate license keys
apiResponse = await this.phoneHome(keys, false); apiResponse = await this.phoneHome(keys, false);
@@ -252,7 +269,9 @@ LQIDAQAB
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(
"Error communicating with license server:"
);
logger.error(e); logger.error(e);
logger.error( logger.error(
`Allowing failure. Will retry one more time at next run interval.` `Allowing failure. Will retry one more time at next run interval.`
@@ -266,6 +285,7 @@ LQIDAQAB
throw e; throw e;
} }
} }
}
// Check and update all license keys with server response // Check and update all license keys with server response
for (const key of keys) { for (const key of keys) {