{"record":{"id":"a2df6a1ce757804a","repo":"calcom/cal.diy","slug":"cannot-redirect-to-this-user","errorCode":null,"errorMessage":"Cannot redirect to this user.","messagePattern":"Cannot redirect to this user\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"apps/api/v2/src/modules/ooo/services/ooo.service.ts","lineNumber":61,"sourceCode":"\n  isStartBeforeEnd(start?: Date, end?: Date) {\n    if ((end && !start) || (start && !end)) {\n      throw new BadRequestException(\"Please specify both ooo start and end time.\");\n    }\n\n    if (start && end) {\n      if (start.getTime() > end.getTime()) {\n        throw new BadRequestException(\"Start date must be before end date.\");\n      }\n    }\n    return true;\n  }\n\n  async checkUserEligibleForRedirect(userId: number, toUserId?: number) {\n    if (toUserId) {\n      const user = await this.usersRepository.findUserOOORedirectEligible(userId, toUserId);\n      if (!user) {\n        throw new BadRequestException(\"Cannot redirect to this user.\");\n      }\n    }\n  }\n\n  async checkExistingOooRedirect(userId: number, start?: Date, end?: Date, toUserId?: number) {\n    if (start && end) {\n      const existingOooRedirect = await this.oooRepository.findExistingOooRedirect(\n        userId,\n        start,\n        end,\n        toUserId\n      );\n\n      if (existingOooRedirect) {\n        throw new BadRequestException(\"Booking redirect infinite not allowed.\");\n      }\n    }\n  }","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/apps/api/v2/src/modules/ooo/services/ooo.service.ts#L43-L79","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Restrict the redirect-target picker to users returned by the teams/membership endpoint for the current user's teams.","Re-fetch team membership when the picker opens rather than caching, so removed members are excluded.","If the 400 occurs, prompt the user to pick a current teammate and resubmit."],"exampleFix":"// before\nawait api.post('/ooo', { start, end, toUserId: arbitraryUserId });\n// after — only offer eligible teammates\nconst teammates = await api.get('/teams/members', { accepted: true });\nawait api.post('/ooo', { start, end, toUserId: teammates[0].userId });","handlingStrategy":"validation","validationCode":"async function pickEligibleRedirectTarget(currentUserId: number): Promise<number> {\n  const members = await api.get('/teams/members', { params: { accepted: true } });\n  const eligible = members.filter((m: { userId: number }) => m.userId !== currentUserId);\n  if (!eligible.length) throw new Error('No eligible teammate to redirect to');\n  return eligible[0].userId;\n}","typeGuard":"const isTeammateOf = async (userId: number, candidateId: number): Promise<boolean> => {\n  const teams = await api.get('/teams', { params: { userId } });\n  return teams.some((t: { members: { userId: number }[] }) =>\n    t.members.some((m: { userId: number }) => m.userId === candidateId));\n};","tryCatchPattern":null,"preventionTips":["Only populate the redirect picker from the current user's accepted team memberships.","Re-fetch membership when the picker opens.","Omit toUserId entirely if no eligible teammate exists."],"tags":["business-rule","ooo","authorization","team-membership","nestjs"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}