feat: sign in with GitHub instead of pasting a token - #13
Conversation
`stele auth login` now asks the server to start a GitHub device flow, prints the code and the URL for a person to open, and waits. What it stores is the publish-only credential the server minted at the end of it. The flow is proxied end to end: the OAuth app's client ID lives on the server and the GitHub access token is consumed inside one server request, so this machine holds neither. A login that pasted a GitHub token would have taken a stele secret out of the agent's reach by putting a GitHub one on the same disk. The pasted-token path stays, and is not a legacy branch. `--admin` takes it, because a sign-in mints `publish` and only `publish` and can never produce the credential that flag asks to keep; and a deployment that has not configured GitHub sign-in refuses the start route, which is also how the first credential on a fresh deployment is made. Without the fallback `auth login` would have no answer at all on those deployments. No new flags and no new exit codes. A terminal refusal is the server's one byte-identical 401 — cancelled, not an owner, no allowlist — so it becomes `.refused` rather than an error with nothing to say, and a 500 still throws: an outage must not read as a person saying no. The poll loop lives in SteleKit with an injected clock, because "does slow_down actually slow the polling down" and "does this give up when the code expires" are the two questions the feature has and both are about seconds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBzRbsSw9JzfEg1p3a3FVo
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (6)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe CLI now defaults to GitHub device-flow authentication. The SteleKit client provides device-code endpoints and bounded polling. Credential storage records authentication origin. Admin and unsupported deployments retain token fallback. Documentation and version metadata were updated. ChangesGitHub device sign-in
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: ⚪ Minimal · up to The CLI changes sign-in from pasted GitHub tokens to the server-mediated device flow while preserving the existing credential path and fallback behavior; no actionable merge-blocking risk remains. Sequence Diagram(s)sequenceDiagram
participant Operator
participant LoginCommand
participant DeviceSignIn
participant SteleClient
participant AuthService
participant CredentialStorage
Operator->>LoginCommand: run auth login
LoginCommand->>DeviceSignIn: start device sign-in
DeviceSignIn->>SteleClient: request device code
SteleClient->>AuthService: POST /auth/github/device
AuthService-->>SteleClient: device code bundle
SteleClient-->>DeviceSignIn: verification URL and user code
DeviceSignIn->>AuthService: poll device-code exchange
AuthService-->>DeviceSignIn: pending or minted/refused
DeviceSignIn-->>LoginCommand: sign-in outcome
LoginCommand->>CredentialStorage: store minted credential and origin
Poem
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/settings/billing. Comment |
A stele server that predates the device flow answers the start route with its uniform 404 — deliberately, so the route's absence is not a probe result — and for as long as such servers exist, "this CLI is newer than that server" is the likeliest meaning of the status. The generic notFound error read as "wrong host" and sent the person auditing a URL they had typed correctly. The 404 now takes the same fallback the 401 does, with its own sentence: the 401 is a deployment that answered and declined, the 404 is one that may simply be older, and the hedge is kept because all this side has seen is a status an arbitrary non-stele host would also produce. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBzRbsSw9JzfEg1p3a3FVo
|
Added 8f50a9d: a 404 from the start route now falls back to the token prompt the way the 401 does, with its own stderr line ("the server may predate the device flow"). Found live: with no --host the CLI resolved the default host — a production server still on the old code — whose uniform 404 surfaced as the generic "the server has nothing there", which reads as a mistyped host rather than an older server. Verified against that same old server: the fallback line prints, then the non-TTY guard stops the prompt as designed. swift build clean; swift test: 188 tests in 21 suites passing. |
stele auth loginused to have the user obtain a GitHub access token and paste it in for the server's exchange route to inspect. The server now proxies GitHub's device flow itself (ProJedi1234/stele-pages#47–#49), so login becomes: callPOST /auth/github/device, print the verification URL and user code on stderr (never open a browser — this tool is usually run over SSH), and pollPOST /auth/github/exchangewith the device code until the server answers. On201the existing path runs unchanged — verify via/admin/whoami, write the credential to disk. On401the sign-in was refused or cancelled, and the remedy printed is to sign in again, since a lost token is recovered by rotation rather than by an operator. The CLI never holds a client ID or a GitHub access token at any point.The poll loop (
DeviceSignIn) sleeps before the first poll, only ever raises the interval (slow_downis a bigger number, not a new case; a smaller or unreadable interval never speeds it up), clamps every wait to the time remaining, and gives up after the server'sexpiresIn. No new flags and no new exit codes:--adminstill skips the sign-in (a sign-in can only mintpublish), and the pasted-token prompt survives as the fallback for a deployment that has not configured a client ID — which is also how the bootstrap token is spent on a fresh one.Version goes 0.4.0 → 0.5.0 per this repo's precedent for feature commits; the server's
minimumCLIVersionis untouched, since the version gate fires on writes and writes did not change.Tests:
swift buildandjust build(release) clean;just test: 188 tests in 21 suites, all passing — newDeviceFlowTestscover the request shapes, all four poll outcomes, interval growth and bounds, the expiry arithmetic, and that the bundle'sdescriptionwithholds the device code.🤖 Generated with Claude Code
https://claude.ai/code/session_01LBzRbsSw9JzfEg1p3a3FVo
Summary by CodeRabbit
New Features
auth login, showing a verification URL and one-time code.Documentation
Chores