Feature Requests
New / Open / Unreviewed

feat: Implement GeoIP location lookup for session IPs (currently stub returning Unknown)

Feature Request

Current Behavior

get_location_from_ip() in backend/src/routes/authRoutes.js (line 304-319) is a stub that returns { country: "Unknown", city: "Unknown" } for all non-private IPs:

// In a real implementation, you'd use a service like MaxMind GeoIP2
// For now, return unknown for external IPs
return { country: "Unknown", city: "Unknown" };

Sessions display "Unknown, Unknown" for location, making it impossible to identify where logins originate from.

Expected Behavior

Session locations should resolve to meaningful country/city information (e.g., "Germany, Hamburg") using a GeoIP database.

Suggested Implementation

Option 1: MaxMind GeoLite2 (recommended, free with registration)

const maxmind = require('maxmind');
const lookup = await maxmind.open('/path/to/GeoLite2-City.mmdb');
const result = lookup.get(ip);
return {
  country: result?.country?.names?.en || "Unknown",
  city: result?.city?.names?.en || "Unknown"
};

Environment variables needed:

GEOIP_DB_PATH=/app/data/GeoLite2-City.mmdb
MAXMIND_ACCOUNT_ID=123456
MAXMIND_LICENSE_KEY=xxxxxx

Option 2: ip-api.com (free for non-commercial, no registration)

const response = await fetch(`http://ip-api.com/json/${ip}?fields=country,city`);
const data = await response.json();

Impact

  • Session security audit: cannot identify suspicious login locations
  • User cannot verify if sessions are legitimate
  • The feature is already partially built (UI displays location field, private IPs show "Local, Local Network")

Notes

  • MaxMind GeoLite2 database auto-update could be handled via geoipupdate container or cron job
  • Consider making GeoIP optional (graceful fallback to "Unknown" if no database configured)
  • The existing private IP detection (127.0.0.1, ::1, 192.168.x, 10.x) should be preserved

1 Comment

Posting anonymously

strausmann·5 months ago

Implementation proposal: Volume-mount with lazy reload

Approach

Instead of building download/update logic into PatchMon, let users provide MaxMind MMDB files via a simple volume mount. The backend detects and loads them automatically — no restart required.

Configuration

# Path to directory with MaxMind MMDB files (optional)
GEOIP_DB_PATH=/app/geoip

The backend searches for (in order): GeoLite2-City.mmdb, GeoIP2-City.mmdb, GeoLite2-Country.mmdb.
If none found or path not set → graceful fallback to { country: "Unknown", city: "Unknown" }.

Docker deployment

services:
  backend:
    environment:
      GEOIP_DB_PATH: /app/geoip
    volumes:
      - geoip_data:/app/geoip:ro

  # Optional: auto-update sidecar
  geoipupdate:
    image: ghcr.io/maxmind/geoipupdate:latest
    environment:
      GEOIPUPDATE_ACCOUNT_ID: "123456"
      GEOIPUPDATE_LICENSE_KEY: "xxxxxx"
      GEOIPUPDATE_EDITION_IDS: "GeoLite2-City GeoLite2-Country"
      GEOIPUPDATE_FREQUENCY: "168"  # weekly
    volumes:
      - geoip_data:/usr/share/GeoIP

volumes:
  geoip_data:

Hot-reload without restart

Uses mtime check on each lookup — if the file was updated (e.g., by geoipupdate), the next login automatically loads the new database:

async function getGeoIPReader() {
    const dbPath = findGeoIPDB();
    if (!dbPath) return null;
    const stat = fs.statSync(dbPath);
    if (!geoipReader || stat.mtimeMs !== geoipMtime) {
        geoipReader = await maxmind.open(dbPath);
        geoipMtime = stat.mtimeMs;
    }
    return geoipReader;
}

Code changes required

  1. New dependency: maxmind (npm package, pure JS, no native code)
  2. get_location_from_ip() becomes async, callers need await
  3. Extended private IP detection: add Tailscale CGNAT (100.64.0.0/10), Docker bridge networks
  4. New env var: GEOIP_DB_PATH (optional, documented in env reference)

Benefits

  • Zero config for existing users — no GeoIP = no change, "Unknown" as before
  • No external API calls — offline, private, fast (~0.1ms per lookup)
  • No restart needed — DB updates are picked up automatically
  • User controls the DB — works with GeoLite2 (free) or GeoIP2 (commercial)
  • Docker-native — shared volume between backend and geoipupdate sidecar

I can submit a PR if this approach looks good.

Posting anonymously