fix(cli): correct the login token URL, and name the real cause of edge 401s - #18
Merged
Merged
Conversation
…e 401s
Three problems surfaced while confirming that PATs don't authenticate against
deployed backends.
1. `yertle login` printed the wrong URL. It appended `/settings` to the *API*
base URL, but tokens are minted in the web app:
https://api.dev.yertle.com/settings -> 401
https://dev.yertle.com/settings -> 200
Every first-time user was sent to a 401 page during their first
interaction with the tool. `_web_url_for` now derives the web host by
stripping an `api.` prefix, maps localhost:8000 -> :3000 for local dev, and
returns None rather than guessing when the mapping isn't obvious — in which
case the prompt stays vague instead of confidently wrong. `--web-url`
overrides it.
2. The 401 message listed three causes, none of which was the real one.
Deployed hosts sit behind API Gateway's Cognito JWT authorizer, which
rejects a `yrt_` PAT before Lambda runs — it isn't a JWT, so it fails
structurally ("token contains an invalid number of segments" in the
www-authenticate header) and is never looked up. Telling the user their
token may be "revoked or expired" sends them to debug a token that is
perfectly valid.
`UnexpectedStatus` carries only status_code and content, so detection uses
the body shape: API Gateway returns {"message": ...}, FastAPI returns
{"detail": ...} — the difference we observed against dev vs localhost.
App-level 401s keep the original message.
3. `yertle login` accepted an empty token, persisting a config that looks
populated but resolves as unauthenticated, since `resolve()` treats "" as
absent. Now refuses and saves nothing. Found by nearly clobbering my own
config while testing (1).
Twelve tests, verified to fail against the previous implementation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three bugs surfaced while confirming that PATs don't authenticate against deployed backends.
1.
yertle loginprinted a URL that 401sIt appended
/settingsto the API base URL. Tokens are minted in the web app:https://api.dev.yertle.com/settings(what login printed)https://dev.yertle.com/settings(where the page is)Every first-time user was sent to a 401 page during their first interaction with the tool. It's been wrong since the command was written.
_web_url_for()now derives the web host, and declines to guess rather than being confidently wrong:--web-urloverrides it.2. The 401 message named three causes, none of them the real one
Deployed hosts sit behind API Gateway's Cognito JWT authorizer. A
yrt_PAT isn't a JWT, so it's rejected structurally, before Lambda runs and before any lookup — thewww-authenticateheader sayserror_description="token contains an invalid number of segments". Telling users their token might be "revoked or expired" sends them off to debug a token that is perfectly valid. (I watched exactly that happen.)Before / after against
api.dev.yertle.comwith a freshly minted PAT:UnexpectedStatusexposes onlystatus_codeandcontent— no headers — so detection keys off the body shape: API Gateway returns{"message": …}, FastAPI returns{"detail": …}. That's precisely the difference observed between dev and localhost. App-level 401s keep the original message, verified against a real bad token on localhost.3.
loginaccepted an empty tokenIt would persist
{"token": ""}— a config that looks populated but resolves as unauthenticated, sinceresolve()treats""as absent. Now refuses and saves nothing.Found by nearly clobbering my own config while testing (1).
Testing
make checkgreen (93 tests, +12). All twelve new tests verified to fail against the previous implementation. Both messages checked against live backends — dev for the edge rejection, localhost for the app-level one.Note
This makes the failure legible; it does not fix it. PATs still cannot authenticate against any deployed environment — that's the infrastructure gap, tracked separately.
🤖 Generated with Claude Code