From 5e238fdad2fd169b808d099e0878bb712cc319b9 Mon Sep 17 00:00:00 2001 From: fuzz <> Date: Wed, 25 Oct 2023 16:23:25 +0100 Subject: [PATCH 1/8] initial commit needs work - currently just an api endpoint that will return geolocation data for a given IP. ToDo: - Display IP in session-card - Clicking IP opens modal with geolocation info --- backend/routes/utils.js | 50 +++++++++++++++++++++++++++++++++++++++++ backend/server.js | 2 ++ 2 files changed, 52 insertions(+) create mode 100644 backend/routes/utils.js diff --git a/backend/routes/utils.js b/backend/routes/utils.js new file mode 100644 index 0000000..12d5bf8 --- /dev/null +++ b/backend/routes/utils.js @@ -0,0 +1,50 @@ +// api.js +const https = require("https"); +const axios = require("axios"); +const express = require("express"); + +const router = express.Router(); + +const geoliteUrlBase = 'https://geolite.info/geoip/v2.1/city'; + +const geoliteAccountId = process.env.GEOLITE_ACCOUNT_ID; +const geoliteLicenseKey = process.env.GEOLITE_LICENSE_KEY; + +//https://stackoverflow.com/questions/5284147/validating-ipv4-addresses-with-regexp +const ipRegex = new RegExp(`^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$`); + +const agent = new https.Agent({ + rejectUnauthorized: (process.env.REJECT_SELF_SIGNED_CERTIFICATES || 'true').toLowerCase() ==='true' +}); + +const axios_instance = axios.create({ + httpsAgent: agent +}); + +router.get("/geolocateIp", async (req, res) => { + try { + if(!(geoliteAccountId && geoliteLicenseKey)) { + return res.status(501).send('GeoLite information missing!'); + } + + const { ipAddress } = req.body; + ipRegex.lastIndex = 0; + + if(!ipAddress || !ipRegex.test(ipAddress)) { + return res.status(400).send('Invalid IP address sent!'); + } + + const response = await axios_instance.get(`${geoliteUrlBase}/${ipAddress}`, { + auth: { + username: geoliteAccountId, + password: geoliteLicenseKey + } + }); + return res.send(response.data); + } catch (error) { + res.status(503); + res.send(error); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 435635a..1e2a357 100644 --- a/backend/server.js +++ b/backend/server.js @@ -15,6 +15,7 @@ const ActivityMonitor = require('./tasks/ActivityMonitor'); const SyncTask = require('./tasks/SyncTask'); const BackupTask = require('./tasks/BackupTask'); const logRouter = require('./routes/logging'); +const utilsRouter = require('./routes/utils'); const dbInstance = require("./db"); @@ -109,6 +110,7 @@ app.use('/sync', authenticate , syncRouter.router,()=>{/* #swagger.tags = ['Syn app.use('/stats', authenticate , statsRouter,()=>{/* #swagger.tags = ['Stats']*/}); // mount the API router at /stats, with JWT middleware app.use('/backup', authenticate , backupRouter.router,()=>{/* #swagger.tags = ['Backup']*/}); // mount the API router at /backup, with JWT middleware app.use('/logs', authenticate , logRouter.router,()=>{/* #swagger.tags = ['Logs']*/}); // mount the API router at /logs, with JWT middleware +app.use('/utils', authenticate, utilsRouter, ()=>{/* #swagger.tags = ['Utils']*/}); // mount the API router at /utils, with JWT middleware const swaggerUi = require('swagger-ui-express'); const swaggerDocument = require('./swagger.json'); From c1d3dec173199eba1ee6428a8c933462ff19ca20 Mon Sep 17 00:00:00 2001 From: fuzz <> Date: Wed, 25 Oct 2023 18:07:47 +0100 Subject: [PATCH 2/8] use proxy, set up modal in frontend Add row to session card with IP Address. Clickable, opens modal. Modal WIP - displays general info about IP location (maybe can include a map within this modal?) --- backend/routes/utils.js | 2 +- src/pages/components/ip-info.js | 30 ++++++++++++ src/pages/components/sessions/session-card.js | 48 +++++++++++++++++-- src/setupProxy.js | 7 +++ 4 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 src/pages/components/ip-info.js diff --git a/backend/routes/utils.js b/backend/routes/utils.js index 12d5bf8..4a1443c 100644 --- a/backend/routes/utils.js +++ b/backend/routes/utils.js @@ -21,7 +21,7 @@ const axios_instance = axios.create({ httpsAgent: agent }); -router.get("/geolocateIp", async (req, res) => { +router.post("/geolocateIp", async (req, res) => { try { if(!(geoliteAccountId && geoliteLicenseKey)) { return res.status(501).send('GeoLite information missing!'); diff --git a/src/pages/components/ip-info.js b/src/pages/components/ip-info.js new file mode 100644 index 0000000..a3c1b76 --- /dev/null +++ b/src/pages/components/ip-info.js @@ -0,0 +1,30 @@ +import React from "react"; +import Loading from "./general/loading"; +import Modal from 'react-bootstrap/Modal'; + +export default function IpInfoModal(props) { + let modalBody = ; + + if(props.geodata) { + modalBody = +

