Skip to content

fix(client): use correct OAuth credentials and handle gzip responses#15

Open
gee-m wants to merge 3 commits intosteipete:mainfrom
gee-m:fix/auth-and-gzip
Open

fix(client): use correct OAuth credentials and handle gzip responses#15
gee-m wants to merge 3 commits intosteipete:mainfrom
gee-m:fix/auth-and-gzip

Conversation

@gee-m
Copy link

@gee-m gee-m commented Feb 15, 2026

Problem

Two bugs in the API client that make eightctl permanently fail with 429 errors:

1. Wrong OAuth credentials in token endpoint

authTokenEndpoint() hardcodes client_id: "sleep-client" with an empty client_secret instead of using the actual app credentials (c.ClientID / c.ClientSecret — the ones extracted from the Eight Sleep Android app).

The auth server rejects this with a 400 (Joi validation fails on the empty secret), which falls through to authLegacyLogin(). By that point, the rate limiter has already been tripped → permanent 429 loop.

2. Gzip responses not decompressed

do() sends Accept-Encoding: gzip but never decompresses the response body, causing:

Error: invalid character '\\x1f' looking for beginning of value

(0x1f is the gzip magic byte.)

Fix

  • Use c.ClientID and c.ClientSecret in the token endpoint payload
  • Decompress gzip responses when Content-Encoding: gzip is present
  • Added tests for both fixes

Tested against a live Pod 5 — both auth and API calls work correctly after the fix.

Fixes #7, fixes #8, fixes #12

Two bugs in the API client:

1. authTokenEndpoint() hardcoded client_id as "sleep-client" with an
   empty client_secret instead of using the actual app credentials
   (c.ClientID / c.ClientSecret). This causes a 400 from the auth
   server, which then falls through to legacy login and triggers
   rate limiting (429) — making the CLI permanently unusable.

2. do() sends Accept-Encoding: gzip but never decompresses the
   response body, causing JSON decode errors (invalid character 0x1f)
   on endpoints that return gzipped responses.
- TestAuthTokenEndpointUsesConfiguredCredentials: verifies New() defaults
  to the app credentials (not 'sleep-client') and that custom creds pass through
- TestDoHandlesGzipResponse: verifies gzip Content-Encoding is decompressed
- TestDoHandlesPlainResponse: verifies plain responses still work
…in tests

The credential test only verified that New() assigns struct fields — it
couldn't actually test that authTokenEndpoint() sends the right values
in the HTTP request because authURL is a package-level const pointing at
the real Eight Sleep server. The captured variables were never asserted.

The plain response test duplicated coverage already provided by
TestRequireUserFilledAutomatically.

The gzip test is the only one that exercises a real code path we changed
(Content-Encoding: gzip decompression in do()), so keep that.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant

Comments