diff --git a/.env_example b/.env_example index 319d1c26..311fe592 100644 --- a/.env_example +++ b/.env_example @@ -3,5 +3,4 @@ ROOT=https://your-produciton-server.com KEY= USER_JOHNDOE= USER_TEST= -LOCALHOST=127.0.0.1 -LOCALHOSTv6=::1 +MAPBOX= diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 269894e8..fea0e1dd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,6 +14,7 @@ jobs: NODE_ENV: ${{ vars.NODE_ENV }} KEY: ${{ secrets.KEY }} USER_TEST: ${{ secrets.USER_TEST }} + MAPBOX: ${{ secrets.MAPBOX }} steps: - uses: actions/checkout@v3 @@ -27,7 +28,7 @@ jobs: - run: npm run build --if-present - name: Start server run: | - sudo NODE_ENV=$NODE_ENV KEY=$KEY USER_TEST=$USER_TEST npm start & + sudo NODE_ENV=$NODE_ENV KEY=$KEY USER_TEST=$USER_TEST MAPBOX=$MAPBOX npm start & sleep 16 # Give server some time to start - name: Check if server is running run: | diff --git a/README.md b/README.md index b33c9377..a3b7040e 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,11 @@ This application is designed to be used with the [OSMAND+ mobile app]([url](http Due to a plugin called [Triprecording]([url](https://osmand.net/docs/user/plugins/trip-recording/)) Using the above link or by [clicking here](https://osmand.net/docs/user/plugins/trip-recording#recording-settings) more information can be found to setup webtracking or "online tracking" +## Mapbox +Mapbox requires an access token. +These can be created for free [register required]([url](https://tiles.mapbox.com/signup)) +Once available, add it to the envrionment variables. + ## DEMO At this point, there is no demo, but here is a screenshot: ![Demo LOREX, markers on a map in berlin, information about speed and distance on the right](https://raw.githubusercontent.com/Type-Style/LOREX/dev/demo.png) diff --git a/demo.png b/demo.png index f012a3f9..8cfb494d 100644 Binary files a/demo.png and b/demo.png differ diff --git a/httpdocs/color-table.svg b/httpdocs/color-table.svg index d2999418..4e051225 100644 --- a/httpdocs/color-table.svg +++ b/httpdocs/color-table.svg @@ -1,159 +1 @@ - - - - - - HEX - - - main - - - info - - - alert - - - success - - - neutral - - - - OKLCH - - - - - 900 - - - - 750 - - - - 625 - - - 500 - - - 375 - - - 250 - - - 100 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file +HEXmaininfoalertsuccessneutralOKLCH900750625500375250100 \ No newline at end of file diff --git a/httpdocs/icon.png b/httpdocs/icon.png index 827ec59b..8cfbcb6f 100644 Binary files a/httpdocs/icon.png and b/httpdocs/icon.png differ diff --git a/httpdocs/images/marker.svg b/httpdocs/images/marker.svg index da4c914c..469c94a9 100644 --- a/httpdocs/images/marker.svg +++ b/httpdocs/images/marker.svg @@ -1,4 +1 @@ - - Marker Arrow - - \ No newline at end of file +Marker Arrow \ No newline at end of file diff --git a/init/generatePassword.js b/init/generatePassword.js index c3b26b0f..40961337 100644 --- a/init/generatePassword.js +++ b/init/generatePassword.js @@ -21,6 +21,13 @@ const rl = readline.createInterface({ output: process.stdout }); + +if (!process.env.KEY) { + console.error("KEY is missing! Please provide Environment Variable KEY. \nExample: KEY=your-key node ./init/generatePassword.js"); + return; +} + + // Prompt user for input rl.question('Enter Password to be generated: ', async (input) => { const cryptedPassword = await crypt(input); diff --git a/package-lock.json b/package-lock.json index c241ae7e..da70d12a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-leaflet": "^4.2.1", - "react-router-dom": "^6.26.0", + "react-router-dom": "^6.26.1", "toobusy-js": "^0.5.1" }, "devDependencies": { @@ -1967,9 +1967,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.0.tgz", - "integrity": "sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz", + "integrity": "sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -11564,12 +11564,12 @@ } }, "node_modules/react-router": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.0.tgz", - "integrity": "sha512-wVQq0/iFYd3iZ9H2l3N3k4PL8EEHcb0XlU2Na8nEwmiXgIUElEH6gaJDtUQxJ+JFzmIXaQjfdpcGWaM6IoQGxg==", + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.1.tgz", + "integrity": "sha512-kIwJveZNwp7teQRI5QmwWo39A5bXRyqpH0COKKmPnyD2vBvDwgFXSqDUYtt1h+FEyfnE8eXr7oe0MxRzVwCcvQ==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.19.0" + "@remix-run/router": "1.19.1" }, "engines": { "node": ">=14.0.0" @@ -11579,13 +11579,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.0.tgz", - "integrity": "sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ==", + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.1.tgz", + "integrity": "sha512-veut7m41S1fLql4pLhxeSW3jlqs+4MtjRLj0xvuCEXsxusJCbs6I8yn9BxzzDX2XDgafrccY6hwjmd/bL54tFw==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.19.0", - "react-router": "6.26.0" + "@remix-run/router": "1.19.1", + "react-router": "6.26.1" }, "engines": { "node": ">=14.0.0" diff --git a/package.json b/package.json index aec3d180..43059118 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-leaflet": "^4.2.1", - "react-router-dom": "^6.26.0", + "react-router-dom": "^6.26.1", "toobusy-js": "^0.5.1" }, "_moduleAliases": { diff --git a/src/app.ts b/src/app.ts index a06be028..a37f7311 100644 --- a/src/app.ts +++ b/src/app.ts @@ -40,7 +40,7 @@ app.use((req, res, next) => { // clean up IPv6 Addresses }) if (process.env.NODE_ENV != "development") { - app.use(helmet({ contentSecurityPolicy: { directives: { "default-src": "'self'", "img-src": "*" } } })); + app.use(helmet({ contentSecurityPolicy: { directives: { "default-src": "'self'", "img-src": "*" } }, referrerPolicy: { policy: "strict-origin-when-cross-origin" } })); } app.use(cache); app.use(compression()) @@ -58,7 +58,7 @@ app.set('trust proxy',true); // routes app.get(['/', '/login'], (req, res) => { logger.log(req.ip + " - " + res.locals.ip, true); - res.render("index"); + res.render("index", {"mapbox": process.env.MAPBOX, "root": process.env.ROOT}); }); app.use('/write', writeRouter); diff --git a/src/client/components/App.tsx b/src/client/components/App.tsx index 62290271..19e7916e 100644 --- a/src/client/components/App.tsx +++ b/src/client/components/App.tsx @@ -4,6 +4,7 @@ import { useColorScheme } from '@mui/material/styles'; import { useMediaQuery } from '@mui/material'; import Start from '../pages/Start'; import Login from '../pages/Login'; +import axios from "axios"; export const Context = createContext([]); @@ -38,18 +39,41 @@ const router = createBrowserRouter([ } ]); + const App = () => { const [userInfo, setUserInfo] = useState(convertJwt()); const [isLoggedIn, setLogin] = useState(loginDefault(userInfo)); const { mode, setMode } = useColorScheme(); const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); + const [mapToken, setMapToken] = useState(null); useEffect(() => { setMode(prefersDarkMode ? "dark" : "light"); }, [prefersDarkMode, setMode]); + + useEffect(() => { + if (!isLoggedIn) return; + const fetchToken = async () => { + try { + const token = localStorage.getItem("jwt"); + const response = await axios.get("/read/maptoken", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + setMapToken(response.data.mapbox); + } catch (error) { + console.error("Error fetching map token:", error); + } + }; + + fetchToken(); + }, [isLoggedIn]); + return ( - + ); diff --git a/src/client/components/Map.tsx b/src/client/components/Map.tsx index 6d40d427..e6b8eb41 100644 --- a/src/client/components/Map.tsx +++ b/src/client/components/Map.tsx @@ -54,18 +54,20 @@ const MultiColorPolyline = ({ cleanEntries }: { cleanEntries: Models.IEntry[] }) }); } + function Map({ entries }: { entries: Models.IEntry[] }) { if (!entries?.length) { return No Data to be displayed } - const [, , , , mode] = useContext(Context); + + const [, , , , mode, , , mapToken] = useContext(Context); const [mapStyle, setMapStyle] = useState(mode); const lastEntry = entries.at(-1); const cleanEntries = entries.filter((entry) => !entry.ignore); const cleanEntriesWithoutLast = cleanEntries.slice(0, -1); - + const replaceKeyword = "XXXREPLACEXXX"; // Function to create custom icon with dynamic className function createCustomIcon(entry: Models.IEntry) { @@ -128,7 +130,7 @@ function Map({ entries }: { entries: Models.IEntry[] }) { > diff --git a/src/client/components/Status.tsx b/src/client/components/Status.tsx index 6dd5c886..ed2690ee 100644 --- a/src/client/components/Status.tsx +++ b/src/client/components/Status.tsx @@ -30,7 +30,6 @@ function getStatusData(entries) { }, 0) / divider; // now that all values have been added together, devide by amount of them - //console.log(prop + ": " + value + " divider: " + divider); return value; } @@ -56,7 +55,6 @@ function getStatusData(entries) { function getDistance() { return cleanEntries.reduce((accumulatorValue: number, entry) => { - console.log(accumulatorValue); if (!entry.distance) { return accumulatorValue } return accumulatorValue + parseFloat(entry.distance.horizontal); }, 0) / 1000; diff --git a/src/client/scripts/layers.ts b/src/client/scripts/layers.ts index 0ede698c..68d19e50 100644 --- a/src/client/scripts/layers.ts +++ b/src/client/scripts/layers.ts @@ -40,7 +40,7 @@ export const layers:client.Layer[] = [ { name: "Mapbox Satelite Streets", attribution: '© Mapbox', - url: 'https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoidHlwZS1zdHlsZSIsImEiOiJjbGJ4aG14enEwZ2toM3BvNW5uanhuOGRvIn0.7TUEM9vA-EYSt3WW_bcsAA', + url: `https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/tiles/{z}/{x}/{y}?access_token=XXXREPLACEXXX`, markerStyle: "dark", size: 512, zoomOffset: -1 diff --git a/src/controller/read.ts b/src/controller/read.ts index 4106251f..accf7220 100644 --- a/src/controller/read.ts +++ b/src/controller/read.ts @@ -36,5 +36,15 @@ router.get('/', }); +router.get('/maptoken', + isLoggedIn, + async function mapToken(req: Request, res: Response, next: NextFunction) { + const mapbox = process.env.MAPBOX; + if (!mapbox) { return createError(res, undefined, `Missing configuration, environment variable not defined`, next); } + + res.json({ mapbox }); + } +); + export default router; diff --git a/src/tests/integration.test.ts b/src/tests/integration.test.ts index 9cad2b5b..4bea5c2b 100644 --- a/src/tests/integration.test.ts +++ b/src/tests/integration.test.ts @@ -335,4 +335,27 @@ describe('read and login', () => { expect(response.status).toBe(200); expect(response.data.entries.length).toBe(1); }); + + + + test(`unable to get maptoken without logged in`, async () => { + try { + await axios.get("http://localhost:80/read/maptoken"); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + expect(axiosError.response.status).toBe(401); + } else { + console.error(axiosError); + } + } + }); + + test(`get maptoken with login`, async () => { + const response = await verifiedRequest("http://localhost:80/read/maptoken", token); + expect(response.status).toBe(200); + expect(response.data).toBeTruthy(); + expect(response.data.mapbox).toBeTruthy(); + expect(typeof response.data.mapbox).toBe('string'); + }); }); \ No newline at end of file