Skip to content

fix: parse TikTok's OAuth exchange envelope so the token reaches the form - #329

Open
aaaaahaaaaa wants to merge 1 commit into
mainfrom
fix/tiktok-oauth-token-envelope
Open

fix: parse TikTok's OAuth exchange envelope so the token reaches the form#329
aaaaahaaaaa wants to merge 1 commit into
mainfrom
fix/tiktok-oauth-token-envelope

Conversation

@aaaaahaaaaa

Copy link
Copy Markdown
Contributor

Problem

"Sign in with TikTok" in production: the popup completes, TikTok reports success, but the connection form never shows a fetched token.

TikTok Business answers POST /open_api/v1.3/oauth2/access_token/ with HTTP 200 for success and failure alike, wrapping the outcome in a {code, message, data} envelope (live-probed: {"code":40002,"message":"app_id: invalid params..."}). The route relayed that body verbatim, and the app read tokens.refresh_token ?? tokens.access_token at the top level, so:

  • on success, the token sat under data and the field stayed silently empty behind a green "Connected to TikTok" toast;
  • on rejection, raise_for_status() never tripped and the flow also reported success.

Both outcomes were indistinguishable from the outside, which is why this read as "exchange issue or UI issue?".

Fix

Providers already own the request side of the code exchange (38a2949); this gives them the response side, mirroring the existing parse_refresh_token_response:

  • core OAuthProvider.parse_authorization_code_response(payload): base is RFC passthrough. TikTokProvider unwraps data and raises ValueError carrying TikTok's message when code != 0.
  • api the exchange route applies the parser before returning, so an in-band rejection becomes a 500 the popup's Details collapse can show.
  • app oauthToken() (composables/oauth.ts) is the single owner of the refresh_token/access_token pick; OAuthSignIn now throws on a token-less response instead of emitting success, routed through the existing errorToast; SchemaForm.handleOAuthSuccess(token: string) just places it.
  • Restored the trailing slash on TikTok's token_url: the slash-less URL 308-redirects and only worked thanks to follow_redirects=True.

Tests

TDD: 5 new tests failed against the old code, then passed.

  • core/tests/oauth/test_base.py: base parser is passthrough.
  • core/tests/oauth/test_providers.py: TikTok unwraps data; raises on non-zero code.
  • api/tests/routes/test_oauth.py: route returns the parsed body for TikTok; in-band rejection is a 500 carrying the message.

pytest (44 in scope) / ruff check / ty / pnpm run lint / nuxt typecheck all green; pre-commit passed on commit.

Reviewer notes

  • Not live-verified end to end: I had no real TikTok auth code. The nested-data success shape is TikTok's documented shape, corroborated by the error probe's envelope and by TiktokAdsConnection.advertisers() already reading body["data"]. Worth one real sign-in after deploy.
  • ruff format --check flags two pre-existing spots in oauth/base.py and test_base.py untouched by this change; left out deliberately (pre-commit runs ruff-check only).
  • OAuthSignIn's success emit changes from Record<string, unknown> to string; SchemaForm is its only consumer.

By Digitl

…form

TikTok Business answers the code exchange with HTTP 200 and a
{code, message, data} envelope on success and failure alike. The route
relayed that body verbatim, and the app read `refresh_token ?? access_token`
at the top level, so the token field stayed silently empty behind a green
"Connected" toast; a rejection inside a 200 looked exactly the same.

Providers already own the request side of the code exchange; give them the
response side too: OAuthProvider.parse_authorization_code_response (base =
RFC passthrough) mirrors parse_refresh_token_response, and TikTokProvider
unwraps `data` and raises on a non-zero `code`. The route applies it before
returning, so an in-band rejection becomes a 500 the popup can display.

App side, `oauthToken()` becomes the single owner of the credential pick and
OAuthSignIn treats a token-less response as a failure rather than emitting
success; SchemaForm just places the issued token.

Also restore the trailing slash on TikTok's token_url: the slash-less URL
308-redirects, which only worked thanks to follow_redirects.

By Digitl
@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant