{"record":{"id":"8f938ac1ae6ef46c","repo":"yikart/AiToEarn","slug":"channelauthrefreshtokenmissing-8f938a","errorCode":"ChannelAuthRefreshTokenMissing","errorMessage":"ChannelAuthRefreshTokenMissing","messagePattern":"ChannelAuthRefreshTokenMissing","errorType":"error_code","errorClass":"AppException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/wechat/wechat-official/wechat-official-auth.provider.ts","lineNumber":68,"sourceCode":"    const callback = parseOAuthCallback(input)\n    const result = await this.wechatService.exchangeOfficialCode(callback.code)\n\n    return {\n      accessToken: result.accessToken,\n      refreshToken: result.refreshToken,\n      expiresAt: new Date(Date.now() + result.expiresIn * 1000),\n      scope: result.scope,\n      platformUid: result.openId,\n      raw: {\n        openId: result.openId,\n        unionId: result.unionId,\n      },\n    }\n  }\n\n  async refresh(input: RefreshCredentialInput): Promise<CredentialResult> {\n    if (!input.refreshToken) {\n      throw new AppException(ResponseCode.ChannelAuthRefreshTokenMissing)\n    }\n\n    const result = await this.wechatService.refreshOfficialToken(input.refreshToken)\n\n    return {\n      accessToken: result.accessToken,\n      refreshToken: result.refreshToken,\n      expiresAt: new Date(Date.now() + result.expiresIn * 1000),\n      scope: result.scope,\n    }\n  }\n\n  async revoke(_input: RevokeCredentialInput): Promise<void> {\n    // WeChat Official Account OAuth does not support server-side token revocation.\n    // Tokens expire naturally (2 hours for access, 30 days for refresh).\n    this.logger.log('WeChat Official Account tokens expire naturally; no revocation needed')\n  }\n","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/wechat/wechat-official/wechat-official-auth.provider.ts#L50-L86","documentation":"The WeChat Official Account auth provider refreshes access tokens via the stored refresh token. Because WeChat OAuth refresh requires a refresh_token, calling refresh without one throws AppException(ResponseCode.ChannelAuthRefreshTokenMissing).","triggerScenarios":"Invoking refresh with input.refreshToken undefined/null — typically when the stored credential never captured a refresh token or it was lost during credential rotation.","commonSituations":"Legacy channel records created before refresh tokens were persisted, credentials imported manually, WeChat returning no refresh_token in the initial OAuth exchange (web-app vs snsapi flows).","solutions":["Require the user to re-authorize the WeChat Official Account to obtain a fresh token pair","Persist the refresh_token returned by WeChat's OAuth code exchange into the credential store","Add a check upstream to route channels lacking refreshToken to re-auth instead of refresh","Audit credential rows for null refresh_token and mark them for re-auth"],"exampleFix":"// before\nawait authProvider.refresh({ accessToken })\n// after\nif (!credential.refreshToken) {\n  await startReauthFlow(channelId)\n} else {\n  await authProvider.refresh(credential)\n}","handlingStrategy":"validation","validationCode":"if (!credential.refreshToken) {\n  scheduleReauth(channelId)\n} else {\n  await authProvider.refresh(credential)\n}","typeGuard":"function hasRefreshToken(c: unknown): c is { refreshToken: string } {\n  return typeof (c as any)?.refreshToken === 'string' && (c as any).refreshToken.length > 0\n}","tryCatchPattern":"try {\n  await authProvider.refresh(input)\n} catch (e) {\n  if (e?.code === 'ChannelAuthRefreshTokenMissing') {\n    await startReauthFlow(input.channelId)\n    return\n  }\n  throw e\n}","preventionTips":["Always persist refresh_token from WeChat OAuth exchange","Audit credentials for null refresh tokens","Route refresh-less channels directly to re-auth","Test OAuth flows end-to-end after WeChat API changes"],"tags":["wechat","oauth","refresh-token","auth"],"backgroundTag":"missing-refresh-token","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}