PROGRESS.md becomes CHANGELOG.md with the finished step lists moved to an appendix. The README is an overview pointing at docs/: configuration, cli, users, sso (refreshed for accounts and admin-only settings), and architecture. CLAUDE.md collects what working on this code actually requires -- pkill -x not -f, the page being compiled in, the dead columns on entries, the Playwright worker that deleted its own database. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
9.2 KiB
Signing in through Cloudflare Zero Trust or Authentik
ipx can take the signed-in identity from whatever sits in front of it, instead of asking for a password itself. Both products below do the same thing in the end: they authenticate the person and pass the result to the origin in a header. ipx reads that header, finds (or creates) the matching account, and gets on with it.
Read How this is secured before exposing anything. The short version: a header is worth exactly as much as the hop that set it, so ipx only believes one from an address you list.
The ipx side (both setups)
[web]
enabled = true
bind = "0.0.0.0:8099"
token = "…" # keep it: it is the admin, used by the healthcheck
# The header your proxy sets. Empty (the default) disables this whole path.
trusted_header = "Cf-Access-Authenticated-User-Email" # Authentik: "X-authentik-username"
# Addresses allowed to assert that header -- the proxy, and nothing else.
trusted_proxies = ["127.0.0.1", "::1"]
# Create an account the first time the proxy vouches for a name ipx has not seen.
auto_create_users = true
session_days = 30
Restart the daemon after editing. Accounts made this way have no password: they can only ever
arrive through the proxy. ipx user list marks them proxy only.
The first account created is an admin. Every later one is an ordinary user, and an ordinary user
cannot change global settings, a feed's URL or folder, or how often feeds are scanned: the API
refuses those with a 403, not just the UI. Everything else about a feed (which items they want,
whether to fetch them, how many at a time) is theirs alone; see users.md.
Somebody arriving through the proxy for the first time starts with no feeds, because subscriptions are per person. Adding a feed someone else already reads costs no second fetch and no second copy on disk.
Promote someone with:
ipx user list
echo -n 'a good password' | ipx user passwd <name> # optional: also lets them sign in directly
Local sign-in at /login keeps working alongside all of this, which is how you get in from the LAN
when the tunnel is down. So does the shared [web] token, which signs in as the admin: that is
what the Docker healthcheck uses, and the way back in if you lock yourself out. A brand new database
starts with admin / ipodderx — change it.
Cloudflare Zero Trust
This is what runs ipodderx.sdf1.net: a cloudflared tunnel to the origin, with an Access
application in front of it. Cloudflare authenticates the visitor and adds
Cf-Access-Authenticated-User-Email to every request it forwards.
1. The tunnel
In Zero Trust → Networks → Tunnels, either use the existing tunnel or create one, then add a public hostname:
| Field | Value |
|---|---|
| Subdomain / domain | ipodderx / sdf1.net |
| Type | HTTP |
| URL | localhost:8099 (or the LAN address of the box) |
Use localhost when cloudflared runs on the same machine as ipx — that keeps the origin request
coming from 127.0.0.1, which is already in trusted_proxies. If cloudflared runs elsewhere (its
own container, another host), put its address in trusted_proxies instead, and make sure
nothing else can reach port 8099.
2. The Access application
Zero Trust → Access → Applications → Add an application → Self-hosted:
- Application domain:
ipodderx.sdf1.net - Session duration: whatever suits; ipx keeps its own 30-day session on top.
- Add a policy — Allow, with a rule such as
Emails→ your address, orEmails ending in→ your domain. Anyone this policy admits gets an ipx account whenauto_create_usersis on, so keep the policy as narrow as the people you actually want reading your feeds.
3. Point ipx at the header
trusted_header = "Cf-Access-Authenticated-User-Email"
trusted_proxies = ["127.0.0.1", "::1"]
The username becomes the email address, lower-cased (ray@example.com). That is what shows in the
sidebar and what ipx user list prints.
4. Check it
# From the box itself: no header, no session -> the sign-in page.
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' -H 'Accept: text/html' http://127.0.0.1:8099/
# Pretending to be the tunnel (only works because 127.0.0.1 is trusted):
curl -s -H 'Cf-Access-Authenticated-User-Email: you@example.com' http://127.0.0.1:8099/api/me
Then load https://ipodderx.sdf1.net in a browser: Cloudflare should ask who you are, and ipx
should show your address in the sidebar footer without ever asking for a password.
Authentik
Authentik does this with a Proxy Provider plus an outpost, which sits in the request path and
adds X-authentik-username (also X-authentik-email, X-authentik-name, X-authentik-groups).
1. Provider
Applications → Providers → Create → Proxy Provider:
- Name:
ipx - Authorization flow: your usual (
default-provider-authorization-implicit-consent) - Mode: Forward auth (single application) if an existing reverse proxy fronts ipx, or Proxy to let the outpost talk to ipx directly.
- External host:
https://ipodderx.example.net - Internal host (Proxy mode):
http://<ip of the ipx box>:8099
2. Application and outpost
Applications → Create, bind it to that provider, and give it a policy so only the people you mean are let through. Then add the provider to an outpost (Applications → Outposts, the embedded one is fine).
3. Forward auth, if you use nginx/SWAG in front
In the server block for ipx:
location /outpost.goauthentik.io {
proxy_pass http://authentik-server:9000/outpost.goauthentik.io;
proxy_set_header Host $host;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
add_header Set-Cookie $auth_cookie;
auth_request_set $auth_cookie $upstream_http_set_cookie;
}
location / {
auth_request /outpost.goauthentik.io/auth/nginx;
error_page 401 = @goauthentik_proxy_signin;
auth_request_set $auth_cookie $upstream_http_set_cookie;
add_header Set-Cookie $auth_cookie;
# This is the line that matters to ipx.
auth_request_set $authentik_username $upstream_http_x_authentik_username;
proxy_set_header X-authentik-username $authentik_username;
proxy_pass http://ipx:8099;
}
4. Point ipx at the header
trusted_header = "X-authentik-username"
trusted_proxies = ["172.18.0.5"] # the outpost or nginx container, NOT a whole subnet
Usernames arrive as Authentik knows them (ray), lower-cased.
How this is secured
The header is only believed from trusted_proxies. Every other source is ignored, and the
request falls through to a session cookie or the shared token. This is the whole security boundary,
so:
- List the proxy's own address, not a range.
["127.0.0.1"]when the tunnel runs beside ipx; the container's IP when it does not. - Never list a LAN subnet. Anyone on your network could then send
Cf-Access-Authenticated-User-Email: admin@…and be your admin. - Make sure the origin port is not reachable around the proxy by anyone you would not admit
through it. If it is, bind ipx to
127.0.0.1and let only the proxy reach it.
Verify the refusal, don't assume it — set trusted_proxies = ["10.9.9.9"] briefly and confirm a
header from your machine gets a 401:
curl -s -o /dev/null -w '%{http_code}\n' \
-H 'Cf-Access-Authenticated-User-Email: someone@example.com' http://127.0.0.1:8099/api/me
What ipx does not do: it does not verify Cloudflare's Cf-Access-Jwt-Assertion signature or
Authentik's session. It trusts the hop. That is a deliberate trade — it keeps the configuration to
three lines — and it is sound exactly as long as the point above holds.
Turning it off: clear trusted_header. Existing proxy-only accounts stay, but nobody can sign
in with them until they are given a password (ipx user passwd <name>).
Everyday administration
ipx user list # who exists, and how each one signs in
echo -n 'secret123' | ipx user add sam # local account, password on stdin
ipx user add sam --no-password # proxy-only account, created ahead of time
echo -n 'newsecret' | ipx user passwd sam # change a password
ipx user rm sam # remove the account
Set auto_create_users = false once everyone who should have an account has one. After that the
proxy vouching for an unknown name is logged and refused, rather than quietly making an account.
Pre-create people instead with ipx user add <name> --no-password, using exactly the name the
header will carry (Cloudflare sends the email address, lower-cased).
Scanning intervals, the disk quota, retention, the download folder and a feed's URL are admin-only: the Settings button is hidden for everyone else, and the API refuses the change even if the request is made by hand. Everyone controls their own keywords, auto-download, explicit setting and per-scan cap, along with their own read state and which feeds they see.
See also users.md for what several people share, configuration.md
for every [web] key, and cli.md for the ipx user commands.