diff --git a/api/src/routes/user.ts b/api/src/routes/user.ts index 3111cc5cb79..c0475d0d6ff 100644 --- a/api/src/routes/user.ts +++ b/api/src/routes/user.ts @@ -63,6 +63,8 @@ const nullableFlags = [ 'keyboardShortcuts' ] as const; +const blockedUserAgentParts = ['python', 'google-apps-script', 'curl']; + type NullableFlag = (typeof nullableFlags)[number]; /** @@ -654,6 +656,18 @@ export const userPublicGetRoutes: FastifyPluginCallbackTypebox = ( schema: schemas.getPublicProfile }, async (req, reply) => { + const userAgent = req.headers['user-agent']; + + if ( + userAgent && + blockedUserAgentParts.some(ua => userAgent.toLowerCase().includes(ua)) + ) { + void reply.code(400); + return reply.send( + 'This endpoint is no longer available outside of the freeCodeCamp ecosystem' + ); + } + // TODO(Post-MVP): look for duplicates unless we can make username unique in the db. const user = await fastify.prisma.user.findFirst({ where: { username: req.query.username } diff --git a/api/src/schemas/api/users/get-public-profile.ts b/api/src/schemas/api/users/get-public-profile.ts index 383a2bd308d..f4305e0cb09 100644 --- a/api/src/schemas/api/users/get-public-profile.ts +++ b/api/src/schemas/api/users/get-public-profile.ts @@ -112,7 +112,12 @@ export const getPublicProfile = { // We can't simply have Type.Object({}), even though that's correct, because // TypeScript will then accept all responses (since every object can be // assigned to {}) - 400: Type.Object({ entities: Type.Optional(Type.Never()) }), + 400: Type.Union([ + Type.Object({ entities: Type.Optional(Type.Never()) }), + Type.Literal( + 'This endpoint is no longer available outside of the freeCodeCamp ecosystem' + ) + ]), 404: Type.Object({ entities: Type.Optional(Type.Never()) }) } };