Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
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