Skip to content

feat(vault): token persistence + refresh#12

Merged
UnbreakableMJ merged 2 commits into
mainfrom
token-refresh
Jun 15, 2026
Merged

feat(vault): token persistence + refresh#12
UnbreakableMJ merged 2 commits into
mainfrom
token-refresh

Conversation

@UnbreakableMJ

Copy link
Copy Markdown
Contributor

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::Offline now fires only when genuinely offline).

vault-api

  • BitwardenClient 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 per-op bearer/expect() code collapses into send_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_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: 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 returns Error::Offline.

Tests

Store refresh-token round-trip; ensure_online returns Offline without a client/token and Ok with a live client; refresh-token recovery from the cache on offline unlock. 144 tests green, clippy -D warnings clean (clean-slate re-lint), cargo deny clean, both headless builds green. No new dependencies.

Out of scope (tracked)

Proactive refresh on an expires_in timer (idle-lock makes long expiry rare); a cache.persist_refresh_token = false opt-out; TUI PIN/unlock entry; Bitwarden personal API-key auth.

🤖 Generated with Claude Code

UnbreakableMJ and others added 2 commits June 15, 2026 03:18
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>
@UnbreakableMJ
UnbreakableMJ merged commit 075c35c into main Jun 15, 2026
8 checks passed
@UnbreakableMJ
UnbreakableMJ deleted the token-refresh branch June 15, 2026 00:30
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