|
|
@@ -107,7 +107,45 @@ APP_ENV=production
|
|
|
`production` silences verbose PHP errors. Any other value (e.g. `dev`)
|
|
|
turns them on — useful when troubleshooting in a non-public install.
|
|
|
|
|
|
-### 3.5 Local admin fallback (optional)
|
|
|
+### 3.5 Nominating the first administrator (OIDC)
|
|
|
+
|
|
|
+Historically the first user to complete sign-in via *any* path was promoted
|
|
|
+to administrator. On a public-facing first deploy that is a land-grab risk
|
|
|
+— anyone with a valid Entra account in your tenant could win the race
|
|
|
+against the intended operator and lock everyone else out
|
|
|
+(see `doc/REVIEW_01.md` finding R01-N03).
|
|
|
+
|
|
|
+The OIDC sign-in path now auto-promotes only when the signing user matches
|
|
|
+an explicit env-bootstrap value:
|
|
|
+
|
|
|
+```
|
|
|
+BOOTSTRAP_ADMIN_OID=00000000-0000-0000-0000-000000000000
|
|
|
+BOOTSTRAP_ADMIN_EMAIL=admin@example.com
|
|
|
+```
|
|
|
+
|
|
|
+| Variable | What to put there |
|
|
|
+|-------------------------|-------------------|
|
|
|
+| `BOOTSTRAP_ADMIN_OID` | The Entra `oid` claim (a GUID, immutable for the lifetime of the user) — preferred when known. Visible on the user's "Object ID" line in the Entra portal. |
|
|
|
+| `BOOTSTRAP_ADMIN_EMAIL` | The user's primary email. Accepted as a fallback when only the email is on hand. Matched case-insensitively, after trimming. |
|
|
|
+
|
|
|
+Either / both / neither may be set:
|
|
|
+
|
|
|
+- **Both set**: the signing user is promoted on a match against either field.
|
|
|
+- **One set**: only that channel matters.
|
|
|
+- **Neither set**: the OIDC path will *never* auto-promote. In that case
|
|
|
+ bootstrap the first administrator via the local-admin fallback (§3.6) or
|
|
|
+ by setting `users.is_admin = 1` directly in `app.sqlite`.
|
|
|
+
|
|
|
+Auto-promotion additionally requires that no administrator already exists
|
|
|
+(`countAdmins() === 0`). Once a first admin is in place, subsequent
|
|
|
+promotions go through the **Users** page (§5.1). The promotion is recorded
|
|
|
+in the audit log as `BOOTSTRAP_ADMIN` with `via=oidc`.
|
|
|
+
|
|
|
+The local-admin fallback (§3.6) is itself an explicit env-bootstrap and
|
|
|
+does not require the variables above — its `BOOTSTRAP_ADMIN` audit row is
|
|
|
+tagged `via=local`.
|
|
|
+
|
|
|
+### 3.6 Local admin fallback (optional)
|
|
|
|
|
|
```
|
|
|
LOCAL_ADMIN_EMAIL=admin@example.com
|
|
|
@@ -153,11 +191,15 @@ Then:
|
|
|
until they expire (the hash is only consulted at sign-in time).
|
|
|
|
|
|
The local-admin user is recorded in the database as
|
|
|
-`entra_oid = "local:<email>"` with `is_admin = 1`. If the `users` table
|
|
|
-is empty at the moment of the first successful login (OIDC or local),
|
|
|
-that user is auto-promoted to admin and an audit entry of type
|
|
|
-`BOOTSTRAP_ADMIN` is recorded. This is how the very first administrator
|
|
|
-is created — there is no separate "create admin" step.
|
|
|
+`entra_oid = "local:<email>"` with `is_admin = 1`, and is always promoted
|
|
|
+to admin on every successful local sign-in (so a manual demotion in the
|
|
|
+Users page is undone on the next local sign-in). When the `users` table
|
|
|
+was empty before the sign-in, an audit entry of type `BOOTSTRAP_ADMIN`
|
|
|
+with `via=local` is also recorded.
|
|
|
+
|
|
|
+For OIDC-only deployments — where you don't want the local fallback at
|
|
|
+all — leave `LOCAL_ADMIN_EMAIL` / `LOCAL_ADMIN_PASSWORD_HASH` blank and
|
|
|
+nominate the first admin via the OIDC bootstrap (§3.5).
|
|
|
|
|
|
To disable the fallback later, blank both variables and restart.
|
|
|
|
|
|
@@ -311,8 +353,10 @@ rm -rf ./data
|
|
|
docker compose up -d
|
|
|
```
|
|
|
|
|
|
-Migrations run again on the next request and you are back to "no users,
|
|
|
-first sign-in becomes admin".
|
|
|
+Migrations run again on the next request. The first administrator is
|
|
|
+re-seeded as described in §3.5 (OIDC bootstrap) or §3.6 (local-admin
|
|
|
+fallback) — there is no longer an unconditional "first sign-in becomes
|
|
|
+admin" path.
|
|
|
|
|
|
### 5.5 Updating to a new release
|
|
|
|
|
|
@@ -333,7 +377,7 @@ before pulling.
|
|
|
| Symptom | Likely cause | Fix |
|
|
|
|---|---|---|
|
|
|
| `/auth/login` returns "redirect URI mismatch" | The redirect URI registered in Entra does not equal `{APP_BASE_URL}/auth/callback`. | Update either `APP_BASE_URL` in `.env` or the Entra app registration. |
|
|
|
-| `/auth/local` returns 404 | `LOCAL_ADMIN_EMAIL` or `LOCAL_ADMIN_PASSWORD_HASH` is blank. | Set both (see §3.5 for the hash recipe), then `docker compose restart`. |
|
|
|
+| `/auth/local` returns 404 | `LOCAL_ADMIN_EMAIL` or `LOCAL_ADMIN_PASSWORD_HASH` is blank. | Set both (see §3.6 for the hash recipe), then `docker compose restart`. |
|
|
|
| Sign-in succeeds but every page shows "not authorised" | The user has no `is_admin` flag and is trying to access an admin-only page. | Promote them via Users (logged in as another admin). |
|
|
|
| Container restarts in a loop | Most often a malformed `.env` line or a permission problem on `./data`. | `docker compose logs` will show the PHP fatal. Check that `./data` is writable by the `www-data` user inside the container (uid 33 on the Apache image). |
|
|
|
| CSS looks broken / unstyled | The Node build stage was skipped or used a stale layer. | `docker compose build --no-cache` and restart. |
|