-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add initial module logic * Convert to correct hapi plugin * Add grunt tasks, lint config, update readme * Fix lint * PR Fixes
- Loading branch information
Showing
13 changed files
with
359 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
extends: fxa/server |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"disallowKeywords": ["with", "eval"], | ||
"disallowKeywordsOnNewLine": ["else"], | ||
"disallowMultipleLineStrings": true, | ||
"disallowSpaceAfterObjectKeys": true, | ||
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-"], | ||
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], | ||
"maximumLineLength": 160, | ||
"requireCapitalizedConstructors": true, | ||
"requireCurlyBraces": ["for", "while", "do"], | ||
"requireLineFeedAtFileEnd": true, | ||
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return"], | ||
"requireSpaceAfterBinaryOperators": ["=", ",", "+", "-", "/", "*", "==", "===", "!=", "!=="], | ||
"requireSpaceAfterPrefixUnaryOperators": ["~"], | ||
"requireSpacesInConditionalExpression": true, | ||
"requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="], | ||
"validateIndentation": 2, | ||
"validateQuoteMarks": "'" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"exceptions": [ | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module.exports = function (grunt) { | ||
require('load-grunt-tasks')(grunt) | ||
|
||
grunt.initConfig({ | ||
pkg: grunt.file.readJSON('./package.json'), | ||
// .js files for ESLint, JSHint, JSCS, etc. | ||
mainJsFiles: '{,grunttasks/,lib/**/,test/**/}*.js' | ||
}) | ||
|
||
grunt.loadTasks('grunttasks') | ||
|
||
grunt.registerTask('default', ['lint', 'nsp']) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,29 @@ | ||
# hapi-hpkp | ||
Hapi module to add HPKP headers to request | ||
Inspired by [Helmetjs](https://github.com/helmetjs/hpkp), this is a Hapi module to add HPKP headers to all requests. | ||
|
||
## Example | ||
|
||
```javascript | ||
var hpkp = require('./index') | ||
var Hapi = require('hapi') | ||
|
||
var hpkpOptions = { | ||
maxAge: 1, // In seconds | ||
sha256s: ["orlando=", "magic="], // Array of sha256 | ||
includeSubdomains: true, // optional | ||
reportUri: 'http://test.site', // optional | ||
reportOnly: false // optional | ||
} | ||
|
||
var server = new Hapi.Server() | ||
|
||
// Register HPKP plugin | ||
server.register({ | ||
register: hpkp, | ||
options: hpkpOptions | ||
}, function (err) { | ||
if (err) { | ||
console.error('Failed to load plugin:', err) | ||
} | ||
}) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module.exports = function (grunt) { | ||
'use strict' | ||
|
||
grunt.config('eslint', { | ||
options: { | ||
eslintrc: '.eslintrc' | ||
}, | ||
files: [ | ||
'{,grunttasks/,lib/**/,test/**/}*.js' | ||
] | ||
}) | ||
grunt.registerTask('quicklint', 'lint the modified files', 'newer:eslint') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
module.exports = function (grunt) { | ||
'use strict' | ||
|
||
grunt.config('jscs', { | ||
app: [ | ||
'<%= mainJsFiles %>' | ||
], | ||
options: { | ||
config: '.jscsrc' | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// meta grunt task to run other linters. | ||
|
||
module.exports = function (grunt) { | ||
'use strict' | ||
|
||
grunt.registerTask('lint', [ | ||
'eslint', | ||
'jscs' | ||
]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module.exports = function (grunt) { | ||
'use strict' | ||
|
||
grunt.config('nsp', { | ||
output: 'summary', | ||
package: grunt.file.readJSON('package.json') | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/** | ||
* Hapi middleware to append HPKP headers to all responses. | ||
* | ||
*/ | ||
'use strict' | ||
|
||
var hpkp = require('./lib/hpkp') | ||
|
||
exports.register = function (server, options, next) { | ||
server.ext('onPreResponse', hpkp(options)) | ||
|
||
next() | ||
} | ||
|
||
exports.register.attributes = { | ||
pkg: require('./package.json') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/** | ||
* Hapi middleware to append HPKP headers to all responses. | ||
* | ||
*/ | ||
var Joi = require('joi') | ||
|
||
module.exports = function (options) { | ||
|
||
var optionsSchema = Joi.object().keys({ | ||
maxAge: Joi.number().min(0).required(), | ||
sha256s: Joi.array().min(1).items(Joi.string()).required(), | ||
reportUri: Joi.string().uri().optional(), | ||
reportOnly: Joi.boolean().optional(), | ||
includeSubdomains: Joi.boolean().optional() | ||
}) | ||
|
||
var error = optionsSchema.validate(options).error | ||
if (error) { | ||
throw new Error(error) | ||
} | ||
|
||
var sha256s = options.sha256s | ||
var maxAge = options.maxAge | ||
var includeSubdomains = options.includeSubdomains | ||
var reportOnly = options.reportOnly | ||
var reportUri = options.reportUri | ||
|
||
var hpkpParts = [] | ||
|
||
sha256s.forEach(function (shaPin) { | ||
hpkpParts.push('pin-sha256="' + shaPin + '"') | ||
}) | ||
|
||
hpkpParts.push('max-age=' + maxAge) | ||
|
||
if (includeSubdomains) { | ||
hpkpParts.push('includeSubdomains') | ||
} | ||
|
||
if (reportUri) { | ||
hpkpParts.push('report-uri="' + reportUri + '"') | ||
} | ||
|
||
var hpkpHeaderKey = 'Public-Key-Pins' | ||
if (reportOnly) { | ||
hpkpHeaderKey = 'Public-Key-Pins-Report-Only' | ||
} | ||
|
||
var hpkpHeader = hpkpParts.join('; ') | ||
|
||
return function (request, reply) { | ||
var response = request.response | ||
|
||
if (response.header) { | ||
response.header(hpkpHeaderKey, hpkpHeader) | ||
} | ||
|
||
return reply.continue() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"name": "hapi-hpkp", | ||
"version": "1.0.0", | ||
"description": "Hapi module to add HPKP headers", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "node ./node_modules/.bin/mocha tests/index.js", | ||
"lint": "grunt lint" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/vbudhram/hapi-hpkp.git" | ||
}, | ||
"keywords": [ | ||
"hapi", | ||
"hpkp" | ||
], | ||
"author": "Vijay Budhram", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/vbudhram/hapi-hpkp/issues" | ||
}, | ||
"homepage": "https://github.com/vbudhram/hapi-hpkp#readme", | ||
"dependencies": { | ||
"joi": "9.0.4" | ||
}, | ||
"devDependencies": { | ||
"chai": "3.5.0", | ||
"eslint-config-fxa": "2.1.0", | ||
"hapi": "15.0.2", | ||
"grunt": "1.0.1", | ||
"grunt-eslint": "19.0.0", | ||
"grunt-jscs": "3.0.1", | ||
"grunt-nsp": "2.3.1", | ||
"load-grunt-tasks": "3.5.2", | ||
"mocha": "3.0.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
var assert = require('chai').assert | ||
var hpkp = require('../lib/hpkp') | ||
var Hapi = require('hapi') | ||
var server | ||
|
||
function createServer(port, hpkpOptions) { | ||
server = new Hapi.Server() | ||
server.connection({ | ||
port: port | ||
}) | ||
|
||
server.register({ | ||
register: require('../index.js'), | ||
options: hpkpOptions | ||
}, function (err) { | ||
if (err) { | ||
console.error('Failed to load plugin:', err) | ||
} | ||
}); | ||
|
||
server.route({ | ||
method: 'GET', | ||
path: '/', | ||
handler: function (request, reply) { | ||
return reply('HPKP!') | ||
} | ||
}) | ||
|
||
server.start() | ||
|
||
return server | ||
} | ||
|
||
var sha256s = [ | ||
'orlando=', | ||
'magic=' | ||
] | ||
|
||
var passingTestCases = [ | ||
{ | ||
name: 'should process minimum HPKP header', | ||
options: { | ||
maxAge: 1, | ||
sha256s: sha256s | ||
}, | ||
expectedKey: 'public-key-pins', | ||
expectedHeader: 'pin-sha256="orlando="; pin-sha256="magic="; max-age=1' | ||
}, | ||
{ | ||
name: 'should process with includeSubdomains', | ||
options: { | ||
maxAge: 1, | ||
sha256s: sha256s, | ||
includeSubdomains: true | ||
}, | ||
expectedKey: 'public-key-pins', | ||
expectedHeader: 'pin-sha256="orlando="; pin-sha256="magic="; max-age=1; includeSubdomains' | ||
}, | ||
{ | ||
name: 'should process with reportOnly', | ||
options: { | ||
maxAge: 1, | ||
sha256s: sha256s, | ||
reportOnly: true | ||
}, | ||
expectedKey: 'public-key-pins-report-only', | ||
expectedHeader: 'pin-sha256="orlando="; pin-sha256="magic="; max-age=1' | ||
}, | ||
{ | ||
name: 'should process with report-uri', | ||
options: { | ||
maxAge: 1, | ||
sha256s: sha256s, | ||
reportOnly: true, | ||
reportUri: 'http://test.site' | ||
}, | ||
expectedKey: 'public-key-pins-report-only', | ||
expectedHeader: 'pin-sha256="orlando="; pin-sha256="magic="; max-age=1; report-uri="http://test.site"' | ||
} | ||
] | ||
|
||
describe('HPKP Headers', function () { | ||
passingTestCases.forEach(function (testCase) { | ||
var server | ||
var requestOptions = { | ||
method: "GET", | ||
url: "/" | ||
} | ||
before(function () { | ||
server = createServer(3000, testCase.options) | ||
}) | ||
|
||
after(function () { | ||
return server.stop() | ||
}) | ||
it(testCase.name, function (done) { | ||
server.inject(requestOptions, function (response) { | ||
assert.equal(response.headers[testCase.expectedKey], testCase.expectedHeader) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
|
||
var failingTestCases = [ | ||
{ | ||
name: 'should throw without any options', | ||
options: {}, | ||
message: 'ValidationError: child "maxAge" fails because ["maxAge" is required]' | ||
}, | ||
{ | ||
name: 'should throw without sha256s', | ||
options: { | ||
maxAge: 1 | ||
}, | ||
message: 'ValidationError: child "sha256s" fails because ["sha256s" is required]' | ||
}, | ||
{ | ||
name: 'should throw with empty sha256s', | ||
options: { | ||
maxAge: 1, | ||
sha256s: [] | ||
}, | ||
message: 'ValidationError: child "sha256s" fails because ["sha256s" must contain at least 1 items]' | ||
} | ||
] | ||
|
||
describe('HPKP Config', function () { | ||
failingTestCases.forEach(function (testCase) { | ||
it(testCase.name, function () { | ||
assert.throws(function () { | ||
hpkp(testCase.options) | ||
}, testCase.message, 'threw error') | ||
}) | ||
}) | ||
}) |