Removed functions from server.js and put them in their own controllers. Updated .gitignore to ignore dbs/configs/.idea files

pull/4/head
Adam Black 2021-05-21 22:32:55 +01:00
parent a5fbb94162
commit ca745ca470
8 changed files with 315 additions and 218 deletions

3
.gitignore vendored
View File

@ -6,3 +6,6 @@ package-lock.json
database.sqlite database.sqlite
config.js config.js
.vscode .vscode
.idea
database.sqlite
config.js

View File

@ -1,6 +1,7 @@
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
let models;
let logger;
async function validateJWT(token, key) { async function validateJWT(token, key) {
@ -14,6 +15,31 @@ async function validateJWT(token, key) {
} }
module.exports = { async function getAuthenticatedAccount(req, res) {
validateJWT: validateJWT const sessionCookie = (req.signedCookies !== undefined ? req.signedCookies.session : null);
if (!sessionCookie || sessionCookie.expires <= Date.now()) { return null; }
const email = sessionCookie.account.trim().toLowerCase();
// don't need to wait for this, logging a ping if a banned user attempts to sign in
// TODO stop storing emails in the cookie
const account = await models.users.getAccountFromEmail(email)
if (!account || account.banned) {
res ? res.clearCookie('session') : logger.warn(`getAuthenticatedAccount unable to clear banned user (${account.email}) cookie, res not passed`);
return null;
}
return account;
}
module.exports = (_models, _logger) => {
models = _models;
logger = _logger;
return {
validateJWT: validateJWT,
getAuthenticatedAccount: getAuthenticatedAccount
}
} }

View File

@ -0,0 +1,51 @@
let models;
let logger;
function formatDuration(durationSeconds) {
durationSeconds = Math.round(durationSeconds);
const secs = durationSeconds % 60;
let mins = Math.floor(durationSeconds / 60);
const hours = Math.floor(mins / 60);
mins = mins % 60;
let response = '';
if (hours > 0) response += hours + 'h ';
if (hours > 0 || mins > 0) response += mins + 'm ';
response += secs + 's';
return response;
}
function simpleStringify(object) {
let simpleObject = {};
for (var prop in object) {
if (!object.hasOwnProperty(prop)) {
continue;
}
if (typeof (object[prop]) == 'object') {
continue;
}
if (typeof (object[prop]) == 'function') {
continue;
}
simpleObject[prop] = object[prop];
}
return JSON.stringify(simpleObject); // returns cleaned up JSON
}
function formatDate(timestampMs) {
return new Date(timestampMs).toISOString().replace(/T/, ' ').replace(/\..+/, '');
}
module.exports = (_models, _logger) => {
models = _models;
logger = _logger;
return {
formatDuration, simpleStringify, formatDate
}
}

View File

@ -1,6 +1,13 @@
const config = require('./../config');
module.exports = async (models, logger) => {
module.exports = {
authenticationController: require('./authentication') return {
} authentication: require('./authentication')(models, logger),
helpers: require('./helpers')(models, logger),
storage: require('./storage')(models, logger)
}
}

View File

@ -0,0 +1,129 @@
const config = require('./../config');
const path = require('path');
const fs = require('fs')
let models;
let logger;
let totalStorageUsed;
function initializeStorage() {
var verifiedPath = mkDirByPathSync(config.storagePath, {isRelativeToScript: (config.storagePath.indexOf("/") === 0 ? false : true)});
if (verifiedPath != null)
logger.info("Verified storage path " + verifiedPath);
else {
logger.error("Unable to verify storage path '" + config.storagePath + "', check filesystem / permissions");
process.exit();
}
}
function mkDirByPathSync(targetDir, {isRelativeToScript = false} = {}) {
const sep = path.sep;
const initDir = path.isAbsolute(targetDir) ? sep : '';
// TODO does this break anything? Commented out code will create a folder in the /controllers directory, defined __basedir as a global var in server.js
const baseDir = __basedir; //isRelativeToScript ? __dirname : '.';
return targetDir.split(sep).reduce((parentDir, childDir) => {
const curDir = path.resolve(baseDir, parentDir, childDir);
try {
fs.mkdirSync(curDir);
} catch (err) {
console.debug(err);
if (err.code === 'EEXIST') { // curDir already exists!
return curDir;
}
// To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.
if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure.
logger.error(`EACCES: permission denied, mkdir '${parentDir}'`);
return null;
}
const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1;
if (!caughtErr || (caughtErr && curDir === path.resolve(targetDir))) {
logger.error("'EACCES', 'EPERM', 'EISDIR' during mkdir");
return null;
}
}
return curDir;
}, initDir);
}
function writeFileSync(path, buffer, permission) {
let fileDescriptor;
try {
fileDescriptor = fs.openSync(path, 'w', permission);
} catch (e) {
fs.chmodSync(path, permission);
fileDescriptor = fs.openSync(path, 'w', permission);
}
if (fileDescriptor) {
fs.writeSync(fileDescriptor, buffer, 0, buffer.length, 0);
fs.closeSync(fileDescriptor);
logger.info("writeFileSync wiriting to '" + path + "' successful");
return true;
}
logger.error("writeFileSync writing to '" + path + "' failed");
return false;
}
function moveUploadedFile(buffer, directory, filename) {
logger.info(`moveUploadedFile called with ${filename} -> ${directory}'`);
if (directory.indexOf("..") >= 0 || filename.indexOf("..") >= 0) {
logger.error("moveUploadedFile failed, .. in directory or filename");
return false;
}
if (config.storagePath.lastIndexOf("/") !== config.storagePath.length - 1)
directory = '/' + directory;
if (directory.lastIndexOf("/") !== directory.length - 1)
directory = directory + '/';
const finalPath = mkDirByPathSync(config.storagePath + directory, {isRelativeToScript: (config.storagePath.indexOf("/") === 0 ? false : true)});
if (finalPath && finalPath.length > 0) {
if (writeFileSync(finalPath + "/" + filename, buffer, 0o660)) {
logger.info("moveUploadedFile successfully written '" + (finalPath + "/" + filename) + "'");
return finalPath + "/" + filename;
}
logger.error("moveUploadedFile failed to writeFileSync");
return false;
}
logger.error("moveUploadedFile invalid final path, check permissions to create / write '" + (config.storagePath + directory) + "'");
return false;
}
async function updateTotalStorageUsed() {
const verifiedPath = mkDirByPathSync(config.storagePath, {isRelativeToScript: (config.storagePath.indexOf("/") === 0 ? false : true)});
if (verifiedPath !== null) {
try {
totalStorageUsed = execSync("du -hs " + verifiedPath + " | awk -F'\t' '{print $1;}'").toString();
} catch (exception) {
totalStorageUsed = "Unsupported Platform";
logger.debug(`Unable to calculate storage used, only supported on systems with 'du' available`)
}
}
setTimeout(function () {
updateTotalStorageUsed();
}, 120000); // update the used storage each 120 seconds
}
async function getTotalStorageUsed() {
return totalStorageUsed;
}
module.exports = (_models, _logger) => {
models = _models;
logger = _logger;
return {
initializeStorage, mkDirByPathSync, writeFileSync, moveUploadedFile, updateTotalStorageUsed, getTotalStorageUsed
}
}

