yikart/AiToEarn · error · Error

授权失败

Error message

授权失败

What it means

handleAuthorizationCode wraps its whole body in try/catch and rethrows a generic Error('授权失败') for ANY failure inside: state lookup, the token exchange POST to oauth2.googleapis.com, id_token verification (verifyIdToken with audience=webClientId), database writes, and even the earlier 无效的状态码 error. The root cause is only in console output, not in the thrown error.

Source

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

      const results = {
        data:
        {
          accountInfo: existingAccount,
          userInfo: {
            "userId": userId,
            "uid": googleId
          }
      },
      msg:"success", code: 0

      };
      console.log("最终返回", results);

      return results;

    } catch (error) {
      console.error('处理授权码失败:', error);
      throw new Error('授权失败');
    }
  }

  /**
   * 获取YouTube频道信息并更新账号数据库
   * @param userId 用户ID
   * @param googleId Google ID
   * @param accessToken 访问令牌
   * @param refreshToken 刷新令牌
   */
  private async updateYouTubeAccountInfo(
    userId: string,
    email: string,
    googleId: string,
    accessToken: string,
    refreshToken: string,
    expires_in: number
  ): Promise<void> {

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Preserve the original error in the rethrow: throw new Error(`授权失败: ${error.message}`) so the API caller sees the cause.
  2. Check the console '处理授权码失败' log for the Google error payload (invalid_grant, redirect_uri_mismatch, invalid_client).
  3. Confirm GOOGLE_CONFIG.WEB_RENDER_URL-based redirect_uri exactly matches a registered redirect URI in Google Cloud Console.
  4. Prevent callback-page refresh/replay; each authorization code is single-use.
  5. Re-run the OAuth flow; if the state error is the cause, the fix is a longer state TTL (see 无效的状态码).

Example fix

// before
} catch (error) {
  console.error('处理授权码失败:', error);
  throw new Error('授权失败');
}
// after
} catch (error) {
  console.error('处理授权码失败:', error);
  if (error instanceof Error && error.message === '无效的状态码') throw error
  throw new Error(`授权失败: ${error?.response?.data?.error || error.message}`)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before exchange: validate inputs and that the code is single-use fresh
if (!code || !state || !userId) throw new Error('missing code/state/userId for authorization callback')
const stateInfo = await redisService.get(`youtube:state:${userId}:${state}`)
if (!stateInfo?.mail) throw new Error('state expired')

Type guard

const isGoogleTokenError = (e: unknown): e is { response: { status: number; data: { error: string; error_description?: string } } } =>
  typeof e === 'object' && e !== null &&
  typeof (e as any).response?.data?.error === 'string'

Try / catch

try {
  return await youtubeAuthService.handleAuthorizationCode(code, state, userId)
} catch (e) {
  if (isGoogleTokenError(e)) {
    const { error, error_description } = e.response.data
    if (error === 'invalid_grant') return restartOAuthFlow() // code used/expired
    if (error === 'redirect_uri_mismatch') return fixRedirectUriConfig()
  }
  if (e.message === '无效的状态码') return restartOAuthFlow()
  throw e
}

Prevention

When it happens

Trigger: Authorization code already used or expired (Google token endpoint returns 400 invalid_grant), redirect_uri mismatch between callback request and the token exchange params, GOOGLE_CONFIG.WEB_CLIENT_SECRET wrong, verifyIdToken failing because id_token audience differs from webClientId, or the earlier state-check throw bubbling here.

Common situations: The web render callback URL not registered as an authorized redirect URI in Google Cloud Console; user refreshing the callback page (code reuse); environment mismatch where the token was issued to a different OAuth client; Redis state expired as in the 无效的状态码 path.

Related errors


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