Skip to content

Commit e952583

Browse files
committed
chore: add validation to opensearch handler
1 parent 1ffdc05 commit e952583

File tree

3 files changed

+33
-12
lines changed

3 files changed

+33
-12
lines changed
Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
1-
import type { NpmSearchResponse } from '#shared/types'
2-
import { NPM_REGISTRY } from '#shared/utils/constants'
1+
import * as v from 'valibot'
2+
import { SearchQuerySchema } from '#shared/schemas/package'
3+
import { CACHE_MAX_AGE_ONE_MINUTE, NPM_REGISTRY } from '#shared/utils/constants'
34

45
export default defineCachedEventHandler(
56
async event => {
67
const query = getQuery(event)
7-
const q = String(query.q || '').trim()
88

9-
if (!q) {
10-
return [q, []]
11-
}
9+
try {
10+
const q = v.parse(SearchQuerySchema, query.q)
11+
12+
if (!q) {
13+
return [q, []]
14+
}
1215

13-
const params = new URLSearchParams({ text: q, size: '10' })
14-
const response = await $fetch<NpmSearchResponse>(`${NPM_REGISTRY}/-/v1/search?${params}`)
16+
const params = new URLSearchParams({ text: q, size: '10' })
17+
const response = await $fetch<NpmSearchResponse>(`${NPM_REGISTRY}/-/v1/search?${params}`)
1518

16-
const suggestions = response.objects.map(obj => obj.package.name)
17-
return [q, suggestions]
19+
const suggestions = response.objects.map(obj => obj.package.name)
20+
return [q, suggestions]
21+
} catch (error: unknown) {
22+
handleApiError(error, {
23+
statusCode: 502,
24+
message: ERROR_SUGGESTIONS_FETCH_FAILED,
25+
})
26+
}
1827
},
1928
{
20-
maxAge: 60,
29+
maxAge: CACHE_MAX_AGE_ONE_MINUTE,
2130
swr: true,
2231
getKey: event => {
2332
const query = getQuery(event)
24-
return `opensearch-suggestions:${query.q || ''}`
33+
const q = String(query.q || '').trim()
34+
return `opensearch-suggestions:${q}`
2535
},
2636
},
2737
)

shared/schemas/package.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ export const FilePathSchema = v.pipe(
3535
v.check(input => !input.startsWith('/'), 'Invalid path: must be relative to package root'),
3636
)
3737

38+
/**
39+
* Schema for search queries, limits length to guard against DoS attacks
40+
*/
41+
export const SearchQuerySchema = v.pipe(
42+
v.string(),
43+
v.trim(),
44+
v.maxLength(100, 'Search query is too long'),
45+
)
46+
3847
/**
3948
* Schema for package fetching where version is not required
4049
*/

shared/utils/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Duration
2+
export const CACHE_MAX_AGE_ONE_MINUTE = 60
23
export const CACHE_MAX_AGE_ONE_HOUR = 60 * 60
34
export const CACHE_MAX_AGE_ONE_DAY = 60 * 60 * 24
45
export const CACHE_MAX_AGE_ONE_YEAR = 60 * 60 * 24 * 365
@@ -14,3 +15,4 @@ export const ERROR_CALC_INSTALL_SIZE_FAILED = 'Failed to calculate install size.
1415
export const NPM_MISSING_README_SENTINEL = 'ERROR: No README data found!'
1516
export const ERROR_JSR_FETCH_FAILED = 'Failed to fetch package from JSR registry.'
1617
export const ERROR_NPM_FETCH_FAILED = 'Failed to fetch package from npm registry.'
18+
export const ERROR_SUGGESTIONS_FETCH_FAILED = 'Failed to fetch suggestions.'

0 commit comments

Comments
 (0)