Reviewed-on: #12
This commit is contained in:
Fred Boniface 2023-05-06 21:54:49 +01:00
parent fe9eeae7db
commit 7da713526f
46 changed files with 3166 additions and 2622 deletions

View File

@ -8,4 +8,6 @@ db-manager
run.sh
LICENSE
*.md
static
.eslintrc.js
.vscode
.test-tools

31
.eslintrc.js Normal file
View File

@ -0,0 +1,31 @@
module.exports = {
'env': {
'browser': true,
'commonjs': true,
'es2021': true
},
'extends': 'eslint:recommended',
'overrides': [
],
'parserOptions': {
'ecmaVersion': 'latest'
},
'rules': {
'indent': [
'error',
2
],
'linebreak-style': [
'error',
'unix'
],
'quotes': [
'error',
'single'
],
'semi': [
'error',
'always'
]
}
};

View File

@ -3,5 +3,12 @@
"git.alwaysSignOff": true,
"git.enableCommitSigning": false,
"git.fetchOnPull": true,
"git.pullBeforeCheckout": true
"git.pullBeforeCheckout": true,
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint",
"editor.formatOnPaste": false, // required
"editor.formatOnType": false, // required
"editor.formatOnSave": true, // optional
"editor.formatOnSaveMode": "file", // required to format on save
"files.autoSave": "onFocusChange", // optional but recommended
"vs-code-prettier-eslint.prettierLast": "false" // set as "true" to run 'prettier' last not first
}

View File

@ -1,3 +1,4 @@
# What to do next:
* Rewrite sanitizing functions to remove external dependancy.
* Change /api/v1/auth endpoints to /api/v1/register endpoints - auth is done in middleware

57
app.js
View File

@ -4,12 +4,19 @@
// licensed separately, each folder contains a license file where a
// different license applies.
console.log(`Initialising OwlBoard`)
/* global process */
console.log('Initialising OwlBoard');
const mode = process.env.NODE_ENV || 'development';
// External Requires
const express = require('express');
const app = express();
const compression = require('compression')
// Middleware
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const authenticate= require('./src/middlewares/auth.middlewares');
// Internal Requires
const log = require('./src/utils/log.utils'); // Log Helper
@ -18,15 +25,25 @@ const listRtr = require('./src/routes/list.routes'); // /list endpoints
const ldbRtr = require('./src/routes/ldb.routes'); // /ldb endpoints
const kubeRtr = require('./src/routes/kube.routes'); // /kube endpoints
const findRtr = require('./src/routes/find.routes'); // /find endpoints
const issueRtr = require('./src/routes/issue.routes') // /issue endpoints
const issueRtr = require('./src/routes/issue.routes'); // /issue endpoints
const statRtr = require('./src/routes/stats.routes'); // /stat endpoints
const regRtr = require('./src/routes/registration.routes'); // /registration endpoints
const pisRtr = require('./src/routes/pis.routes'); // /pis endpoints
// Set Server Configurations
const srvListen = process.env.OWL_SRV_LISTEN || "0.0.0.0"
const srvPort = process.env.OWL_SRV_PORT || 8460
const srvListen = process.env.OWL_SRV_LISTEN || '0.0.0.0';
const srvPort = process.env.OWL_SRV_PORT || 8460;
const limiter = rateLimit({
windowMs: 15 * (60 * 1000), // 15 minutes
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: true, // Disable the `X-RateLimit-*` headers
});
// Print version number:
log.out(`app: Starting OwlBoard - Backend Version: ${version.app} - API versions: ${version.api}`, "init");
log.out(`app: Starting OwlBoard in ${mode} mode`, 'init');
log.out(`app: Starting OwlBoard - Backend Version: ${version.app} - API versions: ${version.api}`, 'init');
// Test for required vars:
// const varTest = require('./src/utils/varTest.utils');
@ -45,25 +62,37 @@ app.use((err, req, res, next) => {
return;
});
// Express Submodules:
// Global Middleware:
app.use(express.json()); //JSON Parsing for POST Requests
//STATIC CONTENT NO LONGER SERVED FROM NODE
app.use(compression()) // Compress API Data if supported by client
app.use(compression()); // Compress API Data if supported by client
app.use(limiter);
// Express Routes
// Unauthenticated Routes
app.use('/api/v1/list', listRtr);
app.use('/api/v1/ldb', ldbRtr);
app.use('/api/v1/kube', kubeRtr);
app.use('/api/v1/find', findRtr);
app.use('/api/v1/issue', issueRtr);
app.use('/api/v1/stats', statRtr)
app.use('/api/v1/stats', statRtr);
app.use('/api/v1/register', regRtr);
// Authented Routes
app.use('/api/v1/ldbs', authenticate, (req, res) => res.status(501).json({status: 'Not Implemented', message: 'This feature is not yet implemented due to upstream issues'}));
app.use('/api/v1/pis', authenticate, pisRtr);
app.use('/api/v1/auth/test', authenticate, (req, res) => res.status(200).json({status: 'ok', message: 'Authentication successful'})); // Returns 401 if auth failed, 200 if successful.
// Number of proxies:
app.set('trust proxy', 4);
mode === 'development'
? app.get('/api/v1/ip', (req, res) => res.send(req.ip))
: null;
// Start Express
app.listen(srvPort, srvListen, (error) =>{
if(!error) {
log.out(`app.listen: Listening on http://${srvListen}:${srvPort}`, "init");
log.out("app.listen: State - alive", "init")
log.out(`app.listen: Listening on http://${srvListen}:${srvPort}`, 'init');
log.out('app.listen: State - alive', 'init');
} else {
log.out(`app.listen: Error occurred, server can't start ${error}`, "err");
log.out(`app.listen: Error occurred, server can't start ${error}`, 'err');
}
});

View File

