lobehub/lobehub · error · TRPCError

FORBIDDEN

FORBIDDEN

Error message

Workspace API Key creation is restricted to admins

What it means

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.

Source

Thrown at apps/server/src/routers/lambda/apiKey.ts:96

        expiresAt: z.date().nullish(),
        name: z.string(),
        // `undefined`/`null` = full access; entries must come from the
        // catalog — unknown scope strings are rejected.
        scopes: apiKeyScopesSchema,
      }),
    )
    .mutation(async ({ input, ctx }) => {
      if (ctx.workspaceId) {
        if (!(await canUseWorkspaceApiKeys(ctx.workspaceId))) {
          throw new TRPCError({
            code: 'PRECONDITION_FAILED',
            message: 'Workspace API Key access is not available',
          });
        }

        const memberCreation = await ctx.workspaceModel.getApiKeyMemberCreation(ctx.workspaceId);
        if (memberCreation === 'admins_only' && !isWorkspaceAdmin(ctx)) {
          throw new TRPCError({
            code: 'FORBIDDEN',
            message: 'Workspace API Key creation is restricted to admins',
          });
        }
      }

      const scopes = normalizeScopes(input.scopes);
      const result = await ctx.apiKeyModel.create({ ...input, scopes });
      await recordApiKeyAudit(ctx, {
        action: 'api_key.created',
        metadata: { expiresAt: result.expiresAt, name: result.name, scopes: result.scopes ?? null },
        resourceId: result.id,
      });

      return result;
    }),

  deleteAllApiKeys: apiKeyProcedure

View on GitHub (pinned to 10f24d7ade)

Solutions

  1. Have a workspace owner/admin create the key, or have an admin escalate the caller's workspaceRole to 'admin'.
  2. Change the workspace setting getApiKeyMemberCreation from 'admins_only' to 'members' (or 'open') via workspace config if the policy is too strict.
  3. 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.
  4. Call the endpoint outside a workspace context (no ctx.workspaceId) to bypass the workspace gate entirely in personal/OSS mode.

Example fix

// before: member calls createApiKey in admins_only workspace -> FORBIDDEN
// after: an admin creates it, or workspace policy is relaxed
const memberCreation = await workspaceModel.getApiKeyMemberCreation(wsId);
if (memberCreation === 'admins_only' && !isWorkspaceAdmin(ctx)) {
  // surface a UI hint: 'Ask a workspace admin to create this key'
  throw new Error('Ask an admin');
}
Defensive patterns

Strategy: validation

Validate before calling

// Before calling createApiKey, check the caller's role and workspace policy
const canCreate = !workspaceId ||
  (await workspaceModel.getApiKeyMemberCreation(workspaceId)) !== 'admins_only' ||
  isWorkspaceAdmin({ workspaceRole });
if (!canCreate) {
  throw new Error('Ask a workspace admin to create this key');
}
await apiKeyRouter.createApiKey.mutate({ name, scopes, expiresAt });

Type guard

const isWorkspaceAdminRole = (
  ctx: { workspaceRole?: string }
): ctx is { workspaceRole: 'owner' | 'admin' } =>
  ctx.workspaceRole === 'owner' || ctx.workspaceRole === 'admin';

Try / catch

try {
  await apiKeyRouter.createApiKey.mutate(input);
} catch (e) {
  if (e.shape?.data?.code === 'FORBIDDEN' && /admins/.test(e.message)) {
    // prompt the user to request admin escalation
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of lobehub/lobehub@10f24d7ade (2026-08-12). Data as JSON: /api/errors/224589a33d20f23c. Report an issue: GitHub.