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

@@ -154,12 +154,26 @@ client with `http://localhost/nocal/oauth/callback`. Microsoft ignores the port
when matching localhost redirects. Literal HTTP `127.0.0.1` registration needs
a manifest edit, and IPv6 loopback redirects are currently unsupported.
These adapters still do not make Office 365 usable. They include no token JSON
parser, Secret Service backend, Graph call, or account UI. libsecret exposes
synchronous operations that may block, so the next phase isolates its adapter
off the render thread. Token parsing and secure persistence come before
read-only Graph calendar discovery and delta synchronization. Remote writes
remain gated on the durability and conflict paths being exercised end to end.
The token and Secret Service boundary strictly parses
bounded token JSON and requires a refresh token at initial authorization. A
rotated refresh token replaces the stored value; if a refresh response omits
one, Nocal retains the prior refresh token. Access and refresh tokens are stored
only as a versioned libsecret secret keyed by an opaque local account ID. They
never appear in SQLite, logs, command lines, or environment variables.
Secret Service operations are synchronous but cancellable and run only on a
worker thread so a D-Bus or keyring wait cannot block rendering. The real
adapter is exercised against isolated D-Bus and gnome-keyring services. Missing
credentials leave an account disconnected. Locked-keyring, cancellation,
parsing, and storage failures are reported distinctly, with no plaintext
fallback and no partially connected account. Connection state is published
only after the complete secret store succeeds.
This phase still does not make Office 365 usable. It contains no Microsoft Graph
call, account UI, or usable account setup. After verification, the next product
slice calls `/me`, discovers calendars, and performs read-only Graph delta
synchronization using delegated `Calendars.Read`. Remote writes remain gated on
the durability and conflict paths being exercised end to end.
## Local file interchange