Skip to content

feat(auth): add createKeyringTokenStore multi-account TokenStore (3/4)#27

Merged
scottlovegrove merged 1 commit into
mainfrom
feat/keyring-token-store-multi-account
May 16, 2026
Merged

feat(auth): add createKeyringTokenStore multi-account TokenStore (3/4)#27
scottlovegrove merged 1 commit into
mainfrom
feat/keyring-token-store-multi-account

Conversation

@scottlovegrove
Copy link
Copy Markdown
Collaborator

Stack position: 3 / 4 — base is #26 (feat/auth-store-read-failed). Replaces the bulk of #24.

Summary

Multi-account TokenStore that keeps secrets in the OS credential manager (via createSecureStore from #25) and per-user metadata in a consumer UserRecordStore port. Satisfies the full multi-user TokenStore contract (active(ref) / clear(ref) / list() / setDefault(ref)) so it plugs straight into the existing logout / status / token attachers.

Behaviour

Write order — keyring first; on SecureStoreUnavailableError, the token lands on fallbackToken instead. userRecords.upsert failure after a successful keyring write triggers rollback deleteSecret to avoid orphan credentials. Default promotion is best-effort.

Read orderfallbackToken first, then keyring. A matching record with an unreadable keyring throws AUTH_STORE_READ_FAILED (the typed code added in #26) so --user <ref> doesn't silently collapse to ACCOUNT_NOT_FOUND.

Clear order — record removal first (source of truth), then always-attempt keyring delete (orphan cleanup even when the record carried fallbackToken).

The shared keyring-write choreography lives in writeRecordWithKeyringFallback so PR 4 (migrateLegacyAuth) can reuse it.

Files

  • src/auth/keyring/types.tsUserRecord, UserRecordStore port, TokenStorageResult.
  • src/auth/keyring/record-write.ts — shared write/upsert/rollback helper.
  • src/auth/keyring/token-store.ts (+ test) — the factory.
  • src/test-support/keyring-mocks.ts — shared test mocks (excluded from build).

Test plan

  • npm run type-check, npm run check, npm test (328 passing, +21 new for the keyring TokenStore)
  • Once merged into the stack, smoke against todoist-cli on Linux desktop and a WSL session with DBUS_SESSION_BUS_ADDRESS unset.

🤖 Generated with Claude Code

Copy link
Copy Markdown
Member

@doistbot doistbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces the createKeyringTokenStore, thoughtfully integrating multi-account support with OS-level credential managers and local fallback storage. The write choreography and alignment with existing attachers establish a strong foundation for robust authentication handling. There are a few areas to refine, specifically around surfacing typed errors for missing defaults or corrupted keyring reads, ensuring atomic error handling when clearing records, removing duplicated target resolution logic, and adding dedicated tests for the shared write helpers to fully satisfy colocation guidelines.

Share FeedbackReview Logs

Comment thread src/auth/keyring/token-store.ts Outdated
Comment thread src/auth/keyring/token-store.ts Outdated
Comment thread src/auth/keyring/token-store.ts Outdated
Comment thread src/auth/keyring/token-store.ts Outdated
Comment thread src/auth/keyring/token-store.ts Outdated
Comment thread src/auth/keyring/token-store.ts Outdated
Comment thread src/auth/keyring/token-store.ts Outdated
Comment thread src/auth/keyring/record-write.ts
Comment thread src/auth/keyring/token-store.test.ts Outdated
Comment thread src/auth/keyring/record-write.ts Outdated
@scottlovegrove scottlovegrove force-pushed the feat/auth-store-read-failed branch from 644b8c5 to 2cd6aaf Compare May 16, 2026 12:35
@scottlovegrove scottlovegrove force-pushed the feat/keyring-token-store-multi-account branch from 492efcf to d196148 Compare May 16, 2026 12:39
@scottlovegrove scottlovegrove force-pushed the feat/auth-store-read-failed branch from 2cd6aaf to 5092110 Compare May 16, 2026 13:50
@scottlovegrove scottlovegrove force-pushed the feat/keyring-token-store-multi-account branch from d196148 to 4c1b71a Compare May 16, 2026 13:51
@scottlovegrove scottlovegrove force-pushed the feat/auth-store-read-failed branch 3 times, most recently from e5b268a to 93bb598 Compare May 16, 2026 14:10
@scottlovegrove scottlovegrove force-pushed the feat/keyring-token-store-multi-account branch from 4c1b71a to e808bd1 Compare May 16, 2026 14:11
@scottlovegrove scottlovegrove force-pushed the feat/auth-store-read-failed branch 3 times, most recently from fe9ab44 to 3e86be6 Compare May 16, 2026 14:23
Base automatically changed from feat/auth-store-read-failed to main May 16, 2026 14:28
@scottlovegrove scottlovegrove force-pushed the feat/keyring-token-store-multi-account branch 3 times, most recently from f700324 to 71ab36d Compare May 16, 2026 14:56
Comment thread src/auth/user-flag.ts Outdated
@scottlovegrove scottlovegrove force-pushed the feat/keyring-token-store-multi-account branch from 71ab36d to 943be1a Compare May 16, 2026 15:00
@scottlovegrove
Copy link
Copy Markdown
Collaborator Author

@doistbot /review

Copy link
Copy Markdown
Member

@doistbot doistbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces a robust multi-account TokenStore that smartly combines OS keyring storage with a fallback system and proper rollback handling. The careful orchestration between the secure store and user records creates a solid foundation for credential management while nicely setting up the upcoming migration work. There are a few areas to refine, primarily around preventing ambiguous account label resolution, decoupling CLI-specific UI copy from the persistence layer, and addressing minor state caching, test coverage, and mock configuration details.

Share FeedbackReview Logs

Comment thread src/auth/keyring/token-store.ts Outdated
Comment thread src/auth/keyring/record-write.ts
Comment thread src/auth/keyring/types.ts Outdated
Comment thread src/auth/keyring/token-store.ts Outdated
Comment thread src/auth/keyring/token-store.ts
Comment thread src/auth/keyring/token-store.ts
Comment thread src/auth/keyring/token-store.test.ts Outdated
Comment thread src/test-support/keyring-mocks.ts Outdated
@scottlovegrove scottlovegrove force-pushed the feat/keyring-token-store-multi-account branch from 943be1a to 76f4a3e Compare May 16, 2026 15:17
Multi-account `TokenStore` that keeps secrets in the OS credential
manager (via `createSecureStore`) and per-user metadata in a consumer
`UserRecordStore` port. Satisfies the full multi-user `TokenStore`
contract — `active(ref)` / `clear(ref)` / `list()` / `setDefault(ref)` —
so it plugs straight into the existing `logout` / `status` / `token`
attachers. Default ref matching is `id || label`; override via
`matchAccount` for case-insensitive email, alias maps, etc.

Behaviour:
- Write: keyring first; on `SecureStoreUnavailableError`, the token
  lands on `fallbackToken` instead. `userRecords.upsert` failure after
  a successful keyring write triggers a rollback `deleteSecret` to
  avoid orphan credentials.
- Default promotion is best-effort — a `setDefaultId` failure can't
  turn a durable credential write into `AUTH_STORE_WRITE_FAILED`.
- Read: `fallbackToken` first, then keyring. A matching record with
  an unreadable keyring throws `AUTH_STORE_READ_FAILED` so
  `--user <ref>` doesn't silently collapse to `ACCOUNT_NOT_FOUND`.
- Clear: record removal first (source of truth), then always-attempt
  keyring delete (even when the record carries `fallbackToken`, in
  case a prior online write parked an orphan there).
- The keyring write/upsert/rollback choreography lives in a shared
  `writeRecordWithKeyringFallback` helper so the migration helper can
  reuse the same routing and rollback semantics.

Reintroduces `DEFAULT_ACCOUNT_FOR_USER` in `secure-store.ts` (a new
caller in this diff needs the shared default) and promotes
`accountNotFoundError` in `auth/user-flag.ts` to a module-level export
so the keyring `setDefault` can reuse the same error wording instead
of redeclaring it.

Test helpers live under `src/test-support/` (excluded from the build
by `tsconfig.build.json`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@scottlovegrove scottlovegrove force-pushed the feat/keyring-token-store-multi-account branch from 76f4a3e to 5fb4526 Compare May 16, 2026 15:27
@scottlovegrove scottlovegrove merged commit adc07d1 into main May 16, 2026
4 checks passed
@scottlovegrove scottlovegrove deleted the feat/keyring-token-store-multi-account branch May 16, 2026 15:30
doist-release-bot Bot added a commit that referenced this pull request May 16, 2026
## [0.15.0](v0.14.0...v0.15.0) (2026-05-16)

### Features

* **auth:** add createKeyringTokenStore (multi-account) ([#27](#27)) ([adc07d1](adc07d1))
@doist-release-bot
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 0.15.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants