calcom/cal.diy · error · UnauthorizedException

ApiAuthStrategy - third-party token - No user or team owner

Error message

ApiAuthStrategy - third-party token - No user or team owner associated with the token.

What it means

Thrown by validateThirdPartyAccessToken at the end of resolution when `user` is still null. Either the decoded token had a userId that didn't match a User, or it had a teamId whose owner lookup also failed, or it had neither claim. The token decoded successfully but resolved to no principal.

Source

Thrown at apps/api/v2/src/modules/auth/strategies/api-auth/api-auth.strategy.ts:350

    if (decodedToken.userId) {
      user = await this.userRepository.findByIdWithProfile(decodedToken.userId);
      if (user) {
        organizationId = this.usersService.getUserMainOrgId(user) as number;
      }
    } else if (decodedToken.teamId) {
      const teamOwner = await this.userRepository.findOwnerByTeamIdWithProfile(decodedToken.teamId);
      if (!teamOwner) {
        throw new UnauthorizedException(
          "ApiAuthStrategy - third-party token - No owner found for the associated team."
        );
      }
      user = teamOwner;
      organizationId =
        teamOwner.profiles?.find((p) => p.organizationId === decodedToken.teamId)?.organizationId ?? null;
    }

    if (!user) {
      throw new UnauthorizedException(
        "ApiAuthStrategy - third-party token - No user or team owner associated with the token."
      );
    }

    request.organizationId = organizationId;
    return { success: true, data: user };
  }
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Re-issue the third-party token after confirming the embedded userId (or teamId + its owner) still exists.
  2. Inspect the decoded token payload to confirm it contains a valid userId or teamId claim.
  3. Switch to platform OAuth access tokens for first-class support and clearer errors.
Defensive patterns

Strategy: validation

Validate before calling

const decoded = decodeThirdPartyToken(token);
if (!decoded?.userId && !decoded?.teamId) {
  throw new Error('Third-party token has neither userId nor teamId; re-issue it');
}
if (decoded.userId) {
  const u = await db.user.findUnique({ where: { id: decoded.userId } });
  if (!u) throw new Error('Third-party token userId does not match an existing user');
}

Type guard

function isThirdPartyTokenPayload(p: unknown): p is { userId?: number; teamId?: number } {
  if (typeof p !== 'object' || p === null) return false;
  const o = p as any;
  return typeof o.userId === 'number' || typeof o.teamId === 'number';
}

Prevention

When it happens

Trigger: A third-party token whose userId refers to a deleted user AND (no teamId, or the teamId owner also fails). Also when the decoded token carries neither userId nor teamId.

Common situations: User deleted from the org; tampered token; integration built against a different deployment's user/team ids.

Related errors


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