From dc9e96ca3db96b1c475bf7a499918f3eb2fefccf Mon Sep 17 00:00:00 2001 From: AdamSBlack Date: Sun, 14 Nov 2021 15:38:10 +0000 Subject: [PATCH] various --- Anetha/index.js | 79 +++++++++++++++++++++++++++++++----------- config.sample.js | 2 ++ routes/api/realtime.js | 13 ++++--- server.js | 8 +++++ 4 files changed, 76 insertions(+), 26 deletions(-) diff --git a/Anetha/index.js b/Anetha/index.js index fe018de..3445b23 100644 --- a/Anetha/index.js +++ b/Anetha/index.js @@ -3,63 +3,87 @@ const fs = require('fs'); const cookie = require('cookie') const jsonwebtoken = require('jsonwebtoken'); const models = require('./../models/index.model') +const config = require('./../config') +const httpsServer = require('https'); +const httpServer = require('http'); +const { readFileSync } = require('fs'); const authenticationController = require('./../controllers/authentication'); const deviceController = require('./../controllers/devices'); const { ws } = require('../routes/api/realtime'); +const log4js = require('log4js'); + +const logger = log4js.getLogger('default'); + + + + let wss; -async function __server() { - wss = new WebSocket.WebSocketServer({ path: '/ws/v2/', port: 4040, handshakeTimeout: 500 }); +function __server() { + let server; - console.log("src") + if (config.athena.secure) { + server = httpsServer.createServer({ + cert: readFileSync(config.sslCrt), + key: readFileSync(config.sslKey) + }); + } else { + server = httpServer.createServer(); + } - const interval = setInterval(function ping() { + wss = new WebSocket.WebSocketServer({ server }, { path: '/ws/v2/', handshakeTimeout: 500 }); + + const interval = setInterval(() => { wss.clients.forEach(function each(ws) { if (ws.isAlive === false) { + logger.info(`Athena(Heartbeat) - Terminated ${ws.dongleId} - ${ws._socket.remoteAddress}`) wss.retropilotFunc.actionLogger(null, null, "ATHENA_DEVICE_TIMEOUT_FORCE_DISCONNECT", null, ws._socket.remoteAddress, null, ws.dongleId); - console.log("TERMINATED", ws.dongleId) return ws.terminate(); } ws.isAlive = false; ws.ping(); }); - }, 5000); + }, config.athena.socket.heartbeatFrequency ? config.athena.socket.heartbeatFrequency : 5000); + + + server.listen(config.athena.socket.port, () => { + logger.info(`Athena(Server) - UP @ ${config.athena.host}:${config.athena.port}`) + }) + wss.on('connection', manageConnection) wss.on('close', function close() { + logger.info(`Athena(Websocket) - DOWN`) clearInterval(interval); }); - } async function heartbeat() { - if (this.heartbeat - Date.now() > 300) { - deviceController.updateLastPing(this.device_id, this.dongleId); - } - this.isAlive = true; this.heartbeat = Date.now(); + ; } async function manageConnection(ws, res) { + logger.info(`Athena(Websocket) - New Connection ${ws._socket.remoteAddress}`) ws.badMessages = 0; ws.isAlive = true; ws.heartbeat = Date.now(); ws.on('pong', heartbeat); + var cookies = cookie.parse(res.headers.cookie); + ws.on('message', async function incoming(message) { heartbeat.call(ws) if (!ws.dongleId) { - // wss.retropilotFunc.actionLogger(null, null, "ATHENA_DEVICE_UNATHENTICATED_MESSAGE", null, ws._socket.remoteAddress, JSON.stringify([message]), ws.dongleId); + wss.retropilotFunc.actionLogger(null, null, "ATHENA_DEVICE_UNATHENTICATED_MESSAGE", null, ws._socket.remoteAddress, JSON.stringify([message]), ws.dongleId); console.log("unauthenticated message, discarded"); return null; } - console.log(message); - const json = JSON.parse(message.toString('utf8')) console.log(json); @@ -80,15 +104,16 @@ async function manageConnection(ws, res) { if (await wss.retropilotFunc.authenticateDongle(ws, res, cookies) === false) { - ws.close(); + ws.terminate(); } + //ws.send(JSON.stringify(await commandBuilder('reboot'))) } -__server(); + __server(); wss.retropilotFunc = { @@ -100,11 +125,23 @@ wss.retropilotFunc = { } }) + + return websocket; }, authenticateDongle: async (ws, res, cookies) => { - unsafeJwt = jsonwebtoken.decode(cookies.jwt); + + + try { + unsafeJwt = jsonwebtoken.decode(cookies.jwt); + } catch(e) { + logger.info(`Athena(Websocket) - AUTHENTICATION FAILED (INVALID JWT) IP: ${ws._socket.remoteAddress}`) + wss.retropilotFunc.actionLogger(null, null, "ATHENA_DEVICE_AUTHENTICATE_INVALID", null, ws._socket.remoteAddress, JSON.stringify({ jwt: cookies.jwt }), null); + return false; + } + + const device = await deviceController.getDeviceFromDongle(unsafeJwt.identity) let verifiedJWT; @@ -112,6 +149,7 @@ wss.retropilotFunc = { try { verifiedJWT = jsonwebtoken.verify(cookies.jwt, device.public_key, { ignoreNotBefore: true }); } catch (err) { + logger.info(`Athena(Websocket) - AUTHENTICATION FAILED (BAD JWT, CHECK SIGNATURE) IP: ${ws._socket.remoteAddress}`) wss.retropilotFunc.actionLogger(null, null, "ATHENA_DEVICE_AUTHENTICATE_INVALID", null, ws._socket.remoteAddress, JSON.stringify({ jwt: cookies.jwt }), null); return false; } @@ -119,14 +157,13 @@ wss.retropilotFunc = { if (verifiedJWT.identify === unsafeJwt.identify) { ws.dongleId = device.dongle_id ws.device_id = device.id - console.log("AUTHENTICATED DONGLE", ws.dongleId) - wss.retropilotFunc.actionLogger(null, device.id, "ATHENA_DEVICE_AUTHENTICATE_SUCCESS", null, ws._socket.remoteAddress, null); + logger.info(`Athena(Websocket) - AUTHENTICATED IP: ${ws._socket.remoteAddress} DONGLE ID: ${ws.dongleId} DEVICE ID: ${ws.device_id}`) return true; } else { - console.log("UNAUTHENTICATED DONGLE"); - wss.retropilotFunc.actionLogger(null, device.id, "ATHENA_DEVICE_AUTHENTICATE_FAILURE", null, ws._socket.remoteAddress, JSON.stringify({ jwt: cookies.jwt }), null); + logger.info(`Athena(Websocket) - AUTHENTICATION FAILED (BAD CREDENTIALS) IP: ${ws._socket.remoteAddress}`); + return false; } }, diff --git a/config.sample.js b/config.sample.js index c4d9b9f..267c2a1 100644 --- a/config.sample.js +++ b/config.sample.js @@ -41,10 +41,12 @@ var config = { athena: { enabled: true, // Enables Athena service + secure: true, // Disables crypto on Websocket server - use for testing on local network, change ATHENA_HOST in openpilot to ws:// instead of wss:// api: { ratelimit: 100 // Maxmium hits to /realtime/* per 30s }, socket: { + port: 4040, heartbeatFrequency: 5000 // Higher the number = lower traffic, varies on how many devices are connected } } diff --git a/routes/api/realtime.js b/routes/api/realtime.js index 2400bea..5ff761a 100644 --- a/routes/api/realtime.js +++ b/routes/api/realtime.js @@ -6,12 +6,12 @@ const deviceController = require('./../../controllers/devices') const models = require('./../../models/index.model') const whitelistParams = { - getmessage: false, + getmessage: true, getversion: true, setnavdestination: true, - listdatadirectory: false, + listdatadirectory: true, reboot: true, - uploadfiletourl: false, + uploadfiletourl: true, listuploadqueue: true, cancelupload: true, primeactivated: true, @@ -41,7 +41,7 @@ router.get('/dongle/:dongle_id/connected', async (req, res) => { return res.status(200).json({success: true, dongle_id: device.dongle_id, data: deviceConnected}); }) -router.get('/dongle/:dongle_id/send/:method', async (req, res) => { +router.get('/dongle/:dongle_id/send/:method/', async (req, res) => { const account = await authenticationController.getAuthenticatedAccount(req, res); if (account == null) { return res.status(403).json({error: true, errorMsg: 'Unauthenticated', errorObject: {authenticated: false}})} @@ -52,8 +52,11 @@ router.get('/dongle/:dongle_id/send/:method', async (req, res) => { // TODO support delgation of access // TODO remove indication of dongle existing if (device.account_id !== account.id) {return res.status(403).json({error: true, errorMsg: 'unauthorised', errorObject: {authenticated: true, dongle_exists: true, authorised_user: false}})} + let data; + + data = await req.athenaWebsocketTemp.invoke(req.params.method, null, device.dongle_id, account.id); + - const data = await req.athenaWebsocketTemp.invoke(req.params.method, null, device.dongle_id, account.id); return res.status(200).json({success: true, dongle_id: device.dongle_id, method: req.params.method, data: data}); diff --git a/server.js b/server.js index 18c8336..0ac8f07 100644 --- a/server.js +++ b/server.js @@ -15,6 +15,7 @@ log4js.configure({ }); + const logger = log4js.getLogger('default'); // TODO evaluate if this is the best way to determine the root of project global.__basedir = __dirname; @@ -57,6 +58,13 @@ const web = async () => { db = _models.models.__db; models = _models.models; + app.use(function(req, res, next) { + res.header('Access-Control-Allow-Origin', "http://localhost:3000"); + res.header('Access-Control-Allow-Credentials', true); + res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); + next(); + }); + const _controllers = await controllers(models, logger); controllers = _controllers;