{"record":{"id":"dfecc88a46357ac5","repo":"yikart/AiToEarn","slug":"channelauthrefreshtokenmissing-dfecc8","errorCode":"ChannelAuthRefreshTokenMissing","errorMessage":"ResponseCode.ChannelAuthRefreshTokenMissing","messagePattern":"ResponseCode\\.ChannelAuthRefreshTokenMissing","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":97,"sourceCode":"    const result = await this.douyinService.exchangeCode(callback.code)\n\n    return {\n      accessToken: result.accessToken,\n      refreshToken: result.refreshToken,\n      expiresAt: result.expiresAt,\n      scope: result.scope,\n      tokenType: 'Bearer',\n      platformUid: result.openId,\n      raw: {\n        openId: result.openId,\n        refreshExpiresAt: result.refreshExpiresAt,\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.douyinService.refreshAccessToken(input.refreshToken)\n\n    return {\n      accessToken: result.accessToken,\n      refreshToken: result.refreshToken,\n      expiresAt: result.expiresAt,\n      scope: result.scope,\n      tokenType: 'Bearer',\n      raw: {\n        refreshExpiresAt: result.refreshExpiresAt,\n      },\n    }\n  }\n\n  async revoke(input: RevokeCredentialInput): Promise<void> {\n    if (!input.platformUid) {","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-auth.provider.ts#L79-L115","documentation":"DouyinAuthProvider.refresh() requires input.refreshToken to refresh a Douyin credential. When the stored credential has no refresh token (it was never captured at connect time, or it was cleared after use), the provider refuses to proceed and throws ChannelAuthRefreshTokenMissing instead of calling refreshAccessToken.","triggerScenarios":"Invoking the credential refresh flow (scheduled token refresh or on-demand refresh before an API call) with a credential record whose refreshToken field is null/empty.","commonSituations":"Credentials created before refresh-token storage was added, credentials persisted after a token exchange that omitted refreshToken, rows where the refresh token was nulled after a prior refresh, or partial imports of channel data.","solutions":["Trigger a full re-authorization (OAuth connect) for the channel to obtain a fresh access + refresh token pair.","Audit the credential store: find rows where refreshToken is null and confirm the connect flow persists it.","If your Douyin app only issues refresh tokens for certain auth flows, ensure the flow used grants one (or shorten access-token TTL assumptions)."],"exampleFix":"// before\nawait channelAuth.refresh(credential) // throws if refreshToken missing\n// after\nif (!credential.refreshToken) {\n  await reauthorizeChannel(channelId) // full OAuth connect\n} else {\n  await channelAuth.refresh(credential)\n}","handlingStrategy":"validation","validationCode":"if (!credential.refreshToken) {\n  await reauthorizeChannel(credential.channelId) // full OAuth connect instead of refresh\n} else {\n  await douyinAuth.refresh(credential)\n}","typeGuard":"function canRefresh(c: Credential): c is Credential & { refreshToken: string } {\n  return typeof c.refreshToken === 'string' && c.refreshToken.length > 0\n}","tryCatchPattern":"try {\n  return await douyinAuth.refresh(credential)\n} catch (e) {\n  if (e.code === 'ChannelAuthRefreshTokenMissing') {\n    await markChannelNeedsReconnect(credential.channelId)\n    return null // or trigger interactive re-auth\n  }\n  throw e\n}","preventionTips":["Always persist refreshToken at connect time in the same transaction as accessToken.","Backfill or flag legacy credentials lacking refresh tokens.","Monitor token expiry and schedule refreshes well before TTL.","Surface a 'reconnect required' state in the UI when refresh is impossible."],"tags":["douyin","oauth","refresh-token","credential-management"],"backgroundTag":"missing-refresh-token","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}