Skip to main content

Module device_flow

Module device_flow 

Source
Expand description

The OAuth 2.0 Device Authorization Grant against the published GitHub App.

This is the only authentication path the product has (D3, D16). It needs a public client_id and nothing else: no client secret, no redirect listener, no loopback port, and no server anywhere in the design. GitHub documents that a public client cannot secure a client secret, and this design never tries to (07-security.md, “Authentication model”).

§The shape of a login

  1. DeviceFlow::start posts the client_id and gets back a user code, a device code, an expiry, and a polling interval.
  2. The caller displays the user code and the canonical crate::DEVICE_VERIFICATION_PATH URL. It displays nothing else — see “Phishing” below.
  3. DeviceFlow::complete polls until the user approves, honouring the interval and every documented error in the matrix.
  4. The token is returned. Nothing here writes it anywhere; d2 owns the machine-scoped store and f1 owns the wiring.

§The error matrix is four outcomes, not one failure

authorization_pending, slow_down, expired_token and access_denied are four different things that each need a different response, and collapsing them into one generic error is how a CLI ends up telling a user who declined the authorization to try again:

GitHub errorHereCaller does
authorization_pendingPollOutcome::Pendingkeep polling, same interval
slow_downPollOutcome::SlowDownkeep polling, longer interval
expired_tokenDeviceFlowError::Expiredstart a whole new login
access_deniedDeviceFlowError::AccessDeniedstop; the user said no

Only the first two are recoverable, and DeviceFlowError::is_retryable says so for the rest.

§Phishing

07-security.md’s threat table names “a phishing page imitates the device-flow prompt to harvest a code”, with the control “the tool prints the canonical github.com/login/device URL and never proxies or embeds the approval page”. Two things implement it. The tool prints crate::Endpoints::verification_url, a compiled-in constant, rather than whatever a response contained; and DeviceFlow::start rejects a verification_uri whose origin is not the configured GitHub web host, so a response that tries to redirect a user elsewhere is an error rather than something the CLI renders.

§Renewal

There is none, and none may be added. The published App opts out of user-token expiration, so GitHub issues no renewal token with the access token; renewing a user token requires the client secret, which a public client cannot hold. lib.rs’s tests::no_renewal_path_and_no_confidential_credential_in_this_crate scans this file and lib.rs for the identifiers such a path would need — after lower-casing and removing _, so that every casing a Rust identifier can take is the same needle. That normalisation is why the prose here writes “renewal token” and “client secret” as separate words.

- is deliberately not removed from a .rs file, because a Rust identifier cannot contain one and removing it made ordinary hyphenated English trip the gate. The manifest is normalised the other way, where - is a kebab-case word separator rather than a hyphen. lib.rs’s tests::normalise_source carries the reasoning and the residual gap.

Structs§

DeviceAuthorization
What DeviceFlow::start returns: everything the login needs, with the one secret in it wrapped.
DeviceFlow
The device-flow client.

Enums§

DeviceFlowError
Every way a login can fail.
PollOutcome
The result of one poll.

Constants§

DEFAULT_POLL_INTERVAL
The interval used when a response omits one. RFC 8628’s default.
DEVICE_GRANT_TYPE
The grant type the device flow’s token request carries, verbatim from RFC 8628.
MAX_TRANSPORT_RETRIES
How many consecutive retryable failures one DeviceFlow::complete loop absorbs before giving up.
SLOW_DOWN_INCREMENT
The minimum a slow_down lengthens the poll interval by.