View File

@ -2,11 +2,7 @@ const sqlite3 = require('sqlite3')
const {open} = require('sqlite') const {open} = require('sqlite')
const config = require('./../config'); const config = require('./../config');
async function validateDatabase(db, logger) {
module.exports = async (logger) => {
let db;
try { try {
db = await open({ db = await open({
filename: config.databaseFile, filename: config.databaseFile,
@ -22,12 +18,36 @@ module.exports = async (logger) => {
logger.error(exception); logger.error(exception);
process.exit(); process.exit();
} }
}
module.exports = async (logger) => {
let db;
try {
db = await open({
filename: config.databaseFile,
driver: sqlite3.Database,
mode: sqlite3.OPEN_READWRITE
});
} catch (exception) {
logger.error(exception);
process.exit();
}
// I'm not sure we _really_ need to wait for this, since it'll exit the application if it's invalid anyway.
await validateDatabase(db, logger);
return { return {
db, db,
models: { models: {
drivesModel: require('./drives')(db) drivesModel: require('./drives')(db),
users: require('./users')(db),
} }
} }
} }

21
models/users.js 100644
View File

@ -0,0 +1,21 @@
let db;
async function userPing(email) {
return await db.run('UPDATE accounts SET last_ping = ? WHERE email = ?', Date.now(), email);
}
async function getAccountFromEmail(email) {
return await db.get('SELECT * FROM accounts WHERE LOWER(email) = ?', email);
}
module.exports = (_db) => {
db = _db;
return {
userPing,
getAccountFromEmail
}
}

252
server.js
View File

@ -3,202 +3,35 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const crypto = require('crypto'); const crypto = require('crypto');
const log4js = require('log4js'); const log4js = require('log4js');
const lockfile = require('proper-lockfile'); const lockfile = require('proper-lockfile');
const http = require('http');
var http = require('http'); const https = require('https');
var https = require('https');
const express = require('express'); const express = require('express');
const cors = require('cors'); const cors = require('cors');
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser'); const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const sendmail = require('sendmail')(); const sendmail = require('sendmail')();
const htmlspecialchars = require('htmlspecialchars'); const htmlspecialchars = require('htmlspecialchars');
const dirTree = require("directory-tree"); const dirTree = require("directory-tree");
const execSync = require('child_process').execSync; const execSync = require('child_process').execSync;
log4js.configure({ log4js.configure({
appenders: {logfile: {type: "file", filename: "server.log"}, out: {type: "console"}}, appenders: {logfile: {type: "file", filename: "server.log"}, out: {type: "console"}},
categories: {default: {appenders: ['out', 'logfile'], level: 'info'}} categories: {default: {appenders: ['out', 'logfile'], level: 'info'}}
}); });
var logger = log4js.getLogger('default'); const logger = log4js.getLogger('default');
// TODO evaluate if this is the best way to determine the root of project
global.__basedir = __dirname;
let models = require('./models/index'); let models = require('./models/index');
let controllers = require('./controllers');
let db; let db;
let {authenticationController} = require('./controllers');
var totalStorageUsed = null; // global variable that is regularly updated in the background to track the total used storage // TODO
function initializeStorage() {
var verifiedPath = mkDirByPathSync(config.storagePath, {isRelativeToScript: (config.storagePath.indexOf("/") === 0 ? false : true)});
if (verifiedPath != null)
logger.info("Verified storage path " + verifiedPath);
else {
logger.error("Unable to verify storage path '" + config.storagePath + "', check filesystem / permissions");
process.exit();
}
}
function validateJWTToken(token, publicKey) {
try {
var decoded = jwt.verify(token.replace("JWT ", ""), publicKey, {algorithms: ['RS256']});
return decoded;
} catch (exception) {
logger.error(exception);
}
return null;
}
function formatDate(timestampMs) {
return new Date(timestampMs).toISOString().replace(/T/, ' ').replace(/\..+/, '');
}
function formatDuration(durationSeconds) {
durationSeconds = Math.round(durationSeconds);
var secs = durationSeconds % 60;
var mins = Math.floor(durationSeconds / 60);
var hours = Math.floor(mins / 60);
mins = mins % 60;
var response = '';
if (hours > 0) response += hours + 'h ';
if (hours > 0 || mins > 0) response += mins + 'm ';
response += secs + 's';
return response;
}
function mkDirByPathSync(targetDir, {isRelativeToScript = false} = {}) {
const sep = path.sep;
const initDir = path.isAbsolute(targetDir) ? sep : '';
const baseDir = isRelativeToScript ? __dirname : '.';
return targetDir.split(sep).reduce((parentDir, childDir) => {
const curDir = path.resolve(baseDir, parentDir, childDir);
try {
fs.mkdirSync(curDir);
} catch (err) {
if (err.code === 'EEXIST') { // curDir already exists!
return curDir;
}
// To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.
if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure.
logger.error(`EACCES: permission denied, mkdir '${parentDir}'`);
return null;
}
const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1;
if (!caughtErr || (caughtErr && curDir === path.resolve(targetDir))) {
logger.error("'EACCES', 'EPERM', 'EISDIR' during mkdir");
return null;
}
}
return curDir;
}, initDir);
}
function simpleStringify(object) {
var simpleObject = {};
for (var prop in object) {
if (!object.hasOwnProperty(prop)) {
continue;
}
if (typeof (object[prop]) == 'object') {
continue;
}
if (typeof (object[prop]) == 'function') {
continue;
}
simpleObject[prop] = object[prop];
}
return JSON.stringify(simpleObject); // returns cleaned up JSON
}
function writeFileSync(path, buffer, permission) {
var fileDescriptor;
try {
fileDescriptor = fs.openSync(path, 'w', permission);
} catch (e) {
fs.chmodSync(path, permission);
fileDescriptor = fs.openSync(path, 'w', permission);
}
if (fileDescriptor) {
fs.writeSync(fileDescriptor, buffer, 0, buffer.length, 0);
fs.closeSync(fileDescriptor);
logger.info("writeFileSync wiriting to '" + path + "' successful");
return true;
}
logger.error("writeFileSync writing to '" + path + "' failed");
return false;
}
function moveUploadedFile(buffer, directory, filename) {
logger.info("moveUploadedFile called with '" + filename + "' -> '" + directory + "'");
if (directory.indexOf("..") >= 0 || filename.indexOf("..") >= 0) {
logger.error("moveUploadedFile failed, .. in directory or filename");
return false;
}
if (config.storagePath.lastIndexOf("/") !== config.storagePath.length - 1)
directory = '/' + directory;
if (directory.lastIndexOf("/") !== directory.length - 1)
directory = directory + '/';
var finalPath = mkDirByPathSync(config.storagePath + directory, {isRelativeToScript: (config.storagePath.indexOf("/") === 0 ? false : true)});
if (finalPath && finalPath.length > 0) {
if (writeFileSync(finalPath + "/" + filename, buffer, 0o660)) {
logger.info("moveUploadedFile successfully written '" + (finalPath + "/" + filename) + "'");
return finalPath + "/" + filename;
}
logger.error("moveUploadedFile failed to writeFileSync");
return false;
}
logger.error("moveUploadedFile invalid final path, check permissions to create / write '" + (config.storagePath + directory) + "'");
return false;
}
async function getAuthenticatedAccount(req) {
var sessionCookie = (req.signedCookies !== undefined ? req.signedCookies.session : null);
if (!sessionCookie || sessionCookie.expires <= Date.now()) {
return null;
}
const account = await db.get('SELECT * FROM accounts WHERE LOWER(email) = ?', sessionCookie.account.trim().toLowerCase());
if (!account || account.banned) {
res.clearCookie('session');
return null;
}
const result = await db.run('UPDATE accounts SET last_ping = ? WHERE email = ?', Date.now(), account.email);
return account;
}
function updateTotalStorageUsed() {
var verifiedPath = mkDirByPathSync(config.storagePath, {isRelativeToScript: (config.storagePath.indexOf("/") === 0 ? false : true)});
if (verifiedPath !== null) {
totalStorageUsed = execSync("du -hs " + verifiedPath + " | awk -F'\t' '{print $1;}'").toString();
}
setTimeout(function () {
updateTotalStorageUsed();
}, 120000); // update the used storage each 120 seconds
}
function runAsyncWrapper(callback) { function runAsyncWrapper(callback) {
return function (req, res, next) { return function (req, res, next) {
callback(req, res, next) callback(req, res, next)
@ -207,6 +40,8 @@ function runAsyncWrapper(callback) {
} }
// CREATE OUR SERVER EXPRESS APP // CREATE OUR SERVER EXPRESS APP
const app = express(); const app = express();
app.use(cors()); app.use(cors());
@ -214,7 +49,8 @@ app.use(cookieParser(config.applicationSalt))
app.use('/favicon.ico', express.static('static/favicon.ico')); app.use('/favicon.ico', express.static('static/favicon.ico'));
app.use(config.baseDriveDownloadPathMapping, express.static(config.storagePath)); //app.use(config.baseDriveDownloadPathMapping, express.static(config.storagePath));
app.use('/.well-known', express.static('.well-known')); app.use('/.well-known', express.static('.well-known'));
// DRIVE & BOOT/CRASH LOG FILE UPLOAD HANDLING // DRIVE & BOOT/CRASH LOG FILE UPLOAD HANDLING
@ -243,7 +79,7 @@ app.put('/backend/post_upload', bodyParser.raw({
} else { } else {
logger.info("HTTP.PUT /backend/post_upload permissions checked, calling moveUploadedFile"); logger.info("HTTP.PUT /backend/post_upload permissions checked, calling moveUploadedFile");
var moveResult = moveUploadedFile(buf, directory, filename); var moveResult = controllers.storage.moveUploadedFile(buf, directory, filename);
if (moveResult === false) { if (moveResult === false) {
logger.error("HTTP.PUT /backend/post_upload moveUploadedFile failed"); logger.error("HTTP.PUT /backend/post_upload moveUploadedFile failed");
res.status(500); res.status(500);
@ -267,7 +103,7 @@ app.put('/backend/post_upload', bodyParser.raw({
} else { } else {
logger.info("HTTP.PUT /backend/post_upload permissions checked, calling moveUploadedFile"); logger.info("HTTP.PUT /backend/post_upload permissions checked, calling moveUploadedFile");
var moveResult = moveUploadedFile(buf, directory, filename); var moveResult = controllers.storage.moveUploadedFile(buf, directory, filename);
if (moveResult === false) { if (moveResult === false) {
logger.error("HTTP.PUT /backend/post_upload moveUploadedFile failed"); logger.error("HTTP.PUT /backend/post_upload moveUploadedFile failed");
res.status(500); res.status(500);
@ -298,7 +134,7 @@ app.put('/backend/post_upload', bodyParser.raw({
return res.send('Unauthorized.').status(400) return res.send('Unauthorized.').status(400)
} }
let decoded = device.public_key ? await authenticationController.validateJWT(req.headers.authorization, device.public_key) : null; let decoded = device.public_key ? await controllers.authentication.validateJWT(req.headers.authorization, device.public_key) : null;
if ((decoded == undefined || decoded.identity !== req.params.dongleId)) { if ((decoded == undefined || decoded.identity !== req.params.dongleId)) {
logger.info(`HTTP.UPLOAD_URL JWT authorization failed, token: ${auth} device: ${JSON.stringify(device)}, decoded: ${JSON.stringify(decoded)}`); logger.info(`HTTP.UPLOAD_URL JWT authorization failed, token: ${auth} device: ${JSON.stringify(device)}, decoded: ${JSON.stringify(decoded)}`);
@ -423,7 +259,7 @@ app.put('/backend/post_upload', bodyParser.raw({
return; return;
} }
var decoded = validateJWTToken(req.query.register_token, public_key); var decoded = controllers.authentication.validateJWT(req.query.register_token, public_key);
if (decoded == null || decoded.register == undefined) { if (decoded == null || decoded.register == undefined) {
logger.error("HTTP.V2.PILOTAUTH JWT token is invalid (" + JSON.stringify(decoded) + ")"); logger.error("HTTP.V2.PILOTAUTH JWT token is invalid (" + JSON.stringify(decoded) + ")");
@ -542,7 +378,7 @@ app.put('/backend/post_upload', bodyParser.raw({
app.get('/useradmin', runAsyncWrapper(async (req, res) => { app.get('/useradmin', runAsyncWrapper(async (req, res) => {
const account = await getAuthenticatedAccount(req); const account = await controllers.authentication.getAuthenticatedAccount(req, res);
if (account != null) { if (account != null) {
res.redirect('/useradmin/overview'); res.redirect('/useradmin/overview');
return; return;
@ -565,7 +401,7 @@ app.put('/backend/post_upload', bodyParser.raw({
'Accounts: ' + accounts.num + ' | ' + 'Accounts: ' + accounts.num + ' | ' +
'Devices: ' + devices.num + ' | ' + 'Devices: ' + devices.num + ' | ' +
'Drives: ' + drives.num + ' | ' + 'Drives: ' + drives.num + ' | ' +
'Storage Used: ' + (totalStorageUsed !== null ? totalStorageUsed : '--') + '<br><br>' + config.welcomeMessage + '</html>'); 'Storage Used: ' + (await controllers.storage.getTotalStorageUsed() !== null ? await controllers.storage.getTotalStorageUsed() : '--') + '<br><br>' + config.welcomeMessage + '</html>');
})), })),
@ -576,7 +412,7 @@ app.put('/backend/post_upload', bodyParser.raw({
return; return;
} }
const authAccount = await getAuthenticatedAccount(req); const authAccount = await controllers.authentication.getAuthenticatedAccount(req, res);
if (authAccount != null) { if (authAccount != null) {
res.redirect('/useradmin/overview'); res.redirect('/useradmin/overview');
return; return;
@ -657,7 +493,7 @@ app.put('/backend/post_upload', bodyParser.raw({
return; return;
} }
const account = await getAuthenticatedAccount(req); const account = await controllers.authentication.getAuthenticatedAccount(req, res);
if (account != null) { if (account != null) {
res.redirect('/useradmin/overview'); res.redirect('/useradmin/overview');
return; return;
@ -678,7 +514,7 @@ app.put('/backend/post_upload', bodyParser.raw({
app.get('/useradmin/overview', runAsyncWrapper(async (req, res) => { app.get('/useradmin/overview', runAsyncWrapper(async (req, res) => {
const account = await getAuthenticatedAccount(req); const account = await controllers.authentication.getAuthenticatedAccount(req, res);
if (account == null) { if (account == null) {
res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session')); res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session'));
return; return;
@ -691,14 +527,14 @@ app.put('/backend/post_upload', bodyParser.raw({
`<br><br><h3>Account Overview</h3> `<br><br><h3>Account Overview</h3>
<b>Account:</b> #` + account.id + `<br> <b>Account:</b> #` + account.id + `<br>
<b>Email:</b> ` + account.email + `<br> <b>Email:</b> ` + account.email + `<br>
<b>Created:</b> ` + formatDate(account.created) + `<br><br> <b>Created:</b> ` + controllers.helpers.formatDate(account.created) + `<br><br>
<b>Devices:</b><br> <b>Devices:</b><br>
<table border=1 cellpadding=2 cellspacing=2> <table border=1 cellpadding=2 cellspacing=2>
<tr><th>dongle_id</th><th>device_type</th><th>created</th><th>last_ping</th><th>storage_used</th></tr> <tr><th>dongle_id</th><th>device_type</th><th>created</th><th>last_ping</th><th>storage_used</th></tr>
`; `;
for (var i in devices) { for (var i in devices) {
response += '<tr><td><a href="/useradmin/device/' + devices[i].dongle_id + '">' + devices[i].dongle_id + '</a></td><td>' + devices[i].device_type + '</td><td>' + formatDate(devices[i].created) + '</td><td>' + formatDate(devices[i].last_ping) + '</td><td>' + devices[i].storage_used + ' MB</td></tr>'; response += '<tr><td><a href="/useradmin/device/' + devices[i].dongle_id + '">' + devices[i].dongle_id + '</a></td><td>' + devices[i].device_type + '</td><td>' + controllers.helpers.formatDate(devices[i].created) + '</td><td>' + controllers.helpers.formatDate(devices[i].last_ping) + '</td><td>' + devices[i].storage_used + ' MB</td></tr>';
} }
response += `</table> response += `</table>
<br> <br>
@ -720,7 +556,7 @@ app.put('/backend/post_upload', bodyParser.raw({
app.get('/useradmin/unpair_device/:dongleId', runAsyncWrapper(async (req, res) => { app.get('/useradmin/unpair_device/:dongleId', runAsyncWrapper(async (req, res) => {
const account = await getAuthenticatedAccount(req); const account = await controllers.authentication.getAuthenticatedAccount(req, res);
if (account == null) { if (account == null) {
res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session')); res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session'));
return; return;
@ -745,7 +581,7 @@ app.put('/backend/post_upload', bodyParser.raw({
app.post('/useradmin/pair_device', bodyParser.urlencoded({extended: true}), runAsyncWrapper(async (req, res) => { app.post('/useradmin/pair_device', bodyParser.urlencoded({extended: true}), runAsyncWrapper(async (req, res) => {
const account = await getAuthenticatedAccount(req); const account = await controllers.authentication.getAuthenticatedAccount(req, res);
if (account == null) { if (account == null) {
res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session')); res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session'));
return; return;
@ -757,7 +593,7 @@ app.put('/backend/post_upload', bodyParser.raw({
if (device == null) { if (device == null) {
res.redirect('/useradmin/overview?linkstatus=' + encodeURIComponent('Device not registered on Server')); res.redirect('/useradmin/overview?linkstatus=' + encodeURIComponent('Device not registered on Server'));
} }
var decoded = validateJWTToken(qrCodeParts[2], device.public_key); var decoded = controllers.authentication.validateJWT(qrCodeParts[2], device.public_key);
if (decoded == null || decoded.pair == undefined) { if (decoded == null || decoded.pair == undefined) {
res.redirect('/useradmin/overview?linkstatus=' + encodeURIComponent('Device QR Token is invalid or has expired')); res.redirect('/useradmin/overview?linkstatus=' + encodeURIComponent('Device QR Token is invalid or has expired'));
} }
@ -776,7 +612,7 @@ app.put('/backend/post_upload', bodyParser.raw({
app.get('/useradmin/device/:dongleId', runAsyncWrapper(async (req, res) => { app.get('/useradmin/device/:dongleId', runAsyncWrapper(async (req, res) => {
const account = await getAuthenticatedAccount(req); const account = await controllers.authentication.getAuthenticatedAccount(req, res);
if (account == null) { if (account == null) {
res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session')); res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session'));
return; return;
@ -836,8 +672,8 @@ app.put('/backend/post_upload', bodyParser.raw({
<b>Type:</b> ` + device.device_type + `<br> <b>Type:</b> ` + device.device_type + `<br>
<b>Serial:</b> ` + device.serial + `<br> <b>Serial:</b> ` + device.serial + `<br>
<b>IMEI:</b> ` + device.imei + `<br> <b>IMEI:</b> ` + device.imei + `<br>
<b>Registered:</b> ` + formatDate(device.created) + `<br> <b>Registered:</b> ` + controllers.helpers.formatDate(device.created) + `<br>
<b>Last Ping:</b> ` + formatDate(device.last_ping) + `<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> <b>Public Key:</b><br><span style="font-size: 0.8em">` + device.public_key.replace(/\r?\n|\r/g, "<br>") + `</span>
<br> <br>
<b>Stored Drives:</b> ` + drives.length + `<br> <b>Stored Drives:</b> ` + drives.length + `<br>
@ -850,7 +686,7 @@ app.put('/backend/post_upload', bodyParser.raw({
<tr><th>date</th><th>file</th><th>size</th></tr> <tr><th>date</th><th>file</th><th>size</th></tr>
`; `;
for (var i = 0; i < Math.min(5, bootlogFiles.length); i++) { for (var i = 0; i < Math.min(5, bootlogFiles.length); i++) {
response += `<tr><td>` + formatDate(bootlogFiles[i].date) + `</td><td><a href="` + config.baseDriveDownloadUrl + device.dongle_id + "/" + dongleIdHash + "/boot/" + bootlogFiles[i].name + `" target=_blank>` + bootlogFiles[i].name + `</a></td><td>` + bootlogFiles[i].size + `</td></tr>`; response += `<tr><td>` + controllers.helpers.formatDate(bootlogFiles[i].date) + `</td><td><a href="` + config.baseDriveDownloadUrl + 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 += `</table><br><br>`;
@ -859,7 +695,7 @@ app.put('/backend/post_upload', bodyParser.raw({
<tr><th>date</th><th>file</th><th>size</th></tr> <tr><th>date</th><th>file</th><th>size</th></tr>
`; `;
for (var i = 0; i < Math.min(5, crashlogFiles.length); i++) { for (var i = 0; i < Math.min(5, crashlogFiles.length); i++) {
response += `<tr><td>` + formatDate(crashlogFiles[i].date) + `</td><td><a href="` + config.baseDriveDownloadUrl + device.dongle_id + "/" + dongleIdHash + "/crash/" + crashlogFiles[i].name + `" target=_blank>` + crashlogFiles[i].name + `</a></td><td>` + crashlogFiles[i].size + `</td></tr>`; response += `<tr><td>` + controllers.helpers.formatDate(crashlogFiles[i].date) + `</td><td><a href="` + config.baseDriveDownloadUrl + 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 += `</table><br><br>`;
@ -870,7 +706,7 @@ app.put('/backend/post_upload', bodyParser.raw({
`; `;
for (var i in drives) { 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>' + 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>' + 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 += '<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> response += `</table>
<br> <br>
@ -887,7 +723,7 @@ app.put('/backend/post_upload', bodyParser.raw({
app.get('/useradmin/drive/:dongleId/:driveIdentifier/:action', runAsyncWrapper(async (req, res) => { app.get('/useradmin/drive/:dongleId/:driveIdentifier/:action', runAsyncWrapper(async (req, res) => {
const account = await getAuthenticatedAccount(req); const account = await controllers.authentication.getAuthenticatedAccount(req, res);
if (account == null) { if (account == null) {
res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session')); res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session'));
return; return;
@ -927,7 +763,8 @@ app.put('/backend/post_upload', bodyParser.raw({
app.get('/useradmin/drive/:dongleId/:driveIdentifier', runAsyncWrapper(async (req, res) => { app.get('/useradmin/drive/:dongleId/:driveIdentifier', runAsyncWrapper(async (req, res) => {
const account = await getAuthenticatedAccount(req); const account = await controllers.authentication.getAuthenticatedAccount(req, res);
if (account == null) { if (account == null) {
res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session')); res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session'));
return; return;
@ -966,11 +803,11 @@ app.put('/backend/post_upload', bodyParser.raw({
` `
<a href="/useradmin/device/` + device.dongle_id + `">< < < Back To Device ` + device.dongle_id + `</a> <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> <br><br><h3>Drive ` + drive.identifier + ` on ` + drive.dongle_id + `</h3>
<b>Drive Date:</b> ` + formatDate(drive.drive_date) + `<br> <b>Drive Date:</b> ` + controllers.helpers.formatDate(drive.drive_date) + `<br>
<b>Upload Date:</b> ` + formatDate(drive.created) + `<br> <b>Upload Date:</b> ` + controllers.helpers.formatDate(drive.created) + `<br>
<b>Num Segments:</b> ` + (drive.max_segment + 1) + `<br> <b>Num Segments:</b> ` + (drive.max_segment + 1) + `<br>
<b>Storage:</b> ` + Math.round(drive.filesize / 1024) + ` MiB<br> <b>Storage:</b> ` + Math.round(drive.filesize / 1024) + ` MiB<br>
<b>Duration:</b> ` + formatDuration(drive.duration) + `<br> <b>Duration:</b> ` + controllers.helpers.formatDuration(drive.duration) + `<br>
<b>Distance:</b> ` + Math.round(drive.distance_meters / 1000) + ` km<br> <b>Distance:</b> ` + Math.round(drive.distance_meters / 1000) + ` km<br>
<b>Is Preserved:</b> ` + drive.is_preserved + `<br> <b>Is Preserved:</b> ` + drive.is_preserved + `<br>
<b>Upload Complete:</b> ` + drive.upload_complete + `<br> <b>Upload Complete:</b> ` + drive.upload_complete + `<br>
@ -1043,23 +880,23 @@ app.put('/backend/post_upload', bodyParser.raw({
})), })),
app.get('/', runAsyncWrapper(async (req, res) => { app.get('/', async (req, res) => {
res.status(404); res.status(404);
var response = '<html style="font-family: monospace"><h2>404 Not found</h2>' + var response = '<html style="font-family: monospace"><h2>404 Not found</h2>' +
'Are you looking for the <a href="/useradmin">useradmin dashboard</a>?'; 'Are you looking for the <a href="/useradmin">useradmin dashboard</a>?';
res.send(response); res.send(response);
})), }),
app.get('*', runAsyncWrapper(async (req, res) => { app.get('*', runAsyncWrapper(async (req, res) => {
logger.error("HTTP.GET unhandled request: " + simpleStringify(req) + ", " + simpleStringify(res) + "") logger.error("HTTP.GET unhandled request: " + controllers.helpers.simpleStringify(req) + ", " + controllers.helpers.simpleStringify(res) + "")
res.status(400); res.status(400);
res.send('Not Implemented'); res.send('Not Implemented');
})), })),
app.post('*', runAsyncWrapper(async (req, res) => { app.post('*', runAsyncWrapper(async (req, res) => {
logger.error("HTTP.POST unhandled request: " + simpleStringify(req) + ", " + simpleStringify(res) + "") logger.error("HTTP.POST unhandled request: " + controllers.helpers.simpleStringify(req) + ", " + controllers.helpers.simpleStringify(res) + "")
res.status(400); res.status(400);
res.send('Not Implemented'); res.send('Not Implemented');
})); }));
@ -1076,9 +913,12 @@ lockfile.lock('retropilot_server.lock', {realpath: false, stale: 30000, update:
db = _models.db; db = _models.db;
models = _models.models; models = _models.models;
const _controllers = await controllers(models, logger);
controllers = _controllers;
initializeStorage();
updateTotalStorageUsed(); controllers.storage.initializeStorage();
await controllers.storage.updateTotalStorageUsed();
var privateKey = fs.readFileSync(config.sslKey, 'utf8'); var privateKey = fs.readFileSync(config.sslKey, 'utf8');
var certificate = fs.readFileSync(config.sslCrt, 'utf8'); var certificate = fs.readFileSync(config.sslCrt, 'utf8');