@ -0,0 +1,70 @@
<html lang="en">
<head>
<title>OwlBoard - Register</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html {
background-color:#404c55;
background-image:radial-gradient(#2b343c,#404c55);
text-align: center;
width: 100%;
}
a {
color:azure;
}
table {
width: 100%;
color: azure;
font-family: sans-serif;
text-align: center;
}
#title {
height: 100px;
padding-top: 0px;
margin-top: 0px;
}
h1 {
color: #00b7b7;
}
#button {
color: azure;
font-size: larger;
background-color: #007979;
padding: 8px;
padding-left: 12px;
padding-right: 12px;
text-decoration: none;
border-radius: 14px;
}
</style>
</head>
<body>
<br><br>
<table>
<tr>
<td>
<img src="https://owlboard.info/images/logo/wide_logo.svg" id="title">
</td>
</tr>
<tr>
<td>
<h1>Register for OwlBoard</h1>
<br>
<p>Use the link below to register for OwlBoard (Staff Version)</p>
<br>
<a href="https://owlboard.info/auth.html?key=>>ACCESSCODE<<" id="button">Register</a>
<br><br><br>
<p>Alternatively copy and paste the link:<br>https://owlboard.info/auth.html?key=>>ACCESSCODE<<</p>
<p>This registration is for one device only, you can register again using the
same email address for other devices and access OwlBoard from elsewhere.
</p>
<p>If you did not request to sign up to OwlBoard (Staff Version), you can
safely ignore this email. Your email address has not been stored by us.
</p>
<p>The registration link will expire after 30 minutes.</p>
</td>
</tr>
</table>
<br>
</body>
</html>

View File

@ -0,0 +1,10 @@
Complete your OwlBoard (Staff) Registration using the link below.
https://owlboard.info/auth.html?key=>>ACCESSCODE<<
Alternatively you can copy and paste the above link.
If you did not request to register to OwlBoard then you can safely ignore this email.
Your email address has not been stored by us and will not be required unless you wish to register again.
The link will expire after 30 minutes.

4332
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,22 +3,30 @@
"axios": "^1.2.1",
"compression": "^1.7.4",
"express": "^4.18.2",
"express-rate-limit": "^6.7.0",
"html-minifier": "^4.0.0",
"juice": "^9.0.0",
"ldbs-json": "^1.2.1",
"mongodb": "^4.13.0",
"string-sanitizer-fix": "^2.0.1"
"nodemailer": "^6.9.1"
},
"name": "owlboard",
"description": "OwlBoard is an API and PWA for live rail departure board in the UK.",
"version": "0.0.1",
"version": "1.2.0-dev",
"main": "express.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node app.js"
"start": "node app.js",
"run": "node app.js"
},
"repository": {
"type": "git",
"url": "https://git.fjla.uk/fred.boniface/owlboard.git"
"url": "https://git.fjla.uk/owlboard/backend.git"
},
"author": "Fred Boniface",
"license": "GPL-3.0-or-later"
"license": "GPL-3.0-or-later",
"devDependencies": {
"eslint": "^8.39.0",
"prettier": "^2.8.8"
}
}

View File

@ -1,10 +1,36 @@
module.exports = valid
const valid = [
"owlboard.info",
"fjla.uk",
"gwr.com",
"swrailway.com",
"firstrail.com",
"networkrail.co.uk"
]
'owlboard.info',
'avantiwestcoast.co.uk',
'btp.police.uk',
'c2crail.net',
'chilternrailways.co.uk',
'crosscountrytrains.co.uk',
'eastmidlandsrailway.co.uk',
'abellio.co.uk',
'tfl.gov.uk',
'mtrel.co.uk',
'eurostar.com',
'eurotunnel.com',
'ffwhr.com',
'gwr.com',
'hitachirail-eu.com',
'greateranglia.co.uk',
'heathrow.com',
'swrailway.com',
'lsltoc.co.uk',
'lner.co.uk',
'arrivarl.co.uk',
'tube.tfl.gov.uk',
'lumo.co.uk',
'merseyrail.org',
'nrcommcentre.com',
'networkrail.co.uk',
'northernrailway.co.uk',
'scotrail.co.uk',
'southeasternrailway.co.uk',
'tpeexpress.co.uk',
'tfwrail.wales',
'wmtrains.co.uk',
];
module.exports = valid;

View File

@ -0,0 +1,12 @@
const statusCodes = {
700: 'no authentication attempt',
701: 'invalid credentials',
702: 'domain not whitelisted',
703: 'registration request not found, maybe expired',
800: 'location code not found',
801: 'unable to fetch location data',
900: 'invalid request format',
950: 'upstream server error',
};
module.exports = statusCodes;

View File

@ -1,6 +1,6 @@
const version = {
api: ["/api/v1/",],
app: "1.1.5"
api: ['/api/v1/',],
app: '2.0.0'
};
module.exports = version;

View File

@ -2,50 +2,50 @@ const find = require('../services/find.services');
async function findName(req, res, next){
try {
var id = req.params.id
res.json(await find.name(id))
var id = req.params.id;
res.json(await find.name(id));
} catch (err) {
console.error(`Unknown Error`, err.message);
console.error('Unknown Error', err.message);
next(err);
}
}
async function findCrs(req, res, next){
try {
var id = req.params.id
res.json(await find.crs(id))
var id = req.params.id;
res.json(await find.crs(id));
} catch (err) {
console.error(`Unknown Error`, err.message);
console.error('Unknown Error', err.message);
next(err);
}
}
async function findNlc(req, res, next){
try {
var id = req.params.id
res.json(await find.nlc(id))
var id = req.params.id;
res.json(await find.nlc(id));
} catch (err) {
console.error(`Unknown Error`, err.message);
console.error('Unknown Error', err.message);
next(err);
}
}
async function findTiploc(req, res, next){
try {
var id = req.params.id
res.json(await find.tiploc(id))
var id = req.params.id;
res.json(await find.tiploc(id));
} catch (err) {
console.error(`Unknown Error`, err.message);
console.error('Unknown Error', err.message);
next(err);
}
}
async function findStanox(req, res, next){
try {
var id = req.params.id
res.json(await find.stanox(id))
var id = req.params.id;
res.json(await find.stanox(id));
} catch (err) {
console.error(`Unknown Error`, err.message);
console.error('Unknown Error', err.message);
next(err);
}
}
@ -55,4 +55,4 @@ module.exports = {
findNlc,
findTiploc,
findStanox
}
};

