You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
6.1 KiB
246 lines
6.1 KiB
/*!
|
|
* lib/http-server/http-server.js
|
|
* Copyright © 2019 – Katana Cryptographic Ltd. All Rights Reserved.
|
|
*/
|
|
'use strict'
|
|
|
|
const { App } = require('@tinyhttp/app')
|
|
const sirv = require('sirv')
|
|
const helmet = require('helmet')
|
|
const nocache = require('nocache')
|
|
const Logger = require('../logger')
|
|
const errors = require('../errors')
|
|
const network = require("../../lib/bitcoin/network")
|
|
const keys = require('../../keys')[network.key]
|
|
|
|
|
|
/**
|
|
* HTTP server
|
|
*/
|
|
class HttpServer {
|
|
|
|
/**
|
|
* Constructor
|
|
* @param {number} port - port used by the http server
|
|
* @param {string} host - host exposing the http server
|
|
*/
|
|
constructor(port, host) {
|
|
// Initialize server host and port
|
|
this.host = host ? host : '127.0.0.1'
|
|
this.port = port
|
|
|
|
// Listening server instance
|
|
this.server = null
|
|
|
|
// Initialize the tiny-http app
|
|
this.app = new App({
|
|
noMatchHandler: (req, res) => {
|
|
Logger.error(null, `HttpServer : 404 - Not found: ${req.method} - ${req.hostname}${req.path}`)
|
|
HttpServer.sendError(res, {status: '404 - Not found'}, 404)
|
|
},
|
|
// Error handler
|
|
onError: (err, req, res) => {
|
|
// Detect if this is auth error
|
|
if (Object.values(errors.auth).includes(err)) {
|
|
HttpServer.sendError(res, err, 401)
|
|
} else {
|
|
Logger.error(err.stack || err, 'HttpServer : general error')
|
|
const ret = {status: 'Server error'}
|
|
HttpServer.sendError(res, ret, 500)
|
|
}
|
|
}
|
|
});
|
|
|
|
// Middlewares for json responses and requests logging
|
|
this.app.use(HttpServer.requestLogger)
|
|
this.app.use(HttpServer.setCrossOrigin)
|
|
this.app.use(helmet(HttpServer.HELMET_POLICY))
|
|
this.app.use(nocache())
|
|
|
|
this.app.use('/static', sirv('../static'));
|
|
|
|
this.app.use((req, res, next) => {
|
|
res.append('X-Dojo-Version', keys.dojoVersion)
|
|
next()
|
|
})
|
|
|
|
this.app.use(HttpServer.setJSONResponse)
|
|
this.app.use(HttpServer.setConnection)
|
|
}
|
|
|
|
|
|
/**
|
|
* Start the http server
|
|
* @returns {object} returns the listening server instance
|
|
*/
|
|
start() {
|
|
// Start a http server
|
|
this.server = this.app.listen(this.port, () => {
|
|
Logger.info(`HttpServer : Listening on ${this.host}:${this.port}`)
|
|
}, this.host)
|
|
|
|
this.server.timeout = 600 * 1000
|
|
// @see https://github.com/nodejs/node/issues/13391
|
|
this.server.keepAliveTimeout = 0
|
|
|
|
return this.server
|
|
}
|
|
|
|
/**
|
|
* Stop the http server
|
|
*/
|
|
stop() {
|
|
if (this.server == null) return
|
|
this.server.close()
|
|
}
|
|
|
|
/**
|
|
* Return a http response without data
|
|
* @param {object} res - http response object
|
|
*/
|
|
static sendOk(res) {
|
|
const ret = {status: 'ok'}
|
|
res.status(200).json(ret)
|
|
}
|
|
|
|
/**
|
|
* Return a http response without status
|
|
* @param {object} res - http response object
|
|
* @param {object} data
|
|
*/
|
|
static sendOkDataOnly(res, data) {
|
|
res.status(200).json(data)
|
|
}
|
|
|
|
/**
|
|
* Return a http response with status and data
|
|
* @param {object} res - http response object
|
|
* @param {object} data - data object
|
|
*/
|
|
static sendOkData(res, data) {
|
|
const ret = {
|
|
status: 'ok',
|
|
data: data
|
|
}
|
|
res.status(200).json(ret)
|
|
}
|
|
|
|
/**
|
|
* Return a http response with raw data
|
|
* @param {object} res - http response object
|
|
* @param {object} data - data object
|
|
*/
|
|
static sendRawData(res, data) {
|
|
res.status(200).send(data)
|
|
}
|
|
|
|
/**
|
|
* Return an error response
|
|
* @param {object} res - http response object
|
|
* @param {string | Error} data - data object
|
|
* @param {number} [errorCode=400] - HTTP status code
|
|
*/
|
|
static sendError(res, data, errorCode) {
|
|
if (errorCode == null)
|
|
errorCode = 400
|
|
|
|
if (data instanceof Error) {
|
|
Logger.error(data, 'API: Unhandled error')
|
|
data = errors.generic.GEN
|
|
}
|
|
|
|
const ret = {
|
|
status: 'error',
|
|
error: data
|
|
}
|
|
|
|
res.status(errorCode).json(ret)
|
|
}
|
|
|
|
/*
|
|
* A middleware returning an authorization error response
|
|
* @param {object} res - http response object
|
|
* @param {string} err - error
|
|
*/
|
|
static sendAuthError(res, err) {
|
|
if (err) {
|
|
HttpServer.sendError(res, err, 401)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Express middleware returnsing a json response
|
|
* @param {object} req - http request object
|
|
* @param {object} res - http response object
|
|
* @param {function} next - next middleware
|
|
*/
|
|
static setJSONResponse(req, res, next) {
|
|
res.set('Content-Type', 'application/json')
|
|
next()
|
|
}
|
|
|
|
/**
|
|
* Express middleware adding cors header
|
|
* @param {object} req - http request object
|
|
* @param {object} res - http response object
|
|
* @param {function} next - next middleware
|
|
*/
|
|
static setCrossOrigin(req, res, next) {
|
|
res.set('Access-Control-Allow-Origin', '*')
|
|
res.set('Access-Control-Allow-Methods', 'GET,HEAD,POST,DELETE')
|
|
res.set('Access-Control-Allow-Headers', 'Content-Type,Authorization,Accept')
|
|
next()
|
|
}
|
|
|
|
/**
|
|
* Express middleware adding connection header
|
|
* @param {object} req - http request object
|
|
* @param {object} res - http response object
|
|
* @param {function} next - next middleware
|
|
*/
|
|
static setConnection(req, res, next) {
|
|
res.set('Connection', 'close')
|
|
next()
|
|
}
|
|
|
|
/**
|
|
* Express middleware logging url and methods called
|
|
* @param {object} req - http request object
|
|
* @param {object} res - http response object
|
|
* @param {function} next - next middleware
|
|
*/
|
|
static requestLogger(req, res, next) {
|
|
Logger.info(`HttpServer : ${req.method} ${req.url}`)
|
|
next()
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Helmet Policy
|
|
*/
|
|
HttpServer.HELMET_POLICY = {
|
|
'contentSecurityPolicy' : {
|
|
'directives': {
|
|
'default-src': ["'self'", "data:"],
|
|
'base-uri': ["'self'"],
|
|
'font-src': ["'self'", "https:", "data:"],
|
|
'frame-ancestors': ["'self'"],
|
|
'img-src': ["'self'", "data:"],
|
|
'object-src': ["'none'"],
|
|
'script-src': ["'self'", "'unsafe-inline'"],
|
|
'style-src': ["'self'", "https:", "'unsafe-inline'"],
|
|
'media-src': ["'self'", 'data:'],
|
|
},
|
|
},
|
|
'dnsPrefetchControl': true,
|
|
'frameguard': true,
|
|
'hidePoweredBy': true,
|
|
'ieNoOpen': true,
|
|
'noSniff': true,
|
|
'referrerPolicy': true,
|
|
'xssFilter': true
|
|
}
|
|
|
|
|
|
module.exports = HttpServer
|
|
|