yikart/AiToEarn · error · AppException

ChannelAuthorizationFailed

ChannelAuthorizationFailed

Error message

ResponseCode.ChannelAuthorizationFailed

What it means

exchangeMiniAppCallback is the mini-app branch of the Douyin auth exchange, but it double-checks that the provider is actually configured for AuthType.QrCode. If config.authType is anything else, the mini-app callback is not a valid flow for this channel configuration and ChannelAuthorizationFailed is thrown — a configuration/flow mismatch, not a user denial.

Source

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

        country: userInfo.country,
        eAccountRole: userInfo.eAccountRole,
      },
    }
  }

  private async exchangeMiniAppCallback(
    input: AuthCallbackInput,
    callback: DouyinMiniAppCallback,
  ): Promise<CredentialResult> {
    const sessionInfo = await this.douyinMiniAppService.code2Session(callback.token)
    if (!sessionInfo?.openid) {
      throw new AppException(ResponseCode.ChannelAccessTokenFailed)
    }
    const openId = sessionInfo.openid
    const unionId = sessionInfo.unionid

    if (this.config.authType !== AuthType.QrCode) {
      throw new AppException(ResponseCode.ChannelAuthorizationFailed)
    }
    if (!callback.tickets) {
      throw new AppException(ResponseCode.ChannelAuthorizationFailed)
    }

    const userDataTicket = callback.tickets[DOUYIN_MINIAPP_USER_DATA_SCOPE]
    const videoBindTicket = callback.tickets[DOUYIN_MINIAPP_VIDEO_BIND_SCOPE]
    const homepageCredential = await this.douyinMiniAppService.getUserAccessToken(userDataTicket)
    const videoCredential = userDataTicket === videoBindTicket
      ? homepageCredential
      : await this.douyinMiniAppService.getUserAccessToken(videoBindTicket)

    if (homepageCredential.openId !== openId || videoCredential.openId !== openId) {
      throw new AppException(ResponseCode.ChannelAuthorizationFailed)
    }

    if (!this.hasMiniAppScope(homepageCredential.scope, DOUYIN_MINIAPP_USER_DATA_SCOPE)) {
      throw new AppException(ResponseCode.ChannelAuthorizationFailed)

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Set the Douyin channel config authType to AuthType.QrCode for environments serving the mini-app flow.
  2. Verify the client is calling the callback endpoint that matches its flow (mini-app callback vs web OAuth redirect).
  3. Review recent config/env changes to DOUYIN auth settings and redeploy with the correct authType.

Example fix

// before (config)
{ "authType": "web_oauth" }
// after (config)
{ "authType": "qr_code" } // AuthType.QrCode — required for mini-app callbacks
Defensive patterns

Strategy: validation

Validate before calling

// before driving the mini-app flow, assert config supports it
if (douyinConfig.authType !== AuthType.QrCode) {
  throw new Error(`mini-app callback requires authType=QrCode, got ${douyinConfig.authType}`)
}

Type guard

function supportsMiniAppFlow(cfg: { authType: AuthType }): cfg is { authType: AuthType.QrCode } {
  return cfg.authType === AuthType.QrCode
}

Try / catch

try {
  await douyinAuth.exchangeCode(callback)
} catch (e) {
  if (e.code === 'ChannelAuthorizationFailed') {
    logger.error('authType mismatch or consent failure — check channel config authType')
  }
  throw e
}

Prevention

When it happens

Trigger: A mini-app callback reaches exchangeCode → exchangeMiniAppCallback while the Douyin channel config's authType is not QrCode (e.g. set to a web/OAuth type), so the guard `this.config.authType !== AuthType.QrCode` trips.

Common situations: Environment config pointing the channel at the wrong auth type, a mini-app client calling a backend configured for the web OAuth flow, or authType renamed/changed in a config migration.

Related errors


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