Repository navigation
Troubleshooting
Failure modes whose cause is visible in the code, with the message you will actually see.
{"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-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.
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.
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.
[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.
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.
{"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.
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.
[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 dashboard shows a polling connection state and updates from its 30-second
refresh only. Order of causes:
-
NEXT_PUBLIC_ENABLE_SIGNALRis exactly"false"— the dashboard opts out. It is a build-time value; theSignalRStatuscomponent reads it the other way and wants"true", so set it to"true"rather than leaving it unset. - The session is a kiosk viewer session.
/api/v1/negotiateis not on the viewer allow-list, so the call is refused with 403. -
/api/v1/negotiate?device=dashboardreturned an error (502{"error":"Negotiate failed"}from the proxy) or nourl, so no stream is configured upstream. - 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.
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.
The page fetches /api/live-installs, and no such route handler exists in this
repository. There is nothing to configure.
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 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.
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.
AGPL-3.0 · reportmate.app