├── .gitignore ├── test ├── testutils │ ├── index.js │ ├── server.js │ └── request.js └── index.js ├── package.json ├── .travis.yml ├── LICENSE ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /test/testutils/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | module.exports = { 5 | request: require('./request'), 6 | server: require('./server') 7 | }; 8 | 9 | -------------------------------------------------------------------------------- /test/testutils/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a full HTTP server for testing purposes. 3 | * 4 | */ 5 | 6 | var restify = require('restify'); 7 | var restifyCookies = require('../../index'); 8 | 9 | 10 | module.exports = function (port, fn){ 11 | var server = restify.createServer(); 12 | 13 | server.use(restifyCookies.parse); 14 | server.listen(port, function(){ 15 | fn(); 16 | }); 17 | 18 | return server; 19 | }; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restify-cookies", 3 | "version": "0.2.6", 4 | "description": "Adds cookie parsing/setting to restify", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node node_modules/.bin/mocha test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/nathschmidt/restify-cookies" 12 | }, 13 | "keywords": [ 14 | "restify", 15 | "cookies", 16 | "cookie" 17 | ], 18 | "author": "Nathan Schmidt", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/nathschmidt/restify-cookies/issues" 22 | }, 23 | "homepage": "https://github.com/nathschmidt/restify-cookies", 24 | "dependencies": { 25 | "cookie": "^0.4.0" 26 | }, 27 | "devDependencies": { 28 | "expect.js": "^0.3.1", 29 | "mocha": "^2.2.4", 30 | "restify": "latest" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node.js 2 | node_js: 3 | - '4.4.0' 4 | install: 5 | - npm install 6 | script: 7 | - npm test 8 | - echo "Testing geriatric version of restify (v4)" && npm install restify@4 && npm test 9 | - echo "Testing less geriatric version of restify (v6)" && npm install restify@6 && npm test 10 | deploy: 11 | provider: npm 12 | email: nathan@cascade-softworks.com 13 | api_key: 14 | secure: RRq+Hl9s70V5qcqB4iR+qZIQJhJCeI4jBVK/9MjFe6CaVscPSFMilEsWco2XZSbFmdsAdXq2rczi9AHWYou4X/ut+3PnU0SeWqBkYPd8BF0brZxXPazgX6uuQZ4N3QdXIozk0Naa/X2e1axLRMay5bo9jdHm/0XLjvwtRW5r/i6mzuxP6DmbaYufKxC7BsIsYI02M8J5Weay2TflDcC+V1dHuH/2A7jcqntBEqdAsCzszqsOfsd5uWHmhkv4BcEVpxzrmofCShn4BqLJDCNegdnNE4QO/t2EkONF7pZtPMC1XmRbSl2I5imqBDhYN9WV4hk1nm2L53GRliC6JQMwsbTUWJRAEsXgalgOXk8sbUWfu0Cl8bDMy+RKYl8aa+pbXfLC3BaWOQgZdB0WLeVlTKoTwVFU/SKKxtQVdEqDwjjoo1wbW9vYlpjtWmY5/6r3W/84Sn9L6RbmFwXct6QD6g5naQ2nGcVSkvuu0jAHpQrl3iyU2VMhCf4/G+ag9yRFKNdfyNErAh7odKrJvS2nLEWtP3IAHTtpZ2qlFhpWnajTGFsEFdUXxqR56s3hguNQR848/IgK+/hjt7rOcT6bSd6uDFePURQUAMbhl6i47FMz+VLYKGlkK55syx/Atn/dKZKEXsDkQHuJcj2FRk+HvILY9Jl79r12TFK4VEq4Qxk= 15 | on: 16 | tags: true 17 | repo: nathschmidt/restify-cookies 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 nathschmidt 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 | -------------------------------------------------------------------------------- /test/testutils/request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Methods for making internal http requests 4 | * 5 | */ 6 | 7 | var http = require('http'); 8 | var cookie = require('cookie'); 9 | 10 | function noop() {} 11 | 12 | function parseCookies(cookies){ 13 | var ret = []; 14 | 15 | for(var key in cookies){ 16 | ret.push(cookie.serialize(key, cookies[key])); 17 | } 18 | 19 | return ret.join("; "); 20 | } 21 | 22 | module.exports = { 23 | 24 | fireAndForget: function(port, fn){ 25 | var opts = { 26 | host: 'localhost', 27 | port: port, 28 | path: '/' 29 | }; 30 | 31 | if (!fn){ 32 | fn = noop; 33 | } 34 | 35 | var req = http.get(opts, function(resp){ 36 | 37 | resp.on('data', function(){ 38 | fn(); 39 | }); 40 | 41 | }); 42 | 43 | req.on('error', fn); 44 | }, 45 | 46 | fireForCookies: function(port, fn){ 47 | var opts = { 48 | host: 'localhost', 49 | port: port, 50 | path: '/' 51 | }; 52 | 53 | var req = http.get(opts, function(resp){ 54 | var cookies = cookie.parse(resp.headers['set-cookie'].join('; ')); 55 | fn(null, cookies); 56 | }); 57 | 58 | req.on('error', fn); 59 | }, 60 | 61 | fireWithCookies: function(port, cookies, fn){ 62 | var opts = { 63 | host: 'localhost', 64 | port: port, 65 | path: '/', 66 | headers: { 67 | cookie: parseCookies(cookies) 68 | } 69 | }; 70 | 71 | var req = http.get(opts, function(resp){ 72 | var cookies = cookie.parse(resp.headers['set-cookie'].join('; ')); 73 | fn(null, cookies); 74 | }); 75 | 76 | req.on('error', fn); 77 | } 78 | 79 | }; 80 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var cookie = require('cookie'); 2 | 3 | function merge (dst, src) { 4 | for (var i = 1; src = arguments[i], i < arguments.length; ++i) 5 | for (var p in src) 6 | if (src.hasOwnProperty(p) && !dst.hasOwnProperty(p) && 7 | dst[p] !== src[p]) dst[p] = src[p]; 8 | return dst; 9 | } 10 | 11 | module.exports = { 12 | /** 13 | * Parse function to be handed to restify server.use 14 | * 15 | * @param {object} req 16 | * @param {object} res 17 | * @param {Function} next 18 | * @return {undefined} 19 | */ 20 | parse: function parseCookies(req, res, next) { 21 | var self = this; 22 | var cookieHeader = req.headers.cookie; 23 | 24 | if (cookieHeader) { 25 | req.cookies = cookie.parse(cookieHeader); 26 | } else { 27 | req.cookies = {}; 28 | } 29 | 30 | /** 31 | * Add a cookie to our response. Uses a Set-Cookie header 32 | * per cookie added. 33 | * 34 | * @param {String} key - Cookie name 35 | * @param {String} val - Cookie value 36 | * @param {[type]} opts - Options object can contain path, secure, 37 | * expires, domain, http 38 | */ 39 | res.setCookie = function setCookie(key, val, opts) { 40 | var HEADER = "Set-Cookie"; 41 | 42 | if (res.header(HEADER)) { 43 | var curCookies = res.header(HEADER); 44 | 45 | if (!(curCookies instanceof Array)) { 46 | curCookies = [curCookies]; 47 | } 48 | 49 | curCookies.push(cookie.serialize(key, val, opts)); 50 | 51 | res.setHeader(HEADER, curCookies); 52 | 53 | } else { 54 | 55 | res.setHeader(HEADER, cookie.serialize(key, val, opts)); 56 | 57 | } 58 | }; 59 | 60 | res.clearCookie = function clearCookie (key, opts) { 61 | var options = merge({ 62 | expires: new Date(1) 63 | }, opts); 64 | 65 | res.setCookie(key, '', options) 66 | } 67 | 68 | next(); 69 | 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Restify-Cookies 2 | =============== 3 | 4 | [![Build Status](https://travis-ci.org/nathschmidt/restify-cookies.svg?branch=master)](https://travis-ci.org/nathschmidt/restify-cookies) 5 | 6 | Adds cookie reading/setting to restify. 7 | 8 | Adds the request.cookie object, which is a hash containing all the key-value cookie pairs sent with this request. For setting cookies this adds the `response.setCookie` method, which takes a key and value and adds it to the cookie header. 9 | 10 | ``` 11 | response.setCookie('key', 'value'), 12 | response.setCookie('key', 'value', cookieOptions); // options explained below 13 | ``` 14 | 15 | There is also `response.clearCookie`, which will set the value of a cookie to an empty string. It takes a key and optionally additional cookie options. 16 | 17 | ``` 18 | response.clearCookie('key'); 19 | response.clearCookie('key', cookieOptions); // options explained below 20 | ``` 21 | 22 | Installation: 23 | 24 | ```bash 25 | npm install restify-cookies 26 | ``` 27 | 28 | Example Usage: 29 | 30 | ```javascript 31 | var CookieParser = require('restify-cookies'); 32 | var Restify = require('restify'); 33 | 34 | var server = Restify.createServer(); 35 | 36 | server.use(CookieParser.parse); 37 | 38 | server.get('/', function(req, res, next){ 39 | var cookies = req.cookies; // Gets read-only cookies from the request 40 | 41 | res.setCookie('my-new-cookie', 'Hi There'); // Adds a new cookie to the response 42 | 43 | if (req.cookies['my-old-cookie']){ 44 | res.clearCookie('my-old-cookie'); // Remove this old cookie 45 | } 46 | 47 | res.send(JSON.stringify(cookies)); 48 | 49 | }); 50 | 51 | server.listen(8080); 52 | ``` 53 | 54 | Cookie Options 55 | -------------- 56 | 57 | Cookies can have options specified detailing the lifetime of the cookie, the domain, the paths the cookie is set for, HTTPS only and disallowing JavaScript access to the cookie. To set these options use `setCookie` with the optional third parameter or `clearCookie` with the optional second parameter. 58 | 59 | The cookie options object is a hash containing these fields: 60 | 61 | - path - A string path the cookie is valid for. Default: no path. 62 | - expires - Date object for when the cookie should expire. Default: No expiry. 63 | - maxAge - Number of seconds until the cookie should expire. Default: No max age. 64 | - domain - The domain the cookie will be sent over. Default: origin only. 65 | - secure - Set to `true` if the cookie should only be sent for HTTPS requests. Default: `false`. 66 | - httpOnly - Set to `true` to disable client-side manipulation of the cookie. Default: `false`. 67 | - sameSite. - Specifies the string to set the value for the SameSite attribute. Values: `lax`, `strict` or `none`. 68 | 69 | Example: 70 | ```javascript 71 | 72 | res.setCookie('myCookie', 'Hi There', { 73 | path: '/home/', 74 | domain: 'www.example.com', 75 | maxAge: 60, 76 | secure: true, 77 | httpOnly: true 78 | }); 79 | 80 | ``` 81 | 82 | License 83 | ------- 84 | 85 | MIT License 86 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Tests for Restify cookies 4 | * 5 | */ 6 | var testutils = require('./testutils'); 7 | var expect = require('expect.js'); 8 | 9 | describe('Testing Restify Cookies', function() { 10 | var PORT = 25000; 11 | 12 | // Before each test setup a new http server. 13 | beforeEach(function(done){ 14 | var self = this; 15 | 16 | self.testSever = testutils.server(PORT, done); 17 | }); 18 | 19 | it('should check our cookies interface is attached', function(done) { 20 | var self = this; 21 | 22 | self.testSever.get('/', function(req, res){ 23 | 24 | expect(req.cookies).to.be.an('object'); 25 | expect(res.setCookie).to.be.a('function'); 26 | 27 | res.send('Done'); 28 | done(); 29 | }); 30 | 31 | testutils.request.fireAndForget(PORT, function(err){ 32 | if(err){ 33 | done(err); 34 | } 35 | }); 36 | }); 37 | 38 | describe('Test setting cookies', function() { 39 | 40 | describe('Set a single cookie', function() { 41 | it('should set a cookie in the response', function(done) { 42 | var self = this; 43 | 44 | self.testSever.get('/', function(req, res, next){ 45 | // Set a cookie val 46 | 47 | res.setCookie('hello', 'world'); 48 | 49 | res.send('Hello'); 50 | next(); 51 | }); 52 | 53 | testutils.request.fireForCookies(PORT, function(err, cookies){ 54 | if(err){ 55 | done(err); 56 | return; 57 | } 58 | 59 | try{ 60 | expect(cookies).to.have.keys('hello'); 61 | expect(cookies['hello']).to.be.eql('world'); 62 | 63 | } catch(err){ 64 | done(err); 65 | return; 66 | } 67 | 68 | done(err); 69 | }); 70 | }); 71 | }); 72 | 73 | describe('Set multiple cookies', function() { 74 | it('should set three different cookies in a single handler', function(done) { 75 | var self = this; 76 | 77 | self.testSever.get('/', function(req, res, next){ 78 | // Set a cookie val 79 | 80 | res.setCookie('hello', 'world'); 81 | res.setCookie('welcome', 'home'); 82 | res.setCookie('goodbye', 'sunshine'); 83 | 84 | res.send('Hello'); 85 | next(); 86 | }); 87 | 88 | testutils.request.fireForCookies(PORT, function(err, cookies){ 89 | if(err){ 90 | done(err); 91 | return; 92 | } 93 | 94 | try{ 95 | 96 | expect(cookies).to.have.keys('hello', 'welcome', 'goodbye'); 97 | expect(cookies['hello']).to.be.eql('world'); 98 | expect(cookies['welcome']).to.be.eql('home'); 99 | expect(cookies['goodbye']).to.be.eql('sunshine'); 100 | 101 | } catch(err){ 102 | done(err); 103 | return; 104 | } 105 | 106 | done(); 107 | }); 108 | }); 109 | 110 | it('should set three different cookies in three different handlers', function(done) { 111 | var self = this; 112 | 113 | self.testSever.use(function(req, res, next){ 114 | // Set a cookie val 115 | res.setCookie('hello', 'world'); 116 | 117 | next(); 118 | }); 119 | 120 | self.testSever.get('/', function(req, res, next){ 121 | // Set a cookie val 122 | res.setCookie('welcome', 'home'); 123 | next('slash2'); 124 | }); 125 | 126 | self.testSever.get({path: '/:id', name:'slash2'}, function(req, res, next){ 127 | // Set a cookie val 128 | res.setCookie('goodbye', 'sunshine'); 129 | res.send('Hello'); 130 | next(); 131 | }); 132 | 133 | testutils.request.fireForCookies(PORT, function(err, cookies){ 134 | if(err){ 135 | done(err); 136 | return; 137 | } 138 | 139 | try { 140 | expect(cookies).to.have.keys('hello', 'welcome', 'goodbye'); 141 | 142 | expect(cookies['hello']).to.be.eql('world'); 143 | expect(cookies['welcome']).to.be.eql('home'); 144 | expect(cookies['goodbye']).to.be.eql('sunshine'); 145 | 146 | } catch(err){ 147 | done(err); 148 | return; 149 | } 150 | 151 | done(); 152 | }); 153 | }); 154 | }); 155 | 156 | }); 157 | 158 | describe('Test Reading cookies', function() { 159 | 160 | it('should read sent cookies', function(done) { 161 | var self = this; 162 | 163 | self.testSever.get('/', function(req, res, next){ 164 | 165 | expect(req).to.have.keys('cookies'); 166 | expect(req.cookies).to.have.keys('hello', 'welcome', 'goodbye'); 167 | 168 | var cookies = req.cookies; 169 | expect(cookies['hello']).to.be.eql('world'); 170 | expect(cookies['welcome']).to.be.eql('home'); 171 | expect(cookies['goodbye']).to.be.eql('sunshine'); 172 | 173 | res.send('Hello'); 174 | 175 | next(); 176 | done(); 177 | }); 178 | 179 | var cookies = { 180 | 'hello': 'world', 181 | 'welcome': 'home', 182 | 'goodbye': 'sunshine' 183 | }; 184 | 185 | testutils.request.fireWithCookies(PORT, cookies, function(err){ 186 | if(err){ 187 | done(err); 188 | } 189 | }); 190 | }); 191 | }); 192 | 193 | describe('Test clearing cookies', function() { 194 | 195 | describe('Clear a single cookie', function() { 196 | it('should remove a cookie in the response', function(done) { 197 | var self = this; 198 | 199 | self.testSever.get('/', function(req, res, next){ 200 | expect(req).to.have.keys('cookies'); 201 | expect(req.cookies).to.have.keys('hello', 'welcome'); 202 | 203 | var cookies = req.cookies; 204 | expect(cookies['hello']).to.be.eql('world'); 205 | expect(cookies['welcome']).to.be.eql('home'); 206 | 207 | res.clearCookie('hello'); 208 | 209 | res.send('Hello'); 210 | next(); 211 | }); 212 | 213 | var cookiesToSend = { 214 | hello: 'world', 215 | 'welcome': 'home' 216 | }; 217 | 218 | testutils.request.fireWithCookies(PORT, cookiesToSend, function(err, cookies){ 219 | if(err){ 220 | done(err); 221 | return; 222 | } 223 | 224 | try{ 225 | expect(cookies).to.have.keys('hello'); 226 | expect(cookies['hello']).to.be.eql(''); 227 | 228 | } catch(err){ 229 | done(err); 230 | return; 231 | } 232 | 233 | done(err); 234 | }); 235 | }); 236 | }); 237 | 238 | }); 239 | 240 | afterEach(function(){ 241 | var self = this; 242 | 243 | self.testSever.close(); 244 | }); 245 | 246 | }); --------------------------------------------------------------------------------