/**
* @module libflitter/express/ExpressUnit
*/
const uuid = require('uuid/v4')
const Unit = require('../Unit')
const session = require('express-session')
const MongoDBStore = require('connect-mongodb-session')(session)
const path = require('path')
const fs = require('fs')
const http = require('http')
const https = require('https')
/**
* The Express unit is responsible for injecting the 3rd-party tools
* that Flitter makes available into the underlying Express framework
* so they can be used in lower contexts. Currently, that includes the
* body parser and session store.
* @extends module:libflitter/Unit~Unit
*/
class ExpressUnit extends Unit {
/**
* Defines the services required by this unit.
* @returns {Array<string>}
*/
static get services() {
return [...super.services, 'configs', 'output', 'database']
}
/**
* Gets the name of the service provided by this unit: 'express'
* @returns {string} - 'express'
*/
static get name() {
return 'express'
}
/**
* If true, the application has been provided an SSL certificate and key.
* @returns {boolean}
*/
use_ssl() {
return !!this.configs.get('server.ssl.enable')
}
/**
* Gets the contents of the configured SSL certificate.
* @returns {Promise<string|undefined>}
*/
async ssl_certificate() {
if ( this.configs.get('server.ssl.enable') )
return fs.promises.readFile(this.configs.get('server.ssl.cert_file'), 'utf8')
}
/**
* Gets the contents of the configured SSL key.
* @returns {Promise<string|undefined>}
*/
async ssl_key() {
if ( this.configs.get('server.ssl.enable') )
return fs.promises.readFile(this.configs.get('server.ssl.key_file'), 'utf8')
}
/**
* Loads the unit. Registers the 'busboy-body-parser' and 'express-session' packages with the underlying Express app.
* @param {module:libflitter/app/FlitterApp~FlitterApp} app - the Flitter app
* @returns {Promise<void>}
*/
async go(app){
/*
* Use a custom HTTP/S server, instead of the built-in
* Express server. This allows for application-provided
* SSL, as well as advanced customizations.
*/
let server
if ( this.configs.get('server.ssl.enable') ){
this.output.info(`Will attempt to use SSL server.`)
server = https.createServer({
cert: fs.readFileSync(this.configs.get('server.ssl.cert_file'), 'utf8'),
key: fs.readFileSync(this.configs.get('server.ssl.key_file'), 'utf8'),
}, app.express)
}
else server = http.createServer(app.express)
this.server = server
const allow_uploads = this.configs.get('server.uploads.enable')
const allowed_path = this.configs.get('server.uploads.allowed_path')
/*
* Load the body parser into the underlying Express app.
* This is done here do that the body is parsed before any
* of the middleware/controllers/routing/etc.
*/
require('express-busboy').extend(app.express, { upload: !!allow_uploads, allowedPath: (allowed_path ? allowed_path : /./) })
let store
if ( this.database && this.database.status() === Unit.STATUS_RUNNING ) {
/*
* Set up the session store if we have the DB.
* Defaults to memory if not.
*/
store = new MongoDBStore({
uri: this.database.connect_string,
collection: 'flitter_sessions'
})
} else {
this.output.warn(`No database found. Using in-memory session driver. This can cause memory issues, and sessions will be lost on restart.`)
}
app.express.use(session({
genid: () => {
return uuid() // use UUIDs for session IDs
},
secret: this.configs.get('server.session.secret'),
resave: true,
saveUninitialized: true,
store: (store ? store : null),
}))
this.session_store = store
}
/**
* Closes the session store's DB connection.
* @param {module:libflitter/app/FlitterApp~FlitterApp} app
* @returns {Promise<void>}
*/
async cleanup(app){
if ( this.session_store && this.session_store.client && this.session_store.client.close ) {
this.output.info(`Closing session storage client.`)
await this.session_store.client.close()
}
}
/**
* Get the fully-qualified path to the migrations provided by this unit.
* @returns {string}
*/
migrations(){
return path.resolve(__dirname, 'migrations')
}
}
module.exports = exports = ExpressUnit