retropilot-server/src/server/controllers/devices.js

375 lines
10 KiB
JavaScript
Raw Normal View History

2022-01-12 08:02:30 -07:00
import crypto from 'crypto';
import dirTree from 'directory-tree';
2022-01-21 16:36:48 -07:00
import log4js from 'log4js';
2022-03-22 07:03:17 -06:00
import sanitizeFactory from 'sanitize';
import { Op } from 'sequelize';
import {
Accounts,
Devices,
Drives,
DriveSegments,
} from '../../models';
2022-03-20 17:59:37 -06:00
import { readJWT, validateJWT } from './authentication';
import { getAccountFromId } from './users';
2021-10-02 17:08:21 -06:00
2022-03-22 09:14:08 -06:00
const logger = log4js.getLogger();
2022-01-21 16:36:48 -07:00
const sanitize = sanitizeFactory();
2022-01-08 17:35:40 -07:00
async function pairDevice(account, qrString) {
if (qrString === undefined || qrString === null) {
2022-01-07 18:35:55 -07:00
return { success: false, badQr: true };
}
2022-01-08 17:35:40 -07:00
// Legacy registrations encode QR data as "imei--serial--pairtoken"
// Versions >= 0.8.3 uses only a pairtoken
2022-01-07 18:35:55 -07:00
2022-01-08 17:35:40 -07:00
const qrCodeParts = qrString.split('--');
2022-03-02 19:30:24 -07:00
let device;
2022-01-07 18:35:55 -07:00
let pairJWT;
2022-03-02 19:18:07 -07:00
2022-01-08 17:35:40 -07:00
if (qrString.indexOf('--') >= 0) {
const [, serial, pairToken] = qrCodeParts;
2022-03-21 17:38:56 -06:00
device = await Devices.findOne({ where: { serial } });
2022-01-08 17:35:40 -07:00
pairJWT = pairToken;
2022-01-08 13:43:57 -07:00
} else {
2022-03-20 17:59:37 -06:00
const data = await readJWT(qrString);
2022-03-02 19:18:07 -07:00
if (!data || !data.pair) {
2022-01-07 18:35:55 -07:00
return { success: false, noPair: true };
2022-01-03 09:29:47 -07:00
}
2022-03-21 17:38:56 -06:00
device = await Devices.findOne({ where: { dongle_id: data.identity } });
2022-01-08 17:35:40 -07:00
pairJWT = qrString;
2022-01-07 18:35:55 -07:00
}
2022-03-20 17:59:37 -06:00
if (device == null || !device.dataValues) {
2022-03-02 19:18:07 -07:00
return { success: false, registered: false, noPair: true };
2022-01-07 18:35:55 -07:00
}
2022-03-20 17:59:37 -06:00
const decoded = await validateJWT(pairJWT, device.public_key);
2022-01-08 15:00:08 -07:00
if (decoded == null || !decoded.pair) {
2022-01-07 18:35:55 -07:00
return { success: false, badToken: true };
}
2022-01-08 15:00:08 -07:00
if (device.account_id !== 0) {
2022-01-07 18:35:55 -07:00
return { success: false, alreadyPaired: true, dongle_id: device.dongle_id };
}
2022-01-08 17:35:40 -07:00
return pairDeviceToAccountId(device.dongle_id, account.id);
2021-10-25 15:56:40 -06:00
}
2022-01-08 17:35:40 -07:00
async function pairDeviceToAccountId(dongleId, accountId) {
2022-03-21 17:38:56 -06:00
await Devices.update(
2022-01-08 17:35:40 -07:00
{ account_id: accountId },
{ where: { dongle_id: dongleId } },
2022-01-07 18:35:55 -07:00
);
2022-03-21 17:38:56 -06:00
const check = await Devices.findOne(
2022-01-08 17:35:40 -07:00
{ where: { dongle_id: dongleId, account_id: accountId } },
2022-01-07 18:35:55 -07:00
);
if (check.dataValues) {
return {
2022-01-08 17:35:40 -07:00
success: true, paired: true, dongle_id: dongleId, account_id: accountId,
2022-01-07 18:35:55 -07:00
};
}
return { success: false, paired: false };
}
2022-03-22 09:14:08 -06:00
async function unpairDevice(dongleId, accountId) {
2022-03-21 17:38:56 -06:00
const device = await Devices.getOne(
2022-03-22 09:14:08 -06:00
{ where: { account_id: accountId, dongle_id: dongleId } },
2022-01-07 18:35:55 -07:00
);
if (device && device.dataValues) {
2022-03-22 09:14:08 -06:00
// TODO: check result?
2022-03-21 17:38:56 -06:00
await Devices.update(
2022-01-07 18:35:55 -07:00
{ account_id: 0 },
2022-01-08 13:43:57 -07:00
{ where: { dongle_id: dongleId } },
2022-01-07 18:35:55 -07:00
);
return { success: true };
}
return { success: false, msg: 'BAD DONGLE', invalidDongle: true };
}
async function setDeviceNickname(account, dongleId, nickname) {
2022-03-21 17:38:56 -06:00
const device = await Devices.getOne(
2022-01-08 13:43:57 -07:00
{ where: { account_id: account.id, dongle_id: dongleId } },
2022-01-07 18:35:55 -07:00
);
const cleanNickname = sanitize.value(nickname, 'string');
if (device && device.dataValues) {
2022-03-21 17:38:56 -06:00
await Devices.update(
2022-01-07 18:35:55 -07:00
{ nickname: cleanNickname },
2022-01-08 13:43:57 -07:00
{ where: { dongle_id: dongleId } },
2022-01-07 18:35:55 -07:00
);
return { success: true, data: { nickname: cleanNickname } };
}
return { success: false, msg: 'BAD DONGLE', invalidDongle: true };
}
async function getDevices(accountId) {
2022-03-21 17:38:56 -06:00
return Devices.findAll({ where: { account_id: accountId } });
}
2022-03-22 09:14:08 -06:00
async function getDeviceFromDongleId(dongleId) {
if (!dongleId) {
2022-01-08 17:35:40 -07:00
return null;
}
2022-03-22 09:14:08 -06:00
const device = await Devices.findOne({ where: { dongle_id: dongleId } });
if (!device || !device.dataValues) {
return null;
}
return device.dataValues;
}
2022-03-22 09:14:08 -06:00
// TODO combine these redundant functions into one
async function getDeviceFromSerial(serial) {
2022-03-22 09:14:08 -06:00
if (!serial) {
return null;
}
2022-03-21 17:38:56 -06:00
const devices = await Devices.findOne({ where: { serial } });
if (!devices || !devices.dataValues) {
return null;
}
return devices.dataValues;
}
async function updateDevice(dongleId, data) {
2022-03-22 09:14:08 -06:00
if (!dongleId) {
return null;
}
2022-03-21 17:38:56 -06:00
return Devices.update(data, { where: { dongle_id: dongleId } });
}
async function setIgnoredUploads(dongleId, isIgnored) {
2022-03-22 07:03:17 -06:00
await Accounts.update(
2022-01-07 18:35:55 -07:00
{ dongle_id: dongleId },
2022-01-08 13:43:57 -07:00
{ where: { uploads_ignored: isIgnored } },
2022-01-07 18:35:55 -07:00
);
2022-01-03 09:29:47 -07:00
2022-01-07 18:35:55 -07:00
// TODO check this change was processed..
return true;
}
async function getAllDevicesFiltered() {
2022-03-21 17:38:56 -06:00
return Devices.findAll();
}
async function updateLastPing(deviceId) {
2022-03-21 17:38:56 -06:00
return Devices.update(
2022-01-07 18:35:55 -07:00
{ last_ping: Date.now() },
{ where: { [Op.or]: [{ id: deviceId }, { dongle_id: deviceId }] } },
2022-01-07 18:35:55 -07:00
);
2021-10-29 14:45:15 -06:00
}
2022-01-08 17:35:40 -07:00
async function isUserAuthorised(accountId, dongleId) {
if (!accountId || !dongleId) {
2022-01-07 18:35:55 -07:00
return { success: false, msg: 'bad_data' };
}
2022-01-08 17:35:40 -07:00
2022-03-20 17:59:37 -06:00
const account = await getAccountFromId(accountId);
2022-01-07 18:35:55 -07:00
if (!account || !account.dataValues) {
2022-01-08 17:35:40 -07:00
return { success: false, msg: 'bad_account', data: { authorised: false, account_id: accountId } };
2022-01-07 18:35:55 -07:00
}
2022-03-22 09:14:08 -06:00
const device = await getDeviceFromDongleId(dongleId);
2022-01-07 18:35:55 -07:00
if (!device) {
2022-01-08 17:35:40 -07:00
return { success: false, msg: 'bad_device', data: { authorised: false, dongle_id: dongleId } };
2022-01-07 18:35:55 -07:00
}
2022-01-08 17:35:40 -07:00
if (device.account_id !== account.id) {
return { success: false, msg: 'not_authorised', data: { authorised: false, account_id: account.id, dongle_id: device.dongle_id } };
2022-01-07 18:35:55 -07:00
}
2022-01-03 09:29:47 -07:00
2022-01-08 17:35:40 -07:00
return {
success: true,
data: {
authorised: true, account_id: account.id, dongle_id: device.dongle_id,
},
};
}
2022-01-03 09:29:47 -07:00
2022-01-08 17:35:40 -07:00
async function getOwnersFromDongle(dongleId) {
2022-03-22 09:14:08 -06:00
const device = await getDeviceFromDongleId(dongleId);
2022-01-08 17:35:40 -07:00
if (!device) {
return { success: false };
2022-01-07 18:35:55 -07:00
}
2022-01-08 17:35:40 -07:00
return { success: true, data: [device.account_id] };
2022-01-03 09:29:47 -07:00
}
2022-01-08 17:35:40 -07:00
async function getDrives(dongleId, includeDeleted, includeMeta) {
let query = { where: { dongle_id: dongleId } };
2022-01-08 17:35:40 -07:00
if (!includeDeleted) {
2022-01-07 18:35:55 -07:00
query = { ...query, where: { ...query.where, is_deleted: false } };
}
2022-01-08 16:07:09 -07:00
if (!includeMeta) {
query = { ...query, attributes: { exclude: ['metadata'] } };
}
2022-01-07 18:35:55 -07:00
2022-03-22 07:03:17 -06:00
return Drives.findAll(query);
}
2022-01-21 16:36:48 -07:00
async function getDrive(identifier) {
2022-03-22 07:03:17 -06:00
const drive = await Drives.findOne({ where: { identifier } });
if (!drive) {
return null;
}
return drive.dataValues;
}
2022-03-22 07:03:17 -06:00
async function getDriveFromIdentifier(dongleId, identifier) {
return Drives.findOne({ where: { dongle_id: dongleId, identifier } });
2022-01-09 03:50:27 -07:00
}
/*
TODO: ADD AUTHENTICATION TO ENDPOINTS
*/
2022-01-08 17:35:40 -07:00
async function getCrashlogs(dongleId) {
const dongleIdHash = crypto.createHmac('sha256', process.env.APP_SALT).update(dongleId).digest('hex');
2022-01-07 18:35:55 -07:00
const directoryTree = dirTree(`${process.env.STORAGE_PATH}${dongleId}/${dongleIdHash}/crash/`, { attributes: ['size'] });
2022-01-08 15:08:37 -07:00
const crashlogFiles = (directoryTree ? directoryTree.children : []).map((file) => {
2022-01-08 17:35:40 -07:00
const timeSplit = file.name.replace('boot-', '').replace('crash-', '').replace('.bz2', '').split('--');
2022-01-08 15:00:08 -07:00
let timeString = `${timeSplit[0]} ${timeSplit[1].replace(/-/g, ':')}`;
if (timeString.indexOf('_') > 0) {
2022-01-08 17:35:40 -07:00
// eslint-disable-next-line prefer-destructuring
2022-01-08 15:00:08 -07:00
timeString = timeString.split('_')[0];
}
2022-01-08 15:00:08 -07:00
let dateObj = null;
try {
dateObj = Date.parse(timeString);
2022-01-08 17:35:40 -07:00
} catch (exception) {
// do nothing
}
if (!dateObj) {
dateObj = new Date(0);
}
2022-01-08 15:00:08 -07:00
return {
name: file.name,
size: file.size,
date: dateObj,
permalink: `${process.env.BASE_DRIVE_DOWNLOAD_URL}${dongleId}/${dongleIdHash}/crash/${file.name}`,
2022-01-08 15:00:08 -07:00
};
});
crashlogFiles.sort((a, b) => ((a.date < b.date) ? 1 : -1));
2022-01-07 18:35:55 -07:00
return crashlogFiles;
}
2022-01-08 17:35:40 -07:00
async function getBootlogs(dongleId) {
const dongleIdHash = crypto.createHmac('sha256', process.env.APP_SALT).update(dongleId).digest('hex');
2022-01-07 18:35:55 -07:00
const directoryTree = dirTree(`${process.env.STORAGE_PATH}${dongleId}/${dongleIdHash}/boot/`, { attributes: ['size'] });
2022-01-08 15:08:37 -07:00
const bootlogFiles = (directoryTree ? directoryTree.children : []).map((file) => {
2022-01-08 17:35:40 -07:00
const timeSplit = file.name.replace('boot-', '').replace('crash-', '').replace('.bz2', '').split('--');
2022-01-08 15:00:08 -07:00
const timeString = `${timeSplit[0]} ${timeSplit[1].replace(/-/g, ':')}`;
let dateObj = null;
try {
dateObj = Date.parse(timeString);
2022-01-08 17:35:40 -07:00
} catch (exception) {
// do nothing
}
2022-01-08 15:00:08 -07:00
if (!dateObj) dateObj = new Date(0);
2022-01-08 15:00:08 -07:00
return {
name: file.name,
size: file.size,
date: dateObj,
permalink: `${process.env.BASE_DRIVE_DOWNLOAD_URL}${dongleId}/${dongleIdHash}/boot/${file.name}`,
2022-01-08 15:00:08 -07:00
};
2022-01-08 15:19:08 -07:00
});
2022-01-08 15:00:08 -07:00
bootlogFiles.sort((a, b) => ((a.date < b.date) ? 1 : -1));
2022-01-07 18:35:55 -07:00
return bootlogFiles;
}
async function updateOrCreateDrive(dongleId, identifier, data) {
2022-01-21 16:36:48 -07:00
logger.info('updateOrCreate Drive', dongleId, identifier, data);
2022-03-22 07:03:17 -06:00
const check = await Drives.findOne({ where: { dongle_id: dongleId, identifier } });
2022-03-21 17:38:56 -06:00
logger.info('checking for existing drive....', check);
2022-01-21 16:36:48 -07:00
if (check) {
2022-03-22 07:03:17 -06:00
return Drives.update(data, { where: { dongle_id: dongleId, identifier } });
}
2022-03-22 07:03:17 -06:00
return Drives.create({
...data,
2022-01-21 16:36:48 -07:00
dongle_id: dongleId,
identifier,
});
}
async function updateOrCreateDriveSegment(dongleId, identifier, segmentId, data) {
2022-01-21 16:36:48 -07:00
logger.info('updateOrCreate Drive_Segment', dongleId, identifier, data);
2022-03-22 07:03:17 -06:00
const check = await DriveSegments.findOne({
2022-01-21 16:36:48 -07:00
where: { segment_id: segmentId, dongle_id: dongleId, drive_identifier: identifier },
});
2022-01-21 16:36:48 -07:00
if (check) {
2022-03-22 07:03:17 -06:00
return DriveSegments.update(
2022-01-21 16:36:48 -07:00
data,
{ where: { segment_id: segmentId, dongle_id: dongleId, drive_identifier: identifier } },
);
}
2022-03-22 07:03:17 -06:00
return DriveSegments.create({
...data,
segment_id: segmentId,
drive_identifier: identifier,
dongle_id: dongleId,
});
}
2022-01-21 16:36:48 -07:00
async function getDriveSegment(driveName, segment) {
2022-03-22 07:03:17 -06:00
return DriveSegments.findOne({
where: {
segment_id: segment,
drive_identifier: driveName,
},
});
}
async function createDongle(dongleId, accountId, imei, serial, publicKey) {
2022-03-21 17:38:56 -06:00
return Devices.create({
dongle_id: dongleId,
account_id: 0,
imei,
serial,
device_type: 'freon',
public_key: publicKey,
created: Date.now(),
last_ping: Date.now(),
storage_used: 0,
});
}
2022-01-12 08:02:30 -07:00
export default {
2022-01-07 18:35:55 -07:00
pairDevice,
unpairDevice,
setDeviceNickname,
getDevices,
2022-03-22 09:14:08 -06:00
getDeviceFromDongleId,
2022-01-07 18:35:55 -07:00
setIgnoredUploads,
getAllDevicesFiltered,
pairDeviceToAccountId,
updateLastPing,
isUserAuthorised,
getOwnersFromDongle,
createDongle,
getDeviceFromSerial,
updateDevice,
2022-01-07 18:35:55 -07:00
// drive stuff, move maybe?
getDrives,
getDrive,
2022-01-07 18:35:55 -07:00
getBootlogs,
2022-01-08 13:43:57 -07:00
getCrashlogs,
2022-03-22 07:03:17 -06:00
getDriveFromIdentifier,
updateOrCreateDrive,
updateOrCreateDriveSegment,
2022-01-09 19:49:24 -07:00
getDriveSegment,
2022-01-07 18:35:55 -07:00
};