mirror of
https://github.com/MrUnknownDE/utools.git
synced 2026-04-06 00:32:04 +02:00
116 lines
5.2 KiB
JavaScript
116 lines
5.2 KiB
JavaScript
// backend/routes/ipinfo.js
|
|
const express = require('express');
|
|
const Sentry = require("@sentry/node");
|
|
const dns = require('dns').promises;
|
|
const pino = require('pino'); // Assuming logger is needed, or pass it down
|
|
|
|
// Import utilities and MaxMind reader access
|
|
const { isValidIp, getCleanIp } = require('../utils');
|
|
const { getMaxMindReaders } = require('../maxmind');
|
|
|
|
// Create a logger instance for this route module
|
|
// Ideally, the main logger instance should be passed down or configured globally
|
|
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
|
|
|
|
const router = express.Router();
|
|
|
|
// Route handler for / (relative to where this router is mounted, e.g., /api/ipinfo)
|
|
router.get('/', async (req, res, next) => {
|
|
const requestIp = req.ip || req.socket.remoteAddress;
|
|
logger.info({ ip: requestIp, method: req.method, url: req.originalUrl }, 'ipinfo request received');
|
|
const clientIp = getCleanIp(requestIp);
|
|
logger.debug({ rawIp: requestIp, cleanedIp: clientIp }, 'IP cleaning result');
|
|
|
|
if (!clientIp || !isValidIp(clientIp)) {
|
|
if (clientIp === '127.0.0.1' || clientIp === '::1') {
|
|
logger.info({ ip: clientIp }, 'Responding with localhost info');
|
|
return res.json({
|
|
ip: clientIp,
|
|
geo: { note: 'Localhost IP, no Geo data available.' },
|
|
asn: { note: 'Localhost IP, no ASN data available.' },
|
|
rdns: ['localhost'],
|
|
});
|
|
}
|
|
logger.error({ rawIp: requestIp, cleanedIp: clientIp }, 'Could not determine a valid client IP');
|
|
Sentry.captureMessage('Could not determine a valid client IP', {
|
|
level: 'error',
|
|
extra: { rawIp: requestIp, cleanedIp: clientIp }
|
|
});
|
|
// Use 400 for client error (invalid IP derived)
|
|
return res.status(400).json({ error: 'Could not determine a valid client IP address.', rawIp: requestIp, cleanedIp: clientIp });
|
|
}
|
|
|
|
try {
|
|
// Get initialized MaxMind readers
|
|
const { cityReader, asnReader } = getMaxMindReaders();
|
|
|
|
let geo = null;
|
|
try {
|
|
const geoData = cityReader.city(clientIp);
|
|
geo = {
|
|
city: geoData.city?.names?.en,
|
|
region: geoData.subdivisions?.[0]?.isoCode,
|
|
country: geoData.country?.isoCode,
|
|
countryName: geoData.country?.names?.en,
|
|
postalCode: geoData.postal?.code,
|
|
latitude: geoData.location?.latitude,
|
|
longitude: geoData.location?.longitude,
|
|
timezone: geoData.location?.timeZone,
|
|
};
|
|
// Remove null/undefined values
|
|
geo = Object.fromEntries(Object.entries(geo).filter(([_, v]) => v != null));
|
|
logger.debug({ ip: clientIp, geo }, 'GeoIP lookup successful');
|
|
} catch (e) {
|
|
// Log as warning, as this is expected for private IPs or IPs not in DB
|
|
logger.warn({ ip: clientIp, error: e.message }, `MaxMind City lookup failed`);
|
|
geo = { error: 'GeoIP lookup failed (IP not found in database or private range).' };
|
|
}
|
|
|
|
let asn = null;
|
|
try {
|
|
const asnData = asnReader.asn(clientIp);
|
|
asn = {
|
|
number: asnData.autonomousSystemNumber,
|
|
organization: asnData.autonomousSystemOrganization,
|
|
};
|
|
asn = Object.fromEntries(Object.entries(asn).filter(([_, v]) => v != null));
|
|
logger.debug({ ip: clientIp, asn }, 'ASN lookup successful');
|
|
} catch (e) {
|
|
logger.warn({ ip: clientIp, error: e.message }, `MaxMind ASN lookup failed`);
|
|
asn = { error: 'ASN lookup failed (IP not found in database or private range).' };
|
|
}
|
|
|
|
let rdns = null;
|
|
try {
|
|
const hostnames = await dns.reverse(clientIp);
|
|
rdns = hostnames;
|
|
logger.debug({ ip: clientIp, rdns }, 'rDNS lookup successful');
|
|
} catch (e) {
|
|
// Log non-existence as debug, other errors as warn
|
|
if (e.code !== 'ENOTFOUND' && e.code !== 'ENODATA') {
|
|
logger.warn({ ip: clientIp, error: e.message, code: e.code }, `rDNS lookup error`);
|
|
} else {
|
|
logger.debug({ ip: clientIp, code: e.code }, 'rDNS lookup failed (No record)');
|
|
}
|
|
// Provide a structured error in the response
|
|
rdns = { error: `rDNS lookup failed (${e.code || 'Unknown error'})` };
|
|
}
|
|
|
|
res.json({
|
|
ip: clientIp,
|
|
// Only include geo/asn if they don't contain an error and have data
|
|
geo: geo.error ? geo : (Object.keys(geo).length > 0 ? geo : null),
|
|
asn: asn.error ? asn : (Object.keys(asn).length > 0 ? asn : null),
|
|
rdns // rdns will contain either the array of hostnames or the error object
|
|
});
|
|
|
|
} catch (error) {
|
|
// Catch unexpected errors during processing (e.g., issues with getMaxMindReaders)
|
|
logger.error({ ip: clientIp, error: error.message, stack: error.stack }, 'Error processing ipinfo');
|
|
Sentry.captureException(error, { extra: { ip: clientIp } });
|
|
// Pass the error to the Sentry error handler middleware
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
module.exports = router; |