├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── lib └── passport-http │ ├── index.js │ └── strategies │ ├── basic.js │ └── digest.js ├── package.json └── test ├── index-test.js └── strategies ├── basic-test.js └── digest-test.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: jaredhanson 2 | ko_fi: jaredhanson 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ** READ THIS FIRST! ** 2 | 3 | #### Are you looking for help? 4 | 5 | Reminder: The issue tracker is not a support forum. 6 | 7 | Issues should only be filed in this project once they are able to be reproduced 8 | and confirmed as a flaw in the software or incorrect information in associated 9 | documention. 10 | 11 | If you are encountering problems integrating this module into your application, 12 | please post a question on the [discussion forum](https://github.com/passport/discuss) 13 | rather than filing an issue. 14 | 15 | #### Is this a security issue? 16 | 17 | Do not open issues that might have security implications. Potential security 18 | vulnerabilities should be reported privately to jaredhanson@gmail.com. Once any 19 | vulerabilities have been repaired, the details will be disclosed publicly in a 20 | responsible manner. This also allows time for coordinating with affected parties 21 | in order to mitigate negative consequences. 22 | 23 | 24 | If neither of the above two scenarios apply to your situation, you should open 25 | an issue. Delete this paragraph and the text above, and fill in the information 26 | requested below. 27 | 28 | 29 | 30 | 31 | 32 | 33 | ### Expected behavior 34 | 35 | 36 | 37 | ### Actual behavior 38 | 39 | 40 | 41 | ### Steps to reproduce 42 | 43 | 44 | 45 | ```js 46 | // Format code using Markdown code blocks 47 | ``` 48 | 49 | ### Environment 50 | 51 | * Operating System: 52 | * Node version: 53 | * passport version: 54 | * passport-http version: 55 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ** READ THIS FIRST! ** 2 | 3 | #### Are you implementing a new feature? 4 | 5 | Requests for new features should first be discussed on the [developer forum](https://github.com/passport/develop). 6 | This allows the community to gather feedback and assess whether or not there is 7 | an existing way to achieve the desired functionality. 8 | 9 | If it is determined that a new feature needs to be implemented, include a link 10 | to the relevant discussion along with the pull request. 11 | 12 | #### Is this a security patch? 13 | 14 | Do not open pull requests that might have security implications. Potential 15 | security vulnerabilities should be reported privately to jaredhanson@gmail.com. 16 | Once any vulerabilities have been repaired, the details will be disclosed 17 | publicly in a responsible manner. This also allows time for coordinating with 18 | affected parties in order to mitigate negative consequences. 19 | 20 | 21 | If neither of the above two scenarios apply to your situation, you should open 22 | a pull request. Delete this paragraph and the text above, and fill in the 23 | information requested below. 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ### Checklist 33 | 34 | 35 | 36 | 37 | - [ ] I have read the [CONTRIBUTING](https://github.com/jaredhanson/passport-http/blob/master/CONTRIBUTING.md) guidelines. 38 | - [ ] I have added test cases which verify the correct operation of this feature or patch. 39 | - [ ] I have added documentation pertaining to this feature or patch. 40 | - [ ] The automated test suite (`$ make test`) executes successfully. 41 | - [ ] The automated code linting (`$ make lint`) executes successfully. 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | .DS_Store 3 | 4 | # Node.js 5 | node_modules 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | README.md 2 | Makefile 3 | doc/ 4 | examples/ 5 | test/ 6 | 7 | # Mac OS X 8 | .DS_Store 9 | 10 | # Node.js 11 | .npmignore 12 | node_modules/ 13 | npm-debug.log 14 | 15 | # Git 16 | .git* 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: "node_js" 2 | node_js: 3 | - "11" 4 | - "10" 5 | - "9" 6 | - "8" 7 | - "7" 8 | - "6" 9 | - "5" 10 | - "4" 11 | - "3" # io.js 12 | - "2" # io.js 13 | - "1" # io.js 14 | - "0.12" 15 | - "0.10" 16 | 17 | 18 | # NOTE: `istanbul` and `coveralls` are pinned for compatibility with node 0.8. 19 | before_install: 20 | - "npm install -g istanbul@0.2.2" 21 | - "npm install -g coveralls@2.11.4" 22 | 23 | script: 24 | - "NODE_PATH=lib make check" 25 | 26 | after_success: 27 | - "NODE_PATH=lib make report-cov" 28 | 29 | sudo: false 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | ### Tests 4 | 5 | The test suite is located in the `test/` directory. All new features are 6 | expected to have corresponding test cases with complete code coverage. Patches 7 | that increase test coverage are happily accepted. 8 | 9 | Ensure that the test suite passes by executing: 10 | 11 | ```bash 12 | $ make test 13 | ``` 14 | 15 | Coverage reports can be generated and viewed by executing: 16 | 17 | ```bash 18 | $ make test-cov 19 | $ make view-cov 20 | ``` 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2011-2013 Jared Hanson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS ?= $(shell find test -type f -name '*-test.js') 2 | 3 | include node_modules/make-node/main.mk 4 | 5 | 6 | # Perform self-tests. 7 | check: test 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Passport-HTTP 2 | 3 | HTTP Basic and Digest authentication strategies for [Passport](https://github.com/jaredhanson/passport). 4 | 5 | This module lets you authenticate HTTP requests using the standard basic and 6 | digest schemes in your Node.js applications. By plugging into Passport, support 7 | for these schemes can be easily and unobtrusively integrated into any 8 | application or framework that supports [Connect](http://www.senchalabs.org/connect/)-style 9 | middleware, including [Express](http://expressjs.com/). 10 | 11 |
12 | 13 | :heart: [Sponsors](https://www.passportjs.org/sponsors/?utm_source=github&utm_medium=referral&utm_campaign=passport-http&utm_content=nav-sponsors) 14 | 15 |
16 | 17 | --- 18 | 19 |

20 | Advertisement 21 |
22 | Node.js, Express, MongoDB & More: The Complete Bootcamp 2020
Master Node by building a real-world RESTful API and web app (with authentication, Node.js security, payments & more) 23 |

24 | 25 | --- 26 | 27 | [![npm](https://img.shields.io/npm/v/passport-http.svg)](https://www.npmjs.com/package/passport-http) 28 | [![build](https://img.shields.io/travis/jaredhanson/passport-http.svg)](https://travis-ci.org/jaredhanson/passport-http) 29 | [![coverage](https://img.shields.io/coveralls/jaredhanson/passport-http.svg)](https://coveralls.io/github/jaredhanson/passport-http) 30 | [...](https://github.com/jaredhanson/passport-http/wiki/Status) 31 | 32 | ## Install 33 | 34 | $ npm install passport-http 35 | 36 | ## Usage of HTTP Basic 37 | 38 | #### Configure Strategy 39 | 40 | The HTTP Basic authentication strategy authenticates users using a userid and 41 | password. The strategy requires a `verify` callback, which accepts these 42 | credentials and calls `done` providing a user. 43 | 44 | passport.use(new BasicStrategy( 45 | function(userid, password, done) { 46 | User.findOne({ username: userid }, function (err, user) { 47 | if (err) { return done(err); } 48 | if (!user) { return done(null, false); } 49 | if (!user.verifyPassword(password)) { return done(null, false); } 50 | return done(null, user); 51 | }); 52 | } 53 | )); 54 | 55 | #### Authenticate Requests 56 | 57 | Use `passport.authenticate()`, specifying the `'basic'` strategy, to 58 | authenticate requests. Requests containing an 'Authorization' header do not 59 | require session support, so the `session` option can be set to `false`. 60 | 61 | For example, as route middleware in an [Express](http://expressjs.com/) 62 | application: 63 | 64 | app.get('/private', 65 | passport.authenticate('basic', { session: false }), 66 | function(req, res) { 67 | res.json(req.user); 68 | }); 69 | 70 | #### Examples 71 | 72 | For a complete, working example, refer to the [Basic example](https://github.com/passport/express-3.x-http-basic-example). 73 | 74 | ## Usage of HTTP Digest 75 | 76 | #### Configure Strategy 77 | 78 | The HTTP Digest authentication strategy authenticates users using a username and 79 | password (aka shared secret). The strategy requires a `secret` callback, which 80 | accepts a `username` and calls `done` providing a user and password known to the 81 | server. The password is used to compute a hash, and authentication fails if it 82 | does not match that contained in the request. 83 | 84 | The strategy also accepts an optional `validate` callback, which receives 85 | nonce-related `params` that can be further inspected to determine if the request 86 | is valid. 87 | 88 | passport.use(new DigestStrategy({ qop: 'auth' }, 89 | function(username, done) { 90 | User.findOne({ username: username }, function (err, user) { 91 | if (err) { return done(err); } 92 | if (!user) { return done(null, false); } 93 | return done(null, user, user.password); 94 | }); 95 | }, 96 | function(params, done) { 97 | // validate nonces as necessary 98 | done(null, true) 99 | } 100 | )); 101 | 102 | #### Authenticate Requests 103 | 104 | Use `passport.authenticate()`, specifying the `'digest'` strategy, to 105 | authenticate requests. Requests containing an 'Authorization' header do not 106 | require session support, so the `session` option can be set to `false`. 107 | 108 | For example, as route middleware in an [Express](http://expressjs.com/) 109 | application: 110 | 111 | app.get('/private', 112 | passport.authenticate('digest', { session: false }), 113 | function(req, res) { 114 | res.json(req.user); 115 | }); 116 | 117 | #### Examples 118 | 119 | For a complete, working example, refer to the [Digest example](https://github.com/passport/express-3.x-http-digest-example). 120 | 121 | ## License 122 | 123 | [The MIT License](http://opensource.org/licenses/MIT) 124 | 125 | Copyright (c) 2011-2013 Jared Hanson <[http://jaredhanson.net/](http://jaredhanson.net/)> 126 | -------------------------------------------------------------------------------- /lib/passport-http/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var BasicStrategy = require('./strategies/basic'); 5 | var DigestStrategy = require('./strategies/digest'); 6 | 7 | /** 8 | * Export constructors. 9 | */ 10 | exports.BasicStrategy = BasicStrategy; 11 | exports.DigestStrategy = DigestStrategy; 12 | -------------------------------------------------------------------------------- /lib/passport-http/strategies/basic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var passport = require('passport-strategy') 5 | , util = require('util'); 6 | 7 | 8 | /** 9 | * `BasicStrategy` constructor. 10 | * 11 | * The HTTP Basic authentication strategy authenticates requests based on 12 | * userid and password credentials contained in the `Authorization` header 13 | * field. 14 | * 15 | * Applications must supply a `verify` callback which accepts `userid` and 16 | * `password` credentials, and then calls the `done` callback supplying a 17 | * `user`, which should be set to `false` if the credentials are not valid. 18 | * If an exception occured, `err` should be set. 19 | * 20 | * Optionally, `options` can be used to change the authentication realm. 21 | * 22 | * Options: 23 | * - `realm` authentication realm, defaults to "Users" 24 | * 25 | * Examples: 26 | * 27 | * passport.use(new BasicStrategy( 28 | * function(userid, password, done) { 29 | * User.findOne({ username: userid, password: password }, function (err, user) { 30 | * done(err, user); 31 | * }); 32 | * } 33 | * )); 34 | * 35 | * For further details on HTTP Basic authentication, refer to [RFC 2617: HTTP Authentication: Basic and Digest Access Authentication](http://tools.ietf.org/html/rfc2617) 36 | * 37 | * @param {Object} options 38 | * @param {Function} verify 39 | * @api public 40 | */ 41 | function BasicStrategy(options, verify) { 42 | if (typeof options == 'function') { 43 | verify = options; 44 | options = {}; 45 | } 46 | if (!verify) throw new Error('HTTP Basic authentication strategy requires a verify function'); 47 | 48 | passport.Strategy.call(this); 49 | this.name = 'basic'; 50 | this._verify = verify; 51 | this._realm = options.realm || 'Users'; 52 | this._passReqToCallback = options.passReqToCallback; 53 | } 54 | 55 | /** 56 | * Inherit from `passport.Strategy`. 57 | */ 58 | util.inherits(BasicStrategy, passport.Strategy); 59 | 60 | /** 61 | * Authenticate request based on the contents of a HTTP Basic authorization 62 | * header. 63 | * 64 | * @param {Object} req 65 | * @api protected 66 | */ 67 | BasicStrategy.prototype.authenticate = function(req) { 68 | var authorization = req.headers['authorization']; 69 | if (!authorization) { return this.fail(this._challenge()); } 70 | 71 | var parts = authorization.split(' ') 72 | if (parts.length < 2) { return this.fail(400); } 73 | 74 | var scheme = parts[0] 75 | , credentials = new Buffer(parts[1], 'base64').toString().split(':'); 76 | 77 | if (!/Basic/i.test(scheme)) { return this.fail(this._challenge()); } 78 | if (credentials.length < 2) { return this.fail(400); } 79 | 80 | var userid = credentials[0]; 81 | var password = credentials[1]; 82 | if (!userid || !password) { 83 | return this.fail(this._challenge()); 84 | } 85 | 86 | var self = this; 87 | 88 | function verified(err, user) { 89 | if (err) { return self.error(err); } 90 | if (!user) { return self.fail(self._challenge()); } 91 | self.success(user); 92 | } 93 | 94 | if (self._passReqToCallback) { 95 | this._verify(req, userid, password, verified); 96 | } else { 97 | this._verify(userid, password, verified); 98 | } 99 | } 100 | 101 | /** 102 | * Authentication challenge. 103 | * 104 | * @api private 105 | */ 106 | BasicStrategy.prototype._challenge = function() { 107 | return 'Basic realm="' + this._realm + '"'; 108 | } 109 | 110 | 111 | /** 112 | * Expose `BasicStrategy`. 113 | */ 114 | module.exports = BasicStrategy; 115 | -------------------------------------------------------------------------------- /lib/passport-http/strategies/digest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var passport = require('passport-strategy') 5 | , crypto = require('crypto') 6 | , util = require('util'); 7 | 8 | 9 | /** 10 | * `DigestStrategy` constructor. 11 | * 12 | * The HTTP Digest authentication strategy authenticates requests based on 13 | * username and digest credentials contained in the `Authorization` header 14 | * field. 15 | * 16 | * Applications must supply a `secret` callback, which is used to look up the 17 | * user and corresponding password (aka shared secret) known to both the server 18 | * and the client, supplying them to the `done` callback as `user` and 19 | *`password`, respectively. The strategy will use the password to compute the 20 | * response hash, failing authentication if it does not match that found in the 21 | * request. If the username is not valid, `user` should be set to false. If an 22 | * exception occured, `err` should be set. 23 | * 24 | * An optional `validate` callback can be supplied, which receives `params` 25 | * containing nonces that the server may want to track and validate. 26 | * 27 | * Options: 28 | * - `realm` authentication realm, defaults to "Users" 29 | * - `domain` list of URIs that define the protection space 30 | * - `algorithm` algorithm used to produce the digest (MD5 | MD5-sess) 31 | * - `qop` list of quality of protection values support by the server (auth | auth-int) (recommended: auth) 32 | * 33 | * `validate` params: 34 | * - `nonce` unique string value specified by the server 35 | * - `cnonce` opaque string value provided by the client 36 | * - `nc` count of the number of requests (including the current request) that the client has sent with the nonce value 37 | * - `opaque` string of data, specified by the server, which should be returned by the client in subsequent requests 38 | * 39 | * Examples: 40 | * 41 | * passport.use(new DigestStrategy({ qop: 'auth' }, 42 | * function(username, done) { 43 | * // secret callback 44 | * User.findOne({ username: username }, function (err, user) { 45 | * if (err) { return done(err); } 46 | * return done(null, user, user.password); 47 | * }); 48 | * }, 49 | * function(params, done) { 50 | * // validate callback, check nonces in params... 51 | * done(err, true); 52 | * } 53 | * )); 54 | * 55 | * For further details on HTTP Basic authentication, refer to [RFC 2617: HTTP Authentication: Basic and Digest Access Authentication](http://tools.ietf.org/html/rfc2617) 56 | * 57 | * @param {Object} options 58 | * @param {Function} secret 59 | * @param {Function} validate 60 | * @api public 61 | */ 62 | function DigestStrategy(options, secret, validate) { 63 | if (typeof options == 'function') { 64 | validate = secret; 65 | secret = options; 66 | options = {}; 67 | } 68 | if (!secret) throw new Error('HTTP Digest authentication strategy requires a secret function'); 69 | 70 | passport.Strategy.call(this); 71 | this.name = 'digest'; 72 | this._secret = secret; 73 | this._validate = validate; 74 | this._realm = options.realm || 'Users'; 75 | if (options.domain) { 76 | this._domain = (Array.isArray(options.domain)) ? options.domain : [ options.domain ]; 77 | } 78 | this._opaque = options.opaque; 79 | this._algorithm = options.algorithm; 80 | if (options.qop) { 81 | this._qop = (Array.isArray(options.qop)) ? options.qop : [ options.qop ]; 82 | } 83 | } 84 | 85 | /** 86 | * Inherit from `passport.Strategy`. 87 | */ 88 | util.inherits(DigestStrategy, passport.Strategy); 89 | 90 | /** 91 | * Authenticate request based on the contents of a HTTP Digest authorization 92 | * header. 93 | * 94 | * @param {Object} req 95 | * @api protected 96 | */ 97 | DigestStrategy.prototype.authenticate = function(req) { 98 | var authorization = req.headers['authorization']; 99 | if (!authorization) { return this.fail(this._challenge()); } 100 | 101 | var parts = authorization.split(' ') 102 | if (parts.length < 2) { return this.fail(400); } 103 | 104 | var scheme = parts[0] 105 | , params = parts.slice(1).join(' '); 106 | 107 | if (!/Digest/i.test(scheme)) { return this.fail(this._challenge()); } 108 | 109 | var creds = parse(params); 110 | if (Object.keys(creds).length === 0) { return this.fail(400); } 111 | 112 | if (!creds.username) { 113 | return this.fail(this._challenge()); 114 | } 115 | if (req.url !== creds.uri) { 116 | return this.fail(400); 117 | } 118 | 119 | 120 | var self = this; 121 | 122 | // Use of digest authentication requires a password (aka shared secret) known 123 | // to both the client and server, but not transported over the wire. This 124 | // secret is needed in order to compute the hashes required to authenticate 125 | // the request, and can be obtained by calling the secret() function the 126 | // application provides to the strategy. Because username is the key for a 127 | // database query, a `user` instance is also obtained from this callback. 128 | // However, the user will only be successfully authenticated if the password 129 | // is correct, as indicated by the challenge response matching the computed 130 | // value. 131 | this._secret(creds.username, function(err, user, password) { 132 | if (err) { return self.error(err); } 133 | if (!user) { return self.fail(self._challenge()); } 134 | 135 | var ha1; 136 | if (!creds.algorithm || creds.algorithm === 'MD5') { 137 | if (typeof password === 'object' && password.ha1) { 138 | ha1 = password.ha1; 139 | } else { 140 | ha1 = md5(creds.username + ":" + creds.realm + ":" + password); 141 | } 142 | } else if (creds.algorithm === 'MD5-sess') { 143 | // TODO: The nonce and cnonce used here should be the initial nonce 144 | // value generated by the server and the first nonce value used by 145 | // the client. This creates a 'session key' for the authentication 146 | // of subsequent requests. The storage of the nonce values and the 147 | // resulting session key needs to be investigated. 148 | // 149 | // See RFC 2617 (Section 3.2.2.2) for further details. 150 | ha1 = md5(md5(creds.username + ":" + creds.realm + ":" + password) + ":" + creds.nonce + ":" + creds.cnonce); 151 | } else { 152 | return self.fail(400); 153 | } 154 | 155 | var ha2; 156 | if (!creds.qop || creds.qop === 'auth') { 157 | ha2 = md5(req.method + ":" + creds.uri); 158 | } else if (creds.qop === 'auth-int') { 159 | // TODO: Implement support for auth-int. Note that the raw entity body 160 | // will be needed, not the parsed req.body property set by Connect's 161 | // bodyParser middleware. 162 | // 163 | // See RFC 2617 (Section 3.2.2.3 and Section 3.2.2.4) for further 164 | // details. 165 | return self.error(new Error('auth-int not implemented')); 166 | } else { 167 | return self.fail(400); 168 | } 169 | 170 | var digest; 171 | if (!creds.qop) { 172 | digest = md5(ha1 + ":" + creds.nonce + ":" + ha2); 173 | } else if (creds.qop === 'auth' || creds.qop === 'auth-int') { 174 | digest = md5(ha1 + ":" + creds.nonce + ":" + creds.nc + ":" + creds.cnonce + ":" + creds.qop + ":" + ha2); 175 | } else { 176 | return self.fail(400); 177 | } 178 | 179 | if (creds.response != digest) { 180 | return self.fail(self._challenge()); 181 | } else { 182 | if (self._validate) { 183 | self._validate({ 184 | nonce: creds.nonce, 185 | cnonce: creds.cnonce, 186 | nc: creds.nc, 187 | opaque: creds.opaque 188 | }, 189 | function(err, valid) { 190 | if (err) { return self.error(err); } 191 | if (!valid) { return self.fail(self._challenge()); } 192 | self.success(user); 193 | }); 194 | } else { 195 | self.success(user); 196 | } 197 | } 198 | }); 199 | } 200 | 201 | /** 202 | * Authentication challenge. 203 | * 204 | * @api private 205 | */ 206 | DigestStrategy.prototype._challenge = function() { 207 | // TODO: For maximum flexibility, a mechanism for delegating the generation 208 | // of the nonce and opaque data to the application would be useful. 209 | 210 | var challenge = 'Digest realm="' + this._realm + '"'; 211 | if (this._domain) { 212 | challenge += ', domain="' + this._domain.join(' ') + '"'; 213 | } 214 | challenge += ', nonce="' + nonce(32) + '"'; 215 | if (this._opaque) { 216 | challenge += ', opaque="' + this._opaque + '"'; 217 | } 218 | if (this._algorithm) { 219 | challenge += ', algorithm=' + this._algorithm; 220 | } 221 | if (this._qop) { 222 | challenge += ', qop="' + this._qop.join(',') + '"'; 223 | } 224 | 225 | return challenge; 226 | } 227 | 228 | 229 | /** 230 | * Parse authentication response. 231 | * 232 | * @api private 233 | */ 234 | function parse(params) { 235 | var opts = {}; 236 | var tokens = params.split(/,(?=(?:[^"]|"[^"]*")*$)/); 237 | for (var i = 0, len = tokens.length; i < len; i++) { 238 | var param = /(\w+)=["]?([^"]+)["]?$/.exec(tokens[i]) 239 | if (param) { 240 | opts[param[1]] = param[2]; 241 | } 242 | } 243 | return opts; 244 | } 245 | 246 | /** 247 | * Return a unique nonce with the given `len`. 248 | * 249 | * utils.uid(10); 250 | * // => "FDaS435D2z" 251 | * 252 | * CREDIT: Connect -- utils.uid 253 | * https://github.com/senchalabs/connect/blob/1.7.1/lib/utils.js 254 | * 255 | * @param {Number} len 256 | * @return {String} 257 | * @api private 258 | */ 259 | function nonce(len) { 260 | var buf = [] 261 | , chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' 262 | , charlen = chars.length; 263 | 264 | for (var i = 0; i < len; ++i) { 265 | buf.push(chars[Math.random() * charlen | 0]); 266 | } 267 | 268 | return buf.join(''); 269 | }; 270 | 271 | 272 | /** 273 | * Return md5 hash of the given string and optional encoding, 274 | * defaulting to hex. 275 | * 276 | * utils.md5('wahoo'); 277 | * // => "e493298061761236c96b02ea6aa8a2ad" 278 | * 279 | * CREDIT: Connect -- utils.md5 280 | * https://github.com/senchalabs/connect/blob/1.7.1/lib/utils.js 281 | * 282 | * @param {String} str 283 | * @param {String} encoding 284 | * @return {String} 285 | * @api private 286 | */ 287 | function md5(str, encoding){ 288 | return crypto 289 | .createHash('md5') 290 | .update(str) 291 | .digest(encoding || 'hex'); 292 | }; 293 | 294 | 295 | /** 296 | * Expose `DigestStrategy`. 297 | */ 298 | module.exports = DigestStrategy; 299 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passport-http", 3 | "version": "0.3.0", 4 | "description": "HTTP Basic and Digest authentication strategies for Passport.", 5 | "keywords": ["passport", "http", "basic", "digest", "auth", "authn", "authentication"], 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/jaredhanson/passport-http.git" 9 | }, 10 | "bugs": { 11 | "url": "http://github.com/jaredhanson/passport-http/issues" 12 | }, 13 | "author": { 14 | "name": "Jared Hanson", 15 | "email": "jaredhanson@gmail.com", 16 | "url": "http://www.jaredhanson.net/" 17 | }, 18 | "licenses": [ { 19 | "type": "MIT", 20 | "url": "http://www.opensource.org/licenses/MIT" 21 | } ], 22 | "main": "./lib/passport-http", 23 | "dependencies": { 24 | "passport-strategy": "1.x.x" 25 | }, 26 | "devDependencies": { 27 | "make-node": "0.4.6", 28 | "vows": "0.8.x" 29 | }, 30 | "scripts": { 31 | "test": "NODE_PATH=lib make test" 32 | }, 33 | "engines": { "node": ">= 0.4.0" } 34 | } 35 | -------------------------------------------------------------------------------- /test/index-test.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'); 2 | var assert = require('assert'); 3 | var util = require('util'); 4 | var http = require('passport-http'); 5 | 6 | 7 | vows.describe('passport-http').addBatch({ 8 | 9 | 'module': { 10 | 'should export BasicStrategy': function (x) { 11 | assert.isFunction(http.BasicStrategy); 12 | }, 13 | 14 | 'should export DigestStrategy': function (x) { 15 | assert.isFunction(http.DigestStrategy); 16 | } 17 | }, 18 | 19 | }).export(module); 20 | -------------------------------------------------------------------------------- /test/strategies/basic-test.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'); 2 | var assert = require('assert'); 3 | var util = require('util'); 4 | var BasicStrategy = require('passport-http/strategies/basic'); 5 | 6 | 7 | vows.describe('BasicStrategy').addBatch({ 8 | 9 | 'strategy': { 10 | topic: function() { 11 | return new BasicStrategy(function() {}); 12 | }, 13 | 14 | 'should be named basic': function (strategy) { 15 | assert.equal(strategy.name, 'basic'); 16 | }, 17 | }, 18 | 19 | 'strategy handling a request': { 20 | topic: function() { 21 | var strategy = new BasicStrategy(function(userid, password, done) { 22 | done(null, { username: userid, password: password }); 23 | }); 24 | return strategy; 25 | }, 26 | 27 | 'after augmenting with actions': { 28 | topic: function(strategy) { 29 | var self = this; 30 | var req = {}; 31 | strategy.success = function(user) { 32 | self.callback(null, user); 33 | } 34 | strategy.fail = function() { 35 | self.callback(new Error('should not be called')); 36 | } 37 | 38 | req.headers = {}; 39 | req.headers.authorization = 'Basic Ym9iOnNlY3JldA=='; 40 | process.nextTick(function () { 41 | strategy.authenticate(req); 42 | }); 43 | }, 44 | 45 | 'should not generate an error' : function(err, user) { 46 | assert.isNull(err); 47 | }, 48 | 'should authenticate' : function(err, user) { 49 | assert.equal(user.username, 'bob'); 50 | assert.equal(user.password, 'secret'); 51 | }, 52 | }, 53 | }, 54 | 55 | 'strategy handling a request that is not verified': { 56 | topic: function() { 57 | var strategy = new BasicStrategy(function(userid, password, done) { 58 | done(null, false); 59 | }); 60 | return strategy; 61 | }, 62 | 63 | 'after augmenting with actions': { 64 | topic: function(strategy) { 65 | var self = this; 66 | var req = {}; 67 | strategy.success = function(user) { 68 | self.callback(new Error('should not be called')); 69 | } 70 | strategy.fail = function(challenge) { 71 | self.callback(null, challenge); 72 | } 73 | 74 | req.headers = {}; 75 | req.headers.authorization = 'Basic Ym9iOnNlY3JldA=='; 76 | process.nextTick(function () { 77 | strategy.authenticate(req); 78 | }); 79 | }, 80 | 81 | 'should fail authentication with challenge' : function(err, challenge) { 82 | // fail action was called, resulting in test callback 83 | assert.isNull(err); 84 | assert.equal(challenge, 'Basic realm="Users"'); 85 | }, 86 | }, 87 | }, 88 | 89 | 'strategy handling a request that encounters an error during verification': { 90 | topic: function() { 91 | var strategy = new BasicStrategy(function(userid, password, done) { 92 | done(new Error('something went wrong')); 93 | }); 94 | return strategy; 95 | }, 96 | 97 | 'after augmenting with actions': { 98 | topic: function(strategy) { 99 | var self = this; 100 | var req = {}; 101 | strategy.success = function(user) { 102 | self.callback(new Error('should not be called')); 103 | } 104 | strategy.fail = function(challenge) { 105 | self.callback(new Error('should not be called')); 106 | } 107 | strategy.error = function(err) { 108 | self.callback(null, err); 109 | } 110 | 111 | req.headers = {}; 112 | req.headers.authorization = 'Basic Ym9iOnNlY3JldA=='; 113 | process.nextTick(function () { 114 | strategy.authenticate(req); 115 | }); 116 | }, 117 | 118 | 'should not call success or fail' : function(err, e) { 119 | assert.isNull(err); 120 | }, 121 | 'should call error' : function(err, e) { 122 | assert.instanceOf(e, Error); 123 | }, 124 | }, 125 | }, 126 | 127 | 'strategy handling a request without authorization credentials': { 128 | topic: function() { 129 | var strategy = new BasicStrategy(function(userid, password, done) { 130 | done(null, { username: userid, password: password }); 131 | }); 132 | return strategy; 133 | }, 134 | 135 | 'after augmenting with actions': { 136 | topic: function(strategy) { 137 | var self = this; 138 | var req = {}; 139 | strategy.success = function(user) { 140 | self.callback(new Error('should not be called')); 141 | } 142 | strategy.fail = function(challenge) { 143 | self.callback(null, challenge); 144 | } 145 | 146 | req.headers = {}; 147 | process.nextTick(function () { 148 | strategy.authenticate(req); 149 | }); 150 | }, 151 | 152 | 'should fail authentication with challenge' : function(err, challenge) { 153 | // fail action was called, resulting in test callback 154 | assert.isNull(err); 155 | assert.equal(challenge, 'Basic realm="Users"'); 156 | }, 157 | }, 158 | }, 159 | 160 | 'strategy handling a request with non-Basic authorization credentials': { 161 | topic: function() { 162 | var strategy = new BasicStrategy(function(userid, password, done) { 163 | done(null, { username: userid, password: password }); 164 | }); 165 | return strategy; 166 | }, 167 | 168 | 'after augmenting with actions': { 169 | topic: function(strategy) { 170 | var self = this; 171 | var req = {}; 172 | strategy.success = function(user) { 173 | self.callback(new Error('should not be called')); 174 | } 175 | strategy.fail = function(challenge) { 176 | self.callback(null, challenge); 177 | } 178 | 179 | req.headers = {}; 180 | req.headers.authorization = 'XXXXX Ym9iOnNlY3JldA=='; 181 | process.nextTick(function () { 182 | strategy.authenticate(req); 183 | }); 184 | }, 185 | 186 | 'should fail authentication with challenge' : function(err, challenge) { 187 | // fail action was called, resulting in test callback 188 | assert.isNull(err); 189 | assert.equal(challenge, 'Basic realm="Users"'); 190 | }, 191 | }, 192 | }, 193 | 194 | 'strategy handling a request with credentials lacking a password': { 195 | topic: function() { 196 | var strategy = new BasicStrategy(function(userid, password, done) { 197 | done(null, { username: userid, password: password }); 198 | }); 199 | return strategy; 200 | }, 201 | 202 | 'after augmenting with actions': { 203 | topic: function(strategy) { 204 | var self = this; 205 | var req = {}; 206 | strategy.success = function(user) { 207 | self.callback(new Error('should not be called')); 208 | } 209 | strategy.fail = function(challenge) { 210 | self.callback(null, challenge); 211 | } 212 | 213 | req.headers = {}; 214 | req.headers.authorization = 'Basic Ym9iOg=='; 215 | process.nextTick(function () { 216 | strategy.authenticate(req); 217 | }); 218 | }, 219 | 220 | 'should fail authentication with challenge' : function(err, challenge) { 221 | // fail action was called, resulting in test callback 222 | assert.isNull(err); 223 | assert.equal(challenge, 'Basic realm="Users"'); 224 | }, 225 | }, 226 | }, 227 | 228 | 'strategy handling a request with credentials lacking a username': { 229 | topic: function() { 230 | var strategy = new BasicStrategy(function(userid, password, done) { 231 | done(null, { username: userid, password: password }); 232 | }); 233 | return strategy; 234 | }, 235 | 236 | 'after augmenting with actions': { 237 | topic: function(strategy) { 238 | var self = this; 239 | var req = {}; 240 | strategy.success = function(user) { 241 | self.callback(new Error('should not be called')); 242 | } 243 | strategy.fail = function(challenge) { 244 | self.callback(null, challenge); 245 | } 246 | 247 | req.headers = {}; 248 | req.headers.authorization = 'Basic OnNlY3JldA=='; 249 | process.nextTick(function () { 250 | strategy.authenticate(req); 251 | }); 252 | }, 253 | 254 | 'should fail authentication with challenge' : function(err, challenge) { 255 | // fail action was called, resulting in test callback 256 | assert.isNull(err); 257 | assert.equal(challenge, 'Basic realm="Users"'); 258 | }, 259 | }, 260 | }, 261 | 262 | 'strategy handling a request with malformed authorization header': { 263 | topic: function() { 264 | var strategy = new BasicStrategy(function(userid, password, done) { 265 | done(null, { username: userid, password: password }); 266 | }); 267 | return strategy; 268 | }, 269 | 270 | 'after augmenting with actions': { 271 | topic: function(strategy) { 272 | var self = this; 273 | var req = {}; 274 | strategy.success = function(user) { 275 | self.callback(new Error('should not be called')); 276 | } 277 | strategy.fail = function(status) { 278 | self.callback(null, status); 279 | } 280 | 281 | req.headers = {}; 282 | req.headers.authorization = 'Basic'; 283 | process.nextTick(function () { 284 | strategy.authenticate(req); 285 | }); 286 | }, 287 | 288 | 'should fail authentication with challenge' : function(err, challenge) { 289 | // fail action was called, resulting in test callback 290 | assert.isNull(err); 291 | assert.equal(challenge, 400); 292 | }, 293 | }, 294 | }, 295 | 296 | 'strategy handling a request with malformed authorization credentials': { 297 | topic: function() { 298 | var strategy = new BasicStrategy(function(userid, password, done) { 299 | done(null, { username: userid, password: password }); 300 | }); 301 | return strategy; 302 | }, 303 | 304 | 'after augmenting with actions': { 305 | topic: function(strategy) { 306 | var self = this; 307 | var req = {}; 308 | strategy.success = function(user) { 309 | self.callback(new Error('should not be called')); 310 | } 311 | strategy.fail = function(status) { 312 | self.callback(null, status); 313 | } 314 | 315 | req.headers = {}; 316 | req.headers.authorization = 'Basic *****'; 317 | process.nextTick(function () { 318 | strategy.authenticate(req); 319 | }); 320 | }, 321 | 322 | 'should fail authentication with challenge' : function(err, challenge) { 323 | // fail action was called, resulting in test callback 324 | assert.isNull(err); 325 | assert.equal(challenge, 400); 326 | }, 327 | }, 328 | }, 329 | 330 | 'strategy handling a request with BASIC scheme in capitalized letters': { 331 | topic: function() { 332 | var strategy = new BasicStrategy(function(userid, password, done) { 333 | done(null, { username: userid, password: password }); 334 | }); 335 | return strategy; 336 | }, 337 | 338 | 'after augmenting with actions': { 339 | topic: function(strategy) { 340 | var self = this; 341 | var req = {}; 342 | strategy.success = function(user) { 343 | self.callback(null, user); 344 | } 345 | strategy.fail = function() { 346 | self.callback(new Error('should not be called')); 347 | } 348 | 349 | req.headers = {}; 350 | req.headers.authorization = 'BASIC Ym9iOnNlY3JldA=='; 351 | process.nextTick(function () { 352 | strategy.authenticate(req); 353 | }); 354 | }, 355 | 356 | 'should not generate an error' : function(err, user) { 357 | assert.isNull(err); 358 | }, 359 | 'should authenticate' : function(err, user) { 360 | assert.equal(user.username, 'bob'); 361 | assert.equal(user.password, 'secret'); 362 | }, 363 | }, 364 | }, 365 | 366 | 'strategy handling a request that is not verified against specific realm': { 367 | topic: function() { 368 | var strategy = new BasicStrategy({ realm: 'Administrators' }, function(userid, password, done) { 369 | done(null, false); 370 | }); 371 | return strategy; 372 | }, 373 | 374 | 'after augmenting with actions': { 375 | topic: function(strategy) { 376 | var self = this; 377 | var req = {}; 378 | strategy.success = function(user) { 379 | self.callback(new Error('should not be called')); 380 | } 381 | strategy.fail = function(challenge) { 382 | self.callback(null, challenge); 383 | } 384 | 385 | req.headers = {}; 386 | req.headers.authorization = 'Basic Ym9iOnNlY3JldA=='; 387 | process.nextTick(function () { 388 | strategy.authenticate(req); 389 | }); 390 | }, 391 | 392 | 'should fail authentication with challenge' : function(err, challenge) { 393 | // fail action was called, resulting in test callback 394 | assert.isNull(err); 395 | assert.equal(challenge, 'Basic realm="Administrators"'); 396 | }, 397 | }, 398 | }, 399 | 400 | 'strategy constructed without a verify callback': { 401 | 'should throw an error': function (strategy) { 402 | assert.throws(function() { new BasicStrategy() }); 403 | }, 404 | }, 405 | 406 | 'strategy with passReqToCallback=true option': { 407 | topic: function() { 408 | var strategy = new BasicStrategy({passReqToCallback:true}, function(req, userid, password, done) { 409 | assert.isNotNull(req); 410 | done(null, { username: userid, password: password }); 411 | }); 412 | return strategy; 413 | }, 414 | 415 | 'after augmenting with actions': { 416 | topic: function(strategy) { 417 | var self = this; 418 | var req = {}; 419 | strategy.success = function(user) { 420 | self.callback(null, user); 421 | } 422 | strategy.fail = function() { 423 | self.callback(new Error('should not be called')); 424 | } 425 | 426 | req.headers = {}; 427 | req.headers.authorization = 'Basic Ym9iOnNlY3JldA=='; 428 | process.nextTick(function () { 429 | strategy.authenticate(req); 430 | }); 431 | }, 432 | 433 | 'should not generate an error' : function(err, user) { 434 | assert.isNull(err); 435 | }, 436 | 'should authenticate' : function(err, user) { 437 | assert.equal(user.username, 'bob'); 438 | assert.equal(user.password, 'secret'); 439 | }, 440 | }, 441 | }, 442 | 443 | }).export(module); 444 | -------------------------------------------------------------------------------- /test/strategies/digest-test.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'); 2 | var assert = require('assert'); 3 | var util = require('util'); 4 | var DigestStrategy = require('passport-http/strategies/digest'); 5 | 6 | 7 | vows.describe('DigestStrategy').addBatch({ 8 | 9 | 'strategy': { 10 | topic: function() { 11 | return new DigestStrategy( 12 | function() {}, 13 | function() {} 14 | ); 15 | }, 16 | 17 | 'should be named digest': function (strategy) { 18 | assert.equal(strategy.name, 'digest'); 19 | }, 20 | }, 21 | 22 | 'strategy handling a valid request': { 23 | topic: function() { 24 | var strategy = new DigestStrategy( 25 | function(username, done) { 26 | done(null, { username: username }, 'secret'); 27 | }, 28 | function(options, done) { 29 | done(null, true); 30 | } 31 | ); 32 | return strategy; 33 | }, 34 | 35 | 'after augmenting with actions': { 36 | topic: function(strategy) { 37 | var self = this; 38 | var req = {}; 39 | strategy.success = function(user) { 40 | self.callback(null, user); 41 | } 42 | strategy.fail = function() { 43 | self.callback(new Error('should not be called')); 44 | } 45 | 46 | req.url = '/'; 47 | req.method = 'HEAD'; 48 | req.headers = {}; 49 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS", uri="/", response="22e3e0a9bbefeb9d229905230cb9ddc8"'; 50 | process.nextTick(function () { 51 | strategy.authenticate(req); 52 | }); 53 | }, 54 | 55 | 'should not generate an error' : function(err, user) { 56 | assert.isNull(err); 57 | }, 58 | 'should authenticate' : function(err, user) { 59 | assert.equal(user.username, 'bob'); 60 | }, 61 | }, 62 | }, 63 | 64 | 65 | 'strategy handling a valid request with an empty username': { 66 | topic: function() { 67 | var strategy = new DigestStrategy( 68 | function(username, done) { 69 | done(null, { username: username }, 'secret'); 70 | }, 71 | function(options, done) { 72 | done(null, true); 73 | } 74 | ); 75 | return strategy; 76 | }, 77 | 78 | 'after augmenting with actions': { 79 | topic: function(strategy) { 80 | var self = this; 81 | var req = {}; 82 | strategy.success = function(user) { 83 | self.callback(new Error('should not be called')); 84 | } 85 | strategy.fail = function(challenge) { 86 | self.callback(null, challenge); 87 | } 88 | 89 | req.url = '/'; 90 | req.method = 'HEAD'; 91 | req.headers = {}; 92 | req.headers.authorization = 'Digest username="", realm="Users", nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS", uri="/", response="459ea26315b4ac2ad14537695acd5a9b"'; 93 | process.nextTick(function () { 94 | strategy.authenticate(req); 95 | }); 96 | }, 97 | 98 | 'should not generate an error' : function(err, user) { 99 | assert.isNull(err); 100 | }, 101 | 'should fail authentication with challenge' : function(err, challenge) { 102 | assert.match(challenge, /^Digest realm="Users", nonce="\w{32}"$/); 103 | }, 104 | }, 105 | }, 106 | 107 | 'strategy handling a valid request with credentials not separated by spaces': { 108 | topic: function() { 109 | var strategy = new DigestStrategy( 110 | function(username, done) { 111 | done(null, { username: username }, 'secret'); 112 | }, 113 | function(options, done) { 114 | done(null, true); 115 | } 116 | ); 117 | return strategy; 118 | }, 119 | 120 | 'after augmenting with actions': { 121 | topic: function(strategy) { 122 | var self = this; 123 | var req = {}; 124 | strategy.success = function(user) { 125 | self.callback(null, user); 126 | } 127 | strategy.fail = function() { 128 | self.callback(new Error('should not be called')); 129 | } 130 | 131 | req.url = '/'; 132 | req.method = 'HEAD'; 133 | req.headers = {}; 134 | req.headers.authorization = 'Digest username="bob",realm="Users",nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS",uri="/",response="22e3e0a9bbefeb9d229905230cb9ddc8"'; 135 | process.nextTick(function () { 136 | strategy.authenticate(req); 137 | }); 138 | }, 139 | 140 | 'should not generate an error' : function(err, user) { 141 | assert.isNull(err); 142 | }, 143 | 'should authenticate' : function(err, user) { 144 | assert.equal(user.username, 'bob'); 145 | }, 146 | }, 147 | }, 148 | 149 | 'strategy handling a valid request and supplying hashed HA1 to secret callback': { 150 | topic: function() { 151 | var strategy = new DigestStrategy( 152 | function(username, done) { 153 | done(null, { username: username }, { ha1: '9e3bcfb22c441e9648cae34400c648d0' }); 154 | }, 155 | function(options, done) { 156 | done(null, true); 157 | } 158 | ); 159 | return strategy; 160 | }, 161 | 162 | 'after augmenting with actions': { 163 | topic: function(strategy) { 164 | var self = this; 165 | var req = {}; 166 | strategy.success = function(user) { 167 | self.callback(null, user); 168 | } 169 | strategy.fail = function() { 170 | self.callback(new Error('should not be called')); 171 | } 172 | 173 | req.url = '/'; 174 | req.method = 'HEAD'; 175 | req.headers = {}; 176 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS", uri="/", response="22e3e0a9bbefeb9d229905230cb9ddc8"'; 177 | process.nextTick(function () { 178 | strategy.authenticate(req); 179 | }); 180 | }, 181 | 182 | 'should not generate an error' : function(err, user) { 183 | assert.isNull(err); 184 | }, 185 | 'should authenticate' : function(err, user) { 186 | assert.equal(user.username, 'bob'); 187 | }, 188 | }, 189 | }, 190 | 191 | 'strategy handling a valid request without optional validate callback': { 192 | topic: function() { 193 | var strategy = new DigestStrategy( 194 | function(username, done) { 195 | done(null, { username: username }, 'secret'); 196 | } 197 | ); 198 | return strategy; 199 | }, 200 | 201 | 'after augmenting with actions': { 202 | topic: function(strategy) { 203 | var self = this; 204 | var req = {}; 205 | strategy.success = function(user) { 206 | self.callback(null, user); 207 | } 208 | strategy.fail = function() { 209 | self.callback(new Error('should not be called')); 210 | } 211 | 212 | req.url = '/'; 213 | req.method = 'HEAD'; 214 | req.headers = {}; 215 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS", uri="/", response="22e3e0a9bbefeb9d229905230cb9ddc8"'; 216 | process.nextTick(function () { 217 | strategy.authenticate(req); 218 | }); 219 | }, 220 | 221 | 'should not generate an error' : function(err, user) { 222 | assert.isNull(err); 223 | }, 224 | 'should authenticate' : function(err, user) { 225 | assert.equal(user.username, 'bob'); 226 | }, 227 | }, 228 | }, 229 | 230 | 'strategy handling a valid request with algorithm set to "MD5"': { 231 | topic: function() { 232 | var strategy = new DigestStrategy({ algorithm: 'MD5' }, 233 | function(username, done) { 234 | done(null, { username: username }, 'secret'); 235 | }, 236 | function(options, done) { 237 | done(null, true); 238 | } 239 | ); 240 | return strategy; 241 | }, 242 | 243 | 'after augmenting with actions': { 244 | topic: function(strategy) { 245 | var self = this; 246 | var req = {}; 247 | strategy.success = function(user) { 248 | self.callback(null, user); 249 | } 250 | strategy.fail = function() { 251 | self.callback(new Error('should not be called')); 252 | } 253 | 254 | req.url = '/'; 255 | req.method = 'HEAD'; 256 | req.headers = {}; 257 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="720rZDMBH44rIsKKSz75zd0fMvcQaL8Y", uri="/", response="1db489e2049a77d27b6f05c917f9aa58", algorithm="MD5"'; 258 | process.nextTick(function () { 259 | strategy.authenticate(req); 260 | }); 261 | }, 262 | 263 | 'should not generate an error' : function(err, user) { 264 | assert.isNull(err); 265 | }, 266 | 'should authenticate' : function(err, user) { 267 | assert.equal(user.username, 'bob'); 268 | }, 269 | }, 270 | }, 271 | 272 | 'strategy handling a valid request with qop set to "auth"': { 273 | topic: function() { 274 | var strategy = new DigestStrategy({ qop: 'auth' }, 275 | function(username, done) { 276 | done(null, { username: username }, 'secret'); 277 | }, 278 | function(options, done) { 279 | if (options.nonce === 'T1vogipt8GzzWyCZt7U3TNV5XsarMW8y' && options.cnonce === 'MTMxOTkx' && options.nc === '00000001') { 280 | done(null, true); 281 | } else { 282 | done(new Error('something is wrong')) 283 | } 284 | } 285 | ); 286 | return strategy; 287 | }, 288 | 289 | 'after augmenting with actions': { 290 | topic: function(strategy) { 291 | var self = this; 292 | var req = {}; 293 | strategy.success = function(user) { 294 | self.callback(null, user); 295 | } 296 | strategy.fail = function() { 297 | self.callback(new Error('should not be called')); 298 | } 299 | strategy.error = function() { 300 | self.callback(new Error('should not be called')); 301 | } 302 | 303 | req.url = '/'; 304 | req.method = 'HEAD'; 305 | req.headers = {}; 306 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="T1vogipt8GzzWyCZt7U3TNV5XsarMW8y", uri="/", cnonce="MTMxOTkx", nc=00000001, qop="auth", response="7495a912e5c52e1e9ac92793c6f5c229"'; 307 | process.nextTick(function () { 308 | strategy.authenticate(req); 309 | }); 310 | }, 311 | 312 | 'should not generate an error' : function(err, user) { 313 | assert.isNull(err); 314 | }, 315 | 'should authenticate' : function(err, user) { 316 | assert.equal(user.username, 'bob'); 317 | }, 318 | }, 319 | }, 320 | 321 | 'strategy handling a valid request with qop set to "auth" and equal sign in URL': { 322 | topic: function() { 323 | var strategy = new DigestStrategy({ qop: 'auth' }, 324 | function(username, done) { 325 | done(null, { username: username }, 'secret'); 326 | }, 327 | function(options, done) { 328 | if (options.nonce === '3sauEztFK9HB2vjADmXE4sQbtwpGCFZ2' && options.cnonce === 'MTM0MTkw' && options.nc === '00000001') { 329 | done(null, { nonce: options.nonce, cnonce: options.cnonce, nc: options.nc }); 330 | } else { 331 | done(new Error('something is wrong')) 332 | } 333 | } 334 | ); 335 | return strategy; 336 | }, 337 | 338 | 'after augmenting with actions': { 339 | topic: function(strategy) { 340 | var self = this; 341 | var req = {}; 342 | strategy.success = function(user) { 343 | self.callback(null, user); 344 | } 345 | strategy.fail = function() { 346 | self.callback(new Error('should not be called')); 347 | } 348 | strategy.error = function() { 349 | self.callback(new Error('should not be called')); 350 | } 351 | 352 | req.url = '/sessions.json?sEcho=2&iColumns=12'; 353 | req.method = 'HEAD'; 354 | req.headers = {}; 355 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="3sauEztFK9HB2vjADmXE4sQbtwpGCFZ2", uri="/sessions.json?sEcho=2&iColumns=12", cnonce="MTM0MTkw", nc=00000001, qop="auth", response="83e2cb1afbb943a0cde78290c5002607"'; 356 | process.nextTick(function () { 357 | strategy.authenticate(req); 358 | }); 359 | }, 360 | 361 | 'should not generate an error' : function(err, user) { 362 | assert.isNull(err); 363 | }, 364 | 'should authenticate' : function(err, user) { 365 | assert.equal(user.username, 'bob'); 366 | }, 367 | }, 368 | }, 369 | 370 | 'strategy handling a valid request with credentials not separated by spaces with qop set to "auth" and equal sign in URL': { 371 | topic: function() { 372 | var strategy = new DigestStrategy({ qop: 'auth' }, 373 | function(username, done) { 374 | done(null, { username: username }, 'secret'); 375 | }, 376 | function(options, done) { 377 | if (options.nonce === '3sauEztFK9HB2vjADmXE4sQbtwpGCFZ2' && options.cnonce === 'MTM0MTkw' && options.nc === '00000001') { 378 | done(null, { nonce: options.nonce, cnonce: options.cnonce, nc: options.nc }); 379 | } else { 380 | done(new Error('something is wrong')) 381 | } 382 | } 383 | ); 384 | return strategy; 385 | }, 386 | 387 | 'after augmenting with actions': { 388 | topic: function(strategy) { 389 | var self = this; 390 | var req = {}; 391 | strategy.success = function(user) { 392 | self.callback(null, user); 393 | } 394 | strategy.fail = function() { 395 | self.callback(new Error('should not be called')); 396 | } 397 | strategy.error = function() { 398 | self.callback(new Error('should not be called')); 399 | } 400 | 401 | req.url = '/sessions.json?sEcho=2&iColumns=12'; 402 | req.method = 'HEAD'; 403 | req.headers = {}; 404 | req.headers.authorization = 'Digest username="bob",realm="Users",nonce="3sauEztFK9HB2vjADmXE4sQbtwpGCFZ2",uri="/sessions.json?sEcho=2&iColumns=12",cnonce="MTM0MTkw",nc=00000001,qop="auth",response="83e2cb1afbb943a0cde78290c5002607"'; 405 | process.nextTick(function () { 406 | strategy.authenticate(req); 407 | }); 408 | }, 409 | 410 | 'should not generate an error' : function(err, user) { 411 | assert.isNull(err); 412 | }, 413 | 'should authenticate' : function(err, user) { 414 | assert.equal(user.username, 'bob'); 415 | }, 416 | }, 417 | }, 418 | 419 | 'strategy handling a valid request with qop set to "auth" and algorithm set to "MD5-sess"': { 420 | topic: function() { 421 | var strategy = new DigestStrategy({ qop: 'auth', algorithm: 'MD5-sess' }, 422 | function(username, done) { 423 | done(null, { username: username }, 'secret'); 424 | }, 425 | function(options, done) { 426 | done(null, true); 427 | } 428 | ); 429 | return strategy; 430 | }, 431 | 432 | 'after augmenting with actions': { 433 | topic: function(strategy) { 434 | var self = this; 435 | var req = {}; 436 | strategy.success = function(user) { 437 | self.callback(null, user); 438 | } 439 | strategy.fail = function() { 440 | self.callback(new Error('should not be called')); 441 | } 442 | 443 | req.url = '/'; 444 | req.method = 'HEAD'; 445 | req.headers = {}; 446 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="Ag1nqGybX7GXpXGWjTJs0pCCRboeLnbI", uri="/", cnonce="MTMxOTkx", nc=00000001, qop="auth", response="db5b2989137bf89622d8cb1ea583eec9", algorithm="MD5-sess"'; 447 | process.nextTick(function () { 448 | strategy.authenticate(req); 449 | }); 450 | }, 451 | 452 | 'should not generate an error' : function(err, user) { 453 | assert.isNull(err); 454 | }, 455 | 'should authenticate' : function(err, user) { 456 | assert.equal(user.username, 'bob'); 457 | }, 458 | }, 459 | }, 460 | 461 | 'strategy handling an invalid request with qop set to "auth-int" and auth-int support not implemented': { 462 | topic: function() { 463 | var strategy = new DigestStrategy({ qop: 'auth-int' }, 464 | function(username, done) { 465 | done(null, { username: username }, 'secret'); 466 | }, 467 | function(options, done) { 468 | done(null, true); 469 | } 470 | ); 471 | return strategy; 472 | }, 473 | 474 | 'after augmenting with actions': { 475 | topic: function(strategy) { 476 | var self = this; 477 | var req = {}; 478 | strategy.success = function(user) { 479 | self.callback(new Error('should not be called')); 480 | } 481 | strategy.fail = function() { 482 | self.callback(new Error('should not be called')); 483 | } 484 | strategy.error = function(err) { 485 | self.callback(null, err); 486 | } 487 | 488 | req.url = '/'; 489 | req.method = 'POST'; 490 | req.headers = {}; 491 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="2HQNNVPOZXBz47jSs3POzWDJn15xSsJp", uri="/", cnonce="MTMxOTky", nc=00000001, qop="auth-int", response="9c63f9e51406979ba661dd820cc21122"'; 492 | // TODO: When support for auth-int is implemented, a raw entity body 493 | // will need to be provided. 494 | process.nextTick(function () { 495 | strategy.authenticate(req); 496 | }); 497 | }, 498 | 499 | 'should not call success or fail' : function(err, e) { 500 | assert.isNull(err); 501 | }, 502 | 'should call error' : function(err, e) { 503 | assert.instanceOf(e, Error); 504 | }, 505 | }, 506 | }, 507 | 508 | 'strategy handling a request with invalid password': { 509 | topic: function() { 510 | var strategy = new DigestStrategy( 511 | function(username, done) { 512 | done(null, { username: username }, 'idontknow'); 513 | }, 514 | function(options, done) { 515 | done(null, true); 516 | } 517 | ); 518 | return strategy; 519 | }, 520 | 521 | 'after augmenting with actions': { 522 | topic: function(strategy) { 523 | var self = this; 524 | var req = {}; 525 | strategy.success = function(user) { 526 | self.callback(new Error('should not be called')); 527 | } 528 | strategy.fail = function(challenge) { 529 | self.callback(null, challenge); 530 | } 531 | 532 | req.url = '/'; 533 | req.method = 'HEAD'; 534 | req.headers = {}; 535 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS", uri="/", response="22e3e0a9bbefeb9d229905230cb9ddc8"'; 536 | process.nextTick(function () { 537 | strategy.authenticate(req); 538 | }); 539 | }, 540 | 541 | 'should fail authentication with challenge' : function(err, challenge) { 542 | // fail action was called, resulting in test callback 543 | assert.isNull(err); 544 | assert.match(challenge, /^Digest realm="Users", nonce="\w{32}"$/); 545 | }, 546 | }, 547 | }, 548 | 549 | 'strategy handling a request that does not have a shared secret': { 550 | topic: function() { 551 | var strategy = new DigestStrategy( 552 | function(username, done) { 553 | done(null, false); 554 | }, 555 | function(options, done) { 556 | done(null, true); 557 | } 558 | ); 559 | return strategy; 560 | }, 561 | 562 | 'after augmenting with actions': { 563 | topic: function(strategy) { 564 | var self = this; 565 | var req = {}; 566 | strategy.success = function(user) { 567 | self.callback(new Error('should not be called')); 568 | } 569 | strategy.fail = function(challenge) { 570 | self.callback(null, challenge); 571 | } 572 | 573 | req.url = '/'; 574 | req.method = 'HEAD'; 575 | req.headers = {}; 576 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS", uri="/", response="22e3e0a9bbefeb9d229905230cb9ddc8"'; 577 | process.nextTick(function () { 578 | strategy.authenticate(req); 579 | }); 580 | }, 581 | 582 | 'should fail authentication with challenge' : function(err, challenge) { 583 | // fail action was called, resulting in test callback 584 | assert.isNull(err); 585 | assert.match(challenge, /^Digest realm="Users", nonce="\w{32}"$/); 586 | }, 587 | }, 588 | }, 589 | 590 | 'strategy handling a request that is not validated': { 591 | topic: function() { 592 | var strategy = new DigestStrategy( 593 | function(username, done) { 594 | done(null, { username: username }, 'secret'); 595 | }, 596 | function(options, done) { 597 | done(null, false); 598 | } 599 | ); 600 | return strategy; 601 | }, 602 | 603 | 'after augmenting with actions': { 604 | topic: function(strategy) { 605 | var self = this; 606 | var req = {}; 607 | strategy.success = function(user) { 608 | self.callback(new Error('should not be called')); 609 | } 610 | strategy.fail = function(challenge) { 611 | self.callback(null, challenge); 612 | } 613 | 614 | req.url = '/'; 615 | req.method = 'HEAD'; 616 | req.headers = {}; 617 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS", uri="/", response="22e3e0a9bbefeb9d229905230cb9ddc8"'; 618 | process.nextTick(function () { 619 | strategy.authenticate(req); 620 | }); 621 | }, 622 | 623 | 'should fail authentication with challenge' : function(err, challenge) { 624 | // fail action was called, resulting in test callback 625 | assert.isNull(err); 626 | assert.match(challenge, /^Digest realm="Users", nonce="\w{32}"$/); 627 | }, 628 | }, 629 | }, 630 | 631 | 'strategy handling a request that encounters an error while finding shared secret': { 632 | topic: function() { 633 | var strategy = new DigestStrategy( 634 | function(username, done) { 635 | done(new Error('something went wrong')); 636 | }, 637 | function(options, done) { 638 | done(null, true); 639 | } 640 | ); 641 | return strategy; 642 | }, 643 | 644 | 'after augmenting with actions': { 645 | topic: function(strategy) { 646 | var self = this; 647 | var req = {}; 648 | strategy.success = function(user) { 649 | self.callback(new Error('should not be called')); 650 | } 651 | strategy.fail = function(challenge) { 652 | self.callback(new Error('should not be called')); 653 | } 654 | strategy.error = function(err) { 655 | self.callback(null, err); 656 | } 657 | 658 | req.url = '/'; 659 | req.method = 'HEAD'; 660 | req.headers = {}; 661 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS", uri="/", response="22e3e0a9bbefeb9d229905230cb9ddc8"'; 662 | process.nextTick(function () { 663 | strategy.authenticate(req); 664 | }); 665 | }, 666 | 667 | 'should not call success or fail' : function(err, e) { 668 | assert.isNull(err); 669 | }, 670 | 'should call error' : function(err, e) { 671 | assert.instanceOf(e, Error); 672 | }, 673 | }, 674 | }, 675 | 676 | 'strategy handling a request that encounters an error during validation': { 677 | topic: function() { 678 | var strategy = new DigestStrategy( 679 | function(username, done) { 680 | done(null, { username: username }, 'secret'); 681 | }, 682 | function(options, done) { 683 | done(new Error('something went wrong')); 684 | } 685 | ); 686 | return strategy; 687 | }, 688 | 689 | 'after augmenting with actions': { 690 | topic: function(strategy) { 691 | var self = this; 692 | var req = {}; 693 | strategy.success = function(user) { 694 | self.callback(new Error('should not be called')); 695 | } 696 | strategy.fail = function(challenge) { 697 | self.callback(new Error('should not be called')); 698 | } 699 | strategy.error = function(err) { 700 | self.callback(null, err); 701 | } 702 | 703 | req.url = '/'; 704 | req.method = 'HEAD'; 705 | req.headers = {}; 706 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS", uri="/", response="22e3e0a9bbefeb9d229905230cb9ddc8"'; 707 | process.nextTick(function () { 708 | strategy.authenticate(req); 709 | }); 710 | }, 711 | 712 | 'should not call success or fail' : function(err, e) { 713 | assert.isNull(err); 714 | }, 715 | 'should call error' : function(err, e) { 716 | assert.instanceOf(e, Error); 717 | }, 718 | }, 719 | }, 720 | 721 | 'strategy handling a request without authorization credentials': { 722 | topic: function() { 723 | var strategy = new DigestStrategy( 724 | function(username, done) { 725 | done(null, { username: username }, 'secret'); 726 | }, 727 | function(options, done) { 728 | done(null, true); 729 | } 730 | ); 731 | return strategy; 732 | }, 733 | 734 | 'after augmenting with actions': { 735 | topic: function(strategy) { 736 | var self = this; 737 | var req = {}; 738 | strategy.success = function(user) { 739 | self.callback(new Error('should not be called')); 740 | } 741 | strategy.fail = function(challenge) { 742 | self.callback(null, challenge); 743 | } 744 | 745 | req.url = '/'; 746 | req.headers = {}; 747 | process.nextTick(function () { 748 | strategy.authenticate(req); 749 | }); 750 | }, 751 | 752 | 'should fail authentication with challenge' : function(err, challenge) { 753 | // fail action was called, resulting in test callback 754 | assert.isNull(err); 755 | assert.match(challenge, /^Digest realm="Users", nonce="\w{32}"$/); 756 | }, 757 | }, 758 | }, 759 | 760 | 'strategy handling a request with non-Digest authorization credentials': { 761 | topic: function() { 762 | var strategy = new DigestStrategy( 763 | function(username, done) { 764 | done(null, { username: username }, 'secret'); 765 | }, 766 | function(options, done) { 767 | done(null, true); 768 | } 769 | ); 770 | return strategy; 771 | }, 772 | 773 | 'after augmenting with actions': { 774 | topic: function(strategy) { 775 | var self = this; 776 | var req = {}; 777 | strategy.success = function(user) { 778 | self.callback(new Error('should not be called')); 779 | } 780 | strategy.fail = function(challenge) { 781 | self.callback(null, challenge); 782 | } 783 | 784 | req.url = '/'; 785 | req.method = 'HEAD'; 786 | req.headers = {}; 787 | req.headers.authorization = 'XXXXX username="bob", realm="Users", nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS", uri="/", response="22e3e0a9bbefeb9d229905230cb9ddc8"'; 788 | process.nextTick(function () { 789 | strategy.authenticate(req); 790 | }); 791 | }, 792 | 793 | 'should fail authentication with challenge' : function(err, challenge) { 794 | // fail action was called, resulting in test callback 795 | assert.isNull(err); 796 | assert.match(challenge, /^Digest realm="Users", nonce="\w{32}"$/); 797 | }, 798 | }, 799 | }, 800 | 801 | 'strategy handling a request with malformed authorization header': { 802 | topic: function() { 803 | var strategy = new DigestStrategy( 804 | function(username, done) { 805 | done(null, { username: username }, 'secret'); 806 | }, 807 | function(options, done) { 808 | done(null, true); 809 | } 810 | ); 811 | return strategy; 812 | }, 813 | 814 | 'after augmenting with actions': { 815 | topic: function(strategy) { 816 | var self = this; 817 | var req = {}; 818 | strategy.success = function(user) { 819 | self.callback(new Error('should not be called')); 820 | } 821 | strategy.fail = function(status) { 822 | self.callback(null, status); 823 | } 824 | 825 | req.url = '/'; 826 | req.method = 'HEAD'; 827 | req.headers = {}; 828 | req.headers.authorization = 'Digest'; 829 | process.nextTick(function () { 830 | strategy.authenticate(req); 831 | }); 832 | }, 833 | 834 | 'should fail authentication with 400 Bad Request' : function(err, status) { 835 | // fail action was called, resulting in test callback 836 | assert.isNull(err); 837 | assert.equal(status, 400); 838 | }, 839 | }, 840 | }, 841 | 842 | 'strategy handling a request with malformed authorization credentials': { 843 | topic: function() { 844 | var strategy = new DigestStrategy( 845 | function(username, done) { 846 | done(null, { username: username }, 'secret'); 847 | }, 848 | function(options, done) { 849 | done(null, true); 850 | } 851 | ); 852 | return strategy; 853 | }, 854 | 855 | 'after augmenting with actions': { 856 | topic: function(strategy) { 857 | var self = this; 858 | var req = {}; 859 | strategy.success = function(user) { 860 | self.callback(new Error('should not be called')); 861 | } 862 | strategy.fail = function(status) { 863 | self.callback(null, status); 864 | } 865 | 866 | req.url = '/'; 867 | req.method = 'HEAD'; 868 | req.headers = {}; 869 | req.headers.authorization = 'Digest *****'; 870 | process.nextTick(function () { 871 | strategy.authenticate(req); 872 | }); 873 | }, 874 | 875 | 'should fail authentication with 400 Bad Request' : function(err, status) { 876 | // fail action was called, resulting in test callback 877 | assert.isNull(err); 878 | assert.equal(status, 400); 879 | }, 880 | }, 881 | }, 882 | 883 | 'strategy handling a request with non-matching uri': { 884 | topic: function() { 885 | var strategy = new DigestStrategy({ algorithm: 'MD5' }, 886 | function(username, done) { 887 | done(null, { username: username }, 'secret'); 888 | }, 889 | function(options, done) { 890 | done(null, true); 891 | } 892 | ); 893 | return strategy; 894 | }, 895 | 896 | 'after augmenting with actions': { 897 | topic: function(strategy) { 898 | var self = this; 899 | var req = {}; 900 | strategy.success = function(user) { 901 | self.callback(new Error('should not be called')); 902 | } 903 | strategy.fail = function(status) { 904 | self.callback(null, status); 905 | } 906 | 907 | req.url = '/admin'; 908 | req.method = 'HEAD'; 909 | req.headers = {}; 910 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="720rZDMBH44rIsKKSz75zd0fMvcQaL8Y", uri="/", response="1db489e2049a77d27b6f05c917f9aa58", algorithm="MD5"'; 911 | process.nextTick(function () { 912 | strategy.authenticate(req); 913 | }); 914 | }, 915 | 916 | 'should fail authentication with 400 Bad Request' : function(err, status) { 917 | // fail action was called, resulting in test callback 918 | assert.isNull(err); 919 | assert.equal(status, 400); 920 | }, 921 | }, 922 | }, 923 | 924 | 'strategy handling a request with unknown algorithm': { 925 | topic: function() { 926 | var strategy = new DigestStrategy({ algorithm: 'MD5' }, 927 | function(username, done) { 928 | done(null, { username: username }, 'secret'); 929 | }, 930 | function(options, done) { 931 | done(null, true); 932 | } 933 | ); 934 | return strategy; 935 | }, 936 | 937 | 'after augmenting with actions': { 938 | topic: function(strategy) { 939 | var self = this; 940 | var req = {}; 941 | strategy.success = function(user) { 942 | self.callback(new Error('should not be called')); 943 | } 944 | strategy.fail = function(status) { 945 | self.callback(null, status); 946 | } 947 | 948 | req.url = '/'; 949 | req.method = 'HEAD'; 950 | req.headers = {}; 951 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="720rZDMBH44rIsKKSz75zd0fMvcQaL8Y", uri="/", response="1db489e2049a77d27b6f05c917f9aa58", algorithm="XXX"'; 952 | process.nextTick(function () { 953 | strategy.authenticate(req); 954 | }); 955 | }, 956 | 957 | 'should fail authentication with 400 Bad Request' : function(err, status) { 958 | // fail action was called, resulting in test callback 959 | assert.isNull(err); 960 | assert.equal(status, 400); 961 | }, 962 | }, 963 | }, 964 | 965 | 'strategy handling a request with unknown quality of protection': { 966 | topic: function() { 967 | var strategy = new DigestStrategy({ qop: 'auth' }, 968 | function(username, done) { 969 | done(null, { username: username }, 'secret'); 970 | }, 971 | function(options, done) { 972 | done(null, true); 973 | } 974 | ); 975 | return strategy; 976 | }, 977 | 978 | 'after augmenting with actions': { 979 | topic: function(strategy) { 980 | var self = this; 981 | var req = {}; 982 | strategy.success = function(user) { 983 | self.callback(new Error('should not be called')); 984 | } 985 | strategy.fail = function(status) { 986 | self.callback(null, status); 987 | } 988 | 989 | req.url = '/'; 990 | req.method = 'HEAD'; 991 | req.headers = {}; 992 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="T1vogipt8GzzWyCZt7U3TNV5XsarMW8y", uri="/", cnonce="MTMxOTkx", nc=00000001, qop="xxxx", response="7495a912e5c52e1e9ac92793c6f5c229"'; 993 | process.nextTick(function () { 994 | strategy.authenticate(req); 995 | }); 996 | }, 997 | 998 | 'should fail authentication with 400 Bad Request' : function(err, status) { 999 | // fail action was called, resulting in test callback 1000 | assert.isNull(err); 1001 | assert.equal(status, 400); 1002 | }, 1003 | }, 1004 | }, 1005 | 1006 | 'strategy handling a request with DIGEST scheme in capitalized letters': { 1007 | topic: function() { 1008 | var strategy = new DigestStrategy( 1009 | function(username, done) { 1010 | done(null, { username: username }, 'secret'); 1011 | }, 1012 | function(options, done) { 1013 | done(null, true); 1014 | } 1015 | ); 1016 | return strategy; 1017 | }, 1018 | 1019 | 'after augmenting with actions': { 1020 | topic: function(strategy) { 1021 | var self = this; 1022 | var req = {}; 1023 | strategy.success = function(user) { 1024 | self.callback(null, user); 1025 | } 1026 | strategy.fail = function() { 1027 | self.callback(new Error('should not be called')); 1028 | } 1029 | 1030 | req.url = '/'; 1031 | req.method = 'HEAD'; 1032 | req.headers = {}; 1033 | req.headers.authorization = 'DIGEST username="bob", realm="Users", nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS", uri="/", response="22e3e0a9bbefeb9d229905230cb9ddc8"'; 1034 | process.nextTick(function () { 1035 | strategy.authenticate(req); 1036 | }); 1037 | }, 1038 | 1039 | 'should not generate an error' : function(err, user) { 1040 | assert.isNull(err); 1041 | }, 1042 | 'should authenticate' : function(err, user) { 1043 | assert.equal(user.username, 'bob'); 1044 | }, 1045 | }, 1046 | }, 1047 | 1048 | 'strategy handling a request that is not verified against specific realm': { 1049 | topic: function() { 1050 | var strategy = new DigestStrategy({ realm: 'Administrators' }, 1051 | function(username, done) { 1052 | done(null, false); 1053 | }, 1054 | function(options, done) { 1055 | done(null, true); 1056 | } 1057 | ); 1058 | return strategy; 1059 | }, 1060 | 1061 | 'after augmenting with actions': { 1062 | topic: function(strategy) { 1063 | var self = this; 1064 | var req = {}; 1065 | strategy.success = function(user) { 1066 | self.callback(new Error('should not be called')); 1067 | } 1068 | strategy.fail = function(challenge) { 1069 | self.callback(null, challenge); 1070 | } 1071 | 1072 | req.url = '/'; 1073 | req.headers = {}; 1074 | req.headers.authorization = 'Digest username="bob", realm="Users", nonce="NOIEDJ3hJtqSKaty8KF8xlkaYbItAkiS", uri="/", response="22e3e0a9bbefeb9d229905230cb9ddc8"'; 1075 | process.nextTick(function () { 1076 | strategy.authenticate(req); 1077 | }); 1078 | }, 1079 | 1080 | 'should fail authentication with challenge' : function(err, challenge) { 1081 | // fail action was called, resulting in test callback 1082 | assert.isNull(err); 1083 | assert.match(challenge, /^Digest realm="Administrators", nonce="\w{32}"$/); 1084 | }, 1085 | }, 1086 | }, 1087 | 1088 | 'strategy handling a request without authorization credentials with domain option set': { 1089 | topic: function() { 1090 | var strategy = new DigestStrategy({ domain: '/admin' }, 1091 | function(username, done) { 1092 | done(null, { username: username }, 'secret'); 1093 | }, 1094 | function(options, done) { 1095 | done(null, true); 1096 | } 1097 | ); 1098 | return strategy; 1099 | }, 1100 | 1101 | 'after augmenting with actions': { 1102 | topic: function(strategy) { 1103 | var self = this; 1104 | var req = {}; 1105 | strategy.success = function(user) { 1106 | self.callback(new Error('should not be called')); 1107 | } 1108 | strategy.fail = function(challenge) { 1109 | self.callback(null, challenge); 1110 | } 1111 | 1112 | req.url = '/'; 1113 | req.headers = {}; 1114 | process.nextTick(function () { 1115 | strategy.authenticate(req); 1116 | }); 1117 | }, 1118 | 1119 | 'should fail authentication with challenge' : function(err, challenge) { 1120 | // fail action was called, resulting in test callback 1121 | assert.isNull(err); 1122 | assert.match(challenge, /^Digest realm="Users", domain="\/admin", nonce="\w{32}"$/); 1123 | }, 1124 | }, 1125 | }, 1126 | 1127 | 'strategy handling a request without authorization credentials with multiple domain options set': { 1128 | topic: function() { 1129 | var strategy = new DigestStrategy({ domain: ['/admin', '/private'] }, 1130 | function(username, done) { 1131 | done(null, { username: username }, 'secret'); 1132 | }, 1133 | function(options, done) { 1134 | done(null, true); 1135 | } 1136 | ); 1137 | return strategy; 1138 | }, 1139 | 1140 | 'after augmenting with actions': { 1141 | topic: function(strategy) { 1142 | var self = this; 1143 | var req = {}; 1144 | strategy.success = function(user) { 1145 | self.callback(new Error('should not be called')); 1146 | } 1147 | strategy.fail = function(challenge) { 1148 | self.callback(null, challenge); 1149 | } 1150 | 1151 | req.url = '/'; 1152 | req.headers = {}; 1153 | process.nextTick(function () { 1154 | strategy.authenticate(req); 1155 | }); 1156 | }, 1157 | 1158 | 'should fail authentication with challenge' : function(err, challenge) { 1159 | // fail action was called, resulting in test callback 1160 | assert.isNull(err); 1161 | assert.match(challenge, /^Digest realm="Users", domain="\/admin \/private", nonce="\w{32}"$/); 1162 | }, 1163 | }, 1164 | }, 1165 | 1166 | 'strategy handling a request without authorization credentials with opaque option set': { 1167 | topic: function() { 1168 | var strategy = new DigestStrategy({ opaque: 'abcdefg1234' }, 1169 | function(username, done) { 1170 | done(null, { username: username }, 'secret'); 1171 | }, 1172 | function(options, done) { 1173 | done(null, true); 1174 | } 1175 | ); 1176 | return strategy; 1177 | }, 1178 | 1179 | 'after augmenting with actions': { 1180 | topic: function(strategy) { 1181 | var self = this; 1182 | var req = {}; 1183 | strategy.success = function(user) { 1184 | self.callback(new Error('should not be called')); 1185 | } 1186 | strategy.fail = function(challenge) { 1187 | self.callback(null, challenge); 1188 | } 1189 | 1190 | req.url = '/'; 1191 | req.headers = {}; 1192 | process.nextTick(function () { 1193 | strategy.authenticate(req); 1194 | }); 1195 | }, 1196 | 1197 | 'should fail authentication with challenge' : function(err, challenge) { 1198 | // fail action was called, resulting in test callback 1199 | assert.isNull(err); 1200 | assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", opaque="abcdefg1234"$/); 1201 | }, 1202 | }, 1203 | }, 1204 | 1205 | 'strategy handling a request without authorization credentials with algorithm option set': { 1206 | topic: function() { 1207 | var strategy = new DigestStrategy({ algorithm: 'MD5-sess' }, 1208 | function(username, done) { 1209 | done(null, { username: username }, 'secret'); 1210 | }, 1211 | function(options, done) { 1212 | done(null, true); 1213 | } 1214 | ); 1215 | return strategy; 1216 | }, 1217 | 1218 | 'after augmenting with actions': { 1219 | topic: function(strategy) { 1220 | var self = this; 1221 | var req = {}; 1222 | strategy.success = function(user) { 1223 | self.callback(new Error('should not be called')); 1224 | } 1225 | strategy.fail = function(challenge) { 1226 | self.callback(null, challenge); 1227 | } 1228 | 1229 | req.url = '/'; 1230 | req.headers = {}; 1231 | process.nextTick(function () { 1232 | strategy.authenticate(req); 1233 | }); 1234 | }, 1235 | 1236 | 'should fail authentication with challenge' : function(err, challenge) { 1237 | // fail action was called, resulting in test callback 1238 | assert.isNull(err); 1239 | assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", algorithm=MD5-sess$/); 1240 | }, 1241 | }, 1242 | }, 1243 | 1244 | 'strategy handling a request without authorization credentials with qop option set': { 1245 | topic: function() { 1246 | var strategy = new DigestStrategy({ qop: 'auth' }, 1247 | function(username, done) { 1248 | done(null, { username: username }, 'secret'); 1249 | }, 1250 | function(options, done) { 1251 | done(null, true); 1252 | } 1253 | ); 1254 | return strategy; 1255 | }, 1256 | 1257 | 'after augmenting with actions': { 1258 | topic: function(strategy) { 1259 | var self = this; 1260 | var req = {}; 1261 | strategy.success = function(user) { 1262 | self.callback(new Error('should not be called')); 1263 | } 1264 | strategy.fail = function(challenge) { 1265 | self.callback(null, challenge); 1266 | } 1267 | 1268 | req.url = '/'; 1269 | req.headers = {}; 1270 | process.nextTick(function () { 1271 | strategy.authenticate(req); 1272 | }); 1273 | }, 1274 | 1275 | 'should fail authentication with challenge' : function(err, challenge) { 1276 | // fail action was called, resulting in test callback 1277 | assert.isNull(err); 1278 | assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", qop="auth"$/); 1279 | }, 1280 | }, 1281 | }, 1282 | 1283 | 'strategy handling a request without authorization credentials with multiple qop options set': { 1284 | topic: function() { 1285 | var strategy = new DigestStrategy({ qop: ['auth', 'auth-int'] }, 1286 | function(username, done) { 1287 | done(null, { username: username }, 'secret'); 1288 | }, 1289 | function(options, done) { 1290 | done(null, true); 1291 | } 1292 | ); 1293 | return strategy; 1294 | }, 1295 | 1296 | 'after augmenting with actions': { 1297 | topic: function(strategy) { 1298 | var self = this; 1299 | var req = {}; 1300 | strategy.success = function(user) { 1301 | self.callback(new Error('should not be called')); 1302 | } 1303 | strategy.fail = function(challenge) { 1304 | self.callback(null, challenge); 1305 | } 1306 | 1307 | req.url = '/'; 1308 | req.headers = {}; 1309 | process.nextTick(function () { 1310 | strategy.authenticate(req); 1311 | }); 1312 | }, 1313 | 1314 | 'should fail authentication with challenge' : function(err, challenge) { 1315 | // fail action was called, resulting in test callback 1316 | assert.isNull(err); 1317 | assert.match(challenge, /^Digest realm="Users", nonce="\w{32}", qop="auth,auth-int"$/); 1318 | }, 1319 | }, 1320 | }, 1321 | 1322 | 'strategy constructed without a secret callback or validate callback': { 1323 | 'should throw an error': function (strategy) { 1324 | assert.throws(function() { new DigestStrategy() }); 1325 | }, 1326 | }, 1327 | 1328 | }).export(module); 1329 | --------------------------------------------------------------------------------