build router for each endpoint

pull/4/head
Cameron Clough 2022-03-22 15:14:08 +00:00
parent c3359bb223
commit 4a39c36818
No known key found for this signature in database
GPG Key ID: BFB3B74B026ED43F
32 changed files with 413 additions and 768 deletions

View File

@ -37,6 +37,11 @@
// retropilot: we allow use of the continue statement
"no-continue": "off",
// disallow else after a return in an if
// https://eslint.org/docs/rules/no-else-return
// retropilot: allow else-if...
"no-else-return": ["error", { "allowElseIf": true }],
// disallow use of variables before they are defined
// http://eslint.org/docs/rules/no-use-before-define
// retropilot: permit referencing functions before they're defined

View File

@ -1,15 +1,13 @@
import cookieParser from 'cookie-parser';
import cors from 'cors';
import express from 'express';
import rateLimit from 'express-rate-limit';
import log4js from 'log4js';
import storageController from './controllers/storage';
import athena from './websocket/athena';
import controllers from './controllers';
import routers from './router';
import router from './router';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
function runAsyncWrapper(callback) {
return function wrapper(req, res, next) {
@ -30,27 +28,7 @@ app.use(cors({
}));
app.use(cookieParser());
app.use('/api', routers.api);
app.use('/useradmin', routers.useradmin);
if (process.env.ATHENA_ENABLED) {
const athenaRateLimit = rateLimit({
windowMs: 30000,
max: process.env.ATHENA_API_RATE_LIMIT,
});
app.use((req, res, next) => {
req.athenaWebsocketTemp = athena;
return next();
});
app.use('/admin', routers.admin);
app.use('/realtime', athenaRateLimit);
app.use('/realtime', routers.realtime);
// app.use(routers.oauthAuthenticator)
} else {
logger.info('Athena disabled');
}
app.use(router);
app.use('/favicon.ico', express.static('static/favicon.ico'));
app.use(process.env.BASE_DRIVE_DOWNLOAD_PATH_MAPPING, express.static(process.env.STORAGE_PATH));

View File

@ -5,7 +5,9 @@ import { Accounts } from '../../models';
async function isCurrentUserAdmin(hardFail, req) {
const account = await authentication.getAuthenticatedAccount(req);
if (!account) return { isAdmin: false, account };
if (!account) {
return { isAdmin: false, account };
}
if (account.admin !== 1) {
return { isAdmin: false, account };
}

View File

@ -4,7 +4,7 @@ import log4js from 'log4js';
import { Accounts } from '../../../models';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
export async function validateJWT(token, key) {
try {
@ -43,22 +43,21 @@ async function signIn(email, password) {
async function changePassword(account, newPassword, oldPassword) {
if (!account || !newPassword || !oldPassword) {
return { success: false, error: 'MISSING_DATA' };
return { success: false, code: 400, error: 'MISSING_DATA' };
}
const oldPasswordHash = crypto.createHash('sha256').update(oldPassword + process.env.APP_SALT).digest('hex');
if (account.password !== oldPasswordHash) {
return { success: false, msg: 'BAD PASSWORD', passwordCorrect: false };
return { success: false, code: 400, msg: 'BAD_PASSWORD' };
}
const newPasswordHash = crypto.createHash('sha256').update(newPassword + process.env.APP_SALT).digest('hex');
await Accounts.update(
{ password: newPasswordHash },
{ where: { id: account.id } },
);
return { success: true, msg: 'PASSWORD CHANGED', changed: true };
return { success: true, msg: 'PASSWORD_CHANGED' };
}
/*

View File

@ -3,7 +3,7 @@ import { ClientCredentials, ResourceOwnerPassword, AuthorizationCode } from 'sim
import log4js from 'log4js';
import { AUTH_OAUTH_ERR_GOOGLE, AUTH_OAUTH_ERR_GOOGLE_FAILED_TOKEN_FETCH } from '../../../consistency/terms';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
const keys = {
web: {

View File

@ -13,7 +13,7 @@ import {
import { readJWT, validateJWT } from './authentication';
import { getAccountFromId } from './users';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
const sanitize = sanitizeFactory();
async function pairDevice(account, qrString) {
@ -72,12 +72,13 @@ async function pairDeviceToAccountId(dongleId, accountId) {
return { success: false, paired: false };
}
async function unpairDevice(account, dongleId) {
async function unpairDevice(dongleId, accountId) {
const device = await Devices.getOne(
{ where: { account_id: account.id, dongle_id: dongleId } },
{ where: { account_id: accountId, dongle_id: dongleId } },
);
if (device && device.dataValues) {
// TODO: check result?
await Devices.update(
{ account_id: 0 },
{ where: { dongle_id: dongleId } },
@ -108,17 +109,24 @@ async function getDevices(accountId) {
return Devices.findAll({ where: { account_id: accountId } });
}
async function getDeviceFromDongle(dongleId) {
if (!dongleId) return null;
const devices = await Devices.findOne({ where: { dongle_id: dongleId } });
if (!devices || !devices.dataValues) {
async function getDeviceFromDongleId(dongleId) {
if (!dongleId) {
return null;
}
return devices.dataValues;
const device = await Devices.findOne({ where: { dongle_id: dongleId } });
if (!device || !device.dataValues) {
return null;
}
return device.dataValues;
}
// TODO combine these redundant functions into one
async function getDeviceFromSerial(serial) {
if (!serial) return null;
if (!serial) {
return null;
}
const devices = await Devices.findOne({ where: { serial } });
if (!devices || !devices.dataValues) {
return null;
@ -127,8 +135,9 @@ async function getDeviceFromSerial(serial) {
}
async function updateDevice(dongleId, data) {
if (!dongleId) return null;
if (!dongleId) {
return null;
}
return Devices.update(data, { where: { dongle_id: dongleId } });
}
@ -163,7 +172,7 @@ async function isUserAuthorised(accountId, dongleId) {
return { success: false, msg: 'bad_account', data: { authorised: false, account_id: accountId } };
}
const device = await getDeviceFromDongle(dongleId);
const device = await getDeviceFromDongleId(dongleId);
if (!device) {
return { success: false, msg: 'bad_device', data: { authorised: false, dongle_id: dongleId } };
}
@ -180,7 +189,7 @@ async function isUserAuthorised(accountId, dongleId) {
}
async function getOwnersFromDongle(dongleId) {
const device = await getDeviceFromDongle(dongleId);
const device = await getDeviceFromDongleId(dongleId);
if (!device) {
return { success: false };
}
@ -342,7 +351,7 @@ export default {
unpairDevice,
setDeviceNickname,
getDevices,
getDeviceFromDongle,
getDeviceFromDongleId,
setIgnoredUploads,
getAllDevicesFiltered,
pairDeviceToAccountId,

View File

@ -1,4 +1,3 @@
/* eslint-disable global-require, no-unused-vars */
import authentication from './authentication';
import helpers from './helpers';
import storage from './storage';
@ -7,7 +6,6 @@ import users from './users';
import admin from './admin';
import devices from './devices';
// TO DO, finish up removing this callback stuff
export default {
authentication,
helpers,

View File

@ -1,7 +1,7 @@
import nodemailer from 'nodemailer';
import log4js from 'log4js';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
const transporter = nodemailer.createTransport(
{
host: process.env.SMTP_HOST,

View File

@ -3,7 +3,7 @@ import fs from 'fs';
import log4js from 'log4js';
import path from 'path';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
let totalStorageUsed;

View File

@ -3,7 +3,7 @@ import log4js from 'log4js';
import { Accounts } from '../../models';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
export async function getAccountFromId(id) {
return Accounts.findByPk(id);

View File

@ -1,8 +1,12 @@
import authenticationController from '../controllers/authentication';
export const getAccount = async (req, res, next) => {
req.account = await authenticationController.getAuthenticatedAccount(req);
next();
};
export const isAuthenticated = async (req, res, next) => {
const account = await authenticationController.getAuthenticatedAccount(req);
if (!account) {
res.status(401).json({
success: false,
@ -11,7 +15,6 @@ export const isAuthenticated = async (req, res, next) => {
return;
}
req.account = account;
next();
};

View File

@ -53,7 +53,7 @@ router.get('/device/:dongle_id', runAsyncWrapper(async (req, res) => {
return req.status(400).json({ error: true, msg: 'MISSING DATA', status: 400 });
}
const device = await controllers.devices.getDeviceFromDongle(dongleId);
const device = await controllers.devices.getDeviceFromDongleId(dongleId);
return res.status(200).json({ success: true, data: device });
}));

View File

@ -1,50 +0,0 @@
import bodyParser from 'body-parser';
import express from 'express';
import authenticationController from '../../controllers/authentication';
import { isAuthenticated } from '../../middlewares/authentication';
const router = express.Router();
router.get('/session', isAuthenticated, async (req, res) => {
return res.status(200).json({
success: true,
data: {
user: req.account.dataValues,
},
});
});
router.post('/login', bodyParser.urlencoded({ extended: true }), async (req, res) => {
const signIn = await authenticationController.signIn(req.body.email, req.body.password);
if (!signIn.success) {
return res.status(401).json(signIn);
}
const account = await authenticationController.getAccountFromJWT(signIn.jwt, true);
return res.status(200).cookie('jwt', signIn.jwt).json({
success: true,
data: {
jwt: signIn.jwt,
user: account.dataValues,
},
});
});
router.get('/logout', async (req, res) => {
res.clearCookie('session');
return res.json({ success: true });
});
// router.get('/session/get', async (req, res) => {
// const account = await authenticationController.getAuthenticatedAccount(req);
//
// if (!account) {
// res.json({ success: true, hasSession: false, session: {} });
// } else {
// res.json({ success: true, hasSession: false, session: account });
// }
// });
export default router;

View File

@ -0,0 +1,84 @@
import bodyParser from 'body-parser';
import express from 'express';
import authenticationController from '../../../controllers/authentication';
import { isAuthenticated } from '../../../middlewares/authentication';
import { createAccount, verifyEmailToken } from '../../../controllers/users';
// /api/auth
const router = express.Router();
router.get('/session', isAuthenticated, async (req, res) => {
return res.status(200).json({
success: true,
data: {
user: req.account.dataValues,
},
});
});
router.post('/login', bodyParser.urlencoded({ extended: true }), async (req, res) => {
const signIn = await authenticationController.signIn(req.body.email, req.body.password);
if (!signIn.success) {
return res.status(401).json(signIn);
}
const account = await authenticationController.getAccountFromJWT(signIn.jwt);
return res.status(200).cookie('jwt', signIn.jwt).json({
success: true,
data: {
jwt: signIn.jwt,
user: account.dataValues,
},
});
});
router.post('/logout', async (req, res) => {
res.clearCookie('session');
return res.json({ success: true });
});
router.post('/register', bodyParser.urlencoded({ extended: true }), async (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
// FIXME: use logger.warn
console.error('/useradmin/register/token - Malformed Request!');
return res.status(400).json({ success: false, msg: 'malformed request' });
}
const accountStatus = await createAccount(req.body.email, req.body.password);
if (accountStatus && accountStatus.status) {
return res.status(accountStatus.status).json(accountStatus);
}
return res.status(500).json({ success: false, msg: 'contact server admin' });
});
router.post('/register/verify', bodyParser.urlencoded({ extended: true }), async (req, res) => {
const { token } = req.body;
if (!token) {
return res.status(400).json({
success: false,
data: { missingToken: true },
});
}
const verified = await verifyEmailToken(req.params.token);
if (verified && verified.status) {
return res.status(verified.status).json(verified);
}
return res.status(500).json({ success: false, msg: 'contact server admin' });
});
// router.get('/session/get', async (req, res) => {
// const account = await authenticationController.getAuthenticatedAccount(req);
//
// if (!account) {
// res.json({ success: true, hasSession: false, session: {} });
// } else {
// res.json({ success: true, hasSession: false, session: account });
// }
// });
export default router;

View File

@ -5,7 +5,7 @@ import { getURL, getToken } from '../../../controllers/authentication/oauth/goog
import { isAuthenticated } from '../../../middlewares/authentication';
const router = express.Router();
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
router.get('/authentication/oauth/callback', async (req, res) => {
logger.info(req.query);

View File

@ -8,7 +8,7 @@ import { isAuthenticated } from '../../middlewares/authentication';
import deviceController from '../../controllers/devices';
import { MutateDevice } from '../../schema/routes/devices';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
// /api/devices
const router = express.Router();

View File

@ -1,14 +1,45 @@
import express from 'express';
import rateLimit from 'express-rate-limit';
import log4js from 'log4js';
import athena from '../../websocket/athena';
import admin from './admin';
import auth from './auth';
import devices from './devices';
import realtime from './realtime';
import useradmin from './useradmin';
const router = express.Router();
const logger = log4js.getLogger();
// /api
const router = express.Router();
router.use('/admin', admin);
router.use('/auth', auth);
router.use('/devices', devices);
router.use('/realtime', realtime);
router.use('/useradmin', useradmin);
// TODO: setup oauth and twofactor endpoints
// app.use(routers.oauthAuthenticator);
if (process.env.ATHENA_ENABLED) {
logger.info('Athena enabled');
const athenaRateLimit = rateLimit({
windowMs: 30000,
max: process.env.ATHENA_API_RATE_LIMIT,
});
router.use((req, res, next) => {
req.athenaWebsocketTemp = athena;
return next();
});
router.use('/realtime', athenaRateLimit);
router.use('/realtime', realtime);
} else {
logger.info('Athena disabled');
}
export default router;

View File

@ -31,7 +31,7 @@ const whitelistParams = {
router.get('/:dongleId/connected', isAuthenticated, async (req, res) => {
const { account, params: { dongleId } } = req;
const device = await deviceController.getDeviceFromDongle(dongleId);
const device = await deviceController.getDeviceFromDongleId(dongleId);
if (!device) {
return res.status(400).json({
error: true,
@ -72,7 +72,7 @@ router.get('/:dongleId/send/:method/', isAuthenticated, async (req, res) => {
});
}
const device = await deviceController.getDeviceFromDongle(dongleId);
const device = await deviceController.getDeviceFromDongleId(dongleId);
if (!device) {
return res.status(400).json({
error: true,
@ -110,7 +110,7 @@ router.get('/:dongle_id/get', async (req, res) => {
errorObject: { authenticated: false },
});
}
const device = await deviceController.getDeviceFromDongle(req.params.dongle_id);
const device = await deviceController.getDeviceFromDongleId(req.params.dongle_id);
if (!device) {
return res.status(400).json({
error: true,
@ -147,7 +147,7 @@ router.get('/:dongle_id/temp/nav/:lat/:long', async (req, res) => {
if (account == null) {
return res.status(403).json({ error: true, errorMsg: 'Unauthenticated', errorObject: { authenticated: false } });
}
const device = await deviceController.getDeviceFromDongle(req.params.dongle_id);
const device = await deviceController.getDeviceFromDongleId(req.params.dongle_id);
if (!device) {
return res.status(400).json({ error: true, errorMsg: 'no_dongle', errorObject: { authenticated: true, dongle_exists: false } });
}

View File

@ -1,36 +0,0 @@
import bodyParser from 'body-parser';
import express from 'express';
import { createAccount, verifyEmailToken } from '../../controllers/users';
const router = express.Router();
router.post('/retropilot/0/register/email', bodyParser.urlencoded({ extended: true }), async (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
// FIXME: use logger.warn
console.error('/useradmin/register/token - Malformed Request!');
return res.status(400).json({ success: false, msg: 'malformed request' });
}
const accountStatus = await createAccount(req.body.email, req.body.password);
if (accountStatus && accountStatus.status) {
return res.status(accountStatus.status).json(accountStatus);
}
return res.status(500).json({ success: false, msg: 'contact server admin' });
});
router.get('/retropilot/0/register/verify/:token', bodyParser.urlencoded({ extended: true }), async (req, res) => {
const { token } = req.params;
if (!token) {
return res.status(400).json({ success: false, status: 400, data: { missingToken: true } });
}
const verified = await verifyEmailToken(req.params.token);
if (verified && verified.status) {
return res.status(verified.status).json(verified);
}
return res.status(500).json({ success: false, msg: 'contact server admin' });
});
export default router;

View File

@ -0,0 +1,130 @@
import express from 'express';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import controllers from '../../controllers';
import deviceController from '../../controllers/devices';
import { isAuthenticated } from '../../middlewares/authentication';
// TODO Remove this, pending on removing all auth logic from routes
// /api/useradmin
const router = express.Router();
router.use(cookieParser());
function runAsyncWrapper(callback) {
return function wrapper(req, res, next) {
callback(req, res, next)
.catch(next);
};
}
let models;
// FIXME: already provided in auth.js
router.post('/auth', bodyParser.urlencoded({ extended: true }), runAsyncWrapper(async (req, res) => {
const signIn = await controllers.authentication.signIn(req.body.email, req.body.password);
if (!signIn.success) {
return res.redirect(`/useradmin?status=${encodeURIComponent('Invalid credentials or banned account')}`);
}
return res.cookie('jwt', signIn.jwt).redirect('/useradmin/overview');
}));
// FIXME: already provided in auth.js
router.get('/signout', runAsyncWrapper(async (req, res) => {
res.clearCookie('session');
return res.json({ success: true });
}));
router.get('/', runAsyncWrapper(async (req, res) => {
// TODO pull these values from db
const accounts = 0;
const devices = 0;
const drives = 0;
return res.status(200).send({
success: true,
data: {
serverStats: {
config: {
registerAllowed: process.env.ALLOW_REGISTRATION,
welcomeMessage: process.env.WELCOME_MESSAGE,
},
accounts: accounts.num,
devices: devices.num,
drives: drives.num,
storageUsed: await controllers.storage.getTotalStorageUsed(),
},
},
});
}));
router.get('/overview', isAuthenticated, runAsyncWrapper(async (req, res) => {
const { account } = req;
const devices = await deviceController.getDevices(account.id);
// TODO implement a _safe_ get account for these use cases to allow for data to be stripped prior to sending to the client.
delete (account.email_verify_token);
return res.status(200).json({
success: true,
data: {
account,
devices,
},
});
}));
router.get('/unpair_device/:dongleId', isAuthenticated, runAsyncWrapper(async (req, res) => {
const { account, params: { dongleId } } = req;
const device = await deviceController.getDeviceFromDongleId(dongleId);
if (!device) {
return res.status(404).json({ success: false, msg: 'NOT_FOUND' });
} else if (device.accountId !== account.id) {
return res.status(403).json({ success: false, msg: 'FORBIDDEN' });
}
const result = await deviceController.unpairDevice(dongleId, account.id);
if (!result.success) {
return res.status(500).json(result);
}
return res.status(200).json({ success: true });
}));
router.post('/pair_device', [isAuthenticated, bodyParser.urlencoded({ extended: true })], runAsyncWrapper(async (req, res) => {
const { account, body: { qrString } } = req;
if (!qrString) {
return res.json({ success: false, msg: 'BAD_REQUEST', status: 400 });
}
const pairDevice = await controllers.devices.pairDevice(account, qrString);
if (!pairDevice.success) {
return res.json({ success: false, msg: 'error', data: pairDevice });
}
return res.json({
success: true,
msg: 'Paired',
status: 200,
data: pairDevice,
});
}));
router.post('/password/change', [isAuthenticated, bodyParser.urlencoded({ extended: true })], runAsyncWrapper(async (req, res) => {
const { account, body: { oldPassword, newPassword } } = req;
const result = await controllers.authentication.changePassword(
account,
newPassword,
oldPassword,
);
if (!result.success) {
return res.status(result.status).json(result);
}
return res.json({ success: true });
}));
export default router;

View File

@ -1,21 +1,15 @@
/* eslint-disable global-require */
import express from 'express';
import useradmin from './useradmin';
import api from './api';
import useradminapi from './userAdminApi';
import admin from './administration/adminApi';
import realtime from './api/realtime';
import deviceApi from './api/devices';
import authenticationApi from './api/auth';
import oauthAuthenticator from './api/authentication/oauth';
import legacy from './legacy';
import useradmin from './useradmin';
export default {
useradmin,
api,
useradminapi,
admin,
realtime,
deviceApi,
authenticationApi,
oauthAuthenticator,
};
const router = express.Router();
// TODO: refactor
router.use(legacy);
router.use('/api', api);
router.use('/useradmin', useradmin);
export default router;

View File

@ -1,17 +1,16 @@
/* eslint-disable */
import express from 'express';
import bodyParser from 'body-parser';
import crypto from 'crypto';
import express from 'express';
import log4js from 'log4js';
import storageController from '../controllers/storage';
import { validateJWT } from '../controllers/authentication';
import deviceController from '../controllers/devices';
import authenticationController from '../controllers/authentication';
import userController from '../controllers/users';
import storageController from '../controllers/storage';
import { getAccountFromId } from '../controllers/users';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
const router = express.Router();
function runAsyncWrapper(callback) {
return function wrapper(req, res, next) {
callback(req, res, next)
@ -21,7 +20,6 @@ function runAsyncWrapper(callback) {
// TODO(cameron): clean up this mess into separate files
// DRIVE & BOOT/CRASH LOG FILE UPLOAD HANDLING
router.put('/backend/post_upload', bodyParser.raw({
inflate: true,
@ -67,7 +65,7 @@ router.get('/v1.1/devices/:dongleId/', runAsyncWrapper(async (req, res) => {
const { dongleId } = req.params;
logger.info(`HTTP.DEVICES called for ${req.params.dongleId}`);
const device = deviceController.getDeviceFromDongle(dongleId);
const device = deviceController.getDeviceFromDongleId(dongleId);
if (!device) {
logger.info(`HTTP.DEVICES device ${dongleId} not found`);
@ -75,7 +73,7 @@ router.get('/v1.1/devices/:dongleId/', runAsyncWrapper(async (req, res) => {
}
const decoded = device.public_key
? await authenticationController.validateJWT(req.headers.authorization, device.public_key)
? await validateJWT(req.headers.authorization, device.public_key)
: null;
if ((!decoded || decoded.identity !== req.params.dongleId)) {
@ -107,14 +105,14 @@ router.get('/v1.1/devices/:dongleId/stats', runAsyncWrapper(async (req, res) =>
},
};
const device = await deviceController.getDeviceFromDongle(dongleId);
const device = await deviceController.getDeviceFromDongleId(dongleId);
if (!device) {
logger.info(`HTTP.STATS device ${dongleId} not found`);
return res.status(404).json('Not found.');
}
const decoded = device.public_key
? await authenticationController.validateJWT(req.headers.authorization, device.public_key)
? await validateJWT(req.headers.authorization, device.public_key)
: null;
if ((!decoded || decoded.identity !== req.params.dongleId)) {
@ -123,27 +121,26 @@ router.get('/v1.1/devices/:dongleId/stats', runAsyncWrapper(async (req, res) =>
}
// TODO reimplement weekly stats
/*const statresult = await models.get('SELECT COUNT(*) as routes, ROUND(SUM(distance_meters)/1609.34) as distance, ROUND(SUM(duration)/60) as duration FROM drives WHERE dongle_id=?', device.dongle_id);
if (statresult != null && statresult.routes != null) {
stats.all.routes = statresult.routes;
stats.all.distance = statresult.distance != null ? statresult.distance : 0;
stats.all.minutes = statresult.duration != null ? statresult.duration : 0;
}
// this determines the date at 00:00:00 UTC of last monday (== beginning of the current "ISO"week)
const d = new Date();
const day = d.getDay();
const diff = d.getDate() - day + (day === 0 ? -6 : 1);
const lastMonday = new Date(d.setDate(diff));
lastMonday.setHours(0, 0, 0, 0);
const statresultweek = await models.get('SELECT COUNT(*) as routes, ROUND(SUM(distance_meters)/1609.34) as distance, ROUND(SUM(duration)/60) as duration FROM drives WHERE dongle_id=? AND drive_date >= ?', device.dongle_id, lastMonday.getTime());
if (statresultweek != null && statresultweek.routes != null) {
stats.week.routes = statresultweek.routes;
stats.week.distance = statresultweek.distance != null ? statresultweek.distance : 0;
stats.week.minutes = statresultweek.duration != null ? statresultweek.duration : 0;
}*/
// const statresult = await models.get('SELECT COUNT(*) as routes, ROUND(SUM(distance_meters)/1609.34) as distance, ROUND(SUM(duration)/60) as duration FROM drives WHERE dongle_id=?', device.dongle_id);
// if (statresult != null && statresult.routes != null) {
// stats.all.routes = statresult.routes;
// stats.all.distance = statresult.distance != null ? statresult.distance : 0;
// stats.all.minutes = statresult.duration != null ? statresult.duration : 0;
// }
//
// // this determines the date at 00:00:00 UTC of last monday (== beginning of the current "ISO"week)
// const d = new Date();
// const day = d.getDay();
// const diff = d.getDate() - day + (day === 0 ? -6 : 1);
// const lastMonday = new Date(d.setDate(diff));
// lastMonday.setHours(0, 0, 0, 0);
//
// const statresultweek = await models.get('SELECT COUNT(*) as routes, ROUND(SUM(distance_meters)/1609.34) as distance, ROUND(SUM(duration)/60) as duration FROM drives WHERE dongle_id=? AND drive_date >= ?', device.dongle_id, lastMonday.getTime());
// if (statresultweek != null && statresultweek.routes != null) {
// stats.week.routes = statresultweek.routes;
// stats.week.distance = statresultweek.distance != null ? statresultweek.distance : 0;
// stats.week.minutes = statresultweek.duration != null ? statresultweek.duration : 0;
// }
logger.info(`HTTP.STATS for ${req.params.dongleId} returning: ${JSON.stringify(stats)}`);
return res.status(200).json(stats);
@ -154,7 +151,7 @@ router.get('/v1/devices/:dongleId/owner', runAsyncWrapper(async (req, res) => {
const { dongleId } = req.params;
logger.info(`HTTP.OWNER called for ${req.params.dongleId}`);
const device = await deviceController.getDeviceFromDongle(dongleId);
const device = await deviceController.getDeviceFromDongleId(dongleId);
if (!device) {
logger.info(`HTTP.OWNER device ${dongleId} not found`);
@ -162,7 +159,7 @@ router.get('/v1/devices/:dongleId/owner', runAsyncWrapper(async (req, res) => {
}
const decoded = device.public_key
? await authenticationController.validateJWT(req.headers.authorization, device.public_key)
? await validateJWT(req.headers.authorization, device.public_key)
: null;
if ((!decoded || decoded.identity !== req.params.dongleId)) {
@ -171,17 +168,17 @@ router.get('/v1/devices/:dongleId/owner', runAsyncWrapper(async (req, res) => {
}
let owner = '';
let points = 0;
const points = 0;
let account = await userController.getAccountFromId(device.account_id);
let account = await getAccountFromId(device.account_id);
if (account != null && account.dataValues != null) {
account = account.dataValues
account = account.dataValues;
[owner] = account.email.split('@');
// TODO reimplement "points"
/*const stats = await models.all('SELECT SUM(distance_meters) as points FROM drives WHERE dongle_id IN (SELECT dongle_id FROM devices WHERE account_id=?)', account.id);
if (stats != null && stats.points != null) {
points = stats.points;
}*/
// const stats = await models.all('SELECT SUM(distance_meters) as points FROM drives WHERE dongle_id IN (SELECT dongle_id FROM devices WHERE account_id=?)', account.id);
// if (stats != null && stats.points != null) {
// points = stats.points;
// }
}
const response = { username: owner, points };
@ -196,14 +193,14 @@ async function upload(req, res) {
const auth = req.headers.authorization;
logger.info(`HTTP.UPLOAD_URL called for ${req.params.dongleId} and file ${path}: ${JSON.stringify(req.headers)}`);
const device = await deviceController.getDeviceFromDongle(dongleId);
const device = await deviceController.getDeviceFromDongleId(dongleId);
if (!device) {
logger.info(`HTTP.UPLOAD_URL device ${dongleId} not found or not linked to an account / refusing uploads`);
return res.send('Unauthorized.').status(400);
}
const decoded = device.public_key
? await authenticationController.validateJWT(req.headers.authorization, device.public_key).catch(logger.error)
? await validateJWT(req.headers.authorization, device.public_key).catch(logger.error)
: null;
if ((!decoded || decoded.identity !== req.params.dongleId)) {
@ -268,15 +265,16 @@ async function upload(req, res) {
responseUrl = `${process.env.BASE_UPLOAD_URL}?file=${filename}&dir=${directory}&dongleId=${dongleId}&ts=${ts}&token=${token}`;
logger.info(`HTTP.UPLOAD_URL matched 'drive' file upload, constructed responseUrl: ${responseUrl}`);
const drive = await deviceController.getDriveFromIdentifier(dongleId, driveName).catch((err)=>{
logger.warn("drive failed to make", err)
})
const drive = await deviceController.getDriveFromIdentifier(dongleId, driveName)
.catch((err) => {
logger.warn('drive failed to make', err);
});
logger.info("drive value", drive)
logger.info("drive name:", driveName)
logger.info('drive value', drive);
logger.info('drive name:', driveName);
if (drive === undefined || drive === null) {
logger.info("CREATING NEW DRIVE")
logger.info('CREATING NEW DRIVE');
// create a new drive
const timeSplit = driveName.split('--');
const timeString = `${timeSplit[0]} ${timeSplit[1].replace(/-/g, ':')}`;
@ -294,7 +292,7 @@ async function upload(req, res) {
is_preserved: false,
is_deleted: false,
is_physically_removed: false,
})
});
await deviceController.updateOrCreateDriveSegment(dongleId, driveName, segment, {
duration: 0,
@ -303,27 +301,26 @@ async function upload(req, res) {
is_processed: false,
is_stalled: false,
created: Date.now(),
})
});
logger.info(`HTTP.UPLOAD_URL created new drive #${JSON.stringify(driveResult.lastID)}`);
} else {
logger.info("UPDATING DRIVE")
logger.info('UPDATING DRIVE');
await deviceController.updateOrCreateDrive(dongleId, driveName, {
max_segment: Math.max(drive.max_segment, segment),
upload_complete: false,
is_processed: false,
last_upload: Date.now(),
})
await deviceController.updateOrCreateDriveSegment(dongleId, driveName, segment, {
duration: 0,
distance_meters: 0,
upload_complete: false,
is_processed: false,
is_stalled: false,
created: Date.now()
})
});
await deviceController.updateOrCreateDriveSegment(dongleId, driveName, segment, {
duration: 0,
distance_meters: 0,
upload_complete: false,
is_processed: false,
is_stalled: false,
created: Date.now(),
});
logger.info(`HTTP.UPLOAD_URL updated existing drive: ${JSON.stringify(drive)}`);
}
@ -347,7 +344,7 @@ router.post('/v2/pilotauth/', bodyParser.urlencoded({ extended: true }), async (
const {
serial,
public_key: publicKey,
register_token: registerToken
register_token: registerToken,
} = req.query;
if (
@ -359,13 +356,13 @@ router.post('/v2/pilotauth/', bodyParser.urlencoded({ extended: true }), async (
return res.status(400).send('Malformed Request.');
}
const decoded = await authenticationController.validateJWT(registerToken, publicKey);
const decoded = await validateJWT(registerToken, publicKey);
if (!decoded || !decoded.register) {
logger.error(`HTTP.V2.PILOTAUTH JWT token is invalid (${JSON.stringify(decoded)})`);
return res.status(400).send('Malformed Request.');
}
const device = await deviceController.getDeviceFromSerial(serial)
const device = await deviceController.getDeviceFromSerial(serial);
if (device == null) {
logger.info(`HTTP.V2.PILOTAUTH REGISTERING NEW DEVICE (${imei1}, ${serial})`);
@ -373,23 +370,31 @@ router.post('/v2/pilotauth/', bodyParser.urlencoded({ extended: true }), async (
// eslint-disable-next-line no-constant-condition
while (true) {
const dongleId = crypto.randomBytes(4).toString('hex');
const isDongleIdTaken = await deviceController.getDeviceFromDongle(dongleId);
const isDongleIdTaken = await deviceController.getDeviceFromDongleId(dongleId);
if (isDongleIdTaken == null) {
await deviceController.createDongle(dongleId, 0, imei1, serial, publicKey)
await deviceController.createDongle(dongleId, 0, imei1, serial, publicKey);
const newDevice = await deviceController.getDeviceFromDongle(dongleId);
const newDevice = await deviceController.getDeviceFromDongleId(dongleId);
logger.info(`HTTP.V2.PILOTAUTH REGISTERED NEW DEVICE: ${JSON.stringify(newDevice)}`);
return res.status(200).json({ dongle_id: newDevice.dongle_id, access_token: 'DEPRECATED-BUT-REQUIRED-FOR-07' });
return res.status(200).json({
dongle_id: newDevice.dongle_id,
access_token: 'DEPRECATED-BUT-REQUIRED-FOR-07',
});
}
}
}
await deviceController.updateDevice(device.dongle_id, {last_ping: Date.now(), public_key: publicKey})
await deviceController.updateDevice(device.dongle_id, {
last_ping: Date.now(),
public_key: publicKey,
});
logger.info(`HTTP.V2.PILOTAUTH REACTIVATING KNOWN DEVICE (${imei1}, ${serial}) with dongle_id ${device.dongle_id}`);
return res.status(200).json({ dongle_id: device.dongle_id, access_token: 'DEPRECATED-BUT-REQUIRED-FOR-07' });
return res.status(200).json({
dongle_id: device.dongle_id,
access_token: 'DEPRECATED-BUT-REQUIRED-FOR-07',
});
});
// RETRIEVES DATASET FOR OUR MODIFIED CABANA - THIS RESPONSE IS USED TO FAKE A DEMO ROUTE
@ -400,7 +405,7 @@ router.get('/useradmin/cabana_drive/:extendedRouteIdentifier', runAsyncWrapper(a
const driveIdentifier = params[2];
const driveIdentifierHashReq = params[3];
const drive = await deviceController.getDrive(dongleId, driveIdentifier)
const drive = await deviceController.getDrive(dongleId, driveIdentifier);
if (!drive) {
return res.status(200).json({ status: 'drive not found' });

View File

@ -1,508 +0,0 @@
/* eslint-disable max-len */
import express from 'express';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import controllers from '../controllers';
import deviceController from '../controllers/devices';
// TODO Remove this, pending on removing all auth logic from routes
const router = express.Router();
router.use(cookieParser());
function runAsyncWrapper(callback) {
return function wrapper(req, res, next) {
callback(req, res, next)
.catch(next);
};
}
/* eslint-disable no-unused-vars */
let models;
let logger;
/* eslint-enable no-unused-vars */
router.post('/retropilot/0/useradmin/auth', bodyParser.urlencoded({ extended: true }), runAsyncWrapper(async (req, res) => {
const signIn = await controllers.authentication.signIn(req.body.email, req.body.password);
if (!signIn.success) {
return res.redirect(`/useradmin?status=${encodeURIComponent('Invalid credentials or banned account')}`);
}
return res.cookie('jwt', signIn.jwt).redirect('/useradmin/overview');
}));
router.get('/retropilot/0/useradmin/signout', runAsyncWrapper(async (req, res) => {
res.clearCookie('session');
return res.json({ success: true });
}));
router.get('/retropilot/0/useradmin', runAsyncWrapper(async (req, res) => {
// TODO pull these values from db
const accounts = 0;
const devices = 0;
const drives = 0;
return res.status(200).send({
success: true,
data: {
serverStats: {
config: {
registerAllowed: process.env.ALLOW_REGISTRATION,
welcomeMessage: process.env.WELCOME_MESSAGE,
},
accounts: accounts.num,
devices: devices.num,
drives: drives.num,
storageUsed: await controllers.storage.getTotalStorageUsed(),
},
},
});
}));
/*
Requires username and password to register
*/
/*
router.post('/useradmin/register/token', bodyParser.urlencoded({extended: true}), runAsyncWrapper(async (req, res) => {
const email = req.body.email;
if (!process.env.ALLOW_REGISTRATION) {
res.send('Unauthorized.').status(401);
return;
}
const authAccount = await controllers.authentication.getAuthenticatedAccount(req);
if (authAccount != null) {
res.redirect('/useradmin/overview');
return;
}
const account = await models.__db.get('SELECT * FROM accounts WHERE LOWER(email) = ?', email.trim().toLowerCase());
if (account != null) {
res.redirect('/useradmin/register?status=' + encodeURIComponent('Email is already registered'));
return;
}
const token = crypto.createHmac('sha256', process.env.APP_SALT).update(email.trim()).digest('hex');
let infoText = '';
if (req.body.token === undefined) { // email entered, token request
infoText = 'Please check your inbox (<b>SPAM</b>) for an email with the registration token.<br>If the token was not delivered, please ask the administrator to check the <i>server.log</i> for the token generated for your email.<br><br>';
const emailStatus = await controllers.mailing.sendEmailVerification(token, email);
} else { // final registration form filled
if (req.body.token != token) {
infoText = 'The registration token you entered was incorrect, please try again.<br><br>';
} else if (req.body.password != req.body.password2 || req.body.password.length < 3) {
infoText = 'The passwords you entered did not or were shorter than 3 characters, please try again.<br><br>';
} else {
const result = await models.__db.run(
'INSERT INTO accounts (email, password, created, banned) VALUES (?, ?, ?, ?)',
email,
crypto.createHash('sha256').update(req.body.password + process.env.APP_SALT).digest('hex'),
Date.now(), false);
if (result.lastID != undefined) {
logger.info("USERADMIN REGISTRATION - created new account #" + result.lastID + " with email " + email + "");
res.cookie('session', {
account: email,
expires: Date.now() + 1000 * 3600 * 24 * 365
}, {signed: true});
res.redirect('/useradmin/overview');
return;
} else {
logger.error("USERADMIN REGISTRATION - account creation failed, resulting account data for email " + email + " is: " + result);
infoText = 'Unable to complete account registration (database error).<br><br>';
}
}
}
res.status(200);
res.send('<html style="font-family: monospace"><h2>Welcome To The RetroPilot Server Dashboard!</h2>' +
`
<a href="/useradmin">< < < Back To Login</a>
<br><br>
<h3>Register / Finish Registration</h3>
` + infoText + `
<form action="/useradmin/register/token" method="POST">
<input type="email" name="email" placeholder="Email" value="` + htmlspecialchars(email.trim()) + `"required>
<input type="text" name="token" placeholder="Email Token" value="` + (req.body.token != undefined ? htmlspecialchars(req.body.token.trim()) : '') + `" required><br>
<input type="password" name="password" placeholder="Password" value="` + (req.body.password != undefined ? htmlspecialchars(req.body.password.trim()) : '') + `" required>
<input type="password" name="password2" placeholder="Repeat Password" value="` + (req.body.password2 != undefined ? htmlspecialchars(req.body.password2.trim()) : '') + `" required>
<input type="submit" value="Finish Registration">
</html>`);
}))
router.get('/useradmin/register', runAsyncWrapper(async (req, res) => {
if (!process.env.ALLOW_REGISTRATION) {
res.status(400);
res.send('Unauthorized.');
return;
}
const account = await controllers.authentication.getAuthenticatedAccount(req);
if (account != null) {
res.redirect('/useradmin/overview');
return;
}
res.status(200);
res.send('<html style="font-family: monospace"><h2>Welcome To The RetroPilot Server Dashboard!</h2>' +
`
<a href="/useradmin">< < < Back To Login</a>
<br><br>
<h3>Register / Request Email Token</h3>
` + (req.query.status !== undefined ? '<u>' + htmlspecialchars(req.query.status) + '</u><br>' : '') + `
<form action="/useradmin/register/token" method="POST">
<input type="email" name="email" placeholder="Email" required>
<input type="submit" value="Verify Email">
</html>`);
}))
*/
router.get('/retropilot/0/overview', runAsyncWrapper(async (req, res) => {
const account = await controllers.authentication.getAuthenticatedAccount(req);
if (account == null) {
res.send({ success: false, data: { session: false } });
return;
}
const devices = await models.__db.all('SELECT * FROM devices WHERE account_id = ? ORDER BY dongle_id ASC', account.id);
// TODO implement a _safe_ get account for these use cases to allow for data to be stripped prior to sending to the client.
delete (account.email_verify_token);
res.json({
success: true,
data: {
account,
devices,
},
}).status(200);
}));
router.get('/retropilot/0/unpair_device/:dongleId', runAsyncWrapper(async (req, res) => {
const account = await controllers.authentication.getAuthenticatedAccount(req);
if (account == null) {
return res.json({ success: false, data: { session: false } }).status(403);
}
const device = await models.__db.get('SELECT * FROM devices WHERE account_id = ? AND dongle_id = ?', account.id, req.params.dongleId);
if (device == null) {
return res.json({ success: false }).status(400);
}
const pairDeviceToAccountId = await deviceController.pairDeviceToAccountId(req.prams.dongleId, 0);
if (pairDeviceToAccountId.success && pairDeviceToAccountId.paired) {
return res.json({ success: true, data: { unlink: true } });
}
return res.json({ success: true, data: { unlink: false } });
}));
router.post('/retropilot/0/pair_device', bodyParser.urlencoded({ extended: true }), runAsyncWrapper(async (req, res) => {
const account = await controllers.authentication.getAuthenticatedAccount(req);
if (account == null) {
return res.json({ success: false, msg: 'UNAUTHORISED', status: 403 });
}
const { qr_string: qrString } = req.body;
if (!qrString) {
return res.json({ success: false, msg: 'BAD_REQUEST', status: 400 });
}
const pairDevice = await controllers.devices.pairDevice(account, qrString);
if (!pairDevice.success) {
return res.json({ success: false, msg: 'error', data: pairDevice });
}
return res.json({
success: true,
msg: 'Paired',
status: 200,
data: pairDevice,
});
}));
router.post('/retropilot/0/password/change', bodyParser.urlencoded({ extended: true }), runAsyncWrapper(async (req, res) => {
const account = await controllers.authentication.getAuthenticatedAccount(req);
if (account == null) {
return res.json({ success: false, msg: 'UNAUTHORISED', status: 403 });
}
const pwChange = await controllers.authentication.changePassword(account, req.body.newPassword, req.body.oldPassword);
if (!pwChange.success) {
return res.json({ success: false, data: pwChange });
}
return res.json({ success: true });
}));
/*
router.get('/useradmin/device/:dongleId', runAsyncWrapper(async (req, res) => {
const account = await controllers.authentication.getAuthenticatedAccount(req);
if (account == null) {
res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session'));
return;
}
const device = await models.__db.get('SELECT * FROM devices WHERE account_id = ? AND dongle_id = ?', account.id, req.params.dongleId);
if (device == null) {
res.status(400);
res.send('Unauthorized.');
return;
}
const drives = await models.__db.all('SELECT * FROM drives WHERE dongle_id = ? AND is_deleted = ? ORDER BY created DESC', device.dongle_id, false);
var dongleIdHash = crypto.createHmac('sha256', process.env.APP_SALT).update(device.dongle_id).digest('hex');
const bootlogDirectoryTree = dirTree(process.env.STORAGE_PATH + device.dongle_id + "/" + dongleIdHash + "/boot/", {attributes: ['size']});
var bootlogFiles = [];
if (bootlogDirectoryTree != undefined) {
for (var i = 0; i < bootlogDirectoryTree.children.length; i++) {
var timeSplit = bootlogDirectoryTree.children[i].name.replace('boot-', '').replace('crash-', '').replace('\.bz2', '').split('--');
var timeString = timeSplit[0] + ' ' + timeSplit[1].replace(/-/g, ':');
bootlogFiles.push({
'name': bootlogDirectoryTree.children[i].name,
'size': bootlogDirectoryTree.children[i].size,
'date': Date.parse(timeString)
});
}
bootlogFiles.sort((a, b) => (a.date < b.date) ? 1 : -1);
}
const crashlogDirectoryTree = dirTree(process.env.STORAGE_PATH + device.dongle_id + "/" + dongleIdHash + "/crash/", {attributes: ['size']});
var crashlogFiles = [];
if (crashlogDirectoryTree != undefined) {
for (var i = 0; i < crashlogDirectoryTree.children.length; i++) {
var timeSplit = crashlogDirectoryTree.children[i].name.replace('boot-', '').replace('crash-', '').replace('\.bz2', '').split('--');
var timeString = timeSplit[0] + ' ' + timeSplit[1].replace(/-/g, ':');
crashlogFiles.push({
'name': crashlogDirectoryTree.children[i].name,
'size': crashlogDirectoryTree.children[i].size,
'date': Date.parse(timeString)
});
}
crashlogFiles.sort((a, b) => (a.date < b.date) ? 1 : -1);
}
var response = '<html style="font-family: monospace"><h2>Welcome To The RetroPilot Server Dashboard!</h2>' +
`
<a href="/useradmin/overview">< < < Back To Overview</a>
<br><br><h3>Device ` + device.dongle_id + `</h3>
<b>Type:</b> ` + device.device_type + `<br>
<b>Serial:</b> ` + device.serial + `<br>
<b>IMEI:</b> ` + device.imei + `<br>
<b>Registered:</b> ` + controllers.helpers.formatDate(device.created) + `<br>
<b>Last Ping:</b> ` + controllers.helpers.formatDate(device.last_ping) + `<br>
<b>Public Key:</b><br><span style="font-size: 0.8em">` + device.public_key.replace(/\r?\n|\r/g, "<br>") + `</span>
<br>
<b>Stored Drives:</b> ` + drives.length + `<br>
<b>Quota Storage:</b> ` + device.storage_used + ` MB / ` + process.env.DEVICE_STORAGE_QUOTA_MB + ` MB<br>
<br>
`;
response += `<b>Boot Logs (last 5):</b><br>
<table border=1 cellpadding=2 cellspacing=2>
<tr><th>date</th><th>file</th><th>size</th></tr>
`;
for (var i = 0; i < Math.min(5, bootlogFiles.length); i++) {
response += `<tr><td>` + controllers.helpers.formatDate(bootlogFiles[i].date) + `</td><td><a href="` + process.env.BASE_DRIVE_DOWNLOAD_URL + device.dongle_id + "/" + dongleIdHash + "/boot/" + bootlogFiles[i].name + `" target=_blank>` + bootlogFiles[i].name + `</a></td><td>` + bootlogFiles[i].size + `</td></tr>`;
}
response += `</table><br><br>`;
response += `<b>Crash Logs (last 5):</b><br>
<table border=1 cellpadding=2 cellspacing=2>
<tr><th>date</th><th>file</th><th>size</th></tr>
`;
for (var i = 0; i < Math.min(5, crashlogFiles.length); i++) {
response += `<tr><td>` + controllers.helpers.formatDate(crashlogFiles[i].date) + `</td><td><a href="` + process.env.BASE_DRIVE_DOWNLOAD_URL + device.dongle_id + "/" + dongleIdHash + "/crash/" + crashlogFiles[i].name + `" target=_blank>` + crashlogFiles[i].name + `</a></td><td>` + crashlogFiles[i].size + `</td></tr>`;
}
response += `</table><br><br>`;
response += `<b>Drives (non-preserved drives expire ` + process.env.DEVICE_EXPIRATION_DAYS + ` days after upload):</b><br>
<table border=1 cellpadding=2 cellspacing=2>
<tr><th>identifier</th><th>filesize</th><th>duration</th><th>distance_meters</th><th>upload_complete</th><th>is_processed</th><th>upload_date</th><th>actions</th></tr>
`;
for (var i in drives) {
response += '<tr><td><a href="/useradmin/drive/' + drives[i].dongle_id + '/' + drives[i].identifier + '">' + (drives[i].is_preserved ? '<b>' : '') + drives[i].identifier + (drives[i].is_preserved ? '</b>' : '') + '</a></td><td>' + Math.round(drives[i].filesize / 1024) + ' MiB</td><td>' + controllers.helpers.formatDuration(drives[i].duration) + '</td><td>' + Math.round(drives[i].distance_meters / 1000) + ' km</td><td>' + drives[i].upload_complete + '</td><td>' + drives[i].is_processed + '</td><td>' + controllers.helpers.formatDate(drives[i].created) + '</td><td>' + '[<a href="/useradmin/drive/' + drives[i].dongle_id + '/' + drives[i].identifier + '/delete" onclick="return confirm(\'Permanently delete this drive?\')">delete</a>]' + (drives[i].is_preserved ? '' : '&nbsp;&nbsp;[<a href="/useradmin/drive/' + drives[i].dongle_id + '/' + drives[i].identifier + '/preserve">preserve</a>]') + '</tr>';
}
response += `</table>
<br>
<hr/>
<a href="/useradmin/unpair_device/` + device.dongle_id + `" onclick="return confirm('Are you sure that you want to unpair your device? Uploads will be rejected until it is paired again.')">Unpair Device</a>
<br><br>
<hr/>
<a href="/useradmin/signout">Sign Out</a></html>`;
res.status(200);
res.send(response);
}))
router.get('/useradmin/drive/:dongleId/:driveIdentifier/:action', runAsyncWrapper(async (req, res) => {
const account = await controllers.authentication.getAuthenticatedAccount(req);
if (account == null) {
res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session'));
return;
}
const device = await models.__db.get('SELECT * FROM devices WHERE account_id = ? AND dongle_id = ?', account.id, req.params.dongleId);
if (device == null) {
res.status(400);
res.send('Unauthorized.');
return;
}
const drive = await models.__db.get('SELECT * FROM drives WHERE identifier = ? AND dongle_id = ?', req.params.driveIdentifier, req.params.dongleId);
if (drive == null) {
res.status(400);
res.send('Unauthorized.');
return;
}
if (req.params.action == 'delete') {
const result = await models.__db.run(
'UPDATE drives SET is_deleted = ? WHERE id = ?',
true, drive.id
);
} else if (req.params.action == 'preserve') {
const result = await models.__db.run(
'UPDATE drives SET is_preserved = ? WHERE id = ?',
true, drive.id
);
}
res.redirect('/useradmin/device/' + device.dongle_id);
}))
router.get('/useradmin/drive/:dongleId/:driveIdentifier', runAsyncWrapper(async (req, res) => {
const account = await controllers.authentication.getAuthenticatedAccount(req);
if (account == null) {
res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session'));
return;
}
const device = await models.__db.get('SELECT * FROM devices WHERE account_id = ? AND dongle_id = ?', account.id, req.params.dongleId);
if (device == null) {
res.status(400);
res.send('Unauthorized.');
return;
}
const drive = await models.__db.get('SELECT * FROM drives WHERE identifier = ? AND dongle_id = ?', req.params.driveIdentifier, req.params.dongleId);
if (drive == null) {
res.status(400);
res.send('Unauthorized.');
return;
}
var dongleIdHash = crypto.createHmac('sha256', process.env.APP_SALT).update(device.dongle_id).digest('hex');
var driveIdentifierHash = crypto.createHmac('sha256', process.env.APP_SALT).update(drive.identifier).digest('hex');
var driveUrl = process.env.BASE_DRIVE_DOWNLOAD_URL + device.dongle_id + "/" + dongleIdHash + "/" + driveIdentifierHash + "/" + drive.identifier + "/";
var cabanaUrl = null;
if (drive.is_processed) {
cabanaUrl = process.env.CABANA_URL + '?retropilotIdentifier=' + device.dongle_id + '|' + dongleIdHash + '|' + drive.identifier + '|' + driveIdentifierHash + '&retropilotHost=' + encodeURIComponent(process.env.BASE_URL) + '&demo=1"';
}
const directoryTree = dirTree(process.env.STORAGE_PATH + device.dongle_id + "/" + dongleIdHash + "/" + driveIdentifierHash + "/" + drive.identifier);
var response = '<html style="font-family: monospace"><h2>Welcome To The RetroPilot Server Dashboard!</h2>' +
`
<a href="/useradmin/device/` + device.dongle_id + `">< < < Back To Device ` + device.dongle_id + `</a>
<br><br><h3>Drive ` + drive.identifier + ` on ` + drive.dongle_id + `</h3>
<b>Drive Date:</b> ` + controllers.helpers.formatDate(drive.drive_date) + `<br>
<b>Upload Date:</b> ` + controllers.helpers.formatDate(drive.created) + `<br>
<b>Num Segments:</b> ` + (drive.max_segment + 1) + `<br>
<b>Storage:</b> ` + Math.round(drive.filesize / 1024) + ` MiB<br>
<b>Duration:</b> ` + controllers.helpers.formatDuration(drive.duration) + `<br>
<b>Distance:</b> ` + Math.round(drive.distance_meters / 1000) + ` km<br>
<b>Is Preserved:</b> ` + drive.is_preserved + `<br>
<b>Upload Complete:</b> ` + drive.upload_complete + `<br>
<b>Processed:</b> ` + drive.is_processed + `<br>
<br><br>
` + (cabanaUrl ? '<a href="' + cabanaUrl + '" target=_blank><b>View Drive in CABANA</b></a><br><br>' : '') + `
<b>Files:</b><br>
<table border=1 cellpadding=2 cellspacing=2>
<tr><th>segment</th><th>qcamera</th><th>qlog</th><th>fcamera</th><th>rlog</th><th>dcamera</th><th>processed</th><th>stalled</th></tr>
`;
var directorySegments = {};
for (var i in directoryTree.children) {
// skip any non-directory entries (for example m3u8 file in the drive directory)
if (directoryTree.children[i].type !== 'directory') continue;
var segment = directoryTree.children[i].name;
var qcamera = '--';
var fcamera = '--';
var dcamera = '--';
var qlog = '--';
var rlog = '--';
for (var c in directoryTree.children[i].children) {
if (directoryTree.children[i].children[c].name == 'fcamera.hevc') fcamera = '<a target="_blank" href="' + driveUrl + segment + '/' + directoryTree.children[i].children[c].name + '">' + directoryTree.children[i].children[c].name + '</a>';
if (directoryTree.children[i].children[c].name == 'dcamera.hevc') fcamera = '<a target="_blank" href="' + driveUrl + segment + '/' + directoryTree.children[i].children[c].name + '">' + directoryTree.children[i].children[c].name + '</a>';
if (directoryTree.children[i].children[c].name == 'qcamera.ts') qcamera = '<a target="_blank" href="' + driveUrl + segment + '/' + directoryTree.children[i].children[c].name + '">' + directoryTree.children[i].children[c].name + '</a>';
if (directoryTree.children[i].children[c].name == 'qlog.bz2') qlog = '<a target="_blank" href="' + driveUrl + segment + '/' + directoryTree.children[i].children[c].name + '">' + directoryTree.children[i].children[c].name + '</a>';
if (directoryTree.children[i].children[c].name == 'rlog.bz2') rlog = '<a target="_blank" href="' + driveUrl + segment + '/' + directoryTree.children[i].children[c].name + '">' + directoryTree.children[i].children[c].name + '</a>';
}
var isProcessed = '?';
var isStalled = '?';
const drive_segment = await models.__db.get('SELECT * FROM drive_segments WHERE segment_id = ? AND drive_identifier = ? AND dongle_id = ?', parseInt(segment), drive.identifier, device.dongle_id);
if (drive_segment) {
isProcessed = drive_segment.is_processed;
isStalled = drive_segment.is_stalled;
}
directorySegments["seg-" + segment] = '<tr><td>' + segment + '</td><td>' + qcamera + '</td><td>' + qlog + '</td><td>' + fcamera + '</td><td>' + rlog + '</td><td>' + dcamera + '</td><td>' + isProcessed + '</td><td>' + isStalled + '</td></tr>';
}
var qcamera = '--';
var fcamera = '--';
var dcamera = '--';
var qlog = '--';
var rlog = '--';
var isProcessed = '?';
var isStalled = '?';
for (var i = 0; i <= drive.max_segment; i++) {
if (directorySegments["seg-" + i] == undefined) {
response += '<tr><td>' + i + '</td><td>' + qcamera + '</td><td>' + qlog + '</td><td>' + fcamera + '</td><td>' + rlog + '</td><td>' + dcamera + '</td><td>' + isProcessed + '</td><td>' + isStalled + '</td></tr>';
} else
response += directorySegments["seg-" + i];
}
response += `</table>
<br><br>
<hr/>
<a href="/useradmin/signout">Sign Out</a></html>`;
res.status(200);
res.send(response);
}))
*/
export default router;

View File

@ -12,8 +12,9 @@ import helperController from '../controllers/helpers';
import mailingController from '../controllers/mailing';
import deviceController from '../controllers/devices';
import userController from '../controllers/users';
import { getAccount, isAuthenticated } from '../middlewares/authentication';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
let models;
const router = express.Router();
// TODO Remove this, pending on removing all auth logic from routes
@ -228,8 +229,8 @@ router.get('/useradmin/overview', runAsyncWrapper(async (req, res) => {
<h3>Pair New Devices</h3>
<i>* To pair a new device, first have it auto-register on this server.<br>Then scan the QR Code and paste the Device Token below.</i><br>
${req.query.linkstatus !== undefined ? `<br><u>${htmlspecialchars(req.query.linkstatus)}</u><br><br>` : ''}
<form action="/useradmin/pair_device" method="POST">
<input type="text" name="qr_string" placeholder="QR Code Device Token" required>
<form action="/api/useradmin/pair_device" method="POST">
<input type="text" name="qrString" placeholder="QR Code Device Token" required>
<input type="submit" value="Pair">
</form>
<br><br>
@ -241,7 +242,7 @@ ${req.query.linkstatus !== undefined ? `<br><u>${htmlspecialchars(req.query.link
return res.status(200).send(response);
}));
router.get('/useradmin/unpair_device/:dongleId', runAsyncWrapper(async (req, res) => {
router.get('/api/useradmin/unpair_device/:dongleId', runAsyncWrapper(async (req, res) => {
const account = await authenticationController.getAuthenticatedAccount(req);
if (account == null) {
return res.redirect(`/useradmin?status=${encodeURIComponent('Invalid or expired session')}`);
@ -250,14 +251,14 @@ router.get('/useradmin/unpair_device/:dongleId', runAsyncWrapper(async (req, res
return res.redirect('/useradmin/overview');
}));
router.post('/useradmin/pair_device', bodyParser.urlencoded({ extended: true }), runAsyncWrapper(async (req, res) => {
const account = await authenticationController.getAuthenticatedAccount(req);
if (account == null) {
router.post('/useradmin/pair_device', [getAccount, bodyParser.urlencoded({ extended: true })], runAsyncWrapper(async (req, res) => {
const { account, body: { qrString } } = req;
if (!account) {
res.redirect(`/useradmin?status=${encodeURIComponent('Invalid or expired session')}`);
return;
}
const pairDevice = await deviceController.pairDevice(account, req.body.qr_string);
const pairDevice = await deviceController.pairDevice(account, req.body.qrString);
if (pairDevice.success === true) {
res.redirect('/useradmin/overview');
} else if (pairDevice.registered === true) {
@ -281,7 +282,7 @@ router.get('/useradmin/device/:dongleId', runAsyncWrapper(async (req, res) => {
return res.redirect(`/useradmin?status=${encodeURIComponent('Invalid or expired session')}`);
}
const device = await deviceController.getDeviceFromDongle(req.params.dongleId);
const device = await deviceController.getDeviceFromDongleId(req.params.dongleId);
if (device == null || device.account_id !== account.id) {
return res.status(400).send('Unauthorized.');
}
@ -428,7 +429,7 @@ router.get('/useradmin/drive/:dongleId/:driveIdentifier', runAsyncWrapper(async
return res.redirect(`/useradmin?status=${encodeURIComponent('Invalid or expired session')}`);
}
const device = await deviceController.getDeviceFromDongle(req.params.dongleId);
const device = await deviceController.getDeviceFromDongleId(req.params.dongleId);
if (device == null || device.account_id !== account.id) {
return res.status(400).send('Unauthorized.');
}

View File

@ -5,7 +5,7 @@ import log4js from 'log4js';
import app from './app';
app.then((server) => {
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
const httpServer = http.createServer(server);
httpServer.listen(process.env.HTTP_PORT, () => {

View File

@ -10,7 +10,7 @@ import { AthenaActionLog, AthenaReturnedData } from '../../../models';
import deviceController from '../../controllers/devices';
import helperFunctions from './helpers';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
let helpers;
let wss;
@ -78,7 +78,7 @@ async function manageConnection(ws, res) {
ws.on('message', async (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_UNAUTHENTICATED_MESSAGE', null, ws._socket.remoteAddress, JSON.stringify([message]), ws.dongleId);
console.log('unauthenticated message, discarded');
return;
}
@ -133,7 +133,7 @@ wss.retropilotFunc = {
return false;
}
const device = await deviceController.getDeviceFromDongle(unsafeJwt.identity);
const device = await deviceController.getDeviceFromDongleId(unsafeJwt.identity);
let verifiedJWT;
console.log('JWT', cookies.jwt);

View File

@ -8,7 +8,7 @@ import athenaRealtime from '../athena';
import controlsFunction from './controls';
import realtimeCommands from './commands';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
let server;
let wss;

View File

@ -11,7 +11,7 @@ process.on('unhandledRejection', (error, p) => {
console.dir(error.stack);
});
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
// make sure bunzip2 is available
try {

View File

@ -2,7 +2,7 @@ import path from 'path';
import fs from 'fs';
import log4js from 'log4js';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
export function initializeStorage() {
const verifiedPath = mkDirByPathSync(process.env.STORAGE_PATH, { isRelativeToScript: (process.env.STORAGE_PATH.indexOf('/') !== 0) });

View File

@ -14,7 +14,7 @@ import {
deleteFolderRecursive,
} from './storage';
const logger = log4js.getLogger('default');
const logger = log4js.getLogger();
const startTime = Date.now();
let lastCleaningTime = 0;

View File

@ -10,7 +10,7 @@ export default (app) => {
describe('/api', () => {
it('Load general app stats', (done) => {
request(server)
.get('/retropilot/0/useradmin')
.get('/api/useradmin')
.expect('Content-Type', /json/)
.expect(200)
.expect((req) => {
@ -36,7 +36,7 @@ export default (app) => {
// eslint-disable-next-line no-empty
} catch (ignored) {
}
throw new Error('Invalid returned parameters in GET /retropilot/0/useradmin ');
throw new Error('Invalid returned parameters in GET /api/useradmin ');
})
.end(done);
});