{"record":{"id":"d10931755f348b6e","repo":"yikart/AiToEarn","slug":"error-d10931","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":194,"sourceCode":"    //   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);\n\n      // 更新或创建TikTok账户信息\n      await this.updateTikTokAccountInfo(\n        userId,\n        userProfile.open_id,\n        tokenResponse.access_token,\n        tokenResponse.refresh_token,","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.auth.service.ts#L176-L212","documentation":"After finding state data in Redis, handleAuthorizationCallback destructures { userId, codeVerifier } from it; if either field is missing it throws BadRequestException('状态数据不完整') ('state data incomplete'). codeVerifier is required for the PKCE token exchange and userId to attribute the connection, so an entry lacking them is unusable.","triggerScenarios":"Calling handleAuthorizationCallback when the Redis value for the state parses as JSON but lacks userId or codeVerifier — i.e. the state was written with incomplete fields (older version of the writer code, manual Redis edit, corrupted or truncated value, or a JSON shape change between versions).","commonSituations":"Deploy where getAuthorizationUrl was changed (field renamed, e.g. codeVerifier -> code_verifier) but old-format state entries persist in Redis; someone manually injected test state into Redis; JSON.parse of a non-conforming cached value.","solutions":["Check what getAuthorizationUrl actually stores and confirm field names (userId, codeVerifier) match exactly","Flush stale tiktok:state:* keys after changing the state payload schema, or version the keys (tiktok:state:v2:<state>)","Have the user restart authorization to generate fresh, complete state data","Add a validation/DTO check when writing state so incomplete entries are rejected at write time"],"exampleFix":"// before\nconst { userId, codeVerifier } = stateData;\nif (!userId || !codeVerifier) {\n  throw new BadRequestException('状态数据不完整');\n}\n// after\nconst { userId, codeVerifier } = stateData ?? {};\nif (typeof userId !== 'string' || typeof codeVerifier !== 'string') {\n  throw new BadRequestException('Stored TikTok state data is incomplete; restart authorization');\n}","handlingStrategy":"type-guard","validationCode":"const raw = await redis.get(`tiktok:state:${state}`);\nconst parsed = raw ? JSON.parse(raw) : null;\nif (!parsed?.userId || !parsed?.codeVerifier) {\n  return res.redirect('/connect/tiktok?reason=state_incomplete');\n}","typeGuard":"function isCompleteState(d: unknown): d is { userId: string; codeVerifier: string } {\n  return !!d && typeof d === 'object'\n    && typeof (d as any).userId === 'string'\n    && typeof (d as any).codeVerifier === 'string';\n}","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_corrupted');\n  }\n  throw err;\n}","preventionTips":["Validate the state payload at write time in getAuthorizationUrl","Version the Redis key schema so old-format entries never match new readers","Clear stale tiktok:state:* keys after deploying schema changes","Wrap JSON.parse in try/catch to treat corrupted values as expired state"],"tags":["tiktok","oauth","pkce","redis","data-integrity"],"backgroundTag":"oauth-state-payload-incomplete","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}