worktree-authelia-sso #1

Merged
janne merged 2 commits from worktree-authelia-sso into main 2026-09-11 11:26:16 +00:00
Owner
No description provided.
Editors signed in twice: once at the gate, once at Ghost's own form. APP.md put
a shim down as a possible later step on the grounds that Ghost has no native
staff SSO, which is no longer true — Ghost 6 consults an `sso` adapter on every
request to /ghost and turns whatever it returns into a verified session.
nix/sso-adapter.js is that adapter, reading the address out of Remote-Email.

Verified is the half that was not obvious. It is the step that would otherwise
email a six-digit code, so the admin becomes usable on an instance that cannot
send mail — proved end to end against a container with
staffDeviceVerification=true and no working SMTP, where the password form
answers 500 and the exchange answers 200. Staging carries
security__staffDeviceVerification=false in ../verde-infra for exactly that
reason, and can drop it once this is deployed.

Nothing is provisioned. An identity with no Ghost account of that address falls
through to the ordinary sign-in form, so staff are still invited in Ghost and
revoking someone in Authelia still stops them at the gate.

What makes the header true is Caddy, so the virtual host now strips every
Remote-* a client sends before forward_auth sets the real ones. That is not
tidiness: excludePaths reaches Ghost without ever meeting the gate, and a
forged header on /ghost/ads would otherwise have been an owner session for
anyone who asked. Behind it, sso.proofFile puts a shared secret in a header
only Caddy sets, covering the loopback port every process on the host can
reach; one sops file is read by both Caddy and the container, so the secret is
in neither the Nix store nor a command line.

The stripping, the forward_auth and the proof have to happen in that order and
the default directive order promises nothing, so they sit in an explicit route
— one that ends in no handler of its own, so ../verde-infra's ad server
handlers on the same virtual host keep the position they had. Checked against
caddy adapt: the ad routes still come first and still terminate, and Ghost only
ever sees requests that went through the strip.

portalUrl closes the loop sign-out would otherwise be. Ghost destroys its
session and then reloads the admin with window.location.replace, which the
exchange would answer with a fresh one; Caddy marks the sign-out response and
redirects the reload that follows to Authelia's /logout.

Fourteen checks against Caddy, a stand-in for Authelia's forward-auth endpoint
and a real Ghost: the chain signs in, a forged header on an excluded path does
not, the loopback port refuses a missing or wrong proof and accepts the right
one, a refusal from Authelia still 401s, and sign-out redirects once and mints
nothing.

The development instance runs the same adapter, with no proof required because
there is no proxy to set one — a Remote-Email header on the loopback port is
how you sign in there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017SZueaEjwNjKsti39wrut4
The proof variables are named per instance, so one sops file can carry them
all — which is the arrangement two instances on one host will reach for. Left
as it was, Caddy's unit listed the same EnvironmentFile once per instance.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017SZueaEjwNjKsti39wrut4
janne merged commit 39d78f6b79 into main 2026-09-11 11:26:16 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
verde/ghost-verde!1
No description provided.