{"record":{"id":"f3b6b1794ae9ce80","repo":"toeverything/AFFiNE","slug":"license-expired","errorCode":"license_expired","errorMessage":"License has expired.","messagePattern":"License has expired\\.","errorType":"exception","errorClass":"LicenseExpired","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/plugins/license/service.ts","lineNumber":112,"sourceCode":"      select: {\n        installedAt: true,\n        validatedAt: true,\n        expiredAt: true,\n        quantity: true,\n        recurring: true,\n        variant: true,\n      },\n      where: {\n        workspaceId,\n      },\n    });\n  }\n\n  @Transactional()\n  async installLicense(workspaceId: string, license: Buffer) {\n    const resolved = this.resolveWorkspaceTeamLicense(workspaceId, license);\n    if (!resolved.valid) {\n      throw new LicenseExpired();\n    }\n\n    const validatedAt = new Date();\n    const previous = await this.db.installedLicense.findUnique({\n      where: { workspaceId },\n    });\n\n    const installed = await this.db.installedLicense.upsert({\n      where: { workspaceId },\n      update: {\n        key: this.licenseSubjectId(resolved),\n        quantity: this.licenseQuantity(resolved),\n        recurring: this.licenseRecurring(resolved),\n        variant: SubscriptionVariant.Onetime,\n        validateKey: '',\n        validatedAt,\n        expiredAt: this.licenseExpiresAt(resolved),\n        license,","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/license/service.ts#L94-L130","documentation":"installLicense() persists a self-hosted team license file after cryptographically resolving it with resolveWorkspaceTeamLicense. It throws LicenseExpired when the resolved entitlement is not valid — in practice the resolution step has already flagged the license as past its end date (errorCode 'expired_end_at'). The install is aborted before any installedLicense row is written.","triggerScenarios":"Uploading a license file whose signed end date is in the past via the self-hosted admin 'install license' flow (installLicense(workspaceId, licenseBuffer)); re-installing an old license after it lapsed; server clock set past the license window.","commonSituations":"Renewal gap: the previous yearly license expired and the admin uploads the old file instead of the new one; VM clock drift after snapshot restore makes a valid license appear expired; staging environments reusing expired test licenses.","solutions":["Generate/download a fresh license from https://app.affine.pro (or Affine support) for the current period and upload that file.","Verify the server's date/time and NTP sync — an ahead-of-time clock invalidates otherwise valid licenses.","If the license should still be valid, contact Affine support with the license subject id, since endAt is inside the signed payload and cannot be edited."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isLicenseExpired(e: unknown): e is LicenseExpired {\n  return e instanceof LicenseExpired; // code === 'license_expired'\n}","tryCatchPattern":"try {\n  await licenseService.installLicense(workspaceId, licenseBuffer);\n} catch (e) {\n  if (e instanceof LicenseExpired) {\n    // show 'license expired, fetch a new file' and link to app.affine.pro\n  } else throw e;\n}","preventionTips":["Track license endAt (visible via preview) and alert admins before renewal date.","Keep server clocks NTP-synced so valid licenses do not read as expired.","Store renewed license files in a known location so the old file is not re-uploaded."],"tags":["license","self-hosted","entitlement","expiry"],"backgroundTag":"license-expired","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}