Skip to content

Commit 9cd3080

Browse files
chore(web): validate /api/avatar query params with Zod
Replaces the manual `searchParams.get('email')` + plain-text 400 response with the Zod safeParse + queryParamsSchemaValidationError pattern used elsewhere in the API. Errors now return structured JSON consistent with the rest of the public API surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 614800a commit 9cd3080

2 files changed

Lines changed: 21 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Added `/api/blame` to the public API to fetch per-line blame information for a file at a given revision. [#1158](https://github.com/sourcebot-dev/sourcebot/pull/1158)
1717

1818
### Changed
19-
- `UserAvatar` now resolves profile pictures via a new `/api/avatar` endpoint, automatically displaying a user's profile image when their email matches a Sourcebot user. Falls back to a minidenticon otherwise. [#1159](https://github.com/sourcebot-dev/sourcebot/pull/1159)
19+
- Added `/api/avatar` to resolve user profile pictures. [#1159](https://github.com/sourcebot-dev/sourcebot/pull/1159)
2020

2121
### Fixed
2222
- Bumped `postcss` to `8.5.10`. [#1155](https://github.com/sourcebot-dev/sourcebot/pull/1155)

packages/web/src/app/api/(server)/avatar/route.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,39 @@
22

33
import { minidenticon } from 'minidenticons';
44
import { NextRequest } from 'next/server';
5+
import { z } from 'zod';
56
import { apiHandler } from '@/lib/apiHandler';
7+
import { queryParamsSchemaValidationError, serviceErrorResponse } from '@/lib/serviceError';
68
import { isServiceError } from '@/lib/utils';
79
import { withOptionalAuth } from '@/middleware/withAuth';
810

11+
const queryParamsSchema = z.object({
12+
email: z.string().min(1),
13+
});
14+
915
// Resolves an email to an avatar image. If the email belongs to a Sourcebot
1016
// user in the requester's org and that user has a profile image set, the
1117
// request is redirected to that URL. Otherwise a minidenticon SVG is returned.
1218
//
1319
// We never 4xx on this endpoint — even if the requester is unauthenticated or
1420
// the user isn't found, we serve the identicon so the avatar visually renders.
1521
export const GET = apiHandler(async (request: NextRequest) => {
16-
const email = request.nextUrl.searchParams.get('email');
17-
if (!email) {
18-
return new Response('Missing email parameter', { status: 400 });
22+
const rawParams = Object.fromEntries(
23+
Object.keys(queryParamsSchema.shape).map(key => [
24+
key,
25+
request.nextUrl.searchParams.get(key) ?? undefined,
26+
])
27+
);
28+
const parsed = queryParamsSchema.safeParse(rawParams);
29+
30+
if (!parsed.success) {
31+
return serviceErrorResponse(
32+
queryParamsSchemaValidationError(parsed.error)
33+
);
1934
}
2035

36+
const { email } = parsed.data;
37+
2138
const lookup = await withOptionalAuth(async ({ org, prisma }) => {
2239
return prisma.user.findFirst({
2340
where: {

0 commit comments

Comments
 (0)