yikart/AiToEarn · error · AppException

ChannelAuthRefreshTokenMissing

ChannelAuthRefreshTokenMissing

Error message

ResponseCode.ChannelAuthRefreshTokenMissing

What it means

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.

Source

Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-auth.provider.ts:97

    const result = await this.douyinService.exchangeCode(callback.code)

    return {
      accessToken: result.accessToken,
      refreshToken: result.refreshToken,
      expiresAt: result.expiresAt,
      scope: result.scope,
      tokenType: 'Bearer',
      platformUid: result.openId,
      raw: {
        openId: result.openId,
        refreshExpiresAt: result.refreshExpiresAt,
      },
    }
  }

  async refresh(input: RefreshCredentialInput): Promise<CredentialResult> {
    if (!input.refreshToken) {
      throw new AppException(ResponseCode.ChannelAuthRefreshTokenMissing)
    }

    const result = await this.douyinService.refreshAccessToken(input.refreshToken)

    return {
      accessToken: result.accessToken,
      refreshToken: result.refreshToken,
      expiresAt: result.expiresAt,
      scope: result.scope,
      tokenType: 'Bearer',
      raw: {
        refreshExpiresAt: result.refreshExpiresAt,
      },
    }
  }

  async revoke(input: RevokeCredentialInput): Promise<void> {
    if (!input.platformUid) {

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Trigger a full re-authorization (OAuth connect) for the channel to obtain a fresh access + refresh token pair.
  2. Audit the credential store: find rows where refreshToken is null and confirm the connect flow persists it.
  3. If your Douyin app only issues refresh tokens for certain auth flows, ensure the flow used grants one (or shorten access-token TTL assumptions).

Example fix

// before
await channelAuth.refresh(credential) // throws if refreshToken missing
// after
if (!credential.refreshToken) {
  await reauthorizeChannel(channelId) // full OAuth connect
} else {
  await channelAuth.refresh(credential)
}
Defensive patterns

Strategy: validation

Validate before calling

if (!credential.refreshToken) {
  await reauthorizeChannel(credential.channelId) // full OAuth connect instead of refresh
} else {
  await douyinAuth.refresh(credential)
}

Type guard

function canRefresh(c: Credential): c is Credential & { refreshToken: string } {
  return typeof c.refreshToken === 'string' && c.refreshToken.length > 0
}

Try / catch

try {
  return await douyinAuth.refresh(credential)
} catch (e) {
  if (e.code === 'ChannelAuthRefreshTokenMissing') {
    await markChannelNeedsReconnect(credential.channelId)
    return null // or trigger interactive re-auth
  }
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/dfecc88a46357ac5. Report an issue: GitHub.