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

@@ -146,9 +146,22 @@ is ignored when matching a `localhost` redirect, allowing a fresh local port;
an HTTP `127.0.0.1` redirect requires manifest editing, while IPv6 loopback
redirects are currently unsupported.
This remains foundation, not working Office 365 integration. There is no token
JSON parser, Secret Service backend, Graph call, account UI, or usable account
setup yet. OAuth tokens and other secrets will never be stored in SQLite. The
synchronous libsecret API may block, so its concrete adapter will run off the
render thread in the next phase. Read-only Microsoft Graph calendar discovery and delta
synchronization follows that token and secret-storage work.
The token and Secret Service boundary parses token responses with strict size
bounds. Initial authorization must return a refresh
token; when Microsoft rotates it, the replacement is retained, while a refresh
response that omits one keeps the previously stored refresh token. Tokens live
only in a versioned libsecret payload keyed by an opaque local account ID. They
never enter SQLite, logs, command-line arguments, or environment variables.
libsecret operations are synchronous but cancellable and must run on a worker
thread, never the render thread. Isolated D-Bus and gnome-keyring tests exercise
the real adapter. A missing secret means the account is disconnected; locked,
cancelled, and storage failures remain distinct errors and never fall back to
another storage channel. Nocal does not mark an account connected until the
complete secret has been stored successfully.
This remains foundation, not working Office 365 integration. There is still no
Microsoft Graph call, account UI, or usable account setup. The next slice is a
Microsoft adapter that calls `/me`, discovers
calendars, and performs read-only delta synchronization with delegated
`Calendars.Read`.