calcom/cal.diy · error · ForbiddenException

OAuthClientGuard - No organization associated with the user.

Error message

OAuthClientGuard - No organization associated with the user.

What it means

Thrown by OAuthClientGuard when either request.user is falsy (no authenticated principal) or getOrganizationId(context) returns falsy. getOrganizationId prefers request.organizationId (set by the auth method) and otherwise derives the org from the user's profile. A user with no organization membership, or an auth method that doesn't populate organizationId, trips this ForbiddenException (HTTP 403).

Source

Thrown at apps/api/v2/src/modules/oauth-clients/guards/oauth-client-guard.ts:28

  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}.`
      );
    }
    return true;
  }

  getOrganizationId(context: ExecutionContext) {

View on GitHub (pinned to 176037d0af)

Solutions

  1. Ensure the authenticated user is a member of the organization that owns the OAuth client — assign the user to the org first.
  2. Use an API key or auth method that populates organizationId for org-scoped routes.
  3. In tests, seed the user with an organization profile before invoking guarded routes.
  4. If the user legitimately has no org, route them through org creation/invitation before they hit OAuth-client endpoints.

Example fix

// before — user has no org membership
const apiKey = createPersonalApiKey(userWithoutOrg);
await fetch(`/v2/oauth-clients/${clientId}/events`, { headers: { Authorization: `Bearer ${apiKey}` } }); // 403

// after — invite the user to the org first
await inviteUserToOrganization(userWithoutOrg.id, orgId, 'MEMBER');
await fetch(`/v2/oauth-clients/${clientId}/events`, { headers: { Authorization: `Bearer ${apiKey}` } });
Defensive patterns

Strategy: validation

Validate before calling

// Before calling a guarded route, ensure the auth context carries an org
function assertOrgContext(user: { organizationId?: number | null }, requestOrgId?: number) {
  const orgId = requestOrgId ?? user.organizationId;
  if (!orgId) throw new Error('User has no organization context for org-scoped routes');
  return orgId;
}

Type guard

function userHasOrgContext(user: unknown, reqOrgId?: unknown): boolean {
  return Boolean(reqOrgId) || Boolean((user as any)?.organizationId);
}

Try / catch

try {
  await guardedRouteCall();
} catch (e) {
  if (e instanceof ForbiddenException && /organization/i.test(e.message)) {
    // prompt the user to create/join an organization
  } else throw e;
}

Prevention

When it happens

Trigger: An API key / session whose user belongs to no organization calls an OAuthClientGuard-protected route; or the auth strategy in play (e.g. a personal API key without org context) did not set request.organizationId and the user has no org profile.

Common situations: Calling org-scoped endpoints with a legacy personal API key that has no org binding; a user that was created outside any organization; an auth method (custom strategy) that forgets to attach organizationId; testing with a seed user that was never assigned to an org.

Related errors


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