{"record":{"id":"3923ef53f007fe86","repo":"toeverything/AFFiNE","slug":"selfhost-license-key-is-required","errorCode":null,"errorMessage":"selfhost license key is required","messagePattern":"selfhost license key is required","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/backend/server/src/core/entitlement/service.ts","lineNumber":309,"sourceCode":"    input: SelfhostLicenseEntitlementInput,\n    options: { emit?: boolean } = {}\n  ) {\n    const emit = options.emit ?? true;\n    const resolved = input.license\n      ? resolveEntitlementV1({\n          deploymentType: 'selfhosted',\n          targetType: 'workspace',\n          targetId: input.workspaceId,\n          signedPayload: input.license,\n          publicKey: this.crypto.AFFiNEProPublicKey?.toString(),\n          licenseAesKey: this.crypto.AFFiNEProLicenseAESKey?.toString('hex'),\n          now: new Date().toISOString(),\n        })\n      : null;\n    const valid = resolved?.valid === true;\n    const subjectId = resolved?.subjectId ?? input.licenseKey;\n    if (!subjectId) {\n      throw new Error('selfhost license key is required');\n    }\n    const entitlement = await this.findBySubject('selfhost_license', subjectId);\n\n    const data = {\n      targetType: 'workspace',\n      targetId: input.workspaceId,\n      source: 'selfhost_license',\n      plan: 'selfhost_team',\n      status: valid ? 'active' : ('needs_reupload' as EntitlementStatus),\n      subjectId,\n      quantity: valid ? resolved.quantity : undefined,\n      signedPayload: input.license ?? undefined,\n      metadata: {\n        recurring: resolved?.recurring ?? input.recurring,\n        validateKey: input.validateKey ?? '',\n        variant: input.variant ?? null,\n        errorCode: resolved?.errorCode ?? (valid ? null : 'needs_reupload'),\n        errorMessage:","sourceCodeStart":291,"sourceCodeEnd":327,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/entitlement/service.ts#L291-L327","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["On the client, require either a non-empty licenseKey or a valid license file before submitting; validate file shape (JSON, expected envelope) before upload.","In the service, throw BadRequest('license or licenseKey is required') instead of a plain Error so it returns 400.","Verify the AFFiNEProPublicKey / AFFiNEProLicenseAESKey env vars are set on the server — without them resolution always returns invalid.","If a license was working and stopped, check key rotation / clock skew (the `now` used for validation)."],"exampleFix":"// before\nconst subjectId = resolved?.subjectId ?? input.licenseKey;\nif (!subjectId) {\n  throw new Error('selfhost license key is required');\n}\n\n// after\nconst subjectId = resolved?.subjectId ?? input.licenseKey;\nif (!subjectId) {\n  throw new BadRequest('A valid signed license or a licenseKey is required.');\n}","handlingStrategy":"validation","validationCode":"function assertLicenseInput(input: { license?: unknown; licenseKey?: string }) {\n  if (!input.license && !input.licenseKey) {\n    throw new UserError('Provide a signed license or a licenseKey');\n  }\n}","typeGuard":"function isSelfHostLicenseKeyRequired(e: unknown): boolean {\n  return e instanceof Error && e.message === 'selfhost license key is required';\n}","tryCatchPattern":"try {\n  await entitlement.upsertSelfHostLicense(input);\n} catch (e) {\n  if (isSelfHostLicenseKeyRequired(e)) {\n    return res.status(400).send('License or licenseKey required');\n  }\n  throw e;\n}","preventionTips":["Require a non-empty license or licenseKey in the form before submit.","Ensure AFFiNEProPublicKey / AFFiNEProLicenseAESKey are configured.","Validate the license envelope shape (JSON, expected fields) client-side."],"tags":["entitlement","self-hosted","license","validation","input-validation"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}