yikart/AiToEarn · error · BadRequestException

无效的账号或刷新令牌丢失

Error message

无效的账号或刷新令牌丢失

What it means

A BadRequestException thrown by getUserAccessToken when no account token record exists in MongoDB for the given accountId+TIKTOK platform, or the record exists but its refreshToken field is empty. This is a local pre-condition check before attempting any refresh call.

Source

Thrown at project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.auth.service.ts:507

   */
  async getUserAccessToken(accountId: string): Promise<string> {
    this.logger.log(`获取TikTok访问令牌: accountId=${accountId}`);

    // 先检查Redis缓存
    const cachedToken = await this.redisService.get(`tiktok:accessToken:${accountId}`);
    if (cachedToken && cachedToken.access_token) {
      this.logger.log("从Redis获取到有效令牌");
      return cachedToken.access_token;
    }

    // 如果缓存中没有,尝试刷新
    const accountTokenInfo = await this.accountTokenModel.findOne({
      accountId: accountId,
      platform: TokenPlatform.TIKTOK
    });

    if (!accountTokenInfo || !accountTokenInfo.refreshToken) {
      throw new BadRequestException('无效的账号或刷新令牌丢失');
    }

    // 刷新并获取新令牌
    const refreshResult = await this.refreshAccessToken(
      accountTokenInfo.userId,
      accountTokenInfo.accountId,
      accountTokenInfo.refreshToken
    );

    // 刷新后再次从Redis获取
    const newToken = await this.redisService.get(`tiktok:accessToken:${accountId}`);
    if (!newToken || !newToken.access_token) {
      throw new BadRequestException('刷新令牌后未能获取访问令牌');
    }

    return newToken.access_token;
  }

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Verify the accountId exists in the accountToken collection with platform='TIKTOK' and a non-empty refreshToken.
  2. If refreshToken is missing, ask the user to complete the TikTok OAuth flow again via /tiktok/auth-url.
  3. Confirm the caller passes the correct accountId (not userId) and that you're querying the right environment's database.
  4. Check whether token cleanup/invalidation logic recently removed the refreshToken field.

Example fix

// before
if (!accountTokenInfo || !accountTokenInfo.refreshToken) {
  throw new BadRequestException('无效的账号或刷新令牌丢失');
}
// after
if (!accountTokenInfo) {
  throw new BadRequestException(`账号 ${accountId} 未绑定 TikTok,请先完成授权`);
}
if (!accountTokenInfo.refreshToken) {
  throw new BadRequestException(`账号 ${accountId} 刷新令牌已失效,请重新授权`);
}
Defensive patterns

Strategy: validation

Validate before calling

// before calling access-token endpoint
const rec = await db.accountTokens.findOne({ accountId, platform: 'TIKTOK' });
if (!rec?.refreshToken) {
  throw new Error(`account ${accountId} has no TikTok refresh token; run OAuth flow first`);
}

Type guard

function hasUsableTikTokAccount(t: unknown): t is { accountId: string; refreshToken: string } {
  return typeof t === 'object' && t !== null &&
    typeof (t as any).refreshToken === 'string' && (t as any).refreshToken.length > 0;
}

Try / catch

try {
  const token = await api.getTikTokAccessToken(accountId);
} catch (e) {
  if (/刷新令牌丢失/.test((e as Error).message)) {
    const authUrl = await api.getTikTokAuthUrl(userId, mail);
    return redirectUserToReauth(authUrl);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling GET /tiktok/access-token (getUserAccessToken) with an accountId that was never authorized, the account was deleted, or the stored document has refreshToken unset/null — e.g. it was cleared after a previous invalid_refresh_token failure.

Common situations: Caller passes a userId instead of accountId, account authorized on a different environment (cn vs ai) with a separate database, refresh token scrubbed by cleanup logic, account revoked in TikTok and the app cleared tokens.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/bfed6a81e0a2c157. Report an issue: GitHub.