feat: store OAuth tokens securely

Add bounded token response parsing with refresh-token retention and a cancellable worker-thread libsecret adapter using versioned credential payloads.

Verify CRUD, isolation, cancellation, corruption, unavailable service, redaction, and cleanup against an isolated D-Bus and gnome-keyring Secret Service in normal, warning-as-error, and sanitizer profiles.
This commit is contained in:
2026-07-18 11:56:04 +01:00
parent 295acbc125
commit 3560a7d23d
13 changed files with 1405 additions and 27 deletions

View File

@@ -215,8 +215,32 @@ the application manifest, and IPv6 loopback redirect addresses are currently
unsupported. The advertised `localhost` URL therefore remains distinct from the
IPv4-only listener binding.
Concrete token JSON parsing, Secret Service storage, Graph requests, account UI,
and usable account setup are still absent. libsecret's synchronous calls may
block; the next adapter isolates them behind an off-render-thread boundary
rather than calling them from the TUI loop. Read-only Graph discovery and delta
follow token parsing and secret persistence.
### Token and Secret Service boundary
The token decoder is strict and
bounded: oversized or structurally invalid JSON, wrong field types, and invalid
token lifetimes fail before any credential is exposed to account state. Initial
authorization requires a refresh token. A later refresh response replaces it
when Microsoft supplies a rotated token and otherwise retains the previously
stored refresh token.
Access and refresh tokens are serialized together in a versioned libsecret
secret payload, addressed by an opaque local account identifier. SQLite stores
neither token material nor a copy of the secret payload. Token values also stay
out of logs, command-line arguments, and environment variables. Schema/version
validation prevents an incompatible secret from being mistaken for current
credentials.
The libsecret adapter exposes synchronous, cancellable operations and is
worker-thread-only because a D-Bus call or keyring unlock may block. Tests run
the real adapter against an isolated D-Bus session and gnome-keyring rather than
claiming integration from an in-memory fake alone. Missing credentials are a
normal disconnected result. A locked collection, cancellation, parse error, or
read/write/delete failure remains an explicit failure; none triggers plaintext
fallback or partial connected state. An account becomes connected only after
the complete versioned payload has been stored successfully.
Graph requests, account UI, and usable account setup remain absent. After this
boundary passes verification, the Microsoft adapter will call `/me`, discover
calendars, and apply read-only delta pages through the durable cache using only
delegated `Calendars.Read`.