View File

@ -2,13 +2,13 @@ const issue = require('../services/issue.services');
async function post(req, res, next){
try {
res.json(await issue.processor(req.body))
res.json(await issue.processor(req.body));
} catch (err) {
console.error(`Controller Error`, err.message);
console.error('Controller Error', err.message);
next(err);
}
}
module.exports = {
post
}
};

View File

@ -2,27 +2,28 @@ const kube = require('../services/kube.services');
async function getAlive(req, res, next){
try {
var state = kube.getAlive()
res.status((await state).code).send((await state).state)
var state = kube.getAlive();
res.status((await state).code).send((await state).state);
} catch (err) {
res.status("503").send({state: "error"})
res.status('503').send({state: 'error'});
}
}
async function getReady(req, res, next){
try {
res.json(await kube.getReady(req.body))
res.json(await kube.getReady(req.body));
} catch (err) {
console.error(`Unknown Error`, err.message);
console.error('Unknown Error', err.message);
next(err);
}
}
async function getTime(req, res, next){
try {
res.json(await kube.getTime(req.body))
res.json(await kube.getTime(req.body));
} catch (err) {
console.error(`Unknown Error`, err.message);
console.error('Unknown Error', err.message);
err.status = 503;
next(err);
}
}
@ -31,4 +32,4 @@ module.exports = {
getAlive,
getReady,
getTime
}
};

View File

@ -2,14 +2,15 @@ const ldb = require('../services/ldb.services');
async function get(req, res, next){
try {
var id = req.params.id
res.json(await ldb.get(req.body, id))
var id = req.params.id;
res.json(await ldb.get(id));
} catch (err) {
console.error(`Unknown Error`, err.message);
console.error('Unknown Error', err.message);
err.status = 500;
next(err);
}
}
module.exports = {
get
}
};

View File

@ -0,0 +1,16 @@
const ldb = require('../services/ldb.services');
async function get(req, res, next){
try {
var id = req.params.id;
res.json(await ldb.get(id, true));
} catch (err) {
console.error('Unknown Error', err.message);
err.status = 500;
next(err);
}
}
module.exports = {
get
};

View File

@ -2,27 +2,30 @@ const list = require('../services/list.services');
async function getStations(req, res, next){
try {
res.json(await list.getStations(req.body))
res.json(await list.getStations(req.body));
} catch (err) {
console.error(`Controller Error`, err.message);
console.error('Controller Error', err.message);
err.status = 500;
next(err);
}
}
async function getCorpus(req, res, next){
try {
res.json(await list.getCorpus(req.body))
res.json(await list.getCorpus(req.body));
} catch (err) {
console.error(`Controller Error`, err.message);
console.error('Controller Error', err.message);
err.status = 500;
next(err);
}
}
async function hits(req, res, next) {
try {
res.json(await list.hits())
res.json(await list.hits());
} catch (err) {
console.error(`Controller Error`, err);
console.error('Controller Error', err);
err.status = 500;
next(err);
}
}
@ -31,4 +34,4 @@ module.exports = {
getStations,
getCorpus,
hits
}
};

View File

@ -0,0 +1,17 @@
const pis = require('../services/pis.services');
async function byOrigDest(req, res, next){
try {
let start = req.params.start;
let end = req.params.end;
res.json(await pis.findPisByOrigDest(start,end));
} catch (err) {
console.error('Unknown Error', err.message);
next(err);
}
}
module.exports = {
byOrigDest
};

View File

@ -0,0 +1,26 @@
const reg = require('../services/registration.services');
async function register(req, res, next) {
try {
let response = await reg.regUser(req.body);
res.status(response.status).json(response);
} catch (err) {
console.error('Controller Error', err.message);
next(err);
}
}
async function request(req, res, next) {
try {
let response = await reg.createRegKey(req.body);
res.status(response.status).json(response);
} catch (err) {
console.error(err);
next(err);
}
}
module.exports = {
register,
request
};

View File

@ -2,12 +2,14 @@ const stat = require('../services/stats.services');
async function get(req, res, next) {
try {
res.json(await stat.hits())
res.json(await stat.hits());
} catch (err) {
console.error(`Controller Error`, err);
console.error('Controller Error', err);
err.status = 500;
next(err);
}
}
module.exports = {
get}
get
};

View File

@ -1,17 +0,0 @@
<html lang="en" style="background-color: grey; width: 100%;">
<head>
<title>OwlBoard - Register</title>
</head>
<body>
<table width="100%">
<tr>
<td>
<img src="https://owlboard.info/images/logo/wide_logo.svg" style="height: 100px">
</td>
<td>
<h1>Register for OwlBoard</h1>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,27 @@
const utils = require('../utils/auth.utils');
const log = require('../utils/log.utils');
module.exports = async function authCheck(req, res, next) {
log.out('authMiddlewares: Checking authentication', 'dbug');
try {
var uuid = req.headers.uuid;
} catch(err) {
log.out('authMiddlewares: No authentication attempted', 'dbug');
err.status = 401;
return next(err);
}
try {
var result = await utils.isAuthed(uuid) || false;
if (!result) {
const err = new Error('Unauthorised');
err.status = 401;
log.out('authMiddlewares: Authentication attempted with incorrect key', 'warn');
return next(err);
} else {
log.out('authMiddlewares: User authenticated', 'dbug');
return next();
}
} catch(err) {
return next(err);
}
};

View File

@ -0,0 +1,12 @@
const log = require('../utils/log.utils');
module.exports = async function requireJson(req, res, next) {
if (req.headers['content-type'] !== 'application/json') {
log.out('requireJson.middlewares: Bad Request: Not in JSON format');
res.status(400).send({status: 400, message: 'Server requires JSON'});
} else {
next();
}
};
// Possibly want to check the req type?

View File

@ -6,4 +6,4 @@ router.get('/alive', kubeController.getAlive);
router.get('/ready', kubeController.getReady);
router.get('/time', kubeController.getTime);
module.exports = router
module.exports = router;

19
src/routes/ldbs.routes.js Normal file
View File

