This release fixes what was broken instead of adding features. Nothing here is
new functionality; it makes what already existed trustworthy.
If you self-host OpenATS, upgrade. Socket.IO was completely
unauthenticated, so anyone who could reach your server could read every hiring
team's chat and post messages as any user.
Security
- Socket.IO connections now require a valid Asgardeo JWT, verified in the
handshake before any handler runs. Previously the server accepted any
connection withcors: "*", and the client supplied its ownsenderId, so an
anonymous client could write chat messages impersonating a real user. The
sender is now taken from the verified token. - Room joins are authorized, not just authenticated.
join_joband
join_candidateare checked against hiring-team membership, and the chat write
handlers require the socket to already be in the room, so a client cannot skip
the join and write to a job it has no access to. - Chat history over HTTP is gated the same way.
GET /chat/job/:jobIdand
/chat/candidate/:candidateIdpreviously returned any conversation to any
logged-in user. - Dashboard broadcasts are scoped to a room. Candidate stage moves, offers
and interviews were sent to every connected client with a bareio.emit(). - Reconnects survive token expiry. The socket token was read once when the
dashboard rendered; if it expired while a tab was open, realtime silently
stopped until a page refresh. - The authenticated API is rate limited, keyed by user id rather than IP so
an office behind one NAT does not share a budget. Tunable with
RATE_LIMIT_APIandRATE_LIMIT_EXPENSIVE.
Fixes
- A partial offer edit wiped the offer's start date. Any
PATCH /api/offers/:idthat did not itself mentionstartDateerased it,
after which the offer could not be sent and failed with "Missing required
fields: startDate". Editing benefits or the reporting manager on a draft offer
was enough to trigger it. - Assessment results could never show a text answer. The frontend declared
question types that do not exist in the database (single_choice,text), so
the branch that renders short and long answers was unreachable. - The interview stage-type indicator never rendered, because the type used
by the candidate page was missing five fields the API actually returns. - Eleven
catchblocks returned HTTP 500 while discarding the underlying error,
making those failures uninvestigable. They now log the cause. - Job updates stringified salary values while creation passed numbers, which
disagreed with the schema.
Testing
The project had 31 tests and no coverage of the things that had actually broken
in production. It now has 83.
- Authentication is tested — expired, wrong-issuer and wrongly-signed
tokens, the claim checks, JIT user provisioning, deactivated accounts, and the
auth middleware. Only the JWKS fetch is faked, so signature,expandiss
are genuinely verified. This includes a regression test for the v0.4.0 login
outage, where a changed Asgardeosubbroke every login. - The core hiring flow is tested end to end through the real HTTP API:
apply, duplicate application, stage move, interview scheduling, and the offer
draft-to-send path. Writing these found the offerstartDatebug above. - Frontend tests exist for the first time, using Vitest with jsdom and
Testing Library. - Coverage reporting via
pnpm test:coverage.
Tooling
- The backend has ESLint, passes with zero problems, and
no-explicit-anyis
enforced as an error — all 108 uses were removed rather than suppressed. - The frontend's 112 lint errors are fixed, so
pnpm lintat the repo root now
passes and can become a CI gate. - CI gates backend lint and type-checks the backend test files, which had
silently been checking nothing. - The worker validates its environment on boot, matching the API.
- Added
CHANGELOG.md.
Upgrade notes
- No database migrations in this release.
- New optional environment variables:
RATE_LIMIT_API(default 1000) and
RATE_LIMIT_EXPENSIVE(default 60), both per 15-minute window. The defaults
are deliberately generous; you do not need to set them. - The frontend must be redeployed alongside the backend. Sockets now require
a token, and the client fetches one from a new/api/socket-tokenroute. An
old frontend against a new backend gets no realtime updates.
Known issues
54 dependency advisories are open (15 high), almost all reached through
next@16.1.6. The upgrade is held until immediately before v1.0.0 so the
version bump is as fresh as possible at GA.
Full Changelog: v0.4.0...v0.5.0