retropilot-server/src/server/websocket/athena/index.js

181 lines
5.7 KiB
JavaScript
Raw Normal View History

2022-03-21 04:56:42 -06:00
import cookie from 'cookie';
import { readFileSync } from 'fs';
import httpServer from 'http';
import httpsServer from 'https';
import jsonwebtoken from 'jsonwebtoken';
2022-01-12 08:02:30 -07:00
import log4js from 'log4js';
import { WebSocketServer } from 'ws';
2022-03-21 17:38:56 -06:00
import { AthenaActionLog, AthenaReturnedData } from '../../../models';
2022-03-21 04:56:42 -06:00
import deviceController from '../../controllers/devices';
import helperFunctions from './helpers';
2022-03-22 09:14:08 -06:00
const logger = log4js.getLogger();
// TODO: I think we need to provide wss as a param here
const helpers = helperFunctions();
let wss;
2022-01-08 16:28:15 -07:00
function __server() {
let server;
2022-02-28 22:28:28 -07:00
if (process.env.ATHENA_SECURE && process.env.SSL_CRT) {
server = httpsServer.createServer({
2022-02-28 22:28:28 -07:00
cert: readFileSync(process.env.SSL_CRT),
key: readFileSync(process.env.SSL_KEY),
});
} else {
server = httpServer.createServer();
}
2022-01-12 08:02:30 -07:00
wss = new WebSocketServer({ server }, { path: '/ws/v2/', handshakeTimeout: 500 });
2022-01-07 18:35:55 -07:00
const interval = setInterval(() => {
2022-01-07 18:35:55 -07:00
wss.clients.forEach((ws) => {
if (ws.isAlive === false) {
2022-01-07 18:35:55 -07:00
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);
if (ws.dongleId) {
helpers.deviceStatus(ws.dongleId, false);
}
2022-01-08 16:07:09 -07:00
ws.terminate();
return;
}
ws.isAlive = false;
ws.ping();
});
}, process.env.ATHENA_SOCKET_HEARTBEAT_FREQ ? process.env.ATHENA_SOCKET_HEARTBEAT_FREQ : 5000);
server.listen(process.env.ATHENA_SOCKET_PORT, () => {
logger.info(`Athena(Server) - UP @ ${process.env.ATHENA_SOCKET_HOST}:${process.env.ATHENA_SOCKET_PORT}`);
2022-01-07 18:35:55 -07:00
});
2022-01-07 18:35:55 -07:00
wss.on('connection', manageConnection);
wss.on('close', () => {
logger.info('Athena(Websocket) - DOWN');
clearInterval(interval);
});
}
async function heartbeat() {
this.isAlive = true;
this.heartbeat = Date.now();
2022-01-07 18:35:55 -07:00
if (this.dongleId) {
helpers.deviceStatus(this.dongleId, true);
}
}
async function manageConnection(ws, res) {
2022-01-07 18:35:55 -07:00
logger.info(`Athena(Websocket) - New Connection ${ws._socket.remoteAddress}`);
ws.badMessages = 0;
ws.isAlive = true;
ws.heartbeat = Date.now();
ws.on('pong', heartbeat);
const cookies = cookie.parse(res.headers.cookie);
2022-01-07 18:35:55 -07:00
ws.on('message', async (message) => {
heartbeat.call(ws);
if (!ws.dongleId) {
2022-03-22 09:14:08 -06:00
wss.retropilotFunc.actionLogger(null, null, 'ATHENA_DEVICE_UNAUTHENTICATED_MESSAGE', null, ws._socket.remoteAddress, JSON.stringify([message]), ws.dongleId);
2022-01-07 18:35:55 -07:00
console.log('unauthenticated message, discarded');
2022-01-08 16:07:09 -07:00
return;
}
2022-01-07 18:35:55 -07:00
const json = JSON.parse(message.toString('utf8'));
console.log(json);
2022-01-07 18:35:55 -07:00
console.log({ device_id: ws.device_id, uuid: json.id });
2022-03-21 17:38:56 -06:00
console.log(await AthenaReturnedData.update({
data: JSON.stringify(json),
2022-01-08 13:43:57 -07:00
resolved_at: Date.now(),
2022-01-07 18:35:55 -07:00
}, { where: { device_id: ws.device_id, uuid: json.id } }));
2022-01-07 18:35:55 -07:00
wss.retropilotFunc.actionLogger(null, null, 'ATHENA_DEVICE_MESSAGE_UNKNOWN', null, ws._socket.remoteAddress, JSON.stringify([message]), ws.dongleId);
2022-01-07 18:35:55 -07:00
console.log(json);
2022-01-07 18:35:55 -07:00
helpers.incoming(ws, res, json);
});
if (await wss.retropilotFunc.authenticateDongle(ws, res, cookies) === false) {
ws.terminate();
}
2022-01-07 18:35:55 -07:00
// ws.send(JSON.stringify(await commandBuilder('reboot')))
}
2022-01-07 18:35:55 -07:00
__server();
wss.retropilotFunc = {
findFromDongle: (dongleId) => {
let websocket = null;
wss.clients.forEach((value) => {
if (value.dongleId === dongleId) {
websocket = value;
}
2022-01-07 18:35:55 -07:00
});
return websocket;
},
authenticateDongle: async (ws, res, cookies) => {
let unsafeJwt;
try {
unsafeJwt = jsonwebtoken.decode(cookies.jwt);
2022-01-08 13:43:57 -07:00
} catch (e) {
2022-01-07 18:35:55 -07:00
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;
}
2022-03-22 09:14:08 -06:00
const device = await deviceController.getDeviceFromDongleId(unsafeJwt.identity);
let verifiedJWT;
2022-01-21 16:36:48 -07:00
console.log('JWT', cookies.jwt);
try {
verifiedJWT = jsonwebtoken.verify(cookies.jwt, device.public_key, { ignoreNotBefore: true });
2022-01-08 13:43:57 -07:00
} catch (err) {
2022-01-07 18:35:55 -07:00
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;
}
if (verifiedJWT.identify === unsafeJwt.identify) {
2022-01-07 18:35:55 -07:00
ws.dongleId = device.dongle_id;
ws.device_id = device.id;
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;
}
2022-01-07 18:35:55 -07:00
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}`);
2022-01-07 18:35:55 -07:00
return false;
},
2022-01-07 18:35:55 -07:00
commandBuilder: (method, params, id) => ({
2022-01-08 13:43:57 -07:00
method, params, jsonrpc: '2.0', id,
2022-01-07 18:35:55 -07:00
}),
actionLogger: async (accountId, deviceId, action, userIp, deviceIp, meta, dongleId) => {
2022-03-21 17:38:56 -06:00
await AthenaActionLog.create({
account_id: accountId,
device_id: deviceId,
action,
user_ip: userIp,
device_ip: deviceIp,
meta,
created_at: Date.now(),
dongle_id: dongleId,
2022-01-07 18:35:55 -07:00
});
2022-01-08 13:43:57 -07:00
},
2022-01-07 18:35:55 -07:00
};
2022-01-12 08:02:30 -07:00
export default helpers;