calcom/cal.diy · error · ForbiddenException

RolesGuard - User is not part of the organization with id=${

Error message

RolesGuard - User is not part of the organization with id=${orgId}.

What it means

ForbiddenException from RolesGuard when BOTH orgId and teamId are present, the request reaches the combined org+team branch, and membershipRepository.findMembershipByOrgId(orgId, user.id) returns null — the user is not part of the organization that owns the team. This throws BEFORE the team membership check, because org-level absence means no inherited team access either.

Source

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

        throw new ForbiddenException(`RolesGuard - User is not a member of the team with id=${teamId}.`);
      }
      if (TEAM_ROLES.includes(allowedRole as unknown as (typeof TEAM_ROLES)[number])) {
        canAccess = hasMinimumRole({
          checkRole: `TEAM_${membership.role}`,
          minimumRole: allowedRole,
          roles: TEAM_ROLES,
        });
      }
    }

    // Checking the role for team and org, org is above team in term of permissions
    else if (Boolean(teamId) && Boolean(orgId)) {
      const teamMembership = await this.membershipRepository.findMembershipByTeamId(Number(teamId), user.id);
      const orgMembership = await this.membershipRepository.findMembershipByOrgId(Number(orgId), user.id);

      if (!orgMembership) {
        this.logger.log(`User (${user.id}) is not part of the organization (${orgId}), denying access.`);
        throw new ForbiddenException(`RolesGuard - User is not part of the organization with id=${orgId}.`);
      }

      // if the role checked is a TEAM role
      if (TEAM_ROLES.includes(allowedRole as unknown as (typeof TEAM_ROLES)[number])) {
        // if the user is admin or owner of org, allow request because org > team
        if (`ORG_${orgMembership.role}` === "ORG_ADMIN" || `ORG_${orgMembership.role}` === "ORG_OWNER") {
          canAccess = true;
        } else {
          if (!teamMembership) {
            this.logger.log(
              `User (${user.id}) is not part of the team (${teamId}) and/or, is not an admin nor an owner of the organization (${orgId}).`
            );
            throw new ForbiddenException(
              `RolesGuard - User is not part of the team with id=${teamId} and/or, is not an admin nor an owner of the organization with id=${orgId}.`
            );
          }

          // if user is not admin nor an owner of org, and is part of the team, then check user team membership role

View on GitHub (pinned to 176037d0af)

Solutions

  1. Have an org admin add the user to the organization (not just the team).
  2. Verify the orgId and teamId belong together (the team's parent org must equal the orgId).
  3. If the user was just added to the org, clear the RolesGuard Redis cache.
  4. For tests, seed both the org Membership (the combined branch checks org first).
Defensive patterns

Strategy: validation

Validate before calling

const orgMembership = await membershipRepository.findMembershipByOrgId(Number(orgId), user.id);
if (!orgMembership) {
  // user must join the org first; team access alone won't satisfy the combined check
}

Try / catch

try {
  await client.get(`/v2/orgs/${orgId}/teams/${teamId}`);
} catch (e) {
  if (e.status === 403 && /not part of the organization/.test(e.message)) {
    // user needs org membership, not just team
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling a route that passes both orgId and teamId where the user has no Membership in the organization. Even if the user is a member of the team in isolation, the org-membership gate fails first.

Common situations: User belongs to the team but the team was moved/reparented to a different org; org membership revoked; orgId/teamId pair is inconsistent (teamId belongs to a different org than the orgId supplied); cross-tenant access.

Related errors


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