Skip to content

Pass the Keycloak client ID to next build (WEB-031) - #266

Merged
LunaUrsa merged 2 commits into
uatfrom
fix/WEB-031-keycloak-build-args
Nov 8, 2026
Merged

LunaUrsa merged 2 commits into
uatfrom
fix/WEB-031-keycloak-build-args

Conversation

@LunaUrsa

@LunaUrsa LunaUrsa commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Problem

Ban-appeal login at /appeal fails after the user signs in. TripBot logs "Keycloak token error" and POST /api/v2/keycloak/token returns 500.

Keycloak binds an auth code to the client that requested it. TripBot redeems the code as the ban-appeals client (KEYCLOAK_BAN_APPEALS_CLIENT_ID). The browser asked for the code as website.

Next.js writes NEXT_PUBLIC_* values into the browser bundle during next build. The Dockerfile never declared them, so next build saw none. The live bundle on tripsit.me still reads the value at runtime from the browser's empty process shim, so it always uses the fallback:

clientId:s.default.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID||"website"

Changes

  • Dockerfile: the build stage declares NEXT_PUBLIC_DNS_DOMAIN, NEXT_PUBLIC_KEYCLOAK_URL and NEXT_PUBLIC_KEYCLOAK_CLIENT_ID as ARG and ENV before RUN npx next build, so compose build.args and --build-arg reach the build. An unset arg becomes an empty string, and the code's || fallbacks still apply.
  • src/utils/keycloak.ts: the comment said config is read "at runtime instead of build time". That's wrong for client code. It now explains that the values are fixed when the bundle is built.
  • Hardening (keycloak.ts): I removed the "website" fallback. That client is always wrong here, because TripBot redeems every code as the appeals client. I didn't hardcode the appeals client ID: this repo doesn't contain it, and the build arg is the source of truth.
    • Outside production (next dev, Jest): a missing client ID throws an error that names the variable.
    • In production: a missing client ID is logged with console.error, and the login URL gets an empty client_id. Keycloak then rejects the request before the user signs in, instead of TripBot failing afterwards with a 500.
    • I didn't make next build itself fail, because the GitHub Actions builds run without these variables.
    • getPostLogoutUrl() doesn't need a client ID, so logout isn't affected.
  • Tests: src/utils/__tests__/keycloak.test.ts covers the login URL, the URL override and both missing-ID cases.
  • .env.example: documents NEXT_PUBLIC_KEYCLOAK_CLIENT_ID for local development.
  • REVIEW-ISSUES.md: adds WEB-031 for this bug and lists it in the Auth and Docker/Drone overlap groups.

Needs changes outside this repo

  • UAT: .drone.yml runs docker compose -f docker-compose.website-prod.yml, but that file isn't in this repo. It lives on the UAT server (see WEB-014), so I couldn't check or change it. Its build: block needs:
    args:
      NEXT_PUBLIC_DNS_DOMAIN: ${NEXT_PUBLIC_DNS_DOMAIN}
      NEXT_PUBLIC_KEYCLOAK_URL: ${NEXT_PUBLIC_KEYCLOAK_URL}
      NEXT_PUBLIC_KEYCLOAK_CLIENT_ID: ${NEXT_PUBLIC_KEYCLOAK_CLIENT_ID}
    The Drone step already sets these three variables for the compose run. Without build.args, UAT builds this branch with an empty client ID: the browser console shows the missing-ID error, and Keycloak rejects the login. Before this change, UAT used website instead, which failed too.
  • Production: the server rebuilds only from the current main of this repo. The live compose change (build.args in docker-compose.website.yml) does nothing until this commit is promoted from uat to main.

Verification

  • npm run lint: 0 errors. The 2 warnings were already there (WEB-025).
  • npm test: 41 of 41 pass, including the 4 new tests.
  • Docker build with the arg: docker build --target production --build-arg NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=placeholder-appeals-client . succeeds, including next build's TypeScript check.
    • The login URL builder sits in /appeal's page chunk, next to openid-connect/auth. It now reads client_id:"placeholder-appeals-client" and url:"https://auth.tripsit.me", both literals.
    • No client chunk contains env.NEXT_PUBLIC_, so nothing reads these values at runtime. "website" no longer appears.
    • I ran the image: GET /appeal returned 200, and the chunk the page loads contains the literal.
  • Docker build without the arg: succeeds. The builder contains the console.error(...) branch and sends an empty client_id, not website.
  • Not verified:
    • I didn't do a real login at /appeal, because that needs a test account and a deployed build with the real client ID.
    • I used a placeholder client ID, not the real appeals client ID.
    • I couldn't check the UAT build, because its compose file is on the server.

🤖 Generated with Claude Code

LunaUrsa and others added 2 commits November 8, 2026 14:08
The ban-appeal login failed after sign-in: the browser asked Keycloak for a
code as the "website" client, but TripBot redeems it as the ban-appeals
client, and Keycloak binds a code to the client that requested it.

Next.js writes NEXT_PUBLIC_* values into the browser bundle during
next build. The Dockerfile never declared them, so the build saw none and
the bundle always fell back to "website". Declare NEXT_PUBLIC_DNS_DOMAIN,
NEXT_PUBLIC_KEYCLOAK_URL and NEXT_PUBLIC_KEYCLOAK_CLIENT_ID as ARG/ENV in
the build stage so compose build.args reach it.

keycloak.ts said its config was read "at runtime instead of build time",
which is wrong for client code; the comment now says how it works. The
"website" fallback is gone: a missing client ID throws outside
production, and in production it is logged and Keycloak rejects the
request before the user signs in, instead of TripBot failing afterwards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant