{"record":{"id":"3aac4ec8d31599f4","repo":"yikart/AiToEarn","slug":"error-3aac4e","errorCode":null,"errorMessage":"无效的状态参数或状态已过期","messagePattern":"无效的状态参数或状态已过期","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.auth.service.ts","lineNumber":187,"sourceCode":"   * @param state 状态码\n   * @returns 处理结果\n   */\n  async handleAuthorizationCallback(code: string, state: string): Promise<object> {\n    // // 解析状态参数\n    // let parsedState;\n    // try {\n    //   parsedState = JSON.parse(decodeURIComponent(state));\n    // } catch (error) {\n    //   this.logger.error('无法解析状态参数:', error);\n    //   throw new BadRequestException('无效的状态参数格式');\n    // }\n\n    // 从Redis获取保存的状态信息\n    // const originalState = parsedState.state;\n\n    const stateDataJson = await this.redisService.get(`tiktok:state:${state}`);\n    if (!stateDataJson) {\n      throw new BadRequestException('无效的状态参数或状态已过期');\n    }\n    // 解析状态数据\n    const stateData = JSON.parse(stateDataJson);\n    console.log('stateData:------', stateData);\n    const { userId, codeVerifier } = stateData;\n    if (!userId || !codeVerifier) {\n      throw new BadRequestException('状态数据不完整');\n    }\n\n    // 删除Redis中的状态信息\n    await this.redisService.del(`tiktok:state:${state}`);\n\n    try {\n      // 使用授权码交换令牌，并传入codeVerifier\n      const tokenResponse = await this.exchangeCodeForTokens(code, codeVerifier);\n      console.log(\"获取授权码成功！\", tokenResponse);\n      // 获取用户信息\n      const userProfile = await this.getTikTokUserProfile(tokenResponse.access_token, tokenResponse.open_id);","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.auth.service.ts#L169-L205","documentation":"handleAuthorizationCallback validates TikTok's OAuth callback `state` parameter against data previously stored in Redis under `tiktok:state:<state>`. If Redis has no entry for the state, it throws BadRequestException('无效的状态参数或状态已过期') ('invalid state parameter or state expired'), protecting against CSRF and replayed/expired callbacks.","triggerScenarios":"Calling handleAuthorizationCallback with a state that was never generated by getAuthorizationUrl, was already consumed and deleted from Redis, or whose Redis TTL expired before the user completed TikTok authorization.","commonSituations":"User left the TikTok consent screen open past the state TTL and then submitted; user reloads/replays the callback URL (state already deleted after first use); server restart or Redis eviction/flush losing the key; multi-instance deployment without shared Redis; state mangled by URL encoding issues.","solutions":["Increase the Redis TTL set in getAuthorizationUrl if users take long on the consent screen","Ensure all app instances share the same Redis instance so state written by one pod is readable by another","Treat this as an expected user flow: catch it in the controller and redirect the user to restart authorization instead of returning a raw 400","Do not delete the state before successful token exchange, or handle replay idempotently","Check for URL-encoding differences (state passed through query may be encoded) before lookup"],"exampleFix":"// before\nconst stateDataJson = await this.redisService.get(`tiktok:state:${state}`);\nif (!stateDataJson) {\n  throw new BadRequestException('无效的状态参数或状态已过期');\n}\n// after\nconst stateDataJson = await this.redisService.get(`tiktok:state:${state}`);\nif (!stateDataJson) {\n  throw new BadRequestException(\n    'Authorization session expired or invalid, please restart the TikTok connection process',\n  );\n}","handlingStrategy":"try-catch","validationCode":"// before redirecting to TikTok, confirm the state was issued recently\nconst issued = await redis.ttl(`tiktok:state:${state}`);\nif (issued < 0) throw new Error('State missing in Redis — restart authorization');","typeGuard":null,"tryCatchPattern":"try {\n  return await tiktokAuthService.handleAuthorizationCallback(state, code);\n} catch (err) {\n  if (err instanceof BadRequestException && err.message.includes('状态')) {\n    return res.redirect('/connect/tiktok?reason=state_expired');\n  }\n  throw err;\n}","preventionTips":["Set a generous state TTL (e.g. 10 min) and surface a 'restart authorization' UX path","Share one Redis across all app instances","Never reuse callback URLs — state is single-use and deleted after consumption","Ensure state is URL-safe (base64url) to avoid encoding mismatches","Alert on tiktok:state Redis eviction under memory pressure"],"tags":["tiktok","oauth","csrf","redis","state-expired"],"backgroundTag":"oauth-state-invalid-or-expired","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}