--- title: "Grav login new-user grants api.* but not admin.* on Admin2-only installs" date: 2026-07-04 category: docs/solutions/test-failures module: testing / account provisioning problem_type: test_failure component: authentication symptoms: - "gpx-manager Playwright specs fail (401 / login form shown) after switching the suite onto a dedicated test account" - "an authenticated account still sees the login form at /gpx-manager instead of the manager UI" - "the generated accounts/*.yaml has an access.api block but no access.admin block" root_cause: missing_permission resolution_type: tooling_addition severity: medium related_components: - testing_framework - tooling tags: - grav - login-plugin - admin2 - permissions - playwright - test-account - gpx-manager --- # Grav login new-user grants api.* but not admin.* on Admin2-only installs ## Problem When the Playwright suite was moved onto a dedicated local `testrunner` account, every `/gpx-manager` spec started failing — the account could authenticate but was treated as unauthorized for the manager page. The account had been created with `bin/plugin login new-user ... -P b` (Admin + Site access) but **without** `--admin-type`, and on this Admin2-only install that grants `api.*` permissions and no `admin.*` permissions. ## Symptoms - The `/gpx-manager` Playwright specs fail after switching from the real user to the `testrunner` account (they passed as the real user). - An authenticated `testrunner` still gets the Login plugin's login form at `/gpx-manager` instead of the manager UI. - The generated `user/accounts/testrunner.yaml` contains an `access.api` block (`login: true`, `super: true`) but **no** `access.admin` block. ## What Didn't Work - **Assuming `-P b` was enough.** `-P/--permissions b` selects the *category* of access (Admin + Site), but the *type* of admin permission — classic `admin.*` vs Admin2 `api.*` — is a separate axis controlled by `--admin-type`, which defaults to auto-detect. `-P b` alone does not guarantee `admin.login`. - **Blaming the wrong specs.** In the same push, the home `H1`/map specs were also red, which looked like it might be the same auth problem. It was not — those were gated by `site.yaml` `travelling: false` hiding the active-trip view, a completely separate cause. Conflating the two delayed pinning the permission root cause. ## Solution Create the account with an explicit `--admin-type both`, and bake it into the idempotent `make test-account` target so every recreation is faithful: ```make test-account: @docker exec intotheeast_grav sh -c 'test -f /var/www/html/user/accounts/$(GRAV_TEST_USER).yaml \ || php bin/plugin login new-user -u $(GRAV_TEST_USER) -p "$(GRAV_TEST_PASS)" \ -e $(GRAV_TEST_USER)@example.test -N "Test Runner" -P b --admin-type both -s enabled -n' ``` Verify the resulting permissions actually include `admin.login`: ```bash docker exec intotheeast_grav rm -f /var/www/html/user/accounts/testrunner.yaml make test-account docker exec intotheeast_grav sh -c 'grep -A6 "^access:" /var/www/html/user/accounts/testrunner.yaml' # access: # admin: # login: true # super: true # api: # login: true # super: true ``` ## Why This Works The `login new-user` help text spells out the axis: > `--admin-type` — Which admin permission type to grant when permissions include Admin: `admin` (classic Admin plugin, `admin.*`), `api` (Admin2, `api.*`), or `both`. **If omitted, auto-detects from which admin plugin is installed.** This site runs **Admin2 only** (the classic `admin` plugin is disabled), so auto-detect resolves to `api` and emits `api.*` alone. `/gpx-manager` is gated by `access.admin.login: true` in its page frontmatter (enforced by the Login plugin), and that check looks specifically for the `admin.login` permission — `api.login` does not satisfy it. Passing `--admin-type both` forces both namespaces into the account, so the admin-gated page accepts the session. ## Prevention - **On Admin2-only Grav installs, always pass `--admin-type both` (or `admin`) to `login new-user`** when the account must reach any page gated by `access.admin.login` (e.g. `/gpx-manager`). Auto-detect will otherwise silently give you api-only. - **Assert the permission, not the exit code.** After provisioning an account for admin-gated pages, check that `access.admin.login` exists in the generated YAML rather than trusting that account creation "succeeded." - **Keep provisioning in one idempotent place.** The `make test-account` target is the single source of truth; `tests/global-setup.js` calls it, so `make test` and a bare `npx playwright test` both get identical permissions. Don't hand-create the account out-of-band with different flags — that reintroduces the drift this fix removed. ## Related Issues - `docs/working/plans/2026-07-04-grav-2.0.4-upgrade.md` — the self-contained test-account infrastructure shipped alongside the Grav 2.0.4 upgrade. - Sibling gotchas from the same 2026-07-04 Grav 2.0.4 production cutover (all surface around admin2/api but with distinct root causes): `docs/solutions/integration-issues/stale-grav-version-blocks-api-plugin-install.md` (stale `GRAV_VERSION` → rc core → GPM won't serve the `api` plugin → login 404s) and `docs/solutions/integration-issues/grav-double-content-encoding-garbage-page.md` (double `Content-Encoding` header → garbage page). - `docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md` — accounts live in the `user/` repo; the `testrunner` account is gitignored so it never reaches production. - GPX manager auth model (`access.admin.login: true` frontmatter + Login plugin) — see the project's GPX manager notes.