{"record":{"id":"e3b0b7d8ab793292","repo":"Budibase/budibase","slug":"offline-license-has-expired-expireat-license-ex","errorCode":null,"errorMessage":"Offline license has expired. expireAt=${license.expireAt}","messagePattern":"Offline license has expired\\. expireAt=(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/pro/src/sdk/licensing/licenses/offline/offline.ts","lineNumber":72,"sourceCode":"\nexport async function getIdentifierBase64(): Promise<string> {\n  const identifier = await getIdentifier()\n  return encoding.objectToBase64(identifier)\n}\n\nexport function getIdentifierFromBase64(\n  identifierBase64: string\n): OfflineIdentifier {\n  return encoding.base64ToObject(identifierBase64)\n}\n\n// LICENSE\n\nexport function verifyExpiry(license: OfflineLicense) {\n  const now = Date.now()\n  const expireAt = new Date(license.expireAt).getTime()\n  if (!Number.isFinite(expireAt) || now > expireAt) {\n    throw new Error(`Offline license has expired. expireAt=${license.expireAt}`)\n  }\n}\n\nexport class OfflineLicenseMismatchError extends Error {}\n\nexport async function verifyInstallation(license: OfflineLicense) {\n  const identifier = await getIdentifier()\n  if (\n    license.identifier.installId !== identifier.installId ||\n    license.identifier.tenantId !== identifier.tenantId\n  ) {\n    // be intentionally vague\n    throw new OfflineLicenseMismatchError(\"Invalid offline license\")\n  }\n}\n\nexport function enrichLicense(license: OfflineLicense) {\n  const planType = license.plan.type","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/pro/src/sdk/licensing/licenses/offline/offline.ts#L54-L90","documentation":"verifyExpiry checks an OfflineLicense's expireAt against the current time and throws this plain Error if the timestamp is unparseable (NaN) or in the past. It is the internal expiry guard for offline license tokens; verifyOfflineLicenseToken catches it and rethrows an HTTPError('Offline license has expired', 400).","triggerScenarios":"verifyExpiry(license) called (directly or via verifyOfflineLicenseToken → getOfflineLicense → cache.refresh) when license.expireAt is a past date or not a valid date string.","commonSituations":"Offline license naturally aged past its renewal date (typically annual); system clock wrong (far future makes tokens 'valid' past true expiry, far past expires fresh tokens); truncated/corrupted token payload losing the expireAt field; token copied from an old expired export.","solutions":["Renew the subscription and generate/download a fresh offline license token from the account portal.","Verify the server clock is correct (NTP-synced) — a wrong clock can wrongly expire or extend licenses.","Re-activate with a new token: activateOfflineLicenseToken(newToken), which refreshes the license cache.","Decode the token to inspect expireAt if you suspect the token itself is malformed."],"exampleFix":"// before\nawait activateOfflineLicenseToken(staleToken)\n// after\nconst license = signing.decodeToken(staleToken)\nif (new Date(license.expireAt).getTime() <= Date.now()) {\n  throw new Error(\"Token expired — download a fresh offline license from the account portal\")\n}\nawait activateOfflineLicenseToken(staleToken)","handlingStrategy":"validation","validationCode":"function isLicenseCurrent(license: { expireAt: string }): boolean {\n  const expireAt = new Date(license.expireAt).getTime()\n  return Number.isFinite(expireAt) && Date.now() <= expireAt\n}\nif (!isLicenseCurrent(license)) throw new Error(\"Offline license expired — renew before use\")","typeGuard":"function isValidDate(v: unknown): v is string {\n  return typeof v === \"string\" && Number.isFinite(new Date(v).getTime())\n}","tryCatchPattern":"try {\n  const license = await getOfflineLicense()\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"has expired\")) {\n    // trigger renewal flow / alert ops to refresh the token\n  } else throw e\n}","preventionTips":["Set renewal reminders ahead of license expireAt dates.","Keep servers NTP-synced so expiry checks are accurate.","Track expireAt of stored offline tokens and alert before expiry.","Re-export offline tokens after subscription renewals."],"tags":["licensing","offline-license","expiry","validation"],"backgroundTag":"license-expired","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}