calcom/cal.diy · error · BadRequestException

Cannot redirect to this user.

Error message

Cannot redirect to this user.

What it means

Thrown by checkUserEligibleForRedirect() when a toUserId is provided but usersRepository.findUserOOORedirectEligible() returns null. The repository query requires the target user to exist AND to be a member of a team in which the source user (userId) is also an accepted member. So the redirect target must be a current teammate of the redirector.

Source

Thrown at apps/api/v2/src/modules/ooo/services/ooo.service.ts:61

  isStartBeforeEnd(start?: Date, end?: Date) {
    if ((end && !start) || (start && !end)) {
      throw new BadRequestException("Please specify both ooo start and end time.");
    }

    if (start && end) {
      if (start.getTime() > end.getTime()) {
        throw new BadRequestException("Start date must be before end date.");
      }
    }
    return true;
  }

  async checkUserEligibleForRedirect(userId: number, toUserId?: number) {
    if (toUserId) {
      const user = await this.usersRepository.findUserOOORedirectEligible(userId, toUserId);
      if (!user) {
        throw new BadRequestException("Cannot redirect to this user.");
      }
    }
  }

  async checkExistingOooRedirect(userId: number, start?: Date, end?: Date, toUserId?: number) {
    if (start && end) {
      const existingOooRedirect = await this.oooRepository.findExistingOooRedirect(
        userId,
        start,
        end,
        toUserId
      );

      if (existingOooRedirect) {
        throw new BadRequestException("Booking redirect infinite not allowed.");
      }
    }
  }

View on GitHub (pinned to 176037d0af)

Solutions

  1. Restrict the redirect-target picker to users returned by the teams/membership endpoint for the current user's teams.
  2. Re-fetch team membership when the picker opens rather than caching, so removed members are excluded.
  3. If the 400 occurs, prompt the user to pick a current teammate and resubmit.

Example fix

// before
await api.post('/ooo', { start, end, toUserId: arbitraryUserId });
// after — only offer eligible teammates
const teammates = await api.get('/teams/members', { accepted: true });
await api.post('/ooo', { start, end, toUserId: teammates[0].userId });
Defensive patterns

Strategy: validation

Validate before calling

async function pickEligibleRedirectTarget(currentUserId: number): Promise<number> {
  const members = await api.get('/teams/members', { params: { accepted: true } });
  const eligible = members.filter((m: { userId: number }) => m.userId !== currentUserId);
  if (!eligible.length) throw new Error('No eligible teammate to redirect to');
  return eligible[0].userId;
}

Type guard

const isTeammateOf = async (userId: number, candidateId: number): Promise<boolean> => {
  const teams = await api.get('/teams', { params: { userId } });
  return teams.some((t: { members: { userId: number }[] }) =>
    t.members.some((m: { userId: number }) => m.userId === candidateId));
};

Prevention

When it happens

Trigger: POST/PATCH /v2/ooo with toUserId pointing to a user who is not on any shared team with the authenticated user, a user who left the team, a user who was never invited/accepted, or a non-existent user id.

Common situations: Frontend offering redirect-to any org member instead of only teammates; stale team membership after a member was removed; cross-organization redirect attempts; passing a userId from a different tenant.

Related errors


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