├── .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 | [](https://www.npmjs.com/package/passport-http)
28 | [](https://travis-ci.org/jaredhanson/passport-http)
29 | [](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 |
--------------------------------------------------------------------------------