├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── index.js ├── lib ├── jwtRedisSession.js └── utils.js ├── package-lock.json ├── package.json └── test ├── fixture ├── client.js └── server.js ├── mocha.opts └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt){ 2 | 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON("package.json"), 5 | 6 | jshint: { 7 | files: [ 8 | "Gruntfile.js", 9 | "lib/**/*.js", 10 | "test/**/*.js" 11 | ], 12 | options: { 13 | eqeqeq: true, 14 | eqnull: true, 15 | browser: false, 16 | laxbreak: true 17 | } 18 | }, 19 | 20 | mochaTest: { 21 | options: { reporter: 'spec', checkLeaks: true }, 22 | src: ['test/**/*.js'] 23 | } 24 | 25 | }); 26 | 27 | grunt.loadNpmTasks("grunt-contrib-jshint"); 28 | grunt.loadNpmTasks("grunt-mocha-test"); 29 | 30 | grunt.registerTask("lint", [ "jshint" ]); 31 | grunt.registerTask("test", [ "mochaTest" ]); 32 | grunt.registerTask("default", [ "lint", "test" ]); 33 | 34 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Azuqua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JWT-Redis-Session 2 | ================= 3 | 4 | JSON Web Token session middleware backed by [Redis](http://redis.io/). This connect middleware module exposes an API surface similar to a [session middleware](https://github.com/expressjs/session#reqsession) module, however instead of using cookies to transport session details this module uses JSON Web Tokens. This is useful for cookie-less clients or for cross service user authentication. 5 | 6 | [Some info on JSON Web Tokens](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-19#section-3) 7 | 8 | # Install 9 | 10 | npm install jwt-redis-session 11 | 12 | # Important Notes 13 | 14 | Developers are free to use either the JWT claims or redis to store session related data. In many cases when serializing a user's session only the minimal amount of data necessary to uniquely identify the user's session is actually serialized and sent to the client. By default when this module creates a JWT token it will only reserve the "jti" property on the JWT claims object. This property will refer to a UUID that acts as the key in redis for the user's session data. This ensures that by default this module will only serialize the minimal amount of data needed. Any other data stored on the JWT session object throughout the request-response process will be serialized and stored in redis. 15 | 16 | Due to the way JSON Web Tokens work the claims object can only be modified when creating a new token. Because of this by default this module does not attach a TTL to the JWT. Any TTL attached to the JWT cannot be refreshed without regenerating a new JWT so this module instead manages a session's expiration via redis key expirations. Aside from the "jti" property, which this module reserves, developers are free to attach any data to the claims object when creating a new JWT, including a TTL, but need to be aware that any TTL on the claims object will supercede the TTL managed by redis. 17 | 18 | # API Overview 19 | 20 | ## Initialization 21 | 22 | This module supports a few initialization parameters that can be used to support several usage scenarios, including running any number of instances of this middleware module alongside each other. 23 | 24 | * **requestKey** - The key name on the request object used to identify the JWT session object. The default for this value is "session". This would interfere with a module such as [express-session](https://github.com/expressjs/session) so developers are free to modify this. 25 | * **requestArg** - The parameter name on the HTTP request that refers to the JWT. The middleware will look for this property in the query string, request body, and headers. The header name will be derived from a camelBack representation of the property name. For example, if the requestArg is "accessToken" (the default) then this instance of the middlware will look for the header name "x-access-token". 26 | * **keyspace** - The prefix of the keys stored in redis. By default this is "sess:". 27 | * **secret** - The secret key used to encrypt token data. 28 | * **algorithm** - The hashing algorithm to use, the default is "HS256" (SHA-256). 29 | * **client** - The redis client to use to perform redis commands. 30 | * **maxAge** - The maximum age (in seconds) of a session. 31 | 32 | ``` 33 | var JWTRedisSession = require("jwt-redis-session"), 34 | express = require("express"), 35 | redis = require("redis"); 36 | 37 | var redisClient = redis.createClient(), 38 | secret = generateSecretKeySomehow(), 39 | app = express(); 40 | 41 | app.use(JWTRedisSession({ 42 | client: redisClient, 43 | secret: secret, 44 | keyspace: "sess:", 45 | maxAge: 86400, 46 | algorithm: "HS256", 47 | requestKey: "jwtSession", 48 | requestArg: "jwtToken" 49 | })); 50 | ``` 51 | 52 | **All examples following this assume the above configuration.** 53 | 54 | ## Create JWT Session 55 | 56 | Create a new JSON Web Token from the provided claims and store any relevant data in redis. 57 | 58 | ``` 59 | var handleRequest = function(req, res){ 60 | User.login(req.param("username"), req.param("password"), function(error, user){ 61 | 62 | // this will be stored in redis 63 | req.jwtSession.user = user.toJSON(); 64 | 65 | // this will be attached to the JWT 66 | var claims = { 67 | iss: "my application name", 68 | aud: "myapplication.com" 69 | }; 70 | 71 | req.jwtSession.create(claims, function(error, token){ 72 | 73 | res.json({ token: token }); 74 | 75 | }); 76 | }); 77 | }; 78 | ``` 79 | 80 | ## Read JWT Data 81 | 82 | The session's UUID, JWT claims, and the JWT itself are all available on the jwtSession object as well. Any of these properties can be used to test for the existence of a valid JWT and session. 83 | 84 | ``` 85 | var handleRequest = function(req, res){ 86 | 87 | console.log("Request JWT session data: ", 88 | req.jwtSession.id, 89 | req.jwtSession.claims, 90 | req.jwtSession.jwt 91 | ); 92 | 93 | res.json(req.jwtSession.toJSON()); 94 | 95 | }; 96 | ``` 97 | 98 | ## Modify Session Data 99 | 100 | Any modifications to the jwtSession will be reflected in redis. 101 | 102 | ``` 103 | var handleRequest = function(req, res){ 104 | 105 | if(req.jwtSession.id){ 106 | 107 | req.jwtSession.foo = "bar"; 108 | 109 | req.jwtSession.update(function(error){ 110 | res.json(req.jwtSession.toJSON()); 111 | }); 112 | 113 | }else{ 114 | res.redirect("/login"); 115 | } 116 | }; 117 | ``` 118 | 119 | ## Reload Session Data 120 | 121 | Force a reload of the session data from redis. 122 | 123 | ``` 124 | var handleRequest = function(req, res){ 125 | 126 | setTimeout(function(){ 127 | 128 | req.jwtSession.reload(function(error){ 129 | res.json(req.jwtSession.toJSON()); 130 | }); 131 | 132 | }, 5000); 133 | 134 | }; 135 | ``` 136 | 137 | ## Refresh the TTL on a Session 138 | 139 | ``` 140 | var handleRequest = function(req, res){ 141 | 142 | req.jwtSession.touch(function(error){ 143 | res.json(req.jwtSession.toJSON()); 144 | }); 145 | 146 | }; 147 | ``` 148 | 149 | ## Destroy a Session 150 | 151 | Remove the session data from redis. The user's JWT may still be valid within its expiration window, but the backing data in redis will no longer exist. This module will not recognize the JWT when this is the case. 152 | 153 | ``` 154 | var handleRequest = function(req, res){ 155 | 156 | req.jwtSession.destroy(function(error){ 157 | res.redirect("/login"); 158 | }); 159 | 160 | }; 161 | ``` 162 | 163 | # Tests 164 | 165 | This module uses Mocha/Chai for testing. In order to run the tests a local redis server must be running or the REDIS_HOST and REDIS_PORT environment variables must be set. 166 | 167 | npm install 168 | grunt test 169 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require("path"); 3 | 4 | module.exports = require(path.join(__dirname, "lib/jwtRedisSession")); 5 | -------------------------------------------------------------------------------- /lib/jwtRedisSession.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var _ = require("lodash"), 4 | jwt = require("jsonwebtoken"), 5 | utils = require("./utils"); 6 | 7 | module.exports = function(options){ 8 | 9 | if(!options.client || !options.secret) 10 | throw new Error("Redis client and secret required for JWT Redis Session!"); 11 | 12 | options = { 13 | client: options.client, 14 | secret: options.secret, 15 | algorithm: options.algorithm || "HS256", 16 | keyspace: options.keyspace || "sess:", 17 | maxAge: options.maxAge || 86400, 18 | requestKey: options.requestKey || "session", 19 | requestArg: options.requestArg || "accessToken" 20 | }; 21 | 22 | var SessionUtils = utils(options); 23 | 24 | var requestHeader = _.reduce(options.requestArg.split(""), function(memo, ch){ 25 | return memo + (ch.toUpperCase() === ch ? "-" + ch.toLowerCase() : ch); 26 | }, "x" + (options.requestArg.charAt(0) === options.requestArg.charAt(0).toUpperCase() ? "" : "-")); 27 | 28 | return function jwtRedisSession(req, res, next){ 29 | 30 | req[options.requestKey] = new SessionUtils(); 31 | 32 | var token = req.get(requestHeader) 33 | || req.query[options.requestArg] 34 | || (req.body && req.body[options.requestArg]); 35 | 36 | if(token){ 37 | jwt.verify(token, options.secret, function(error, decoded){ 38 | if(error || !decoded.jti) 39 | return next(); 40 | 41 | options.client.get(options.keyspace + decoded.jti, function(err, session){ 42 | if(err || !session) 43 | return next(); 44 | 45 | try{ 46 | session = JSON.parse(session); 47 | }catch(e){ 48 | return next(); 49 | } 50 | 51 | _.extend(req[options.requestKey], session); 52 | req[options.requestKey].claims = decoded; 53 | req[options.requestKey].id = decoded.jti; 54 | req[options.requestKey].jwt = token; 55 | // Update the TTL 56 | req[options.requestKey].touch(_.noop); 57 | next(); 58 | }); 59 | }); 60 | }else{ 61 | next(); 62 | } 63 | }; 64 | 65 | }; 66 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var _ = require("lodash"), 4 | jwt = require("jsonwebtoken"), 5 | uuid = require("node-uuid"); 6 | 7 | 8 | var extendSession = function(session, data){ 9 | _.reduce(data, function(memo, val, key){ 10 | if(typeof val !== "function" && key !== "id") 11 | memo[key] = val; 12 | return memo; 13 | }, session); 14 | }; 15 | 16 | var serializeSession = function(session){ 17 | return _.reduce(session, function(memo, val, key){ 18 | if(typeof val !== "function" && key !== "id") 19 | memo[key] = val; 20 | return memo; 21 | }, {}); 22 | }; 23 | 24 | // these are bound to the session 25 | module.exports = function(options){ 26 | 27 | var SessionUtils = function(){}; 28 | 29 | _.extend(SessionUtils.prototype, { 30 | 31 | // create a new session and return the jwt 32 | create: function(claims, callback){ 33 | if(typeof claims === "function" && !callback){ 34 | callback = claims; 35 | claims = {}; 36 | } 37 | var self = this, 38 | sid = uuid.v4(); 39 | var token = jwt.sign(_.extend({ jti: sid }, claims || {}), options.secret, { algorithm: options.algorithm }); 40 | options.client.setex(options.keyspace + sid, options.maxAge, JSON.stringify(serializeSession(self)), function(error){ 41 | self.id = sid; 42 | callback(error, token); 43 | }); 44 | }, 45 | 46 | // update the TTL on a session 47 | touch: function(callback){ 48 | if(!this.id){ 49 | return process.nextTick(function(){ 50 | callback(new Error("Invalid session ID")); 51 | }); 52 | } 53 | options.client.expire(options.keyspace + this.id, options.maxAge, callback); 54 | }, 55 | 56 | // update a session's data, update the ttl 57 | update: function(callback){ 58 | if(!this.id){ 59 | return process.nextTick(function(){ 60 | callback(new Error("Invalid session ID")); 61 | }); 62 | } 63 | options.client.setex(options.keyspace + this.id, options.maxAge, JSON.stringify(serializeSession(this)), callback); 64 | }, 65 | 66 | // reload a session data from redis 67 | reload: function(callback){ 68 | var self = this; 69 | if(!this.id){ 70 | return process.nextTick(function(){ 71 | callback(new Error("Invalid session ID")); 72 | }); 73 | } 74 | 75 | options.client.get(options.keyspace + self.id, function(error, resp){ 76 | if(error) 77 | return callback(error); 78 | try{ 79 | resp = JSON.parse(resp); 80 | }catch(e){ 81 | return callback(e); 82 | } 83 | extendSession(self, resp); 84 | callback(); 85 | }); 86 | }, 87 | 88 | // destroy a session 89 | destroy: function(callback){ 90 | if(!this.id){ 91 | return process.nextTick(function(){ 92 | callback(new Error("Invalid session ID")); 93 | }); 94 | } 95 | options.client.del(options.keyspace + this.id, callback); 96 | }, 97 | 98 | toJSON: function(){ 99 | return serializeSession(this); 100 | } 101 | 102 | }); 103 | 104 | return SessionUtils; 105 | }; -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jwt-redis-session", 3 | "version": "1.0.6", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "abbrev": { 8 | "version": "1.1.1", 9 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 10 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 11 | "dev": true 12 | }, 13 | "accepts": { 14 | "version": "1.3.7", 15 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 16 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 17 | "dev": true, 18 | "requires": { 19 | "mime-types": "~2.1.24", 20 | "negotiator": "0.6.2" 21 | } 22 | }, 23 | "argparse": { 24 | "version": "0.1.16", 25 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", 26 | "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", 27 | "dev": true, 28 | "requires": { 29 | "underscore": "~1.7.0", 30 | "underscore.string": "~2.4.0" 31 | }, 32 | "dependencies": { 33 | "underscore.string": { 34 | "version": "2.4.0", 35 | "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", 36 | "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", 37 | "dev": true 38 | } 39 | } 40 | }, 41 | "array-flatten": { 42 | "version": "1.1.1", 43 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 44 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", 45 | "dev": true 46 | }, 47 | "assertion-error": { 48 | "version": "1.0.0", 49 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", 50 | "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", 51 | "dev": true 52 | }, 53 | "async": { 54 | "version": "0.9.2", 55 | "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", 56 | "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", 57 | "dev": true 58 | }, 59 | "body-parser": { 60 | "version": "1.19.0", 61 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 62 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 63 | "dev": true, 64 | "requires": { 65 | "bytes": "3.1.0", 66 | "content-type": "~1.0.4", 67 | "debug": "2.6.9", 68 | "depd": "~1.1.2", 69 | "http-errors": "1.7.2", 70 | "iconv-lite": "0.4.24", 71 | "on-finished": "~2.3.0", 72 | "qs": "6.7.0", 73 | "raw-body": "2.4.0", 74 | "type-is": "~1.6.17" 75 | } 76 | }, 77 | "buffer-equal-constant-time": { 78 | "version": "1.0.1", 79 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 80 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 81 | }, 82 | "bytes": { 83 | "version": "3.1.0", 84 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 85 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", 86 | "dev": true 87 | }, 88 | "chai": { 89 | "version": "1.10.0", 90 | "resolved": "https://registry.npmjs.org/chai/-/chai-1.10.0.tgz", 91 | "integrity": "sha1-5AMcyHZURhp1lD5aNatG6vOcHrk=", 92 | "dev": true, 93 | "requires": { 94 | "assertion-error": "1.0.0", 95 | "deep-eql": "0.1.3" 96 | } 97 | }, 98 | "cli": { 99 | "version": "0.6.6", 100 | "resolved": "https://registry.npmjs.org/cli/-/cli-0.6.6.tgz", 101 | "integrity": "sha1-Aq1Eo4Cr8nraxebwzdewQ9dMU+M=", 102 | "dev": true, 103 | "requires": { 104 | "exit": "0.1.2", 105 | "glob": "~ 3.2.1" 106 | }, 107 | "dependencies": { 108 | "glob": { 109 | "version": "3.2.11", 110 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", 111 | "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", 112 | "dev": true, 113 | "requires": { 114 | "inherits": "2", 115 | "minimatch": "0.3" 116 | } 117 | }, 118 | "minimatch": { 119 | "version": "0.3.0", 120 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", 121 | "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", 122 | "dev": true, 123 | "requires": { 124 | "lru-cache": "2", 125 | "sigmund": "~1.0.0" 126 | } 127 | } 128 | } 129 | }, 130 | "coffee-script": { 131 | "version": "1.3.3", 132 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", 133 | "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=", 134 | "dev": true 135 | }, 136 | "colors": { 137 | "version": "0.6.2", 138 | "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", 139 | "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", 140 | "dev": true 141 | }, 142 | "commander": { 143 | "version": "2.0.0", 144 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", 145 | "integrity": "sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=", 146 | "dev": true 147 | }, 148 | "console-browserify": { 149 | "version": "1.1.0", 150 | "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", 151 | "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", 152 | "dev": true, 153 | "requires": { 154 | "date-now": "^0.1.4" 155 | } 156 | }, 157 | "content-disposition": { 158 | "version": "0.5.3", 159 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 160 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 161 | "dev": true, 162 | "requires": { 163 | "safe-buffer": "5.1.2" 164 | }, 165 | "dependencies": { 166 | "safe-buffer": { 167 | "version": "5.1.2", 168 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 169 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 170 | "dev": true 171 | } 172 | } 173 | }, 174 | "content-type": { 175 | "version": "1.0.4", 176 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 177 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", 178 | "dev": true 179 | }, 180 | "cookie": { 181 | "version": "0.4.0", 182 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 183 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", 184 | "dev": true 185 | }, 186 | "cookie-signature": { 187 | "version": "1.0.6", 188 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 189 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", 190 | "dev": true 191 | }, 192 | "core-util-is": { 193 | "version": "1.0.2", 194 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 195 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 196 | "dev": true 197 | }, 198 | "date-now": { 199 | "version": "0.1.4", 200 | "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", 201 | "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", 202 | "dev": true 203 | }, 204 | "dateformat": { 205 | "version": "1.0.2-1.2.3", 206 | "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", 207 | "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=", 208 | "dev": true 209 | }, 210 | "debug": { 211 | "version": "2.6.9", 212 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 213 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 214 | "dev": true, 215 | "requires": { 216 | "ms": "2.0.0" 217 | }, 218 | "dependencies": { 219 | "ms": { 220 | "version": "2.0.0", 221 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 222 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 223 | "dev": true 224 | } 225 | } 226 | }, 227 | "deep-eql": { 228 | "version": "0.1.3", 229 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 230 | "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", 231 | "dev": true, 232 | "requires": { 233 | "type-detect": "0.1.1" 234 | } 235 | }, 236 | "depd": { 237 | "version": "1.1.2", 238 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 239 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", 240 | "dev": true 241 | }, 242 | "destroy": { 243 | "version": "1.0.4", 244 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 245 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", 246 | "dev": true 247 | }, 248 | "diff": { 249 | "version": "1.0.7", 250 | "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", 251 | "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=", 252 | "dev": true 253 | }, 254 | "dom-serializer": { 255 | "version": "0.2.2", 256 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", 257 | "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", 258 | "dev": true, 259 | "requires": { 260 | "domelementtype": "^2.0.1", 261 | "entities": "^2.0.0" 262 | }, 263 | "dependencies": { 264 | "domelementtype": { 265 | "version": "2.0.1", 266 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", 267 | "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", 268 | "dev": true 269 | }, 270 | "entities": { 271 | "version": "2.0.3", 272 | "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", 273 | "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", 274 | "dev": true 275 | } 276 | } 277 | }, 278 | "domelementtype": { 279 | "version": "1.3.1", 280 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", 281 | "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", 282 | "dev": true 283 | }, 284 | "domhandler": { 285 | "version": "2.3.0", 286 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", 287 | "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", 288 | "dev": true, 289 | "requires": { 290 | "domelementtype": "1" 291 | } 292 | }, 293 | "domutils": { 294 | "version": "1.5.1", 295 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", 296 | "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", 297 | "dev": true, 298 | "requires": { 299 | "dom-serializer": "0", 300 | "domelementtype": "1" 301 | } 302 | }, 303 | "double-ended-queue": { 304 | "version": "2.1.0-0", 305 | "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", 306 | "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=", 307 | "dev": true 308 | }, 309 | "ecdsa-sig-formatter": { 310 | "version": "1.0.11", 311 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 312 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 313 | "requires": { 314 | "safe-buffer": "^5.0.1" 315 | } 316 | }, 317 | "ee-first": { 318 | "version": "1.1.1", 319 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 320 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", 321 | "dev": true 322 | }, 323 | "encodeurl": { 324 | "version": "1.0.2", 325 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 326 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", 327 | "dev": true 328 | }, 329 | "entities": { 330 | "version": "1.0.0", 331 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", 332 | "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", 333 | "dev": true 334 | }, 335 | "escape-html": { 336 | "version": "1.0.3", 337 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 338 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", 339 | "dev": true 340 | }, 341 | "esprima": { 342 | "version": "1.0.4", 343 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", 344 | "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=", 345 | "dev": true 346 | }, 347 | "etag": { 348 | "version": "1.8.1", 349 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 350 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", 351 | "dev": true 352 | }, 353 | "eventemitter2": { 354 | "version": "0.4.14", 355 | "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", 356 | "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", 357 | "dev": true 358 | }, 359 | "exit": { 360 | "version": "0.1.2", 361 | "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", 362 | "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", 363 | "dev": true 364 | }, 365 | "express": { 366 | "version": "4.17.1", 367 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 368 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 369 | "dev": true, 370 | "requires": { 371 | "accepts": "~1.3.7", 372 | "array-flatten": "1.1.1", 373 | "body-parser": "1.19.0", 374 | "content-disposition": "0.5.3", 375 | "content-type": "~1.0.4", 376 | "cookie": "0.4.0", 377 | "cookie-signature": "1.0.6", 378 | "debug": "2.6.9", 379 | "depd": "~1.1.2", 380 | "encodeurl": "~1.0.2", 381 | "escape-html": "~1.0.3", 382 | "etag": "~1.8.1", 383 | "finalhandler": "~1.1.2", 384 | "fresh": "0.5.2", 385 | "merge-descriptors": "1.0.1", 386 | "methods": "~1.1.2", 387 | "on-finished": "~2.3.0", 388 | "parseurl": "~1.3.3", 389 | "path-to-regexp": "0.1.7", 390 | "proxy-addr": "~2.0.5", 391 | "qs": "6.7.0", 392 | "range-parser": "~1.2.1", 393 | "safe-buffer": "5.1.2", 394 | "send": "0.17.1", 395 | "serve-static": "1.14.1", 396 | "setprototypeof": "1.1.1", 397 | "statuses": "~1.5.0", 398 | "type-is": "~1.6.18", 399 | "utils-merge": "1.0.1", 400 | "vary": "~1.1.2" 401 | }, 402 | "dependencies": { 403 | "safe-buffer": { 404 | "version": "5.1.2", 405 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 406 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 407 | "dev": true 408 | } 409 | } 410 | }, 411 | "finalhandler": { 412 | "version": "1.1.2", 413 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 414 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 415 | "dev": true, 416 | "requires": { 417 | "debug": "2.6.9", 418 | "encodeurl": "~1.0.2", 419 | "escape-html": "~1.0.3", 420 | "on-finished": "~2.3.0", 421 | "parseurl": "~1.3.3", 422 | "statuses": "~1.5.0", 423 | "unpipe": "~1.0.0" 424 | } 425 | }, 426 | "findup-sync": { 427 | "version": "0.1.3", 428 | "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", 429 | "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", 430 | "dev": true, 431 | "requires": { 432 | "glob": "~3.2.9", 433 | "lodash": "~2.4.1" 434 | }, 435 | "dependencies": { 436 | "glob": { 437 | "version": "3.2.11", 438 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", 439 | "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", 440 | "dev": true, 441 | "requires": { 442 | "inherits": "2", 443 | "minimatch": "0.3" 444 | } 445 | }, 446 | "lodash": { 447 | "version": "2.4.2", 448 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", 449 | "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", 450 | "dev": true 451 | }, 452 | "minimatch": { 453 | "version": "0.3.0", 454 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", 455 | "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", 456 | "dev": true, 457 | "requires": { 458 | "lru-cache": "2", 459 | "sigmund": "~1.0.0" 460 | } 461 | } 462 | } 463 | }, 464 | "forwarded": { 465 | "version": "0.1.2", 466 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 467 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", 468 | "dev": true 469 | }, 470 | "fresh": { 471 | "version": "0.5.2", 472 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 473 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", 474 | "dev": true 475 | }, 476 | "fs-extra": { 477 | "version": "0.9.1", 478 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.9.1.tgz", 479 | "integrity": "sha1-h9v8ATg6jdzn2dVJbzYIVkiJ8VY=", 480 | "dev": true, 481 | "requires": { 482 | "jsonfile": "~1.1.0", 483 | "mkdirp": "^0.5.0", 484 | "ncp": "^0.5.1", 485 | "rimraf": "^2.2.8" 486 | } 487 | }, 488 | "getobject": { 489 | "version": "0.1.0", 490 | "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", 491 | "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", 492 | "dev": true 493 | }, 494 | "glob": { 495 | "version": "3.1.21", 496 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", 497 | "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", 498 | "dev": true, 499 | "requires": { 500 | "graceful-fs": "~1.2.0", 501 | "inherits": "1", 502 | "minimatch": "~0.2.11" 503 | }, 504 | "dependencies": { 505 | "inherits": { 506 | "version": "1.0.2", 507 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", 508 | "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", 509 | "dev": true 510 | } 511 | } 512 | }, 513 | "graceful-fs": { 514 | "version": "1.2.3", 515 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", 516 | "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", 517 | "dev": true 518 | }, 519 | "growl": { 520 | "version": "1.7.0", 521 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", 522 | "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=", 523 | "dev": true 524 | }, 525 | "grunt": { 526 | "version": "0.4.5", 527 | "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", 528 | "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", 529 | "dev": true, 530 | "requires": { 531 | "async": "~0.1.22", 532 | "coffee-script": "~1.3.3", 533 | "colors": "~0.6.2", 534 | "dateformat": "1.0.2-1.2.3", 535 | "eventemitter2": "~0.4.13", 536 | "exit": "~0.1.1", 537 | "findup-sync": "~0.1.2", 538 | "getobject": "~0.1.0", 539 | "glob": "~3.1.21", 540 | "grunt-legacy-log": "~0.1.0", 541 | "grunt-legacy-util": "~0.2.0", 542 | "hooker": "~0.2.3", 543 | "iconv-lite": "~0.2.11", 544 | "js-yaml": "~2.0.5", 545 | "lodash": "~0.9.2", 546 | "minimatch": "~0.2.12", 547 | "nopt": "~1.0.10", 548 | "rimraf": "~2.2.8", 549 | "underscore.string": "~2.2.1", 550 | "which": "~1.0.5" 551 | }, 552 | "dependencies": { 553 | "async": { 554 | "version": "0.1.22", 555 | "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", 556 | "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", 557 | "dev": true 558 | }, 559 | "iconv-lite": { 560 | "version": "0.2.11", 561 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", 562 | "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=", 563 | "dev": true 564 | }, 565 | "lodash": { 566 | "version": "0.9.2", 567 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", 568 | "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", 569 | "dev": true 570 | } 571 | } 572 | }, 573 | "grunt-contrib-jshint": { 574 | "version": "0.10.0", 575 | "resolved": "https://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-0.10.0.tgz", 576 | "integrity": "sha1-V+vMyofo8yevZkXYo8WG1IReTYE=", 577 | "dev": true, 578 | "requires": { 579 | "hooker": "~0.2.3", 580 | "jshint": "~2.5.0" 581 | } 582 | }, 583 | "grunt-legacy-log": { 584 | "version": "0.1.3", 585 | "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", 586 | "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", 587 | "dev": true, 588 | "requires": { 589 | "colors": "~0.6.2", 590 | "grunt-legacy-log-utils": "~0.1.1", 591 | "hooker": "~0.2.3", 592 | "lodash": "~2.4.1", 593 | "underscore.string": "~2.3.3" 594 | }, 595 | "dependencies": { 596 | "lodash": { 597 | "version": "2.4.2", 598 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", 599 | "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", 600 | "dev": true 601 | }, 602 | "underscore.string": { 603 | "version": "2.3.3", 604 | "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", 605 | "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", 606 | "dev": true 607 | } 608 | } 609 | }, 610 | "grunt-legacy-log-utils": { 611 | "version": "0.1.1", 612 | "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", 613 | "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", 614 | "dev": true, 615 | "requires": { 616 | "colors": "~0.6.2", 617 | "lodash": "~2.4.1", 618 | "underscore.string": "~2.3.3" 619 | }, 620 | "dependencies": { 621 | "lodash": { 622 | "version": "2.4.2", 623 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", 624 | "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=", 625 | "dev": true 626 | }, 627 | "underscore.string": { 628 | "version": "2.3.3", 629 | "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", 630 | "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", 631 | "dev": true 632 | } 633 | } 634 | }, 635 | "grunt-legacy-util": { 636 | "version": "0.2.0", 637 | "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", 638 | "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", 639 | "dev": true, 640 | "requires": { 641 | "async": "~0.1.22", 642 | "exit": "~0.1.1", 643 | "getobject": "~0.1.0", 644 | "hooker": "~0.2.3", 645 | "lodash": "~0.9.2", 646 | "underscore.string": "~2.2.1", 647 | "which": "~1.0.5" 648 | }, 649 | "dependencies": { 650 | "async": { 651 | "version": "0.1.22", 652 | "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", 653 | "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=", 654 | "dev": true 655 | }, 656 | "lodash": { 657 | "version": "0.9.2", 658 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", 659 | "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=", 660 | "dev": true 661 | } 662 | } 663 | }, 664 | "grunt-mocha-test": { 665 | "version": "0.11.0", 666 | "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.11.0.tgz", 667 | "integrity": "sha1-deQboQdZDkrL0phgklwBLkQ8oyI=", 668 | "dev": true, 669 | "requires": { 670 | "fs-extra": "~0.9.1", 671 | "hooker": "~0.2.3", 672 | "mocha": "~1.20.0" 673 | } 674 | }, 675 | "hooker": { 676 | "version": "0.2.3", 677 | "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", 678 | "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", 679 | "dev": true 680 | }, 681 | "htmlparser2": { 682 | "version": "3.8.3", 683 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", 684 | "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", 685 | "dev": true, 686 | "requires": { 687 | "domelementtype": "1", 688 | "domhandler": "2.3", 689 | "domutils": "1.5", 690 | "entities": "1.0", 691 | "readable-stream": "1.1" 692 | } 693 | }, 694 | "http-errors": { 695 | "version": "1.7.2", 696 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 697 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 698 | "dev": true, 699 | "requires": { 700 | "depd": "~1.1.2", 701 | "inherits": "2.0.3", 702 | "setprototypeof": "1.1.1", 703 | "statuses": ">= 1.5.0 < 2", 704 | "toidentifier": "1.0.0" 705 | } 706 | }, 707 | "iconv-lite": { 708 | "version": "0.4.24", 709 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 710 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 711 | "dev": true, 712 | "requires": { 713 | "safer-buffer": ">= 2.1.2 < 3" 714 | } 715 | }, 716 | "inherits": { 717 | "version": "2.0.3", 718 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 719 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 720 | "dev": true 721 | }, 722 | "ipaddr.js": { 723 | "version": "1.9.1", 724 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 725 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 726 | "dev": true 727 | }, 728 | "isarray": { 729 | "version": "0.0.1", 730 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 731 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 732 | "dev": true 733 | }, 734 | "jade": { 735 | "version": "0.26.3", 736 | "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", 737 | "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", 738 | "dev": true, 739 | "requires": { 740 | "commander": "0.6.1", 741 | "mkdirp": "0.3.0" 742 | }, 743 | "dependencies": { 744 | "commander": { 745 | "version": "0.6.1", 746 | "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", 747 | "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", 748 | "dev": true 749 | }, 750 | "mkdirp": { 751 | "version": "0.3.0", 752 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", 753 | "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", 754 | "dev": true 755 | } 756 | } 757 | }, 758 | "js-yaml": { 759 | "version": "2.0.5", 760 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", 761 | "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", 762 | "dev": true, 763 | "requires": { 764 | "argparse": "~ 0.1.11", 765 | "esprima": "~ 1.0.2" 766 | } 767 | }, 768 | "jshint": { 769 | "version": "2.5.11", 770 | "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.5.11.tgz", 771 | "integrity": "sha1-4tlYWLuxqngwAQii6BCZ+wlWIuA=", 772 | "dev": true, 773 | "requires": { 774 | "cli": "0.6.x", 775 | "console-browserify": "1.1.x", 776 | "exit": "0.1.x", 777 | "htmlparser2": "3.8.x", 778 | "minimatch": "1.0.x", 779 | "shelljs": "0.3.x", 780 | "strip-json-comments": "1.0.x", 781 | "underscore": "1.6.x" 782 | }, 783 | "dependencies": { 784 | "minimatch": { 785 | "version": "1.0.0", 786 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-1.0.0.tgz", 787 | "integrity": "sha1-4N0hILSeG3JM6NcUxSCCKpQ4V20=", 788 | "dev": true, 789 | "requires": { 790 | "lru-cache": "2", 791 | "sigmund": "~1.0.0" 792 | } 793 | }, 794 | "underscore": { 795 | "version": "1.6.0", 796 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", 797 | "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", 798 | "dev": true 799 | } 800 | } 801 | }, 802 | "jsonfile": { 803 | "version": "1.1.1", 804 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.1.1.tgz", 805 | "integrity": "sha1-2k/WrXfxolUgPqY8e8Mtwx72RDM=", 806 | "dev": true 807 | }, 808 | "jsonwebtoken": { 809 | "version": "8.5.1", 810 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 811 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 812 | "requires": { 813 | "jws": "^3.2.2", 814 | "lodash.includes": "^4.3.0", 815 | "lodash.isboolean": "^3.0.3", 816 | "lodash.isinteger": "^4.0.4", 817 | "lodash.isnumber": "^3.0.3", 818 | "lodash.isplainobject": "^4.0.6", 819 | "lodash.isstring": "^4.0.1", 820 | "lodash.once": "^4.0.0", 821 | "ms": "^2.1.1", 822 | "semver": "^5.6.0" 823 | } 824 | }, 825 | "jwa": { 826 | "version": "1.4.1", 827 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 828 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 829 | "requires": { 830 | "buffer-equal-constant-time": "1.0.1", 831 | "ecdsa-sig-formatter": "1.0.11", 832 | "safe-buffer": "^5.0.1" 833 | } 834 | }, 835 | "jws": { 836 | "version": "3.2.2", 837 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 838 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 839 | "requires": { 840 | "jwa": "^1.4.1", 841 | "safe-buffer": "^5.0.1" 842 | } 843 | }, 844 | "lodash": { 845 | "version": "4.17.15", 846 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 847 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" 848 | }, 849 | "lodash.includes": { 850 | "version": "4.3.0", 851 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 852 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 853 | }, 854 | "lodash.isboolean": { 855 | "version": "3.0.3", 856 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 857 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 858 | }, 859 | "lodash.isinteger": { 860 | "version": "4.0.4", 861 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 862 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 863 | }, 864 | "lodash.isnumber": { 865 | "version": "3.0.3", 866 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 867 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 868 | }, 869 | "lodash.isplainobject": { 870 | "version": "4.0.6", 871 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 872 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 873 | }, 874 | "lodash.isstring": { 875 | "version": "4.0.1", 876 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 877 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 878 | }, 879 | "lodash.once": { 880 | "version": "4.1.1", 881 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 882 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 883 | }, 884 | "lru-cache": { 885 | "version": "2.7.3", 886 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", 887 | "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", 888 | "dev": true 889 | }, 890 | "media-typer": { 891 | "version": "0.3.0", 892 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 893 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 894 | "dev": true 895 | }, 896 | "merge-descriptors": { 897 | "version": "1.0.1", 898 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 899 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", 900 | "dev": true 901 | }, 902 | "methods": { 903 | "version": "1.1.2", 904 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 905 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", 906 | "dev": true 907 | }, 908 | "mime": { 909 | "version": "1.6.0", 910 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 911 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 912 | "dev": true 913 | }, 914 | "mime-db": { 915 | "version": "1.44.0", 916 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 917 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", 918 | "dev": true 919 | }, 920 | "mime-types": { 921 | "version": "2.1.27", 922 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 923 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 924 | "dev": true, 925 | "requires": { 926 | "mime-db": "1.44.0" 927 | } 928 | }, 929 | "minimatch": { 930 | "version": "0.2.14", 931 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", 932 | "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", 933 | "dev": true, 934 | "requires": { 935 | "lru-cache": "2", 936 | "sigmund": "~1.0.0" 937 | } 938 | }, 939 | "minimist": { 940 | "version": "1.2.5", 941 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 942 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 943 | "dev": true 944 | }, 945 | "mkdirp": { 946 | "version": "0.5.5", 947 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 948 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 949 | "dev": true, 950 | "requires": { 951 | "minimist": "^1.2.5" 952 | } 953 | }, 954 | "mocha": { 955 | "version": "1.20.1", 956 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.20.1.tgz", 957 | "integrity": "sha1-80ODLZ/gx9l8ZPxwRI9RNt+f7Vs=", 958 | "dev": true, 959 | "requires": { 960 | "commander": "2.0.0", 961 | "debug": "*", 962 | "diff": "1.0.7", 963 | "glob": "3.2.3", 964 | "growl": "1.7.x", 965 | "jade": "0.26.3", 966 | "mkdirp": "0.3.5" 967 | }, 968 | "dependencies": { 969 | "glob": { 970 | "version": "3.2.3", 971 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", 972 | "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", 973 | "dev": true, 974 | "requires": { 975 | "graceful-fs": "~2.0.0", 976 | "inherits": "2", 977 | "minimatch": "~0.2.11" 978 | } 979 | }, 980 | "graceful-fs": { 981 | "version": "2.0.3", 982 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", 983 | "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", 984 | "dev": true 985 | }, 986 | "mkdirp": { 987 | "version": "0.3.5", 988 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", 989 | "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", 990 | "dev": true 991 | } 992 | } 993 | }, 994 | "ms": { 995 | "version": "2.1.2", 996 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 997 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 998 | }, 999 | "ncp": { 1000 | "version": "0.5.1", 1001 | "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz", 1002 | "integrity": "sha1-dDmFMW49tFkoG1hxaehFc1oFQ58=", 1003 | "dev": true 1004 | }, 1005 | "negotiator": { 1006 | "version": "0.6.2", 1007 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 1008 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", 1009 | "dev": true 1010 | }, 1011 | "node-uuid": { 1012 | "version": "1.4.8", 1013 | "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", 1014 | "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" 1015 | }, 1016 | "nopt": { 1017 | "version": "1.0.10", 1018 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 1019 | "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", 1020 | "dev": true, 1021 | "requires": { 1022 | "abbrev": "1" 1023 | } 1024 | }, 1025 | "on-finished": { 1026 | "version": "2.3.0", 1027 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1028 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1029 | "dev": true, 1030 | "requires": { 1031 | "ee-first": "1.1.1" 1032 | } 1033 | }, 1034 | "parseurl": { 1035 | "version": "1.3.3", 1036 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1037 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1038 | "dev": true 1039 | }, 1040 | "path-to-regexp": { 1041 | "version": "0.1.7", 1042 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1043 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", 1044 | "dev": true 1045 | }, 1046 | "proxy-addr": { 1047 | "version": "2.0.6", 1048 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 1049 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 1050 | "dev": true, 1051 | "requires": { 1052 | "forwarded": "~0.1.2", 1053 | "ipaddr.js": "1.9.1" 1054 | } 1055 | }, 1056 | "qs": { 1057 | "version": "6.7.0", 1058 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 1059 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", 1060 | "dev": true 1061 | }, 1062 | "range-parser": { 1063 | "version": "1.2.1", 1064 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1065 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1066 | "dev": true 1067 | }, 1068 | "raw-body": { 1069 | "version": "2.4.0", 1070 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 1071 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 1072 | "dev": true, 1073 | "requires": { 1074 | "bytes": "3.1.0", 1075 | "http-errors": "1.7.2", 1076 | "iconv-lite": "0.4.24", 1077 | "unpipe": "1.0.0" 1078 | } 1079 | }, 1080 | "readable-stream": { 1081 | "version": "1.1.14", 1082 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 1083 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 1084 | "dev": true, 1085 | "requires": { 1086 | "core-util-is": "~1.0.0", 1087 | "inherits": "~2.0.1", 1088 | "isarray": "0.0.1", 1089 | "string_decoder": "~0.10.x" 1090 | } 1091 | }, 1092 | "redis": { 1093 | "version": "2.8.0", 1094 | "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", 1095 | "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", 1096 | "dev": true, 1097 | "requires": { 1098 | "double-ended-queue": "^2.1.0-0", 1099 | "redis-commands": "^1.2.0", 1100 | "redis-parser": "^2.6.0" 1101 | } 1102 | }, 1103 | "redis-commands": { 1104 | "version": "1.5.0", 1105 | "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz", 1106 | "integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==", 1107 | "dev": true 1108 | }, 1109 | "redis-parser": { 1110 | "version": "2.6.0", 1111 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", 1112 | "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=", 1113 | "dev": true 1114 | }, 1115 | "restjs": { 1116 | "version": "github:azuqua/restjs#2536a38bda4271d9edd11392070cf9bed8c75ae4", 1117 | "from": "github:azuqua/restjs", 1118 | "dev": true, 1119 | "requires": { 1120 | "async": "0.2.10", 1121 | "qs": "^6.0.0", 1122 | "xml2js": "*" 1123 | }, 1124 | "dependencies": { 1125 | "async": { 1126 | "version": "0.2.10", 1127 | "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", 1128 | "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", 1129 | "dev": true 1130 | } 1131 | } 1132 | }, 1133 | "rimraf": { 1134 | "version": "2.2.8", 1135 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", 1136 | "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", 1137 | "dev": true 1138 | }, 1139 | "safe-buffer": { 1140 | "version": "5.2.1", 1141 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1142 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1143 | }, 1144 | "safer-buffer": { 1145 | "version": "2.1.2", 1146 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1147 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1148 | "dev": true 1149 | }, 1150 | "sax": { 1151 | "version": "1.2.4", 1152 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 1153 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", 1154 | "dev": true 1155 | }, 1156 | "semver": { 1157 | "version": "5.7.1", 1158 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1159 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 1160 | }, 1161 | "send": { 1162 | "version": "0.17.1", 1163 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 1164 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 1165 | "dev": true, 1166 | "requires": { 1167 | "debug": "2.6.9", 1168 | "depd": "~1.1.2", 1169 | "destroy": "~1.0.4", 1170 | "encodeurl": "~1.0.2", 1171 | "escape-html": "~1.0.3", 1172 | "etag": "~1.8.1", 1173 | "fresh": "0.5.2", 1174 | "http-errors": "~1.7.2", 1175 | "mime": "1.6.0", 1176 | "ms": "2.1.1", 1177 | "on-finished": "~2.3.0", 1178 | "range-parser": "~1.2.1", 1179 | "statuses": "~1.5.0" 1180 | }, 1181 | "dependencies": { 1182 | "ms": { 1183 | "version": "2.1.1", 1184 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 1185 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 1186 | "dev": true 1187 | } 1188 | } 1189 | }, 1190 | "serve-static": { 1191 | "version": "1.14.1", 1192 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 1193 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 1194 | "dev": true, 1195 | "requires": { 1196 | "encodeurl": "~1.0.2", 1197 | "escape-html": "~1.0.3", 1198 | "parseurl": "~1.3.3", 1199 | "send": "0.17.1" 1200 | } 1201 | }, 1202 | "setprototypeof": { 1203 | "version": "1.1.1", 1204 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 1205 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", 1206 | "dev": true 1207 | }, 1208 | "shelljs": { 1209 | "version": "0.3.0", 1210 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", 1211 | "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", 1212 | "dev": true 1213 | }, 1214 | "sigmund": { 1215 | "version": "1.0.1", 1216 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", 1217 | "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", 1218 | "dev": true 1219 | }, 1220 | "statuses": { 1221 | "version": "1.5.0", 1222 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1223 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", 1224 | "dev": true 1225 | }, 1226 | "string_decoder": { 1227 | "version": "0.10.31", 1228 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 1229 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 1230 | "dev": true 1231 | }, 1232 | "strip-json-comments": { 1233 | "version": "1.0.4", 1234 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", 1235 | "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", 1236 | "dev": true 1237 | }, 1238 | "toidentifier": { 1239 | "version": "1.0.0", 1240 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 1241 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", 1242 | "dev": true 1243 | }, 1244 | "type-detect": { 1245 | "version": "0.1.1", 1246 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 1247 | "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", 1248 | "dev": true 1249 | }, 1250 | "type-is": { 1251 | "version": "1.6.18", 1252 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1253 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1254 | "dev": true, 1255 | "requires": { 1256 | "media-typer": "0.3.0", 1257 | "mime-types": "~2.1.24" 1258 | } 1259 | }, 1260 | "underscore": { 1261 | "version": "1.7.0", 1262 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", 1263 | "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", 1264 | "dev": true 1265 | }, 1266 | "underscore.string": { 1267 | "version": "2.2.1", 1268 | "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", 1269 | "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=", 1270 | "dev": true 1271 | }, 1272 | "unpipe": { 1273 | "version": "1.0.0", 1274 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1275 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 1276 | "dev": true 1277 | }, 1278 | "utils-merge": { 1279 | "version": "1.0.1", 1280 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1281 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", 1282 | "dev": true 1283 | }, 1284 | "vary": { 1285 | "version": "1.1.2", 1286 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1287 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", 1288 | "dev": true 1289 | }, 1290 | "which": { 1291 | "version": "1.0.9", 1292 | "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", 1293 | "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", 1294 | "dev": true 1295 | }, 1296 | "xml2js": { 1297 | "version": "0.4.23", 1298 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", 1299 | "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", 1300 | "dev": true, 1301 | "requires": { 1302 | "sax": ">=0.6.0", 1303 | "xmlbuilder": "~11.0.0" 1304 | } 1305 | }, 1306 | "xmlbuilder": { 1307 | "version": "11.0.1", 1308 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", 1309 | "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", 1310 | "dev": true 1311 | } 1312 | } 1313 | } 1314 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jwt-redis-session", 3 | "description": "JSON Web Token session middleware backed by Redis", 4 | "version": "1.0.6", 5 | "authors": "Azuqua", 6 | "contributors": [ 7 | { 8 | "name": "Alec Embke", 9 | "url": "alec@azuqua.com" 10 | } 11 | ], 12 | "licenses": [ 13 | { 14 | "type": "MIT", 15 | "url": "http://opensource.org/licenses/mit-license.php" 16 | } 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/azuqua/jwt-redis-session" 21 | }, 22 | "scripts": { 23 | "test": "mocha test/tests.js" 24 | }, 25 | "dependencies": { 26 | "jsonwebtoken": "^8.0.0", 27 | "lodash": "^4.16.1", 28 | "node-uuid": "^1.4.1" 29 | }, 30 | "devDependencies": { 31 | "async": "^0.9.0", 32 | "body-parser": "^1.15.0", 33 | "chai": "^1.9.1", 34 | "express": "^4.13.4", 35 | "grunt": "^0.4.5", 36 | "grunt-contrib-jshint": "^0.10.0", 37 | "grunt-mocha-test": "^0.11.0", 38 | "redis": "^2.4.2", 39 | "restjs": "azuqua/restjs" 40 | }, 41 | "keywords": [ 42 | "azuqua", 43 | "jwt", 44 | "token", 45 | "redis", 46 | "session", 47 | "middleware" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /test/fixture/client.js: -------------------------------------------------------------------------------- 1 | 2 | var RestJS = require("restjs"), 3 | _ = require("lodash"); 4 | 5 | if(RestJS.Rest) 6 | RestJS = RestJS.Rest; 7 | 8 | var serializeArray = function(array, prefix){ 9 | var idx, out = []; 10 | for(idx in array){ 11 | var formatted = (prefix ? prefix : "") + "[]"; 12 | if(prefix && typeof array[idx] === "object") 13 | formatted = formatted.replace(/\[\]$/i, "[" + idx + "]"); 14 | if(typeof array[idx] === "object" && JSON.stringify(array[idx]) === "{}"){ 15 | continue; 16 | } 17 | if(array[idx] instanceof Array) 18 | out.push(serializeArray(array[idx], formatted)); 19 | else if(typeof array[idx] === "object") 20 | out.push(serializeObject(array[idx], formatted)); 21 | else 22 | out.push(encodeURIComponent(formatted) + "=" + encodeURIComponent(array[idx])); 23 | } 24 | return out.join("&"); 25 | }; 26 | 27 | var serializeObject = function(obj, prefix) { 28 | var key, out = []; 29 | for(key in obj){ 30 | var formatted = prefix ? prefix + "[" + key + "]" : key; 31 | if(obj[key] instanceof Array){ 32 | if(obj[key].length < 1) 33 | continue; 34 | out.push(serializeArray(obj[key], formatted)); 35 | }else if(typeof obj[key] === "object"){ 36 | if(JSON.stringify(obj[key]) === "{}") 37 | continue; 38 | out.push(serializeObject(obj[key], formatted)); 39 | }else{ 40 | out.push(encodeURIComponent(formatted) + "=" + encodeURIComponent(obj[key])); 41 | } 42 | } 43 | return out.join("&"); 44 | }; 45 | 46 | var client = new RestJS({ protocol: "http" }); 47 | 48 | module.exports = function(options, data, callback){ 49 | options = { 50 | host: options.host || "127.0.0.1", 51 | port: options.port || 8000, 52 | method: options.method || "get", 53 | path: options.path, 54 | headers: options.headers || {} 55 | }; 56 | if(options.method.toLowerCase() === "post"){ 57 | data = JSON.stringify(data); 58 | options.headers = _.extend(options.headers, { 59 | "Content-Type": "application/json", 60 | "Content-Length": Buffer.byteLength(data) 61 | }); 62 | }else if(options.method.toLowerCase() === "get" && data && Object.keys(data).length > 0){ 63 | options.path += (options.path.indexOf("?") > -1 ? "&" : "?") + serializeObject(data); 64 | data = null; 65 | } 66 | // server always responds with JSON for these tests 67 | client.request(options, data, function(error, resp){ 68 | if(error){ 69 | callback(error); 70 | }else{ 71 | try{ 72 | resp = JSON.parse(resp.body); 73 | }catch(e){ 74 | return callback(resp.body); 75 | } 76 | callback(null, resp); 77 | } 78 | }); 79 | }; 80 | -------------------------------------------------------------------------------- /test/fixture/server.js: -------------------------------------------------------------------------------- 1 | 2 | var http = require("http"), 3 | express = require("express"), 4 | bodyParser = require("body-parser"), 5 | _ = require("lodash"), 6 | redis = require("redis"); 7 | 8 | var client, app, server; 9 | 10 | module.exports = { 11 | 12 | addRoute: function(path, method, callback){ 13 | callback.name = callback.name; 14 | app[method](path, callback); 15 | }, 16 | 17 | removeRoute: function(path, method){ 18 | app._router.stack = _.reject(app._router.stack, function(route){ 19 | return route.route && route.route.path === path 20 | && (method ? route.route.methods[method] : true); 21 | }); 22 | }, 23 | 24 | inspect: function(callback){ 25 | return { 26 | app: app, 27 | client: client, 28 | server: server 29 | }; 30 | }, 31 | 32 | start: function(log, setup, callback){ 33 | 34 | client = redis.createClient( 35 | process.env.REDIS_PORT || 6379, 36 | process.env.REDIS_HOST || "127.0.0.1" 37 | ); 38 | 39 | client.on("error", function(e){ 40 | log("Error with redis server!", e); 41 | }); 42 | 43 | app = express(); 44 | 45 | app.use(bodyParser.urlencoded({ extended: false })); 46 | app.use(bodyParser.json()); 47 | 48 | setup(app, client, function(port){ 49 | 50 | port = port ? port : 8000; 51 | 52 | server = http.createServer(app); 53 | server.listen(port, callback); 54 | 55 | }); 56 | 57 | }, 58 | 59 | end: function(callback){ 60 | client.quit(); 61 | server.close(callback); 62 | } 63 | 64 | }; 65 | 66 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --check-leaks -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var path = require("path"), 4 | _ = require("lodash"), 5 | async = require("async"), 6 | assert = require("chai").assert, 7 | JWT = require("../index"), 8 | server = require(path.join(__dirname, "fixture/server")), 9 | request = require(path.join(__dirname, "fixture/client")); 10 | 11 | 12 | describe("JWT Redis Session Tests", function(){ 13 | 14 | describe("Default JWT usage tests", function(){ 15 | 16 | var token = null; 17 | 18 | before(function(done){ 19 | server.start(console.log, function(app, redisClient, callback){ 20 | app.use(JWT({ 21 | client: redisClient, 22 | secret: "abc123" 23 | })); 24 | callback(8000); 25 | }, done); 26 | }); 27 | 28 | after(function(done){ 29 | server.inspect().client.quit(); 30 | server.end(done); 31 | }); 32 | 33 | it("Should expose session methods to the application", function(done){ 34 | 35 | // hmm, setting a name on the function the "normal" way doesnt work... 36 | var handler = function handler(req, res){ 37 | assert.isObject(req.session, "Request session is an object"); 38 | assert.isFunction(req.session.create, "Session has create function"); 39 | assert.isFunction(req.session.touch, "Session has touch function"); 40 | assert.isFunction(req.session.reload, "Session has reload function"); 41 | assert.isFunction(req.session.update, "Session has update function"); 42 | assert.isFunction(req.session.destroy, "Session has destroy function"); 43 | assert.isFunction(req.session.toJSON, "Session has toJSON function"); 44 | res.json({}); 45 | }; 46 | 47 | server.addRoute("/ping", "get", handler); 48 | request({ path: "/ping", method: "get" }, null, function(error, resp){ 49 | assert.notOk(error, "Ping does not return an error"); 50 | assert.isObject(resp, "Ping response is an object"); 51 | server.removeRoute("/ping", "get"); 52 | done(); 53 | }); 54 | 55 | }); 56 | 57 | it("Should allow the user to create a new JWT session", function(done){ 58 | 59 | var handler = function handler(req, res){ 60 | req.session.create(function(error, token){ 61 | assert.isString(token, "Token is a string"); 62 | assert.notOk(error, "Error is null when creating token"); 63 | res.json({ token: token }); 64 | }); 65 | }; 66 | 67 | server.addRoute("/login", "get", handler); 68 | request({ method: "get", path: "/login" }, null, function(error, resp){ 69 | assert.notOk(error, "Token creation did not return an error"); 70 | assert.isObject(resp, "Response is an object"); 71 | assert.property(resp, "token", "Response contains a token property"); 72 | assert.isString(resp.token, "Token is a string"); 73 | token = resp.token; 74 | server.removeRoute("/login", "get"); 75 | done(); 76 | }); 77 | 78 | }); 79 | 80 | it("Should look for the JWT in the query, body, and headers", function(done){ 81 | 82 | var handler = function handler(req, res){ 83 | assert.isString(req.session.id, "Session has an ID"); 84 | assert.isString(req.session.jwt, "Session has a JWT"); 85 | res.json({}); 86 | }; 87 | 88 | server.addRoute("/ping", "all", handler); 89 | 90 | var testResponse = function(error, resp, callback){ 91 | assert.notOk(error, "No error thrown"); 92 | assert.isObject(resp, "Response is an object"); 93 | assert.deepEqual(resp, {}, "Response is a blank object"); 94 | callback(error); 95 | }; 96 | 97 | async.series([ 98 | function(callback){ 99 | request( 100 | { method: "get", path: "/ping" }, 101 | { accessToken: token }, 102 | _.partialRight(testResponse, callback) 103 | ); 104 | }, 105 | function(callback){ 106 | request( 107 | { method: "post", path: "/ping" }, 108 | { accessToken: token }, 109 | _.partialRight(testResponse, callback) 110 | ); 111 | }, 112 | function(callback){ 113 | request( 114 | { 115 | method: "get", 116 | path: "/ping", 117 | headers: { 118 | "x-access-token": token 119 | } 120 | }, 121 | null, 122 | _.partialRight(testResponse, callback) 123 | ); 124 | } 125 | ], function(error){ 126 | assert.notOk(error, "Async series did not return an error"); 127 | server.removeRoute("/ping"); 128 | done(); 129 | }); 130 | }); 131 | 132 | it("Should expose the correct data to the application", function(done){ 133 | 134 | var handler = function handler(req, res){ 135 | assert.isString(req.session.id, "Session has an ID"); 136 | assert.isString(req.session.jwt, "Session has a JWT"); 137 | assert.isObject(req.session.claims, "Session has a claims object"); 138 | res.json({}); 139 | }; 140 | 141 | server.addRoute("/ping", "get", handler); 142 | request({ method: "get", path: "/ping" }, { accessToken: token }, function(error, resp){ 143 | assert.notOk(error, "Ping did not return an error"); 144 | assert.isObject(resp, "Ping response is an object"); 145 | server.removeRoute("/ping", "get"); 146 | done(); 147 | }); 148 | 149 | }); 150 | 151 | it("Should allow the user to update and reload a session", function(done){ 152 | 153 | var handler = function handler(req, res){ 154 | req.session.foo = "bar"; 155 | req.session.update(function(error){ 156 | assert.notOk(error, "No error when updating session"); 157 | req.session.reload(function(err){ 158 | assert.notOk(err, "No error when reloading session"); 159 | assert.property(req.session, "foo", "Session has new foo property"); 160 | res.json(req.session.toJSON()); 161 | }); 162 | }); 163 | }; 164 | 165 | server.addRoute("/ping", "get", handler); 166 | request({ method: "get", path: "/ping" }, { accessToken: token }, function(error, resp){ 167 | assert.notOk(error, "Ping did not return an error"); 168 | assert.isObject(resp, "Ping response is an object"); 169 | assert.property(resp, "foo", "Response has new foo property"); 170 | server.removeRoute("/ping", "get"); 171 | done(); 172 | }); 173 | 174 | }); 175 | 176 | it("Should allow the user to manually update the TTL on the session", function(done){ 177 | 178 | var handler = function handler(req, res){ 179 | req.session.touch(function(error){ 180 | assert.notOk(error, "No error when updating TTL on session"); 181 | res.json({}); 182 | }); 183 | }; 184 | 185 | server.addRoute("/ping", "get", handler); 186 | request({ method: "get", path: "/ping" }, { accessToken: token }, function(error, resp){ 187 | assert.notOk(error, "Ping did not return an error"); 188 | assert.isObject(resp, "Ping response is an object"); 189 | server.removeRoute("/ping", "get"); 190 | done(); 191 | }); 192 | 193 | }); 194 | 195 | it("Should allow the user to serialize a session", function(done){ 196 | 197 | var session = { name: "Don Draper", realName: "Richard Witman" }; 198 | 199 | var handler = function handler(req, res){ 200 | _.extend(req.session, session); 201 | req.session.update(function(error){ 202 | assert.notOk(error, "No error when updating session"); 203 | res.json(req.session.toJSON()); 204 | }); 205 | }; 206 | 207 | server.addRoute("/ping", "get", handler); 208 | request({ method: "get", path: "/ping" }, { accessToken: token }, function(error, resp){ 209 | assert.notOk(error, "Ping did not return an error"); 210 | assert.isObject(resp, "Ping response is an object"); 211 | _.each(session, function(val, key){ 212 | assert.property(resp, key, "Response contains key for session property"); 213 | assert.equal(resp[key], session[key], "Response has correct value for key"); 214 | }); 215 | server.removeRoute("/ping", "get"); 216 | done(); 217 | }); 218 | 219 | }); 220 | 221 | it("Should allow the user to destroy a session", function(done){ 222 | 223 | var handler1 = function handler1(req, res){ 224 | req.session.destroy(function(error){ 225 | assert.notOk(error, "Destroy did not return an error"); 226 | res.json({}); 227 | }); 228 | }; 229 | var handler2 = function handler2(req, res){ 230 | assert.notOk(req.session.id, "Session does not have an ID"); 231 | assert.notOk(req.session.jwt, "Session does not have a JWT"); 232 | res.json(req.session.toJSON()); 233 | }; 234 | 235 | server.addRoute("/destroy", "get", handler1); 236 | server.addRoute("/ping", "get", handler2); 237 | 238 | async.series([ 239 | function(callback){ 240 | request({ method: "get", path: "/destroy" }, { accessToken: token }, function(error, resp){ 241 | assert.notOk(error, "Destroy call did not return an error"); 242 | assert.isObject(resp, "Destroy call returned an object"); 243 | callback(error); 244 | }); 245 | }, 246 | function(callback){ 247 | request({ method: "get", path: "/ping" }, { accessToken: token }, function(error, resp){ 248 | assert.notOk(error, "Ping did not return an error"); 249 | assert.isObject(resp, "Ping returned an object"); 250 | assert.deepEqual(resp, {}, "Ping returned a blank object"); 251 | callback(error); 252 | }); 253 | } 254 | ], function(error){ 255 | assert.notOk(error, "Async series did not return an error"); 256 | server.removeRoute("/destroy", "get"); 257 | server.removeRoute("/ping", "get"); 258 | done(); 259 | }); 260 | 261 | }); 262 | 263 | }); 264 | 265 | describe("Custom JWT usage tests", function(){ 266 | 267 | var customClaims = { foo: "bar" }, 268 | customRequestKey = "jwtSession", 269 | customArg = "fancyAccessToken", 270 | customRedisKeyspace = "jwt:"; 271 | 272 | var token = null; 273 | 274 | before(function(done){ 275 | server.start(console.log, function(app, redisClient, callback){ 276 | app.use(JWT({ 277 | client: redisClient, 278 | secret: "abc123", 279 | requestKey: customRequestKey, 280 | keyspace: customRedisKeyspace 281 | })); 282 | callback(8000); 283 | }, done); 284 | }); 285 | 286 | after(function(done){ 287 | server.inspect().client.quit(); 288 | server.end(done); 289 | }); 290 | 291 | it("Should allow for a custom requestKey", function(done){ 292 | 293 | var handler = function handler(req, res){ 294 | assert.property(req, customRequestKey, "Request has custom requestKey property"); 295 | assert.isObject(req[customRequestKey], "Request has custom requestKey object"); 296 | res.json({}); 297 | }; 298 | 299 | server.addRoute("/ping", "get", handler); 300 | request({ method: "get", path: "/ping" }, null, function(error, resp){ 301 | assert.notOk(error, "Ping did not return an error"); 302 | assert.isObject(resp, "Ping returned an object"); 303 | server.removeRoute("/ping", "get"); 304 | done(); 305 | }); 306 | 307 | }); 308 | 309 | it("Should allow the user to attach custom claims", function(done){ 310 | 311 | var claims = { 312 | frodo: "baggins", 313 | bilbo: "baggins" 314 | }; 315 | 316 | var handler1 = function handler1(req, res){ 317 | req[customRequestKey].create(claims, function(error, token){ 318 | assert.isString(token, "Token is a string"); 319 | assert.notOk(error, "Error is null when creating token"); 320 | res.json({ token: token }); 321 | }); 322 | }; 323 | var handler2 = function handler2(req, res){ 324 | assert.isObject(req[customRequestKey], "Request object has JWT object"); 325 | assert.isObject(req[customRequestKey].claims, "Request object has JWT claims object"); 326 | _.each(claims, function(val, key){ 327 | assert.ok(req[customRequestKey].claims[key], "Request claims key matches original claims"); 328 | assert.equal(req[customRequestKey].claims[key], val, "Request claims value matches orignal claims value"); 329 | }); 330 | res.json({}); 331 | }; 332 | 333 | server.addRoute("/login", "get", handler1); 334 | server.addRoute("/ping", "get", handler2); 335 | 336 | async.series([ 337 | function(callback){ 338 | request({ method: "get", path: "/login" }, null, function(error, resp){ 339 | assert.notOk(error, "Token creation did not return an error"); 340 | assert.isObject(resp, "Response is an object"); 341 | assert.property(resp, "token", "Response contains a token property"); 342 | assert.isString(resp.token, "Token is a string"); 343 | token = resp.token; 344 | callback(error); 345 | }); 346 | }, 347 | function(callback){ 348 | request({ method: "get", path: "/ping" }, { accessToken: token }, function(error, resp){ 349 | assert.notOk(error, "Ping did not return an error"); 350 | assert.isObject(resp, "Ping returned an object"); 351 | callback(error); 352 | }); 353 | } 354 | ], function(error){ 355 | assert.notOk(error, "Async series did not return error"); 356 | server.removeRoute("/ping", "get"); 357 | server.removeRoute("/login", "get"); 358 | done(); 359 | }); 360 | 361 | }); 362 | 363 | it("Should allow the user to use a custom request argument name", function(done){ 364 | 365 | var testResponse = function(error, resp, callback){ 366 | assert.notOk(error, "No error thrown"); 367 | assert.isObject(resp, "Response is an object"); 368 | assert.deepEqual(resp, {}, "Response is a blank object"); 369 | callback(error); 370 | }; 371 | 372 | var restartServer = function(options, callback){ 373 | server.inspect().client.quit(); 374 | server.end(function(){ 375 | server.start(console.log, function(app, redisClient, cb){ 376 | options.client = redisClient; 377 | app.use(JWT(options)); 378 | cb(8000); 379 | }, callback); 380 | }); 381 | }; 382 | 383 | var testData = {}; 384 | 385 | var handler1 = function handler1(req, res){ 386 | req.session.create(function(error, token){ 387 | assert.isString(token, "Token is a string"); 388 | assert.notOk(error, "Error is null when creating token"); 389 | res.json({ token: token }); 390 | }); 391 | }; 392 | var handler2 = function handler2(req, res){ 393 | assert.isObject(req.session, "Request object has JWT object"); 394 | assert.isString(req.session.jwt, "Request object found the token"); 395 | res.json({}); 396 | }; 397 | 398 | async.series([ 399 | function(callback){ 400 | restartServer({ 401 | secret: "abc123", 402 | requestArg: customArg 403 | }, callback); 404 | }, 405 | function(callback){ 406 | server.addRoute("/login", "get", handler1); 407 | server.addRoute("/ping", "all", handler2); 408 | callback(); 409 | }, 410 | function(callback){ 411 | request({ method: "get", path: "/login" }, null, function(error, resp){ 412 | assert.notOk(error, "Token creation did not return an error"); 413 | assert.isObject(resp, "Response is an object"); 414 | assert.property(resp, "token", "Response contains a token property"); 415 | assert.isString(resp.token, "Token is a string"); 416 | token = resp.token; 417 | testData[customArg] = token; 418 | callback(error); 419 | }); 420 | }, 421 | function(callback){ 422 | request({ method: "get", path: "/ping" }, testData, _.partialRight(testResponse, callback)); 423 | }, 424 | function(callback){ 425 | request({ method: "post", path: "/ping" }, testData, _.partialRight(testResponse, callback)); 426 | }, 427 | function(callback){ 428 | request({ 429 | method: "get", 430 | path: "/ping", 431 | headers: { "x-fancy-access-token": token } 432 | }, 433 | null, 434 | _.partialRight(testResponse, callback) 435 | ); 436 | } 437 | ], function(error){ 438 | assert.notOk(error, "Async waterfall did not return an error"); 439 | server.removeRoute("/login", "get"); 440 | server.removeRoute("/ping"); 441 | restartServer({ 442 | secret: "abc123", 443 | requestKey: customRequestKey, 444 | keyspace: customRedisKeyspace 445 | }, done); 446 | }); 447 | 448 | }); 449 | 450 | }); 451 | 452 | }); 453 | --------------------------------------------------------------------------------