{"record":{"id":"65399bd948d41b71","repo":"yikart/AiToEarn","slug":"error-65399b","errorCode":null,"errorMessage":"授权参数不完整","messagePattern":"授权参数不完整","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"project/aitoearn-electron/server/src/modules/plat/youtube/youtube.controller.ts","lineNumber":79,"sourceCode":"  //   //   throw new BadRequestException('邮箱参数不能为空');\n  //   // }\n\n    return this.youtubeAuthService.getAuthorizationUrl(mail, systemToken.id);\n  }\n\n  @ApiOperation({ summary: '处理YouTube授权回调' })\n  @Public()\n  @Get('auth/callback')\n  async handleAuthCallback(\n    // @GetToken() systemToken: TokenInfo,\n    @Query('code') code: string,\n    @Query('state') state: string,\n    // @Query('userId') userId: string,\n    @Res() res: Response\n  ) {\n\n    if (!code || !state) {\n      throw new BadRequestException('授权参数不完整');\n    }\n      // 解析state参数以获取token\n      let stateData;\n      try {\n        stateData = JSON.parse(decodeURIComponent(state));\n      } catch (error) {\n        throw new BadRequestException('无效的state参数');\n      }\n\n      const { originalState, userId, email } = stateData;\n\n      // 现在您可以使用token变量\n      console.log('Retrieved userId and originalState:', userId, originalState, email);\n\n    try {\n      const results = await this.youtubeAuthService.handleAuthorizationCode(code, originalState, userId);\n      // 重定向到前端页面，带上token\n      // return res.redirect(`/auth/success?token=${token}`);","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/youtube/youtube.controller.ts#L61-L97","documentation":"The OAuth callback endpoint GET /youtube/auth/callback requires both 'code' (Google's authorization code) and 'state' (the state payload) query parameters. If either is missing or empty, a BadRequestException (HTTP 400) '授权参数不完整' (incomplete authorization parameters) is thrown.","triggerScenarios":"Google redirects to the callback without code (user denied consent) or without state (state not included/lost in the auth URL), or a manual/test request to the callback omits either parameter.","commonSituations":"User clicks 'cancel' on the Google consent screen so Google redirects with error=access_denied and no code; the authorization URL was built without state; redirect URI misconfigured so query params are dropped; user bookmarks/reloads a stale callback URL.","solutions":["Check whether the callback URL contains error=access_denied — the user denied consent; restart the flow and require approval","Ensure the auth URL was generated with both state and the correct scope so Google returns code and state","Verify the redirect URI registered in Google Cloud Console exactly matches the one used to build the auth URL","Handle the user-denied case gracefully instead of letting it surface as 400"],"exampleFix":"// before\nconst res = await fetch(`/api/plat/youtube/auth/callback`);\n// after\nconst res = await fetch(`/api/plat/youtube/auth/callback?code=${code}&state=${encodeURIComponent(state)}`);","handlingStrategy":"validation","validationCode":"const url = new URL(callbackRequestUrl, base);\nif (!url.searchParams.get('code') || !url.searchParams.get('state')) {\n  const err = url.searchParams.get('error');\n  throw new Error(`Callback missing code/state${err ? ` (OAuth error: ${err})` : ''}`);\n}","typeGuard":"function hasOAuthCallbackParams(q: Record<string, unknown>): q is { code: string; state: string } {\n  return typeof q.code === 'string' && q.code.length > 0 && typeof q.state === 'string' && q.state.length > 0;\n}","tryCatchPattern":"try {\n  await api.get('/plat/youtube/auth/callback', { params: { code, state } });\n} catch (e) {\n  if (e.response?.status === 400 && String(e.response.data?.message).includes('授权参数不完整')) {\n    return restartOAuthFlow(); // e.g. user denied consent — no code was returned\n  }\n  throw e;\n}","preventionTips":["Include state in the authorization URL every time","Detect error=access_denied on the callback URL and handle the denial path explicitly","Verify the redirect URI matches Google Cloud Console registration exactly","Never reuse stale callback URLs from bookmarks/history"],"tags":["oauth","http-400","callback","query-param"],"backgroundTag":"oauth-callback-missing-params","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}