feat(vault): Bitwarden personal API-key auth (2FA accounts)#14
Merged
Conversation
Add a `client_credentials`-grant auth path so a 2FA-enabled account can authenticate without an interactive TOTP prompt (Vault has none). The API key authenticates the session only; the master password is still required to decrypt the vault. - vault-api: BitwardenClient::login_api_key (grant_type=client_credentials, scope=api); new Error::Credential for a non-UTF-8 secret. - vault-store: ApiKeyCreds + save/load/delete_apikey_to_dir (atomic, 0600, Debug redacts the secret). - vault-ipc: optional serde-defaulted api_key on Request::Unlock (forward- compat) + ApiKeyStatus/ApiKeyForget verbs + ApiKeyStatus response (never carries the secret). - vault-agent: grant selection (request creds -> persisted key -> password) with persistence on enrollment; ensure_online falls back to API-key re-auth so a PIN/offline session can still go online. - vault-cli: `vault login --api-key` (BW_CLIENTID/BW_CLIENTSECRET or prompt, master password still read); `vault apikey status/forget`. - Docs: README section, docs/apikey.md live recipe, CHANGELOG, CLAUDE.md. The key is stored as a sibling 0600 file (apikey.json), not in the encrypted envelope: it must be readable before unlock, so it's FS-perms- protected like the refresh token. 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.
What
Adds a Bitwarden personal API-key authentication path (OAuth2
client_credentialsgrant), so an account with two-factor auth enabled canfinally log in — that grant is not 2FA-challenged, and Vault has no interactive
TOTP entry, so the password grant was a dead end for such accounts.
The API key authenticates the session only. The
Keyit returns is stillwrapped under the stretched master key, so the master password is still
required at every unlock to decrypt the vault. The API key just gets you past
2FA.
How
vault login --api-key— reads$BW_CLIENTID/$BW_CLIENTSECRET(likethe official
bwCLI), else prompts, then reads the master password. Onsuccess the agent authenticates via
client_credentialsand persists thekey (
apikey.json,0600, in the account data dir) so plainvault unlockand the TUI unlock auto-reuse it — no further 2FA, no re-supplying creds.
vault apikey status/vault apikey forget— status echoes only thenon-secret
client_id; forget reverts to the password grant.vault-api:BitwardenClient::login_api_key.vault-store:ApiKeyCreds+save/load/delete_apikey_to_dir(atomic,
0600, customDebugthat redacts the secret).vault-ipc: optional serde-defaultedapi_keyonRequest::Unlock(forward-compatible) +
ApiKeyStatus/ApiKeyForgetverbs +ApiKeyStatusresponse (never carries the secret).
vault-agent: grant selection (request creds → persisted key → password)with persistence on enrollment;
ensure_onlinefalls back to API-key re-authso a PIN/offline session of an API-key account can still go online for
sync/edits even when the grant issued no refresh token.Security note
The key is a sibling
0600file, not in the encrypted cache envelope: itmust be readable before unlock (it's used during auth), so it can't be wrapped
under the user key — the same trust level as the stored refresh token / an SSH
private key.
Tests
vault-apiwiremock: theclient_credentialsform + a bad-keyServerStatus(
#[ignore]d like the rest oflogin_sync.rs— ring test-link; CI runs them).vault-store: round-trip / delete /NotFound/0600-mode / Debug-redaction.vault-ipc: transport round-trips (api_keypresent + absent-decodes-to-None,ApiKeyStatus).docs/apikey.md.Local CI-exact gates all green:
fmt, fresh isolatedclippy -D warnings,cargo test --workspace --all-targetsunderRUSTFLAGS=-D warnings,cargo deny check, both headless builds.🤖 Generated with Claude Code