toeverything/AFFiNE · error · Error

selfhost license key is required

Error message

selfhost license key is required

What it means

Plain Error thrown during self-host license resolution when both resolved.subjectId and input.licenseKey are absent. resolveEntitlementV1 only returns a subjectId from a valid signed payload; if the license is invalid/missing AND no fallback licenseKey was supplied, there's nothing to key the entitlement row on. This is a generic Error (would surface as 500) — a candidate for promotion to BadRequest.

Source

Thrown at packages/backend/server/src/core/entitlement/service.ts:309

    input: SelfhostLicenseEntitlementInput,
    options: { emit?: boolean } = {}
  ) {
    const emit = options.emit ?? true;
    const resolved = input.license
      ? resolveEntitlementV1({
          deploymentType: 'selfhosted',
          targetType: 'workspace',
          targetId: input.workspaceId,
          signedPayload: input.license,
          publicKey: this.crypto.AFFiNEProPublicKey?.toString(),
          licenseAesKey: this.crypto.AFFiNEProLicenseAESKey?.toString('hex'),
          now: new Date().toISOString(),
        })
      : null;
    const valid = resolved?.valid === true;
    const subjectId = resolved?.subjectId ?? input.licenseKey;
    if (!subjectId) {
      throw new Error('selfhost license key is required');
    }
    const entitlement = await this.findBySubject('selfhost_license', subjectId);

    const data = {
      targetType: 'workspace',
      targetId: input.workspaceId,
      source: 'selfhost_license',
      plan: 'selfhost_team',
      status: valid ? 'active' : ('needs_reupload' as EntitlementStatus),
      subjectId,
      quantity: valid ? resolved.quantity : undefined,
      signedPayload: input.license ?? undefined,
      metadata: {
        recurring: resolved?.recurring ?? input.recurring,
        validateKey: input.validateKey ?? '',
        variant: input.variant ?? null,
        errorCode: resolved?.errorCode ?? (valid ? null : 'needs_reupload'),
        errorMessage:

View on GitHub (pinned to 26c515e050)

Solutions

  1. On the client, require either a non-empty licenseKey or a valid license file before submitting; validate file shape (JSON, expected envelope) before upload.
  2. In the service, throw BadRequest('license or licenseKey is required') instead of a plain Error so it returns 400.
  3. Verify the AFFiNEProPublicKey / AFFiNEProLicenseAESKey env vars are set on the server — without them resolution always returns invalid.
  4. If a license was working and stopped, check key rotation / clock skew (the `now` used for validation).

Example fix

// before
const subjectId = resolved?.subjectId ?? input.licenseKey;
if (!subjectId) {
  throw new Error('selfhost license key is required');
}

// after
const subjectId = resolved?.subjectId ?? input.licenseKey;
if (!subjectId) {
  throw new BadRequest('A valid signed license or a licenseKey is required.');
}
Defensive patterns

Strategy: validation

Validate before calling

function assertLicenseInput(input: { license?: unknown; licenseKey?: string }) {
  if (!input.license && !input.licenseKey) {
    throw new UserError('Provide a signed license or a licenseKey');
  }
}

Type guard

function isSelfHostLicenseKeyRequired(e: unknown): boolean {
  return e instanceof Error && e.message === 'selfhost license key is required';
}

Try / catch

try {
  await entitlement.upsertSelfHostLicense(input);
} catch (e) {
  if (isSelfHostLicenseKeyRequired(e)) {
    return res.status(400).send('License or licenseKey required');
  }
  throw e;
}

Prevention

When it happens

Trigger: License upload mutation invoked with neither a parseable signed license nor a licenseKey string. The signed payload is malformed/unsigned so resolved is null/invalid, and licenseKey was omitted.

Common situations: User pasted a truncated or wrong-format license. License was generated for a different deployment (publicKey mismatch) so resolution returns invalid with no subjectId. Frontend only sends one of the two fields due to a form bug.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/3923ef53f007fe86. Report an issue: GitHub.