diff --git a/backend/package.json b/backend/package.json index 34d5b82..f14648e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -21,4 +21,4 @@ "pino-pretty": "^13.0.0", "whois-json": "^2.0.4" } -} +} \ No newline at end of file diff --git a/backend/routes/macLookup.js b/backend/routes/macLookup.js new file mode 100644 index 0000000..4a7e77f --- /dev/null +++ b/backend/routes/macLookup.js @@ -0,0 +1,39 @@ +// backend/routes/macLookup.js +const express = require('express'); +const Sentry = require("@sentry/node"); +const macaddress = require('macaddress'); +const pino = require('pino'); +const { isValidMacAddress } = require('../utils'); // Wir fügen diese neue Funktion hinzu + +const logger = pino({ level: process.env.LOG_LEVEL || 'info' }); +const router = express.Router(); + +router.get('/', async (req, res) => { + const macRaw = req.query.mac; + const mac = typeof macRaw === 'string' ? macRaw.trim() : macRaw; + const requestIp = req.ip || req.socket.remoteAddress; + + logger.info({ requestIp, mac }, 'MAC lookup request received'); + + if (!isValidMacAddress(mac)) { + logger.warn({ requestIp, mac }, 'Invalid MAC address for lookup'); + return res.status(400).json({ success: false, error: 'Invalid MAC address format provided.' }); + } + + try { + const vendor = await macaddress.lookup(mac); + if (vendor) { + logger.info({ requestIp, mac, vendor }, 'MAC lookup successful'); + res.json({ success: true, mac, vendor }); + } else { + logger.info({ requestIp, mac }, 'MAC address not found in OUI database'); + res.status(404).json({ success: false, error: 'Vendor not found for this MAC address.' }); + } + } catch (error) { + logger.error({ requestIp, mac, error: error.message }, 'MAC lookup failed'); + Sentry.captureException(error, { extra: { requestIp, mac } }); + res.status(500).json({ success: false, error: 'An unexpected error occurred during the MAC lookup.' }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index b0a31e8..fe33dab 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,6 +1,3 @@ -// backend/server.js -// server.js -// Load .env variables FIRST! require('dotenv').config(); // --- Sentry Initialisierung (GANZ OBEN, nach dotenv) --- @@ -35,6 +32,7 @@ const dnsLookupRoutes = require('./routes/dnsLookup'); const whoisLookupRoutes = require('./routes/whoisLookup'); const versionRoutes = require('./routes/version'); const portScanRoutes = require('./routes/portScan'); +const macLookupRoutes = require('./routes/macLookup'); // --- Logger Initialisierung --- const logger = pino({ @@ -97,6 +95,7 @@ app.use('/api/lookup', generalLimiter); app.use('/api/dns-lookup', generalLimiter); app.use('/api/whois-lookup', generalLimiter); app.use('/api/port-scan', generalLimiter); +app.use('/api/mac-lookup', generalLimiter); // --- API Routes --- @@ -109,6 +108,7 @@ app.use('/api/dns-lookup', dnsLookupRoutes); app.use('/api/whois-lookup', whoisLookupRoutes); app.use('/api/version', versionRoutes); app.use('/api/port-scan', portScanRoutes); +app.use('/api/mac-lookup', macLookupRoutes); // --- Sentry Error Handler --- diff --git a/backend/utils.js b/backend/utils.js index 0518fe1..a6e6bc1 100644 --- a/backend/utils.js +++ b/backend/utils.js @@ -1,3 +1,4 @@ +// backend/utils.js const net = require('net'); // Node.js built-in module for IP validation const { spawn } = require('child_process'); const pino = require('pino'); // Import pino for logging within utils if needed @@ -65,6 +66,20 @@ function isValidDomain(domain) { return domainRegex.test(domain.trim()); } +/** + * Validiert eine MAC-Adresse. + * @param {string} mac - Die zu validierende MAC-Adresse. + * @returns {boolean} True, wenn das Format gültig ist, sonst false. + */ +function isValidMacAddress(mac) { + if (!mac || typeof mac !== 'string') { + return false; + } + // This regex matches common MAC address formats (e.g., 00:1A:2B:3C:4D:5E, 00-1A-2B-3C-4D-5E, 001A2B3C4D5E) + const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$|^([0-9A-Fa-f]{12})$/; + return macRegex.test(mac.trim()); +} + /** * Bereinigt eine IP-Adresse (z.B. entfernt ::ffff: Präfix von IPv4-mapped IPv6). @@ -302,6 +317,7 @@ module.exports = { isValidIp, isPrivateIp, isValidDomain, + isValidMacAddress, getCleanIp, executeCommand, parsePingOutput, diff --git a/frontend/app/index.html b/frontend/app/index.html index df6c3d4..1573376 100644 --- a/frontend/app/index.html +++ b/frontend/app/index.html @@ -88,11 +88,11 @@

