Skip to content

fix: macOS Keychain token caching bugs from #1#3

Closed
salmonumbrella wants to merge 5 commits intosteipete:mainfrom
salmonumbrella:fix/macos-keychain-token-caching
Closed

fix: macOS Keychain token caching bugs from #1#3
salmonumbrella wants to merge 5 commits intosteipete:mainfrom
salmonumbrella:fix/macos-keychain-token-caching

Conversation

@salmonumbrella
Copy link

@salmonumbrella salmonumbrella commented Dec 14, 2025

Summary

Fixes the token caching bugs mentioned in #1:

"still needs some iteration locally. seems to not be saving the token correctly"

Multiple issues were identified and fixed:

1. KeychainTrustApplication not enabled

Without KeychainTrustApplication: true, the keyring library saves tokens with an empty TrustedApplications list, which means no application can read them back - including eightctl itself.

2. Authenticate() bypasses token cache

Commands like whoami call Authenticate() directly, which bypassed the cache entirely and always hit the server. This caused unnecessary API calls and 429 rate limiting.

3. Keyring backend issues on macOS

The keyring library's auto-detection was unreliable. Switched to explicitly use FileBackend with a singleton pattern to ensure consistent token storage in ~/.config/eightctl/keyring/.

4. OAuth token endpoint broken (NEW)

The authTokenEndpoint was hardcoded to use wrong credentials:

  • client_id: "sleep-client" instead of the actual client ID
  • client_secret: "" instead of the actual secret

This caused 400 errors and fallback to legacy login, which then failed on temperature API calls with 401.

5. Gzip response handling (NEW)

Explicit Accept-Encoding: gzip headers were causing issues because Go's http.Transport handles this automatically. Removed the headers to let Go handle compression transparently.

Changes

  • Enable KeychainTrustApplication: true in keyring config
  • Make Authenticate() check in-memory and cached tokens before hitting the server
  • Use FileBackend only with singleton pattern for reliable token storage
  • Optimize requireAuthFields to avoid creating temporary Client objects
  • Fix OAuth endpoint to use c.ClientID and c.ClientSecret
  • Remove explicit Accept-Encoding: gzip headers

Notes

  • Users with timezone: "local" in their config should change it to an IANA timezone (e.g., America/Los_Angeles) as time.Local.String() returns "Local" which the API rejects

Test plan

  • Build passes
  • All tests pass
  • eightctl whoami uses cached token on subsequent runs
  • eightctl status works (previously failed with 401)
  • eightctl sleep day works with proper timezone config
  • eightctl device info returns full device details

🤖 Generated with Claude Code

salmonumbrella and others added 5 commits December 14, 2025 15:50
…tence

Without KeychainTrustApplication: true, tokens are saved with an empty
TrustedApplications list ([]string{}), which prevents ANY application
from reading them back - including eightctl itself.

This caused the symptom where "keyring saved token" would succeed but
subsequent "keyring get" calls would fail with "item not found".

🤖 Generated with [Claude Code](https://claude.ai/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Authenticate() previously always hit the server, bypassing the token
cache entirely. This caused unnecessary API calls and rate limiting,
especially for commands like `whoami` that explicitly call Authenticate().

Now Authenticate() checks:
1. In-memory token (if still valid)
2. Cached token from keychain
3. Only then authenticates with the server

This mirrors the behavior of ensureToken() but is needed because some
commands call Authenticate() directly.

🤖 Generated with [Claude Code](https://claude.ai/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The macOS Keychain backend has issues with adhoc-signed Go binaries:
Set() succeeds but Get() cannot retrieve items due to code signature
and ACL problems. This causes tokens to appear "saved" but never load.

Switch to FileBackend exclusively, which stores encrypted tokens in
~/.config/eightctl/keyring/ using JOSE encryption.

Also add singleton pattern for the keyring connection to reduce
file system operations and avoid potential concurrent access issues.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changes:
- requireAuthFields: Check credentials first before cache lookup,
  avoiding unnecessary Client creation when email/password are set
- requireAuthFields: Construct Identity directly with same defaults
  as Client.New() to ensure cache key consistency
- do(): Add debug logging when API returns 401 to help diagnose
  authentication issues

The 401 logging revealed that the temperature API rejects tokens
from legacy login, which is a separate issue to investigate.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The OAuth token endpoint was failing with 400 error because:
1. client_id was hardcoded as "sleep-client" instead of using c.ClientID
2. client_secret was empty instead of using c.ClientSecret

Also removed explicit Accept-Encoding: gzip headers - Go's http.Transport
handles gzip decompression automatically when headers are not manually set.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@salmonumbrella salmonumbrella closed this by deleting the head repository Dec 17, 2025
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