Added tests

pull/4/head
Adam Black 2021-05-22 17:33:28 +01:00
parent 2f3c094ca3
commit 0d49d5ce6c
9 changed files with 187 additions and 12 deletions

3
.gitignore vendored
View File

@ -8,4 +8,5 @@ config.js
.vscode
.idea
database.sqlite
config.js
config.js
test/.devKeys

View File

@ -8,13 +8,13 @@ async function validateJWT(token, key) {
try {
return jwt.verify(token.replace("JWT ", ""), key, {algorithms: ['RS256']});
} catch (exception) {
// TODO add logger to authentication controller
//logger.error(exception);
//logger.warn(exception)
}
return null;
}
async function getAuthenticatedAccount(req, res) {
const sessionCookie = (req.signedCookies !== undefined ? req.signedCookies.session : null);
if (!sessionCookie || sessionCookie.expires <= Date.now()) { return null; }

View File

@ -28,7 +28,7 @@ function mkDirByPathSync(targetDir, {isRelativeToScript = false} = {}) {
try {
fs.mkdirSync(curDir);
} catch (err) {
console.debug(err);
//console.debug(err);
if (err.code === 'EEXIST') { // curDir already exists!
return curDir;
}

View File

@ -4,13 +4,15 @@
"description": "replacement for comma.ai backend and useradmin dashboard. can be combined with a modified cabana instance.",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "mocha",
"start": "node server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@commaai/log_reader": "^0.8.0",
"chai": "^4.3.4",
"chai-http": "^4.3.0",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"crypto": "^1.0.1",
@ -25,10 +27,12 @@
"htmlspecialchars": "^1.0.5",
"jsonwebtoken": "^8.5.1",
"log4js": "^6.3.0",
"mocha": "^8.4.0",
"multer": "^1.4.2",
"proper-lockfile": "^4.1.2",
"sendmail": "^1.6.1",
"sqlite": "^4.0.22",
"sqlite3": "^5.0.2"
"sqlite3": "^5.0.2",
"supertest": "^6.1.3"
}
}

View File

@ -212,20 +212,20 @@ router.get('/v1.3/:dongleId/upload_url/', runAsyncWrapper(async (req, res) => {
// DEVICE REGISTRATION OR RE-ACTIVATION
router.post('/v2/pilotauth/', bodyParser.urlencoded({extended: true}), runAsyncWrapper(async (req, res) => {
router.post('/v2/pilotauth/', bodyParser.urlencoded({extended: true}), async (req, res) => {
var imei1 = req.query.imei;
var serial = req.query.serial;
var public_key = req.query.public_key;
var register_token = req.query.register_token;
if (imei1 == null || imei1.length < 5 || serial == null || serial.length < 5 || public_key == null || public_key.length < 5 || register_token == null || register_token.length < 5) {
logger.error("HTTP.V2.PILOTAUTH a required parameter is missing or empty");
logger.error(`HTTP.V2.PILOTAUTH a required parameter is missing or empty ${JSON.stringify(req.query)}`);
res.status(400);
res.send('Malformed Request.');
return;
}
var decoded = await controllers.authentication.validateJWT(req.query.register_token, public_key);
var decoded = controllers.authentication.validateJWT(req.query.register_token, public_key);
if (decoded == null || decoded.register == undefined) {
logger.error("HTTP.V2.PILOTAUTH JWT token is invalid (" + JSON.stringify(decoded) + ")");
@ -264,7 +264,7 @@ router.post('/v2/pilotauth/', bodyParser.urlencoded({extended: true}), runAsyncW
res.json({dongle_id: device.dongle_id});
}
}))
})
// RETRIEVES DATASET FOR OUR MODIFIED CABANA - THIS RESPONSE IS USED TO FAKE A DEMO ROUTE

View File

@ -71,7 +71,7 @@ const web = async () => {
app.get('/', async (req, res) => {
res.status(404);
res.status(200);
var response = '<html style="font-family: monospace"><h2>404 Not found</h2>' +
'Are you looking for the <a href="/useradmin">useradmin dashboard</a>?';
res.send(response);
@ -113,15 +113,22 @@ lockfile.lock('retropilot_server.lock', {realpath: false, stale: 30000, update:
httpServer.listen(config.httpPort, config.httpInterface, () => {
logger.info(`Retropilot Server listening at http://` + config.httpInterface + `:` + config.httpPort)
});
httpsServer.listen(config.httpsPort, config.httpsInterface, () => {
logger.info(`Retropilot Server listening at https://` + config.httpsInterface + `:` + config.httpsPort)
});
})();
}).catch((e) => {
console.error(e)
process.exit();
});
});
module.exports = app;

View File

@ -0,0 +1,68 @@
const jwt = require('jsonwebtoken')
const crypto = require('crypto')
const devicePrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIEowIBAAKCAQEAwhH9PqBd/R/QPvcf1Gom5Vp+zYb1+DLjiFMC7a1lNvV8MUqK\n" +
"cKVzboq/TjkKxkPUxRRjhgt4TmxhxJ6AHAOvONMvXtS1gm8EuiJbzSUDbgr6Y3PV\n" +
"/jHQEb8tWcmM5UZ4TV+VPBmY4w9UWJbCiJW1Udn253bqil3Mv2D4WjpxlQDNGmpc\n" +
"Aq0b7N20WoMt/DB3Z/AnixYKLGDLmHIe8Umq9btFPv/ulVexuzeoJoYjMZLDv4Sf\n" +
"SE4ONmDqAacjTtBPaEFedlerKVMN0PI2IzDeGvEqif98lEEVh4/3X1UP21A2Cgiy\n" +
"nHQn92HRTR8Xkc5EYDOYEpwi97G6g+qaFtOacQIDAQABAoIBAQCFtuVZGB+KPzg5\n" +
"mgXZUkZ4cnC55YpmN5HkJOX4oycAxgWK5MQcNzMgcAK9v7m3v5bDL3gfLJn41t5K\n" +
"HbdBFhzNt1yFJ2Pked+06+V6pE0HrhK1IWPJH8Mv5xw1KBSnCHXtQbVOUoivsak4\n" +
"3K8ucpAa1GY1Nw8ExPpExmh3qpsFwPFFq3ZkDdGPaxQdOGzrNwC9Z6R+XNEdh+Ub\n" +
"N6On3McK+AmI4deW5GWdL54vHsC0MfWhdWMklPcw98o9ZVQ9V6Bzf8tRIBVB4qRB\n" +
"pyQaRRPmkpX0s5mNCAZljLxyO1oD0yfugSZOnbbmo47BmYQiNd1WfxVXwMqR0dNE\n" +
"js4HCW7BAoGBAPrMOFlsYpP3DLOXSHQwt+ZGcnh54NJcz97KJKQRE93H4pVUcnsn\n" +
"VhgNTq1bYkw8CdFpPJgQgeBeX4djDyfFYEDYQnmAi0hcIFuwEV8U8LYsp7EyLWVA\n" +
"pR+vtj5mIkdZ/j4jsYAMrQbwbweptxWiOeGqGr7vGzxOXBLDS9W4FZNZAoGBAMYY\n" +
"iU58TTWPdUllkvxPToXv6+tjognnbatYxzrwiRRKlAuc6JPZP3qADhJ7SZNxaB88\n" +
"aun+GZEwOITCZHkKl5oSyshb0mp9SIWlG7Nkn08/8464eAJbk7tNwtdOAHdbzKS0\n" +
"LXpNlQ9ZGy36vE6KtGctPfGY5H4r8uIX+SlmJNTZAoGAMDDnrv8tngL9tNCgAnuO\n" +
"CriErHO26JUe+E9dZQ1HBPmwp0MX0GRJncuIz7TcmYt703pmQ04Ats1Li+dT9S9v\n" +
"BGbJtzElEl1pdlTJsbyDWG4SNvFOWcNnN0R7P1g+w/kd6nDPXayR3uB6ZT2OSaDn\n" +
"gF5AT2oAkMD53j0aqFF8C9kCgYB2pTN3wpMrxSRmNWP3ojhRmAUhEqd2bxoMSjvp\n" +
"XS98674Hxo62HqQaZqAHCbhjisTmEHWod/wwLUVsnlE2/dUW/rJdlkFMboUFJoKU\n" +
"y2tvN8pUbL/UCa1NvaE4+wrkciL7cr7aRaVFcAULYOVv1Tt/oGU9Umln+EKcj+c3\n" +
"mGnu4QKBgBq7yEEj99q4BoK0DhS9t/Y/akN60rPrkOetxgbpSgvLifictFg9Og0p\n" +
"empY8kk3cQACUIKoLkbrx7mOrC/MUFWZ7H4/65QxvJWsyVvdgD3JCuX6gntgxFLR\n" +
"gELymgXiYG6TBxfH6xcFtNrFe6DeTv8YXrKRR50Kg8kjFpvmm5s9\n" +
"-----END RSA PRIVATE KEY-----\n"
const devicePubKey = "-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhH9PqBd/R/QPvcf1Gom\n" +
"5Vp+zYb1+DLjiFMC7a1lNvV8MUqKcKVzboq/TjkKxkPUxRRjhgt4TmxhxJ6AHAOv\n" +
"ONMvXtS1gm8EuiJbzSUDbgr6Y3PV/jHQEb8tWcmM5UZ4TV+VPBmY4w9UWJbCiJW1\n" +
"Udn253bqil3Mv2D4WjpxlQDNGmpcAq0b7N20WoMt/DB3Z/AnixYKLGDLmHIe8Umq\n" +
"9btFPv/ulVexuzeoJoYjMZLDv4SfSE4ONmDqAacjTtBPaEFedlerKVMN0PI2IzDe\n" +
"GvEqif98lEEVh4/3X1UP21A2CgiynHQn92HRTR8Xkc5EYDOYEpwi97G6g+qaFtOa\n" +
"cQIDAQAB\n" +
"-----END PUBLIC KEY-----\n";
const rougePublicKey = "-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsD2nKi9wqmib8kEAuyz7\n" +
"6N2OiL5ZpCkgai02V0G/cHUdkQXGxw0gWnYaDmY4uhgQM4W1jpHkwLW3PXavyDYE\n" +
"mCeORRS2ChYqPpJJkSQ+MO1bkR1blhixF6O39gIH5+0ZuiqnDYJIcn+DcYJrTzCz\n" +
"HXyPvRztFuuKp1unJRi8cSL6ljq5LMjZLsuY9Eb7JmYRsXB/xHDpXysyqq1VGD5c\n" +
"QSCJMFzykQUe4PR3AhP05SunJMA+QNhRxKUVzXyo3bpAXsRhhRr/E/jl48E22edl\n" +
"cgXar6R9CxyHY31jdJnd9pp2KPUnNgnTBdF2w3pdN9frS9QHCDLDvLbCgd2bibSj\n" +
"CwIDAQAB\n" +
"-----END PUBLIC KEY-----"
function makeJWT() {
const token = jwt.sign({ register: true }, devicePrivateKey, { algorithm: 'RS256'});
return `JWT ${token}`
}
function getImei() {
return parseInt(Math.random().toFixed(15).replace("0.",""))
}
function getSerial() {
return crypto.randomBytes(10).toString('hex');
}
module.exports = {
makeJWT, getImei, getSerial, rougePublicKey, devicePubKey, devicePrivateKey
}

View File

@ -0,0 +1,74 @@
const request = require('supertest');
const dummyGenerator = require('./../dummyGenerator');
let app;
module.exports = (server) => {
app = server;
describe('/v2/pilotauth/ - Testing device registration', function() {
it('Returns dongle ID on valid registration', function(done) {
request(server)
.post('/v2/pilotauth/')
.query({
imei: dummyGenerator.getImei(),
serial: dummyGenerator.getSerial(),
public_key: dummyGenerator.devicePubKey,
register_token: dummyGenerator.makeJWT()
})
.set('Accept', 'application/x-www-form-urlencoded')
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
if (!res.body.dongle_id) throw new Error("API Failed to return dongle_id on status 200")
})
.end(done)
});
it('Returns 400 when incorrect public key given', function(done) {
request(server)
.post('/v2/pilotauth/')
.query({
imei: dummyGenerator.getImei(),
serial: dummyGenerator.getSerial(),
public_key: dummyGenerator.rougePublicKey,
register_token: dummyGenerator.makeJWT()
})
.set('Accept', 'application/x-www-form-urlencoded')
.expect('Content-Type', /text/)
.expect(400)
.end(done)
});
it('Returns 400 when incorrect public key given', function(done) {
request(server)
.post('/v2/pilotauth/')
.query({
imei: dummyGenerator.getImei(),
serial: dummyGenerator.getSerial(),
public_key: dummyGenerator.rougePublicKey,
register_token: ""
})
.set('Accept', 'application/x-www-form-urlencoded')
.expect('Content-Type', /text/)
.expect(400)
.end(done)
});
it('Returns 400 when missing query', function(done) {
request(server)
.post('/v2/pilotauth/')
.set('Accept', 'application/x-www-form-urlencoded')
.expect('Content-Type', /text/)
.expect(400)
.end(done)
});
});
}

21
test/test.js 100644
View File

@ -0,0 +1,21 @@
var server = require('./../server')
var request = require('supertest');
// TODO better way to only run tests once server is up
describe('loading express', function () {
it('responds to /', function testSlash(done) {
request(server)
.get('/')
.expect(200, done);
});
it('404 everything else', function testPath(done) {
request(server)
.get('/foo/bar')
.expect(404, done);
});
});
require('./routes/api.test')(server);