2026.5.1 — sign-in fix
A fix release for 2026.5.0. Headline: sign-in actually works now.
Two mismatches introduced by the Better Auth dependency bump broke OIDC sign-in on a fresh 2026.5.0 install — every attempt ended in a 500 or a Keycloak invalid_redirect_uri. If you're on 2026.5.0, upgrade.
⚠️ Upgrading from 2026.5.0
- Run migrations. New migration
0005renames the Better Auth columns to camelCase. The Helm pre-upgrade Job / the composemigrateservice /pnpm migrateapply it automatically; it preserves data, indexes, and foreign keys. - Update your Keycloak (OIDC) client redirect URI to
<BASE_URL>/api/auth/oauth2/callback/keycloak— note the new/oauth2/segment. The old/api/auth/callback/keycloakno longer matches what StateBoard sends. (The bundled dev realm is already fixed; bring-your-own-IdP deployments must update this themselves.)
Fixed — sign-in (broken in 2026.5.0)
- Better Auth columns match the library again. They were created snake_case while Better Auth queries camelCase (
expiresAt,emailVerified,userId, …), so sign-in500'd withcolumn "expiresAt" of relation "verification" does not exist. Migration0005renames them. - OIDC callback path corrected to
/api/auth/oauth2/callback/keycloak(Better Auth's genericOAuth path). The bundled realm and all setup docs had registered/api/auth/callback/keycloak, which Keycloak rejected. pnpm migratenow loads.env, socp .env.example .env && pnpm migratecreates the schema instead of failing with "DATABASE_URL required".
Fixed — deployment
- Base64 DB passwords (e.g. from
openssl rand -base64) no longer breakDATABASE_URLparsing; the parse-error log masks the password.
Hardened
- Free-text fields (name / description / label / notes) and JSON request bodies are length-capped on every write. The uploads route sends
X-Content-Type-Options: nosniffand turns a truncated image into a clean400instead of a500. The region box invariant (x+w,y+h≤ 1) is enforced on PATCH as well as POST.
Editor & accessibility
- Failed saves now surface an error instead of silently doing nothing.
- The editor's "open share view" icon button has an accessible name.
Under the hood
- A zero-dependency
node:testsuite now covers the coordinate/validation logic, the example board's invariants, the role-rank authorization check, and theDATABASE_URLredaction. CI runs on Node 26 to match the runtime image.
Container image
ghcr.io/saschb2b/stateboard:2026.5.1 # also :2026.5 and :latest
Full changelog: v2026.5.0...v2026.5.1