admin-bro-expressjs
Version:
This is an official AdminBro plugin which integrates it to expressjs framework
264 lines (235 loc) • 7.85 kB
JavaScript
const express = require('express')
const AdminBro = require('admin-bro')
const path = require('path')
const formidableMiddleware = require('express-formidable')
const pkg = require('./package.json')
let session
try {
session = require('express-session') // eslint-disable-line global-require
} catch (e) {
console.info('express-session was not required')
}
/**
* Builds the Express Router that handles all the pages and assets
*
* @param {AdminBro} admin instance of AdminBro
* @param {express.Router} [predefinedRouter] Express.js router
* @param {ExpressFormidableOptions} [formidableOptions] Express.js router
* @return {express.Router} Express.js router
* @function
* @static
* @memberof module:admin-bro-expressjs
*/
const buildRouter = (admin, predefinedRouter, formidableOptions) => {
if (!admin || admin.constructor.name !== 'AdminBro') {
const e = new Error('you have to pass an instance of AdminBro to the buildRouter() function')
e.name = 'WrongArgumentError'
throw e
}
admin.initialize().then(() => {
console.log('AdminBro: bundle ready')
})
const { routes, assets } = AdminBro.Router
const router = predefinedRouter || express.Router()
router.use(formidableMiddleware(formidableOptions))
routes.forEach((route) => {
// we have to change routes defined in AdminBro from {recordId} to :recordId
const expressPath = route.path.replace(/{/g, ':').replace(/}/g, '')
/**
* @type {express.Handler}
*/
const handler = async (req, res, next) => {
try {
const controller = new route.Controller({ admin }, req.session && req.session.adminUser)
const { params, query } = req
const method = req.method.toLowerCase()
const payload = {
...(req.fields || {}),
...(req.files || {}),
}
const html = await controller[route.action]({
...req,
params,
query,
payload,
method,
}, res)
if (route.contentType) {
res.set({ 'Content-Type': route.contentType })
}
if (html) {
res.send(html)
}
} catch (e) {
next(e)
}
}
if (route.method === 'GET') {
router.get(expressPath, handler)
}
if (route.method === 'POST') {
router.post(expressPath, handler)
}
})
assets.forEach((asset) => {
router.get(asset.path, async (req, res) => {
res.sendFile(path.resolve(asset.src))
})
})
return router
}
/**
* @typedef {Function} Authenticate
* @memberof module:admin-bro-expressjs
* @description
* function taking 2 arguments email and password
* @param {string} [email] email given in the form
* @param {string} [password] password given in the form
* @return {CurrentAdmin | null} returns current admin or null
*/
/**
* Builds the Express Router which is protected by a session auth
*
* Using the router requires you to install `express-session` as a
* dependency. Normally express-session holds session in memory, which is
* not optimized for production usage and, in development, it causes
* logging out after every page refresh (if you use nodemon).
*
* @param {AdminBro} admin instance of AdminBro
* @param {Object} auth authentication options
* @param {module:admin-bro-expressjs.Authenticate} auth.authenticate authenticate function
* @param {String} auth.cookiePassword secret used to encrypt cookies
* @param {String} auth.cookieName=adminbro cookie name
* @param {express.Router} [predefinedRouter] Express.js router
* @param {SessionOptions} [sessionOptions] Options that are passed to [express-session](https://github.com/expressjs/session)
* @param {ExpressFormidableOptions} [formidableOptions] Options that are passed to [express-session](https://github.com/expressjs/session)
* @return {express.Router} Express.js router
* @static
* @memberof module:admin-bro-expressjs
* @example
* const ADMIN = {
* email: 'test@example.com',
* password: 'password',
* }
*
* AdminBroExpress.buildAuthenticatedRouter(adminBro, {
* authenticate: async (email, password) => {
* if (ADMIN.password === password && ADMIN.email === email) {
* return ADMIN
* }
* return null
* },
* cookieName: 'adminbro',
* cookiePassword: 'somePassword',
* }, [router])
*/
const buildAuthenticatedRouter = (
admin,
auth,
predefinedRouter,
sessionOptions = {},
formidableOptions = {},
) => {
if (!session) {
throw new Error(['In order to use authentication, you have to install',
' express-session package',
].join(' '))
}
const router = predefinedRouter || express.Router()
router.use((req, res, next) => {
if (req._body) {
next(new Error([
'You probably used old `body-parser` middleware, which is not compatible',
'with admin-bro-expressjs. In order to make it work you will have to',
'1. move body-parser invocation after the admin bro setup like this:',
'const adminBro = new AdminBro()',
'const router = new buildRouter(adminBro)',
'app.use(adminBro.options.rootPath, router)',
'// body parser goes after the AdminBro router',
'app.use(bodyParser())',
'2. Upgrade body-parser to the latest version and use it like this:',
'app.use(bodyParser.json())',
].join('\n')))
}
next()
})
router.use(session({
...sessionOptions,
secret: auth.cookiePassword,
name: auth.cookieName || 'adminbro',
}))
router.use(formidableMiddleware(formidableOptions))
const { rootPath } = admin.options
let { loginPath, logoutPath } = admin.options
loginPath = loginPath.replace(rootPath, '')
logoutPath = logoutPath.replace(rootPath, '')
router.get(loginPath, async (req, res) => {
const login = await admin.renderLogin({
action: admin.options.loginPath,
errorMessage: null,
})
res.send(login)
})
router.post(loginPath, async (req, res, next) => {
const { email, password } = req.fields
const adminUser = await auth.authenticate(email, password)
if (adminUser) {
req.session.adminUser = adminUser
req.session.save((err) => {
if (err) {
next(err)
}
if (req.session.redirectTo) {
res.redirect(req.session.redirectTo)
} else {
res.redirect(rootPath)
}
})
} else {
const login = await admin.renderLogin({
action: admin.options.loginPath,
errorMessage: 'invalidCredentials',
})
res.send(login)
}
})
router.use((req, res, next) => {
if (AdminBro.Router.assets.find(asset => req.originalUrl.match(asset.path))) {
next()
} else if (req.session.adminUser) {
next()
} else {
// If the redirection is caused by API call to some action just redirect to resource
const [redirectTo] = req.originalUrl.split('/actions')
req.session.redirectTo = redirectTo.includes(`${rootPath}/api`) ? rootPath : redirectTo
req.session.save((err) => {
if (err) {
next(err)
}
res.redirect(admin.options.loginPath)
})
}
})
router.get(logoutPath, async (req, res) => {
req.session.destroy(() => {
res.redirect(admin.options.loginPath)
})
})
return buildRouter(admin, router, formidableOptions)
}
module.exports = {
buildRouter,
buildAuthenticatedRouter,
/**
* Version of the plugin
* @static
* @memberof module:admin-bro-expressjs
*/
version: pkg.version,
/**
* Plugin name
* @static
* @memberof module:admin-bro-expressjs
*/
name: 'AdminBroExpressjs',
}