# Connect an app to Home

`connect` lets an app keep its existing local accounts and accept Home sign-in
after the person explicitly links both identities. The app still decides who
may enter and what they may access.

Load optional settings with `CredentialsFromEnv("SHEETS")`, then validate them
with `DecodeCredentials(settings, origin)` once you know the app's origin.
The variables end in `_HOME_ISSUER`, `_HOME_CONSUMER_ID`, `_HOME_KEYS_BASE64`
and `_HOME_DEK_BASE64`. All empty means disabled; a partial configuration fails.
`HOME_DIRECTORY_ORIGIN` (unprefixed, the name Home itself reads) optionally
supplies the hub directory origin as `Credentials.Directory`, for an app switcher
and "All apps" link. It enables nothing on its own and must be a bare https origin.
Use a dedicated random 32-byte storage key and a distinct namespace per app.
Keep this key stable: it protects proof state and keyed local-person bindings.
Replacing it without a migration makes existing links unusable; rotation requires
an explicit migration with both keys, which this package does not yet provide.

Apply `handoff.Migrations` and `linkstore.Migrations` using your framework's
migration package. The latter's namespace is `linkstore.SchemaNamespace`.
This package imports neither framework session nor migration types, so old
and new Rastrillo apps can share the same protocol.

Construct `Bridge` with the hooks in `Config`. Read the actual session cookie;
`MintHomeSession` stores the opaque Home subject with method `HomeMethod` and
the supplied authentication time. `LookupLocalSession` reads an existing
local account. `LookupPerson` uses its supplied SQL query object and rechecks
current admission; return `sql.ErrNoRows` for missing or disallowed accounts.
Neither hook creates accounts, samples or identity mappings. The returned label
must be the canonical verified local address, never an editable display name.
Local IDs must never be reused, even for a new account with the same address.
Delete the account's `home_identity_links` rows in the same transaction as the
local account. The shared schema cannot impose a foreign key on app-owned tables.
A keyed binding detects reuse with a different address at every proof stage and
resolution. Address changes require explicit removal and fresh linking; old or
unbound links fail closed, rather than adopting the current account.

Session cookie values must be exactly 43 characters of canonical unpadded
base64url encoding of 32 bytes. `Session.AuthTime` is the method's actual
verification time. `Session.At` is immutable verified-session creation time,
never last-seen time or request time: legacy magic-link sessions can use it when
`AuthTime` is absent. Keymail sessions cannot use that fallback.

On every authenticated app request, resolve Home sessions through
`Bridge.ResolveSession`. Use the returned local ID for ownership and grants.
Never pass an opaque Home subject through a helper that treats subjects as
email addresses or creates a local account. Keep ordinary local sign-in's
existing behavior in the app's local authentication path.

`Routes` supplies shared navigation, local reauthentication markers and the
narrow callback exception to your app's CSRF middleware. Mount its methods
alongside `Bridge.Start`, `Callback`, `Finish` and `ConfirmPage`. The local
plugin's successful return must be `LocalCompletePath`. Its pending cookie must
be `__Host-rastrillo_pending`, and the local entry is `/signin/local`.
Mount exactly `Bridge.Callback` at `POST /auth/assertion`: this path alone bypasses
CSRF protection because the bridge verifies its browser-bound assertion.
`LocalLandingPath` optionally sets a validated local destination after ordinary
local sign-in (default `/`); explicit account-link navigation takes precedence. Render shared pages
inside the app's existing Estilo layout; all paths work without JavaScript.
Apps with additional content-security directives must supply their existing
policy with the validated Home issuer added to `form-action`.

`Routes.Signin` sends a person who already has a live session straight to the
validated `return_to` (or `/`) instead of starting another exchange. `Routes.Signout`
ends the app session and then answers `307` to `{Issuer}/signout`, which ends the
Home sign-in session too and returns the browser to the app's `/signed-out`. Home
admits that hop only from an origin registered in its `HOME_CONSUMERS`, by
`Referer` when the browser nulls `Origin`, so a page showing Sign out must not send
`Referrer-Policy: no-referrer` (use `strict-origin`), and a custom policy must keep
the issuer in `form-action`.

Wrap the trusted local plugin's Begin handler with `OAuthContinuation` when it
can redirect to Keymail. Render the supplied destination with `OAuthPage` and the
shared `home-oauth-head` and `home-oauth` templates inside your existing layout.
The helper validates the client origin, `/auth/callback`, scope and PKCE method,
then ends the POST with a no-store continuation page so Safari does not apply
form-action to Keymail's redirect chain. It does not discover or trust arbitrary
request-provided destinations. Keep the existing local plugin callback route.

## Extraction record

The link proof protocol, bridge, freshness rules and navigation originate in
Oficina Docs commit `3fffd1be535ec87ab6e92da753046a4f1e4c754c`, whose tree
matches reviewed deployment `a8f5c0e1d67ad042e89793aa941135eacd98aa23`.
The original files live under `internal/docs/home_*` and `internal/docs/linkstore`.
This extraction preserves the consumed proof stages, nonce/session binding,
clock watermark, freshness windows and encrypted flow purpose strings.

Changes are the neutral session DTO and app lookup hooks, new `home_*` link
tables, app-selected encryption namespace, and shared rendering/navigation.
The local-person foreign key becomes a transaction-scoped existence/admission
lookup, because apps use different local identity tables. Every link write
and subsequent resolution still checks the current local account. Additive
migration 0002 adds a keyed person binding; earlier unbound rows fail closed.
OAuth continuation also derives from Docs `internal/docs/auth_navigation.go`.

The original Docs state-machine tests were ported with synthetic session and
local-account hooks. The app-owned content-preservation test now checks an
unchanged content row; framework CSRF coverage belongs to the shared Routes
and each app's router integration tests. Existing Docs tables and bytes are
untouched. This is not a migration path for an already deployed Docs instance.
