Files
verdande/internal/store/migrations/0011_instance_settings.sql
T
Kristian WindandClaude Opus 5 31bf0557df feat(gmail): the OAuth client can be entered instead of deployed
The one click that opens Google's consent screen already existed. What it needs
behind it is a registered client, and no app can conjure one: Google issues no
Gmail access to an unregistered client, and `gmail.readonly` is a restricted
scope, so an id shipped inside a public image would be both extractable from it
and unusable without Google's security review of every deployment using it.

The registration is a one-off. Having to edit a Rune's manifest and recreate the
container to paste the result was not.

`VERDANDE_GMAIL_CLIENT_ID` and `_SECRET` still win when set. A value in the
environment is a deliberate deployment decision, applied again on every recreate,
and a form that could quietly override it would make the manifest a lie — so the
page says where the value came from, and PUT answers 409.

The redirect URI is spelled out on the page rather than left to be reconstructed.
It is derived from VERDANDE_BASE_URL, it has to match Google's copy exactly, and
the error Google returns when it does not names neither value.

Its own table rather than `user_settings`: an OAuth client is the instance's, not
one person's — and `user_settings` has `user_id` as its whole primary key, so a
second scope there would silently overwrite the AI provider's key.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 09:05:18 +02:00

25 lines
1.3 KiB
SQL

-- Settings that belong to the instance rather than to a person.
--
-- The first of them is the Gmail OAuth client. It was configuration only —
-- `VERDANDE_GMAIL_CLIENT_ID` and `_SECRET` — which on a Rune means editing the
-- manifest and recreating the container to change a value you paste once and
-- never touch again. The registration itself cannot be avoided (Google issues no
-- Gmail access to an unregistered client, and `gmail.readonly` is a restricted
-- scope, so a client id shipped inside a public image would be both extractable
-- and unusable), but having to redeploy to enter it can.
--
-- Its own table rather than `user_settings`, for two reasons. An OAuth client is
-- the instance's, not one person's — everybody connecting their mailbox uses the
-- same registration. And `user_settings` has `user_id` as its whole primary key,
-- so it can hold exactly one scope per person and its upsert rewrites the scope
-- name on conflict; adding a second scope there would silently overwrite the AI
-- provider's key.
--
-- JSON for the same reason templates are: written whole, read whole, and it would
-- otherwise be a migration every time a setting is added.
CREATE TABLE instance_settings (
scope TEXT PRIMARY KEY,
values_json TEXT NOT NULL DEFAULT '{}',
updated_at INTEGER NOT NULL
);