yikart/AiToEarn · error · BadRequestException

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

Error message

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

What it means

getUserAccessToken() in twitter.auth.service.ts loads the cached Twitter access token from Redis; when absent it looks up the account's stored refresh token in the accountTokenModel MongoDB collection. If no AccountToken document exists for that accountId, or the document has no refreshToken field, the service cannot refresh the OAuth token and throws this BadRequestException (HTTP 400). It effectively means the Twitter account was never authorized through OAuth or its refresh token was removed.

Source

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

  /**
   * 获取用户的Twitter访问令牌
   * @param accountId 账号ID
   * @returns 访问令牌
   */
  async getUserAccessToken(accountId: string): Promise<string> {
    console.log("获取访问令牌,accountId:", accountId);

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

    // 如果缓存中没有,尝试刷新
    const accountTokenInfo = await this.accountTokenModel.findOne({accountId: accountId});
    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(`twitter:accessToken:${accountId}`);
    if (!newToken || !newToken.access_token) {
      throw new BadRequestException('刷新令牌后未能获取访问令牌');
    }

    return newToken.access_token;
  }

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Complete the OAuth flow first: GET /plat/twitter/auth/url?mail=... then finish the callback so an AccountToken document with refreshToken is created
  2. Verify the accountId exists: query the accountToken collection (findOne({accountId})) and confirm a non-empty refreshToken is stored
  3. If the record exists but refreshToken is missing, re-authorize the account; revokeAuthorization permanently unsets refreshToken so the account must re-run OAuth
  4. Re-run POST /plat/twitter/auth/refresh with valid userId/accountId/refreshToken to repopulate the token

Example fix

// before: calling with an arbitrary id
await twitterAuthService.getUserAccessToken('665f...guess');
// after: ensure the account is authorized first
if (!(await twitterAuthService.isAuthorized(accountId))) {
  const { url } = await twitterAuthService.getAuthorizationUrl(userId, mail);
  // redirect user to url to complete OAuth
}
const token = await twitterAuthService.getUserAccessToken(accountId);
Defensive patterns

Strategy: validation

Validate before calling

// Before calling any endpoint that needs the token, check authorization
const authorized = await api.get('/plat/twitter/auth/check', { params: { accountId } });
if (!authorized) throw new Error(`Twitter account ${accountId} is not authorized; run the OAuth flow first`);

Type guard

function hasRefreshToken(t: { accountId: string; refreshToken?: string } | null): t is { accountId: string; refreshToken: string } {
  return !!t && typeof t.refreshToken === 'string' && t.refreshToken.length > 0;
}

Try / catch

try {
  const token = await twitterAuthService.getUserAccessToken(accountId);
} catch (e) {
  if (e instanceof BadRequestException && e.message === '无效的账号或刷新令牌丢失') {
    // surface re-authorization flow to the user
    return { needsReauth: true, accountId };
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling any authenticated Twitter endpoint (timeline, createTweet, checkAuthStatus via isAuthorized, etc.) with an accountId that has no AccountToken record in MongoDB, or one whose refreshToken was unset (e.g. after revokeAuthorization's $unset or manual DB cleanup).

Common situations: Passing a wrong/typo'd accountId; testing with an account id from a different environment/database; account revoked via POST auth/revoke (which unsets refreshToken); Redis flushed while the DB record was also deleted; data migration losing the accountTokens collection.

Related errors


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