Skip to content

feat(macos): opt-in Keychain for sign-ins - #6

Merged
parda21 merged 2 commits into
feat/track-notificationsfrom
feat/macos-keychain
Oct 15, 2026
Merged

parda21 merged 2 commits into
feat/track-notificationsfrom
feat/macos-keychain

Conversation

@yk-sgr

@yk-sgr yk-sgr commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #5. Last of the stack.

What changes

credentials = "keychain" in config.toml keeps both sign-ins in the macOS Keychain instead of files: the Web API token (webapi-token.json) and librespot's reusable streaming credentials (session/credentials.json).

  • New secret store in cloofy-os. It uses the login keychain through security-framework (already in the tree via rustls), as generic passwords under the service cloofy. On other systems it isn't available, and the setting fails with a clear message instead of being ignored silently.
  • Secret in cloofy-spotify. A sign-in lives either in a private file or in the keychain. Paths carries the mode, so every existing call site (paths.webapi_token(), session::prepare(paths)) stays the same.
  • Moving from files. A file in the old place always wins and moves into the keychain when read. That covers switching from files, and cloofy login, where librespot writes the real reusable credentials (with their auth_type) itself: they move over right after. The rest of the time, librespot's cache gets no credentials file.
  • Refreshed credentials. After every connection, cloofy updates the stored credentials when Spotify handed out new ones (session::remember), as librespot does for files. PendingSession::connect (used by login, doctor, query) does it itself; the Spirc connections in the TUI and in play/connect call it explicitly.

Why it's off by default

macOS ties keychain access to the binary's code signature. For an unsigned, ad-hoc signed build, every update is a new binary, so macOS asks again whether cloofy may use its items. Once releases are signed, this can become the default.

Verified

  • A round trip in the real login keychain (#[ignore]d, since CI can't answer keychain prompts): save, overwrite, read, delete, plus moving a file into the keychain.
  • End-to-end with the real binary, using fake credentials in a temporary XDG environment with credentials = "keychain":
    • Run 1: both files moved into the keychain.
    • Run 2: with no files left, cloofy doctor read the token from the keychain (401 from Spotify for the fake token, not "not logged in").
    • Both items were deleted afterwards.
  • Unit tests: private-file storage (mode 0600), and the credentials values.

To test by hand

  • Set credentials = "keychain" and start cloofy: the files in ~/.local/share/cloofy disappear, and Keychain Access shows two cloofy items
  • Restart: cloofy is still signed in
  • cloofy login --force: still signed in, and no credentials.json remains
  • Remove the setting: cloofy login is needed again (sign-ins aren't moved back to files)

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b683b844-49a3-4037-bad2-3aa93b92c439

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yk-sgr
yk-sgr force-pushed the feat/track-notifications branch from 0d7293f to ffa05c0 Compare October 15, 2026 14:02
@yk-sgr
yk-sgr force-pushed the feat/macos-keychain branch from 8738f36 to d3daf27 Compare October 15, 2026 14:02
yk-sgr and others added 2 commits October 15, 2026 16:45
`credentials = "keychain"` in config.toml keeps the Web API token and
the streaming credentials in the macOS Keychain instead of files.

- cloofy-os gets a secret store, the login keychain on macOS through
  security-framework, under the service "cloofy". Elsewhere it isn't
  available, and the setting says so instead of being ignored.
- A file in the old place always wins and moves into the keychain when
  read. That covers the switch from files and `cloofy login`, where
  librespot writes the reusable credentials itself: they move over
  right after. Otherwise librespot doesn't get to keep them in a file.
- After connecting, cloofy updates the stored credentials when Spotify
  handed out new ones, as librespot does for files.

It's off by default: macOS ties keychain access to the binary, so an
unsigned build has to ask again after each update. With signed releases
it can become the default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`doctor`, `query` and `dj-sample` connect through
`PendingSession::connect`, which didn't save credentials Spotify renewed
while connecting. In keychain mode the cache has no file for librespot
to update, so those were lost. `connect` now saves them itself, and
`login` relies on that too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yk-sgr
yk-sgr force-pushed the feat/track-notifications branch from ffa05c0 to 97380b7 Compare October 15, 2026 14:47
@yk-sgr
yk-sgr force-pushed the feat/macos-keychain branch from d3daf27 to 59a2cbe Compare October 15, 2026 14:47
parda21 added a commit that referenced this pull request Oct 15, 2026
Merged with the speed and polish work: the Web API client keeps its
timeouts and rate-limit retries and now reads its token from the
chosen store, and the player saves renewed credentials before it
subscribes to Spotify Connect pushes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@parda21
parda21 merged commit 59a2cbe into feat/track-notifications Oct 15, 2026
3 checks passed
@parda21

parda21 commented Oct 15, 2026

Copy link
Copy Markdown
Owner

Merged through #7. The only conflicts were with the audit work: the Web API client keeps its new timeouts and rate-limit retries and reads its token from your Secret, and the supervisor saves renewed credentials before subscribing to Connect pushes.

🤖 Generated with Claude Code

@parda21
parda21 deleted the feat/macos-keychain branch October 15, 2026 15:00
parda21 added a commit that referenced this pull request Oct 15, 2026
Merged with the speed and polish work: the Web API client keeps its
timeouts and rate-limit retries and now reads its token from the
chosen store, and the player saves renewed credentials before it
subscribes to Spotify Connect pushes.
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