pull/4/head
AdamSBlack 2022-01-03 16:29:47 +00:00
parent c2e730f09a
commit 71b6eb32ac
11 changed files with 147 additions and 332 deletions

View File

@ -1,52 +0,0 @@
let wss;
const orm = require('../models/index.model');
const {v4: uuid} = require('uuid');
function invoke(command, params, dongleId, accountId) {
const websocket = wss.retropilotFunc.findFromDongle(dongleId);
if (!websocket) {
wss.retropilotFunc.actionLogger(accountId, null, "ATHENA_USER_INVOKE__FAILED_DISCONNECTED", null, null, null, dongleId);
return { connected: false }
}
const uniqueID = uuid();
wss.retropilotFunc.actionLogger(accountId, websocket.device_id, "ATHENA_USER_INVOKE__ISSUED", null, websocket._socket.remoteAddress, JSON.stringify({ command, params, uniqueID }), websocket.dongleId);
orm.models.athena_returned_data.create({
device_id: websocket.device_id,
type: command,
created_at: Date.now(),
uuid: uniqueID
})
websocket.send(JSON.stringify(wss.retropilotFunc.commandBuilder(command, params, uniqueID)))
return { dispatched: true, heartbeat: websocket.heartbeat, id: uniqueID }
}
function isDeviceConnected(accountId, deviceId, dongleId) {
const websocket = wss.retropilotFunc.findFromDongle(dongleId);
wss.retropilotFunc.actionLogger(accountId, deviceId, "ATHENA_USER_STATUS__IS_CONNECTED", null, websocket ? websocket._socket.remoteAddress : null, JSON.stringify({ connected: websocket ? true : false, heartbeat: websocket ? websocket.heartbeat : null }), dongleId);
if (!websocket) return { connected: false }
return { connected: true, heartbeat: websocket.heartbeat };
}
module.exports = (websocketServer) => {
wss = websocketServer;
return {
isDeviceConnected,
invoke
}
}

View File

@ -1,186 +0,0 @@
const WebSocket = require('ws');
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;
function __server() {
let server;
if (config.athena.secure) {
server = httpsServer.createServer({
cert: readFileSync(config.sslCrt),
key: readFileSync(config.sslKey)
});
} else {
server = httpServer.createServer();
}
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);
return ws.terminate();
}
ws.isAlive = false;
ws.ping();
});
}, 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() {
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);
console.log("unauthenticated message, discarded");
return null;
}
const json = JSON.parse(message.toString('utf8'))
console.log(json);
console.log({device_id: ws.device_id, uuid: json.id});
console.log( await models.models.athena_returned_data.update({
data: JSON.stringify(json),
resolved_at: Date.now()
}, {where: {device_id: ws.device_id, uuid: json.id}}))
wss.retropilotFunc.actionLogger(null, null, "ATHENA_DEVICE_MESSAGE_UNKNOWN", null, ws._socket.remoteAddress, JSON.stringify([message]), ws.dongleId);
console.log(json)
});
if (await wss.retropilotFunc.authenticateDongle(ws, res, cookies) === false) {
ws.terminate();
}
//ws.send(JSON.stringify(await commandBuilder('reboot')))
}
__server();
wss.retropilotFunc = {
findFromDongle: (dongleId) => {
let websocket = null;
wss.clients.forEach((value) => {
if (value.dongleId === dongleId) {
websocket = value;
}
})
return websocket;
},
authenticateDongle: async (ws, res, cookies) => {
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;
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;
}
if (verifiedJWT.identify === unsafeJwt.identify) {
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;
} else {
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;
}
},
commandBuilder: (method, params, id) => {
return { method, params: params, "jsonrpc": "2.0", "id": id }
},
actionLogger: async (account_id, device_id, action, user_ip, device_ip, meta, dongle_id) => {
models.models.athena_action_log.create({
account_id, device_id, action, user_ip, device_ip, meta, created_at: Date.now(), dongle_id
})
},
}
const helpers = require('./helpers')(wss)
module.exports = helpers;

View File

