{"record":{"id":"f4f919babf2049aa","repo":"yikart/AiToEarn","slug":"channelauthcsrfinvalid","errorCode":"ChannelAuthCsrfInvalid","errorMessage":"ResponseCode.ChannelAuthCsrfInvalid","messagePattern":"ResponseCode\\.ChannelAuthCsrfInvalid","errorType":"exception","errorClass":"AppException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-auth.provider.ts","lineNumber":238,"sourceCode":"\n  private parseMiniAppCallback(input: AuthCallbackInput): DouyinMiniAppCallback | undefined {\n    const payload = {\n      state: input.query?.state ?? input.body?.state,\n      token: input.query?.token ?? input.body?.token,\n      nickname: input.query?.nickname ?? input.body?.nickname,\n      avatar: input.query?.avatar ?? input.body?.avatar,\n      tickets: input.body?.tickets,\n    }\n    if (payload.token === undefined && payload.tickets === undefined) {\n      return undefined\n    }\n\n    const result = DouyinMiniAppCallbackSchema.safeParse(payload)\n    if (!result.success) {\n      const hasInvalidState = result.error.issues.some(issue => issue.path[0] === 'state')\n      const hasInvalidToken = result.error.issues.some(issue => issue.path[0] === 'token')\n      if (hasInvalidState) {\n        throw new AppException(ResponseCode.ChannelAuthCsrfInvalid)\n      }\n      if (hasInvalidToken) {\n        throw new AppException(ResponseCode.ChannelAuthCodeMissing)\n      }\n\n      throw new AppException(ResponseCode.ChannelAuthorizationFailed)\n    }\n\n    assertParsedCallbackState(result.data.state, input.session.id)\n    return result.data\n  }\n\n  private hasMiniAppScope(scopes: string | undefined, scope: string) {\n    return !!scopes?.split(/[,\\s]+/).includes(scope)\n  }\n\n  private maskOpenId(openId: string) {\n    if (openId.length <= 8) {","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-auth.provider.ts#L220-L256","documentation":"Thrown in parseMiniAppCallback when the zod schema validation of the Douyin mini-app callback payload fails specifically on the 'state' field. 'state' carries the CSRF/session identifier, so an invalid state means the callback cannot be tied to a legitimate auth session.","triggerScenarios":"Douyin mini-app callback POST whose state is absent, malformed, or fails assertParsedCallbackState-shaped schema checks (e.g. tampered query params, callback replayed from a different session).","commonSituations":"User bookmarked/refreshed the callback URL, session expired and state no longer parses, a bot hits the callback endpoint with junk parameters, or payload structure changed after a Douyin SDK/flow update.","solutions":["Re-initiate the mini-app authorization flow to generate a fresh state bound to the current session","Check that the frontend forwards the full callback query (including state) unchanged to the backend","Verify DouyinMiniAppCallbackSchema's state field still matches what the Douyin flow actually sends"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const state = callbackPayload?.state\nif (typeof state !== 'string' || state.length === 0) {\n  throw new Error('Missing or invalid OAuth state parameter')\n}","typeGuard":"function hasValidState(payload: unknown): payload is { state: string } {\n  return typeof payload === 'object' && payload !== null && typeof (payload as any).state === 'string' && (payload as any).state.length > 0\n}","tryCatchPattern":"try {\n  await provider.miniAppCallback(payload)\n} catch (e) {\n  if (e instanceof AppException && e.code === 'ChannelAuthCsrfInvalid') {\n    return { restartAuth: true, message: 'Authorization session expired or invalid, please retry' }\n  }\n  throw e\n}","preventionTips":["Store state server-side keyed to the session and verify it before forwarding the callback","Avoid bookmarking/refreshing OAuth callback URLs — always start a new flow","Ensure the frontend forwards the callback query string intact","Expire auth sessions promptly so stale states fail cleanly with a retry hint"],"tags":["oauth","douyin","csrf","validation"],"backgroundTag":"oauth-state-invalid","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}