Make local login reachable, and write down how it works
Local login is implemented, wired through api, alerting and web, and undiscoverable. No compose file turns it on, the Helm chart sets none of its variables, and no markdown in the repository mentions -seed-admin, LOCAL_AUTH_ENABLED or local login at all. The only way to find it is to read cmd/api/main.go's authorizer switch. Enabling it in docker-compose.yml is not the answer: a plain `docker compose up` has no authentication, and every Phase 0-3 runbook verifies the pipeline with bare curl against /query. Turning login on by default would break the project's own documented verification. So it's an opt-in overlay instead. Four settings have to agree, and only one of them is obviously about login. Each fails differently and none of the failures name the cause: the route 404s, or the browser refuses the request before sending it, or login returns 200 and every later request is anonymous because the cookie was never stored, or the same symptom again from the opposite end because the bundle never attaches it. That is what the new document is mostly for. The Helm chart still has no local-login support. Recorded in the document as a gap rather than papered over. Signed-off-by: John Coffey <[email protected]>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# Local login
|
||||
|
||||
Username and password authentication, served by `api` itself against the
|
||||
control-plane Postgres. It is the alternative to Phase 4's SSO
|
||||
(`enterprise-auth`, OIDC/SAML) for a deployment that wants a login screen
|
||||
without an identity provider behind it.
|
||||
|
||||
The two are mutually exclusive by construction. `cmd/api/main.go` picks
|
||||
one authorizer: `ENTERPRISE_AUTH_URL` wins if it is set, and only
|
||||
otherwise does `LOCAL_AUTH_ENABLED` take effect. With SSO configured,
|
||||
`/auth/*` is never registered and local login does not exist.
|
||||
|
||||
## Off by default, and why
|
||||
|
||||
A plain `docker compose up` has no authentication at all. That is
|
||||
deliberate rather than an oversight: every Phase 0-3 runbook verifies the
|
||||
pipeline with bare `curl` against `/query`, and those steps only work
|
||||
against an unauthenticated API. Turning login on globally would break the
|
||||
project's own documented verification procedure.
|
||||
|
||||
So it is an opt-in overlay:
|
||||
|
||||
```sh
|
||||
docker compose -f docker-compose.yml -f docker-compose.local-auth.yml up -d --build
|
||||
```
|
||||
|
||||
`--build` is not optional. One of the four settings is a Vite build arg
|
||||
baked into the static bundle, so a restart alone leaves the frontend
|
||||
disagreeing with the backend.
|
||||
|
||||
## Creating the first account
|
||||
|
||||
There is no sign-up. The first account is an operator action:
|
||||
|
||||
```sh
|
||||
docker compose -f docker-compose.yml -f docker-compose.local-auth.yml \
|
||||
run --rm api -seed-admin
|
||||
```
|
||||
|
||||
```
|
||||
created default admin user:
|
||||
username: admin
|
||||
password: <generated>
|
||||
this password will not be shown again -- save it now.
|
||||
```
|
||||
|
||||
It creates `admin` as an **owner** with a random password, prints it once,
|
||||
and stores only a bcrypt hash. Losing it means resetting it, not
|
||||
recovering it. The command is idempotent: if the `admin` account already
|
||||
exists it says so and does nothing.
|
||||
|
||||
Then sign in at <http://localhost:3000/login>. Change the password from
|
||||
the account page.
|
||||
|
||||
## The four settings, and why they must agree
|
||||
|
||||
| Setting | Service | What it does |
|
||||
|---|---|---|
|
||||
| `LOCAL_AUTH_ENABLED` | `api`, `alerting` | Registers `/auth/*`, and swaps `WithCORS` for `WithCredentialedCORS` |
|
||||
| `CORS_ALLOWED_ORIGIN` | `api`, `alerting` | Must be a literal origin — the default `*` is refused by browsers for credentialed requests |
|
||||
| `LOCAL_AUTH_COOKIE_SECURE` | `api` | Defaults to `true`; the cookie is not stored over plain `http://localhost` unless this is `false` |
|
||||
| `VITE_LOCAL_AUTH_ENABLED` | `web` (build arg) | Without it the bundle never sends `credentials: 'include'` |
|
||||
|
||||
Only the first is obviously about login, and every one of them fails
|
||||
differently:
|
||||
|
||||
- `LOCAL_AUTH_ENABLED` unset — the login page posts to `/auth/login` and
|
||||
gets a **404**. The route is not registered at all, deliberately, so a
|
||||
deployment with the feature off answers as though it does not exist
|
||||
rather than advertising a disabled feature.
|
||||
- `CORS_ALLOWED_ORIGIN` left at `*` — the browser refuses the request
|
||||
before it is sent, and the page reports a network failure rather than
|
||||
an HTTP status.
|
||||
- `LOCAL_AUTH_COOKIE_SECURE` left at `true` over HTTP — login returns
|
||||
`200` and appears to work, then every subsequent request is
|
||||
unauthenticated, because the cookie was never stored.
|
||||
- `VITE_LOCAL_AUTH_ENABLED` unset — same symptom as the previous one, and
|
||||
from a different cause: the cookie exists but is never attached.
|
||||
|
||||
Set `LOCAL_AUTH_COOKIE_SECURE` back to `true` for anything served over
|
||||
HTTPS, and set `CORS_ALLOWED_ORIGIN` to the real web origin. The values in
|
||||
the overlay assume `http://localhost:3000`.
|
||||
|
||||
## Roles
|
||||
|
||||
`owner`, `admin`, `editor`, `viewer`, from `api/authz`. Owners manage
|
||||
everyone; admins may create and delete `viewer`/`editor` accounts only,
|
||||
and cannot reset an owner's password. Two guards prevent a deployment
|
||||
from becoming unadministrable: the last owner can be neither deleted nor
|
||||
demoted, and no account can delete itself while signed in.
|
||||
|
||||
## Not covered by the Helm chart
|
||||
|
||||
`deploy/helm/cairnobs` has no local-login support — it sets none of these
|
||||
variables. A Kubernetes deployment authenticates via `enterprise-auth`
|
||||
SSO or not at all. This is a gap, not a decision.
|
||||
Reference in New Issue
Block a user