feat(github-oauth): add github oauth lease backend#464
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new github-oauth lease backend, enabling GitHub App user access tokens via the OAuth device flow. The implementation includes the core backend logic, configuration support, documentation, and comprehensive tests. My review identified three actionable improvements: replacing the unstable let_chains syntax with nested if let statements for stable Rust compatibility, improving the keyring cache key to prevent collisions when using multiple GitHub instances, and optimizing the polling loop to perform an immediate initial request rather than sleeping first.
| if let Some(cached) = self.read_cached_token() | ||
| && let Some(refreshed) = self.refresh_access_token(&cached).await? | ||
| { | ||
| self.write_cached_token(&refreshed); | ||
| return Ok(refreshed); | ||
| } |
There was a problem hiding this comment.
This code uses the let_chains feature (if let ... && let ...), which is currently unstable in Rust. Unless the project explicitly requires a nightly toolchain, it is recommended to use nested if let statements to maintain compatibility with the stable Rust compiler.
| if let Some(cached) = self.read_cached_token() | |
| && let Some(refreshed) = self.refresh_access_token(&cached).await? | |
| { | |
| self.write_cached_token(&refreshed); | |
| return Ok(refreshed); | |
| } | |
| if let Some(cached) = self.read_cached_token() { | |
| if let Some(refreshed) = self.refresh_access_token(&cached).await? { | |
| self.write_cached_token(&refreshed); | |
| return Ok(refreshed); | |
| } | |
| } |
| } | ||
|
|
||
| fn cache_key(&self) -> String { | ||
| let hash = blake3::hash(format!("{}|{}", self.client_id, self.scope).as_bytes()); |
There was a problem hiding this comment.
The cache key for the OS keyring currently only includes the client_id and scope. If a user configures multiple GitHub instances (e.g., public GitHub and a GitHub Enterprise Server) using the same client ID and scope, the tokens will collide in the keyring. Including the auth_base and api_base in the hash ensures that tokens are cached uniquely per endpoint.
| let hash = blake3::hash(format!("{}|{}", self.client_id, self.scope).as_bytes()); | |
| let hash = blake3::hash(format!("{}|{}|{}|{}", self.client_id, self.scope, self.auth_base, self.api_base).as_bytes()); |
| loop { | ||
| if chrono::Utc::now() >= deadline { | ||
| return Err(auth_failed("Device authorization expired".to_string())); | ||
| } | ||
|
|
||
| tokio::time::sleep(Duration::from_secs(interval)).await; |
There was a problem hiding this comment.
The polling loop currently sleeps before making the first request. This introduces an unnecessary delay (e.g., 5 seconds) even if the user has already authorized the device in their browser. It is better to perform the first poll immediately and then sleep only if the authorization is still pending.
3ad12c0 to
2d88dbf
Compare
2d88dbf to
f18158a
Compare
Greptile SummaryAdds a
Confidence Score: 5/5Safe to merge. The new backend is self-contained, all match arms are exhaustively updated, and the three concerns raised in previous review rounds have been properly addressed. The device-flow implementation correctly follows RFC 8628 (slow_down back-off, deadline enforcement, error discrimination). Token/refresh caching is gated behind keyring_cache, browser opens are non-blocking via spawn_blocking, and refresh failures fall back gracefully with a debug log. Config wiring in mod.rs is complete and consistent. The only note is the implicit URL derivation in device_auth_base, which works correctly for standard GitHub and GHE paths but could silently misbehave for unusual custom auth_base values. No files require special attention for a standard merge. Users setting a non-standard auth_base (without a trailing /oauth segment) for GitHub Enterprise should be aware of how the device-code endpoint URL is derived. Reviews (3): Last reviewed commit: "fix(github-oauth): address pr feedback" | Re-trigger Greptile |
### 🚀 Features - **(github-oauth)** add github oauth lease backend by [@jdx](https://github.com/jdx) in [#464](#464) ### 🐛 Bug Fixes - **(ci)** de-duplicate sponsor section in release notes by [@jdx](https://github.com/jdx) in [#459](#459) ### 🔍 Other Changes - **(ci)** use !cancelled() instead of always() for final job by [@jdx](https://github.com/jdx) in [#461](#461) - set dev profile debug to 1 by [@jdx](https://github.com/jdx) in [#462](#462)
## Upstream release Bumps bundled fnox binary from 1.23.1 to 1.24.0. **Release**: https://github.com/jdx/fnox/releases/tag/v1.24.0 ## Release notes A focused release that adds a new `github-oauth` lease backend for minting short-lived, user-attributed GitHub tokens via OAuth device flow — without distributing an app private key. ## Added **`github-oauth` lease backend** ([#464](jdx/fnox#464)) -- @jdx A new lease type that creates GitHub App *user access tokens* using the OAuth device flow and injects them as `GITHUB_TOKEN` (or a custom env var) for the duration of `fnox exec`. It is the recommended option for local development and user-attributed `gh` / GitHub API usage where you want a short-lived token tied to the signed-in user instead of a long-lived PAT in `fnox.toml`. ```toml [leases.github] type = "github-oauth" client_id = "Iv1.yourgithubappclientid" scope = "repo read:org workflow" duration = "8h" ``` ```sh fnox exec -- gh pr list ``` On first run, fnox prints a verification URL and user code, optionally opens the URL in your browser, and polls GitHub until you approve the device prompt. Subsequent runs reuse the cached token until it expires. Highlights of the backend: - **Only the GitHub App client ID is required** — no app private key and no client secret, so the lease config can be checked in and shared across a team. (The existing `github-app` backend remains the right choice for installation tokens in CI.) - **OS keyring caching** of access and refresh tokens, keyed by client id + scope + endpoints. Disable with `keyring_cache = false` to force the device flow on every lease. - **Refresh token reuse** when GitHub issues one — refreshes happen transparently; if the refresh fails, fnox falls back to a fresh device flow. - **Configurable env var** via `env_var` (e.g. `"GH_TOKEN"`) and configurable `auth_base` / `api_base` for GitHub Enterprise Server. - **`open_browser`** controls whether fnox tries to launch the verification URL automatically (uses `open` / `xdg-open` / `start`). The supported-backends table in the leases guide is updated, and the `github-app` docs now point local/user-attributed workflows at `github-oauth`. See the [GitHub OAuth lease docs](https://fnox.jdx.dev/leases/github-oauth) for the full reference. **Full Changelog**: jdx/fnox@v1.23.1...v1.24.0 ## 💚 Sponsor fnox fnox is maintained by [@jdx](https://github.com/jdx) under [**en.dev**](https://en.dev) — a small independent studio building developer tooling like [mise](https://mise.jdx.dev/), [aube](https://aube.en.dev/), hk, and more. Keeping fnox secure, maintained, and free is funded by sponsors. If fnox is handling secrets or config for you or your team, please consider [sponsoring at en.dev](https://en.dev). Sponsorships are what let fnox stay independent and the project keep moving. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
Adds a
github-oauthlease backend that mirrors the useful parts ofghtkninside Fnox: GitHub App user access tokens via OAuth device flow, optional browser opening, OS keyring caching, refresh-token reuse, and injection asGITHUB_TOKENor a configured env var.A key benefit is that
github-oauthonly needs the GitHub App client ID. It does not require an app private key or app client secret, so the lease config can be safely shared across a team. The existinggithub-appbackend remains the installation-token path and still uses an app private key, not the app client secret.Also adds docs for the backend, adds a
github-apptip suggestinggithub-oauthfor local/user-attributed workflows, registers it in the lease backend table, regenerates the public config schema, and covers the device-flow path with a mock GitHub OAuth bats test.Validation
cargo fmt --checkcargo check -p fnox-corecargo build./test/bats/bin/bats test/lease_github_oauth.batsNote:
mise run buildand thehkpre-commit hook both blocked while installingvault@2.0.0; the commit was made withHK=0after the checks above passed.This PR description was generated by Codex.
Note
Medium Risk
Adds a new GitHub OAuth device-flow backend that vends and caches user access tokens (including optional refresh-token reuse and OS keyring storage), which affects credential acquisition paths and introduces new external API interactions.
Overview
Adds a new
github-oauthlease backend that obtains GitHub App user access tokens via OAuth device flow, optionally opens the verification URL in a browser, and caches/refreshes tokens via the OS keyring before injecting the token into a configurable env var (defaultGITHUB_TOKEN).Wires the backend into
LeaseBackendConfig(defaults for scope/auth+api base URLs, config schema updates), adds user-facing docs (including a tip ingithub-appdocs and updating the supported backends table), and introduces a bats test that exercises the device/refresh flows against a mock HTTP server.Reviewed by Cursor Bugbot for commit 29f9d75. Bugbot is set up for automated code reviews on this repo. Configure here.