Setup logger, configloader, middleware & endpoints

This commit is contained in:
2021-01-03 18:16:01 +01:00
parent 444502a84c
commit 5b9d9aeca8
9 changed files with 303 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
const path = require('path');
const Field = require('./field.js');
let instance = null;
class Config {
constructor() {
this.location = Config.determineLocation();
this.fields = require(`${this.location}`);
}
static getInstance() {
if (instance == null) {
instance = new Config();
}
return instance;
}
static determineLocation() {
if (process.env.NODE_ENV === "production")
return path.join(__dirname, "../../config/env/production.json");
return path.join(__dirname, "../../config/env/development.json");
}
get(section, option) {
if (this.fields[section] === undefined || this.fields[section][option] === undefined) {
throw new Error(`Field "${section} => ${option}" does not exist.`);
}
const field = new Field(this.fields[section][option]);
if (field.value === '') {
const envField = process.env[[section.toUpperCase(), option.toUpperCase()].join('_')];
if (envField !== undefined && envField.length !== 0) { return envField; }
}
if (field.value === undefined) {
throw new Error(`${section} => ${option} is empty.`);
}
return field.value;
}
}
module.exports = Config;

View File

@@ -0,0 +1,15 @@
class EnvironmentVariables {
constructor(variables) {
this.variables = variables || process.env;
}
get(variable) {
return this.variables[variable];
}
has(variable) {
return this.get(variable) !== undefined;
}
}
module.exports = EnvironmentVariables;

49
api/config/field.js Normal file
View File

@@ -0,0 +1,49 @@
const Filters = require('./filters.js');
const EnvironmentVariables = require('./environmentVariables.js');
class Field {
constructor(rawValue, environmentVariables) {
this.rawValue = rawValue;
this.filters = new Filters(rawValue);
this.valueWithoutFilters = this.filters.removeFiltersFromValue();
this.environmentVariables = new EnvironmentVariables(environmentVariables);
}
get value() {
if (this.filters.isEmpty()) {
return this.valueWithoutFilters;
}
if (this.filters.has('base64') && !this.filters.has('env')) {
return Field.base64Decode(this.valueWithoutFilters);
}
if (this.environmentVariables.has(this.valueWithoutFilters) &&
this.environmentVariables.get(this.valueWithoutFilters) === '') {
return undefined;
}
if (!this.filters.has('base64') && this.filters.has('env')) {
if (this.environmentVariables.has(this.valueWithoutFilters)) {
return this.environmentVariables.get(this.valueWithoutFilters);
}
return undefined;
}
if (this.filters.has('env') && this.filters.has('base64')) {
if (this.environmentVariables.has(this.valueWithoutFilters)) {
const encodedEnvironmentVariable = this.environmentVariables.get(this.valueWithoutFilters);
return Field.base64Decode(encodedEnvironmentVariable);
}
return undefined;
}
return this.valueWithoutFilters;
}
static base64Decode(string) {
return new Buffer(string, 'base64').toString('utf-8');
}
}
module.exports = Field;

34
api/config/filters.js Normal file
View File

@@ -0,0 +1,34 @@
class Filters {
constructor(value) {
this.value = value;
this.delimiter = '|';
}
get filters() {
return this.value.split(this.delimiter).slice(0, -1);
}
isEmpty() {
return !this.hasValidType() || this.value.length === 0;
}
has(filter) {
return this.filters.includes(filter);
}
hasValidType() {
return (typeof this.value === 'string');
}
removeFiltersFromValue() {
if (this.hasValidType() === false) {
return this.value;
}
let filtersCombined = this.filters.join(this.delimiter);
filtersCombined += this.filters.length >= 1 ? this.delimiter : '';
return this.value.replace(filtersCombined, '');
}
}
module.exports = Filters;

57
api/logger.js Normal file
View File

@@ -0,0 +1,57 @@
const winston = require('winston');
const httpContext = require("express-http-context");
const logLevel = 'trace';
const customLevels = {
levels: {
fatal: 0,
error: 1,
warning: 2,
info: 3,
debug: 4,
trace: 5
},
colors: {
trace: 'blue',
debug: 'white',
info: 'green',
warning: 'yellow',
error: 'red',
fatal: 'red'
}
};
const appendSessionId = winston.format(info => {
info.sessionId = httpContext.get("sessionId");
return info
});
const logger = winston.createLogger({
level: logLevel,
levels: customLevels.levels,
transports: [
new winston.transports.File({
filename: `${__base}/logs/all-logs.log`,
format: winston.format.combine(
appendSessionId(),
winston.format.json()
)
})
]
});
winston.addColors(customLevels.colors);
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
};
module.exports = logger;

View File

@@ -0,0 +1,23 @@
const crypto = require("crypto");
const httpContext = require("express-http-context");
const addIdToRequest = (req, res, next) => {
try {
crypto.randomBytes(16, (err, buf) => {
if (err) {
// log err
id = null;
}
id = buf.toString("hex");
httpContext.set("sessionId", id);
next();
});
} catch (err) {
// log err
httpContext.set("sessionId", null);
next();
}
};
module.exports = addIdToRequest;

View File

@@ -0,0 +1,6 @@
const openCORS = (req, res, next) => {
res.set("Access-Control-Allow-Origin", "*")
return next();
};
module.exports = openCORS;

View File

@@ -0,0 +1,37 @@
const camelToKebabCase = str => str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
const mapFeaturePolicyToString = (features) => {
return Object.entries(features).map(([key, value]) => {
key = camelToKebabCase(key)
value = value == "*" ? value : `'${ value }'`
return `${key} ${value}`
}).join("; ")
}
const setupHeaders = (req, res, next) => {
res.set("Access-Control-Allow-Headers", "Content-Type")
// Security
res.set("X-Content-Type-Options", "nosniff");
res.set("X-XSS-Protection", "1; mode=block");
res.set("X-Frame-Options", "SAMEORIGIN");
res.set("X-DNS-Prefetch-Control", "off");
res.set("X-Download-Options", "noopen");
res.set("Strict-Transport-Security", "max-age=15552000; includeSubDomains")
// Feature policy
const features = {
fullscreen: "*",
payment: "none",
microphone: "none",
camera: "self",
speaker: "*",
syncXhr: "self"
}
const featureString = mapFeaturePolicyToString(features);
res.set("Feature-Policy", featureString)
return next();
}
module.exports = setupHeaders;

37
api/webserver/server.js Normal file
View File

@@ -0,0 +1,37 @@
const express = require("express");
const app = express();
const path = require("path");
global.__base = path.join(__dirname, "..");
global.__middleware = path.join(__dirname, "middleware");
global.__controllers = path.join(__dirname, "controllers");
// logging
const logger = require(`${__base}/logger`);
// middleware
const httpContext = require("express-http-context");
const setupCORS = require(`${__middleware}/setupCORS`);
const setupHeaders = require(`${__middleware}/setupHeaders`);
const addIdToRequest = require(`${__middleware}/addIdToRequest`);
app.use(httpContext.middleware);
app.use(setupCORS);
app.use(setupHeaders);
app.use(addIdToRequest);
// parse application/json
app.use(express.json());
const router = express.Router();
// const TokenController = require(`${__controllers}/tokenController`);
const PostController = require(`${__controllers}/postController`);
router.get("/api/post/:id/render", PostController.renderPost);
router.get("/api/post/:id", PostController.getPost);
router.put("/api/post/:id", PostController.updatePost);
// router.post("/api/payment/callback/v2/payments/:id", PaymentController.updatePayment);
app.use(router);
logger.info("Server started, listening at :30010");
app.listen(30010);