calcom/cal.diy · critical · ForbiddenException
OAuthClientGuard - No OAuth client associated with the reque
Error message
OAuthClientGuard - No OAuth client associated with the request.
What it means
Thrown by the OAuthClientGuard (a NestJS CanActivate guard applied to OAuth-client-scoped routes) when request.params.clientId is falsy. The guard reads the client id from the route param; if the route was mounted without a :clientId segment, or the param is somehow absent, it rejects with ForbiddenException (HTTP 403). This is a routing/configuration defect, not a user input error.
Source
Thrown at apps/api/v2/src/modules/oauth-clients/guards/oauth-client-guard.ts:24
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
NotFoundException,
} from "@nestjs/common";
@Injectable()
export class OAuthClientGuard implements CanActivate {
constructor(private oAuthClientRepository: OAuthClientRepository, private usersService: UsersService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<ApiAuthGuardRequest>();
const organizationId = this.getOrganizationId(context);
const user: ApiAuthGuardUser = request.user;
const oAuthClientId = request.params.clientId;
if (!oAuthClientId) {
throw new ForbiddenException("OAuthClientGuard - No OAuth client associated with the request.");
}
if (!user || !organizationId) {
throw new ForbiddenException("OAuthClientGuard - No organization associated with the user.");
}
const oAuthClient = await this.oAuthClientRepository.getOAuthClient(oAuthClientId);
if (!oAuthClient) {
throw new NotFoundException("OAuthClientGuard - OAuth client not found.");
}
const allowed = Boolean(user.isSystemAdmin || oAuthClient.organizationId === organizationId);
if (!allowed) {
throw new ForbiddenException(
`OAuthClientGuard - forbidden. oAuth client with id=${oAuthClientId} does not belong to the organization with id=${organizationId}.`
);
}View on GitHub (pinned to 176037d0af)
Solutions
- Ensure every route guarded by OAuthClientGuard includes :clientId in its path, e.g. @Controller(':clientId') at the class level or :clientId in each @Post/@Get path.
- In unit tests, mock request.params = { clientId: '<id>' } before invoking the guard.
- Verify the controller's @Controller() prefix and method path together contain :clientId.
- Grep the module routing to confirm no guard-protected route lacks the param.
Example fix
// before
@Controller('oauth-clients')
@UseGuards(OAuthClientGuard)
export class OAuthClientController {
@Get('events') // no :clientId — guard throws
getEvents() { ... }
}
// after
@Controller('oauth-clients/:clientId')
@UseGuards(OAuthClientGuard)
export class OAuthClientController {
@Get('events') // :clientId now in scope
getEvents() { ... }
} Defensive patterns
Strategy: validation
Validate before calling
// Server-side: assert the route exposes :clientId before mounting the guard
function assertRouteHasClientId(path: string) {
if (!/:clientId(\b|\?|$|\()/.test(path)) {
throw new Error(`Route '${path}' uses OAuthClientGuard but has no :clientId param`);
}
} Type guard
function requestHasClientId(req: { params?: Record<string, unknown> }): req is { params: { clientId: string } } {
return typeof req.params?.clientId === 'string' && (req.params as any).clientId.length > 0;
} Try / catch
// Guard-level: throw early and descriptive — callers cannot recover from a routing bug. // Fix the route definition rather than catching at runtime.
Prevention
- Keep :clientId in the @Controller prefix for every OAuthClientGuard-protected controller.
- Add an integration test that hits each guarded route.
- In unit tests, mock request.params.clientId explicitly.
When it happens
Trigger: A controller method decorated with @UseGuards(OAuthClientGuard) is mounted on a route that has no :clientId parameter, or the request reaches it via a path that didn't populate the param. Reproducible only by misconfigured routing — a correctly mounted route always has the param.
Common situations: Refactoring a controller and forgetting to keep :clientId in the route path; mounting the controller under a prefix that already consumed the param; a test that invokes the handler directly without a mocked params object.
Related errors
- No user id found in request params.
- No ooo entry id found in request params.
- IsUserWebhookGuard - No webhook id found in request params.
- PermissionsGuard - no authentication provided. Provide eithe
- PermissionsGuard - oAuth client with id=${oAuthClient.id} do
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/adf330c2dc6eb6e4.
Report an issue: GitHub.