mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
[web] consolidate clsi downloads and add zod validation (#33069)
* [web] consolidate clsi downloads and add zod validation * [validation-tools] make prettier happy * [web] make clsiServerId optional * [web] fix type of buildId * [web] gracefully handle ObjectId * [web] fix type of buildId * [monorepo] address review feedback - cjs export - update module path in comments - skip adding ?clsiserverid if not set - allow nested output file download for submissions and add tests * [web] address review feedback * [web] cache one more zod schema * [web] fix unit tests GitOrigin-RevId: 0a1e618955983e035defd6d3c0528b81e0e85c95
This commit is contained in:
@@ -217,4 +217,121 @@ describe('zodHelpers', () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
describe('buildId', () => {
|
||||
it('fails to parse when provided with an invalid buildId', () => {
|
||||
const parsed = zz.buildId().safeParse('aa')
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'invalid buildId',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('parses successfully when provided with a valid buildId', () => {
|
||||
const parsed = zz.buildId().safeParse('19d6c341530-878fff6cdab7fb0c')
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.data).toBe('19d6c341530-878fff6cdab7fb0c')
|
||||
})
|
||||
|
||||
it('fails to parse when provided with an editorBuildId', () => {
|
||||
const parsed = zz
|
||||
.buildId()
|
||||
.safeParse(
|
||||
'03b1d773-6203-4669-b365-6a0aa5625878-19d6c341530-878fff6cdab7fb0c'
|
||||
)
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'invalid buildId',
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('editorBuildId', () => {
|
||||
it('fails to parse when provided with an invalid buildId', () => {
|
||||
const parsed = zz.editorBuildId().safeParse('aa')
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'invalid editorId-buildId',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('fails to parse when provided with a buildId', () => {
|
||||
const parsed = zz
|
||||
.editorBuildId()
|
||||
.safeParse('19d6c341530-878fff6cdab7fb0c')
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'invalid editorId-buildId',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('parses successfully when provided with a valid editorId-buildId', () => {
|
||||
const parsed = zz
|
||||
.editorBuildId()
|
||||
.safeParse(
|
||||
'03b1d773-6203-4669-b365-6a0aa5625878-19d6c341530-878fff6cdab7fb0c'
|
||||
)
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.data).toBe(
|
||||
'03b1d773-6203-4669-b365-6a0aa5625878-19d6c341530-878fff6cdab7fb0c'
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('filepath', () => {
|
||||
it('fails to parse with empty input', () => {
|
||||
const parsed = zz.filepath().safeParse('')
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'path is empty',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('fails to parse with absolute path', () => {
|
||||
const parsed = zz.filepath().safeParse('/output.pdf')
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'path is absolute',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('fails to parse when provided with path traversal', () => {
|
||||
const parsed = zz.filepath().safeParse('../output.pdf')
|
||||
expect(parsed.success).toBe(false)
|
||||
expect(parsed.error?.issues).toHaveLength(1)
|
||||
expect(parsed.error?.issues).toMatchObject([
|
||||
expect.objectContaining({
|
||||
message: 'path traversal detected',
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('parses successfully when provided a valid path', () => {
|
||||
const parsed = zz.filepath().safeParse('output.pdf')
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.data).toBe('output.pdf')
|
||||
})
|
||||
|
||||
it('parses successfully when provided a valid nested path', () => {
|
||||
const parsed = zz.filepath().safeParse('foo/output.pdf')
|
||||
expect(parsed.success).toBe(true)
|
||||
expect(parsed.data).toBe('foo/output.pdf')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
8
libraries/validation-tools/testUtils.js
Normal file
8
libraries/validation-tools/testUtils.js
Normal file
@@ -0,0 +1,8 @@
|
||||
function asZodError(...def) {
|
||||
return {
|
||||
name: 'ZodError',
|
||||
_zod: { def },
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { asZodError }
|
||||
@@ -33,6 +33,31 @@ const zz = {
|
||||
datetimeNullable: options => datetimeSchema({ ...options, allowNull: true }),
|
||||
datetimeNullish: options =>
|
||||
datetimeSchema({ ...options, allowNull: true, allowUndefined: true }),
|
||||
buildId: () =>
|
||||
z.string().regex(/^[0-9a-f]+-[0-9a-f]+$/, { message: 'invalid buildId' }),
|
||||
editorBuildId: () =>
|
||||
z.string().regex(/^[a-f0-9-]{36}-[0-9a-f]+-[0-9a-f]+$/, {
|
||||
message: 'invalid editorId-buildId',
|
||||
}),
|
||||
clsiServerId: () =>
|
||||
z.string().regex(/^[a-z0-9-]+$/, { message: 'invalid clsiServerId' }),
|
||||
compileBackendClass: () =>
|
||||
z
|
||||
.string()
|
||||
.regex(/^[a-z0-9-]+$/, { message: 'invalid compileBackendClass' }),
|
||||
compileGroup: () =>
|
||||
z.enum(['alpha', 'gvisor', 'standard', 'priority'], {
|
||||
message: 'invalid compileGroup',
|
||||
}),
|
||||
submissionId: () => z.string().regex(/^[a-zA-Z0-9_-]+$/),
|
||||
filepath: () =>
|
||||
z
|
||||
.string()
|
||||
.nonempty({ message: 'path is empty' })
|
||||
.refine(s => !s.startsWith('/'), { message: 'path is absolute' })
|
||||
.refine(s => !s.split('/').includes('..'), {
|
||||
message: 'path traversal detected',
|
||||
}),
|
||||
}
|
||||
|
||||
module.exports = { zz }
|
||||
|
||||
Reference in New Issue
Block a user