City: {props.geodata.city.names['en']}

+

Country: {props.geodata.country.names['en']}

+

Postcode: {props.geodata.postal.code}

+

ISP: {props.geodata.traits.autonomous_system_organization}

+
+ } + + return ( +
+ props.onHide()}> + + + Geolocation info for {props.ipAddress} + + + + {modalBody} + +
+ ); +} \ No newline at end of file diff --git a/src/pages/components/sessions/session-card.js b/src/pages/components/sessions/session-card.js index d597b9c..327b281 100644 --- a/src/pages/components/sessions/session-card.js +++ b/src/pages/components/sessions/session-card.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, {useState} from "react"; import { Link } from 'react-router-dom'; import Card from 'react-bootstrap/Card'; import Row from 'react-bootstrap/Row'; @@ -11,7 +11,9 @@ import PauseFillIcon from "remixicon-react/PauseFillIcon"; import { clientData } from "../../../lib/devices"; import Tooltip from "@mui/material/Tooltip"; +import IpInfoModal from '../ip-info'; +import axios from 'axios'; function ticksToTimeString(ticks) { // Convert ticks to seconds @@ -56,7 +58,7 @@ function convertBitrate(bitrate) { } } -function sessionCard(props) { +function SessionCard(props) { const cardStyle = { backgroundImage: `url(Proxy/Items/Images/Backdrop?id=${(props.data.session.NowPlayingItem.SeriesId ? props.data.session.NowPlayingItem.SeriesId : props.data.session.NowPlayingItem.Id)}&fillHeight=320&fillWidth=213&quality=80), linear-gradient(to right, #00A4DC, #AA5CC3)`, height:'100%', @@ -69,9 +71,42 @@ function sessionCard(props) { height:'100%', }; + const token = localStorage.getItem('token'); + + const [modalVisible, setModalVisible] = useState(false); + const [modalData, setModalData] = useState(); + + function showModal() { + const fetchData = async () => { + const result = await axios.post(`/utils/geolocateIp`, { + ipAddress: props.data.session.RemoteEndPoint + }, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + } + }); + setModalData(result.data); + }; + + if(!modalData) { + fetchData(); + } + + setModalVisible(true); + } + + function hideModal() { + setModalVisible(false); + } return ( +
@@ -99,6 +134,13 @@ function sessionCard(props) { {props.data.session.NowPlayingItem.SubtitleStream} + + + + IP Address: {props.data.session.RemoteEndPoint} + + + @@ -223,4 +265,4 @@ function sessionCard(props) { ); } -export default sessionCard; +export default SessionCard; diff --git a/src/setupProxy.js b/src/setupProxy.js index efff95e..2467e96 100644 --- a/src/setupProxy.js +++ b/src/setupProxy.js @@ -64,6 +64,13 @@ module.exports = function(app) { changeOrigin: true, }) ); + app.use( + '/utils', + createProxyMiddleware({ + target: `http://127.0.0.1:${process.env.PORT || 3003}`, + changeOrigin: true + }) + ); console.log(`Proxy middleware applied`); From e1c628900ed5aa36d46065e7796f6c14606cef23 Mon Sep 17 00:00:00 2001 From: fuzz <> Date: Fri, 3 Nov 2023 12:40:32 +0000 Subject: [PATCH 3/8] increase stat card size add padding to image --- src/pages/css/statCard.css | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pages/css/statCard.css b/src/pages/css/statCard.css index ccd4e52..1e8d2b6 100644 --- a/src/pages/css/statCard.css +++ b/src/pages/css/statCard.css @@ -14,12 +14,12 @@ background-color: var(--background-color)!important; color: white; max-width: 500px; - max-height: 180px; + max-height: 190px; } .stat-card-banner { - max-width: 120px !important; + max-width: 127px !important; } @@ -27,8 +27,9 @@ .stat-card-image { - width: 120px !important; - height: 180px; + width: 127px !important; + height: 190px; + padding: 5px; } .stat-card-icon From ba6a885e40271d6c38c991d867899a8d64a78bfb Mon Sep 17 00:00:00 2001 From: fuzz <> Date: Fri, 3 Nov 2023 12:51:57 +0000 Subject: [PATCH 4/8] merge fix --- src/pages/components/sessions/session-card.js | 4 +++- src/pages/css/statCard.css | 9 ++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/pages/components/sessions/session-card.js b/src/pages/components/sessions/session-card.js index 41d2cef..836a3a5 100644 --- a/src/pages/components/sessions/session-card.js +++ b/src/pages/components/sessions/session-card.js @@ -137,7 +137,9 @@ function SessionCard(props) { - IP Address: {props.data.session.RemoteEndPoint} + + IP Address: {props.data.session.RemoteEndPoint} + diff --git a/src/pages/css/statCard.css b/src/pages/css/statCard.css index 1e8d2b6..ccd4e52 100644 --- a/src/pages/css/statCard.css +++ b/src/pages/css/statCard.css @@ -14,12 +14,12 @@ background-color: var(--background-color)!important; color: white; max-width: 500px; - max-height: 190px; + max-height: 180px; } .stat-card-banner { - max-width: 127px !important; + max-width: 120px !important; } @@ -27,9 +27,8 @@ .stat-card-image { - width: 127px !important; - height: 190px; - padding: 5px; + width: 120px !important; + height: 180px; } .stat-card-icon From 3126d636f8e622031c7c9b0a58f45efbac356436 Mon Sep 17 00:00:00 2001 From: fuzz <> Date: Fri, 3 Nov 2023 13:03:42 +0000 Subject: [PATCH 5/8] explicitly move to new row otherwise ends up on same line as subtitle info --- src/pages/components/sessions/session-card.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/components/sessions/session-card.js b/src/pages/components/sessions/session-card.js index 836a3a5..bde79f2 100644 --- a/src/pages/components/sessions/session-card.js +++ b/src/pages/components/sessions/session-card.js @@ -129,14 +129,16 @@ function SessionCard(props) { {props.data.session.Client + " " + props.data.session.ApplicationVersion} {props.data.session.PlayState.PlayMethod} - {(props.data.session.NowPlayingItem.MediaStreams ? '( '+props.data.session.NowPlayingItem.MediaStreams.find(stream => stream.Type==='Video')?.Codec.toUpperCase()+(props.data.session.TranscodingInfo? ' - '+props.data.session.TranscodingInfo.VideoCodec.toUpperCase() : '')+' - '+convertBitrate(props.data.session.TranscodingInfo ? props.data.session.TranscodingInfo.Bitrate :props.data.session.NowPlayingItem.MediaStreams.find(stream => stream.Type==='Video')?.BitRate)+' )':'')} + {(props.data.session.NowPlayingItem.MediaStreams ? '( '+props.data.session.NowPlayingItem.MediaStreams.find(stream => stream.Type==='Video')?.Codec.toUpperCase()+(props.data.session.TranscodingInfo? ' - '+props.data.session.TranscodingInfo.VideoCodec.toUpperCase() : '')+' - '+convertBitrate(props.data.session.TranscodingInfo ? props.data.session.TranscodingInfo.Bitrate :props.data.session.NowPlayingItem.MediaStreams.find(stream => stream.Type==='Video')?.BitRate)+' )':'')} {props.data.session.NowPlayingItem.SubtitleStream} - + + + IP Address: {props.data.session.RemoteEndPoint} From 300c6d5dfdebdfd77c4630e2cf10c29c64262037 Mon Sep 17 00:00:00 2001 From: fuzz <> Date: Fri, 3 Nov 2023 17:03:50 +0000 Subject: [PATCH 6/8] update ipv4 regex - filter out local ip addresses - do not show modal for local ip addresses, only remote - tooltip to show local/remote --- backend/routes/utils.js | 4 +-- src/pages/components/sessions/session-card.js | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/backend/routes/utils.js b/backend/routes/utils.js index 4a1443c..60f055c 100644 --- a/backend/routes/utils.js +++ b/backend/routes/utils.js @@ -10,8 +10,8 @@ const geoliteUrlBase = 'https://geolite.info/geoip/v2.1/city'; const geoliteAccountId = process.env.GEOLITE_ACCOUNT_ID; const geoliteLicenseKey = process.env.GEOLITE_LICENSE_KEY; -//https://stackoverflow.com/questions/5284147/validating-ipv4-addresses-with-regexp -const ipRegex = new RegExp(`^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$`); +//https://stackoverflow.com/a/29268025 +const ipRegex = new RegExp(/\b(?!(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168))(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/); const agent = new https.Agent({ rejectUnauthorized: (process.env.REJECT_SELF_SIGNED_CERTIFICATES || 'true').toLowerCase() ==='true' diff --git a/src/pages/components/sessions/session-card.js b/src/pages/components/sessions/session-card.js index bde79f2..552c0b4 100644 --- a/src/pages/components/sessions/session-card.js +++ b/src/pages/components/sessions/session-card.js @@ -1,4 +1,4 @@ -import React, {useState} from "react"; +import React, {useState,useEffect} from "react"; import { Link } from 'react-router-dom'; import Card from 'react-bootstrap/Card'; import Row from 'react-bootstrap/Row'; @@ -73,10 +73,24 @@ function SessionCard(props) { const token = localStorage.getItem('token'); + const ipv4Regex = new RegExp(/\b(?!(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168))(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/); + const [modalVisible, setModalVisible] = useState(false); const [modalData, setModalData] = useState(); + const [isRemoteSession, setIsRemoteSession] = useState(); + + useEffect(() => { + ipv4Regex.lastIndex = 0; + if(ipv4Regex.test(props.data.session.RemoteEndPoint)) { + setIsRemoteSession(true) + } + }, []); function showModal() { + if(!isRemoteSession) { + return + } + const fetchData = async () => { const result = await axios.post(`/utils/geolocateIp`, { ipAddress: props.data.session.RemoteEndPoint @@ -140,7 +154,17 @@ function SessionCard(props) { - IP Address: {props.data.session.RemoteEndPoint} + + {isRemoteSession ? + + IP Address: {props.data.session.RemoteEndPoint} + + : + + IP Address: {props.data.session.RemoteEndPoint} + + } + From 3089cb2c3f384b1a323e373badb7560d3b8bcdd3 Mon Sep 17 00:00:00 2001 From: fuzz <> Date: Fri, 3 Nov 2023 18:01:28 +0000 Subject: [PATCH 7/8] use table-style for modal more consistent with stream info modal from activity table --- src/pages/components/ip-info.js | 53 ++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/pages/components/ip-info.js b/src/pages/components/ip-info.js index a3c1b76..5d3d040 100644 --- a/src/pages/components/ip-info.js +++ b/src/pages/components/ip-info.js @@ -1,16 +1,49 @@ import React from "react"; import Loading from "./general/loading"; -import Modal from 'react-bootstrap/Modal'; +import { Button, Modal } from "react-bootstrap"; + +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; export default function IpInfoModal(props) { let modalBody = ; if(props.geodata) { - modalBody = -

City: {props.geodata.city.names['en']}

-

Country: {props.geodata.country.names['en']}

-

Postcode: {props.geodata.postal.code}

-

ISP: {props.geodata.traits.autonomous_system_organization}

+ modalBody = +
+ + + + + + + + + + + City + {props.geodata.city.names['en']} + + + Country + {props.geodata.country.names['en']} + + + Postcode + {props.geodata.postal.code} + + + ISP + {props.geodata.traits.autonomous_system_organization} + + +
+
+
} @@ -21,9 +54,13 @@ export default function IpInfoModal(props) { Geolocation info for {props.ipAddress} - - + {modalBody} + + +
); From c9b64aea1ebae3a306fc858b22d2b5c68803c943 Mon Sep 17 00:00:00 2001 From: Thegan Govender Date: Sat, 4 Nov 2023 23:34:29 +0200 Subject: [PATCH 8/8] Update session-card.js Removed Tooltip as it was unnecessary Also added a check in the frontend. if the env variables where not set for the GEOLITE API, it would disable the option entirely --- src/pages/components/sessions/session-card.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/components/sessions/session-card.js b/src/pages/components/sessions/session-card.js index 552c0b4..674fe3f 100644 --- a/src/pages/components/sessions/session-card.js +++ b/src/pages/components/sessions/session-card.js @@ -154,8 +154,8 @@ function SessionCard(props) { - - {isRemoteSession ? + + {isRemoteSession && (process.env.GEOLITE_ACCOUNT_ID && process.env.GEOLITE_LICENSE_KEY) ? IP Address: {props.data.session.RemoteEndPoint} @@ -164,7 +164,7 @@ function SessionCard(props) { IP Address: {props.data.session.RemoteEndPoint} } - +