Skip to content

feat: gate the control panel behind a login session - #1

Open
yixian-huang wants to merge 3 commits into
Patrick130306:mainfrom
yixian-huang:panel-login
Open

yixian-huang wants to merge 3 commits into
Patrick130306:mainfrom
yixian-huang:panel-login

Conversation

@yixian-huang

@yixian-huang yixian-huang commented Sep 27, 2026 •

Copy link
Copy Markdown

Why

/panel was a public admin page. With api_keys set, the JSON API required a Bearer token, but the page itself still shipped the whole console (add account, passwords, logs) and asked the browser to keep that key in localStorage. On a public hostname there is no login gate, and the key is readable by any script on the page.

What changed

When api_keys is non-empty:

  • GET / and GET /panel return a login form until a session exists. The admin HTML is not sent.
  • POST /api/panel/login checks the same gateway key and sets an HttpOnly, SameSite=Lax cookie. The cookie value is a random session id, not the key. Behind X-Forwarded-Proto: https the cookie is also Secure.
  • /api/panel/* accepts that cookie or the existing Bearer / x-api-key header.
  • /v1/* does not accept the panel cookie, so a panel login cannot be used as a model credential.
  • POST /api/panel/logout revokes the session. Eight failed logins from one client lock that address out for 60 seconds.
  • Sessions live in memory and are dropped on restart. They are not written next to accounts.json.

With api_keys empty, the panel stays open. That path is only for localhost.

/health is unchanged and still public.

Test plan

  • python3 -m unittest discover -s tests -t . (119 tests)
  • Open /panel with api_keys set and confirm the login form is the only UI
  • Sign in, add/disable an account, then log out and confirm the admin page is gone
  • Call /v1/models with the panel cookie and confirm 401; the same call with Authorization: Bearer still works

When api_keys is set, / and /panel serve a login form instead of the
admin UI. A correct key sets an HttpOnly cookie accepted only on
/api/panel/*; the model API still requires Bearer or x-api-key.
LOOMY_PROXY accepts socks5://, http:// and https://. The panel can
save that proxy without returning the password, and it takes effect
immediately.
refresh_models waits out the upstream timeout, which left the port closed
while the model gateway's TLS handshake hung.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant