Skip to content

Troubleshooting

Rod Christiansen edited this page Oct 8, 2026 · 3 revisions

Troubleshooting

Failure modes whose cause is visible in the code, with the message you will actually see.

Every page is empty and API routes return 500

{"error":"API configuration error","details":"API_BASE_URL environment variable not configured"}

API_BASE_URL is unset. The dashboard has no fallback data source. The same condition also silently disables device-identifier resolution in the middleware, so /device/<asset-tag> stops redirecting to the serial.

Confirm with /api/healthz, which reports api_base_url and has_internal_secret.

API routes return 401 and the server log says no auth is configured

[API-AUTH] WARNING: No API_INTERNAL_SECRET or REPORTMATE_PASSPHRASE configured!

reportmate-api authenticates every request. With neither variable set, the proxy sends no credential header and the API rejects it. Set API_INTERNAL_SECRET to the value the API expects. If both are set, API_INTERNAL_SECRET wins — REPORTMATE_PASSPHRASE is never tried as a second attempt.

A fetch in the browser returns {"error":"Unauthorized"}

The session expired. The middleware answers /api/* with JSON 401 rather than redirecting, precisely so client code gets something parseable. Reload the page to re-authenticate.

If this happens on a fresh deployment for every request, NEXTAUTH_SECRET is probably unset or differs between replicas — getToken cannot verify a JWT it did not sign.

Sign-in loops, or the callback goes to the wrong host

NEXTAUTH_URL is unset behind a proxy or CDN. Auth callbacks, the sign-in redirect and the kiosk redirect are all rebuilt from NEXTAUTH_URL (falling back to NEXT_PUBLIC_SITE_URL); without it the app uses the request host, which is the container's internal address. Set it to the public URL and register <public URL>/api/auth/callback/azure-ad with the identity provider.

Every auth route fails at startup

[AUTH] Missing required Entra ID environment variables

AUTH_PROVIDERS includes azure-ad but one of AZURE_AD_CLIENT_ID, AZURE_AD_CLIENT_SECRET, AZURE_AD_TENANT_ID is missing. The provider builder throws at module load, so this presents as the whole auth surface being broken rather than as a sign-in error. /api/test-auth reports which of the three are SET.

A device shows Storage instead of Stale or Missing

The API carries an inventory state from an asset inventory system saying the device is checked in to storage, so it is expected to be silent. The status clears as soon as the inventory system reports the device checked out again.

A user signs in but everything admin is 403

{"error":"Forbidden","detail":"Requires role admin"}

The admin role is not in their token. Roles come from the roles claim and are stamped into the JWT at sign-in, so a newly granted role does not appear until the user signs out and back in.

Anyone with any email address can sign in

ALLOWED_DOMAINS is empty, which the domain check treats as "allow all". This is the default. Set it on any deployment reachable from the internet.

The credentials provider rejects every login

[AUTH] credentials provider is enabled but not implemented; rejecting

That is intentional: authorize returns null unconditionally, so the provider authenticates nobody. Use azure-ad.

The live event feed never connects

The dashboard shows a polling connection state and updates from its 30-second refresh only. Order of causes:

  1. NEXT_PUBLIC_ENABLE_SIGNALR is exactly "false" — the dashboard opts out. It is a build-time value; the SignalRStatus component reads it the other way and wants "true", so set it to "true" rather than leaving it unset.
  2. The session is a kiosk viewer session. /api/v1/negotiate is not on the viewer allow-list, so the call is refused with 403.
  3. /api/v1/negotiate?device=dashboard returned an error (502 {"error":"Negotiate failed"} from the proxy) or no url, so no stream is configured upstream.
  4. The negotiate call took more than 10 seconds and timed out.

The browser must go through /api/v1/negotiate, never the API directly — minting a stream token requires the internal secret, which the browser does not have. A direct call returns 401 every time.

A kiosk display lands on the sign-in page

The token is no longer listed in KIOSK_TOKENS, or it is shorter than 16 characters and was discarded when the list was parsed. Rotating the variable intentionally ends every display session. A listed token should not produce this: the middleware re-mints a lapsed session from the pinned kiosk cookie.

If the display can sign in but pages 403 with:

{"error":"Forbidden","details":"Kiosk sessions are read-only"}

it reached a path outside the viewer allow-list, or used a method other than GET or HEAD.

/live-installs shows nothing

The page fetches /api/live-installs, and no such route handler exists in this repository. There is nothing to configure.

/profiles data is gone

The dashboard's own /api/v1/profiles route returns HTTP 410 with a deprecation notice, even though reportmate-api still serves /api/v1/profiles. The profiles module was folded into management; the data is in the management module of /api/device/<id>.

The Docker build fails on pnpm

The Dockerfile pins pnpm@10.34.5 deliberately. Newer pnpm requires Node 22 or later and fails on the Node 20 base image; pnpm 12 is a standalone binary that ignores the Node constraint and turns ignored build scripts into a hard error. Do not change the pin to latest.

A NEXT_PUBLIC_ change does not take effect in a container

Those values are inlined into the client bundle at build time. Changing the environment variable on a running container does nothing — rebuild the image with the corresponding build argument. See Deployment.

Clone this wiki locally