feat(server): support installable license (#12181)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added support for installing self-hosted team licenses via encrypted license files.
  - Introduced a new "Onetime" license variant for self-hosted environments.
  - Added a GraphQL mutation to upload and install license files.
  - License details now display the license variant.

- **Bug Fixes**
  - Improved error messages for license activation and expiration, including dynamic reasons.

- **Localization**
  - Updated and improved license-related error messages for better clarity.

- **Tests**
  - Added comprehensive end-to-end tests for license installation scenarios.

- **Chores**
  - Enhanced environment variable handling and public key management for license verification.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
forehalo
2025-05-09 04:16:05 +00:00
parent 3db91bdc8e
commit 93e01b4442
34 changed files with 718 additions and 187 deletions
@@ -62,7 +62,9 @@ export class LicenseController {
await using lock = await this.mutex.acquire(`license-activation:${key}`);
if (!lock) {
throw new InvalidLicenseToActivate();
throw new InvalidLicenseToActivate({
reason: 'Too Many Requests',
});
}
const license = await this.db.license.findUnique({
@@ -72,7 +74,9 @@ export class LicenseController {
});
if (!license) {
throw new InvalidLicenseToActivate();
throw new InvalidLicenseToActivate({
reason: 'License not found',
});
}
const subscription = await this.manager.getSubscription({
@@ -85,7 +89,9 @@ export class LicenseController {
license.installedAt ||
subscription.status !== SubscriptionStatus.Active
) {
throw new InvalidLicenseToActivate();
throw new InvalidLicenseToActivate({
reason: 'Invalid license',
});
}
const validateKey = randomUUID();
@@ -144,7 +150,9 @@ export class LicenseController {
}
if (license.validateKey && license.validateKey !== revalidateKey) {
throw new InvalidLicenseToActivate();
throw new InvalidLicenseToActivate({
reason: 'Invalid validate key',
});
}
const validateKey = randomUUID();