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
- Complete the OAuth flow first: GET /plat/twitter/auth/url?mail=... then finish the callback so an AccountToken document with refreshToken is created
- Verify the accountId exists: query the accountToken collection (findOne({accountId})) and confirm a non-empty refreshToken is stored
- If the record exists but refreshToken is missing, re-authorize the account; revokeAuthorization permanently unsets refreshToken so the account must re-run OAuth
- 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
- Always complete the OAuth callback flow before calling token-consuming endpoints
- Never reuse accountIds across environments (dev/staging databases differ)
- Remember revokeAuthorization unsets refreshToken permanently — plan re-auth after revocation
- Monitor the accountToken collection for records missing refreshToken
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
- ChannelAuthRefreshTokenMissing
- No response from Gemini
- ChannelAuthRefreshTokenMissing
- ChannelAuthRefreshTokenMissing
- Twitter user profile not found
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/66007202cdb98050.
Report an issue: GitHub.