calcom/cal.diy · critical · ForbiddenException

No user id found in request params.

Error message

No user id found in request params.

What it means

Thrown by the IsUserOOO guard (NestJS CanActivate) when request.params.userId is falsy. The guard protects out-of-office routes that are scoped by user; it expects a :userId route param. An absent param indicates a routing misconfiguration on the server, not bad end-user input. ForbiddenException (HTTP 403).

Source

Thrown at apps/api/v2/src/modules/ooo/guards/is-user-ooo.ts:15

import { UserOOORepository } from "@/modules/ooo/repositories/ooo.repository";
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
import { Request } from "express";

@Injectable()
export class IsUserOOO implements CanActivate {
  constructor(private oooRepo: UserOOORepository) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<Request>();
    const oooId: string = request.params.oooId;
    const userId: string = request.params.userId;

    if (!userId) {
      throw new ForbiddenException("No user id found in request params.");
    }

    if (!oooId) {
      throw new ForbiddenException("No ooo entry id found in request params.");
    }

    const ooo = await this.oooRepo.getUserOOOByIdAndUserId(Number(oooId), Number(userId));

    if (ooo) {
      return true;
    }

    throw new ForbiddenException("This OOO entry does not belong to this user.");
  }
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Ensure every OOO route guarded by IsUserOOO includes :userId in its path.
  2. In tests, mock request.params = { userId: '<id>', oooId: '<id>' } before invoking the guard.
  3. Grep the OOO controller to confirm :userId is present on all guarded routes.
  4. Add a route-level integration test that asserts the param is wired.

Example fix

// before
@Controller('ooo')
@UseGuards(IsUserOOO)
export class OOOController {
  @Patch(':oooId')        // no :userId — guard throws
  update() { ... }
}

// after
@Controller('users/:userId/ooo')
@UseGuards(IsUserOOO)
export class OOOController {
  @Patch(':oooId')
  update() { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

// Server-side: assert the route exposes :userId before applying IsUserOOO
function assertRouteHasUserId(path: string) {
  if (!/:userId(\b|\?|$|\()/.test(path)) {
    throw new Error(`Route '${path}' uses IsUserOOO but has no :userId param`);
  }
}

Type guard

function requestHasUserId(req: { params?: Record<string, unknown> }): req is { params: { userId: string } } {
  return typeof req.params?.userId === 'string' && (req.params as any).userId.length > 0;
}

Try / catch

// Routing bug — fix the route, do not catch at runtime.

Prevention

When it happens

Trigger: An OOO controller route guarded by IsUserOOO is mounted without a :userId segment in its path, or the request reaches the handler via a path that doesn't populate the param. Reproducible only by misconfigured routing or a test that omits the param.

Common situations: Refactoring OOO routes and dropping :userId; a test invoking the guard with an unpopulated params object; mounting the controller under a prefix that doesn't carry :userId.

Related errors


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