Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/composables/usePackageComparison.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ export function usePackageComparison(packageNames: MaybeRefOrGetter<string[]>) {
$fetch<VulnerabilityTreeResult>(
`/api/registry/vulnerabilities/${encodePackageName(name)}`,
).catch(() => null),
$fetch<PackageLikes>(`/api/social/likes/${name}`).catch(() => null),
$fetch<PackageLikes>(`/api/social/likes/${encodePackageName(name)}`).catch(
() => null,
),
])
const versionData = pkgData.versions[latestVersion]
const packageSize = versionData?.dist?.unpackedSize
Expand Down
29 changes: 25 additions & 4 deletions server/api/social/likes/[...pkg].get.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
import * as v from 'valibot'
import { PackageRouteParamsSchema } from '#shared/schemas/package'

/**
* GET /api/social/likes/:name
*
* Gets the likes for a npm package on npmx
*/
export default eventHandlerWithOAuthSession(async (event, oAuthSession, _) => {
const packageName = getRouterParam(event, 'pkg')
if (!packageName) {
const pkgParamSegments = getRouterParam(event, 'pkg')?.split('/') ?? []
const { rawPackageName } = parsePackageParams(pkgParamSegments)

if (!rawPackageName) {
throw createError({
status: 400,
message: 'package name not provided',
})
}

const likesUtil = new PackageLikesUtils()
return await likesUtil.getLikes(packageName, oAuthSession?.did.toString())
try {
const { packageName } = v.parse(PackageRouteParamsSchema, {
packageName: decodeURIComponent(rawPackageName),
})

const likesUtil = new PackageLikesUtils()
return await likesUtil.getLikes(packageName, oAuthSession?.did.toString())
} catch (error: unknown) {
handleApiError(error, {
statusCode: 502,
message: 'Failed to get likes',
})
Comment on lines +20 to +31

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n server/api/social/likes/[...pkg].get.ts | head -50

Repository: npmx-dev/npmx.dev

Length of output: 1211


🏁 Script executed:

rg -n "decodeURIComponent\(" server/api -C 2

Repository: npmx-dev/npmx.dev

Length of output: 886


🏁 Script executed:

rg -n "createError\|handleApiError" server/api -l | head -10

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

rg -n "import.*createError\|import.*handleApiError" server/api

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

cat -n server/api/registry/vulnerabilities/[...pkg].get.ts | head -40

Repository: npmx-dev/npmx.dev

Length of output: 1826


🏁 Script executed:

rg -n "createError" server/ -t ts -t tsx | head -5

Repository: npmx-dev/npmx.dev

Length of output: 88


🏁 Script executed:

rg -n "createError\|handleApiError" server/api/social/likes/[...pkg].get.ts

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

rg -n "export.*handleApiError\|function handleApiError" server/

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

fd "utils" server/ -type d

Repository: npmx-dev/npmx.dev

Length of output: 231


🏁 Script executed:

fd "utils" server/ -type d | head -20

Repository: npmx-dev/npmx.dev

Length of output: 231


🏁 Script executed:

rg -n "handleApiError" server/ | head -10

Repository: npmx-dev/npmx.dev

Length of output: 909


🏁 Script executed:

rg -n "function handleApiError|const handleApiError" . | head -5

Repository: npmx-dev/npmx.dev

Length of output: 174


🏁 Script executed:

cat -n server/utils/error-handler.ts

Repository: npmx-dev/npmx.dev

Length of output: 1414


🏁 Script executed:

rg -n "createError" server/ | head -15

Repository: npmx-dev/npmx.dev

Length of output: 1532


🏁 Script executed:

rg -n "URIError" server/

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

rg -n "import.*createError" server/api/social/likes/[...pkg].get.ts

Repository: npmx-dev/npmx.dev

Length of output: 43


Catch URIError from malformed percent-encoding to return 400 instead of 502.

decodeURIComponent throws URIError when given invalid percent-encoding. The current implementation funnels this to handleApiError's generic fallback, which returns 502, misclassifying a client error as a server error. Wrap the call to catch URIError separately and return a 400 status.

This pattern repeats across multiple endpoints (e.g. server/api/registry/vulnerabilities/[...pkg].get.ts), so consider applying the fix consistently.

Suggested approach

Extract decodeURIComponent outside the v.parse call to isolate the error:

  try {
+    const decodedPackageName = decodeURIComponent(rawPackageName)
    const { packageName } = v.parse(PackageRouteParamsSchema, {
-      packageName: decodeURIComponent(rawPackageName),
+      packageName: decodedPackageName,
    })

    const likesUtil = new PackageLikesUtils()
    return await likesUtil.getLikes(packageName, oAuthSession?.did.toString())
  } catch (error: unknown) {
+    if (error instanceof URIError) {
+      throw createError({
+        status: 400,
+        message: 'invalid package name encoding',
+      })
+    }
     handleApiError(error, {
       statusCode: 502,
       message: 'Failed to get likes',
     })
   }

}
})
Loading