Skip to content

Fix OAuth client credentials and gzip decoding#27

Closed
petersentaylor wants to merge 1 commit intosteipete:mainfrom
petersentaylor:fix/oauth-auth-and-gzip
Closed

Fix OAuth client credentials and gzip decoding#27
petersentaylor wants to merge 1 commit intosteipete:mainfrom
petersentaylor:fix/oauth-auth-and-gzip

Conversation

@petersentaylor
Copy link
Copy Markdown

Summary

  • use the configured/default Eight Sleep OAuth client credentials for password-grant auth
  • stop forcing Accept-Encoding: gzip so Go can transparently decode API responses

Why

The current auth flow sends a hardcoded sleep-client payload with an empty client secret, which causes the OAuth token endpoint to fail and forces the CLI onto the legacy login fallback. In practice that fallback can get rate-limited with 429 Too Many Requests.

After fixing the OAuth request, live status requests succeeded, but response decoding still failed because the client explicitly requested gzip and then attempted to JSON-decode compressed bytes directly. Removing the manual Accept-Encoding header lets Go handle decompression correctly.

Verification

  • go test ./...
  • verified live eightctl status on macOS after rebuilding the binary

omarshahine added a commit to omarshahine/eightctl that referenced this pull request Apr 15, 2026
…dless envs

- Replace infinite 429/401 retry loops with bounded retries (max 3) and
  exponential backoff to prevent permanent rate-limit storms
- Remove explicit Accept-Encoding: gzip header; let Go's http.Transport
  handle compression transparently (simpler, no manual gzip.NewReader)
- Detect headless environments (SSH, no TTY, EIGHTCTL_KEYRING_FILE=1) and
  fall back to file-based keyring to avoid macOS Keychain hang

Credit to @davidfencik (steipete#16) for the retry and keyring patterns, and
@petersentaylor (steipete#27) for the simpler gzip approach.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@omarshahine
Copy link
Copy Markdown
Collaborator

Clean and elegant approach, @petersentaylor — just removing the explicit Accept-Encoding: gzip header and letting Go's http.Transport handle it transparently is the simplest fix. I've adopted this approach in #24 (with credit in the commit message and PR description) instead of the manual gzip.NewReader path.

Closing this in favor of #24, which combines your gzip approach with OAuth form-encoding, bounded retries, and headless keyring support.

Thanks for the contribution — we'll get this released soon! 🙏

omarshahine added a commit that referenced this pull request Apr 16, 2026
* fix(client): use form-urlencoded for OAuth token endpoint

The Eight Sleep auth server (auth-api.8slp.net/v1/tokens) expects
standard OAuth2 form-urlencoded requests, not JSON. The previous
implementation sent JSON with hardcoded "sleep-client" credentials,
which caused a 400 from Joi validation. The fallback to legacy
/login then tripped the rate limiter, resulting in a permanent 429
loop.

Changes:
- Send application/x-www-form-urlencoded instead of application/json
- Use c.ClientID and c.ClientSecret (the real app creds extracted
  from the Android APK) instead of hardcoded "sleep-client"/""
- Make authURL a var so tests can point it at a local server
- Add tests for form encoding, credential passthrough, and
  legacy login fallback

Fixes #7, fixes #8, fixes #12

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(client): decompress gzip API responses

The do() method sends Accept-Encoding: gzip but never decompresses
the response body, causing json.Decode to fail with:

    invalid character '\x1f' looking for beginning of value

(0x1f is the gzip magic byte.)

Check Content-Encoding: gzip on responses and wrap the body in a
gzip.Reader before decoding. Added test with a mock gzip response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: cap retry loops, simplify gzip handling, fix keyring hang in headless envs

- Replace infinite 429/401 retry loops with bounded retries (max 3) and
  exponential backoff to prevent permanent rate-limit storms
- Remove explicit Accept-Encoding: gzip header; let Go's http.Transport
  handle compression transparently (simpler, no manual gzip.NewReader)
- Detect headless environments (SSH, no TTY, EIGHTCTL_KEYRING_FILE=1) and
  fall back to file-based keyring to avoid macOS Keychain hang

Credit to @davidfencik (#16) for the retry and keyring patterns, and
@petersentaylor (#27) for the simpler gzip approach.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(client): drop legacy /login fallback, OAuth-only auth

The legacy /login endpoint no longer works reliably upstream, and the
silent fallback was masking real OAuth errors. Remove it so OAuth
failures surface directly. Also revert the keyring-backend tweaks from
9f49cf0 — leave tokencache behavior identical to main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): remove contradictory golangci-lint config

`disable-all: true` and `disable: [errcheck, unused]` can't be combined
— golangci-lint errors with "can't combine options --disable-all and
--disable". Remove the redundant `disable` list and stale `revive`
settings since only `govet` is enabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Lobster <lobster@shahine.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants