feat(auth): add createSecureStore keyring primitive (1/4)#25
Conversation
Thin cross-platform wrapper around `@napi-rs/keyring` exposed from the `./auth` subpath as a building block for any consumer that needs OS-keyring backed token storage. Every failure mode (missing native binary on an exotic arch, libsecret unreachable on headless Linux / WSL without D-Bus, Keychain locked, …) is normalised into `SecureStoreUnavailableError`. The `@napi-rs/keyring` import is dynamic so a missing binary doesn't crash module load before the typed error can surface. Declared in cli-core's own `optionalDependencies` so it ships transitively to consumers without them having to add it themselves. This is the first slice of a 4-PR series that splits the original keyring-backed multi-account TokenStore work into independently reviewable units: primitive → AUTH_STORE_READ_FAILED + logout tolerance → multi-account TokenStore → migration helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
doistbot
left a comment
There was a problem hiding this comment.
This PR successfully introduces the createSecureStore keyring primitive, laying a solid foundation for the multi-account auth stack. The dynamic import approach and normalization of platform-specific failures into a unified SecureStoreUnavailableError will significantly streamline downstream CLI integrations. There are a few opportunities to clean up dead exports, consolidate repetitive error handling and entry initialization, preserve underlying error causes, and refine the test suite to safely handle mocks and comprehensively cover empty and failure states.
0d7273c to
165fb07
Compare
|
@doistbot /review |
doistbot
left a comment
There was a problem hiding this comment.
This PR introduces the foundational createSecureStore keyring primitive to wrap @napi-rs/keyring and gracefully standardize authentication storage failures. The implementation cleanly abstracts the native binary dependencies and sets a solid groundwork for the upcoming multi-account token store. A few minor adjustments have been flagged regarding an unused export and test suite robustness, specifically around resetting mock implementations and verifying the rejected promise memoisation.
- Drop the unused `DEFAULT_ACCOUNT_FOR_USER` const — no caller in this diff reaches for it; a future change that needs a shared per-user slug helper can add it back where the consumer lands. - Drop the public `SECURE_STORE_DESCRIPTION` export. The constant is still defined for internal use within `secure-store.ts` (it composes the fallback warning string elsewhere in the module), but it is no longer reachable from `@doist/cli-core/auth` until a real public caller asks for it. - Extract a `withEntry` helper inside `createSecureStore` so the per-method try/catch/getEntry boilerplate collapses into one place, and memoise the dynamic-import + `AsyncEntry` construction in a per-store `entryPromise` so repeated reads/writes share one entry and a missing native binary fast-fails on subsequent calls. - `toUnavailableError` now uses the shared `getErrorMessage` helper from `src/errors.ts` and attaches the original throwable as `cause` on `SecureStoreUnavailableError`, so platform-specific stack/native error codes aren't lost on the way to the consumer. - Tests rework: a single hoisted `vi.mock` with a getter checks a `throwOnImport` toggle on every access — replaces `vi.doUnmock` cleanup that was leaving the real keyring exposed to any test added later. Adds back the empty-slot `getSecret` case and a parameterised failure test covering `getSecret` / `setSecret` / `deleteSecret` (each asserts the cause is preserved). Adds a memoisation test that locks the entry-construction caching. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
165fb07 to
5b0cfd7
Compare
## [0.13.0](v0.12.0...v0.13.0) (2026-05-16) ### Features * **auth:** add createSecureStore keyring primitive (1/4) ([#25](#25)) ([30bcbd7](30bcbd7))
|
🎉 This PR is included in version 0.13.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Stack position: 1 / 4 — replaces the monolithic #24.
Summary
@napi-rs/keyring(Keychain / Credential Manager / libsecret) exposed ascreateSecureStorefrom@doist/cli-core/auth.SecureStoreUnavailableError.@napi-rs/keyringdeclared in cli-core's ownoptionalDependenciesso it arrives transitively — consumer CLIs don't add it themselves.Why this is a stack
The original PR (#24) was ~1900 LoC across primitive, multi-account TokenStore, migration helper, and attacher behavioural change. Splitting into 4 reviewable pieces:
createSecureStore). Usable on its own; todoist-cli / twist-cli can drop their duplicatesecure-store.tsagainst just this.feat/auth-store-read-failed—AUTH_STORE_READ_FAILEDerror code +logout --usertolerance so consumerTokenStores can throw it without breaking logout.feat/keyring-token-store-multi-account—createKeyringTokenStoremulti-account factory +UserRecordStoreport + shared write helper.feat/keyring-migrate—migrateLegacyAuthv1→v2 helper.Test plan
npm run type-checknpm run checknpm test(304 passing — 3 new tests for the primitive)npm linkinto a downstream CLI, forceSecureStoreUnavailableErrorby unsettingDBUS_SESSION_BUS_ADDRESSin WSL.🤖 Generated with Claude Code