@ -0,0 +1,19 @@
const express = require('express');
const router = express.Router();
const ldbsController = require('../controllers/ldbs.controllers');
/* GET programming languages. */
//router.get('/', programmingLanguagesController.get);
/* POST programming language */
//router.post('/', programmingLanguagesController.create);
/* PUT programming language */
//router.put('/:id', programmingLanguagesController.update);
/* DELETE programming language */
//router.delete('/:id', programmingLanguagesController.remove);
router.get('/:id', ldbsController.get);
module.exports = router;

7
src/routes/pis.routes.js Normal file
View File

@ -0,0 +1,7 @@
const express = require('express');
const router = express.Router();
const pisController = require('../controllers/pis.controllers');
router.get('/origdest/:start/:end', pisController.byOrigDest);
module.exports = router;

View File

@ -0,0 +1,8 @@
const express = require('express');
const router = express.Router();
const regController = require('../controllers/registration.controllers');
router.post('/request', regController.request);
router.post('/register', regController.register);
module.exports = router;

View File

@ -1,10 +1,11 @@
/* global process */
const log = require('../utils/log.utils'); // Log Helper
const dbUser = process.env.OWL_DB_USER || "owl"
const dbPass = process.env.OWL_DB_PASS || "twittwoo"
const dbName = process.env.OWL_DB_NAME || "owlboard"
const dbPort = process.env.OWL_DB_PORT || 27017
const dbHost = process.env.OWL_DB_HOST || "localhost"
const dbUser = process.env.OWL_DB_USER || 'owl';
const dbPass = process.env.OWL_DB_PASS || 'twittwoo';
const dbName = process.env.OWL_DB_NAME || 'owlboard';
const dbPort = process.env.OWL_DB_PORT || 27017;
const dbHost = process.env.OWL_DB_HOST || 'localhost';
const uri = `mongodb://${dbUser}:${dbPass}@${dbHost}:${dbPort}`;
const { MongoClient } = require('mongodb');
@ -14,26 +15,69 @@ const db = client.db(dbName);
async function query(collection, query){
await client.connect();
log.out(`dbAccess.query: Connecting to collection: '${collection}'`, "info")
log.out(`dbAccess.query: Connecting to collection: '${collection}'`, 'info');
var qcoll = db.collection(collection);
var qcursor = qcoll.find(query)
qcursor.project({_id: 0})
log.out(`dbAccess.query: Running Query: ${JSON.stringify(query)}`, "info")
increment(collection)
var qcursor = qcoll.find(query);
qcursor.project({_id: 0});
log.out(`dbAccess.query: Running Query: ${JSON.stringify(query)}`, 'info');
increment(collection);
return (await qcursor.toArray());
}
async function increment(target) {
log.out(`dbAccess.increment: Incrementing counter for: ${target}`, "info")
log.out(`dbAccess.increment: Incrementing counter for: ${target}`, 'info');
await client.connect();
let col = db.collection("meta");
let update = {}
update[target] = 1
col.updateOne({target: "counters"}, {$inc:update})
return;
let col = db.collection('meta');
let update = {};
update[target] = 1;
col.updateOne({target: 'counters'}, {$inc:update});
}
async function addUser(uuid, domain) { // Needs testing
log.out('dbAccess.addUser: Adding user to database');
let doc = {uuid: uuid, domain: domain, atime: new Date};
await client.connect();
let col = db.collection('users');
let res = await col.insertOne(doc);
if (res.insertedId) {
return true;
}
return false;
}
async function addRegReq(uuid, domain) { // Needs testing
log.out('dbAccess.addRegReq: Adding registration request');
let doc = {uuid: uuid, time: new Date, domain: domain};
await client.connect();
let col = db.collection('registrations');
let res = col.insertOne(doc);
return res;
}
async function userAtime(uuid) { // Needs testing
log.out('dbAccess.userAtime: Updating access time for user');
let q = {uuid: uuid};
let n = {$set: {uuid: uuid, atime: new Date}};
await client.connect();
let col = db.collection('users');
let res = col.updateOne(q, n, {upsert: true});
return res;
}
// Deletes one single registration request entry from the DB
async function delRegReq(uuid) {
log.out('dbAccess.delRegReq: Deleting a Registration Request');
let collection = 'registrations';
await client.connect();
let col = db.collection(collection);
col.deleteOne({uuid: uuid});
}
module.exports = {
query,
increment
}
increment, // Probqbly doesn't need exporting? - It does, ldbServices needs to increment when the API is hit!
addUser,
userAtime,
addRegReq,
delRegReq
};

View File

@ -2,52 +2,52 @@
const log = require('../utils/log.utils'); // Log Helper
const db = require('../services/dbAccess.services');
const san = require('../utils/sanitizer.utils')
const san = require('../utils/sanitizer.utils');
// DB Query: query(collection, query)
// Define collection as all queries are for the "corpus" collection.
const col = "corpus"
const col = 'corpus';
async function name(id){
log.out(`findServices.name: Finding station name: ${id}`, "info")
var name = san.cleanApiEndpointTxt(id.toUpperCase())
query = {NLCDESC: name}
log.out(`findServices.name: Finding station name: ${id}`, 'info');
var name = san.cleanApiEndpointTxt(id.toUpperCase());
let query = {NLCDESC: name};
//var data = await db.query(col,query)
return await db.query(col,query)
return await db.query(col,query);
}
async function crs(id){
log.out(`findServices.crs: Finding crs: ${id}`, "info")
var crs = san.cleanApiEndpointTxt(id.toUpperCase())
query = {'3ALPHA': crs}
log.out(`findServices.crs: Finding crs: ${id}`, 'info');
var crs = san.cleanApiEndpointTxt(id.toUpperCase());
let query = {'3ALPHA': crs};
//var data = await db.query(col,query)
return await db.query(col,query)
return await db.query(col,query);
}
async function nlc(id){
log.out(`findServices.nlc: Finding nlc: ${id}`, "info")
var nlc = san.cleanApiEndpointNum(id)
query = {NLC: parseInt(nlc)}
log.out(`findServices.nlc: NLC Converted to int: ${query}`, "info")
log.out(`findServices.nlc: Finding nlc: ${id}`, 'info');
var nlc = san.cleanApiEndpointNum(id);
let query = {NLC: parseInt(nlc)};
log.out(`findServices.nlc: NLC Converted to int: ${query}`, 'info');
//var data = await db.query(col,query)
return await db.query(col,query)
return await db.query(col,query);
}
async function tiploc(id){
log.out(`findServices.tiploc: Finding tiploc: ${id}`, "info")
var tiploc = san.cleanApiEndpointTxt(id.toUpperCase())
query = {TIPLOC: tiploc}
log.out(`findServices.tiploc: Finding tiploc: ${id}`, 'info');
var tiploc = san.cleanApiEndpointTxt(id.toUpperCase());
let query = {TIPLOC: tiploc};
//var data = await db.query(col,query)
return await db.query(col,query)
return await db.query(col,query);
}
async function stanox(id){
log.out(`findServices.stanox: Finding stanox: ${id}`, "info")
var stanox = san.cleanApiEndpointNum(id)
query = {STANOX: String(stanox)}
log.out(`findServices.stanox: Finding stanox: ${id}`, 'info');
var stanox = san.cleanApiEndpointNum(id);
let query = {STANOX: String(stanox)};
//var data = await db.query(col,query)
return await db.query(col,query)
return await db.query(col,query);
}
module.exports = {
@ -56,4 +56,4 @@ module.exports = {
nlc,
tiploc,
stanox
}
};

