{"record":{"id":"224589a33d20f23c","repo":"lobehub/lobehub","slug":"forbidden-224589","errorCode":"FORBIDDEN","errorMessage":"Workspace API Key creation is restricted to admins","messagePattern":"Workspace API Key creation is restricted to admins","errorType":"exception","errorClass":"TRPCError","httpStatus":403,"severity":"error","filePath":"apps/server/src/routers/lambda/apiKey.ts","lineNumber":96,"sourceCode":"        expiresAt: z.date().nullish(),\n        name: z.string(),\n        // `undefined`/`null` = full access; entries must come from the\n        // catalog — unknown scope strings are rejected.\n        scopes: apiKeyScopesSchema,\n      }),\n    )\n    .mutation(async ({ input, ctx }) => {\n      if (ctx.workspaceId) {\n        if (!(await canUseWorkspaceApiKeys(ctx.workspaceId))) {\n          throw new TRPCError({\n            code: 'PRECONDITION_FAILED',\n            message: 'Workspace API Key access is not available',\n          });\n        }\n\n        const memberCreation = await ctx.workspaceModel.getApiKeyMemberCreation(ctx.workspaceId);\n        if (memberCreation === 'admins_only' && !isWorkspaceAdmin(ctx)) {\n          throw new TRPCError({\n            code: 'FORBIDDEN',\n            message: 'Workspace API Key creation is restricted to admins',\n          });\n        }\n      }\n\n      const scopes = normalizeScopes(input.scopes);\n      const result = await ctx.apiKeyModel.create({ ...input, scopes });\n      await recordApiKeyAudit(ctx, {\n        action: 'api_key.created',\n        metadata: { expiresAt: result.expiresAt, name: result.name, scopes: result.scopes ?? null },\n        resourceId: result.id,\n      });\n\n      return result;\n    }),\n\n  deleteAllApiKeys: apiKeyProcedure","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/lobehub/lobehub/blob/10f24d7ade75139093a9373b364f6bc91f3cd7db/apps/server/src/routers/lambda/apiKey.ts#L78-L114","documentation":"Thrown by createApiKey when the workspace's API-key-creation policy is 'admins_only' and the caller's workspaceRole is neither 'owner' nor 'admin'. It is a role-gate that runs after the workspace feature flag (canUseWorkspaceApiKeys) but before the key is actually created. The check reads ctx.workspaceRole, which the cloud workspace-auth middleware injects and the OSS stub leaves absent.","triggerScenarios":"Calling apiKeyRouter.createApiKey while ctx.workspaceId is set, the workspace setting getApiKeyMemberCreation() returns 'admins_only', and ctx.workspaceRole is 'member', 'viewer', or undefined (OSS stub). A non-admin member or a viewer attempting to mint a key in a workspace locked down to admins hits this.","commonSituations":"An enterprise/cloud workspace where an owner restricted API key creation to admins via workspace settings. A member tries to create a personal key through the workspace-scoped endpoint. Also occurs in OSS deployments if the workspace auth stub injects a non-admin role.","solutions":["Have a workspace owner/admin create the key, or have an admin escalate the caller's workspaceRole to 'admin'.","Change the workspace setting getApiKeyMemberCreation from 'admins_only' to 'members' (or 'open') via workspace config if the policy is too strict.","If the caller is actually an admin, verify ctx.workspaceRole is being injected correctly by the workspace-auth middleware — an absent/undefined field fails the check.","Call the endpoint outside a workspace context (no ctx.workspaceId) to bypass the workspace gate entirely in personal/OSS mode."],"exampleFix":"// before: member calls createApiKey in admins_only workspace -> FORBIDDEN\n// after: an admin creates it, or workspace policy is relaxed\nconst memberCreation = await workspaceModel.getApiKeyMemberCreation(wsId);\nif (memberCreation === 'admins_only' && !isWorkspaceAdmin(ctx)) {\n  // surface a UI hint: 'Ask a workspace admin to create this key'\n  throw new Error('Ask an admin');\n}","handlingStrategy":"validation","validationCode":"// Before calling createApiKey, check the caller's role and workspace policy\nconst canCreate = !workspaceId ||\n  (await workspaceModel.getApiKeyMemberCreation(workspaceId)) !== 'admins_only' ||\n  isWorkspaceAdmin({ workspaceRole });\nif (!canCreate) {\n  throw new Error('Ask a workspace admin to create this key');\n}\nawait apiKeyRouter.createApiKey.mutate({ name, scopes, expiresAt });","typeGuard":"const isWorkspaceAdminRole = (\n  ctx: { workspaceRole?: string }\n): ctx is { workspaceRole: 'owner' | 'admin' } =>\n  ctx.workspaceRole === 'owner' || ctx.workspaceRole === 'admin';","tryCatchPattern":"try {\n  await apiKeyRouter.createApiKey.mutate(input);\n} catch (e) {\n  if (e.shape?.data?.code === 'FORBIDDEN' && /admins/.test(e.message)) {\n    // prompt the user to request admin escalation\n  } else throw e;\n}","preventionTips":["Surface the workspace's getApiKeyMemberCreation policy in the UI before showing the 'create key' action to non-admins.","Gate the 'Create API Key' button on the caller's workspaceRole being owner or admin when policy is admins_only.","Document the admins_only policy effect for workspace members onboarding."],"tags":["authorization","workspace","rbac","api-key"],"backgroundTag":null,"analyzedSha":"10f24d7ade75139093a9373b364f6bc91f3cd7db","analyzedAt":"2026-08-12T11:43:19.543Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}