Signs people in to Seerr behind oauth2-proxy
  • Go 99.5%
  • Dockerfile 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
agent a1d86af9a1
All checks were successful
ci / test (push) Successful in 19s
ci / image (push) Successful in 16s
seerr-oidc: /_gate/seerr reports a Seerr that hands out no sessions
Seerr's session store (connect-typeorm) emits disconnect on any database
error and express-session never re-enables it, so every sign-in after that
is a 200 without connect.sid until Seerr restarts. The endpoint is for a
liveness probe on Seerr: it signs in again only while the latest sign-in
got no session, so a healthy Seerr sees no extra sign-ins and no sessions
pile up; other failures answer ok, since a restart mends none of them.

Proxy errors are answered 502 in plain text: the default handler's empty
502 made browsers save an empty file.
2026-11-01 12:20:34 +01:00
.forgejo/workflows seerr-oidc: the Seerr sign-in proxy, out of zeleerr 2026-10-22 23:00:15 +02:00
api seerr-oidc: the Seerr sign-in proxy, out of zeleerr 2026-10-22 23:00:15 +02:00
internal seerr-oidc: /_gate/seerr reports a Seerr that hands out no sessions 2026-11-01 12:20:34 +01:00
.dockerignore seerr-oidc: the Seerr sign-in proxy, out of zeleerr 2026-10-22 23:00:15 +02:00
.gitignore seerr-oidc: the Seerr sign-in proxy, out of zeleerr 2026-10-22 23:00:15 +02:00
Dockerfile seerr-oidc: the Seerr sign-in proxy, out of zeleerr 2026-10-22 23:00:15 +02:00
go.mod seerr-oidc: sign in with OpenID Connect itself 2026-10-22 23:10:40 +02:00
go.sum seerr-oidc: sign in with OpenID Connect itself 2026-10-22 23:10:40 +02:00
LICENSE seerr-oidc: the Seerr sign-in proxy, out of zeleerr 2026-10-22 23:00:15 +02:00
main.go seerr-oidc: /_gate/seerr reports a Seerr that hands out no sessions 2026-11-01 12:20:34 +01:00
main_test.go seerr-oidc: /_gate/seerr reports a Seerr that hands out no sessions 2026-11-01 12:20:34 +01:00
README.md seerr-oidc: /_gate/seerr reports a Seerr that hands out no sessions 2026-11-01 12:20:34 +01:00

seerr-oidc

A reverse proxy that serves Seerr's own pages to signed-in people without a sign-in of Seerr's. It keeps a Seerr session for each email and puts it on their requests; the browser never holds one. It signs in as the local Seerr user of that email with the password HMAC-SHA256(GATE_KEY, email), and sets that password through the API, creating the user if Seerr has none, when the sign-in fails. Seerr wants the current password to change the API key owner's own, so a new GATE_KEY needs that user's password reset by hand.

A request whose path starts with /api/ and that carries a non-empty X-Api-Key header goes to Seerr as it came: no sign-in, no session, the key left for Seerr to check.

Apps that sign in to Seerr themselves, such as TV apps, are passed through in both modes, their requests and Seerr's answers untouched, connect.sid included:

  • a POST to /api/v1/auth/local or /api/v1/auth/jellyfin, always;
  • a POST to /api/v1/auth/logout, and any request under /api/ with a connect.sid cookie, when it comes without a session of seerr-oidc's (OIDC mode) or without the GATE_USER_HEADER header (header mode).

A request that has both the session or the header and a connect.sid cookie is served on seerr-oidc's own Seerr session, as any other. Outside /api/, connect.sid lets nothing through. A request that Seerr cannot serve, or for whose person no Seerr session can be had, is answered 502 in plain text.

Seerr's session store disconnects on a database error and stays disconnected until Seerr restarts; Seerr then accepts sign-ins without a session. /_gate/seerr reports that state for a liveness probe: once a sign-in gets no session, each call signs in again as that person, and answers 503 until a session comes back. Before that, and on any other failure, it answers ok without asking Seerr anything.

Modes

Header mode, when OIDC_ISSUER_URL is unset: an authenticating proxy in front (such as oauth2-proxy) signs people in, and the email is read from the GATE_USER_HEADER request header. Any other request without the header is answered 401.

OIDC mode, when OIDC_ISSUER_URL is set: seerr-oidc signs people in itself with the OpenID Connect authorization-code flow (PKCE, state and nonce), scopes openid email profile groups. The GATE_USER_HEADER header from the client is removed and never trusted. After sign-in the ID token's email and groups go into the seerr_oidc cookie, encrypted and authenticated with AES-256-GCM under SESSION_KEY (Secure, HttpOnly, SameSite=Lax, path /, lasting SESSION_TTL); the cookie is not passed to Seerr. A token with email_verified false, or whose groups include none of OIDC_ALLOWED_GROUPS, is answered 403, as is a session whose groups no longer do.

Without a valid session, and not passed through as above, a page request (a GET or HEAD outside /api/, not marked X-Requested-With: XMLHttpRequest, with Sec-Fetch-Mode absent or navigate) is redirected to the provider and comes back to the same path; any other request is answered 401. A return path that is not a path on this site becomes /. A callback whose state does not match a sign-in this browser started is answered 400.

Path Answers
/_gate/healthz ok, without a session or the header, in both modes
/_gate/seerr 503 while Seerr accepts sign-ins without a session, ok otherwise; for a liveness probe on Seerr
path of OIDC_REDIRECT_URL the provider's callback (OIDC mode)
/oauth2/sign_out clears the session cookie (OIDC mode)

Configuration

Variable Default Meaning
SEERR_URL Seerr, without /api/v1
SEERR_API_KEY Seerr API key with MANAGE_USERS
GATE_KEY key the passwords derive from
GATE_ADDR :8081 listen address
GATE_USER_HEADER X-Forwarded-Email header carrying the user's email in header mode, removed in OIDC mode
GATE_PERMISSIONS 160 Seerr permission bits for a user the proxy creates
OIDC_ISSUER_URL OpenID provider; setting it turns on OIDC mode
OIDC_CLIENT_ID client ID, required in OIDC mode
OIDC_CLIENT_SECRET client secret, required in OIDC mode
OIDC_REDIRECT_URL full callback URL registered with the provider, required in OIDC mode; without a path, its path is /oauth2/callback
OIDC_ALLOWED_GROUPS comma-separated groups let in, required in OIDC mode
SESSION_KEY 32 bytes in standard base64, required in OIDC mode: openssl rand -base64 32
SESSION_TTL 168h session lifetime, a Go duration

With OIDC_ISSUER_URL set, startup fails naming any other required OIDC variable that is unset, and when the provider's discovery document cannot be fetched.

Running

go build .
SEERR_URL=http://127.0.0.1:5055 SEERR_API_KEY=... GATE_KEY=... ./seerr-oidc

The image, sarak.sh/arr/seerr-oidc, runs /seerr-oidc as its entrypoint and exposes port 8081.