diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000000000000000000000000000000000..c2c99e5f435563029cc9b12db815c75af89d054d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "<node_internals>/**" + ], + "program": "${workspaceFolder}/src/connectionTests/jwtTokenTest.js" + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 585b1c2891e35b3ed0a6ab962e58291d41356d2d..391572889384a1bd375195d7a0f910325a63dbed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "body-parser": "^1.18.3", "dotenv": "^16.0.3", "express": "^4.18.2", + "jsonwebtoken": "^9.0.0", "mongoose": "^5.3.16", "morgan": "^1.9.1", "nodemon": "^1.18.4" @@ -327,6 +328,11 @@ "node": ">=0.6.19" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -711,6 +717,14 @@ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1458,6 +1472,75 @@ "node": ">=0.10.0" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsonwebtoken/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", @@ -1485,6 +1568,11 @@ "node": ">=4" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -3466,6 +3554,11 @@ "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -3760,6 +3853,14 @@ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4332,6 +4433,64 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" }, + "jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "requires": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", @@ -4353,6 +4512,11 @@ "package-json": "^4.0.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", diff --git a/package.json b/package.json index 120d21f743f04b3600a3c3ccd4915cf018afebff..7bb577ce5b675b50dbab090e36ab09cc3d9d41a5 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "body-parser": "^1.18.3", "dotenv": "^16.0.3", "express": "^4.18.2", + "jsonwebtoken": "^9.0.0", "mongoose": "^5.3.16", "morgan": "^1.9.1", "nodemon": "^1.18.4" diff --git a/src/App.js b/src/App.js index 4209f8d9f12fd7fe97ef6f316ec26369e5392124..d3969f2b0a9e1070ba7f89ff28690f44833c755c 100644 --- a/src/App.js +++ b/src/App.js @@ -1,66 +1,110 @@ const express = require('express'); const mongoose = require('mongoose'); -const {MONGO_URI, API_KEY } = APIKeyAccess(); +const { MONGO_URI, API_KEY } = APIKeyAccess(); require('dotenv').config(); const app = express(); const port = 3000; const ParkingLocation = require('./model/ParkingLocation'); const bodyParser = require('body-parser'); +const jwt = require('jsonwebtoken'); function APIKeyAccess() { const API_KEY = process.env.REACT_APP_GOOGLE_MAPS_API_KEY; const MONGO_URI = "mongodb://localhost:27017/ParkingLocations"; return { MONGO_URI, API_KEY }; - } +} app.use(bodyParser.json()); mongoose.connect(MONGO_URI, { - useNewUrlParser: true, - useUnifiedTopology: true, + useNewUrlParser: true, + useUnifiedTopology: true, }).then(() => { - console.log('🤖: Successfully connected to:', MONGO_URI); + console.log('🤖: Successfully connected to:', MONGO_URI); }).catch((x) => { - console.error(x); - console.log('â›”ï¸ Database Error: An error has occured, please try again later') + console.error(x); + console.log('â›”ï¸ Database Error: An error has occured, please try again later'); }); +// Default admin user +const user = { id: 123, isAdmin: true }; +const token = jwt.sign(user, 'secret'); +console.log('🤖 Admin Token:', token) + app.get('/parking-locations', async (request, response) => { - try { - const parkingLocations = await ParkingLocation.find({}); - console.log(`🤖: Successfully retrieved all ${parkingLocations.length} parking locations:`); - response.send(parkingLocations); - } catch (x) { - console.error(x); - response.status(500).send('â›”ï¸ Server Error: An error has occured, please try again later'); - } + try { + const parkingLocations = await ParkingLocation.find({}); + console.log(`🤖: Successfully retrieved all ${parkingLocations.length} parking locations:`); + response.send(parkingLocations); + } catch (x) { + console.error(x); + response.status(500).send('â›”ï¸ Server Error: An error has occured, please try again later'); + } }); -app.post('/parking-locations', async (request, response) => { - try { - const { ID, Title, Description, lat, lon, city,postcode,total_spaces, spaces_available, street_address } = request.body; - const parkingLocation = new ParkingLocation({ - ID, - Title, - Description, - lat, - lon, - city, - postcode, - total_spaces, - spaces_available, - street_address - }); - await parkingLocation.save(); - response.status(201).json({ message: '🤖: Parking was successfully added to database.'}); +function isAdmin(request, response, next) { + const authHeader = request.headers.authorization; + const token = authHeader && authHeader.split(' ')[1]; + if (!token) { + response.status(401).json({ error: 'â›”ï¸: Access denied, please provide a valid token.' }); + return; + } + try { + const user = jwt.verify(token, 'secret'); + if (user && user.isAdmin) { + console.log('🤖: User is admin, proceed.') + request.user = user; + next(); + } else { + response.status(403).json({ error: 'â›”ï¸: Only admin can add parking location.' }); + } + } catch (err) { + console.error(err); + response.status(401).json({ error: 'â›”ï¸: Invalid token.' }); + } +} - } catch (x) { - console.error(x); - response.status(500).json({ error: 'â›”ï¸: Failed to add parking to database, please try again later.' }); +app.post('/parking-locations', isAdmin, async (request, response) => { + try { + const { ID, Title, Description, lat, lon, city, postcode, total_spaces, spaces_available, street_address } = request.body; + const parkingLocation = new ParkingLocation({ + ID, + Title, + Description, + lat, + lon, + city, + postcode, + total_spaces, + spaces_available, + street_address + }); + await parkingLocation.save(); + response.status(201).json({ message: '🤖: Parking was successfully added to database.' }); + + } catch (x) { + console.error(x); + response.status(500).json({ error: 'â›”ï¸: Failed to add parking to database, please try again later.' }); + } +}); + +app.delete('/parking-locations/:id', isAdmin, async (request, response) => { + try { + const parkingLocation = await ParkingLocation.findById(request.params.id); + if (!parkingLocation) { + response.status(404).json({ error: 'â›”ï¸: Parking spot not found.' }); + return; + } + await parkingLocation.remove(); + response.json({ message: '🤖: Parking spot deleted.' }); + } catch (x) { + console.error(x); + response.status(500).json({ error: 'â›”ï¸: Failed to delete parking spot, please try again later.' }); } }); + app.listen(port, () => { console.log(`🤖: Now tuning into http://localhost:${port}`); }); diff --git a/src/connectionTests/jwtTokenTest.js b/src/connectionTests/jwtTokenTest.js new file mode 100644 index 0000000000000000000000000000000000000000..0fb0ef8166828856dbc8c155bb7d4bc49a353c49 --- /dev/null +++ b/src/connectionTests/jwtTokenTest.js @@ -0,0 +1,6 @@ +const jwt = require('jsonwebtoken'); + +const user = { id: 0101010101, isAdmin: true }; +const token = jwt.sign(user, 'secret'); + +console.log(token);