View File

@ -1,33 +1,35 @@
const axios = require('axios')
const log = require('../utils/log.utils')
/* eslint-disable no-useless-escape */
/* global process */
const axios = require('axios');
const log = require('../utils/log.utils');
async function processor(data) {
log.out(`issueService.processor: Issue received`, "info")
let out = {}
log.out('issueService.processor: Issue received', 'info');
let out = {};
out.title = data.subject.replace(/<[^>]+>|[\*\$]/g, '');
out.body = data.msg.replace(/<[^>]+>|[\*\$]/g, '')
out.body = data.msg.replace(/<[^>]+>|[\*\$]/g, '');
return await sendToGitea(out);
}
async function sendToGitea(body) {
let key = process.env.OWL_GIT_ISSUEBOT
let url = process.env.OWL_GIT_APIENDPOINT
let key = process.env.OWL_GIT_ISSUEBOT;
let url = process.env.OWL_GIT_APIENDPOINT;
let opts = {
headers: {
Authorization: key
}
}
var res = await axios.post(url, body, opts)
};
var res = await axios.post(url, body, opts);
// Need to read the output from the POST and pass the result upwards to the client.
if (res.status == 201) {
log.out("issueService.sendToGitea: Issue sent to Gitea", "info")
return {status: res.status,message:"issue created"}
log.out('issueService.sendToGitea: Issue sent to Gitea', 'info');
return {status: res.status,message:'issue created'};
} else {
log.out(`issueService.sendToGitea: Failed to send issue to Gitea: ${res.body}`, "err")
return {status: res.status,message:"issue not created"}
log.out(`issueService.sendToGitea: Failed to send issue to Gitea: ${res.body}`, 'err');
return {status: res.status,message:'issue not created'};
}
}
module.exports = {
processor
}
};

View File

@ -1,20 +1,28 @@
const testing = require('../services/mail.services');
const log = require('../utils/log.utils');
async function getAlive(){
log.out(`kubeServices.getAlive: alive hook checked`, "info")
return {code: 200, state: {state: "alive",noise: "twit-twoo"}}
log.out('kubeServices.getAlive: alive hook checked', 'info');
return {code: 200, state: {state: 'alive',noise: 'twit-twoo'}};
}
async function getReady(){
log.out(`kubeServices.getReady: ready hook checked`, "info")
return "not_implemented";
};
log.out('kubeServices.getReady: ready hook checked', 'info');
testing.send({
to: 'fred@fjla.uk',
subject: 'OwlBoard Test',
txt: 'This is a test message from OwlBoard (testing)'
});
return 'not_implemented';
}
async function getTime(){
var now = new Date()
return {responseGenerated: now}
var now = new Date();
return {responseGenerated: now};
}
module.exports = {
getAlive,
getReady,
getTime
}
};

View File

@ -1,46 +1,67 @@
/* global process */
// Parse and return an LDB Request
const log = require('../utils/log.utils'); // Log Helper
const ldb = require('ldbs-json')
const util = require('../utils/ldb.utils')
const san = require('../utils/sanitizer.utils')
const db = require('../services/dbAccess.services')
const ldb = require('ldbs-json');
const util = require('../utils/ldb.utils');
const san = require('../utils/sanitizer.utils');
const db = require('../services/dbAccess.services');
const ldbKey = process.env.OWL_LDB_KEY
const ldbsvKey = process.env.OWL_LDB_SVKEY
const ldbKey = process.env.OWL_LDB_KEY;
const ldbsvKey = process.env.OWL_LDB_SVKEY;
async function get(body, id){
var cleanId = san.cleanApiEndpointTxt(id);
var obj = await util.checkCrs(cleanId);
async function get(id, staff=false){
const cleanId = san.cleanApiEndpointTxt(id);
const obj = await util.checkCrs(cleanId);
try {
var crs = obj[0]['3ALPHA'];
log.out(`ldbService.get: Determined CRS for lookup to be: ${crs}`, "info");
var data = arrDepBoard(crs);
db.increment("ldbws");
await data;
} catch (err) {
log.out(`ldbService.get: Error, Unable to find CRS: ${err}`, "info")
var data = {ERROR:'NOT_FOUND',description:'The entered station was not found. Please check and try again.'};
const crs = obj[0]['3ALPHA'];
log.out(`ldbService.get: Determined CRS for lookup to be: ${crs}`, 'info');
if (staff) {
const data = arrDepBoardStaff(crs);
db.increment('ldbsvws');
return await data;
} else {
const data = arrDepBoard(crs);
db.increment('ldbws');
return await data;
}
} catch (err) {
log.out(`ldbService.get: Error, Unable to find CRS: ${err}`, 'info');
return {ERROR:'NOT_FOUND',description:'The entered station was not found. Please check and try again.'};
}
return data;
}
async function arrDepBoard(CRS){
log.out(`ldbService.arrDepBoard: Trying to fetch ArrDep Board for ${CRS}`, "info")
log.out(`ldbService.arrDepBoard: Trying to fetch ArrDep Board for ${CRS}`, 'info');
try {
var options = {
const options = {
numRows: 10,
crs: CRS.toUpperCase()
}
var api = new ldb(ldbKey,false)
var reply = api.call("GetArrDepBoardWithDetails", options, false, false)
return await reply
} catch (err) {
log.out(`ldbService.arrDepBoard: Lookup Failed for: ${CRS}`, "warn")
return {GetStationBoardResult: "not available", Reason: `The CRS code ${CRS} is not valid`, Why: `Sometimes a station will have more than one CRS - for example Filton Abbey Wood has FIT and FAW however schedules are only available when looking up with FIT - this is how the National Rail Enquiries systems work.`};
}
};
const api = new ldb(ldbKey,false);
return await api.call('GetArrDepBoardWithDetails', options, false, false);
} catch (err) {
log.out(`ldbService.arrDepBoard: Lookup Failed for: ${CRS}`, 'warn');
return {GetStationBoardResult: 'not available', Reason: `The CRS code ${CRS} is not valid`, Why: 'Sometimes a station will have more than one CRS - for example Filton Abbey Wood has FIT and FAW however schedules are only available when looking up with FIT - this is how the National Rail Enquiries systems work.'};
}
}
async function arrDepBoardStaff(CRS) {
log.out(`ldbService.arrDepBoardStaff: Trying to fetch ArrDep Board for ${CRS}`, 'dbug');
try {
const options = {
numRows: 25,
crs: CRS.toUpperCase(),
getNonPassengerServices: true
};
const api = new ldb(ldbsvKey,true);
return await api.call('GetArrDepBoardWithDetails', options, false, false);
} catch (err) {
log.out(`ldbService.arrDepBoardStaff: Lookup Failed for: ${CRS}, "warn`);
return {GetStationBoardResult: 'not available', Reason: `The CRS code ${CRS} is not valid`, Why: 'Sometimes a station will have more than one CRS - for example Filton Abbey Wood has FIT and FAW however schedules are only available when looking up with FIT - this is how the National Rail Enquiries systems work.'};
}
}
module.exports = {
get
}
};

View File

@ -1,20 +1,19 @@
const log = require('../utils/log.utils'); // Log Helper
const db = require('../services/dbAccess.services')
const os = require('os')
const db = require('../services/dbAccess.services');
async function getStations(){
var out = db.query("stations")
log.out(`listServices.getStations: Fetching stations list`, "info")
var out = db.query('stations');
log.out('listServices.getStations: Fetching stations list', 'info');
return await out;
}
async function getCorpus(){
var out = db.query("corpus")
log.out(`listServices.getCorpus: Fetching CORPUS list`, "info")
var out = db.query('corpus');
log.out('listServices.getCorpus: Fetching CORPUS list', 'info');
return await out;
}
module.exports = {
getStations,
getCorpus
}
};

View File

@ -1,7 +1,36 @@
const log = require('../utils/log.utils')
const mail = require('some-mail-module')
/* global process */
const log = require('../utils/log.utils');
const mail = require('nodemailer'); //>> Probs wrong
async function sendTest() {
// Send test mail message
return;
const fromAddr = process.env.OWL_EML_FROM;
const smtpUser = process.env.OWL_EML_USER;
const smtpPass = process.env.OWL_EML_PASS;
const smtpHost = process.env.OWL_EML_HOST;
const smtpPort = process.env.OWL_EML_PORT;
let transporter = mail.createTransport({
host: smtpHost,
port: smtpPort,
secure: false, // Must be false for STARTTLS on port 587
auth: {
user: smtpUser,
pass: smtpPass
}
});
async function send(message){ // message is an object containing strings for: *to, cc, bcc, *subject, *txt, html (* denotes required)
log.out('mailServices.send: Message send request received', 'info');
message.from = fromAddr;
try {
var res = await transporter.sendMail(message);
} catch(err) {
log.out(`mailServices.send: Message send failed: ${err}`, 'err');
return false;
}
log.out(`mailServices.send: SMTP Response: ${res.response}`);
return true;
}
module.exports = {
send
};

View File

@ -0,0 +1,34 @@
// Finds PIS Codes using DB Lookups
const db = require('../services/dbAccess.services');
const log = require('../utils/log.utils');
const clean = require('../utils/sanitizer.utils');
async function findPisByOrigDest(start,end) {
log.out(`pisServices.findPisByOrigDest: Searching for PIS for Orig: ${start}, Dest: ${end}`, 'dbug');
const firstCrs = clean.cleanApiEndpointTxt(start.toLowerCase());
const lastCrs = clean.cleanApiEndpointTxt(end.toLowerCase());
const query = {
stops: {
$all: [
{ $elemMatch: { $eq: firstCrs } },
{ $elemMatch: { $eq: lastCrs } }
]
},
$expr: {
$and: [
{ $eq: [{ $arrayElemAt: [ '$stops', -1 ] }, lastCrs] },
{ $eq: [{ $arrayElemAt: [ '$stops', 0 ] }, firstCrs] }
]
}
};
//const oldQuery = {$and:[{$expr:{$eq:[{$first:"$stops"},firstCrs]}},{$expr:{$eq:[{$last:"$stops"},lastCrs]}}]}
const search = db.query('pis', query);
return search;
}
// Hopefully at some point, I will also be able to implement a find PIS code by headcode option.
module.exports = {
findPisByOrigDest
};

View File

@ -0,0 +1,51 @@
const log = require('../utils/log.utils');
const auth = require('../utils/auth.utils');
const db = require('./dbAccess.services');
const mail = require('./mail.services');
const clean = require('../utils/sanitizer.utils');
const domains = require('../configs/domains.configs');
const errors = require('../configs/errorCodes.configs');
async function createRegKey(body) {
log.out('registerServices.createRegKey: Incoming request', 'INFO');
const domain = await clean.getDomainFromEmail(body.email); // The function should validate the email
log.out(`registerServices: New registration request from domain: ${domain}`, 'info');
if (domains.includes(domain)) {
log.out(`registerServices.createRegKey: Key from valid domain: ${domain}`);
const uuid = await auth.generateKey();
db.addRegReq(uuid, domain);
const message = await auth.generateConfirmationEmail(body.email, uuid);
if (!message) {
const err = new Error('Message generation error');
log.out('registerServices.createRegKey: Error generating registration email', 'err');
log.out(err, 'err');
return 500;
}
if (await mail.send(message) == true) {
return {status: 201, message: 'email sent'};
}
return {status: 500, errorCode: 950, errorMsg: errors[950]};
}
return {status: 403, errorCode: 702, errorMsg: errors[702]};
}
async function regUser(req) { // Add input validation
log.out(`Read UUID: ${req.uuid}`, 'dbug');
log.out(`registrationServices.regUser: Checking validity of : ${req.uuid}`, 'info');
const res = await auth.checkRequest(req.uuid);
log.out(`registrationServices.regUser: checkRequest returned: ${JSON.stringify(res)}`, 'info');
if (res.result) {
const uuid = await auth.generateKey();
const apiKey = await db.addUser(uuid, res.domain);
if (apiKey) {
db.delRegReq(req.uuid);
return {status: 201, message: 'User added', api_key: uuid};
}
}
return {status: 401, errorCode: 703, errorMsg: errors[703]};
}
module.exports = {
regUser,
createRegKey
};

View File

@ -1,24 +1,25 @@
const log = require('../utils/log.utils'); // Log Helper
const db = require('../services/dbAccess.services')
const os = require('os')
const vers = require('../configs/version.configs')
const db = require('../services/dbAccess.services');
const os = require('os');
const vers = require('../configs/version.configs');
async function hits(){
log.out("statsServices.hits: Statistics Requested", "info")
var dat = db.query("meta", {target: "counters"});
var ver = db.query("meta", {target: "versions"});
log.out(`statsServices.hits: fetched server meta`, "info")
log.out('statsServices.hits: Statistics Requested', 'info');
var dat = db.query('meta', {target: 'counters'});
var ver = db.query('meta', {target: 'versions'});
log.out('statsServices.hits: fetched server meta', 'info');
let out = {};
out.host = os.hostname();
// eslint-disable-next-line no-undef
out.mode = process.env.NODE_ENV;
out.verBkend = vers.app;
out.verApi = vers.api;
out.dat = await dat;
out.ver = await ver;
log.out(`statsServices.hits: Sending Data: ${JSON.stringify(out)}`, "info")
log.out(`statsServices.hits: Sending Data: ${JSON.stringify(out)}`, 'info');
return out;
}
module.exports = {
hits
}
};