uTools Network Suite

diff --git a/frontend/app/mac-lookup.html b/frontend/app/mac-lookup.html new file mode 100644 index 0000000..9f1cbc2 --- /dev/null +++ b/frontend/app/mac-lookup.html @@ -0,0 +1,90 @@ +// frontend/app/mac-lookup.html + + + + + + MAC Vendor Lookup - uTools + + + + + + + +
+

uTools Network Suite

+ +
+ +
+ +

MAC Address Vendor Lookup

+ +
+
+ + +
+ + +
+ + + + + +
+ + + + \ No newline at end of file diff --git a/frontend/app/mac-lookup.js b/frontend/app/mac-lookup.js new file mode 100644 index 0000000..a31549a --- /dev/null +++ b/frontend/app/mac-lookup.js @@ -0,0 +1,77 @@ +// frontend/app/mac-lookup.js +document.addEventListener('DOMContentLoaded', () => { + const macInput = document.getElementById('mac-input'); + const macLookupButton = document.getElementById('mac-lookup-button'); + const macLookupErrorEl = document.getElementById('mac-lookup-error'); + const macLookupResultsSection = document.getElementById('mac-lookup-results-section'); + const macLookupQueryEl = document.getElementById('mac-lookup-query'); + const macLookupLoader = document.getElementById('mac-lookup-loader'); + const macLookupOutputEl = document.getElementById('mac-lookup-output'); + const commitShaEl = document.getElementById('commit-sha'); + const globalErrorEl = document.getElementById('global-error'); + + const API_BASE_URL = '/api'; + + function showGlobalError(message) { + if (!globalErrorEl) return; + globalErrorEl.textContent = `Error: ${message}`; + globalErrorEl.classList.remove('hidden'); + } + + async function fetchVersionInfo() { + if (!commitShaEl) return; + try { + const response = await fetch(`${API_BASE_URL}/version`); + if (!response.ok) throw new Error(`Network response: ${response.statusText}`); + const data = await response.json(); + commitShaEl.textContent = data.commitSha || 'unknown'; + } catch (error) { + console.error('Failed to fetch version info:', error); + commitShaEl.textContent = 'error'; + } + } + + function displayMacResult(data, outputEl) { + outputEl.textContent = data.vendor || 'No vendor found.'; + } + + async function handleMacLookup() { + const mac = macInput.value.trim(); + if (!mac) { + macLookupErrorEl.textContent = 'Please enter a MAC address.'; + macLookupErrorEl.classList.remove('hidden'); + return; + } + + macLookupResultsSection.classList.remove('hidden'); + macLookupLoader.classList.remove('hidden'); + macLookupErrorEl.classList.add('hidden'); + macLookupOutputEl.textContent = ''; + macLookupQueryEl.textContent = mac; + + try { + const response = await fetch(`${API_BASE_URL}/mac-lookup?mac=${encodeURIComponent(mac)}`); + const data = await response.json(); + + if (!response.ok || !data.success) { + throw new Error(data.error || `Request failed with status ${response.status}`); + } + + displayMacResult(data, macLookupOutputEl); + + } catch (error) { + console.error('Failed to fetch MAC vendor:', error); + macLookupErrorEl.textContent = `Error: ${error.message}`; + macLookupErrorEl.classList.remove('hidden'); + macLookupOutputEl.textContent = ''; + } finally { + macLookupLoader.classList.add('hidden'); + } + } + + fetchVersionInfo(); + macLookupButton.addEventListener('click', handleMacLookup); + macInput.addEventListener('keypress', (event) => { + if (event.key === 'Enter') handleMacLookup(); + }); +}); \ No newline at end of file