Skip to content

fix: cache OAuth across household userIDs; add --side to away#37

Merged
omarshahine merged 2 commits into
steipete:mainfrom
omarshahine:fix/away-cache-and-side-targeting
Apr 19, 2026
Merged

fix: cache OAuth across household userIDs; add --side to away#37
omarshahine merged 2 commits into
steipete:mainfrom
omarshahine:fix/away-cache-and-side-targeting

Conversation

@omarshahine

Copy link
Copy Markdown
Collaborator

Summary

  • Token cache: tokencache.Load was rejecting a valid cached token whenever the currently-targeted userID differed from the one stored at auth time. Households share one OAuth principal across multiple userIDs, so running e.g. eightctl away on for one side then the other in separate invocations forced a fresh password-grant auth each time and tripped Eight Sleep's 429 rate limit. Tokens are now namespaced strictly by Identity (base URL + client ID + email); the cached UserID stays as informational metadata used to populate c.UserID when the caller didn't supply one.
  • Away symmetry: away on|off now accepts --side left|right|solo and --target-user-id, matching on/off/temp/status. This removes the asymmetry where only the config-default side could be toggled without an explicit --user-id. --both still applies to every household member and now errors cleanly if combined with --side.

Test plan

  • go test ./... — all green (tokencache + client + cmd)
  • go build ./... / go vet ./...
  • eightctl away on --help shows new --side / --target-user-id flags
  • Run eightctl away on --side left and eightctl away on --side right back-to-back in separate invocations; verify no 429 and a single network auth in verbose mode
  • Run eightctl away on --both and confirm both sides toggle without re-auth
  • Run eightctl away on with no flags and confirm it still targets the authenticated user's side (unchanged default)

🤖 Generated with Claude Code

omarshahine and others added 2 commits April 18, 2026 22:18
… away

The token cache was rejecting a valid cached token whenever the currently
targeted userID differed from the one stored at auth time. Households share
a single OAuth principal (one email) across multiple userIDs, so back-to-back
invocations against different sides each forced a fresh password-grant auth
and tripped Eight Sleep's 429 rate limit.

Tokens are now namespaced strictly by Identity (base URL + client ID + email),
which is the authoritative boundary. The cached UserID stays as informational
metadata for populating c.UserID when the caller didn't supply one.

Also wires --side/--target-user-id into `away on|off` so both household
members can target their side symmetrically, matching on/off/temp/status.
--both still applies to every household member and now errors cleanly if
combined with --side.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ber test

- Extract targetScope() in targeting.go so callers can compose a scope label
  without TrimPrefix-ing the leading " for " out of targetSuffix().
- Revert the map[bool]string cleverness in away.go to the plain if/else form
  used elsewhere in the codebase.
- Add unit test locking in the requireAuthFields no-clobber guard: an
  explicit user_id (via flag or config) must survive a successful cache load.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@omarshahine omarshahine merged commit b3e15a2 into steipete:main Apr 19, 2026
2 checks passed
0xble added a commit to 0xble/eightctl that referenced this pull request Apr 20, 2026
Merge upstream/main (steipete#35 side-aware targeting, steipete#36 away-mode fixes and
keychain fallback, steipete#37 cache OAuth across household userIDs).

Conflict resolution highlights:
- tokencache: keep fork's JSON-file fallback and FileBackend-only keyring;
  drop expectedUserID filter in Load to match upstream household sharing
  semantic from steipete#37
- eightsleep.go: add doApp wrapper routing to AppURL via doWithBase, keep
  fork's API drift resilience (fallbackBaseURL, legacyLoginBaseURL,
  explicit gzip handling), pull in resolveTZ for presence.go
- cmd: use resolveAPITimezone (upstream's IANA fallback) instead of
  fork's time.Local.String() hack
- root_test: combine SetOpenKeyringForTest with SetFallbackPathForTest
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.

1 participant