oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1# Connect an app to Home
2
3`connect` lets an app keep its existing local accounts and accept Home sign-in
4after the person explicitly links both identities. The app still decides who
5may enter and what they may access.
6
7Load optional settings with `CredentialsFromEnv("SHEETS")`, then validate them
8with `DecodeCredentials(settings, origin)` once you know the app's origin.
9The variables end in `_HOME_ISSUER`, `_HOME_CONSUMER_ID`, `_HOME_KEYS_BASE64`
10and `_HOME_DEK_BASE64`. All empty means disabled; a partial configuration fails.
11`HOME_DIRECTORY_ORIGIN` (unprefixed, the name Home itself reads) optionally
12supplies the hub directory origin as `Credentials.Directory`, for an app switcher
13and "All apps" link. It enables nothing on its own and must be a bare https origin.
14Use a dedicated random 32-byte storage key and a distinct namespace per app.
15Keep this key stable: it protects proof state and keyed local-person bindings.
16Replacing it without a migration makes existing links unusable; rotation requires
17an explicit migration with both keys, which this package does not yet provide.
18
19Apply `handoff.Migrations` and `linkstore.Migrations` using your framework's
20migration package. The latter's namespace is `linkstore.SchemaNamespace`.
21This package imports neither framework session nor migration types, so old
22and new Rastrillo apps can share the same protocol.
23
24Construct `Bridge` with the hooks in `Config`. Read the actual session cookie;
25`MintHomeSession` stores the opaque Home subject with method `HomeMethod` and
26the supplied authentication time. `LookupLocalSession` reads an existing
27local account. `LookupPerson` uses its supplied SQL query object and rechecks
28current admission; return `sql.ErrNoRows` for missing or disallowed accounts.
29Neither hook creates accounts, samples or identity mappings. The returned label
30must be the canonical verified local address, never an editable display name.
31Local IDs must never be reused, even for a new account with the same address.
32Delete the account's `home_identity_links` rows in the same transaction as the
33local account. The shared schema cannot impose a foreign key on app-owned tables.
34A keyed binding detects reuse with a different address at every proof stage and
35resolution. Address changes require explicit removal and fresh linking; old or
36unbound links fail closed, rather than adopting the current account.
37
38Session cookie values must be exactly 43 characters of canonical unpadded
39base64url encoding of 32 bytes. `Session.AuthTime` is the method's actual
40verification time. `Session.At` is immutable verified-session creation time,
41never last-seen time or request time: legacy magic-link sessions can use it when
42`AuthTime` is absent. Keymail sessions cannot use that fallback.
43
44On every authenticated app request, resolve Home sessions through
45`Bridge.ResolveSession`. Use the returned local ID for ownership and grants.
46Never pass an opaque Home subject through a helper that treats subjects as
47email addresses or creates a local account. Keep ordinary local sign-in's
48existing behavior in the app's local authentication path.
49
50`Routes` supplies shared navigation, local reauthentication markers and the
51narrow callback exception to your app's CSRF middleware. Mount its methods
52alongside `Bridge.Start`, `Callback`, `Finish` and `ConfirmPage`. The local
53plugin's successful return must be `LocalCompletePath`. Its pending cookie must
54be `__Host-rastrillo_pending`, and the local entry is `/signin/local`.
55Mount exactly `Bridge.Callback` at `POST /auth/assertion`: this path alone bypasses
56CSRF protection because the bridge verifies its browser-bound assertion.
57`LocalLandingPath` optionally sets a validated local destination after ordinary
58local sign-in (default `/`); explicit account-link navigation takes precedence. Render shared pages
59inside the app's existing Estilo layout; all paths work without JavaScript.
60Apps with additional content-security directives must supply their existing
61policy with the validated Home issuer added to `form-action`.
62
63`Routes.Signin` sends a person who already has a live session straight to the
64validated `return_to` (or `/`) instead of starting another exchange. `Routes.Signout`
65ends the app session and then answers `307` to `{Issuer}/signout`, which ends the
66Home sign-in session too and returns the browser to the app's `/signed-out`. Home
67admits that hop only from an origin registered in its `HOME_CONSUMERS`, by
68`Referer` when the browser nulls `Origin`, so a page showing Sign out must not send
69`Referrer-Policy: no-referrer` (use `strict-origin`), and a custom policy must keep
70the issuer in `form-action`.
71
72Wrap the trusted local plugin's Begin handler with `OAuthContinuation` when it
73can redirect to Keymail. Render the supplied destination with `OAuthPage` and the
74shared `home-oauth-head` and `home-oauth` templates inside your existing layout.
75The helper validates the client origin, `/auth/callback`, scope and PKCE method,
76then ends the POST with a no-store continuation page so Safari does not apply
77form-action to Keymail's redirect chain. It does not discover or trust arbitrary
78request-provided destinations. Keep the existing local plugin callback route.
79
80## Extraction record
81
82The link proof protocol, bridge, freshness rules and navigation originate in
83Oficina Docs commit `3fffd1be535ec87ab6e92da753046a4f1e4c754c`, whose tree
84matches reviewed deployment `a8f5c0e1d67ad042e89793aa941135eacd98aa23`.
85The original files live under `internal/docs/home_*` and `internal/docs/linkstore`.
86This extraction preserves the consumed proof stages, nonce/session binding,
87clock watermark, freshness windows and encrypted flow purpose strings.
88
89Changes are the neutral session DTO and app lookup hooks, new `home_*` link
90tables, app-selected encryption namespace, and shared rendering/navigation.
91The local-person foreign key becomes a transaction-scoped existence/admission
92lookup, because apps use different local identity tables. Every link write
93and subsequent resolution still checks the current local account. Additive
94migration 0002 adds a keyed person binding; earlier unbound rows fail closed.
95OAuth continuation also derives from Docs `internal/docs/auth_navigation.go`.
96
97The original Docs state-machine tests were ported with synthetic session and
98local-account hooks. The app-owned content-preservation test now checks an
99unchanged content row; framework CSRF coverage belongs to the shared Routes
100and each app's router integration tests. Existing Docs tables and bytes are
101untouched. This is not a migration path for an already deployed Docs instance.
102