@ -1,4 +1,4 @@
const jwt = require('jsonwebtoken');
const jsonwebtoken = require('jsonwebtoken');
let logger;
const models_orm = require('./../models/index.model')
const crypto = require('crypto');
@ -6,7 +6,7 @@ const config = require('./../config')
async function validateJWT(token, key) {
try {
return jwt.verify(token.replace("JWT ", ""), key, {algorithms: ['RS256'], ignoreNotBefore: true});
return jsonwebtoken.verify(token.replace("JWT ", ""), key, {algorithms: ['RS256'], ignoreNotBefore: true});
} catch (exception) {
console.log(`failed to validate JWT ${exception}`)
}
@ -15,7 +15,7 @@ async function validateJWT(token, key) {
async function readJWT(token) {
try {
return jwt.decode(token);
return jsonwebtoken.decode(token);
} catch (exception) {
logger.warn(`failed to read JWT ${exception}`)
}
@ -30,7 +30,10 @@ async function signIn(email, password) {
account = account.dataValues;
const inputPassword = crypto.createHash('sha256').update(password + config.applicationSalt).digest('hex');
if (account.password === inputPassword) {
const token = jwt.sign({accountId: account.id}, config.applicationSalt)
const token = jsonwebtoken.sign({accountId: account.id}, config.applicationSalt)
// TODO: INSECURE, DEBUG
console.log("jwt: ", token)
return {success: true, jwt: token};
} else {
return {success: false, msg: 'BAD PASSWORD', invalidPassword: true}
@ -66,9 +69,22 @@ async function changePassword(account, newPassword, oldPassword) {
*/
async function getAuthenticatedAccount(req, res) {
const sessionJWT = (req.signedCookies !== undefined ? req.signedCookies.jwt : null)
const sessionJWT = req.cookies.jwt
if ((!sessionJWT || sessionJWT.expires <= Date.now())) { return null; }
const token = jwt.verify(sessionJWT, config.applicationSalt);
return await getAccountFromJWT(sessionJWT);
}
async function getAccountFromJWT(jwt) {
let token;
try {
token = jsonwebtoken.verify(jwt, config.applicationSalt);
} catch (err) {
return null// {success: false, msg: 'BAD_JWT'}
}
if (token && token.accountId) {
const account = await models_orm.models.accounts.findOne({where: {id: token.accountId}});
@ -90,16 +106,14 @@ async function getAuthenticatedAccount(req, res) {
}
return null;
}
module.exports = {
validateJWT: validateJWT,
getAuthenticatedAccount: getAuthenticatedAccount,
changePassword: changePassword,
signIn: signIn,
readJWT: readJWT
readJWT: readJWT,
getAccountFromJWT: getAccountFromJWT
}

View File

@ -2,106 +2,103 @@ const config = require('./../config');
const authenticationController = require('./authentication');
const models_orm = require('./../models/index.model')
const usersController = require('./users')
const sanitize = require('sanitize')();
const { Op } = require('sequelize')
const { Op } = require('sequelize');
const { Logger } = require('log4js');
const { allowAccountRegistration } = require('./../config');
async function pairDevice(account, qr_string) {
if (qr_string === undefined || qr_string === null) { return {success: false, badQr: true} }
if (qr_string === undefined || qr_string === null) { return { success: false, badQr: true } }
// Legacy registrations encode QR data as imei - serial - pairtoken, => 0.8.3 uses only a pairtoken
var qrCodeParts = qr_string.split("--");
let deviceQuery;
let pairJWT;
if (qrCodeParts.length > 1) {
deviceQuery = await models_orm.models.device.findOne({ where: { serial: qrCodeParts[1] }});
var qrCodeParts = qr_string.split("--");
let deviceQuery;
let pairJWT;
if (qrCodeParts.length > 1) {
deviceQuery = await models_orm.models.device.findOne({ where: { serial: qrCodeParts[1] } });
pairJWT = qrCodeParts[2];
} else {
pairJWT = qr_string;
const data = await authenticationController.readJWT(qr_string);
if (data.pair === true) {
deviceQuery = await models_orm.models.device.findOne({ where: { dongle_id: data.identiy }});
} else{
return {success: false, noPair: true}
}
}
} else {
pairJWT = qr_string;
const data = await authenticationController.readJWT(qr_string);
if (data.pair === true) {
deviceQuery = await models_orm.models.device.findOne({ where: { dongle_id: data.identity } });
} else {
return { success: false, noPair: true }
}
if (deviceQuery == null) {
return {success: false, registered: false}
}
const device = deviceQuery.dataValues;
var decoded = await authenticationController.validateJWT(pairJWT, device.public_key);
if (decoded == null || decoded.pair == undefined) {
return {success: false, badToken: true}
}
if (device.account_id != 0) {
return {success: false, alreadyPaired: true, dongle_id: device.dongle_id}
if (deviceQuery == null) {
return { success: false, registered: false }
}
return await pairDeviceToAccountId(device.dongle_id, account.id )
const device = deviceQuery.dataValues;
var decoded = await authenticationController.validateJWT(pairJWT, device.public_key);
if (decoded == null || decoded.pair == undefined) {
return { success: false, badToken: true }
}
if (device.account_id != 0) {
return { success: false, alreadyPaired: true, dongle_id: device.dongle_id }
}
return await pairDeviceToAccountId(device.dongle_id, account.id)
}
async function pairDeviceToAccountId(dongle_id, account_id) {
console.log("input", account_id, dongle_id)
const update = await models_orm.models.device.update(
const update = await models_orm.models.device.update(
{ account_id: account_id },
{ where: { dongle_id: dongle_id } }
{ where: { dongle_id: dongle_id } }
)
console.log("update:" , update)
const check = await models_orm.models.device.findOne({where: {dongle_id: dongle_id, account_id: account_id}})
console.log(check);
const check = await models_orm.models.device.findOne({ where: { dongle_id: dongle_id, account_id: account_id } })
if (check.dataValues) {
return {success: true, paired: true, dongle_id: dongle_id, account_id: account_id}
return { success: true, paired: true, dongle_id: dongle_id, account_id: account_id }
} else {
return {success: false, paired: false}
return { success: false, paired: false }
}
}
async function unpairDevice(account, dongleId) {
const device = await models_orm.models.device.getOne({where: {account_id: account.id, dongle_id: dongleId}});
const device = await models_orm.models.device.getOne({ where: { account_id: account.id, dongle_id: dongleId } });
if (device && device.dataValues) {
await models_orm.models.device.update({account_id: 0}, {where: {dongle_id: dongleId}});
return {success: true}
await models_orm.models.device.update({ account_id: 0 }, { where: { dongle_id: dongleId } });
return { success: true }
} else {
return {success: false, msg: 'BAD DONGLE', invalidDongle: true};
return { success: false, msg: 'BAD DONGLE', invalidDongle: true };
}
}
async function setDeviceNickname(account, dongleId, nickname) {
const device = await models_orm.models.device.getOne({where: {account_id: account.id, dongle_id: dongleId}});
const device = await models_orm.models.device.getOne({ where: { account_id: account.id, dongle_id: dongleId } });
const cleanNickname = sanitize.value(nickname, 'string')
if (device && device.dataValues) {
await models_orm.models.device.update({nickname: cleanNickname}, {where: {dongle_id: dongleId}});
return {success: true, data: {nickname: cleanNickname}}
await models_orm.models.device.update({ nickname: cleanNickname }, { where: { dongle_id: dongleId } });
return { success: true, data: { nickname: cleanNickname } }
} else {
return {success: false, msg: 'BAD DONGLE', invalidDongle: true};
return { success: false, msg: 'BAD DONGLE', invalidDongle: true };
}
}
async function getDevices(accountId) {
const devices = await models_orm.models.device.findAll();
console.log("kkk", devices);
return devices.dataValues
const devices = await models_orm.models.device.findAll({where: {account_id: accountId}});
return devices
}
async function getDeviceFromDongle(dongleId) {
const devices = await models_orm.models.device.findOne({where: {dongle_id: dongleId}});
const devices = await models_orm.models.device.findOne({ where: { dongle_id: dongleId } });
return devices.dataValues || null
return devices && devices.hasOwnProperty('dataValues') ? devices.dataValues : null
}
async function setIgnoredUploads(dongleId, isIgnored) {
@ -112,7 +109,7 @@ async function setIgnoredUploads(dongleId, isIgnored) {
// TODO check this change was processed..
return true;
}
async function getAllDevicesFiltered() {
@ -123,18 +120,48 @@ async function getAllDevicesFiltered() {
async function updateLastPing(device_id, dongle_id) {
models_orm.models.device.update({ last_ping: Date.now() }, {where: {[Op.or] : [{id: device_id}, {dongle_id: dongle_id}]}})
models_orm.models.device.update({ last_ping: Date.now() }, { where: { [Op.or]: [{ id: device_id }, { dongle_id: dongle_id }] } })
}
async function isUserAuthorised(account_id, dongle_id) {
if (!account_id || !dongle_id) {return {success: false, msg: 'bad_data'}}
const account = await usersController.getAccountFromId(account_id);
if (!account || !account.dataValues) { return { success: false, msg: 'bad_account', data: {authorised: false, account_id: account_id} } }
const device = await getDeviceFromDongle(dongle_id)
if (!device) { return { success: false, msg: 'bad_device', data: {authorised: false, dongle_id: dongle_id} } }
if (device.account_id === account.id) {
return {success: true, data: {authorised: true, account_id: account.id, dongle_id: device.dongle_id}};
} else {
return { success: false, msg: 'not_authorised', data: {authorised: false, account_id: account.id, dongle_id: device.dongle_id} }
}
}
async function getOwnersFromDongle(dongle_id) {
const device = await getDeviceFromDongle(dongle_id);
if (device) {
return {success: true, data: [device.account_id]};
} else {
return {success: false}
}
}
module.exports = {
pairDevice: pairDevice,
unpairDevice: unpairDevice,
setDeviceNickname: setDeviceNickname,
getDevices: getDevices,
getDeviceFromDongle,
setIgnoredUploads,
getAllDevicesFiltered,
pairDeviceToAccountId,
updateLastPing
}
pairDevice: pairDevice,
unpairDevice: unpairDevice,
setDeviceNickname: setDeviceNickname,
getDevices: getDevices,
getDeviceFromDongle,
setIgnoredUploads,
getAllDevicesFiltered,
pairDeviceToAccountId,
updateLastPing,
isUserAuthorised,
getOwnersFromDongle
}

View File

@ -1,12 +1,18 @@
function log() {
return true;
}
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'database.sqlite',
logQueryParameters: true,
benchmark: true,
logging: false
logging: () => {console.log("FUCK OFF")}
});
sequelize.options.logging = (eee) => {console.log("EEEEE")}
const modelDefiners = [
require('./devices.model'),
require('./drives.model'),

View File

@ -17,6 +17,7 @@
"chai-http": "^4.3.0",
"cookie": "^0.4.1",
"cookie-parser": "^1.4.5",
"cookies": "^0.8.0",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"directory-tree": "^2.2.9",

View File

@ -19,7 +19,7 @@ router.post('/retropilot/0/useradmin/auth', bodyParser.urlencoded({extended: tru
const signIn = await authentication.signIn(req.body.email, req.body.password)
if (signIn.success) {
res.cookie('jwt', signIn.jwt, {signed: true});
res.cookie('jwt', signIn.jwt);
res.redirect('/useradmin/overview');
} else {
res.redirect('/useradmin?status=' + encodeURIComponent('Invalid credentials or banned account'));

View File

@ -8,6 +8,8 @@ module.exports = (_models, _controllers, _logger) => {
api: require('./api')(_models, _controllers, _logger),
useradminapi: require('./userAdminApi')(_models, _controllers, _logger),
admin: require('./administration/adminApi')(_models, _controllers, _logger),
realtime: require('./api/realtime')
realtime: require('./api/realtime'),
deviceApi: require('./api/devices')
}
}

View File

@ -1,13 +1,11 @@
const router = require('express').Router();
const bodyParser = require('body-parser');
const crypto = require('crypto');
const cookieParser = require('cookie-parser');
const config = require('./../config');
// TODO Remove this, pending on removing all auth logic from routes
router.use(cookieParser(config.applicationSalt))
var cookieParser = require('cookie-parser')
router.use(cookieParser())
function runAsyncWrapper(callback) {
return function (req, res, next) {
@ -25,7 +23,7 @@ router.post('/retropilot/0/useradmin/auth', bodyParser.urlencoded({extended: tru
const signIn = await controllers.authentication.signIn(req.body.email, req.body.password)
if (signIn.success) {
res.cookie('jwt', signIn.jwt, {signed: true});
res.cookie('jwt', signIn.jwt);
res.redirect('/useradmin/overview');
} else {
res.redirect('/useradmin?status=' + encodeURIComponent('Invalid credentials or banned account'));

View File

@ -3,12 +3,13 @@ const bodyParser = require('body-parser');
const crypto = require('crypto');
const htmlspecialchars = require('htmlspecialchars');
const dirTree = require("directory-tree");
const cookieParser = require('cookie-parser');
const cookie = require('cookie');
const config = require('./../config');
var cookieParser = require('cookie-parser')
// TODO Remove this, pending on removing all auth logic from routes
router.use(cookieParser(config.applicationSalt))
router.use(cookieParser());
function runAsyncWrapper(callback) {
@ -29,7 +30,7 @@ router.post('/useradmin/auth', bodyParser.urlencoded({extended: true}), runAsync
console.log(signIn)
if (signIn.success) {
res.cookie('jwt', signIn.jwt, {signed: true});
res.cookie('jwt', signIn.jwt);
res.redirect('/useradmin/overview');
} else {
res.redirect('/useradmin?status=' + encodeURIComponent('Invalid credentials or banned account'));

View File

@ -6,7 +6,6 @@ const http = require('http');
const https = require('https');
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const rateLimit = require("express-rate-limit");
log4js.configure({
@ -24,7 +23,10 @@ let models = require('./models/index');
let models_sqli = require('./models/index.model');
let controllers = require('./controllers');
let routers = require('./routes')
const athena = require('./Anetha/index');
const athena = require('./websocket/athena');
const webWebsocket = require('./websocket/web');
var cookieParser = require('cookie-parser');
const router = require('./routes/api/realtime');
@ -90,17 +92,19 @@ const web = async () => {
logger.log("Athena disabled");
}
app.use(cors());
app.use(cookieParser(config.applicationSalt))
app.use(cookieParser());
app.use('/favicon.ico', express.static('static/favicon.ico'));
app.use(config.baseDriveDownloadPathMapping, express.static(config.storagePath));
app.use(routers.deviceApi)
app.use('/.well-known', express.static('.well-known'));
app.use('/cabana', express.static('cabana/'));