Repository navigation
fix(http-server): validate types and lengths for join and create quiz input - #234
Merged
TheCodeHeist-Coder merged 1 commit intoNov 5, 2026
Conversation
code, username, title and description were only checked for being truthy, so an object, array or number got through to prisma and came back as a 500. A request with no body was a 500 too. parseJoinQuiz and parseCreateQuiz in utils/quizInput.ts now check the type and length (username 30 after trim, title 120, description 2000) and the controllers answer with errorResponse 400. Closes TheCodeHeist-Coder#231
TheCodeHeist-Coder
approved these changes
Nov 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
joinQuizControllerandcreateQuizControlleronly checked that the fields were truthy, so an object, array or number went straight into prisma and came back as a generic 500. There was also no length limit. Now both controllers check the body first and answer througherrorResponse(res, 400, ...), same setup asparseCreateQuestionandparseRegisterInput.apps/http-server/src/utils/quizInput.tswithparseJoinQuizandparseCreateQuiz(zod, one message for the 400):code: stringusername: string, trimmed, 1 to 30 characterstitle: string, 1 to 120 charactersdescription: string, 1 to 2000 charactersreq.bodyat all, which was a 500 from the destructure on both routes. That is a 400 now too (same handling asparseRegisterInput).Messages:
errornullor blankCode and username are required/Title and description are required(same text as before)username must be a string,code must be a string,title must be a string,description must be a stringusername must be at most 30 characters,title must be at most 120 characters,description must be at most 2000 charactersRelated issue
Closes #231
Type of change
Which services does this touch?
apps/frontendapps/http-serverapps/ws-serverapps/genAIpackages/db(schema or Prisma changes)How did you test this?
Baseline is clean
main(19334fe). Node 24 here (CI uses 22). I generated the Prisma client first withpnpm --filter @repo/db run db:generate.In
apps/http-server:pnpm test(vitest)pnpm check-typespnpm build(tsc -b)From the repo root:
pnpm run check-typespnpm run buildpnpm run lintfrontend#lintonly, 20 problems (18 errors, 2 warnings)For the root build I set
VITE_ADMIN_API_URLon both sides, the vite config inapps/adminthrows without it. The frontend lint errors are the known ones fromCI-CD.md, this PR doesn't touch the frontend.Tests:
utils/quizInput.test.ts: object, array, number and boolean for every field, empty, blank andnull, the limits (30, 120, 2000 pass, 31, 121, 2001 fail), trim happens before the length check, and a missing or non-object body.controllers/createQuiz.test.ts: 400 for the bad bodies andprisma.quiz.createis not called, a valid quiz still gives 201.controllers/sessionController.test.tsextended: same bad-body cases,findUniqueand$transactionare not called, and the participant is created with the trimmed username.As a sanity check I put the old controllers back with the new tests and 18 of them fail. With the new controllers all 146 pass.
I also started the built server with a
DATABASE_URLthat points at nothing and sent real requests with curl:code: {}/["ABC123"]PrismaClientValidationErrorin the logcode must be a stringusername: {}/["ada"]/42username must be a stringusernameusername must be at most 30 charactersTypeErroron the destructureCode and username are requiredtitle: {}/["t"]PrismaClientValidationErrorin the logtitle must be a stringdescription: 42PrismaClientValidationErrorin the logdescription must be a stringtitle/ 2001 characterdescriptionTypeErroron the destructureTitle and description are requiredWith no database the join
usernamerows on main stop atfindUniquewith a connection error, not at the username, and the too long rows on main only fail because there is no database. So those 500s don't prove anything on their own. For the username I calledprisma.participant.createdirectly with{},["ada"]and42, and all three throwPrismaClientValidationErrorbefore any connection is made, which matches what the issue describes. On this branch a valid join and a valid quiz get past the check and only fail on the missing database.I did not run this against a real Postgres or with Docker, and I did not try it in the browser. The valid paths (join storing the trimmed username, quiz create) are covered with mocks only.
Checklist
pnpm run lintpnpm run check-typespnpm run builddocker-compose up.envfile, API key or other secretLint is unticked because the root command fails on the frontend errors above, same as main.
docker-compose upwas not run.Notes
routes/quiz.tshas no PUT or PATCH for a quiz, onlyPUT /:quizId/questions/:questionId, which already goes throughparseCreateQuestion. The other quiz writes in the repo (generateAccessCodeController,endQuizSession.tsinws-server,routes/sessions.tsinadmin-server) don't writetitleordescription. So there was nothing else to cover.JoinQuiz.tsxshows it). The new messages follow the<field> must be ...wording fromregisterInput.tsandquestionInput.ts.parseCreateQuestion.username,titleordescriptionused to be accepted and is now a 400, and a whitespace-onlycodewas a 404 and is now a 400.usernameis stored trimmed.JoinQuiz.tsxalready trims, so nothing changes for the app there.titleanddescriptionare returned as sent, not trimmed, likeparseRegisterInputdoes, so their limits count any surrounding spaces. The issue only asked for the trim onusername.JoinQuiz.tsxshows the server message, so a long nickname gets the real reason.Dashboard.tsxshows a generic "Failed to create the quiz" for any failure, so someone with a 121 character title won't see why. Neither form has amaxLength. I can add those in a follow-up if you want.