Skip to content

stele auth login runs the GitHub device flow #7

Description

@ProJedi1234

Parent

ProJedi1234/stele-pages#25 — Epic: GitHub sign-in replaces the shared bootstrap token. The server side of this (POST /auth/github/exchange) is a separate slice in that repo and must be deployed before this one can be tested end to end.

What to build

stele auth login runs the GitHub device flow (RFC 8628) instead of prompting for a pasted token.

No callback URL, no inbound connection, no localhost redirect. The CLI only ever makes outbound HTTPS requests, which is why this works from an SSH session on a headless machine. The short code the human carries to their browser is the pairing channel a redirect would otherwise provide, and the browser doing the approving need not be on the same machine — or the same network — as the CLI.

Three interactions, all initiated by the CLI:

  1. POST https://github.com/login/device/code with the client ID and Accept: application/jsondevice_code (secret, held by the CLI), user_code (shown to the human), verification_uri, expires_in, interval.
  2. Print enter ABCD-1234 at github.com/login/device and wait.
  3. Poll POST https://github.com/login/oauth/access_token with the device_code and grant_type=urn:ietf:params:oauth:grant-type:device_code, sleeping interval seconds between attempts, until GitHub answers with a token.

Then trade that token at the server's exchange endpoint and discard it. The GitHub token is never written to disk; the only thing that reaches the credential file is the minted stele credential.

Structure

A GitHubDeviceFlow type in SteleKit, beside SteleClient rather than inside it. This is the CLI's first HTTP conversation with a host other than the configured stele deployment, and keeping "SteleClient talks only to your stele host" true is worth a separate type. It also gives the tests a clean seam to fake GitHub's responses through the existing transport fakes.

Nothing exotic is needed: Transport.swift is already plain URLSession with the FoundationNetworking import handled for Linux, so this is two form-encoded POSTs, a JSON decode, and try await Task.sleep(for:). No new dependency, no local socket, no browser spawned.

What must not change

The minted credential goes through the existing store-and-report path — the 0600 file, the atomic temp-file-and-rename, the --json shape, and the rule that nothing is written until the server has verified it. --admin semantics are unchanged. Prompts and warnings stay on stderr so --json still emits exactly one document on stdout.

Token-paste survives behind a fallback flag, for older servers and for break-glass.

Polling edge cases

slow_down means add 5 seconds to the interval and keep going, not fail. authorization_pending is the normal answer and must not be reported as an error. expired_token (~15 minutes) and access_denied are terminal and each need a message naming what the person should do next. The loop is bounded by expires_in — it must not be able to spin.

Acceptance criteria

  • Device flow is the default stele auth login; user code and verification URI printed to stderr
  • Poll loop honours interval, slow_down, authorization_pending, expired_token and access_denied; bounded by expires_in and cannot spin
  • Minted credential stored through the existing path; the GitHub token reaches no file
  • Fallback flag restores the paste-a-token prompt; --admin semantics unchanged
  • --json still emits one document on stdout, with all prompting on stderr
  • A server without the exchange route produces a message naming the upgrade, not a raw 404
  • Verified manually over SSH with no browser on the CLI's machine; noted in the PR
  • Tests fake GitHub's device-code, pending, slow-down and success responses; swift test needs no network

Blocked by

The server's exchange endpoint slice in ProJedi1234/stele-pages — it must be deployed to test against.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions