# YouTube manual PKCE callback token exchange

Use this when a remote/headless AutoShortsBot session cannot receive the local `localhost` OAuth redirect directly and the user pastes the final Google callback URL back into chat.

## Preconditions

- Allowed scope is exactly `https://www.googleapis.com/auth/youtube.upload`.
- Redirect URI, generated `state`, and PKCE `code_verifier` are stored together for the current manual auth URL.
- The pasted callback URL is treated as secret-bearing input. Never echo the raw `code`, access token, refresh token, client secret, or full callback URL in the final answer.

## Safe sequence

1. Parse the pasted callback URL and verify all of these before exchanging the code:
   - `state` equals the stored current state.
   - redirect host/port/path equals the stored redirect URI, e.g. `http://localhost:8765/`.
   - callback `scope` is exactly `https://www.googleapis.com/auth/youtube.upload`.
   - stored `code_verifier` exists and belongs to the same auth URL.
2. If any value mismatches, do **not** exchange the code. Stop the old flow, generate a fresh upload-only URL, and ask for the fresh callback.
3. Exchange the code at `https://oauth2.googleapis.com/token` with:
   - `client_id`
   - `client_secret` if present for the installed/web client
   - `code`
   - `code_verifier`
   - `grant_type=authorization_code`
   - exact `redirect_uri`
4. Validate the token response scopes before writing anything:
   - upload scope must be present.
   - no extra YouTube, YouTube Partner, or YouTube Analytics scopes may be present.
5. Write the token only after scope validation, using authorized-user JSON shape:
   - `token`
   - `refresh_token`
   - `token_uri=https://oauth2.googleapis.com/token`
   - `client_id`
   - `client_secret`
   - `scopes`
6. Set token file permissions to `0600`.
7. Delete the temporary manual OAuth state file.
8. Run `python -m autoshorts.cli.youtube_auth_upload_only --validate-only` and report only redacted status.

## Reporting format

Report concise safety facts only:

- token exchange succeeded/failed
- token file exists
- file mode, e.g. `0600`
- upload-only scope present
- forbidden scope count or absence
- refresh token present yes/no
- temporary state deleted yes/no
- no real upload executed

Do not print or summarize raw token JSON, authorization code, refresh token, client secret, or the complete callback URL.

## Pitfalls

- A correct-looking upload-only scope is not sufficient if `state`, redirect port, or PKCE verifier belongs to another OAuth run.
- Do not reuse callback codes from killed or stale `run_local_server` flows. PKCE is designed to reject or make this unsafe.
- Do not preserve a token that contains broad YouTube/read/analytics scopes. Mark it unsafe and refuse upload use.
