diff --git a/backend/routes/privacy.js b/backend/routes/privacy.js new file mode 100644 index 0000000..ce983af --- /dev/null +++ b/backend/routes/privacy.js @@ -0,0 +1,71 @@ +const express = require('express'); +const router = express.Router(); +const dns = require('dns').promises; +const pino = require('pino'); +const { getMaxMindReaders } = require('../maxmind'); + +const logger = pino({ level: process.env.LOG_LEVEL || 'info' }); + +// ASN org-name patterns that strongly suggest a VPN service +const VPN_PATTERNS = [ + /\bvpn\b/i, /nordvpn/i, /expressvpn/i, /mullvad/i, /surfshark/i, + /protonvpn/i, /cyberghost/i, /ipvanish/i, /purevpn/i, /tunnelbear/i, + /private.?internet.?access/i, /\bpia\b/i, /hide\.?my\.?ip/i, /hidemyass/i, +]; + +// ASN org-name patterns that suggest cloud/datacenter/hosting (but not necessarily VPN) +const DATACENTER_PATTERNS = [ + /amazon/i, /\baws\b/i, /google.*cloud/i, /microsoft/i, /\bazure\b/i, + /cloudflare/i, /digitalocean/i, /linode/i, /vultr/i, /hetzner/i, + /\bovh\b/i, /leaseweb/i, /rackspace/i, /choopa/i, /equinix/i, + /hostinger/i, /\bhosting\b/i, /data.?cent(?:er|re)/i, /\bcloud\b/i, + /\bcoloc\b/i, /\bvps\b/i, /akamai/i, /fastly/i, /\bcdn\b/i, +]; + +// Tor DNS exit-node check via torproject.org DNSBL +async function isTorExit(ip) { + if (!ip || ip.includes(':')) return false; // IPv6 not supported by this DNSBL + try { + const reversed = ip.split('.').reverse().join('.'); + await dns.resolve4(`${reversed}.dnsel.torproject.org`); + return true; // resolves → known Tor exit node + } catch { + return false; // NXDOMAIN → not a Tor exit node + } +} + +router.get('/:ip', async (req, res, next) => { + const { ip } = req.params; + const flags = []; + + try { + // ASN-based classification (synchronous, no network call) + try { + const { asnReader } = getMaxMindReaders(); + const asnData = asnReader.asn(ip); + const org = asnData?.autonomousSystemOrganization || ''; + + if (VPN_PATTERNS.some(p => p.test(org))) { + flags.push({ id: 'vpn', label: 'VPN', color: 'yellow' }); + } else if (DATACENTER_PATTERNS.some(p => p.test(org))) { + flags.push({ id: 'datacenter', label: 'Datacenter / Hosting', color: 'orange' }); + } else if (org) { + flags.push({ id: 'residential', label: 'Residential / ISP', color: 'green' }); + } + } catch (e) { + logger.debug({ ip, err: e.message }, 'ASN lookup skipped for privacy check'); + } + + // Tor exit-node check (async DNS, ~100–300 ms) + const tor = await isTorExit(ip); + if (tor) flags.push({ id: 'tor', label: 'Tor Exit Node', color: 'red' }); + + logger.info({ ip, flags: flags.map(f => f.id) }, 'Privacy check complete'); + res.json({ success: true, ip, flags }); + } catch (err) { + logger.error({ ip, err: err.message }, 'Privacy check failed'); + next(err); + } +}); + +module.exports = router; diff --git a/backend/server.js b/backend/server.js index f1dbc8a..5a0a930 100644 --- a/backend/server.js +++ b/backend/server.js @@ -32,6 +32,7 @@ const versionRoutes = require('./routes/version'); const portScanRoutes = require('./routes/portScan'); const macLookupRoutes = require('./routes/macLookup'); const asnLookupRoutes = require('./routes/asnLookup'); +const privacyRoutes = require('./routes/privacy'); // --- Logger Initialisierung --- const logger = pino({ @@ -88,6 +89,7 @@ app.use('/api/version', versionRoutes); app.use('/api/port-scan', portScanRoutes); app.use('/api/mac-lookup', macLookupRoutes); app.use('/api/asn-lookup', asnLookupRoutes); +app.use('/api/privacy', privacyRoutes); // Sentry error handler — must be after routes, before custom error handler diff --git a/frontend/app/pages/home.js b/frontend/app/pages/home.js index 455aedf..fb199ce 100644 --- a/frontend/app/pages/home.js +++ b/frontend/app/pages/home.js @@ -25,6 +25,15 @@ export const page = { + + + + +
@@ -68,23 +77,71 @@ export const page = {
- -
-

- + +
+

+ - Location Visualization + Location

-
-
- - -
+
+
+ + +
+ +
+

+ + + + Browser Fingerprint +

+
+
+

Browser

+

-

+
+
+

Operating System

+

-

+
+
+

Screen

+

-

+
+
+

Viewport

+

-

+
+
+

Language

+

-

+
+
+

Timezone

+

-

+
+
+

Color Depth

+

-

+
+
+

Privacy

+

-

+
+
+
+ User Agent string +

+
+
+

@@ -104,12 +161,14 @@ export const page = {