Root cause: auth_middleware redirected all non-admins (including logged-in
ones) to /login, and login_page redirected logged-in users back — a loop.
Fix:
- auth_middleware now distinguishes unauthenticated (→ /login?next=) from
logged-in-but-not-admin (→ /denied), breaking the loop entirely
- /denied page's "sign in with a different account" link now goes to /logout
first, so clicking it clears the session before the login form appears
The login_page auto-redirect for logged-in users is restored, which is
required for the Caddy forward_auth flow (deployed apps redirecting through
/login?next=<app-url>).
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
When a non-admin user with a valid session cookie visited an admin-protected
route, auth_middleware redirected them to /login?next=<admin-path>, and
login_page immediately redirected them back because they were "logged in",
causing an infinite redirect loop.
Fix: only skip the login page when the logged-in user is also an admin.
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Two bugs:
1. verify() built the login URL with `//login` (double slash) — now `/login`
2. safe_path() rejected absolute https:// next-URLs, so after login the
user was silently dropped at `/` instead of their original app URL.
Replaced safe_path with safe_redirect(next, domain) which allows relative
paths OR absolute URLs whose host is the configured domain (or a subdomain).
safe_path is kept as a thin wrapper (domain="") for the admin-UI middleware
where next is always a relative path.
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
Control plane:
- Users and app grants stored in SQLite (users + user_apps tables)
- bcrypt password hashing
- Sessions: HashMap<token, user_id> (in-memory, cleared on restart)
- Bootstrap: first admin auto-created from HIY_ADMIN_USER/HIY_ADMIN_PASS if DB is empty
- /admin/users page: create/delete users, toggle admin, grant/revoke app access
- /api/users + /api/users/:id/apps/:app_id REST endpoints (admin-only)
Deployed apps:
- Every app route now uses Caddy forward_auth pointing at /auth/verify
- /auth/verify checks session cookie + user_apps grant (admins have access to all apps)
- Unauthenticated -> 302 to /login?next=<original URL>
- Authorised but not granted -> /denied page
- Session cookie set with Domain=.DOMAIN_SUFFIX for cross-subdomain auth
Other:
- /denied page for "logged in but not granted" case
- Login page skips re-auth if already logged in
- Cookie uses SameSite=Lax (required for cross-subdomain redirect flows)
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
- New HIY_ADMIN_USER / HIY_ADMIN_PASS env vars control access
- Login page at /login with redirect-after-login support
- Cookie-based sessions (HttpOnly, SameSite=Strict); cleared on restart
- Auth middleware applied to all routes except /webhook/:app_id (HMAC) and /login
- Auth is skipped when credentials are not configured (dev mode, warns at startup)
- Logout link in both dashboard nav bars
- Caddy admin port 2019 no longer published to the host in docker-compose
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH