feat(vault): token persistence + refresh#12
Merged
Conversation
Keep and reuse the OAuth2 refresh token so a cache/PIN/offline session can become fully capable, and a long-lived session survives access-token expiry — lifting the read-only boundary PR B documented. vault-api: BitwardenClient now keeps the refresh_token from login_password, exposes it (refresh_token()), and can be seeded with one (with_refresh_token). New refresh() runs grant_type=refresh_token. The four server ops (sync/create/update/delete) route through one send_with_auth helper that, on a 401 with a refresh token held, refreshes once and retries; the bespoke per-op bearer/expect code is collapsed into send_with_auth + expect_success (and the now-false "# Panics: never" docs + expect()s are gone). Persistence: the refresh token is stored encrypted under the user key in the cache (refresh_token envelope field) on online unlock + every sync, and recovered into the in-memory session on a cache/PIN unlock. Vault::ensure_online: a token-less session (offline / PIN) lazily upgrades to online on its first sync/add/edit/remove by building a client from the stored refresh token and refreshing — PIN unlock stays fast/offline, but server ops work once the network is up. A genuinely offline box (or no stored refresh token) still returns Error::Offline. Tests: store refresh-token round-trip; ensure_online Offline-without / Ok-with-client; refresh-token recovery from cache on offline unlock. 144 workspace tests green. No new dependencies. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI clippy flagged two unbackticked `OAuth2` doc items (the codebase backticks it elsewhere). Verified with an isolated CARGO_TARGET_DIR clippy run so the result doesn't share cargo's build cache (the source of the repeated local false-greens). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Keep and reuse the OAuth2 refresh token so a cache/PIN/offline session can become fully capable, and a long-lived session survives access-token expiry — lifting the read-only boundary PR B documented (
Error::Offlinenow fires only when genuinely offline).vault-apiBitwardenClientkeeps therefresh_tokenfromlogin_password, exposes it (refresh_token()), and can be seeded with one (with_refresh_token). Newrefresh()runsgrant_type=refresh_token.sync/create/update/delete) route through onesend_with_authhelper that, on a401with a refresh token held, refreshes once and retries. The per-op bearer/expect()code collapses intosend_with_auth+expect_success(and the now-false "# Panics: never" docs are gone).Persistence
The refresh token is stored encrypted under the user key in the cache (
refresh_tokenenvelope field) on online unlock + everysync, and recovered into the in-memory session on a cache/PIN unlock.Vault::ensure_onlineA token-less session (offline / PIN) lazily upgrades to online on its first
sync/add/edit/remove: it builds a client from the stored refresh token and refreshes. So PIN unlock stays fast/offline, but server ops "just work" once the network is up. A genuinely offline box (or no stored refresh token) still returnsError::Offline.Tests
Store refresh-token round-trip;
ensure_onlinereturnsOfflinewithout a client/token andOkwith a live client; refresh-token recovery from the cache on offline unlock. 144 tests green,clippy -D warningsclean (clean-slate re-lint),cargo denyclean, both headless builds green. No new dependencies.Out of scope (tracked)
Proactive refresh on an
expires_intimer (idle-lock makes long expiry rare); acache.persist_refresh_token = falseopt-out; TUI PIN/unlock entry; Bitwarden personal API-key auth.🤖 Generated with Claude Code