Skip to content

fix(admin): add zod input validation for /api/admin/users route - #1005

Merged
greatest0fallt1me merged 3 commits into
CalloraOrg:mainfrom
therealbibson:fix/zod-validation-admin
Jul 29, 2026
Merged

fix(admin): add zod input validation for /api/admin/users route#1005
greatest0fallt1me merged 3 commits into
CalloraOrg:mainfrom
therealbibson:fix/zod-validation-admin

Conversation

@therealbibson

@therealbibson therealbibson commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Overview

This pull request introduces Zod-based input validation for the GET /api/admin/users endpoint within the admin routes. Prior to this change, the /users route relied solely on a custom parsePagination() utility for query-parameter parsing, which performed inline validation that threw generic ValidationError instances. While functional, this approach lacked a formal, declarative validation boundary at the HTTP layer -- making it inconsistent with the rest of the admin router, which uses the validate() middleware with pre-defined Zod schemas.

The fix adds the existing usersQuerySchema (already defined in src/validators/admin.ts but never wired to the route) as the validation gate for query parameters. Invalid limit, offset, or page values are now caught early with a structured VALIDATION_ERROR envelope, complete with field-level detail arrays, before the request ever reaches the route handler.

Additionally, the usersQuerySchema was extended with a page field to reflect the full set of pagination parameters that parsePagination() supports -- ensuring the schema accurately documents the expected API surface.

Related Issue

Closes #872 -- Add zod input validation for /api/admin [b#007]

Changes

Modified Files

src/routes/admin.ts

  • Imported usersQuerySchema from ../validators/admin.js
  • Added validate({ query: usersQuerySchema }) as middleware to the GET /users route handler, sitting between the adminLogMiddleware and the handler callback
  • This ensures that any request with malformed pagination parameters (negative integers, non-numeric strings, floating-point values, etc.) receives an HTTP 400 response with the canonical VALIDATION_ERROR error code and a details array specifying which field failed and why
  • The route handler itself remains unchanged -- parsePagination() continues to process the validated query as before, so existing behaviour for valid inputs is fully preserved

src/validators/admin.ts

  • Extended usersQuerySchema with a new page field:
    • Type: z.string().optional()
    • Validated via .refine() requiring the string to match regex for digits with Number(v) >= 1
    • Error message: "page must be a positive integer"
  • This mirrors the page to offset conversion logic inside parsePagination(), so callers who use ?page=2 (instead of ?offset=25) also get proper validation

What This Fixes

Previously, sending GET /api/admin/users?limit=-5 would reach the route handler, where parsePagination() would throw a ValidationError that bubbled up through the error-handling middleware. Now, the same request is rejected at the validation boundary with a clean 400 response before any business logic executes -- consistent with how every other admin route behaves.

Testing and Verification

Unit Tests

  • All existing tests in src/validators/admin.test.ts continue to pass -- the new page field follows the exact same validation pattern as limit and offset
  • The validate() middleware integration is already thoroughly tested by the suites for /maintenance/banner, /db/explain, /usage/anomalies, and /usage/export in the same test file

Manual Verification Scenarios

Scenario Input Expected Result
Valid pagination ?limit=50&offset=0 HTTP 200, handler processes normally
Valid page param ?page=2 HTTP 200 (page converted to offset internally)
Negative limit ?limit=-5 HTTP 400, VALIDATION_ERROR
Zero limit ?limit=0 HTTP 400, VALIDATION_ERROR (must be positive)
Non-numeric limit ?limit=abc HTTP 400, VALIDATION_ERROR
Negative offset ?offset=-1 HTTP 400, VALIDATION_ERROR
Zero page (invalid) ?page=0 HTTP 400, VALIDATION_ERROR (page must be >= 1)
Fractional limit ?limit=1.5 HTTP 400, VALIDATION_ERROR

Error Envelope Shape

On validation failure, clients receive a JSON response with:

  • success: false
  • error.code: "VALIDATION_ERROR"
  • error.message: "Request validation failed"
  • error.details: array of { field, message, code } objects
  • requestId: unique request identifier
  • timestamp: ISO-8601 timestamp

Acceptance Criteria

Criteria Status
Invalid pagination params return structured HTTP 400 with VALIDATION_ERROR code Yes
Error details include field, message, and code per invalid parameter Yes
Valid queries continue to reach the handler and produce 200 responses Yes
The page parameter is validated alongside limit and offset Yes
No breaking changes to the API response shape or behaviour Yes
Follows existing validation patterns used by other admin routes Yes

Documentation

  • The usersQuerySchema JSDoc block was already present in src/validators/admin.ts
  • No API surface changes were introduced -- the route, response shape, and query-parameter names are identical to before
  • The only change is that invalid requests now receive a structured error response instead of reaching the handler

Security Considerations

  • Input validation at the HTTP boundary prevents malformed pagination values from propagating into business logic
  • The Zod schema uses allow-listing (only known fields are accepted) -- extra query parameters are silently stripped during validation
  • Error messages are informative but do not leak internal implementation details

This PR addresses issue #872 as part of the GrantFox FWC26 campaign. All changes adhere to the repository's lint rules, code style, and testing conventions.

Adds validate middleware with usersQuerySchema to the GET /api/admin/users endpoint for structured 400 error responses on invalid pagination params. Also adds 'page' field to usersQuerySchema since parsePagination supports it alongside limit/offset. Closes CalloraOrg#872
@drips-wave

drips-wave Bot commented Jul 28, 2026

Copy link
Copy Markdown

@therealbibson Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@greatest0fallt1me

Copy link
Copy Markdown
Contributor

Merged into main via admin resolver (-X theirs).

@greatest0fallt1me
greatest0fallt1me merged commit 3eb9230 into CalloraOrg:main Jul 29, 2026
1 check passed
@greatest0fallt1me

Copy link
Copy Markdown
Contributor

LGTM 🎉 merging now.

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.

Add zod input validation for /api/admin [b#007]

2 participants