57
src/utils/auth.utils.js Normal file
View File

@ -0,0 +1,57 @@
const log = require('../utils/log.utils');
const crypto = require('crypto');
const db = require('../services/dbAccess.services');
const fs = require('fs/promises');
const minify = require('../utils/minify.utils');
// Checks users registration key against issued keys
async function isAuthed(uuid) { // Needs testing
const q = {uuid: uuid};
const res = await db.query('users', q);
log.out(`authUtils.checkUser: DB Query answer: ${JSON.stringify(res[0])}`, 'dbug');
const authorized = res && res[0] && res[0].domain;
if (authorized) db.userAtime(uuid);
return authorized;
}
// Checks whether a registration request key is valid
async function checkRequest(key) {
const collection = 'registrations';
const query = {uuid: key};
const res = await db.query(collection, query);
log.out(`authUtils.checkRequest: DB Query result: ${JSON.stringify(res)}`, 'dbug');
const result = res.length > 0 && res[0].time
? { result: true, domain: res[0].domain }
: { result: false };
return result;
}
// Creates an API key for a user
async function generateKey() { // Needs testing & moving to 'register.utils'
return crypto.randomUUID();
}
async function generateConfirmationEmail(eml, uuid) {
try {
const htmlTpl = await fs.readFile('mail-templates/register.html', 'utf-8');
const htmlStr = htmlTpl.replace(/>>ACCESSCODE<</g, uuid);
const htmlMin = await minify(htmlStr);
const txtTpl = fs.readFile('mail-templates/register.txt', 'utf-8');
return {
to: eml,
subject: 'OwlBoard Registration',
text: (await txtTpl).replace(/>>ACCESSCODE<</g, uuid),
html: htmlMin
};
} catch(err) {
log.out('mailServices.generateConfirmationEmail: Error reading templates, $(err)', 'err');
return false;
}
}
module.exports = {
isAuthed,
generateKey,
generateConfirmationEmail,
checkRequest
};

View File

