Procházet zdrojové kódy

Merge pull request #28380 from overleaf/td-zod-history

Migrate history-related web back end to Zod

GitOrigin-RevId: 38ed56927bee4f5670604d7178b096e382c8cb65
Tim Down před 11 měsíci
rodič
revize
d2f5cd4e46

+ 35 - 5
services/web/app/src/Features/History/HistoryController.mjs

@@ -25,6 +25,7 @@ import ProjectEntityUpdateHandler from '../Project/ProjectEntityUpdateHandler.js
 import RestoreManager from './RestoreManager.mjs'
 import { prepareZipAttachment } from '../../infrastructure/Response.js'
 import Features from '../../infrastructure/Features.js'
+import { z, zz, validateReq } from '../../infrastructure/Validation.js'
 
 // Number of seconds after which the browser should send a request to revalidate
 // blobs
@@ -44,8 +45,19 @@ async function headBlob(req, res) {
   await requestBlob('HEAD', req, res)
 }
 
+const requestBlobSchema = z.object({
+  params: z.object({
+    project_id: zz.coercedObjectId(),
+    hash: zz.hex().length(40),
+  }),
+  query: z.object({
+    fallback: zz.coercedObjectId().optional(),
+  }),
+})
+
 async function requestBlob(method, req, res) {
-  const { project_id: projectId, hash } = req.params
+  const { params } = validateReq(req, requestBlobSchema)
+  const { project_id: projectId, hash } = params
 
   // Handle conditional GET request
   if (req.get('If-None-Match') === hash) {
@@ -461,17 +473,35 @@ async function _pipeHistoryZipToResponse(v1ProjectId, version, name, req, res) {
   }
 }
 
+const getLatestHistorySchema = z.object({
+  params: z.object({
+    project_id: zz.objectId(),
+  }),
+})
+
 async function getLatestHistory(req, res, next) {
-  const projectId = req.params.project_id
+  const { params } = validateReq(req, getLatestHistorySchema)
+  const projectId = params.project_id
   const history = await HistoryManager.promises.getLatestHistory(projectId)
   res.json(history)
 }
 
+const getChangesSchema = z.object({
+  params: z.object({
+    project_id: zz.objectId(),
+  }),
+  query: z.object({
+    since: z.coerce.number().int().min(0).optional(),
+    paginated: z.stringbool().optional(),
+  }),
+})
+
 async function getChanges(req, res, next) {
-  const projectId = req.params.project_id
-  let since = req.query.since
+  const { params, query } = validateReq(req, getChangesSchema)
+  const projectId = params.project_id
+  let since = query.since
   // TODO: Transition flag; remove after a while
-  const paginated = req.query.paginated === 'true'
+  const paginated = query.paginated
 
   if (paginated) {
     const changes = await HistoryManager.promises.getChanges(projectId, {

+ 2 - 2
services/web/app/src/Features/History/HistoryManager.js

@@ -280,8 +280,8 @@ async function getLatestHistory(projectId) {
  * Get history changes since a given version
  *
  * @param {string} projectId
- * @param {object} opts
- * @param {number} opts.since - The start version of changes to get
+ * @param {object} [opts]
+ * @param {number} [opts.since] - The start version of changes to get
  */
 async function getChanges(projectId, opts = {}) {
   const historyId = await getHistoryId(projectId)

+ 0 - 32
services/web/app/src/Features/History/HistoryRouter.mjs

@@ -1,7 +1,6 @@
 // @ts-check
 
 import Settings from '@overleaf/settings'
-import { Joi, validate } from '../../infrastructure/Validation.js'
 import { RateLimiter } from '../../infrastructure/RateLimiter.js'
 import AuthenticationController from '../Authentication/AuthenticationController.js'
 import AuthorizationMiddleware from '../Authorization/AuthorizationMiddleware.mjs'
@@ -29,30 +28,12 @@ function apply(webRouter, privateApiRouter) {
 
   webRouter.head(
     '/project/:project_id/blob/:hash',
-    validate({
-      params: Joi.object({
-        project_id: Joi.objectId().required(),
-        hash: Joi.string().required().hex().length(40),
-      }),
-      query: Joi.object({
-        fallback: Joi.objectId().optional(),
-      }),
-    }),
     RateLimiterMiddleware.rateLimit(rateLimiters.getProjectBlob),
     AuthorizationMiddleware.ensureUserCanReadProject,
     HistoryController.headBlob
   )
   webRouter.get(
     '/project/:project_id/blob/:hash',
-    validate({
-      params: Joi.object({
-        project_id: Joi.objectId().required(),
-        hash: Joi.string().required().hex().length(40),
-      }),
-      query: Joi.object({
-        fallback: Joi.objectId().optional(),
-      }),
-    }),
     RateLimiterMiddleware.rateLimit(rateLimiters.getProjectBlob),
     AuthorizationMiddleware.ensureUserCanReadProject,
     HistoryController.getBlob
@@ -151,25 +132,12 @@ function apply(webRouter, privateApiRouter) {
 
   webRouter.get(
     '/project/:project_id/latest/history',
-    validate({
-      params: Joi.object({
-        project_id: Joi.objectId().required(),
-      }),
-    }),
     AuthorizationMiddleware.blockRestrictedUserFromProject,
     AuthorizationMiddleware.ensureUserCanReadProject,
     HistoryController.getLatestHistory
   )
   webRouter.get(
     '/project/:project_id/changes',
-    validate({
-      params: Joi.object({
-        project_id: Joi.objectId().required(),
-      }),
-      query: Joi.object({
-        since: Joi.number().integer().min(0).optional(),
-      }),
-    }),
     AuthorizationMiddleware.blockRestrictedUserFromProject,
     AuthorizationMiddleware.ensureUserCanReadProject,
     HistoryController.getChanges

+ 1 - 0
services/web/app/src/infrastructure/Validation.js

@@ -48,6 +48,7 @@ const zz = {
       .string()
       .refine(ObjectId.isValid, { message: 'invalid Mongo ObjectId' })
       .transform(val => new ObjectId(val)),
+  hex: () => z.string().regex(/^[0-9a-f]*$/),
 }
 
 /**