calcom/cal.diy · error · ForbiddenException

RolesGuard - user with id=${user.id} does not have the minim

Error message

RolesGuard - user with id=${user.id} does not have the minimum required role=${allowedRole} within

What it means

ForbiddenException from RolesGuard.throwForbiddenError — built dynamically to state that the authenticated user lacks the minimum required role within a specific organization and/or team. The message appends the org and/or team id depending on which scope was requested. Role checks use hasMinimumRole against ORG_ROLES / TEAM_ROLES hierarchies.

Source

Thrown at apps/api/v2/src/modules/auth/guards/roles/roles.guard.ts:50

    if (!canAccess) {
      this.throwForbiddenError(user, orgId, teamId, allowedRole);
    }

    return true;
  }

  throwForbiddenError(user: ApiAuthGuardUser, orgId: string, teamId: string, allowedRole: string) {
    let errorMessage = `RolesGuard - user with id=${user.id} does not have the minimum required role=${allowedRole} within`;
    if (orgId) {
      errorMessage += ` organization with id=${orgId}`;
    }
    if (teamId) {
      errorMessage += ` team with id=${teamId}`;
    }
    errorMessage += `.`;

    throw new ForbiddenException(errorMessage);
  }

  async checkUserRoleAccess(
    user: ApiAuthGuardUser,
    orgId: string,
    teamId: string,
    allowedRole: string
  ): Promise<{ canAccess: boolean }> {
    const REDIS_CACHE_KEY = `apiv2:user:${user.id ?? "none"}:org:${orgId ?? "none"}:team:${
      teamId ?? "none"
    }:guard:roles:${allowedRole}`;
    const cachedAccess = JSON.parse((await this.redisService.redis.get(REDIS_CACHE_KEY)) ?? "false");

    if (cachedAccess) {
      return { canAccess: cachedAccess };
    }

    let canAccess = false;

View on GitHub (pinned to 176037d0af)

Solutions

  1. Have an org owner/admin promote the user to the required role in the Cal.com dashboard.
  2. Verify the request targets the correct orgId/teamId — the user may be sufficiently privileged in a different team.
  3. For SYSTEM_ADMIN_ROLE routes, confirm the user has the system admin flag in the database.
  4. Clear the Redis cache key apiv2:user:<id>:org:<orgId>:team:<teamId>:guard:roles:<role> if roles were just changed and the cached 'false' is stale.
Defensive patterns

Strategy: try-catch

Validate before calling

const allowed = ['ORG_ADMIN'];
const userRole = membership.role; // 'MEMBER' | 'ADMIN' | 'OWNER' | ...
const ok = hasMinimumRole({ checkRole: `ORG_${userRole}`, minimumRole: allowed[0], roles: ORG_ROLES });
if (!ok) { /* tell user they need elevation */ }

Try / catch

try {
  await client.get(`/v2/orgs/${orgId}/settings`);
} catch (e) {
  if (e.status === 403 && /does not have the minimum required role/.test(e.message)) {
    // prompt user to request role elevation from an owner
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling a @Roles(...) / @MinimumRole(...) guarded endpoint where the user's membership role in the target org/team is below the allowed role — e.g. an ORG_MEMBER calling a route that requires ORG_ADMIN, or a TEAM_MEMBER calling a route requiring TEAM_OWNER.

Common situations: Promoting a user is pending/awaiting acceptance; user is a member of the parent org but not with a sufficient role; test fixtures assign the wrong role; team-only route called by an org-only admin where org-above-team escalation does not apply; SYSTEM_ADMIN_ROLE route hit by a non-system-admin.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/6d417090dd29763e. Report an issue: GitHub.