@ -1,23 +1,29 @@
const log = require('../utils/log.utils'); // Log Helper
const db = require('../services/dbAccess.services') // DB Access
const san = require('../utils/sanitizer.utils') // Sanitiser
const db = require('../services/dbAccess.services'); // DB Access
const san = require('../utils/sanitizer.utils'); // Sanitiser
async function checkCrs(input){
var INPUT = input.toUpperCase()
log.out(`ldbUtils.checkCrs: Building database query to find: '${INPUT}'`, "info")
var query = {'$or':[{'3ALPHA':INPUT},{'TIPLOC':INPUT},{'STANOX':INPUT}]};
var result = await db.query("stations", query)
log.out(`ldbUtils.checkCrs: Query results: ${JSON.stringify(result)}`, "info")
return result
var INPUT = input.toUpperCase();
log.out(`ldbUtils.checkCrs: Building database query to find: '${INPUT}'`, 'info');
var query = {
'$or':[
{'3ALPHA':INPUT},
{'TIPLOC':INPUT},
{'STANOX':INPUT}
]
};
var result = await db.query('stations', query);
log.out(`ldbUtils.checkCrs: Query results: ${JSON.stringify(result)}`, 'dbug');
return result;
}
async function cleanMessages(input){ // Needs to be moved to the frontend `ensureArray() func`
var out = []
if (typeof input.message == "string") {
out.push(await san.cleanNrcc(input.message))
} else if (typeof input.message == "object") {
var out = [];
if (typeof input.message == 'string') {
out.push(await san.cleanNrcc(input.message));
} else if (typeof input.message == 'object') {
for(var i = 0; i < input.message.length; i++) {
out.push(await san.cleanNrcc(input.message[i]))
out.push(await san.cleanNrcc(input.message[i]));
}
}
return out;
@ -25,11 +31,11 @@ async function cleanMessages(input){ // Needs to be moved to the frontend `ensur
// Accepts an object but not an Array and returns it wrapped in an array.
async function cleanServices(input){ // Need to triple check but I don't think this is used anymore.
var out = []
var out = [];
if (!Array.isArray(input)) {
log.out(`ldbUtils.cleanServices: Transforming input: ${input}`, "depr")
out.push(input)
log.out(`ldbUtils.cleanServices: Returning output: ${out}`, "depr")
log.out(`ldbUtils.cleanServices: Transforming input: ${input}`, 'depr');
out.push(input);
log.out(`ldbUtils.cleanServices: Returning output: ${out}`, 'depr');
return out;
} else {
return input;
@ -40,4 +46,4 @@ module.exports = {
checkCrs,
cleanMessages,
cleanServices
}
};

View File

@ -1,7 +1,10 @@
/* global process */
const environment = process.env.NODE_ENV;
const hideInProduction = ['info', 'dbug'];
async function out(msg, level = 'othr') {
if (environment === "production" && level === "info") {
if (environment === 'production' && hideInProduction.includes(level.toLowerCase())) {
return;
} else {
const time = new Date().toISOString();
@ -11,4 +14,4 @@ async function out(msg, level = 'othr') {
module.exports = {
out
}
};

10
src/utils/minify.utils.js Normal file
View File

@ -0,0 +1,10 @@
const htmlShrink = require('html-minifier').minify;
const juice = require('juice');
module.exports = async function minifyMail(input) {
const inlined = juice(input);
return htmlShrink(inlined, {
removeComments: true,
collapseWhitespace: true
});
};

View File

@ -1,45 +1,31 @@
const clean = require('string-sanitizer-fix');
const log = require('../utils/log.utils');
//const log = require('../utils/log.utils');
/*
string.sanitize("a.bc@d efg#h"); // abcdefgh
string.sanitize.keepSpace("a.bc@d efg#h"); // abcd efgh
string.sanitize.keepUnicode("a.bc@d efg#hক"); // abcd efghক
string.sanitize.addFullstop("a.bc@d efg#h"); // abcd.efgh
string.sanitize.addUnderscore("a.bc@d efg#h"); // abcd_efgh
string.sanitize.addDash("a.bc@d efg#h"); // abcd-efgh
string.sanitize.removeNumber("@abcd efgh123"); // abcdefgh
string.sanitize.keepNumber("@abcd efgh123"); // abcdefgh123
string.addFullstop("abcd efgh"); // abcd.efgh
string.addUnderscore("@abcd efgh"); // @abcd_efgh
string.addDash("@abcd efgh"); // @abcd-efgh
string.removeSpace("@abcd efgh"); // @abcdefgh
*/
function cleanApiEndpointTxt(input) {
var output = clean.sanitize.keepSpace(input)
if (output != input){
log.out(`sanitizerUtils.cleanApiEndpoint: WARN: Sanitizing changed string. Input = ${input}`, "warn");
}
return output
function removeNonAlphanumeric(inputString) { // Should be able to replace sanitizer module
return inputString.replace(/[^a-zA-Z0-9]/g, '');
}
function cleanApiEndpointNum(input) {
var output = clean.sanitize.keepNumber(input)
if (output != input){
log.out(`sanitizerUtils.cleanApiEndpointNum: WARN: Sanitizing changed string. Input = ${input}`, "warn");
}
return output
function removeNonAlpha(inputString) { // Should be able to replace sanitizer module
return inputString.replace(/[^a-zA-Z]/g, '');
}
function cleanNrcc(input) {
var rmNewline = input.replace(/[\n\r]/g, ""); // Remove newlines
var rmPara = rmNewline.replace(/<\/?p[^>]*>/g, ""); // Remove <p> & </p>
return rmPara;
const cleanApiEndpointTxt = removeNonAlpha;
const cleanApiEndpointNum = removeNonAlphanumeric;
function cleanNrcc(input) { // Remove newlines and then <p> tags from input
const cleanInput = input.replace(/[\n\r]/g, '').replace(/<\/?p[^>]*>/g, '');
return cleanInput;
}
async function getDomainFromEmail(mail) { // Needs testing
let split = mail.split('@');
return split[1];
}
module.exports = {
cleanApiEndpointTxt,
cleanApiEndpointNum,
cleanNrcc
}
removeNonAlpha,
removeNonAlphanumeric,
cleanNrcc,
getDomainFromEmail,
};

View File

@ -1,15 +1,15 @@
function unixLocal(unix) {
var jsTime = unix*1000
var dt = new Date(jsTime)
return dt.toLocaleString()
var jsTime = unix*1000;
var dt = new Date(jsTime);
return dt.toLocaleString();
}
function jsUnix(js) {
var preRound = js / 1000
return Math.round(preRound)
var preRound = js / 1000;
return Math.round(preRound);
}
module.exports = {
unixLocal,
jsUnix,
}
};

View File

@ -1,3 +1,4 @@
/* global process */
// Checks that all required environment variables are present.
// Returns True or False and offers an object detailing what is missing.
@ -7,10 +8,10 @@ async function varTest(){
OWL_LDB_CORPUSUSER: process.env.OWL_LDB_CORPUSUSER,
OWL_LDB_CORPUSPASS: process.env.OWL_LDB_CORPUSPASS,
OWL_NOT_USED: process.env.OWL_NOT_USED
}
};
var desired = {
OWL_DB_PASS: process.env.OWL_DB_PASS
}
};
// DO NOT LOG CREDENTIALS!!!
// Test that each of required is NOT undefined.
@ -24,4 +25,4 @@ async function varTest(){
module.exports = {
varTest
}
};