{"action":"created","comment":{"url":"https://api.github.com/repos/prevoty/prevoty-express/pulls/comments/29371755","id":29371755,"diff_hunk":"@@ -0,0 +1,839 @@\n+'use strict';\n+\n+var async = require('async');\n+var express = require('express');\n+var expressVersion = require('express/package.json').version;\n+var prevoty = require('prevoty');\n+var bytes = require('bytes');\n+var urlencode = require('urlencode');\n+var parseurl = require('parseurl');\n+var cheerio = require('cheerio');\n+var secureRandom = require('secure-random');\n+\n+var version = require('../package.json').version;\n+\n+var INVALIDATION_MODE_BLOCK = \"block\";\n+var INVALIDATION_MODE_CONTINUE = \"continue\";\n+var INVALIDATION_MODE_REDIRECT_REFERRER = \"referrer\";\n+var INVALIDATION_MODE_REDIRECT_URL = \"url\";\n+\n+var DEFAULT_BODY_SIZE_LIMIT = '1mb'; // Reasonable\n+var DEFAULT_TOKEN_TTL = 600; // 10 minutes\n+var DEFAULT_TOKEN_HIDDEN_INPUT_NAME = 'pvt_token';\n+var DEFAULT_ACTION_HIDDEN_INPUT_NAME = 'pvt_action';\n+var DEFAULT_TOKEN_SKIP_INPUT_NAME = '2b375ee2-6b94-41de-8c8c-0e89d268cd88';\n+var DEFAULT_INTERNAL_REQUEST_HEADER_NAME = 'PREVOTY-CSRF-STATUS';\n+var DEFAULT_INVALIDATION_MODE = INVALIDATION_MODE_BLOCK;\n+var DEFAULT_INVALIDATION_REDIRECT_URL = '/';\n+\n+var trustedContentInitialized = false;\n+var trustedUserInitialized = false;\n+var trustedQueryInitialized = false;\n+\n+/**\n+ * Initialize all Prevoty Trusted Content settings.\n+ *\n+ * @method\n+ * @private\n+ *\n+ * @param {Object} app - The express application.\n+ * @param {Object} opts - A JSON hash of user supplied options.\n+ *\n+ * @return void\n+ */\n+function initTrustedContentSettings(app, opts) {\n+    return function(next) {\n+        if (trustedContentInitialized) {\n+            throw new Error(\"You should only call prevoty.initTrustedContent once\");\n+            next();\n+            return;\n+        }\n+\n+        // Basic credentials and configuration.\n+        app.set('prevotyTrustedContentKey', \n+                opts.trustedContentConfig.trustedContentKey);\n+\n+        // convert the limit to an integer\n+        var bodySizeLimit = \n+            opts.trustedContentConfig.bodySizeLimit;\n+        if (typeof bodySizeLimit === 'number') {\n+            app.set('prevotyBodySizeLimit', bodySizeLimit);\n+        } else if (typeof bodySizeLimit === 'string') {\n+            app.set('prevotyBodySizeLimit', bytes(bodySizeLimit));\n+        } else {\n+            app.set('prevotyBodySizeLimit', bytes(DEFAULT_BODY_SIZE_LIMIT));\n+        }\n+\n+        if (opts.trustedContentConfig.extended == undefined) {\n+            throw new Error(\"ERROR: You must define the extended Prevoty option\");\n+        } else if (opts.trustedContentConfig.extended) {\n+            app.set('prevotyExtended', true);\n+        } else {\n+            app.set('prevotyExtended', false);\n+        }\n+\n+        trustedContentInitialized = true;\n+\n+        next();\n+        return;\n+    };\n+};\n+\n+/**\n+ * Verify that all user supplied settings are properly configured.\n+ *\n+ * @method\n+ * @private\n+ *\n+ * @param {Object} app - The express application.\n+ *\n+ * @return {Function} A function which accepts a callback.\n+ */\n+function checkTrustedContentSettings(app) {\n+    return function(next) {\n+        if (!app.get('prevotyTrustedContentKey')) {\n+            throw new Error('ERROR: No Prevoty Trusted Content Configuration Key specified.');\n+        }\n+        next();\n+        return;\n+    };\n+};\n+\n+/**\n+ * Initialize all Prevoty Trusted User settings.\n+ *\n+ * @method\n+ * @private\n+ *\n+ * @param {Object} app - The express application.\n+ * @param {Object} opts - A JSON hash of user supplied options.\n+ *\n+ * @return void\n+ */\n+function initTrustedUserSettings(app, opts) {\n+    return function(next) {\n+        if (trustedUserInitialized) {\n+            throw new Error(\"You should only call prevoty.initTrustedUser once\");\n+            next();\n+            return;\n+        }\n+\n+        // Basic credentials and configuration.\n+        if (!app.get('prevotyApiKey')) {\n+            app.set('prevotyApiKey', opts.apiKey);\n+        }\n+\n+        app.set('prevotyTokenTTL', opts.trustedUserConfig.tokenTTL);\n+\n+        app.set('prevotyTokenHiddenInputName', \n+                opts.trustedUserConfig.tokenHiddenInputName);\n+\n+        app.set('prevotyActionHiddenInputName', \n+                opts.trustedUserConfig.actionHiddenInputName);\n+\n+        app.set('prevotyTokenSkipInputName', \n+                opts.trustedUserConfig.tokenSkipInputName);\n+\n+        app.set('prevotySessionIDLookupCallback',\n+                opts.trustedUserConfig.sessionIDLookupCallback);\n+\n+        if (opts.trustedUserConfig.setInternalRequestHeader) {\n+            app.set('prevotySetInternalRequestHeader', true);\n+        } else {\n+            app.set('prevotySetInternalRequestHeader', false);\n+        }\n+\n+        app.set('prevotyInternalRequestHeaderName',\n+                opts.trustedUserConfig.internalRequestHeaderName);\n+\n+        app.set('prevotyInvalidationMode',\n+                opts.trustedUserConfig.invalidationMode);\n+\n+        app.set('prevotyInvalidationRedirectURL',\n+                opts.trustedUserConfig.invalidationRedirectURL);\n+\n+        trustedUserInitialized = true;\n+\n+        next();\n+        return;\n+    };\n+};\n+\n+/**\n+ * Verify that all user supplied settings are properly configured.\n+ *\n+ * @method\n+ * @private\n+ *\n+ * @param {Object} app - The express application.\n+ *\n+ * @return {Function} A function which accepts a callback.\n+ */\n+function checkTrustedUserSettings(app) {\n+    return function(next) {\n+        if (!app.get('prevotyApiKey')) {\n+            throw new Error('ERROR: No Prevoty credentials specified.');\n+        }\n+\n+        if (!app.get('prevotySessionIDLookupCallback')) {\n+            throw new Error('ERROR: No Lookup Session Info CB specified.');\n+        }\n+\n+        if (!app.get('prevotyTokenTTL')) {\n+            app.set('prevotyTokenTTL', DEFAULT_TOKEN_TTL);\n+        }\n+\n+        if (!app.get('prevotyTokenHiddenInputName')) {\n+            app.set('prevotyTokenHiddenInputName',\n+                    DEFAULT_TOKEN_HIDDEN_INPUT_NAME);\n+        }\n+\n+        if (!app.get('prevotyActionHiddenInputName')) {\n+            app.set('prevotyActionHiddenInputName',\n+                    DEFAULT_ACTION_HIDDEN_INPUT_NAME);\n+        }\n+\n+        if (!app.get('prevotyTokenSkipInputName')) {\n+            app.set('prevotyTokenSkipInputName',\n+                    DEFAULT_TOKEN_SKIP_INPUT_NAME);\n+        }\n+\n+        if (!app.get('prevotyInternalRequestHeaderName')) {\n+            app.set('prevotyInternalRequestHeaderName',\n+                    DEFAULT_INTERNAL_REQUEST_HEADER_NAME);\n+        }\n+\n+        if (!app.get('prevotyInvalidationMode')) {\n+            app.set('prevotyInvalidationMode',\n+                    DEFAULT_INVALIDATION_MODE);\n+        }\n+\n+        if (!app.get('prevotyInvalidationRedirectURL')) {\n+            app.set('prevotyInvalidationRedirectURL',\n+                    DEFAULT_INVALIDATION_REDIRECT_URL);\n+        }\n+\n+        next();\n+        return;\n+    };\n+};\n+\n+/**\n+ * Initialize all Prevoty Trusted Query settings.\n+ *\n+ * @method\n+ * @private\n+ *\n+ * @param {Object} app - The express application.\n+ * @param {Object} opts - A JSON hash of user supplied options.\n+ *\n+ * @return void\n+ */\n+function initTrustedQuerySettings(app, opts) {\n+    return function(next) {\n+        if (trustedQueryInitialized) {\n+            throw new Error(\"You should only call prevoty.initTrustedQuery once\");\n+            next();\n+            return;\n+        }\n+\n+        app.set('prevotyTrustedQueryKey', \n+                opts.trustedQueryConfig.trustedQueryKey);\n+\n+        app.set('prevotyMysqlConnection', \n+                opts.trustedQueryConfig.mysqlConnection);\n+\n+        trustedQueryInitialized = true;\n+\n+        next();\n+        return;\n+    };\n+};\n+\n+/**\n+ * Verify that all user supplied settings are properly configured.\n+ *\n+ * @method\n+ * @private\n+ *\n+ * @param {Object} app - The express application.\n+ *\n+ * @return {Function} A function which accepts a callback.\n+ */\n+function checkTrustedQuerySettings(app) {\n+    return function(next) {\n+\n+        if (!app.get('prevotyMysqlConnection')) {\n+            throw new Error('ERROR: No Mysql Connection specified.');\n+        }\n+\n+        next();\n+        return;\n+    };\n+};\n+\n+/**\n+ * Initialize the Prevoty client.\n+ *\n+ * @method\n+ * @private\n+ *\n+ * @param {Object} app - The express application.\n+ *\n+ * @return {Function} A function which accepts a callback.\n+ */\n+function initClient(app, opts) {\n+    return function(next) {\n+        var connection;\n+        var userAgent = 'prevoty-express/' + version + ' ' + 'express/' + expressVersion;\n+\n+        // Basic credentials and configuration.\n+        app.set('prevotyApiKey', opts.apiKey);\n+        if (!app.get('prevotyApiKey')) {\n+            throw new Error('ERROR: No Prevoty credentials specified.');\n+        }\n+\n+        app.set('prevotyClient', \n+                new prevoty.client({key: app.get('prevotyApiKey')}));\n+        app.get('prevotyClient').verify(function(err, verified) {\n+            if (err) {\n+                throw new Error('ERROR: Prevoty client initialization failed: ' + err);\n+            }\n+            app.get('prevotyClient').info(function(err, info) {\n+                if (err) {\n+                    throw new Error('ERROR: Prevoty key info lookup failed: ' + err);\n+                }\n+                console.log('Prevoty Key info:', info);\n+                next();\n+                return;\n+            });\n+        });\n+    };\n+}\n+\n+function streamOnWrapper(eventName, cb) {\n+    this.prevoty.storedOnCallbacks[eventName] = cb;\n+    if ((true == this.prevoty.streamEnded) && \n+        ('data' == eventName)) {\n+        cb(this.prevoty.filteredBody);\n+    }\n+}\n+\n+function streamOnceWrapper(eventName, cb) {\n+    this.prevoty.storedOnceCallbacks[eventName] = cb;\n+    if ((true == this.prevoty.streamEnded) && \n+        ('end' == eventName)) {\n+        cb();\n+    }\n+}\n+\n+function streamRemoveListenerWrapper(eventName, cb) {\n+    this.prevoty.storedRemoveListenerCallbacks[eventName] = cb;\n+}\n+\n+function generateNonce() {\n+    var ndx;\n+    var nonce = '';\n+    var BYTES_IN_NONCE = 9;\n+\n+    var bytes = secureRandom(BYTES_IN_NONCE, {type: 'Uint8Array'});\n+    for (ndx = 0; ndx < BYTES_IN_NONCE; ndx++) {\n+        nonce += bytes[ndx.toString()].toString();\n+    }\n+    return nonce;\n+\n+// Alternative simpler method if we ever need it\n+//    var hrTime = process.hrtime()[1];\n+//    console.dir(hrTime);\n+//    return hrTime.toString();\n+}\n+\n+// If there's AJAX stuff going on we can't be guaranteed that an action\n+// attribute will be present.  So we'll just generate a random action\n+// string for every form and hide it as a hidden field\n+function generateAction() {\n+    var ndx;\n+    var action = '';\n+    var BYTES_IN_ACTION = 20;\n+\n+    var bytes = secureRandom(BYTES_IN_ACTION, {type: 'Uint8Array'});\n+    for (ndx = 0; ndx < BYTES_IN_ACTION; ndx++) {\n+        action += bytes[ndx.toString()].toString();\n+    }\n+    return action;\n+}\n+\n+function addTokenToForm(res, form) {\n+    return function(cb) {\n+        var app = res.app\n+        var client = app.get('prevotyClient');\n+        var ttl = app.get('prevotyTokenTTL');\n+        var tokenSkipInputName = app.get('prevotyTokenSkipInputName');\n+        var tokenHiddenInputName = app.get('prevotyTokenHiddenInputName');\n+        var actionHiddenInputName = app.get('prevotyActionHiddenInputName');\n+        var method = form.attr('method');\n+\n+        if (method.toUpperCase() !== 'POST') {\n+            // We only insert tokens for POST forms\n+            cb(null);\n+            return;\n+        }\n+\n+        var action = generateAction();\n+        var nonce = generateNonce();\n+        var callableAction = action+nonce;\n+\n+        client.generate_token(res.prevoty.sessionID, callableAction, ttl, function(err, response) {\n+            if (err) {\n+                // Fail gracefully\n+                console.log(\"ERROR in Prevoty generate_token\");\n+                form.prepend('<input type=\\'hidden\\' name=\\'' + \n+                             tokenSkipInputName + '\\' value=\\'1\\'>');\n+                cb(null);\n+                return;\n+            } else {\n+                form.prepend('<input type=\\'hidden\\' name=\\'' + \n+                             tokenHiddenInputName + '\\' value=\\'' + \n+                             response.token + '\\'>');\n+                form.prepend('<input type=\\'hidden\\' name=\\'' + \n+                             actionHiddenInputName + '\\' value=\\'' + \n+                             callableAction + '\\'>');\n+                cb(null);\n+                return;\n+            }\n+        });\n+    }\n+}\n+\n+function resRenderWrapper(view, options, fn) {\n+    var req = this.req;\n+    var res = this;\n+    \n+    var renderCB = function(err, html) {\n+        var tasks = [];\n+\n+        if (err) {\n+            console.dir(\"Prevoty resRenderWrapper.renderCB ERROR found\");\n+            return req.next(err);\n+        }\n+\n+        var $ = cheerio.load(html,\n+                             { normalizeWhitespace: true,\n+                               lowerCaseTags : true,\n+                               lowerCaseAttributeNames : true,\n+                               xmlMode: false,\n+                               decodeEntities : false});\n+\n+        $('form').each(function(i, elem) {\n+            var form = $(this);\n+            tasks.push(addTokenToForm(res, form));\n+        });\n+        async.series(tasks, function(err, results) {\n+            res.send($.html());\n+            res.end();\n+        });\n+    }\n+\n+    this.prevoty.resInitialRenderFunc.call(this, view, options, renderCB);\n+}\n+\n+/**\n+ * Initialize the Prevoty middleware.\n+ *\n+ * @method\n+ *\n+ * @param {Object} app - The express application.\n+ * @param {object} opts - A JSON hash of user supplied options.\n+ *\n+ * @return void\n+ */\n+module.exports.initPrevoty = function(app, opts) {\n+    var router = express.Router();\n+\n+    opts = opts || {};\n+\n+    var initArray = [];\n+\n+    if (opts.trustedContentConfig) {\n+        initArray.push(initTrustedContentSettings(app, opts));\n+        initArray.push(checkTrustedContentSettings(app));\n+    } \n+\n+    if (opts.trustedUserConfig) {\n+        initArray.push(initTrustedUserSettings(app, opts));\n+        initArray.push(checkTrustedUserSettings(app));\n+    }\n+\n+    if (opts.trustedQueryConfig) {\n+        initArray.push(initTrustedQuerySettings(app, opts));\n+        initArray.push(checkTrustedQuerySettings(app));\n+    }\n+\n+    initArray.push(initClient(app, opts));\n+    async.series(initArray);\n+\n+    // This works by rewriting the req.url, req.originalUrl,\n+    // as well as req._parsedUrl and req._parsedOriginalUrl\n+    //\n+    // We do store the original in req.prevoty.originalUrl in case we need it.\n+    var contentQueryMiddleware = function(req, res, next) {\n+        req.prevoty = {};\n+\n+        // I wonder if we could skip all this just by overriding the\n+        // req.params(), req.query(), and req.body() functions or if other\n+        // libraries look down into this original URL stuff directly.  Nastier\n+        // this way but probably safer.\n+        if (req.url) {\n+            req.prevoty.originalUrl = req.url;\n+        } else if (req.originalUrl) {\n+            req.prevoty.originalUrl = req.originalUrl;\n+        }\n+\n+        var parsedUrl = parseurl(req);\n+        if (parsedUrl.query) {\n+            app.get('prevotyClient').filter_content(\n+                urlencode.decode(parsedUrl.query), \n+                app.get('prevotyTrustedContentKey'), \n+                function (err, filtered) {\n+                    if (err) {\n+                        throw new Error('Error with Prevoty filter_content: ' + err.toString());\n+                    } else {\n+                        req.url = req.originalUrl = \n+                            parsedUrl.pathname + '?' + filtered.output;\n+                        parsedUrl = parseurl(req);\n+                        req._parsedUrl = req._parsedOriginalUrl = \n+                            parsedUrl;\n+                        next();\n+                        return;\n+                    }\n+                }\n+            );\n+        } else {\n+            next();\n+            return;\n+        }\n+    }\n+\n+    var contentBodyMiddleware = function(req, res, next) {\n+        req.prevoty.rawBody = '';\n+        req.prevoty.receivedBytes = 0;\n+        req.prevoty.receivedChunks = [];\n+        req.prevoty.streamEnded = false;\n+\n+        req.prevoty.reqInitialOnFunc = req.on;\n+        req.prevoty.reqInitialOnceFunc = req.once;\n+        req.prevoty.reqInitialRemoveListenerFunc = req.removeListener;\n+\n+        req.prevoty.storedOnCallbacks = \n+            {'data' : [], 'end' : [], 'error' : [], 'close' : []};\n+        req.prevoty.storedOnceCallbacks = \n+            {'data' : [], 'end' : [], 'error' : [], 'close' : []};\n+        req.prevoty.storedRemoveListenerCallbacks =\n+            {'data' : [], 'end' : [], 'error' : [], 'close' : []};\n+\n+        req.on = streamOnWrapper;\n+        req.once = streamOnceWrapper;\n+        req.removeListener = streamRemoveListenerWrapper;\n+\n+        // Inspired by raw-body\n+        req.prevoty.reqInitialOnFunc.call(req, 'data', prevotyOnData)\n+        req.prevoty.reqInitialOnceFunc.call(req, 'end', prevotyOnEnd)\n+        req.prevoty.reqInitialOnceFunc.call(req, 'error', prevotyOnError)\n+        req.prevoty.reqInitialOnceFunc.call(req, 'close', prevotyOnClose)\n+\n+        function prevotyOnData(chunk) {\n+            req.prevoty.receivedBytes += chunk.length;\n+            req.prevoty.receivedChunks.push(chunk);\n+\n+            if (req.prevoty.receivedBytes > app.get('prevotyBodySizeLimit')) {\n+                // !!! Might be some better way to handle this\n+                // Look to raw-body for inspiration\n+                throw new Error('request entity too large');\n+            }\n+        }\n+\n+        function prevotyOnEnd() {\n+            // !!! Might be some better way to handle these errors\n+            // Look to raw-body for inspiration\n+            if (null != req.length && req.prevoty.receivedBytes != req.length) {\n+                throw new Error('request size did not match content length');\n+            } else {\n+                req.prevoty.rawBody = Buffer.concat(req.prevoty.receivedChunks)\n+                req.prevoty.streamEnded = true;\n+                if (req.prevoty.rawBody.length > 0) {\n+                    app.get('prevotyClient').filter_content(\n+                        urlencode.decode(req.prevoty.rawBody), \n+                        app.get('prevotyTrustedContentKey'), \n+                        function (err, filtered) {\n+                            if (err) {\n+                                throw new Error('Error with Prevoty filter_content: ' + err.toString);\n+                            } else {\n+                                // Prevoty library will urlencode for us so\n+                                // there's no need for us to reverse the\n+                                // urldecode\n+                                //console.dir('req.prevoty.rawBody: ' + req.prevoty.rawBody);\n+                                req.prevoty.filteredBody = filtered.output;\n+                                //console.dir(filtered);\n+\n+                                // Fuzz this because body-parser.read()\n+                                // collects the length and sends it to raw-body\n+                                // for verification.\n+                                //req.length = req.prevoty.filteredBody.length;\n+                                if (req.headers['content-length'] != null) {\n+                                    req.headers['content-length'] = req.prevoty.filteredBody.length;\n+                                }\n+                                req.prevoty.storedOnCallbacks['end'].forEach(function(cb, cbIndex, cbArray) {\n+                                    cb();\n+                                });\n+                                // This is some kind of dirty hack, there must be some library\n+                                // function that resets req.on (but not req.once etc) by the\n+                                // time we get here\n+                                req.on = streamOnWrapper;\n+                                next();\n+                                return;\n+                            }\n+                        }\n+                    );\n+                } else {\n+                    req.prevoty.storedOnCallbacks['end'].forEach(function(cb, cbIndex, cbArray) {\n+                        cb();\n+                    });\n+                    // This is some kind of dirty hack, there must be some library\n+                    // function that resets req.on (but not req.once etc) by the\n+                    // time we get here\n+                    req.on = streamOnWrapper;\n+                    next();\n+                    return;\n+                }\n+            }\n+        }\n+\n+        function prevotyOnError(err) {\n+            req.prevoty.storedOnCallbacks['error'].forEach(function(cb, cbIndex, cbArray) {\n+                cb(err);\n+            });\n+        }\n+\n+        function prevotyOnClose(err) {\n+            req.prevoty.storedOnCallbacks['close'].forEach(function(cb, cbIndex, cbArray) {\n+                cb(err);\n+            });\n+        }\n+    };\n+\n+    var userTokenGenerationMiddleware = function(req, res, next) {\n+        res.prevoty = {}\n+\n+        // Passing a null this pointer as the first argument since javascript\n+        // now thinks this function is part of an object\n+        res.prevoty.sessionID = \n+            app.get('prevotySessionIDLookupCallback').call(null, req);\n+\n+        res.prevoty.resInitialRenderFunc = res.render;\n+        res.prevoty.appInitialRenderFunc = req.app.render;\n+\n+        res.render = resRenderWrapper;\n+        //res.send = function() {throw new Error(\"res.send\");};\n+\n+        next();","path":"lib/prevoty.js","position":638,"original_position":638,"commit_id":"0c7ac1a680b30de3257e5d37196a84599a1adcd2","original_commit_id":"0c7ac1a680b30de3257e5d37196a84599a1adcd2","user":{"login":"ctindel","id":1863749,"avatar_url":"https://avatars.githubusercontent.com/u/1863749?v=3","gravatar_id":"","url":"https://api.github.com/users/ctindel","html_url":"https://github.com/ctindel","followers_url":"https://api.github.com/users/ctindel/followers","following_url":"https://api.github.com/users/ctindel/following{/other_user}","gists_url":"https://api.github.com/users/ctindel/gists{/gist_id}","starred_url":"https://api.github.com/users/ctindel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ctindel/subscriptions","organizations_url":"https://api.github.com/users/ctindel/orgs","repos_url":"https://api.github.com/users/ctindel/repos","events_url":"https://api.github.com/users/ctindel/events{/privacy}","received_events_url":"https://api.github.com/users/ctindel/received_events","type":"User","site_admin":false},"body":"done","created_at":"2015-04-29T19:32:52Z","updated_at":"2015-04-29T19:32:52Z","html_url":"https://github.com/prevoty/prevoty-express/pull/1#discussion_r29371755","pull_request_url":"https://api.github.com/repos/prevoty/prevoty-express/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/prevoty/prevoty-express/pulls/comments/29371755"},"html":{"href":"https://github.com/prevoty/prevoty-express/pull/1#discussion_r29371755"},"pull_request":{"href":"https://api.github.com/repos/prevoty/prevoty-express/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/prevoty/prevoty-express/pulls/1","id":34219249,"html_url":"https://github.com/prevoty/prevoty-express/pull/1","diff_url":"https://github.com/prevoty/prevoty-express/pull/1.diff","patch_url":"https://github.com/prevoty/prevoty-express/pull/1.patch","issue_url":"https://api.github.com/repos/prevoty/prevoty-express/issues/1","number":1,"state":"open","locked":false,"title":"Initial Review","user":{"login":"jrozner","id":26415,"avatar_url":"https://avatars.githubusercontent.com/u/26415?v=3","gravatar_id":"","url":"https://api.github.com/users/jrozner","html_url":"https://github.com/jrozner","followers_url":"https://api.github.com/users/jrozner/followers","following_url":"https://api.github.com/users/jrozner/following{/other_user}","gists_url":"https://api.github.com/users/jrozner/gists{/gist_id}","starred_url":"https://api.github.com/users/jrozner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jrozner/subscriptions","organizations_url":"https://api.github.com/users/jrozner/orgs","repos_url":"https://api.github.com/users/jrozner/repos","events_url":"https://api.github.com/users/jrozner/events{/privacy}","received_events_url":"https://api.github.com/users/jrozner/received_events","type":"User","site_admin":false},"body":"This is to do an initial code review of the express plugin and get it all cleaned up for general distribution.","created_at":"2015-04-27T23:03:06Z","updated_at":"2015-04-29T19:32:52Z","closed_at":null,"merged_at":null,"merge_commit_sha":"288dafe62fd6c95a6e2c9e8184eec374afa77f51","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/prevoty/prevoty-express/pulls/1/commits","review_comments_url":"https://api.github.com/repos/prevoty/prevoty-express/pulls/1/comments","review_comment_url":"https://api.github.com/repos/prevoty/prevoty-express/pulls/comments{/number}","comments_url":"https://api.github.com/repos/prevoty/prevoty-express/issues/1/comments","statuses_url":"https://api.github.com/repos/prevoty/prevoty-express/statuses/0c7ac1a680b30de3257e5d37196a84599a1adcd2","head":{"label":"prevoty:tc/initial_review","ref":"tc/initial_review","sha":"0c7ac1a680b30de3257e5d37196a84599a1adcd2","user":{"login":"prevoty","id":4421556,"avatar_url":"https://avatars.githubusercontent.com/u/4421556?v=3","gravatar_id":"","url":"https://api.github.com/users/prevoty","html_url":"https://github.com/prevoty","followers_url":"https://api.github.com/users/prevoty/followers","following_url":"https://api.github.com/users/prevoty/following{/other_user}","gists_url":"https://api.github.com/users/prevoty/gists{/gist_id}","starred_url":"https://api.github.com/users/prevoty/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/prevoty/subscriptions","organizations_url":"https://api.github.com/users/prevoty/orgs","repos_url":"https://api.github.com/users/prevoty/repos","events_url":"https://api.github.com/users/prevoty/events{/privacy}","received_events_url":"https://api.github.com/users/prevoty/received_events","type":"Organization","site_admin":false},"repo":{"id":32226353,"name":"prevoty-express","full_name":"prevoty/prevoty-express","owner":{"login":"prevoty","id":4421556,"avatar_url":"https://avatars.githubusercontent.com/u/4421556?v=3","gravatar_id":"","url":"https://api.github.com/users/prevoty","html_url":"https://github.com/prevoty","followers_url":"https://api.github.com/users/prevoty/followers","following_url":"https://api.github.com/users/prevoty/following{/other_user}","gists_url":"https://api.github.com/users/prevoty/gists{/gist_id}","starred_url":"https://api.github.com/users/prevoty/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/prevoty/subscriptions","organizations_url":"https://api.github.com/users/prevoty/orgs","repos_url":"https://api.github.com/users/prevoty/repos","events_url":"https://api.github.com/users/prevoty/events{/privacy}","received_events_url":"https://api.github.com/users/prevoty/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/prevoty/prevoty-express","description":" Build simple, secure web applications with Prevoty and Express!","fork":false,"url":"https://api.github.com/repos/prevoty/prevoty-express","forks_url":"https://api.github.com/repos/prevoty/prevoty-express/forks","keys_url":"https://api.github.com/repos/prevoty/prevoty-express/keys{/key_id}","collaborators_url":"https://api.github.com/repos/prevoty/prevoty-express/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/prevoty/prevoty-express/teams","hooks_url":"https://api.github.com/repos/prevoty/prevoty-express/hooks","issue_events_url":"https://api.github.com/repos/prevoty/prevoty-express/issues/events{/number}","events_url":"https://api.github.com/repos/prevoty/prevoty-express/events","assignees_url":"https://api.github.com/repos/prevoty/prevoty-express/assignees{/user}","branches_url":"https://api.github.com/repos/prevoty/prevoty-express/branches{/branch}","tags_url":"https://api.github.com/repos/prevoty/prevoty-express/tags","blobs_url":"https://api.github.com/repos/prevoty/prevoty-express/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/prevoty/prevoty-express/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/prevoty/prevoty-express/git/refs{/sha}","trees_url":"https://api.github.com/repos/prevoty/prevoty-express/git/trees{/sha}","statuses_url":"https://api.github.com/repos/prevoty/prevoty-express/statuses/{sha}","languages_url":"https://api.github.com/repos/prevoty/prevoty-express/languages","stargazers_url":"https://api.github.com/repos/prevoty/prevoty-express/stargazers","contributors_url":"https://api.github.com/repos/prevoty/prevoty-express/contributors","subscribers_url":"https://api.github.com/repos/prevoty/prevoty-express/subscribers","subscription_url":"https://api.github.com/repos/prevoty/prevoty-express/subscription","commits_url":"https://api.github.com/repos/prevoty/prevoty-express/commits{/sha}","git_commits_url":"https://api.github.com/repos/prevoty/prevoty-express/git/commits{/sha}","comments_url":"https://api.github.com/repos/prevoty/prevoty-express/comments{/number}","issue_comment_url":"https://api.github.com/repos/prevoty/prevoty-express/issues/comments{/number}","contents_url":"https://api.github.com/repos/prevoty/prevoty-express/contents/{+path}","compare_url":"https://api.github.com/repos/prevoty/prevoty-express/compare/{base}...{head}","merges_url":"https://api.github.com/repos/prevoty/prevoty-express/merges","archive_url":"https://api.github.com/repos/prevoty/prevoty-express/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/prevoty/prevoty-express/downloads","issues_url":"https://api.github.com/repos/prevoty/prevoty-express/issues{/number}","pulls_url":"https://api.github.com/repos/prevoty/prevoty-express/pulls{/number}","milestones_url":"https://api.github.com/repos/prevoty/prevoty-express/milestones{/number}","notifications_url":"https://api.github.com/repos/prevoty/prevoty-express/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/prevoty/prevoty-express/labels{/name}","releases_url":"https://api.github.com/repos/prevoty/prevoty-express/releases{/id}","created_at":"2015-03-14T18:36:29Z","updated_at":"2015-04-27T22:58:18Z","pushed_at":"2015-04-27T23:03:06Z","git_url":"git://github.com/prevoty/prevoty-express.git","ssh_url":"git@github.com:prevoty/prevoty-express.git","clone_url":"https://github.com/prevoty/prevoty-express.git","svn_url":"https://github.com/prevoty/prevoty-express","homepage":null,"size":220,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"prevoty:master","ref":"master","sha":"e57fd5d06fe9320f57a54ac1d694a1f517f30944","user":{"login":"prevoty","id":4421556,"avatar_url":"https://avatars.githubusercontent.com/u/4421556?v=3","gravatar_id":"","url":"https://api.github.com/users/prevoty","html_url":"https://github.com/prevoty","followers_url":"https://api.github.com/users/prevoty/followers","following_url":"https://api.github.com/users/prevoty/following{/other_user}","gists_url":"https://api.github.com/users/prevoty/gists{/gist_id}","starred_url":"https://api.github.com/users/prevoty/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/prevoty/subscriptions","organizations_url":"https://api.github.com/users/prevoty/orgs","repos_url":"https://api.github.com/users/prevoty/repos","events_url":"https://api.github.com/users/prevoty/events{/privacy}","received_events_url":"https://api.github.com/users/prevoty/received_events","type":"Organization","site_admin":false},"repo":{"id":32226353,"name":"prevoty-express","full_name":"prevoty/prevoty-express","owner":{"login":"prevoty","id":4421556,"avatar_url":"https://avatars.githubusercontent.com/u/4421556?v=3","gravatar_id":"","url":"https://api.github.com/users/prevoty","html_url":"https://github.com/prevoty","followers_url":"https://api.github.com/users/prevoty/followers","following_url":"https://api.github.com/users/prevoty/following{/other_user}","gists_url":"https://api.github.com/users/prevoty/gists{/gist_id}","starred_url":"https://api.github.com/users/prevoty/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/prevoty/subscriptions","organizations_url":"https://api.github.com/users/prevoty/orgs","repos_url":"https://api.github.com/users/prevoty/repos","events_url":"https://api.github.com/users/prevoty/events{/privacy}","received_events_url":"https://api.github.com/users/prevoty/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/prevoty/prevoty-express","description":" Build simple, secure web applications with Prevoty and Express!","fork":false,"url":"https://api.github.com/repos/prevoty/prevoty-express","forks_url":"https://api.github.com/repos/prevoty/prevoty-express/forks","keys_url":"https://api.github.com/repos/prevoty/prevoty-express/keys{/key_id}","collaborators_url":"https://api.github.com/repos/prevoty/prevoty-express/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/prevoty/prevoty-express/teams","hooks_url":"https://api.github.com/repos/prevoty/prevoty-express/hooks","issue_events_url":"https://api.github.com/repos/prevoty/prevoty-express/issues/events{/number}","events_url":"https://api.github.com/repos/prevoty/prevoty-express/events","assignees_url":"https://api.github.com/repos/prevoty/prevoty-express/assignees{/user}","branches_url":"https://api.github.com/repos/prevoty/prevoty-express/branches{/branch}","tags_url":"https://api.github.com/repos/prevoty/prevoty-express/tags","blobs_url":"https://api.github.com/repos/prevoty/prevoty-express/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/prevoty/prevoty-express/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/prevoty/prevoty-express/git/refs{/sha}","trees_url":"https://api.github.com/repos/prevoty/prevoty-express/git/trees{/sha}","statuses_url":"https://api.github.com/repos/prevoty/prevoty-express/statuses/{sha}","languages_url":"https://api.github.com/repos/prevoty/prevoty-express/languages","stargazers_url":"https://api.github.com/repos/prevoty/prevoty-express/stargazers","contributors_url":"https://api.github.com/repos/prevoty/prevoty-express/contributors","subscribers_url":"https://api.github.com/repos/prevoty/prevoty-express/subscribers","subscription_url":"https://api.github.com/repos/prevoty/prevoty-express/subscription","commits_url":"https://api.github.com/repos/prevoty/prevoty-express/commits{/sha}","git_commits_url":"https://api.github.com/repos/prevoty/prevoty-express/git/commits{/sha}","comments_url":"https://api.github.com/repos/prevoty/prevoty-express/comments{/number}","issue_comment_url":"https://api.github.com/repos/prevoty/prevoty-express/issues/comments{/number}","contents_url":"https://api.github.com/repos/prevoty/prevoty-express/contents/{+path}","compare_url":"https://api.github.com/repos/prevoty/prevoty-express/compare/{base}...{head}","merges_url":"https://api.github.com/repos/prevoty/prevoty-express/merges","archive_url":"https://api.github.com/repos/prevoty/prevoty-express/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/prevoty/prevoty-express/downloads","issues_url":"https://api.github.com/repos/prevoty/prevoty-express/issues{/number}","pulls_url":"https://api.github.com/repos/prevoty/prevoty-express/pulls{/number}","milestones_url":"https://api.github.com/repos/prevoty/prevoty-express/milestones{/number}","notifications_url":"https://api.github.com/repos/prevoty/prevoty-express/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/prevoty/prevoty-express/labels{/name}","releases_url":"https://api.github.com/repos/prevoty/prevoty-express/releases{/id}","created_at":"2015-03-14T18:36:29Z","updated_at":"2015-04-27T22:58:18Z","pushed_at":"2015-04-27T23:03:06Z","git_url":"git://github.com/prevoty/prevoty-express.git","ssh_url":"git@github.com:prevoty/prevoty-express.git","clone_url":"https://github.com/prevoty/prevoty-express.git","svn_url":"https://github.com/prevoty/prevoty-express","homepage":null,"size":220,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/prevoty/prevoty-express/pulls/1"},"html":{"href":"https://github.com/prevoty/prevoty-express/pull/1"},"issue":{"href":"https://api.github.com/repos/prevoty/prevoty-express/issues/1"},"comments":{"href":"https://api.github.com/repos/prevoty/prevoty-express/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/prevoty/prevoty-express/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/prevoty/prevoty-express/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/prevoty/prevoty-express/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/prevoty/prevoty-express/statuses/0c7ac1a680b30de3257e5d37196a84599a1adcd2"}}}}