(
request: FastifyRequest<{ Querystring: { ip?: string } }>,
reply: FastifyReply,
)
| 685 | } |
| 686 | |
| 687 | export async function ipLookup( |
| 688 | request: FastifyRequest<{ Querystring: { ip?: string } }>, |
| 689 | reply: FastifyReply, |
| 690 | ) { |
| 691 | const ipParam = request.query.ip; |
| 692 | |
| 693 | const { ip: clientIp } = getClientIpFromHeaders(request.headers); |
| 694 | if ( |
| 695 | clientIp && |
| 696 | !checkRateLimit(`ip:${clientIp}`, IP_LOOKUP_WINDOW, IP_LOOKUP_MAX) |
| 697 | ) { |
| 698 | return reply |
| 699 | .status(429) |
| 700 | .send({ error: 'Rate limit exceeded. Please try again later.' }); |
| 701 | } |
| 702 | |
| 703 | const ipToLookup = ipParam ? ipParam.trim() : clientIp || ''; |
| 704 | |
| 705 | if (!ipToLookup) { |
| 706 | return reply |
| 707 | .status(400) |
| 708 | .send({ error: 'No IP address provided or detected' }); |
| 709 | } |
| 710 | |
| 711 | const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; |
| 712 | const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/; |
| 713 | if (!ipv4Regex.test(ipToLookup) && !ipv6Regex.test(ipToLookup)) { |
| 714 | return reply.status(400).send({ error: 'Invalid IP address format' }); |
| 715 | } |
| 716 | |
| 717 | try { |
| 718 | const geo = await getGeoLocation(ipToLookup); |
| 719 | const isLocalhost = ipToLookup === '127.0.0.1' || ipToLookup === '::1'; |
| 720 | const isPrivate = isPrivateIP(ipToLookup); |
| 721 | |
| 722 | return reply.send({ |
| 723 | ip: ipToLookup, |
| 724 | location: { |
| 725 | country: geo.country, |
| 726 | city: geo.city, |
| 727 | region: geo.region, |
| 728 | latitude: geo.latitude, |
| 729 | longitude: geo.longitude, |
| 730 | }, |
| 731 | isLocalhost, |
| 732 | isPrivate, |
| 733 | }); |
| 734 | } catch (error) { |
| 735 | request.log.error({ err: error }, 'IP lookup error'); |
| 736 | return reply.status(500).send({ |
| 737 | error: |
| 738 | error instanceof Error |
| 739 | ? error.message |
| 740 | : 'Failed to lookup IP address', |
| 741 | }); |
| 742 | } |
| 743 | } |
nothing calls this directly
no test coverage detected