oficina / home Public

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

Plain git — no account needed to clone.

Download

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.