UNPKG

electrode-csrf-jwt

Version:

Stateless Cross-Site Request Forgery (CSRF) protection with JWT

89 lines (70 loc) 2.27 kB
"use strict"; const Boom = require("@hapi/boom"); const CSRF = require("./csrf"); const pkg = require("../package.json"); const makeCookieConfig = require("./make-cookie-config"); const constants = require("./constants"); function csrfPlugin(server, options, next) { if (!options.secret) { return next(new Error(`${pkg.name}: hapi-plugin options missing secret`)); } const cookieConfig = makeCookieConfig( { path: "/", isSecure: false, // prevent scripts from reading the cookie isHttpOnly: true }, options.cookieConfig ); const csrf = new CSRF(options); const createToken = (request, payload) => { const plugin = request.plugins[pkg.name]; if (plugin) { if (!plugin.tokens) { plugin.tokens = csrf.create(payload); plugin.createToken = undefined; } return plugin.tokens; } return undefined; }; server.ext("onPreAuth", (request, reply) => { const routeConfig = request.route.settings.plugins[pkg.name] || {}; csrf.process( { request, method: request.method, firstPost: request.headers[constants.firstPostHeaderName], create: () => { // initialize plugin in request to let onPreResponse to create tokens later request.plugins[pkg.name] = { createToken }; Object.defineProperty(request.app, "jwt", { get: () => { const tokens = createToken(request); return tokens && tokens.header; } }); }, verify: () => csrf.verify(request.headers[csrf.headerName], request.state[csrf.cookieName]), continue: () => reply.continue(), error: verify => reply(Boom.badRequest(verify.error.message)) }, routeConfig ); }); server.ext("onPreResponse", (request, reply) => { const tokens = createToken(request); if (tokens) { const headers = request.response.isBoom ? request.response.output.headers : request.response.headers; reply.state(csrf.cookieName, tokens.cookie, cookieConfig); headers[csrf.headerName] = tokens.header; } reply.continue(); }); return next(); } csrfPlugin.attributes = { pkg }; module.exports = csrfPlugin;