cellar-door/server.js

200 lines
5 KiB
JavaScript

'use strict'
require('dotenv').config()
const Hapi = require('hapi')
const init = async () => {
const server = Hapi.server({
port: process.env.PORT || 3000,
host: 'localhost',
state: {
// let cookies live twelve hours
ttl: 1000 * 60 * 60 * 12,
strictHeader: true,
ignoreErrors: true,
// cookies should only be set on https connections, except in testing.
isSecure: process.env.NODE_ENV !== 'test',
isHttpOnly: true,
isSameSite: 'Strict',
encoding: 'iron',
password: process.env.IRON_SECRET
}
})
server.app.tokenStore = server.cache({
segment: 'tokens',
expiresIn: 1000 * 60 * 60 * 3 // three hours
})
// make helper method available throughout
server.method('createStatefulUrl', require('./src/url.service'))
// Setup logging
await server.register({
plugin: require('good'),
options: {
ops: {
interval: 1000
},
reporters: {
myConsoleReporter: [
{
module: 'good-squeeze',
name: 'Squeeze',
args: [{ log: '*', response: '*' }]
},
{
module: 'good-console'
},
'stdout'
]
}
}
})
// Setup html templating
await server.register(require('vision'))
await server.register(require('inert'))
server.views({
engines: {
hbs: require('handlebars')
},
path: 'views',
partialsPath: 'views/partials',
relativeTo: __dirname,
isCached: process.env.NODE_ENV !== 'test'
})
// Setup authentication. Please note: You still need to add an if check
// on the routes you want to protect.
server.auth.scheme('cookie-scheme', require('./src/authentication.scheme'))
server.auth.strategy('cookie-strategy', 'cookie-scheme')
server.auth.default({
strategy: 'cookie-strategy',
mode: 'try'
})
// Decorate all responses with additional security headers. Hardens usage of
// javascript and iframes.
server.ext('onPreResponse', (request, h) => {
// exception responses returned by Boom can't be manipulated, exit early.
if (!request.response.header) {
return h.continue
}
// Prevent site from being iframed since that might lead people to sniff
// out passwords
request.response.header('X-FRAME-OPTIONS', 'deny')
if (process.env.NODE_ENV !== 'test') {
// CSP breaks browser-sync, so we ignore it for development
request.response.header('Content-Security-Policy', "default-src 'self'")
}
return h.continue
})
// Shows setup screen, redirects to login or shows app to authorize.
server.route({
method: 'GET',
path: '/',
handler: require('./src/authorization.handler').showAppToAuthorize
})
server.route({
method: 'GET',
path: '/favicon.ico',
handler: (request, h) => h.file('./public/favicon.ico')
})
// exchange authorization code for token
server.route({
method: 'POST',
path: '/',
options: { auth: false },
handler: require('./src/authorization.handler')
.exchangeAuthorizationCodeForToken
})
// user posts data to this endpoint to confirm that they want to give
// an authorization code to the site they want to login to. Code is
// generated, stored and given to the external site through redirect.
server.route({
method: 'POST',
path: '/authorize',
handler: require('./src/authorization.handler')
.authorizeCreationOfAuthorizationCode
})
server.route({
method: 'GET',
path: '/login',
options: { auth: false },
handler: (request, h) => {
if (
!process.env.USERNAME ||
!process.env.USER_PASSWORD ||
!process.env.IRON_SECRET
) {
// app is not configured so we redirect back to frontpage which will
// show a getting started screen.
return h.redirect('/')
}
const { message } = request.state
h.unstate('message')
return h.view('login', { ...request.query, ...{ message } })
}
})
server.route({
method: 'POST',
path: '/login',
options: { auth: false },
handler: require('./src/login.handler')
})
server.route({
method: 'GET',
path: '/logout',
options: { auth: false },
handler: (request, h) => h.redirect('/').unstate('sid-indieauth')
})
server.route({
method: 'GET',
path: '/token',
options: { auth: false },
handler: require('./src/token.handler').verifyToken
})
server.route({
method: 'POST',
path: '/token',
options: { auth: false },
handler: require('./src/token.handler').exchangeCodeForToken
})
server.route({
method: 'GET',
path: '/public/{param*}',
handler: {
directory: {
path: 'public'
}
}
})
await server.start()
console.log(`Server running at: ${server.info.uri}`)
return server
}
process.on('unhandledRejection', err => {
console.log(err)
process.exit(1)
})
// only start server if file is called directly
if (require.main === module) {
init()
}
module.exports = init