calcom/cal.diy · critical · UnauthorizedException

Unauthorized

Error message

Unauthorized

What it means

A 401 UnauthorizedException (with NestJS default message 'Unauthorized') thrown by the BookingPbacGuard when request.user is null or undefined. The PBAC (Policy-Based Access Control) guard runs after the ApiAuthGuard, which is responsible for authenticating the request and populating request.user. If the guard fires without a prior successful authentication, it rejects with 401.

Source

Thrown at apps/api/v2/src/platform/bookings/2024-08-13/guards/booking-pbac.guard.ts:35

  private bookingAccessService: BookingAccessService;

  constructor(private readonly prismaReadService: PrismaReadService) {
    this.bookingAccessService = new BookingAccessService(
      this.prismaReadService.prisma
    );
  }

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context
      .switchToHttp()
      .getRequest<
        Request & { user?: ApiAuthGuardUser; pbacAuthorizedRequest?: boolean }
      >();
    const user = request.user;
    const bookingUid = request.params.bookingUid;

    if (!user) {
      throw new UnauthorizedException();
    }

    if (!bookingUid) {
      throw new BadRequestException(
        "BookingPbacGuard - bookingUid is required"
      );
    }

    const hasAccess =
      await this.bookingAccessService.doesUserIdHaveAccessToBooking({
        userId: user.id,
        bookingUid,
      });

    if (!hasAccess) {
      throw new ForbiddenException(
        `BookingPbacGuard - user with id=${user.id} does not have access to booking with uid=${bookingUid}`
      );

View on GitHub (pinned to 176037d0af)

Solutions

  1. Ensure the route has @UseGuards(ApiAuthGuard, BookingPbacGuard) in the correct order — ApiAuthGuard must run first to populate request.user.
  2. Verify the Authorization: Bearer header is present and valid — if ApiAuthGuard rejects, it should throw before PBAC runs.
  3. Check the ApiAuthGuard strategy (JWT or API key) for silent failures that return without throwing or setting user.
  4. If the guard ordering is correct, the issue is in the auth strategy — test authentication on a simpler ApiAuthGuard-only endpoint first.

Example fix

// before — missing ApiAuthGuard before PBAC
@UseGuards(BookingPbacGuard)
@Patch(':bookingUid')
async updateBooking(...) { ... }

// after — correct guard ordering
@UseGuards(ApiAuthGuard, BookingPbacGuard)
@Patch(':bookingUid')
async updateBooking(...) { ... }
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the token is valid before hitting a PBAC-protected route
async function validateToken(token) {
  const res = await fetch('/v2/me', {
    headers: { Authorization: `Bearer ${token}` }
  });
  return res.ok;
}

if (!(await validateToken(token))) {
  throw new Error('Token is invalid or expired');
}

Try / catch

try {
  await api.updateBooking(bookingUid, updates);
} catch (err) {
  if (err.statusCode === 401) {
    // Token is invalid or missing — refresh or re-authenticate
    const newToken = await refreshToken();
    await api.updateBooking(bookingUid, updates); // retry
  } else { throw err; }
}

Prevention

When it happens

Trigger: Any route protected by BookingPbacGuard where the ApiAuthGuard did not set request.user — this typically means the route requires both @UseGuards(ApiAuthGuard, BookingPbacGuard) but the ApiAuthGuard was omitted, misconfigured, or the token validation failed silently.

Common situations: The route decorator is missing @UseGuards(ApiAuthGuard) before @UseGuards(BookingPbacGuard). The ApiAuthGuard strategy threw during authentication but the error was swallowed. The access token was rejected by the JWT strategy but a default/fallback strategy returned without setting user. A route was recently refactored and the guard ordering was broken.

Understand the failure class

Related errors


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