├── .github
└── FUNDING.yml
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── Makefile
├── README.md
├── lib
└── passport-http-oauth
│ ├── index.js
│ ├── multihash.js
│ └── strategies
│ ├── consumer.js
│ ├── token.js
│ └── utils.js
├── package.json
└── test
├── index-test.js
├── multihash-test.js
└── strategies
├── consumer-test.js
├── token-test.js
└── utils-test.js
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: jaredhanson
2 | ko_fi: jaredhanson
3 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2012-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-OAuth
2 |
3 | HTTP OAuth authentication strategy for [Passport](https://github.com/jaredhanson/passport).
4 |
5 | This module lets you authenticate HTTP requests using the authorization scheme
6 | defined by the [OAuth](http://tools.ietf.org/html/rfc5849) 1.0 protocol. OAuth
7 | is typically used protect API endpoints, including endpoints defined by the
8 | OAuth protocol itself, as well as other endpoints exposed by the server.
9 |
10 | By plugging into Passport, OAuth API authentication can be easily and
11 | unobtrusively integrated into any application or framework that supports [Connect](http://www.senchalabs.org/connect/)-style
12 | middleware, including [Express](http://expressjs.com/).
13 |
14 | Note that this strategy provides support for implementing OAuth as a service
15 | provider. If your application is implementing OAuth as a client for delegated
16 | authentication (for example, using [Facebook](https://github.com/jaredhanson/passport-facebook)
17 | or [Twitter](https://github.com/jaredhanson/passport-twitter)), please see
18 | [Passport-OAuth](https://github.com/jaredhanson/passport-oauth) for the
19 | appropriate strategy.
20 |
21 |
22 |
23 | :heart: [Sponsors](https://www.passportjs.org/sponsors/?utm_source=github&utm_medium=referral&utm_campaign=passport-http-oauth&utm_content=nav-sponsors)
24 |
25 |
26 |
27 | [](https://www.npmjs.com/package/passport-http-oauth)
28 | [](https://travis-ci.org/jaredhanson/passport-http-oauth)
29 | [](https://coveralls.io/github/jaredhanson/passport-http-oauth)
30 | [...](https://github.com/jaredhanson/passport-http-oauth/wiki/Status)
31 |
32 | ## Install
33 |
34 | $ npm install passport-http-oauth
35 |
36 | ## Usage of Consumer Strategy
37 |
38 | #### Configure Strategy
39 |
40 | The OAuth consumer authentication strategy authenticates consumers based on a
41 | consumer key and secret (and optionally a temporary request token and secret).
42 | The strategy requires a `consumer` callback, `token` callback, and `validate`
43 | callback. The secrets supplied by the `consumer` and `token` callbacks are used
44 | to compute a signature, and authentication fails if it does not match the
45 | request signature. `consumer` as supplied by the `consumer` callback is the
46 | authenticating entity of this strategy, and will be set by Passport at
47 | `req.user`.
48 |
49 | passport.use('consumer', new ConsumerStrategy(
50 | function(consumerKey, done) {
51 | Consumer.findByKey({ key: consumerKey }, function (err, consumer) {
52 | if (err) { return done(err); }
53 | if (!consumer) { return done(null, false); }
54 | return done(null, consumer, consumer.secret);
55 | });
56 | },
57 | function(requestToken, done) {
58 | RequestToken.findOne(requestToken, function (err, token) {
59 | if (err) { return done(err); }
60 | if (!token) { return done(null, false); }
61 | // third argument is optional info. typically used to pass
62 | // details needed to authorize the request (ex: `verifier`)
63 | return done(null, token.secret, { verifier: token.verifier });
64 | });
65 | },
66 | function(timestamp, nonce, done) {
67 | // validate the timestamp and nonce as necessary
68 | done(null, true)
69 | }
70 | ));
71 |
72 | #### Authenticate Requests
73 |
74 | Use `passport.authenticate()`, specifying the `'consumer'` strategy, to
75 | authenticate requests. This strategy is intended for use in the request token
76 | and access token API endpoints, so the `session` option can be set to `false`.
77 |
78 | For example, as route middleware in an [Express](http://expressjs.com/)
79 | application:
80 |
81 | app.post('/access_token',
82 | passport.authenticate('consumer', { session: false }),
83 | oauthorize.accessToken(
84 | // ...
85 | });
86 |
87 | ## Usage of Token Strategy
88 |
89 | #### Configure Strategy
90 |
91 | The OAuth token authentication strategy authenticates users based on an
92 | access token issued to a consumer. The strategy requires a `consumer` callback,
93 | `verify` callback, and `validate` callback. The secrets supplied by the
94 | `consumer` and `verify` callbacks are used to compute a signature, and
95 | authentication fails if it does not match the request signature. `user` as
96 | supplied by the `verify` callback is the authenticating entity of this strategy,
97 | and will be set by Passport at `req.user`.
98 |
99 | passport.use('token', new TokenStrategy(
100 | function(consumerKey, done) {
101 | Consumer.findByKey({ key: consumerKey }, function (err, consumer) {
102 | if (err) { return done(err); }
103 | if (!consumer) { return done(null, false); }
104 | return done(null, consumer, consumer.secret);
105 | });
106 | },
107 | function(accessToken, done) {
108 | AccessToken.findOne(accessToken, function (err, token) {
109 | if (err) { return done(err); }
110 | if (!token) { return done(null, false); }
111 | Users.findOne(token.userId, function(err, user) {
112 | if (err) { return done(err); }
113 | if (!user) { return done(null, false); }
114 | // fourth argument is optional info. typically used to pass
115 | // details needed to authorize the request (ex: `scope`)
116 | return done(null, user, token.secret, { scope: token.scope });
117 | });
118 | });
119 | },
120 | function(timestamp, nonce, done) {
121 | // validate the timestamp and nonce as necessary
122 | done(null, true)
123 | }
124 | ));
125 |
126 | #### Authenticate Requests
127 |
128 | Use `passport.authenticate()`, specifying the `'token'` strategy, to
129 | authenticate requests. This strategy is intended for use in protected API
130 | endpoints, so the `session` option can be set to `false`.
131 |
132 | For example, as route middleware in an [Express](http://expressjs.com/)
133 | application:
134 |
135 | app.get('/api/userinfo',
136 | passport.authenticate('token', { session: false }),
137 | function(req, res) {
138 | res.json(req.user);
139 | });
140 |
141 | ## Issuing Tokens
142 |
143 | [OAuthorize](https://github.com/jaredhanson/oauthorize) is a toolkit for
144 | implementing OAuth service providers. It bundles a suite of middleware
145 | implementing the request token, access token, and user authorization endpoints
146 | of the OAuth 1.0 protocol.
147 |
148 | The toolkit, combined with the `ConsumerStrategy` and a user authentication
149 | strategy can be used to implement the complete OAuth flow, issuing access tokens
150 | to consumers. `TokenStrategy` can then be used to protect API endpoints using
151 | the access tokens issued.
152 |
153 | ## Examples
154 |
155 | The [example](https://github.com/jaredhanson/oauthorize/tree/master/examples/express2)
156 | included with [OAuthorize](https://github.com/jaredhanson/oauthorize)
157 | demonstrates how to implement a complete OAuth service provider.
158 | `ConsumerStrategy` is used to authenticate clients as they request tokens from
159 | the request token and access token endpoints. `TokenStrategy` is used to
160 | authenticate users and clients making requests to API endpoints.
161 |
162 | ## Tests
163 |
164 | $ npm install --dev
165 | $ make test
166 |
167 | [](http://travis-ci.org/jaredhanson/passport-http-oauth)
168 |
169 | ## Credits
170 |
171 | - [Jared Hanson](http://github.com/jaredhanson)
172 |
173 | ## License
174 |
175 | [The MIT License](http://opensource.org/licenses/MIT)
176 |
177 | Copyright (c) 2012-2013 Jared Hanson <[http://jaredhanson.net/](http://jaredhanson.net/)>
178 |
--------------------------------------------------------------------------------
/lib/passport-http-oauth/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 | var ConsumerStrategy = require('./strategies/consumer');
5 | var TokenStrategy = require('./strategies/token');
6 |
7 |
8 | /**
9 | * Strategy version.
10 | */
11 | require('pkginfo')(module, 'version');
12 |
13 | /**
14 | * Expose constructors.
15 | */
16 | exports.ClientStrategy =
17 | exports.ConsumerStrategy = ConsumerStrategy;
18 | exports.TokenStrategy = TokenStrategy;
19 |
--------------------------------------------------------------------------------
/lib/passport-http-oauth/multihash.js:
--------------------------------------------------------------------------------
1 | /**
2 | * `MultiHash` constructor.
3 | *
4 | * @api private
5 | */
6 | function MultiHash() {
7 | this._hash = {};
8 | this.__defineGetter__('length', this._length);
9 | }
10 |
11 | /**
12 | * Test if `key` is set.
13 | *
14 | * @param {String} key
15 | * @return {Boolean} _true_ if set, _false_ otherwise
16 | * @api private
17 | */
18 | MultiHash.prototype.has = function(key) {
19 | return (this._hash[key] !== undefined);
20 | }
21 |
22 | /**
23 | * Number of values set for `key`.
24 | *
25 | * @param {String} key
26 | * @return {Number}
27 | * @api private
28 | */
29 | MultiHash.prototype.count = function(key) {
30 | return this.has(key) ? this._hash[key].length : 0;
31 | }
32 |
33 | /**
34 | * Array of keys.
35 | *
36 | * @return {Array}
37 | * @api private
38 | */
39 | MultiHash.prototype.keys = function() {
40 | return Object.keys(this._hash);
41 | }
42 |
43 | /**
44 | * Array of values for `key`.
45 | *
46 | * @param {String} key
47 | * @return {Array}
48 | * @api private
49 | */
50 | MultiHash.prototype.values = function(key) {
51 | return this.has(key) ? this._hash[key] : [];
52 | }
53 |
54 | /**
55 | * Put `value` for `key`.
56 | *
57 | * Multi-hashes can contain multiple values for the same key. Putting a value
58 | * to a key will add a value, rather than replace an existing value.
59 | *
60 | * @param {String} key
61 | * @param {Mixed} value
62 | * @api private
63 | */
64 | MultiHash.prototype.put = function(key, value) {
65 | if (this.has(key)) {
66 | this._hash[key].push(value);
67 | } else {
68 | this._hash[key] = [ value ];
69 | }
70 | }
71 |
72 | /**
73 | * Add keys and values of `obj`.
74 | *
75 | * @param {Object} obj
76 | * @param {Mixed} value
77 | * @api private
78 | */
79 | MultiHash.prototype.add = function(obj) {
80 | if (!obj) { return; }
81 | var self = this;
82 | Object.keys(obj).forEach(function(key) {
83 | self.put(key, obj[key]);
84 | });
85 | }
86 |
87 | /**
88 | * Delete `key`.
89 | *
90 | * @param {String} key
91 | * @api private
92 | */
93 | MultiHash.prototype.del = function(key) {
94 | delete this._hash[key];
95 | }
96 |
97 | /**
98 | * Number of keys in the multi-hash.
99 | *
100 | * @return {Number}
101 | * @api private
102 | */
103 | MultiHash.prototype._length = function() {
104 | return this.keys().length;
105 | }
106 |
107 |
108 | /**
109 | * Expose `MultiHash`.
110 | */
111 | module.exports = MultiHash;
112 |
--------------------------------------------------------------------------------
/lib/passport-http-oauth/strategies/consumer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 | var passport = require('passport')
5 | , util = require('util')
6 | , utils = require('./utils');
7 |
8 |
9 | /**
10 | * `ConsumerStrategy` constructor.
11 | *
12 | * The OAuth consumer authentication strategy authenticates requests based on
13 | * the `oauth_consumer_key` parameter contained in an HTTP request. This
14 | * parameter can be located in an `Authorization` header field, the request
15 | * entity body, or the URL query parameters.
16 | *
17 | * This strategy requires three functions as callbacks, referred to as
18 | * `consumer`, `token` and `validate`.
19 | *
20 | * The `consumer` callback accepts `consumerKey` and must call `done` supplying
21 | * a `consumer` and `consumerSecret`. The strategy will use the secret to
22 | * compute the signature, failing authentication if it does not match the
23 | * request's signature. Note that `consumer` is the authenticating entity of
24 | * this strategy, and will be set by Passport at `req.user` upon success. If an
25 | * exception occured, `err` should be set.
26 | *
27 | * The `token` callback accepts `requestToken` and must call `done` supplying
28 | * a `tokenSecret` and optional `info`. The strategy will use the secret to
29 | * compute the signature, failing authentication if it does not match the
30 | * request's signature. The optional `info` is typically used to carry
31 | * additional authorization details associated with the token (the verifier, for
32 | * example). `info` will be set by Passport at `req.authInfo`, where it can be
33 | * used by later middleware, avoiding the need to re-query a database for the
34 | * same information. If an exception occured, `err` should be set.
35 | *
36 | * The `validate` callback is optional, accepting `timestamp` and `nonce` as a
37 | * means to protect against replay attacks.
38 | *
39 | *
40 | * Note that despite defining a single authentication scheme, OAuth
41 | * authentication serves two distinct purposes:
42 | * 1. Authenticating consumers (aka clients) that are requesting access to
43 | * protected resources.
44 | * 2. Authenticating users associated with an access token obtained by a
45 | * consumer, with possibly limited scope.
46 | *
47 | * This strategy covers the former purpose (see `TokenStrategy` for the latter).
48 | * Due to the nature of OAuth, in cases where the consumer is attempting to
49 | * obtain an access token, the credentials will also contain a callback URL or a
50 | * previously issued request token and verifier. This information will be
51 | * carried through by Passport in `req.authInfo` to be handled by other
52 | * middleware or routes as necessary.
53 | *
54 | * When the consumer is making a request to the request token endpoint,
55 | * `authInfo` will contain the following properties:
56 | *
57 | * scheme always set to `OAuth`
58 | * oauth.callbackURL URL to redirect the user to after authorization
59 | *
60 | * When the consumer is making a request to the access token endpoint,
61 | * `authInfo` will contain the following properties (in addition to any optional
62 | * info supplied by the application):
63 | *
64 | * scheme always set to `OAuth`
65 | * oauth.token previously obtained request token
66 | * oauth.verifier verification code
67 | *
68 | * This strategy is inteded to be employed in routes for the request token URL
69 | * and access token URL, as defined by the OAuth specification (aka the
70 | * temporary credential endpoint and token endpoint in RFC 5849).
71 | *
72 | * Examples:
73 | *
74 | * passport.use('consumer', new ConsumerStrategy(
75 | * function(consumerKey, done) {
76 | * Consumer.findByKey({ key: consumerKey }, function (err, consumer) {
77 | * if (err) { return done(err); }
78 | * if (!consumer) { return done(null, false); }
79 | * return done(null, consumer, consumer.secret);
80 | * });
81 | * },
82 | * function(requestToken, done) {
83 | * RequestToken.findOne(requestToken, function (err, token) {
84 | * if (err) { return done(err); }
85 | * if (!token) { return done(null, false); }
86 | * // third argument is optional info. typically used to pass
87 | * // details needed to authorize the request (ex: `verifier`)
88 | * return done(null, token.secret, { verifier: token.verifier });
89 | * });
90 | * },
91 | * function(timestamp, nonce, done) {
92 | * // validate the timestamp and nonce as necessary
93 | * done(null, true)
94 | * }
95 | * ));
96 | *
97 | * References:
98 | * - [Temporary Credentials](http://tools.ietf.org/html/rfc5849#section-2.1)
99 | * - [Token Credentials](http://tools.ietf.org/html/rfc5849#section-2.3)
100 | * - [Obtaining an Unauthorized Request Token](http://oauth.net/core/1.0a/#auth_step1)
101 | * - [Obtaining an Access Token](http://oauth.net/core/1.0a/#auth_step3)
102 | * - [Obtaining an Unauthorized Request Token](http://oauth.net/core/1.0/#auth_step1)
103 | * - [Obtaining an Access Token](http://oauth.net/core/1.0/#auth_step3)
104 | *
105 | * @param {Object} options
106 | * @param {Function} consumer
107 | * @param {Function} verify
108 | * @api public
109 | */
110 | function ConsumerStrategy(options, consumer, token, validate) {
111 | if (typeof options == 'function') {
112 | validate = token;
113 | token = consumer;
114 | consumer = options;
115 | options = {};
116 | }
117 | if (!consumer) throw new Error('HTTP OAuth consumer authentication strategy requires a consumer function');
118 | if (!token) throw new Error('HTTP OAuth consumer authentication strategy requires a token function');
119 |
120 | passport.Strategy.call(this);
121 | this.name = 'oauth';
122 | this._consumer = consumer;
123 | this._token = token;
124 | this._validate = validate;
125 | this._host = options.host || null;
126 | this._realm = options.realm || 'Clients';
127 | this._ignoreVersion = options.ignoreVersion || false;
128 | }
129 |
130 | /**
131 | * Inherit from `passport.Strategy`.
132 | */
133 | util.inherits(ConsumerStrategy, passport.Strategy);
134 |
135 | /**
136 | * Authenticate request based on the contents of a HTTP OAuth authorization
137 | * header, body parameters, or query parameters.
138 | *
139 | * @param {Object} req
140 | * @api protected
141 | */
142 | ConsumerStrategy.prototype.authenticate = function(req) {
143 | var params = undefined
144 | , header = null;
145 |
146 | if (req.headers && req.headers['authorization']) {
147 | var parts = req.headers['authorization'].split(' ');
148 | if (parts.length >= 2) {
149 | var scheme = parts[0];
150 | var credentials = null;
151 |
152 | parts.shift();
153 | credentials = parts.join(' ');
154 |
155 | if (/OAuth/i.test(scheme)) {
156 | params = utils.parseHeader(credentials);
157 | header = params;
158 | }
159 | } else {
160 | return this.fail(400);
161 | }
162 | }
163 |
164 | if (req.body && req.body['oauth_signature']) {
165 | if (params) { return this.fail(400); }
166 | params = req.body;
167 | }
168 |
169 | if (req.query && req.query['oauth_signature']) {
170 | if (params) { return this.fail(400); }
171 | token = req.query['access_token'];
172 | params = req.query;
173 | }
174 |
175 | if (!params) { return this.fail(this._challenge()); }
176 |
177 | if (!params['oauth_consumer_key'] ||
178 | !params['oauth_signature_method'] ||
179 | !params['oauth_signature'] ||
180 | !params['oauth_timestamp'] ||
181 | !params['oauth_nonce']) {
182 | return this.fail(this._challenge('parameter_absent'), 400);
183 | }
184 |
185 | var consumerKey = params['oauth_consumer_key']
186 | , requestToken = params['oauth_token']
187 | , signatureMethod = params['oauth_signature_method']
188 | , signature = params['oauth_signature']
189 | , timestamp = params['oauth_timestamp']
190 | , nonce = params['oauth_nonce']
191 | , callback = params['oauth_callback']
192 | , verifier = params['oauth_verifier']
193 | , version = params['oauth_version']
194 |
195 | if (version && version !== '1.0' && !this._ignoreVersion) {
196 | return this.fail(this._challenge('version_rejected'), 400);
197 | }
198 |
199 |
200 | var self = this;
201 | this._consumer(consumerKey, function(err, consumer, consumerSecret) {
202 | if (err) { return self.error(err); }
203 | if (!consumer) { return self.fail(self._challenge('consumer_key_rejected')); }
204 |
205 | if (!requestToken) {
206 | // If no `oauth_token` is present, the consumer is attempting to abtain
207 | // a request token. Validate the request using only the consumer key
208 | // and secret, with the token secret being an empty string.
209 | validate('', function() {
210 | // At this point, the request has been validated and the consumer is
211 | // successfully authenticated. The duty of this strategy is complete.
212 | //
213 | // However, the consumer is attempting to obtain a request token. In
214 | // OAuth, the `oauth_callback` parameter is contained within the
215 | // credentials. This parameter will be passed through as info to
216 | // Passport, to be made availabe at `req.authInfo`. At that point,
217 | // another middleware or route handler can respond as necessary.
218 | var info = {};
219 | info.scheme = 'OAuth';
220 | info.oauth = { callbackURL: callback }
221 |
222 | // WARNING: If the consumer is not using OAuth 1.0a, the
223 | // `oauth_callback` parameter will not be present. Instead, it
224 | // will be supplied when the consumer redirects the user to the
225 | // service provider when obtaining authorization. A service
226 | // provider that unconditionally accepts a URL during this
227 | // phase may be inadvertently assisting in session fixation
228 | // attacks, as described here:
229 | //
230 | // http://oauth.net/advisories/2009-1/
231 | // http://hueniverse.com/2009/04/explaining-the-oauth-session-fixation-attack/
232 | //
233 | // Service providers are encouraged to implement monitoring to
234 | // detect potential attacks, and display advisory notices to
235 | // users.
236 |
237 | return self.success(consumer, info);
238 | });
239 | } else {
240 |
241 | // An `oauth_token` is present, containing a request token. In order to
242 | // validate the request, the corresponding token secret needs to be
243 | // retrieved. The application can supply additional `info` about the
244 | // token which will be passed through as info to Passport and made
245 | // available at `req.authInfo`.
246 | //
247 | // The same database query that is used to retrieve the secret typically
248 | // returns other details encoded into the request token, such as the user
249 | // who authorized it and the consumer it was issued to. These details are
250 | // relevant to middleware or routes further along the chain, and it is an
251 | // optimization to pass them along rather than repeat the same query
252 | // later.
253 | self._token(requestToken, function(err, tokenSecret, info) {
254 | if (err) { return self.error(err); }
255 | if (!tokenSecret) { return self.fail(self._challenge('token_rejected')); }
256 |
257 | validate(tokenSecret, function() {
258 | info = info || {};
259 | info.scheme = 'OAuth';
260 | info.oauth = { token: requestToken, verifier: verifier }
261 |
262 | // WARNING: If the consumer is not using OAuth 1.0a, the
263 | // `oauth_verifier` parameter will not be present. This
264 | // makes it impossible to know if the user who authorized the
265 | // request token is the same user returning to the
266 | // application, as described here:
267 | //
268 | // http://hueniverse.com/2009/04/explaining-the-oauth-session-fixation-attack/
269 |
270 | return self.success(consumer, info);
271 | });
272 | });
273 | }
274 |
275 | function validate(tokenSecret, ok) {
276 | var url = utils.originalURL(req, self._host)
277 | , query = req.query
278 | , body = req.body;
279 |
280 | var sources = [ header, query ];
281 | if (req.headers['content-type'] &&
282 | req.headers['content-type'].slice(0, 'application/x-www-form-urlencoded'.length) ===
283 | 'application/x-www-form-urlencoded') {
284 | sources.push(body);
285 | }
286 |
287 | var normalizedURL = utils.normalizeURI(url)
288 | , normalizedParams = utils.normalizeParams.apply(undefined, sources)
289 | , base = utils.constructBaseString(req.method, normalizedURL, normalizedParams);
290 |
291 | if (signatureMethod == 'HMAC-SHA1') {
292 | var key = utils.encode(consumerSecret) + '&';
293 | if (tokenSecret) { key += utils.encode(tokenSecret); }
294 | var computedSignature = utils.hmacsha1(key, base);
295 |
296 | if (signature !== computedSignature) {
297 | return self.fail(self._challenge('signature_invalid'));
298 | }
299 |
300 | } else if (signatureMethod === 'HMAC-SHA256') {
301 | var key = utils.encode(consumerSecret) + '&';
302 | if (tokenSecret) { key += utils.encode(tokenSecret); }
303 | var computedSignature = utils.hmacsha256(key, base);
304 |
305 | if (signature !== computedSignature) {
306 | return self.fail(self._challenge('signature_invalid'));
307 | }
308 | } else if (signatureMethod == 'PLAINTEXT') {
309 | var computedSignature = utils.plaintext(consumerSecret, tokenSecret);
310 |
311 | if (signature !== computedSignature) {
312 | return self.fail(self._challenge('signature_invalid'));
313 | }
314 | } else {
315 | return self.fail(self._challenge('signature_method_rejected'), 400);
316 | }
317 |
318 | // If execution reaches this point, the request signature has been
319 | // verified and authentication is successful.
320 | if (self._validate) {
321 | // Give the application a chance it validate the timestamp and nonce, if
322 | // it so desires.
323 | self._validate(timestamp, nonce, function(err, valid) {
324 | if (err) { return self.error(err); }
325 | if (!valid) { return self.fail(self._challenge('nonce_used')); }
326 | return ok();
327 | });
328 | } else {
329 | return ok();
330 | }
331 | } // validate
332 | });
333 | }
334 |
335 | /**
336 | * Authentication challenge.
337 | *
338 | * References:
339 | * - [Problem Reporting](http://wiki.oauth.net/w/page/12238543/ProblemReporting)
340 | *
341 | * @api private
342 | */
343 | ConsumerStrategy.prototype._challenge = function(problem, advice) {
344 | var challenge = 'OAuth realm="' + this._realm + '"';
345 | if (problem) {
346 | challenge += ', oauth_problem="' + utils.encode(problem) + '"';
347 | }
348 | if (advice && advice.length) {
349 | challenge += ', oauth_problem_advice="' + utils.encode(advice) + '"';
350 | }
351 |
352 | return challenge;
353 | }
354 |
355 |
356 | /**
357 | * Expose `ConsumerStrategy`.
358 | */
359 | module.exports = ConsumerStrategy;
360 |
--------------------------------------------------------------------------------
/lib/passport-http-oauth/strategies/token.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 | var passport = require('passport')
5 | , uri = require('url')
6 | , util = require('util')
7 | , utils = require('./utils');
8 |
9 |
10 | /**
11 | * `TokenStrategy` constructor.
12 | *
13 | * The OAuth token authentication strategy authenticates requests based on the
14 | * `oauth_token` parameter contained in an HTTP request. This parameter can be
15 | * located in an `Authorization` header field, the request entity body, or the
16 | * URL query parameters.
17 | *
18 | * This strategy requires three functions as callbacks, referred to as
19 | * `consumer`, `verify` and `validate`.
20 | *
21 | * The `consumer` callback accepts `consumerKey` and must call `done` supplying
22 | * a `consumer` and `consumerSecret`. The strategy will use the secret to
23 | * compute the signature, failing authentication if it does not match the
24 | * request's signature. If an exception occured, `err` should be set.
25 | *
26 | * The `verify` callback accepts `accessToken` and must call `done` supplying
27 | * a `user`, `tokenSecret` and optional `info`. Note that `user` is the
28 | * authenticating entity of this strategy, and will be set by Passport at
29 | * `req.user` upon success. The strategy will use the secret to compute the
30 | * signature, failing authentication if it does not match the request's
31 | * signature. The optional `info` is typically used to carry additional
32 | * authorization details associated with the token (scope of access, for
33 | * example). `info` will be set by Passport at `req.authInfo`, where it can be
34 | * used by later middleware, avoiding the need to re-query a database for the
35 | * same information. If an exception occured, `err` should be set.
36 | *
37 | * The `validate` callback is optional, accepting `timestamp` and `nonce` as a
38 | * means to protect against replay attacks.
39 | *
40 | *
41 | * Note that despite defining a single authentication scheme, OAuth
42 | * authentication serves two distinct purposes:
43 | * 1. Authenticating consumers (aka clients) that are requesting access to
44 | * protected resources.
45 | * 2. Authenticating users associated with an access token obtained by a
46 | * consumer, with possibly limited scope.
47 | *
48 | * This strategy covers the latter purpose (see `ConsumerStrategy` for the
49 | * former). Due to the nature of OAuth, both the user and the consumer will be
50 | * identified by employing this strategy, with the user being the entity of
51 | * primary interest.
52 | *
53 | * When authenticating using the `TokenStrategy`, `authInfo` will contain the
54 | * following properties (in addition to any optional info supplied by the
55 | * application to the `verify` callback):
56 | *
57 | * scheme always set to `OAuth`
58 | * consumer the consumer instance supplied by the application to the `consumer` callback
59 | * client alias for `consumer`
60 | *
61 | * This strategy is intended to be employed in routes for protected resources.
62 | *
63 | * Examples:
64 | *
65 | * passport.use('token', new TokenStrategy(
66 | * function(consumerKey, done) {
67 | * Consumer.findByKey({ key: consumerKey }, function (err, consumer) {
68 | * if (err) { return done(err); }
69 | * if (!consumer) { return done(null, false); }
70 | * return done(null, consumer, consumer.secret);
71 | * });
72 | * },
73 | * function(accessToken, done) {
74 | * AccessToken.findOne(accessToken, function (err, token) {
75 | * if (err) { return done(err); }
76 | * if (!token) { return done(null, false); }
77 | * Users.findOne(token.userId, function(err, user) {
78 | * if (err) { return done(err); }
79 | * if (!user) { return done(null, false); }
80 | * // fourth argument is optional info. typically used to pass
81 | * // details needed to authorize the request (ex: `scope`)
82 | * return done(null, user, token.secret, { scope: token.scope });
83 | * });
84 | * });
85 | * },
86 | * function(timestamp, nonce, done) {
87 | * // validate the timestamp and nonce as necessary
88 | * done(null, true)
89 | * }
90 | * ));
91 | *
92 | * References:
93 | * - [Authenticated Requests](http://tools.ietf.org/html/rfc5849#section-3)
94 | * - [Accessing Protected Resources](http://oauth.net/core/1.0a/#anchor12)
95 | * - [Accessing Protected Resources](http://oauth.net/core/1.0/#anchor13)
96 | *
97 | * @param {Object} options
98 | * @param {Function} consumer
99 | * @param {Function} verify
100 | * @api public
101 | */
102 | function TokenStrategy(options, consumer, verify, validate) {
103 | if (typeof options == 'function') {
104 | validate = verify;
105 | verify = consumer;
106 | consumer = options;
107 | options = {};
108 | }
109 | if (!consumer) throw new Error('HTTP OAuth token authentication strategy requires a consumer function');
110 | if (!verify) throw new Error('HTTP OAuth token authentication strategy requires a verify function');
111 |
112 | passport.Strategy.call(this);
113 | this.name = 'oauth';
114 | this._consumer = consumer;
115 | this._verify = verify;
116 | this._validate = validate;
117 | this._host = options.host || null;
118 | this._realm = options.realm || 'Users';
119 | this._ignoreVersion = options.ignoreVersion || false;
120 | }
121 |
122 | /**
123 | * Inherit from `passport.Strategy`.
124 | */
125 | util.inherits(TokenStrategy, passport.Strategy);
126 |
127 | /**
128 | * Authenticate request based on the contents of a HTTP OAuth authorization
129 | * header, body parameters, or query parameters.
130 | *
131 | * @param {Object} req
132 | * @api protected
133 | */
134 | TokenStrategy.prototype.authenticate = function(req) {
135 | var params = undefined
136 | , header = null;
137 |
138 | if (req.headers && req.headers['authorization']) {
139 | var parts = req.headers['authorization'].split(' ');
140 | if (parts.length >= 2) {
141 | var scheme = parts[0];
142 | var credentials = null;
143 |
144 | parts.shift();
145 | credentials = parts.join(' ');
146 |
147 | if (/OAuth/i.test(scheme)) {
148 | params = utils.parseHeader(credentials);
149 | header = params;
150 | }
151 | } else {
152 | return this.fail(400);
153 | }
154 | }
155 |
156 | if (req.body && req.body['oauth_signature']) {
157 | if (params) { return this.fail(400); }
158 | params = req.body;
159 | }
160 |
161 | if (req.query && req.query['oauth_signature']) {
162 | if (params) { return this.fail(400); }
163 | token = req.query['access_token'];
164 | params = req.query;
165 | }
166 |
167 | if (!params) { return this.fail(this._challenge()); }
168 |
169 | if (!params['oauth_consumer_key'] ||
170 | !params['oauth_token'] ||
171 | !params['oauth_signature_method'] ||
172 | !params['oauth_signature'] ||
173 | !params['oauth_timestamp'] ||
174 | !params['oauth_nonce']) {
175 | return this.fail(this._challenge('parameter_absent'), 400);
176 | }
177 |
178 | var consumerKey = params['oauth_consumer_key']
179 | , accessToken = params['oauth_token']
180 | , signatureMethod = params['oauth_signature_method']
181 | , signature = params['oauth_signature']
182 | , timestamp = params['oauth_timestamp']
183 | , nonce = params['oauth_nonce']
184 | , version = params['oauth_version']
185 |
186 | if (version && version !== '1.0' && !this._ignoreVersion) {
187 | return this.fail(this._challenge('version_rejected'), 400);
188 | }
189 |
190 | var self = this;
191 | this._consumer(consumerKey, function(err, consumer, consumerSecret) {
192 | if (err) { return self.error(err); }
193 | if (!consumer) { return self.fail(self._challenge('consumer_key_rejected')); }
194 |
195 | self._verify(accessToken, verified);
196 |
197 | function verified(err, user, tokenSecret, info) {
198 | if (err) { return self.error(err); }
199 | if (!user) { return self.fail(self._challenge('token_rejected')); }
200 |
201 | info = info || {};
202 | info.scheme = 'OAuth';
203 | info.client =
204 | info.consumer = consumer;
205 |
206 | var url = utils.originalURL(req, self._host)
207 | , query = req.query
208 | , body = req.body;
209 |
210 | var sources = [ header, query ];
211 | if (req.headers['content-type'] &&
212 | req.headers['content-type'].slice(0, 'application/x-www-form-urlencoded'.length) ===
213 | 'application/x-www-form-urlencoded') {
214 | sources.push(body);
215 | }
216 |
217 | var normalizedURL = utils.normalizeURI(url)
218 | , normalizedParams = utils.normalizeParams.apply(undefined, sources)
219 | , base = utils.constructBaseString(req.method, normalizedURL, normalizedParams);
220 |
221 | if (signatureMethod == 'HMAC-SHA1') {
222 | var key = utils.encode(consumerSecret) + '&';
223 | if (tokenSecret) { key += utils.encode(tokenSecret); }
224 | var computedSignature = utils.hmacsha1(key, base);
225 |
226 | if (signature !== computedSignature) {
227 | return self.fail(self._challenge('signature_invalid'));
228 | }
229 |
230 | } else if (signatureMethod == 'HMAC-SHA256') {
231 | var key = utils.encode(consumerSecret) + '&';
232 | if (tokenSecret) { key += utils.encode(tokenSecret); }
233 | var computedSignature = utils.hmacsha256(key, base);
234 |
235 | if (signature !== computedSignature) {
236 | return self.fail(self._challenge('signature_invalid'));
237 | }
238 |
239 | } else if (signatureMethod == 'PLAINTEXT') {
240 | var computedSignature = utils.plaintext(consumerSecret, tokenSecret);
241 |
242 | if (signature !== computedSignature) {
243 | return self.fail(self._challenge('signature_invalid'));
244 | }
245 |
246 | } else {
247 | return self.fail(self._challenge('signature_method_rejected'), 400);
248 | }
249 |
250 | // If execution reaches this point, the request signature has been
251 | // verified and authentication is successful.
252 | if (self._validate) {
253 | // Give the application a chance it validate the timestamp and nonce, if
254 | // it so desires.
255 | self._validate(timestamp, nonce, function(err, valid) {
256 | if (err) { return self.error(err); }
257 | if (!valid) { return self.fail(self._challenge('nonce_used')); }
258 | return self.success(user, info);
259 | });
260 | } else {
261 | return self.success(user, info);
262 | }
263 | }
264 | });
265 | }
266 |
267 | /**
268 | * Authentication challenge.
269 | *
270 | * References:
271 | * - [Problem Reporting](http://wiki.oauth.net/w/page/12238543/ProblemReporting)
272 | *
273 | * @api private
274 | */
275 | TokenStrategy.prototype._challenge = function(problem, advice) {
276 | var challenge = 'OAuth realm="' + this._realm + '"';
277 | if (problem) {
278 | challenge += ', oauth_problem="' + utils.encode(problem) + '"';
279 | }
280 | if (advice && advice.length) {
281 | challenge += ', oauth_problem_advice="' + utils.encode(advice) + '"';
282 | }
283 |
284 | return challenge;
285 | }
286 |
287 |
288 | /**
289 | * Expose `TokenStrategy`.
290 | */
291 | module.exports = TokenStrategy;
292 |
--------------------------------------------------------------------------------
/lib/passport-http-oauth/strategies/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 | var url = require('url')
5 | , crypto = require('crypto')
6 | , util = require('util')
7 | , MultiHash = require('../multihash')
8 |
9 |
10 | /**
11 | * Reconstructs the original URL of the request.
12 | *
13 | * This function builds a URL that corresponds the original URL requested by the
14 | * client, including the protocol (http or https) and host.
15 | *
16 | * If the request passed through any proxies that terminate SSL, the
17 | * `X-Forwarded-Proto` header is used to detect if the request was encrypted to
18 | * the proxy.
19 | *
20 | * @return {String}
21 | * @api private
22 | */
23 | exports.originalURL = function(req, defaultHost) {
24 | var headers = req.headers
25 | , protocol = (req.connection.encrypted || req.headers['x-forwarded-proto'] == 'https')
26 | ? 'https'
27 | : 'http'
28 | , host = defaultHost || headers.host
29 | , path = req.url || '';
30 | return protocol + '://' + host + path;
31 | };
32 |
33 | /**
34 | * Parse credentials in `Authorization` header into params hash.
35 | *
36 | * References:
37 | * - [Authorization Header](http://tools.ietf.org/html/rfc5849#section-3.5.1)
38 | * - [OAuth HTTP Authorization Scheme](http://oauth.net/core/1.0a/#auth_header)
39 | * - [OAuth HTTP Authorization Scheme](http://oauth.net/core/1.0/#auth_header)
40 | *
41 | * @api private
42 | */
43 | exports.parseHeader = function(credentials) {
44 | var params = {}
45 | , comps = credentials.match(/(\w+)="([^"]+)"/g);
46 |
47 | if (comps) {
48 | for (var i = 0, len = comps.length; i < len; i++) {
49 | var comp = /(\w+)="([^"]+)"/.exec(comps[i])
50 | , name = exports.decode(comp[1])
51 | , val = exports.decode(comp[2]);
52 |
53 | // Some clients (I'm looking at you request) erroneously add non-protocol
54 | // params to the `Authorization` header. This check filters those params
55 | // out. It also filters out the `realm` parameter, which is valid to
56 | // include in the header, but should be excluded for purposes of
57 | // generating a signature.
58 | if (name.indexOf('oauth_') == 0) {
59 | params[name] = val;
60 | }
61 | }
62 | }
63 | return params;
64 | }
65 |
66 | /**
67 | * Percent-decodes `str` per RFC 3986.
68 | *
69 | * References:
70 | * - [Percent Encoding](http://tools.ietf.org/html/rfc5849#section-3.6)
71 | * - [Parameter Encoding](http://oauth.net/core/1.0a/#encoding_parameters)
72 | * - [Parameter Encoding](http://oauth.net/core/1.0/#encoding_parameters)
73 | *
74 | * @param {String} str
75 | * @api private
76 | */
77 | exports.decode = function(str) {
78 | return decodeURIComponent(str);
79 | }
80 |
81 | /**
82 | * Percent-encodes `str` per RFC 3986.
83 | *
84 | * References:
85 | * - [Percent Encoding](http://tools.ietf.org/html/rfc5849#section-3.6)
86 | * - [Parameter Encoding](http://oauth.net/core/1.0a/#encoding_parameters)
87 | * - [Parameter Encoding](http://oauth.net/core/1.0/#encoding_parameters)
88 | *
89 | * @param {String} str
90 | * @api private
91 | */
92 | exports.encode = function(str) {
93 | return encodeURIComponent(str)
94 | .replace(/!/g,'%21')
95 | .replace(/'/g,'%27')
96 | .replace(/\(/g,'%28')
97 | .replace(/\)/g,'%29')
98 | .replace(/\*/g,'%2A');
99 | }
100 |
101 | /**
102 | * Construct base string by encoding and concatenating components.
103 | *
104 | * References:
105 | * - [String Construction](http://tools.ietf.org/html/rfc5849#section-3.4.1.1)
106 | * - [Concatenate Request Elements](http://oauth.net/core/1.0a/#anchor13)
107 | * - [Concatenate Request Elements](http://oauth.net/core/1.0/#anchor14)
108 | *
109 | * @param {String} method
110 | * @param {String} uri
111 | * @param {String} params
112 | * @api private
113 | */
114 | exports.constructBaseString = function(method, uri, params) {
115 | return [ method.toUpperCase(), exports.encode(uri), exports.encode(params) ].join('&');
116 | }
117 |
118 | /**
119 | * Normalize base string URI, including scheme, authority, and path.
120 | *
121 | * References:
122 | * - [Base String URI](http://tools.ietf.org/html/rfc5849#section-3.4.1.2)
123 | * - [Construct Request URL](http://oauth.net/core/1.0a/#anchor13)
124 | * - [Construct Request URL](http://oauth.net/core/1.0/#anchor14)
125 | *
126 | * @param {String} method
127 | * @param {String} uri
128 | * @param {String} params
129 | * @api private
130 | */
131 | exports.normalizeURI =
132 | exports.normalizeURL = function(uri) {
133 | var parsed = url.parse(uri, true);
134 | delete parsed.query;
135 | delete parsed.search;
136 | return url.format(parsed);
137 | }
138 |
139 | /**
140 | * Normalize request parameters from header, query, and body sources.
141 | *
142 | * References:
143 | * - [Request Parameters](http://tools.ietf.org/html/rfc5849#section-3.4.1.3)
144 | * - [Normalize Request Parameters](http://oauth.net/core/1.0a/#anchor13)
145 | * - [Normalize Request Parameters](http://oauth.net/core/1.0/#anchor14)
146 | *
147 | * @param {Object} header
148 | * @param {Object} query
149 | * @param {Object} body
150 | * @api private
151 | */
152 | exports.normalizeParams = function(header, query, body) {
153 | var mh = new MultiHash();
154 | for (var i = 0, len = arguments.length; i < len; i++) {
155 | var source = arguments[i];
156 | if (!source) { continue; }
157 | Object.keys(source).forEach(function(key) {
158 | mh.put(exports.encode(key), exports.encode(source[key] || ''));
159 | });
160 | }
161 | mh.del('oauth_signature');
162 |
163 | var normalizedParams = [];
164 | mh.keys().sort().forEach(function(key) {
165 | mh.values(key).sort().forEach(function(val) {
166 | normalizedParams.push(key + '=' + val);
167 | });
168 | });
169 | return normalizedParams.join('&');
170 | }
171 |
172 | /**
173 | * Generate HMAC-SHA1 signature.
174 | *
175 | * References:
176 | * - [HMAC-SHA1](http://oauth.net/core/1.0a/#anchor15)
177 | * - [HMAC-SHA1](http://oauth.net/core/1.0/#anchor16)
178 | *
179 | * @param {String} key
180 | * @param {String} text
181 | * @return {String}
182 | * @api private
183 | */
184 | exports.hmacsha1 = function(key, text) {
185 | return crypto.createHmac('sha1', key).update(text).digest('base64')
186 | }
187 |
188 | /**
189 | * Generate HMAC-SHA256 signature.
190 | *
191 | * @param {String} key
192 | * @param {String} text
193 | * @return {String}
194 | * @api private
195 | */
196 | exports.hmacsha256 = function(key, text) {
197 | return crypto.createHmac('sha256', key).update(text).digest('base64')
198 | }
199 |
200 | /**
201 | * Generate PLAINTEXT signature.
202 | *
203 | * References:
204 | * - [PLAINTEXT](http://oauth.net/core/1.0a/#anchor21)
205 | * - [PLAINTEXT](http://oauth.net/core/1.0/#anchor22)
206 | *
207 | * @param {String} consumerSecret
208 | * @param {String} tokenSecret
209 | * @return {String}
210 | * @api private
211 | */
212 | exports.plaintext = function(consumerSecret, tokenSecret) {
213 | return exports.encode([exports.encode(consumerSecret), exports.encode(tokenSecret)].join('&'));
214 | }
215 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "passport-http-oauth",
3 | "version": "0.1.3",
4 | "description": "HTTP OAuth authentication strategy for Passport.",
5 | "keywords": ["passport", "http", "oauth", "authn", "authentication", "authz", "authorization", "api"],
6 | "repository": {
7 | "type": "git",
8 | "url": "git://github.com/jaredhanson/passport-http-oauth.git"
9 | },
10 | "bugs": {
11 | "url": "http://github.com/jaredhanson/passport-http-oauth/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-oauth",
23 | "dependencies": {
24 | "pkginfo": "0.2.x",
25 | "passport": "~0.1.11"
26 | },
27 | "devDependencies": {
28 | "make-node": "0.4.6",
29 | "vows": "0.8.x"
30 | },
31 | "scripts": {
32 | "test": "NODE_PATH=lib make test"
33 | },
34 | "engines": { "node": ">= 0.4.0" }
35 | }
36 |
--------------------------------------------------------------------------------
/test/index-test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows');
2 | var assert = require('assert');
3 | var util = require('util');
4 | var oauth = require('passport-http-oauth');
5 |
6 |
7 | vows.describe('passport-http-oauth').addBatch({
8 |
9 | 'should report a version': function () {
10 | assert.isString(oauth.version);
11 | },
12 |
13 | 'should export strategies': function () {
14 | assert.isFunction(oauth.ClientStrategy);
15 | assert.strictEqual(oauth.ClientStrategy, oauth.ConsumerStrategy);
16 | assert.isFunction(oauth.TokenStrategy);
17 | },
18 |
19 | }).export(module);
20 |
--------------------------------------------------------------------------------
/test/multihash-test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows');
2 | var assert = require('assert');
3 | var util = require('util');
4 | var MultiHash = require('passport-http-oauth/multihash');
5 |
6 |
7 | vows.describe('MultiHash').addBatch({
8 |
9 | 'multihash with no elements': {
10 | topic: function() {
11 | return new MultiHash();
12 | },
13 |
14 | 'should report length of zero' : function(hash) {
15 | assert.equal(hash.length, 0);
16 | },
17 | 'should not have key' : function(hash) {
18 | assert.isFalse(hash.has('something'));
19 | },
20 | 'should report count of zero values for key' : function(hash) {
21 | assert.equal(hash.count('something'), 0);
22 | },
23 | 'should return an empty array for keys' : function(hash) {
24 | assert.lengthOf(hash.keys(), 0);
25 | },
26 | 'should return an empty array for values' : function(hash) {
27 | assert.lengthOf(hash.values('x'), 0);
28 | },
29 | },
30 |
31 | 'multihash with two single-value elements': {
32 | topic: function() {
33 | var hash = new MultiHash();
34 | hash.put('hello', 'world');
35 | hash.put('foo', 'bar');
36 | return hash;
37 | },
38 |
39 | 'should report length of two' : function(hash) {
40 | assert.equal(hash.length, 2);
41 | },
42 | 'should have keys' : function(hash) {
43 | assert.isTrue(hash.has('hello'));
44 | assert.isTrue(hash.has('foo'));
45 | },
46 | 'should report count of one value for each key' : function(hash) {
47 | assert.equal(hash.count('hello'), 1);
48 | assert.equal(hash.count('foo'), 1);
49 | },
50 | 'should return an array of keys' : function(hash) {
51 | assert.lengthOf(hash.keys(), 2);
52 | assert.equal(hash.keys()[0], 'hello');
53 | assert.equal(hash.keys()[1], 'foo');
54 | },
55 | 'should return an empty array for values' : function(hash) {
56 | assert.lengthOf(hash.values('hello'), 1);
57 | assert.equal(hash.values('hello')[0], 'world');
58 | },
59 | },
60 |
61 | 'multihash with one multi-value element': {
62 | topic: function() {
63 | var hash = new MultiHash();
64 | hash.put('foo', 'bar');
65 | hash.put('foo', 'baz');
66 | return hash;
67 | },
68 |
69 | 'should report length of one' : function(hash) {
70 | assert.equal(hash.length, 1);
71 | },
72 | 'should have key' : function(hash) {
73 | assert.isTrue(hash.has('foo'));
74 | },
75 | 'should report count of two values for key' : function(hash) {
76 | assert.equal(hash.count('foo'), 2);
77 | },
78 | 'should return an array of keys' : function(hash) {
79 | assert.lengthOf(hash.keys(), 1);
80 | assert.equal(hash.keys()[0], 'foo');
81 | },
82 | 'should return an empty array for values' : function(hash) {
83 | assert.lengthOf(hash.values('foo'), 2);
84 | assert.equal(hash.values('foo')[0], 'bar');
85 | assert.equal(hash.values('foo')[1], 'baz');
86 | },
87 | },
88 |
89 | 'multihash#add': {
90 | 'should add objects containing different keys' : function() {
91 | var mh = new MultiHash();
92 | mh.add({ foo: 'x' });
93 | mh.add({ bar: 'y', baz: 'z' });
94 | assert.equal(mh.length, 3);
95 | assert.equal(mh.keys()[0], 'foo');
96 | assert.equal(mh.values('foo')[0], 'x');
97 | assert.equal(mh.keys()[1], 'bar');
98 | assert.equal(mh.values('bar')[0], 'y');
99 | assert.equal(mh.keys()[2], 'baz');
100 | assert.equal(mh.values('baz')[0], 'z');
101 | },
102 | 'should add objects containing same keys' : function() {
103 | var mh = new MultiHash();
104 | mh.add({ hello: 'bob' });
105 | mh.add({ hello: 'joe' });
106 | assert.equal(mh.length, 1);
107 | assert.equal(mh.keys()[0], 'hello');
108 | assert.equal(mh.values('hello')[0], 'bob');
109 | assert.equal(mh.values('hello')[1], 'joe');
110 | },
111 | 'should not add null object' : function() {
112 | var mh = new MultiHash();
113 | mh.add({ hello: 'bob' });
114 | assert.equal(mh.length, 1);
115 | mh.add(null);
116 | assert.equal(mh.length, 1);
117 | },
118 | },
119 |
120 | 'multihash#del': {
121 | 'should delete keys' : function() {
122 | var mh = new MultiHash();
123 | mh.put('hello', 'world');
124 | mh.put('foo', 'bar');
125 | assert.equal(mh.length, 2);
126 | mh.del('hello')
127 | assert.equal(mh.length, 1);
128 | assert.equal(mh.values('foo')[0], 'bar');
129 | },
130 | },
131 |
132 | }).export(module);
133 |
--------------------------------------------------------------------------------
/test/strategies/consumer-test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows');
2 | var assert = require('assert');
3 | var url = require('url');
4 | var util = require('util');
5 | var ConsumerStrategy = require('passport-http-oauth/strategies/consumer');
6 |
7 |
8 | vows.describe('ConsumerStrategy').addBatch({
9 |
10 | 'strategy': {
11 | topic: function() {
12 | return new ConsumerStrategy(function() {}, function() {});
13 | },
14 |
15 | 'should be named oauth': function(strategy) {
16 | assert.equal(strategy.name, 'oauth');
17 | },
18 | },
19 |
20 | 'strategy handling a valid request without a request token placing credentials in header': {
21 | topic: function() {
22 | var strategy = new ConsumerStrategy(
23 | // consumer callback
24 | function(consumerKey, done) {
25 | if (consumerKey == 'abc123') {
26 | done(null, { id: '1' }, 'ssh-secret');
27 | } else {
28 | done(new Error('something is wrong'))
29 | }
30 | },
31 | // token callback
32 | function(requestToken, done) {
33 | done(new Error('token callback should not be called'));
34 | }
35 | );
36 | return strategy;
37 | },
38 |
39 | 'after augmenting with actions': {
40 | topic: function(strategy) {
41 | var self = this;
42 | var req = {};
43 | strategy.success = function(user, info) {
44 | self.callback(null, user, info);
45 | }
46 | strategy.fail = function(challenge, status) {
47 | self.callback(new Error('should not be called'));
48 | }
49 | strategy.error = function(err) {
50 | self.callback(new Error('should not be called'));
51 | }
52 |
53 | req.url = '/oauth/request_token';
54 | req.method = 'POST';
55 | req.headers = {};
56 | req.headers['host'] = '127.0.0.1:3000';
57 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
58 | req.query = url.parse(req.url, true).query;
59 | req.connection = { encrypted: false };
60 | process.nextTick(function () {
61 | strategy.authenticate(req);
62 | });
63 | },
64 |
65 | 'should not generate an error' : function(err, user, info) {
66 | assert.isNull(err);
67 | },
68 | 'should authenticate' : function(err, user, info) {
69 | assert.equal(user.id, '1');
70 | },
71 | 'should set scheme to OAuth' : function(err, user, info) {
72 | assert.equal(info.scheme, 'OAuth');
73 | },
74 | 'should set callbackURL' : function(err, user, info) {
75 | assert.equal(info.oauth.callbackURL, 'http://macbook-air.local.jaredhanson.net:3001/oauth/callback');
76 | },
77 | },
78 | },
79 |
80 | 'strategy handling a valid request without a request token placing credentials with spaces in header': {
81 | topic: function() {
82 | var strategy = new ConsumerStrategy(
83 | // consumer callback
84 | function(consumerKey, done) {
85 | if (consumerKey == 'abc123') {
86 | done(null, { id: '1' }, 'ssh-secret');
87 | } else {
88 | done(new Error('something is wrong'))
89 | }
90 | },
91 | // token callback
92 | function(requestToken, done) {
93 | done(new Error('token callback should not be called'));
94 | }
95 | );
96 | return strategy;
97 | },
98 |
99 | 'after augmenting with actions': {
100 | topic: function(strategy) {
101 | var self = this;
102 | var req = {};
103 | strategy.success = function(user, info) {
104 | self.callback(null, user, info);
105 | }
106 | strategy.fail = function(challenge, status) {
107 | self.callback(new Error('should not be called'));
108 | }
109 | strategy.error = function(err) {
110 | self.callback(new Error('should not be called'));
111 | }
112 |
113 | req.url = '/oauth/request_token';
114 | req.method = 'POST';
115 | req.headers = {};
116 | req.headers['host'] = '127.0.0.1:3000';
117 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback", oauth_consumer_key="abc123", oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1341176111", oauth_version="1.0", oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
118 | req.query = url.parse(req.url, true).query;
119 | req.connection = { encrypted: false };
120 | process.nextTick(function () {
121 | strategy.authenticate(req);
122 | });
123 | },
124 |
125 | 'should not generate an error' : function(err, user, info) {
126 | assert.isNull(err);
127 | },
128 | 'should authenticate' : function(err, user, info) {
129 | assert.equal(user.id, '1');
130 | },
131 | 'should set scheme to OAuth' : function(err, user, info) {
132 | assert.equal(info.scheme, 'OAuth');
133 | },
134 | 'should set callbackURL' : function(err, user, info) {
135 | assert.equal(info.oauth.callbackURL, 'http://macbook-air.local.jaredhanson.net:3001/oauth/callback');
136 | },
137 | },
138 | },
139 |
140 | 'strategy handling a valid request without a request token placing credentials in header using 1.0A version': {
141 | topic: function() {
142 | var strategy = new ConsumerStrategy(
143 | { ignoreVersion: true },
144 | // consumer callback
145 | function(consumerKey, done) {
146 | if (consumerKey == 'abc123') {
147 | done(null, { id: '1' }, 'ssh-secret');
148 | } else {
149 | done(new Error('something is wrong'))
150 | }
151 | },
152 | // token callback
153 | function(requestToken, done) {
154 | done(new Error('token callback should not be called'));
155 | }
156 | );
157 | return strategy;
158 | },
159 |
160 | 'after augmenting with actions': {
161 | topic: function(strategy) {
162 | var self = this;
163 | var req = {};
164 | strategy.success = function(user, info) {
165 | self.callback(null, user, info);
166 | }
167 | strategy.fail = function(challenge, status) {
168 | self.callback(new Error('should not be called'));
169 | }
170 | strategy.error = function(err) {
171 | self.callback(new Error('should not be called'));
172 | }
173 |
174 | req.url = '/oauth/request_token';
175 | req.method = 'POST';
176 | req.headers = {};
177 | req.headers['host'] = '127.0.0.1:3000';
178 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341176111",oauth_version="1.0A",oauth_signature="VfpGYYsNM4Ih0Lt7JsIbJz6%2FJM4%3D"';
179 | req.query = url.parse(req.url, true).query;
180 | req.connection = { encrypted: false };
181 | process.nextTick(function () {
182 | strategy.authenticate(req);
183 | });
184 | },
185 |
186 | 'should not generate an error' : function(err, user, info) {
187 | assert.isNull(err);
188 | },
189 | 'should authenticate' : function(err, user, info) {
190 | assert.equal(user.id, '1');
191 | },
192 | 'should set scheme to OAuth' : function(err, user, info) {
193 | assert.equal(info.scheme, 'OAuth');
194 | },
195 | 'should set callbackURL' : function(err, user, info) {
196 | assert.equal(info.oauth.callbackURL, 'http://macbook-air.local.jaredhanson.net:3001/oauth/callback');
197 | },
198 | },
199 | },
200 |
201 | 'strategy handling a valid request without a request token using host option instead of host header': {
202 | topic: function() {
203 | var strategy = new ConsumerStrategy(
204 | { host: '127.0.0.1:3000' },
205 | // consumer callback
206 | function(consumerKey, done) {
207 | if (consumerKey == 'abc123') {
208 | done(null, { id: '1' }, 'ssh-secret');
209 | } else {
210 | done(new Error('something is wrong'))
211 | }
212 | },
213 | // token callback
214 | function(requestToken, done) {
215 | done(new Error('token callback should not be called'));
216 | }
217 | );
218 | return strategy;
219 | },
220 |
221 | 'after augmenting with actions': {
222 | topic: function(strategy) {
223 | var self = this;
224 | var req = {};
225 | strategy.success = function(user, info) {
226 | self.callback(null, user, info);
227 | }
228 | strategy.fail = function(challenge, status) {
229 | self.callback(new Error('should not be called'));
230 | }
231 | strategy.error = function(err) {
232 | self.callback(new Error('should not be called'));
233 | }
234 |
235 | req.url = '/oauth/request_token';
236 | req.method = 'POST';
237 | req.headers = {};
238 | //req.headers['host'] = '127.0.0.1:3000';
239 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
240 | req.query = url.parse(req.url, true).query;
241 | req.connection = { encrypted: false };
242 | process.nextTick(function () {
243 | strategy.authenticate(req);
244 | });
245 | },
246 |
247 | 'should not generate an error' : function(err, user, info) {
248 | assert.isNull(err);
249 | },
250 | 'should authenticate' : function(err, user, info) {
251 | assert.equal(user.id, '1');
252 | },
253 | 'should set scheme to OAuth' : function(err, user, info) {
254 | assert.equal(info.scheme, 'OAuth');
255 | },
256 | 'should set callbackURL' : function(err, user, info) {
257 | assert.equal(info.oauth.callbackURL, 'http://macbook-air.local.jaredhanson.net:3001/oauth/callback');
258 | },
259 | },
260 | },
261 |
262 | 'strategy handling a valid request without a request token using PLAINTEXT signature': {
263 | topic: function() {
264 | var strategy = new ConsumerStrategy(
265 | // consumer callback
266 | function(consumerKey, done) {
267 | done(null, { id: '1' }, 'ssh-secret');
268 | },
269 | // token callback
270 | function(requestToken, done) {
271 | done(new Error('token callback should not be called'));
272 | }
273 | );
274 | return strategy;
275 | },
276 |
277 | 'after augmenting with actions': {
278 | topic: function(strategy) {
279 | var self = this;
280 | var req = {};
281 | strategy.success = function(user, info) {
282 | self.callback(null, user, info);
283 | }
284 | strategy.fail = function(challenge, status) {
285 | self.callback(new Error('should not be called'));
286 | }
287 | strategy.error = function(err) {
288 | self.callback(new Error('should not be called'));
289 | }
290 |
291 | req.url = '/oauth/request_token';
292 | req.method = 'POST';
293 | req.headers = {};
294 | req.headers['host'] = '127.0.0.1:3000';
295 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="s9ncyMbjTtZyoEYi25dHaRyWI9nIilRQ",oauth_signature_method="PLAINTEXT",oauth_timestamp="1341196367",oauth_version="1.0",oauth_signature="ssh-secret%2526"';
296 | req.query = url.parse(req.url, true).query;
297 | req.connection = { encrypted: false };
298 | process.nextTick(function () {
299 | strategy.authenticate(req);
300 | });
301 | },
302 |
303 | 'should not generate an error' : function(err, user, info) {
304 | assert.isNull(err);
305 | },
306 | 'should authenticate' : function(err, user, info) {
307 | assert.equal(user.id, '1');
308 | },
309 | 'should set scheme to OAuth' : function(err, user, info) {
310 | assert.equal(info.scheme, 'OAuth');
311 | },
312 | 'should set callbackURL' : function(err, user, info) {
313 | assert.equal(info.oauth.callbackURL, 'http://macbook-air.local.jaredhanson.net:3001/oauth/callback');
314 | },
315 | },
316 | },
317 |
318 | 'strategy handling a valid request without a request token using HMAC-256 signature': {
319 | topic: function() {
320 | var strategy = new ConsumerStrategy(
321 | // consumer callback
322 | function(consumerKey, done) {
323 | if (consumerKey == 'abc123') {
324 | done(null, { id: '1' }, 'ssh-secret');
325 | } else {
326 | done(new Error('something is wrong'))
327 | }
328 | },
329 | // token callback
330 | function(requestToken, done) {
331 | done(new Error('token callback should not be called'));
332 | }
333 | );
334 | return strategy;
335 | },
336 |
337 | 'after augmenting with actions': {
338 | topic: function(strategy) {
339 | var self = this;
340 | var req = {};
341 | strategy.success = function(user, info) {
342 | self.callback(null, user, info);
343 | }
344 | strategy.fail = function(challenge, status) {
345 | self.callback(new Error('should not be called'));
346 | }
347 | strategy.error = function(err) {
348 | self.callback(new Error('should not be called'));
349 | }
350 |
351 | req.url = '/oauth/request_token';
352 | req.method = 'POST';
353 | req.headers = {};
354 | req.headers['host'] = '127.0.0.1:3000';
355 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="qlKeUBu8wDmP/L24e4SErAoSmyomhyHgiL9J3xnX9Xk="';
356 | req.query = url.parse(req.url, true).query;
357 | req.connection = { encrypted: false };
358 | process.nextTick(function () {
359 | strategy.authenticate(req);
360 | });
361 | },
362 |
363 | 'should not generate an error' : function(err, user, info) {
364 | assert.isNull(err);
365 | },
366 | 'should authenticate' : function(err, user, info) {
367 | assert.equal(user.id, '1');
368 | },
369 | 'should set scheme to OAuth' : function(err, user, info) {
370 | assert.equal(info.scheme, 'OAuth');
371 | },
372 | 'should set callbackURL' : function(err, user, info) {
373 | assert.equal(info.oauth.callbackURL, 'http://macbook-air.local.jaredhanson.net:3001/oauth/callback');
374 | },
375 | },
376 | },
377 |
378 | 'strategy handling a valid request without a request token placing credentials in header with all-caps scheme': {
379 | topic: function() {
380 | var strategy = new ConsumerStrategy(
381 | // consumer callback
382 | function(consumerKey, done) {
383 | done(null, { id: '1' }, 'ssh-secret');
384 | },
385 | // token callback
386 | function(requestToken, done) {
387 | done(new Error('token callback should not be called'));
388 | }
389 | );
390 | return strategy;
391 | },
392 |
393 | 'after augmenting with actions': {
394 | topic: function(strategy) {
395 | var self = this;
396 | var req = {};
397 | strategy.success = function(user, info) {
398 | self.callback(null, user, info);
399 | }
400 | strategy.fail = function(challenge, status) {
401 | self.callback(new Error('should not be called'));
402 | }
403 | strategy.error = function(err) {
404 | self.callback(new Error('should not be called'));
405 | }
406 |
407 | req.url = '/oauth/request_token';
408 | req.method = 'POST';
409 | req.headers = {};
410 | req.headers['host'] = '127.0.0.1:3000';
411 | req.headers['authorization'] = 'OAUTH oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
412 | req.query = url.parse(req.url, true).query;
413 | req.connection = { encrypted: false };
414 | process.nextTick(function () {
415 | strategy.authenticate(req);
416 | });
417 | },
418 |
419 | 'should not generate an error' : function(err, user, info) {
420 | assert.isNull(err);
421 | },
422 | 'should authenticate' : function(err, user, info) {
423 | assert.equal(user.id, '1');
424 | },
425 | 'should set scheme to OAuth' : function(err, user, info) {
426 | assert.equal(info.scheme, 'OAuth');
427 | },
428 | 'should set callbackURL' : function(err, user, info) {
429 | assert.equal(info.oauth.callbackURL, 'http://macbook-air.local.jaredhanson.net:3001/oauth/callback');
430 | },
431 | },
432 | },
433 |
434 | // TODO: Implement test case for request with params in body
435 |
436 | // TODO: Implement test case for request with params in query
437 |
438 | 'strategy handling a valid request without a request token where timestamp and nonce are validated': {
439 | topic: function() {
440 | var strategy = new ConsumerStrategy(
441 | // consumer callback
442 | function(consumerKey, done) {
443 | done(null, { id: '1' }, 'ssh-secret');
444 | },
445 | // token callback
446 | function(requestToken, done) {
447 | done(new Error('token callback should not be called'));
448 | },
449 | // validate callback
450 | function(timestamp, nonce, done) {
451 | if (timestamp == 1341176111 && nonce == 'fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q') {
452 | done(null, true);
453 | } else {
454 | done(new Error('something is wrong'))
455 | }
456 | }
457 | );
458 | return strategy;
459 | },
460 |
461 | 'after augmenting with actions': {
462 | topic: function(strategy) {
463 | var self = this;
464 | var req = {};
465 | strategy.success = function(user, info) {
466 | self.callback(null, user, info);
467 | }
468 | strategy.fail = function(challenge, status) {
469 | self.callback(new Error('should not be called'));
470 | }
471 | strategy.error = function(err) {
472 | self.callback(new Error('should not be called'));
473 | }
474 |
475 | req.url = '/oauth/request_token';
476 | req.method = 'POST';
477 | req.headers = {};
478 | req.headers['host'] = '127.0.0.1:3000';
479 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
480 | req.query = url.parse(req.url, true).query;
481 | req.connection = { encrypted: false };
482 | process.nextTick(function () {
483 | strategy.authenticate(req);
484 | });
485 | },
486 |
487 | 'should not generate an error' : function(err, user, info) {
488 | assert.isNull(err);
489 | },
490 | 'should authenticate' : function(err, user, info) {
491 | assert.equal(user.id, '1');
492 | },
493 | 'should set scheme to OAuth' : function(err, user, info) {
494 | assert.equal(info.scheme, 'OAuth');
495 | },
496 | 'should set callbackURL' : function(err, user, info) {
497 | assert.equal(info.oauth.callbackURL, 'http://macbook-air.local.jaredhanson.net:3001/oauth/callback');
498 | },
499 | },
500 | },
501 |
502 | 'strategy handling a valid request without a request token where consumer is not authenticated': {
503 | topic: function() {
504 | var strategy = new ConsumerStrategy(
505 | // consumer callback
506 | function(consumerKey, done) {
507 | done(null, false);
508 | },
509 | // token callback
510 | function(requestToken, done) {
511 | done(new Error('token callback should not be called'));
512 | }
513 | );
514 | return strategy;
515 | },
516 |
517 | 'after augmenting with actions': {
518 | topic: function(strategy) {
519 | var self = this;
520 | var req = {};
521 | strategy.success = function(user, info) {
522 | self.callback(new Error('should not be called'));
523 | }
524 | strategy.fail = function(challenge, status) {
525 | self.callback(null, challenge, status);
526 | }
527 | strategy.error = function(err) {
528 | self.callback(new Error('should not be called'));
529 | }
530 |
531 | req.url = '/oauth/request_token';
532 | req.method = 'POST';
533 | req.headers = {};
534 | req.headers['host'] = '127.0.0.1:3000';
535 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
536 | req.query = url.parse(req.url, true).query;
537 | req.connection = { encrypted: false };
538 | process.nextTick(function () {
539 | strategy.authenticate(req);
540 | });
541 | },
542 |
543 | 'should not generate an error' : function(err, user, info) {
544 | assert.isNull(err);
545 | },
546 | 'should respond with challenge' : function(err, challenge, status) {
547 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="consumer_key_rejected"');
548 | },
549 | 'should respond with default status' : function(err, challenge, status) {
550 | assert.isUndefined(status);
551 | },
552 | },
553 | },
554 |
555 | 'strategy handling a valid request without a request token where timestamp and nonce are not validated': {
556 | topic: function() {
557 | var strategy = new ConsumerStrategy(
558 | // consumer callback
559 | function(consumerKey, done) {
560 | done(null, { id: '1' }, 'ssh-secret');
561 | },
562 | // token callback
563 | function(requestToken, done) {
564 | done(new Error('token callback should not be called'));
565 | },
566 | // validate callback
567 | function(timestamp, nonce, done) {
568 | done(null, false);
569 | }
570 | );
571 | return strategy;
572 | },
573 |
574 | 'after augmenting with actions': {
575 | topic: function(strategy) {
576 | var self = this;
577 | var req = {};
578 | strategy.success = function(user, info) {
579 | self.callback(new Error('should not be called'));
580 | }
581 | strategy.fail = function(challenge, status) {
582 | self.callback(null, challenge, status);
583 | }
584 | strategy.error = function(err) {
585 | self.callback(new Error('should not be called'));
586 | }
587 |
588 | req.url = '/oauth/request_token';
589 | req.method = 'POST';
590 | req.headers = {};
591 | req.headers['host'] = '127.0.0.1:3000';
592 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
593 | req.query = url.parse(req.url, true).query;
594 | req.connection = { encrypted: false };
595 | process.nextTick(function () {
596 | strategy.authenticate(req);
597 | });
598 | },
599 |
600 | 'should not generate an error' : function(err, user, info) {
601 | assert.isNull(err);
602 | },
603 | 'should respond with challenge' : function(err, challenge, status) {
604 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="nonce_used"');
605 | },
606 | 'should respond with default status' : function(err, challenge, status) {
607 | assert.isUndefined(status);
608 | },
609 | },
610 | },
611 |
612 | 'strategy handling a valid request without a request token using HMAC-SHA1 signature where consumer secret is wrong': {
613 | topic: function() {
614 | var strategy = new ConsumerStrategy(
615 | // consumer callback
616 | function(consumerKey, done) {
617 | done(null, { id: '1' }, 'ssh-secret-wrong');
618 | },
619 | // token callback
620 | function(requestToken, done) {
621 | done(new Error('token callback should not be called'));
622 | }
623 | );
624 | return strategy;
625 | },
626 |
627 | 'after augmenting with actions': {
628 | topic: function(strategy) {
629 | var self = this;
630 | var req = {};
631 | strategy.success = function(user, info) {
632 | self.callback(new Error('should not be called'));
633 | }
634 | strategy.fail = function(challenge, status) {
635 | self.callback(null, challenge, status);
636 | }
637 | strategy.error = function(err) {
638 | self.callback(new Error('should not be called'));
639 | }
640 |
641 | req.url = '/oauth/request_token';
642 | req.method = 'POST';
643 | req.headers = {};
644 | req.headers['host'] = '127.0.0.1:3000';
645 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
646 | req.query = url.parse(req.url, true).query;
647 | req.connection = { encrypted: false };
648 | process.nextTick(function () {
649 | strategy.authenticate(req);
650 | });
651 | },
652 |
653 | 'should not generate an error' : function(err, challenge, status) {
654 | assert.isNull(err);
655 | },
656 | 'should respond with challenge' : function(err, challenge, status) {
657 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="signature_invalid"');
658 | },
659 | 'should respond with default status' : function(err, challenge, status) {
660 | assert.isUndefined(status);
661 | },
662 | },
663 | },
664 |
665 | 'strategy handling a valid request without a request token using PLAINTEXT signature where consumer secret is wrong': {
666 | topic: function() {
667 | var strategy = new ConsumerStrategy(
668 | // consumer callback
669 | function(consumerKey, done) {
670 | done(null, { id: '1' }, 'ssh-secret-wrong');
671 | },
672 | // token callback
673 | function(requestToken, done) {
674 | done(new Error('token callback should not be called'));
675 | }
676 | );
677 | return strategy;
678 | },
679 |
680 | 'after augmenting with actions': {
681 | topic: function(strategy) {
682 | var self = this;
683 | var req = {};
684 | strategy.success = function(user, info) {
685 | self.callback(new Error('should not be called'));
686 | }
687 | strategy.fail = function(challenge, status) {
688 | self.callback(null, challenge, status);
689 | }
690 | strategy.error = function(err) {
691 | self.callback(new Error('should not be called'));
692 | }
693 |
694 | req.url = '/oauth/request_token';
695 | req.method = 'POST';
696 | req.headers = {};
697 | req.headers['host'] = '127.0.0.1:3000';
698 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="s9ncyMbjTtZyoEYi25dHaRyWI9nIilRQ",oauth_signature_method="PLAINTEXT",oauth_timestamp="1341196367",oauth_version="1.0",oauth_signature="ssh-secret%2526"';
699 | req.query = url.parse(req.url, true).query;
700 | req.connection = { encrypted: false };
701 | process.nextTick(function () {
702 | strategy.authenticate(req);
703 | });
704 | },
705 |
706 | 'should not generate an error' : function(err, challenge, status) {
707 | assert.isNull(err);
708 | },
709 | 'should respond with challenge' : function(err, challenge, status) {
710 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="signature_invalid"');
711 | },
712 | 'should respond with default status' : function(err, challenge, status) {
713 | assert.isUndefined(status);
714 | },
715 | },
716 | },
717 |
718 | 'strategy handling a valid request without a request token using HMAC-SHA256 signature where consumer secret is wrong': {
719 | topic: function() {
720 | var strategy = new ConsumerStrategy(
721 | // consumer callback
722 | function(consumerKey, done) {
723 | done(null, { id: '1' }, 'ssh-secret-wrong');
724 | },
725 | // token callback
726 | function(requestToken, done) {
727 | done(new Error('token callback should not be called'));
728 | }
729 | );
730 | return strategy;
731 | },
732 |
733 | 'after augmenting with actions': {
734 | topic: function(strategy) {
735 | var self = this;
736 | var req = {};
737 | strategy.success = function(user, info) {
738 | self.callback(new Error('should not be called'));
739 | }
740 | strategy.fail = function(challenge, status) {
741 | self.callback(null, challenge, status);
742 | }
743 | strategy.error = function(err) {
744 | self.callback(new Error('should not be called'));
745 | }
746 |
747 | req.url = '/oauth/request_token';
748 | req.method = 'POST';
749 | req.headers = {};
750 | req.headers['host'] = '127.0.0.1:3000';
751 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="ignored"';
752 | req.query = url.parse(req.url, true).query;
753 | req.connection = { encrypted: false };
754 | process.nextTick(function () {
755 | strategy.authenticate(req);
756 | });
757 | },
758 |
759 | 'should not generate an error' : function(err, challenge, status) {
760 | assert.isNull(err);
761 | },
762 | 'should respond with challenge' : function(err, challenge, status) {
763 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="signature_invalid"');
764 | },
765 | 'should respond with default status' : function(err, challenge, status) {
766 | assert.isUndefined(status);
767 | },
768 | },
769 | },
770 |
771 | 'strategy handling a valid request without a request token using unkown signature method': {
772 | topic: function() {
773 | var strategy = new ConsumerStrategy(
774 | // consumer callback
775 | function(consumerKey, done) {
776 | done(null, { id: '1' }, 'ssh-secret');
777 | },
778 | // token callback
779 | function(requestToken, done) {
780 | done(new Error('token callback should not be called'));
781 | }
782 | );
783 | return strategy;
784 | },
785 |
786 | 'after augmenting with actions': {
787 | topic: function(strategy) {
788 | var self = this;
789 | var req = {};
790 | strategy.success = function(user, info) {
791 | self.callback(new Error('should not be called'));
792 | }
793 | strategy.fail = function(challenge, status) {
794 | self.callback(null, challenge, status);
795 | }
796 | strategy.error = function(err) {
797 | self.callback(new Error('should not be called'));
798 | }
799 |
800 | req.url = '/oauth/request_token';
801 | req.method = 'POST';
802 | req.headers = {};
803 | req.headers['host'] = '127.0.0.1:3000';
804 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="UNKNOWN",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
805 | req.query = url.parse(req.url, true).query;
806 | req.connection = { encrypted: false };
807 | process.nextTick(function () {
808 | strategy.authenticate(req);
809 | });
810 | },
811 |
812 | 'should not generate an error' : function(err, challenge, status) {
813 | assert.isNull(err);
814 | },
815 | 'should respond with challenge' : function(err, challenge, status) {
816 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="signature_method_rejected"');
817 | },
818 | 'should respond with 400 status' : function(err, challenge, status) {
819 | assert.equal(status, 400);
820 | },
821 | },
822 | },
823 |
824 | 'strategy handling a valid request without a request token where consumer callback fails with an error': {
825 | topic: function() {
826 | var strategy = new ConsumerStrategy(
827 | // consumer callback
828 | function(consumerKey, done) {
829 | done(new Error('consumer callback failure'));
830 | },
831 | // token callback
832 | function(requestToken, done) {
833 | done(new Error('token callback should not be called'));
834 | }
835 | );
836 | return strategy;
837 | },
838 |
839 | 'after augmenting with actions': {
840 | topic: function(strategy) {
841 | var self = this;
842 | var req = {};
843 | strategy.success = function(user, info) {
844 | self.callback(new Error('should not be called'));
845 | }
846 | strategy.fail = function(challenge, status) {
847 | self.callback(new Error('should not be called'));
848 | }
849 | strategy.error = function(err) {
850 | self.callback(null, err);
851 | }
852 |
853 | req.url = '/oauth/request_token';
854 | req.method = 'POST';
855 | req.headers = {};
856 | req.headers['host'] = '127.0.0.1:3000';
857 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
858 | req.query = url.parse(req.url, true).query;
859 | req.connection = { encrypted: false };
860 | process.nextTick(function () {
861 | strategy.authenticate(req);
862 | });
863 | },
864 |
865 | 'should not call success or fail' : function(err, e) {
866 | assert.isNull(err);
867 | },
868 | 'should call error' : function(err, e) {
869 | assert.instanceOf(e, Error);
870 | assert.equal(e.message, 'consumer callback failure');
871 | },
872 | },
873 | },
874 |
875 | 'strategy handling a valid request without a request token where validate callback fails with an error': {
876 | topic: function() {
877 | var strategy = new ConsumerStrategy(
878 | // consumer callback
879 | function(consumerKey, done) {
880 | done(null, { id: '1' }, 'ssh-secret');
881 | },
882 | // token callback
883 | function(requestToken, done) {
884 | done(new Error('token callback should not be called'));
885 | },
886 | // validate callback
887 | function(timestamp, nonce, done) {
888 | done(new Error('validate callback failure'));
889 | }
890 | );
891 | return strategy;
892 | },
893 |
894 | 'after augmenting with actions': {
895 | topic: function(strategy) {
896 | var self = this;
897 | var req = {};
898 | strategy.success = function(user, info) {
899 | self.callback(new Error('should not be called'));
900 | }
901 | strategy.fail = function(challenge, status) {
902 | self.callback(new Error('should not be called'));
903 | }
904 | strategy.error = function(err) {
905 | self.callback(null, err);
906 | }
907 |
908 | req.url = '/oauth/request_token';
909 | req.method = 'POST';
910 | req.headers = {};
911 | req.headers['host'] = '127.0.0.1:3000';
912 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
913 | req.query = url.parse(req.url, true).query;
914 | req.connection = { encrypted: false };
915 | process.nextTick(function () {
916 | strategy.authenticate(req);
917 | });
918 | },
919 |
920 | 'should not call success or fail' : function(err, e) {
921 | assert.isNull(err);
922 | },
923 | 'should call error' : function(err, e) {
924 | assert.instanceOf(e, Error);
925 | assert.equal(e.message, 'validate callback failure');
926 | },
927 | },
928 | },
929 |
930 | /* with request token (aka temporary credential) */
931 |
932 | 'strategy handling a valid request with a request token placing credentials in header': {
933 | topic: function() {
934 | var strategy = new ConsumerStrategy(
935 | // consumer callback
936 | function(consumerKey, done) {
937 | if (consumerKey == 'abc123') {
938 | done(null, { id: '1' }, 'ssh-secret');
939 | } else {
940 | done(new Error('something is wrong'))
941 | }
942 | },
943 | // token callback
944 | function(requestToken, done) {
945 | if (requestToken == 'wM9YRRm5') {
946 | done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF');
947 | } else {
948 | done(new Error('something is wrong'))
949 | }
950 | }
951 | );
952 | return strategy;
953 | },
954 |
955 | 'after augmenting with actions': {
956 | topic: function(strategy) {
957 | var self = this;
958 | var req = {};
959 | strategy.success = function(user, info) {
960 | self.callback(null, user, info);
961 | }
962 | strategy.fail = function(challenge, status) {
963 | self.callback(new Error('should not be called'));
964 | }
965 | strategy.error = function(err) {
966 | self.callback(new Error('should not be called'));
967 | }
968 |
969 | req.url = '/oauth/access_token';
970 | req.method = 'POST';
971 | req.headers = {};
972 | req.headers['host'] = '127.0.0.1:3000';
973 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="ZP5%2FtXZcUiiD2HXKrevCL5FjY%2FM%3D"';
974 | req.query = url.parse(req.url, true).query;
975 | req.connection = { encrypted: false };
976 | process.nextTick(function () {
977 | strategy.authenticate(req);
978 | });
979 | },
980 |
981 | 'should not generate an error' : function(err, user, info) {
982 | assert.isNull(err);
983 | },
984 | 'should authenticate' : function(err, user, info) {
985 | assert.equal(user.id, '1');
986 | },
987 | 'should set scheme to OAuth' : function(err, user, info) {
988 | assert.equal(info.scheme, 'OAuth');
989 | },
990 | 'should include token and verifier' : function(err, user, info) {
991 | assert.equal(info.oauth.token, 'wM9YRRm5');
992 | assert.equal(info.oauth.verifier, 'qriPjOnc');
993 | },
994 | },
995 | },
996 |
997 | 'strategy handling a valid request with a request token using host option instead of host header': {
998 | topic: function() {
999 | var strategy = new ConsumerStrategy(
1000 | { host: '127.0.0.1:3000' },
1001 | // consumer callback
1002 | function(consumerKey, done) {
1003 | if (consumerKey == 'abc123') {
1004 | done(null, { id: '1' }, 'ssh-secret');
1005 | } else {
1006 | done(new Error('something is wrong'))
1007 | }
1008 | },
1009 | // token callback
1010 | function(requestToken, done) {
1011 | if (requestToken == 'wM9YRRm5') {
1012 | done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF');
1013 | } else {
1014 | done(new Error('something is wrong'))
1015 | }
1016 | }
1017 | );
1018 | return strategy;
1019 | },
1020 |
1021 | 'after augmenting with actions': {
1022 | topic: function(strategy) {
1023 | var self = this;
1024 | var req = {};
1025 | strategy.success = function(user, info) {
1026 | self.callback(null, user, info);
1027 | }
1028 | strategy.fail = function(challenge, status) {
1029 | self.callback(new Error('should not be called'));
1030 | }
1031 | strategy.error = function(err) {
1032 | self.callback(new Error('should not be called'));
1033 | }
1034 |
1035 | req.url = '/oauth/access_token';
1036 | req.method = 'POST';
1037 | req.headers = {};
1038 | //req.headers['host'] = '127.0.0.1:3000';
1039 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="ZP5%2FtXZcUiiD2HXKrevCL5FjY%2FM%3D"';
1040 | req.query = url.parse(req.url, true).query;
1041 | req.connection = { encrypted: false };
1042 | process.nextTick(function () {
1043 | strategy.authenticate(req);
1044 | });
1045 | },
1046 |
1047 | 'should not generate an error' : function(err, user, info) {
1048 | assert.isNull(err);
1049 | },
1050 | 'should authenticate' : function(err, user, info) {
1051 | assert.equal(user.id, '1');
1052 | },
1053 | 'should set scheme to OAuth' : function(err, user, info) {
1054 | assert.equal(info.scheme, 'OAuth');
1055 | },
1056 | 'should include token and verifier' : function(err, user, info) {
1057 | assert.equal(info.oauth.token, 'wM9YRRm5');
1058 | assert.equal(info.oauth.verifier, 'qriPjOnc');
1059 | },
1060 | },
1061 | },
1062 |
1063 | 'strategy handling a valid request with a request token using PLAINTEXT signature': {
1064 | topic: function() {
1065 | var strategy = new ConsumerStrategy(
1066 | // consumer callback
1067 | function(consumerKey, done) {
1068 | done(null, { id: '1' }, 'ssh-secret');
1069 | },
1070 | // token callback
1071 | function(requestToken, done) {
1072 | done(null, '3yG0Panskjm5GGwdP5SUHFFXmF7aCl0v');
1073 | }
1074 | );
1075 | return strategy;
1076 | },
1077 |
1078 | 'after augmenting with actions': {
1079 | topic: function(strategy) {
1080 | var self = this;
1081 | var req = {};
1082 | strategy.success = function(user, info) {
1083 | self.callback(null, user, info);
1084 | }
1085 | strategy.fail = function(challenge, status) {
1086 | self.callback(new Error('should not be called'));
1087 | }
1088 | strategy.error = function(err) {
1089 | self.callback(new Error('should not be called'));
1090 | }
1091 |
1092 | req.url = '/oauth/access_token';
1093 | req.method = 'POST';
1094 | req.headers = {};
1095 | req.headers['host'] = '127.0.0.1:3000';
1096 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="iiWqS4a7mKrpQWXO07osM9Om0PCDsMHN",oauth_signature_method="PLAINTEXT",oauth_timestamp="1341196375",oauth_token="AbSRoiyN",oauth_verifier="FOXJJYN0",oauth_version="1.0",oauth_signature="ssh-secret%25263yG0Panskjm5GGwdP5SUHFFXmF7aCl0v"';
1097 | req.query = url.parse(req.url, true).query;
1098 | req.connection = { encrypted: false };
1099 | process.nextTick(function () {
1100 | strategy.authenticate(req);
1101 | });
1102 | },
1103 |
1104 | 'should not generate an error' : function(err, user, info) {
1105 | assert.isNull(err);
1106 | },
1107 | 'should authenticate' : function(err, user, info) {
1108 | assert.equal(user.id, '1');
1109 | },
1110 | 'should set scheme to OAuth' : function(err, user, info) {
1111 | assert.equal(info.scheme, 'OAuth');
1112 | },
1113 | 'should include token and verifier' : function(err, user, info) {
1114 | assert.equal(info.oauth.token, 'AbSRoiyN');
1115 | assert.equal(info.oauth.verifier, 'FOXJJYN0');
1116 | },
1117 | },
1118 | },
1119 |
1120 | 'strategy handling a valid request with a request token using HMAC-SHA256 signature': {
1121 | topic: function() {
1122 | var strategy = new ConsumerStrategy(
1123 | // consumer callback
1124 | function(consumerKey, done) {
1125 | if (consumerKey == 'abc123') {
1126 | done(null, { id: '1' }, 'ssh-secret');
1127 | } else {
1128 | done(new Error('something is wrong'))
1129 | }
1130 | },
1131 | // token callback
1132 | function(requestToken, done) {
1133 | if (requestToken == 'wM9YRRm5') {
1134 | done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF');
1135 | } else {
1136 | done(new Error('something is wrong'))
1137 | }
1138 | }
1139 | );
1140 | return strategy;
1141 | },
1142 |
1143 | 'after augmenting with actions': {
1144 | topic: function(strategy) {
1145 | var self = this;
1146 | var req = {};
1147 | strategy.success = function(user, info) {
1148 | self.callback(null, user, info);
1149 | }
1150 | strategy.fail = function(challenge, status) {
1151 | self.callback(new Error('should not be called'));
1152 | }
1153 | strategy.error = function(err) {
1154 | self.callback(new Error('should not be called'));
1155 | }
1156 |
1157 | req.url = '/oauth/access_token';
1158 | req.method = 'POST';
1159 | req.headers = {};
1160 | req.headers['host'] = '127.0.0.1:3000';
1161 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="06Kn8VWWkoLLz2yKbtc1j8hVXjFMlORwVMedb5S3otE="';
1162 | req.query = url.parse(req.url, true).query;
1163 | req.connection = { encrypted: false };
1164 | process.nextTick(function () {
1165 | strategy.authenticate(req);
1166 | });
1167 | },
1168 |
1169 | 'should not generate an error' : function(err, user, info) {
1170 | assert.isNull(err);
1171 | },
1172 | 'should authenticate' : function(err, user, info) {
1173 | assert.equal(user.id, '1');
1174 | },
1175 | 'should set scheme to OAuth' : function(err, user, info) {
1176 | assert.equal(info.scheme, 'OAuth');
1177 | },
1178 | 'should include token and verifier' : function(err, user, info) {
1179 | assert.equal(info.oauth.token, 'wM9YRRm5');
1180 | assert.equal(info.oauth.verifier, 'qriPjOnc');
1181 | },
1182 | },
1183 | },
1184 |
1185 | 'strategy handling a valid request with a request token where token callback supplies info': {
1186 | topic: function() {
1187 | var strategy = new ConsumerStrategy(
1188 | // consumer callback
1189 | function(consumerKey, done) {
1190 | done(null, { id: '1' }, 'ssh-secret');
1191 | },
1192 | // token callback
1193 | function(requestToken, done) {
1194 | done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF', { verifier: 'x1y2z3', userID: '456' });
1195 | }
1196 | );
1197 | return strategy;
1198 | },
1199 |
1200 | 'after augmenting with actions': {
1201 | topic: function(strategy) {
1202 | var self = this;
1203 | var req = {};
1204 | strategy.success = function(user, info) {
1205 | self.callback(null, user, info);
1206 | }
1207 | strategy.fail = function(challenge, status) {
1208 | self.callback(new Error('should not be called'));
1209 | }
1210 | strategy.error = function(err) {
1211 | self.callback(new Error('should not be called'));
1212 | }
1213 |
1214 | req.url = '/oauth/access_token';
1215 | req.method = 'POST';
1216 | req.headers = {};
1217 | req.headers['host'] = '127.0.0.1:3000';
1218 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="ZP5%2FtXZcUiiD2HXKrevCL5FjY%2FM%3D"';
1219 | req.query = url.parse(req.url, true).query;
1220 | req.connection = { encrypted: false };
1221 | process.nextTick(function () {
1222 | strategy.authenticate(req);
1223 | });
1224 | },
1225 |
1226 | 'should not generate an error' : function(err, user, info) {
1227 | assert.isNull(err);
1228 | },
1229 | 'should authenticate' : function(err, user, info) {
1230 | assert.equal(user.id, '1');
1231 | },
1232 | 'should set scheme to OAuth' : function(err, user, info) {
1233 | assert.equal(info.scheme, 'OAuth');
1234 | },
1235 | 'should include token and verifier' : function(err, user, info) {
1236 | assert.equal(info.oauth.token, 'wM9YRRm5');
1237 | assert.equal(info.oauth.verifier, 'qriPjOnc');
1238 | },
1239 | 'should preserve info' : function(err, user, info) {
1240 | assert.equal(info.verifier, 'x1y2z3');
1241 | assert.equal(info.userID, '456');
1242 | },
1243 | },
1244 | },
1245 |
1246 | 'strategy handling a valid request with a request token where timestamp and nonce are validated': {
1247 | topic: function() {
1248 | var strategy = new ConsumerStrategy(
1249 | // consumer callback
1250 | function(consumerKey, done) {
1251 | done(null, { id: '1' }, 'ssh-secret');
1252 | },
1253 | // token callback
1254 | function(requestToken, done) {
1255 | done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF');
1256 | },
1257 | // validate callback
1258 | function(timestamp, nonce, done) {
1259 | if (timestamp == 1341178687 && nonce == 'KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW') {
1260 | done(null, true);
1261 | } else {
1262 | done(new Error('something is wrong'))
1263 | }
1264 | }
1265 | );
1266 | return strategy;
1267 | },
1268 |
1269 | 'after augmenting with actions': {
1270 | topic: function(strategy) {
1271 | var self = this;
1272 | var req = {};
1273 | strategy.success = function(user, info) {
1274 | self.callback(null, user, info);
1275 | }
1276 | strategy.fail = function(challenge, status) {
1277 | self.callback(new Error('should not be called'));
1278 | }
1279 | strategy.error = function(err) {
1280 | self.callback(new Error('should not be called'));
1281 | }
1282 |
1283 | req.url = '/oauth/access_token';
1284 | req.method = 'POST';
1285 | req.headers = {};
1286 | req.headers['host'] = '127.0.0.1:3000';
1287 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="ZP5%2FtXZcUiiD2HXKrevCL5FjY%2FM%3D"';
1288 | req.query = url.parse(req.url, true).query;
1289 | req.connection = { encrypted: false };
1290 | process.nextTick(function () {
1291 | strategy.authenticate(req);
1292 | });
1293 | },
1294 |
1295 | 'should not generate an error' : function(err, user, info) {
1296 | assert.isNull(err);
1297 | },
1298 | 'should authenticate' : function(err, user, info) {
1299 | assert.equal(user.id, '1');
1300 | },
1301 | 'should set scheme to OAuth' : function(err, user, info) {
1302 | assert.equal(info.scheme, 'OAuth');
1303 | },
1304 | 'should include token and verifier' : function(err, user, info) {
1305 | assert.equal(info.oauth.token, 'wM9YRRm5');
1306 | assert.equal(info.oauth.verifier, 'qriPjOnc');
1307 | },
1308 | },
1309 | },
1310 |
1311 | 'strategy handling a valid request with a request token where consumer is not authenticated': {
1312 | topic: function() {
1313 | var strategy = new ConsumerStrategy(
1314 | // consumer callback
1315 | function(consumerKey, done) {
1316 | done(null, false);
1317 | },
1318 | // token callback
1319 | function(requestToken, done) {
1320 | done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF', { verifier: 'x1y2z3', userID: '456' });
1321 | }
1322 | );
1323 | return strategy;
1324 | },
1325 |
1326 | 'after augmenting with actions': {
1327 | topic: function(strategy) {
1328 | var self = this;
1329 | var req = {};
1330 | strategy.success = function(user, info) {
1331 | self.callback(new Error('should not be called'));
1332 | }
1333 | strategy.fail = function(challenge, status) {
1334 | self.callback(null, challenge, status);
1335 | }
1336 | strategy.error = function(err) {
1337 | self.callback(new Error('should not be called'));
1338 | }
1339 |
1340 | req.url = '/oauth/access_token';
1341 | req.method = 'POST';
1342 | req.headers = {};
1343 | req.headers['host'] = '127.0.0.1:3000';
1344 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="ZP5%2FtXZcUiiD2HXKrevCL5FjY%2FM%3D"';
1345 | req.query = url.parse(req.url, true).query;
1346 | req.connection = { encrypted: false };
1347 | process.nextTick(function () {
1348 | strategy.authenticate(req);
1349 | });
1350 | },
1351 |
1352 | 'should not generate an error' : function(err, user, info) {
1353 | assert.isNull(err);
1354 | },
1355 | 'should respond with challenge' : function(err, challenge, status) {
1356 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="consumer_key_rejected"');
1357 | },
1358 | 'should respond with default status' : function(err, challenge, status) {
1359 | assert.isUndefined(status);
1360 | },
1361 | },
1362 | },
1363 |
1364 | 'strategy handling a valid request with a request token where token is not authenticated': {
1365 | topic: function() {
1366 | var strategy = new ConsumerStrategy(
1367 | // consumer callback
1368 | function(consumerKey, done) {
1369 | done(null, { id: '1' }, 'ssh-secret');
1370 | },
1371 | // token callback
1372 | function(requestToken, done) {
1373 | done(null, false);
1374 | }
1375 | );
1376 | return strategy;
1377 | },
1378 |
1379 | 'after augmenting with actions': {
1380 | topic: function(strategy) {
1381 | var self = this;
1382 | var req = {};
1383 | strategy.success = function(user, info) {
1384 | self.callback(new Error('should not be called'));
1385 | }
1386 | strategy.fail = function(challenge, status) {
1387 | self.callback(null, challenge, status);
1388 | }
1389 | strategy.error = function(err) {
1390 | self.callback(new Error('should not be called'));
1391 | }
1392 |
1393 | req.url = '/oauth/access_token';
1394 | req.method = 'POST';
1395 | req.headers = {};
1396 | req.headers['host'] = '127.0.0.1:3000';
1397 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="ZP5%2FtXZcUiiD2HXKrevCL5FjY%2FM%3D"';
1398 | req.query = url.parse(req.url, true).query;
1399 | req.connection = { encrypted: false };
1400 | process.nextTick(function () {
1401 | strategy.authenticate(req);
1402 | });
1403 | },
1404 |
1405 | 'should not generate an error' : function(err, user, info) {
1406 | assert.isNull(err);
1407 | },
1408 | 'should respond with challenge' : function(err, challenge, status) {
1409 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="token_rejected"');
1410 | },
1411 | 'should respond with default status' : function(err, challenge, status) {
1412 | assert.isUndefined(status);
1413 | },
1414 | },
1415 | },
1416 |
1417 | 'strategy handling a valid request with a request token where timestamp and nonce are not validated': {
1418 | topic: function() {
1419 | var strategy = new ConsumerStrategy(
1420 | // consumer callback
1421 | function(consumerKey, done) {
1422 | done(null, { id: '1' }, 'ssh-secret');
1423 | },
1424 | // token callback
1425 | function(requestToken, done) {
1426 | done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF');
1427 | },
1428 | // validate callback
1429 | function(timestamp, nonce, done) {
1430 | done(null, false);
1431 | }
1432 | );
1433 | return strategy;
1434 | },
1435 |
1436 | 'after augmenting with actions': {
1437 | topic: function(strategy) {
1438 | var self = this;
1439 | var req = {};
1440 | strategy.success = function(user, info) {
1441 | self.callback(new Error('should not be called'));
1442 | }
1443 | strategy.fail = function(challenge, status) {
1444 | self.callback(null, challenge, status);
1445 | }
1446 | strategy.error = function(err) {
1447 | self.callback(new Error('should not be called'));
1448 | }
1449 |
1450 | req.url = '/oauth/access_token';
1451 | req.method = 'POST';
1452 | req.headers = {};
1453 | req.headers['host'] = '127.0.0.1:3000';
1454 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="ZP5%2FtXZcUiiD2HXKrevCL5FjY%2FM%3D"';
1455 | req.query = url.parse(req.url, true).query;
1456 | req.connection = { encrypted: false };
1457 | process.nextTick(function () {
1458 | strategy.authenticate(req);
1459 | });
1460 | },
1461 |
1462 | 'should not generate an error' : function(err, user, info) {
1463 | assert.isNull(err);
1464 | },
1465 | 'should respond with challenge' : function(err, challenge, status) {
1466 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="nonce_used"');
1467 | },
1468 | 'should respond with default status' : function(err, challenge, status) {
1469 | assert.isUndefined(status);
1470 | },
1471 | },
1472 | },
1473 |
1474 | 'strategy handling a valid request with a request token using HMAC-SHA1 signature where token secret is wrong': {
1475 | topic: function() {
1476 | var strategy = new ConsumerStrategy(
1477 | // consumer callback
1478 | function(consumerKey, done) {
1479 | done(null, { id: '1' }, 'ssh-secret');
1480 | },
1481 | // token callback
1482 | function(requestToken, done) {
1483 | done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF-wrong');
1484 | }
1485 | );
1486 | return strategy;
1487 | },
1488 |
1489 | 'after augmenting with actions': {
1490 | topic: function(strategy) {
1491 | var self = this;
1492 | var req = {};
1493 | strategy.success = function(user, info) {
1494 | self.callback(new Error('should not be called'));
1495 | }
1496 | strategy.fail = function(challenge, status) {
1497 | self.callback(null, challenge, status);
1498 | }
1499 | strategy.error = function(err) {
1500 | self.callback(new Error('should not be called'));
1501 | }
1502 |
1503 | req.url = '/oauth/access_token';
1504 | req.method = 'POST';
1505 | req.headers = {};
1506 | req.headers['host'] = '127.0.0.1:3000';
1507 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="ZP5%2FtXZcUiiD2HXKrevCL5FjY%2FM%3D"';
1508 | req.query = url.parse(req.url, true).query;
1509 | req.connection = { encrypted: false };
1510 | process.nextTick(function () {
1511 | strategy.authenticate(req);
1512 | });
1513 | },
1514 |
1515 | 'should not generate an error' : function(err, user, info) {
1516 | assert.isNull(err);
1517 | },
1518 | 'should respond with challenge' : function(err, challenge, status) {
1519 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="signature_invalid"');
1520 | },
1521 | 'should respond with default status' : function(err, challenge, status) {
1522 | assert.isUndefined(status);
1523 | },
1524 | },
1525 | },
1526 |
1527 | 'strategy handling a valid request with a request token using PLAINTEXT signature where token secret is wrong': {
1528 | topic: function() {
1529 | var strategy = new ConsumerStrategy(
1530 | // consumer callback
1531 | function(consumerKey, done) {
1532 | done(null, { id: '1' }, 'ssh-secret');
1533 | },
1534 | // token callback
1535 | function(requestToken, done) {
1536 | done(null, '3yG0Panskjm5GGwdP5SUHFFXmF7aCl0v-wrong');
1537 | }
1538 | );
1539 | return strategy;
1540 | },
1541 |
1542 | 'after augmenting with actions': {
1543 | topic: function(strategy) {
1544 | var self = this;
1545 | var req = {};
1546 | strategy.success = function(user, info) {
1547 | self.callback(new Error('should not be called'));
1548 | }
1549 | strategy.fail = function(challenge, status) {
1550 | self.callback(null, challenge, status);
1551 | }
1552 | strategy.error = function(err) {
1553 | self.callback(new Error('should not be called'));
1554 | }
1555 |
1556 | req.url = '/oauth/access_token';
1557 | req.method = 'POST';
1558 | req.headers = {};
1559 | req.headers['host'] = '127.0.0.1:3000';
1560 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="iiWqS4a7mKrpQWXO07osM9Om0PCDsMHN",oauth_signature_method="PLAINTEXT",oauth_timestamp="1341196375",oauth_token="AbSRoiyN",oauth_verifier="FOXJJYN0",oauth_version="1.0",oauth_signature="ssh-secret%25263yG0Panskjm5GGwdP5SUHFFXmF7aCl0v"';
1561 | req.query = url.parse(req.url, true).query;
1562 | req.connection = { encrypted: false };
1563 | process.nextTick(function () {
1564 | strategy.authenticate(req);
1565 | });
1566 | },
1567 |
1568 | 'should not generate an error' : function(err, user, info) {
1569 | assert.isNull(err);
1570 | },
1571 | 'should respond with challenge' : function(err, challenge, status) {
1572 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="signature_invalid"');
1573 | },
1574 | 'should respond with default status' : function(err, challenge, status) {
1575 | assert.isUndefined(status);
1576 | },
1577 | },
1578 | },
1579 |
1580 | 'strategy handling a valid request with a request token using HMAC-SHA256 signature where token secret is wrong': {
1581 | topic: function() {
1582 | var strategy = new ConsumerStrategy(
1583 | // consumer callback
1584 | function(consumerKey, done) {
1585 | done(null, { id: '1' }, 'ssh-secret');
1586 | },
1587 | // token callback
1588 | function(requestToken, done) {
1589 | done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF-wrong');
1590 | }
1591 | );
1592 | return strategy;
1593 | },
1594 |
1595 | 'after augmenting with actions': {
1596 | topic: function(strategy) {
1597 | var self = this;
1598 | var req = {};
1599 | strategy.success = function(user, info) {
1600 | self.callback(new Error('should not be called'));
1601 | }
1602 | strategy.fail = function(challenge, status) {
1603 | self.callback(null, challenge, status);
1604 | }
1605 | strategy.error = function(err) {
1606 | self.callback(new Error('should not be called'));
1607 | }
1608 |
1609 | req.url = '/oauth/access_token';
1610 | req.method = 'POST';
1611 | req.headers = {};
1612 | req.headers['host'] = '127.0.0.1:3000';
1613 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="nobodycares"';
1614 | req.query = url.parse(req.url, true).query;
1615 | req.connection = { encrypted: false };
1616 | process.nextTick(function () {
1617 | strategy.authenticate(req);
1618 | });
1619 | },
1620 |
1621 | 'should not generate an error' : function(err, user, info) {
1622 | assert.isNull(err);
1623 | },
1624 | 'should respond with challenge' : function(err, challenge, status) {
1625 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="signature_invalid"');
1626 | },
1627 | 'should respond with default status' : function(err, challenge, status) {
1628 | assert.isUndefined(status);
1629 | },
1630 | },
1631 | },
1632 |
1633 | 'strategy handling a valid request with a request token where consumer callback fails with an error': {
1634 | topic: function() {
1635 | var strategy = new ConsumerStrategy(
1636 | // consumer callback
1637 | function(consumerKey, done) {
1638 | done(new Error('consumer callback failure'));
1639 | },
1640 | // token callback
1641 | function(requestToken, done) {
1642 | done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF');
1643 | }
1644 | );
1645 | return strategy;
1646 | },
1647 |
1648 | 'after augmenting with actions': {
1649 | topic: function(strategy) {
1650 | var self = this;
1651 | var req = {};
1652 | strategy.success = function(user, info) {
1653 | self.callback(new Error('should not be called'));
1654 | }
1655 | strategy.fail = function(challenge, status) {
1656 | self.callback(new Error('should not be called'));
1657 | }
1658 | strategy.error = function(err) {
1659 | self.callback(null, err);
1660 | }
1661 |
1662 | req.url = '/oauth/access_token';
1663 | req.method = 'POST';
1664 | req.headers = {};
1665 | req.headers['host'] = '127.0.0.1:3000';
1666 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="ZP5%2FtXZcUiiD2HXKrevCL5FjY%2FM%3D"';
1667 | req.query = url.parse(req.url, true).query;
1668 | req.connection = { encrypted: false };
1669 | process.nextTick(function () {
1670 | strategy.authenticate(req);
1671 | });
1672 | },
1673 |
1674 | 'should not call success or fail' : function(err, e) {
1675 | assert.isNull(err);
1676 | },
1677 | 'should call error' : function(err, e) {
1678 | assert.instanceOf(e, Error);
1679 | assert.equal(e.message, 'consumer callback failure');
1680 | },
1681 | },
1682 | },
1683 |
1684 | 'strategy handling a valid request with a request token where token callback fails with an error': {
1685 | topic: function() {
1686 | var strategy = new ConsumerStrategy(
1687 | // consumer callback
1688 | function(consumerKey, done) {
1689 | done(null, { id: '1' }, 'ssh-secret');
1690 | },
1691 | // token callback
1692 | function(requestToken, done) {
1693 | done(new Error('token callback failure'));
1694 | }
1695 | );
1696 | return strategy;
1697 | },
1698 |
1699 | 'after augmenting with actions': {
1700 | topic: function(strategy) {
1701 | var self = this;
1702 | var req = {};
1703 | strategy.success = function(user, info) {
1704 | self.callback(new Error('should not be called'));
1705 | }
1706 | strategy.fail = function(challenge, status) {
1707 | self.callback(new Error('should not be called'));
1708 | }
1709 | strategy.error = function(err) {
1710 | self.callback(null, err);
1711 | }
1712 |
1713 | req.url = '/oauth/access_token';
1714 | req.method = 'POST';
1715 | req.headers = {};
1716 | req.headers['host'] = '127.0.0.1:3000';
1717 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="ZP5%2FtXZcUiiD2HXKrevCL5FjY%2FM%3D"';
1718 | req.query = url.parse(req.url, true).query;
1719 | req.connection = { encrypted: false };
1720 | process.nextTick(function () {
1721 | strategy.authenticate(req);
1722 | });
1723 | },
1724 |
1725 | 'should not call success or fail' : function(err, e) {
1726 | assert.isNull(err);
1727 | },
1728 | 'should call error' : function(err, e) {
1729 | assert.instanceOf(e, Error);
1730 | assert.equal(e.message, 'token callback failure');
1731 | },
1732 | },
1733 | },
1734 |
1735 | 'strategy handling a valid request with a request token where validate callback fails with an error': {
1736 | topic: function() {
1737 | var strategy = new ConsumerStrategy(
1738 | // consumer callback
1739 | function(consumerKey, done) {
1740 | done(null, { id: '1' }, 'ssh-secret');
1741 | },
1742 | // token callback
1743 | function(requestToken, done) {
1744 | done(null, 'rxt0E5hKbslOEtzxD43hclL28XBZLJsF');
1745 | },
1746 | // validate callback
1747 | function(timestamp, nonce, done) {
1748 | done(new Error('validate callback failure'));
1749 | }
1750 | );
1751 | return strategy;
1752 | },
1753 |
1754 | 'after augmenting with actions': {
1755 | topic: function(strategy) {
1756 | var self = this;
1757 | var req = {};
1758 | strategy.success = function(user, info) {
1759 | self.callback(new Error('should not be called'));
1760 | }
1761 | strategy.fail = function(challenge, status) {
1762 | self.callback(new Error('should not be called'));
1763 | }
1764 | strategy.error = function(err) {
1765 | self.callback(null, err);
1766 | }
1767 |
1768 | req.url = '/oauth/access_token';
1769 | req.method = 'POST';
1770 | req.headers = {};
1771 | req.headers['host'] = '127.0.0.1:3000';
1772 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="KyEf2M5ptWGDcz04jMScA2iJHkXHzkUW",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341178687",oauth_token="wM9YRRm5",oauth_verifier="qriPjOnc",oauth_version="1.0",oauth_signature="ZP5%2FtXZcUiiD2HXKrevCL5FjY%2FM%3D"';
1773 | req.query = url.parse(req.url, true).query;
1774 | req.connection = { encrypted: false };
1775 | process.nextTick(function () {
1776 | strategy.authenticate(req);
1777 | });
1778 | },
1779 |
1780 | 'should not call success or fail' : function(err, e) {
1781 | assert.isNull(err);
1782 | },
1783 | 'should call error' : function(err, e) {
1784 | assert.instanceOf(e, Error);
1785 | assert.equal(e.message, 'validate callback failure');
1786 | },
1787 | },
1788 | },
1789 |
1790 | 'strategy handling a request without authentication credentials': {
1791 | topic: function() {
1792 | var strategy = new ConsumerStrategy(
1793 | // consumer callback
1794 | function(consumerKey, done) {
1795 | done(null, { id: '1' }, 'ssh-secret');
1796 | },
1797 | // token callback
1798 | function(requestToken, done) {
1799 | done(new Error('token callback should not be called'));
1800 | }
1801 | );
1802 | return strategy;
1803 | },
1804 |
1805 | 'after augmenting with actions': {
1806 | topic: function(strategy) {
1807 | var self = this;
1808 | var req = {};
1809 | strategy.success = function(user, info) {
1810 | self.callback(new Error('should not be called'));
1811 | }
1812 | strategy.fail = function(challenge, status) {
1813 | self.callback(null, challenge, status);
1814 | }
1815 | strategy.error = function(err) {
1816 | self.callback(new Error('should not be called'));
1817 | }
1818 |
1819 | req.url = '/oauth/request_token';
1820 | req.method = 'POST';
1821 | req.headers = {};
1822 | req.headers['host'] = '127.0.0.1:3000';
1823 | req.query = url.parse(req.url, true).query;
1824 | req.connection = { encrypted: false };
1825 | process.nextTick(function () {
1826 | strategy.authenticate(req);
1827 | });
1828 | },
1829 |
1830 | 'should not generate an error' : function(err, challenge, status) {
1831 | assert.isNull(err);
1832 | },
1833 | 'should respond with challenge' : function(err, challenge, status) {
1834 | assert.equal(challenge, 'OAuth realm="Clients"');
1835 | },
1836 | 'should respond with default status' : function(err, challenge, status) {
1837 | assert.isUndefined(status);
1838 | },
1839 | },
1840 | },
1841 |
1842 | 'strategy handling a request without authentication credentials and realm option': {
1843 | topic: function() {
1844 | var strategy = new ConsumerStrategy({ realm: 'Foo' },
1845 | // consumer callback
1846 | function(consumerKey, done) {
1847 | done(null, { id: '1' }, 'ssh-secret');
1848 | },
1849 | // token callback
1850 | function(requestToken, done) {
1851 | done(new Error('token callback should not be called'));
1852 | }
1853 | );
1854 | return strategy;
1855 | },
1856 |
1857 | 'after augmenting with actions': {
1858 | topic: function(strategy) {
1859 | var self = this;
1860 | var req = {};
1861 | strategy.success = function(user, info) {
1862 | self.callback(new Error('should not be called'));
1863 | }
1864 | strategy.fail = function(challenge, status) {
1865 | self.callback(null, challenge, status);
1866 | }
1867 | strategy.error = function(err) {
1868 | self.callback(new Error('should not be called'));
1869 | }
1870 |
1871 | req.url = '/oauth/request_token';
1872 | req.method = 'POST';
1873 | req.headers = {};
1874 | req.headers['host'] = '127.0.0.1:3000';
1875 | req.query = url.parse(req.url, true).query;
1876 | req.connection = { encrypted: false };
1877 | process.nextTick(function () {
1878 | strategy.authenticate(req);
1879 | });
1880 | },
1881 |
1882 | 'should not generate an error' : function(err, challenge, status) {
1883 | assert.isNull(err);
1884 | },
1885 | 'should respond with challenge' : function(err, challenge, status) {
1886 | assert.equal(challenge, 'OAuth realm="Foo"');
1887 | },
1888 | 'should respond with default status' : function(err, challenge, status) {
1889 | assert.isUndefined(status);
1890 | },
1891 | },
1892 | },
1893 |
1894 | 'strategy handling a request with non-OAuth scheme': {
1895 | topic: function() {
1896 | var strategy = new ConsumerStrategy(
1897 | // consumer callback
1898 | function(consumerKey, done) {
1899 | done(null, { id: '1' }, 'ssh-secret');
1900 | },
1901 | // token callback
1902 | function(requestToken, done) {
1903 | done(new Error('token callback should not be called'));
1904 | }
1905 | );
1906 | return strategy;
1907 | },
1908 |
1909 | 'after augmenting with actions': {
1910 | topic: function(strategy) {
1911 | var self = this;
1912 | var req = {};
1913 | strategy.success = function(user, info) {
1914 | self.callback(new Error('should not be called'));
1915 | }
1916 | strategy.fail = function(challenge, status) {
1917 | self.callback(null, challenge, status);
1918 | }
1919 | strategy.error = function(err) {
1920 | self.callback(new Error('should not be called'));
1921 | }
1922 |
1923 | req.url = '/oauth/request_token';
1924 | req.method = 'POST';
1925 | req.headers = {};
1926 | req.headers['host'] = '127.0.0.1:3000';
1927 | req.headers['authorization'] = 'FooBar vF9dft4qmT';
1928 | req.query = url.parse(req.url, true).query;
1929 | req.connection = { encrypted: false };
1930 | process.nextTick(function () {
1931 | strategy.authenticate(req);
1932 | });
1933 | },
1934 |
1935 | 'should not generate an error' : function(err, challenge, status) {
1936 | assert.isNull(err);
1937 | },
1938 | 'should respond with challenge' : function(err, challenge, status) {
1939 | assert.equal(challenge, 'OAuth realm="Clients"');
1940 | },
1941 | 'should respond with default status' : function(err, challenge, status) {
1942 | assert.isUndefined(status);
1943 | },
1944 | },
1945 | },
1946 |
1947 | 'strategy handling a request with malformed OAuth scheme': {
1948 | topic: function() {
1949 | var strategy = new ConsumerStrategy(
1950 | // consumer callback
1951 | function(consumerKey, done) {
1952 | done(null, { id: '1' }, 'ssh-secret');
1953 | },
1954 | // token callback
1955 | function(requestToken, done) {
1956 | done(new Error('token callback should not be called'));
1957 | }
1958 | );
1959 | return strategy;
1960 | },
1961 |
1962 | 'after augmenting with actions': {
1963 | topic: function(strategy) {
1964 | var self = this;
1965 | var req = {};
1966 | strategy.success = function(user, info) {
1967 | self.callback(new Error('should not be called'));
1968 | }
1969 | strategy.fail = function(challenge, status) {
1970 | self.callback(null, challenge, status);
1971 | }
1972 | strategy.error = function(err) {
1973 | self.callback(new Error('should not be called'));
1974 | }
1975 |
1976 | req.url = '/oauth/request_token';
1977 | req.method = 'POST';
1978 | req.headers = {};
1979 | req.headers['host'] = '127.0.0.1:3000';
1980 | req.headers['authorization'] = 'OAuth';
1981 | req.query = url.parse(req.url, true).query;
1982 | req.connection = { encrypted: false };
1983 | process.nextTick(function () {
1984 | strategy.authenticate(req);
1985 | });
1986 | },
1987 |
1988 | 'should not generate an error' : function(err, challenge, status) {
1989 | assert.isNull(err);
1990 | },
1991 | 'should respond without bad request status' : function(err, challenge, status) {
1992 | assert.strictEqual(challenge, 400);
1993 | },
1994 | },
1995 | },
1996 |
1997 | 'strategy handling a request with missing parameters': {
1998 | topic: function() {
1999 | var strategy = new ConsumerStrategy(
2000 | // consumer callback
2001 | function(consumerKey, done) {
2002 | done(null, { id: '1' }, 'ssh-secret');
2003 | },
2004 | // token callback
2005 | function(requestToken, done) {
2006 | done(new Error('token callback should not be called'));
2007 | }
2008 | );
2009 | return strategy;
2010 | },
2011 |
2012 | 'after augmenting with actions': {
2013 | topic: function(strategy) {
2014 | var self = this;
2015 | var req = {};
2016 | strategy.success = function(user, info) {
2017 | self.callback(new Error('should not be called'));
2018 | }
2019 | strategy.fail = function(challenge, status) {
2020 | self.callback(null, challenge, status);
2021 | }
2022 | strategy.error = function(err) {
2023 | self.callback(new Error('should not be called'));
2024 | }
2025 |
2026 | req.url = '/oauth/request_token';
2027 | req.method = 'POST';
2028 | req.headers = {};
2029 | req.headers['host'] = '127.0.0.1:3000';
2030 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341176111",oauth_version="1.0",oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
2031 | req.query = url.parse(req.url, true).query;
2032 | req.connection = { encrypted: false };
2033 | process.nextTick(function () {
2034 | strategy.authenticate(req);
2035 | });
2036 | },
2037 |
2038 | 'should not generate an error' : function(err, challenge, status) {
2039 | assert.isNull(err);
2040 | },
2041 | 'should respond with challenge' : function(err, challenge, status) {
2042 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="parameter_absent"');
2043 | },
2044 | 'should respond with 400 status' : function(err, challenge, status) {
2045 | assert.equal(status, 400);
2046 | },
2047 | },
2048 | },
2049 |
2050 | 'strategy handling a request with OAuth scheme with bad version': {
2051 | topic: function() {
2052 | var strategy = new ConsumerStrategy(
2053 | // consumer callback
2054 | function(consumerKey, done) {
2055 | done(null, { id: '1' }, 'ssh-secret');
2056 | },
2057 | // token callback
2058 | function(requestToken, done) {
2059 | done(new Error('token callback should not be called'));
2060 | }
2061 | );
2062 | return strategy;
2063 | },
2064 |
2065 | 'after augmenting with actions': {
2066 | topic: function(strategy) {
2067 | var self = this;
2068 | var req = {};
2069 | strategy.success = function(user, info) {
2070 | self.callback(new Error('should not be called'));
2071 | }
2072 | strategy.fail = function(challenge, status) {
2073 | self.callback(null, challenge, status);
2074 | }
2075 | strategy.error = function(err) {
2076 | self.callback(new Error('should not be called'));
2077 | }
2078 |
2079 | req.url = '/oauth/request_token';
2080 | req.method = 'POST';
2081 | req.headers = {};
2082 | req.headers['host'] = '127.0.0.1:3000';
2083 | req.headers['authorization'] = 'OAuth oauth_callback="http%3A%2F%2Fmacbook-air.local.jaredhanson.net%3A3001%2Foauth%2Fcallback",oauth_consumer_key="abc123",oauth_nonce="fNyKdt8ZTgTVdEABtUMFzcXRxF4a230q",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1341176111",oauth_version="1.1",oauth_signature="tgsFsPL%2BDDQmfEz6hbCywhO%2BrE4%3D"';
2084 | req.query = url.parse(req.url, true).query;
2085 | req.connection = { encrypted: false };
2086 | process.nextTick(function () {
2087 | strategy.authenticate(req);
2088 | });
2089 | },
2090 |
2091 | 'should not generate an error' : function(err, challenge, status) {
2092 | assert.isNull(err);
2093 | },
2094 | 'should respond with challenge' : function(err, challenge, status) {
2095 | assert.equal(challenge, 'OAuth realm="Clients", oauth_problem="version_rejected"');
2096 | },
2097 | 'should respond with 400 status' : function(err, challenge, status) {
2098 | assert.equal(status, 400);
2099 | },
2100 | },
2101 | },
2102 |
2103 | // TODO: Add test case for bad request with OAuth params in multiple locations
2104 |
2105 | 'strategy constructed without a consumer callback or token callback': {
2106 | 'should throw an error': function (strategy) {
2107 | assert.throws(function() { new ConsumerStrategy() });
2108 | },
2109 | },
2110 |
2111 | 'strategy constructed without a token callback': {
2112 | 'should throw an error': function (strategy) {
2113 | assert.throws(function() { new ConsumerStrategy(function() {}) });
2114 | },
2115 | },
2116 |
2117 | }).export(module);
2118 |
--------------------------------------------------------------------------------
/test/strategies/token-test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows');
2 | var assert = require('assert');
3 | var url = require('url');
4 | var util = require('util');
5 | var TokenStrategy = require('passport-http-oauth/strategies/token');
6 |
7 |
8 | vows.describe('TokenStrategy').addBatch({
9 |
10 | 'strategy': {
11 | topic: function() {
12 | return new TokenStrategy(function() {}, function() {});
13 | },
14 |
15 | 'should be named oauth': function(strategy) {
16 | assert.equal(strategy.name, 'oauth');
17 | },
18 | },
19 |
20 | 'strategy handling a valid request with credentials in header': {
21 | topic: function() {
22 | var strategy = new TokenStrategy(
23 | // consumer callback
24 | function(consumerKey, done) {
25 | if (consumerKey == '1234') {
26 | done(null, { id: '1' }, 'keep-this-secret');
27 | } else {
28 | done(new Error('something is wrong'))
29 | }
30 | },
31 | // verify callback
32 | function(accessToken, done) {
33 | if (accessToken == 'abc-123-xyz-789') {
34 | done(null, { username: 'bob' }, 'lips-zipped');
35 | } else {
36 | done(new Error('something is wrong'))
37 | }
38 | }
39 | );
40 | return strategy;
41 | },
42 |
43 | 'after augmenting with actions': {
44 | topic: function(strategy) {
45 | var self = this;
46 | var req = {};
47 | strategy.success = function(user, info) {
48 | self.callback(null, user, info);
49 | }
50 | strategy.fail = function(challenge, status) {
51 | self.callback(new Error('should not be called'));
52 | }
53 | strategy.error = function(err) {
54 | self.callback(new Error('should not be called'));
55 | }
56 |
57 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
58 | req.method = 'GET';
59 | req.headers = {};
60 | req.headers['host'] = '127.0.0.1:3000';
61 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
62 | req.query = url.parse(req.url, true).query;
63 | req.connection = { encrypted: false };
64 | process.nextTick(function () {
65 | strategy.authenticate(req);
66 | });
67 | },
68 |
69 | 'should not generate an error' : function(err, user, info) {
70 | assert.isNull(err);
71 | },
72 | 'should authenticate' : function(err, user, info) {
73 | assert.equal(user.username, 'bob');
74 | },
75 | 'should set scheme to OAuth' : function(err, user, info) {
76 | assert.equal(info.scheme, 'OAuth');
77 | },
78 | 'should set consumer' : function(err, user, info) {
79 | assert.equal(info.consumer.id, '1');
80 | assert.strictEqual(info.client, info.consumer);
81 | },
82 | },
83 | },
84 |
85 | 'strategy handling a valid request with credentials with spaces in header': {
86 | topic: function() {
87 | var strategy = new TokenStrategy(
88 | // consumer callback
89 | function(consumerKey, done) {
90 | if (consumerKey == '1234') {
91 | done(null, { id: '1' }, 'keep-this-secret');
92 | } else {
93 | done(new Error('something is wrong'))
94 | }
95 | },
96 | // verify callback
97 | function(accessToken, done) {
98 | if (accessToken == 'abc-123-xyz-789') {
99 | done(null, { username: 'bob' }, 'lips-zipped');
100 | } else {
101 | done(new Error('something is wrong'))
102 | }
103 | }
104 | );
105 | return strategy;
106 | },
107 |
108 | 'after augmenting with actions': {
109 | topic: function(strategy) {
110 | var self = this;
111 | var req = {};
112 | strategy.success = function(user, info) {
113 | self.callback(null, user, info);
114 | }
115 | strategy.fail = function(challenge, status) {
116 | self.callback(new Error('should not be called'));
117 | }
118 | strategy.error = function(err) {
119 | self.callback(new Error('should not be called'));
120 | }
121 |
122 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
123 | req.method = 'GET';
124 | req.headers = {};
125 | req.headers['host'] = '127.0.0.1:3000';
126 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234", oauth_nonce="A7E738D9A9684A60A40607017735ADAD", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1339004912", oauth_token="abc-123-xyz-789", oauth_version="1.0", oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
127 | req.query = url.parse(req.url, true).query;
128 | req.connection = { encrypted: false };
129 | process.nextTick(function () {
130 | strategy.authenticate(req);
131 | });
132 | },
133 |
134 | 'should not generate an error' : function(err, user, info) {
135 | assert.isNull(err);
136 | },
137 | 'should authenticate' : function(err, user, info) {
138 | assert.equal(user.username, 'bob');
139 | },
140 | 'should set scheme to OAuth' : function(err, user, info) {
141 | assert.equal(info.scheme, 'OAuth');
142 | },
143 | 'should set consumer' : function(err, user, info) {
144 | assert.equal(info.consumer.id, '1');
145 | assert.strictEqual(info.client, info.consumer);
146 | },
147 | },
148 | },
149 |
150 | 'strategy handling a valid request with credentials in header using 1.0A version': {
151 | topic: function() {
152 | var strategy = new TokenStrategy(
153 | { ignoreVersion: true },
154 | // consumer callback
155 | function(consumerKey, done) {
156 | if (consumerKey == '1234') {
157 | done(null, { id: '1' }, 'keep-this-secret');
158 | } else {
159 | done(new Error('something is wrong'))
160 | }
161 | },
162 | // verify callback
163 | function(accessToken, done) {
164 | if (accessToken == 'abc-123-xyz-789') {
165 | done(null, { username: 'bob' }, 'lips-zipped');
166 | } else {
167 | done(new Error('something is wrong'))
168 | }
169 | }
170 | );
171 | return strategy;
172 | },
173 |
174 | 'after augmenting with actions': {
175 | topic: function(strategy) {
176 | var self = this;
177 | var req = {};
178 | strategy.success = function(user, info) {
179 | self.callback(null, user, info);
180 | }
181 | strategy.fail = function(challenge, status) {
182 | self.callback(new Error('should not be called'));
183 | }
184 | strategy.error = function(err) {
185 | self.callback(new Error('should not be called'));
186 | }
187 |
188 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
189 | req.method = 'GET';
190 | req.headers = {};
191 | req.headers['host'] = '127.0.0.1:3000';
192 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0A",oauth_signature="W%2BppR%2BZyXT5UgrLV%2FTQnmlVSjZI%3D"';
193 | req.query = url.parse(req.url, true).query;
194 | req.connection = { encrypted: false };
195 | process.nextTick(function () {
196 | strategy.authenticate(req);
197 | });
198 | },
199 |
200 | 'should not generate an error' : function(err, user, info) {
201 | assert.isNull(err);
202 | },
203 | 'should authenticate' : function(err, user, info) {
204 | assert.equal(user.username, 'bob');
205 | },
206 | 'should set scheme to OAuth' : function(err, user, info) {
207 | assert.equal(info.scheme, 'OAuth');
208 | },
209 | 'should set consumer' : function(err, user, info) {
210 | assert.equal(info.consumer.id, '1');
211 | assert.strictEqual(info.client, info.consumer);
212 | },
213 | },
214 | },
215 |
216 | 'strategy handling a valid request using host option instead of host header': {
217 | topic: function() {
218 | var strategy = new TokenStrategy(
219 | { host: '127.0.0.1:3000' },
220 | // consumer callback
221 | function(consumerKey, done) {
222 | if (consumerKey == '1234') {
223 | done(null, { id: '1' }, 'keep-this-secret');
224 | } else {
225 | done(new Error('something is wrong'))
226 | }
227 | },
228 | // verify callback
229 | function(accessToken, done) {
230 | if (accessToken == 'abc-123-xyz-789') {
231 | done(null, { username: 'bob' }, 'lips-zipped');
232 | } else {
233 | done(new Error('something is wrong'))
234 | }
235 | }
236 | );
237 | return strategy;
238 | },
239 |
240 | 'after augmenting with actions': {
241 | topic: function(strategy) {
242 | var self = this;
243 | var req = {};
244 | strategy.success = function(user, info) {
245 | self.callback(null, user, info);
246 | }
247 | strategy.fail = function(challenge, status) {
248 | self.callback(new Error('should not be called'));
249 | }
250 | strategy.error = function(err) {
251 | self.callback(new Error('should not be called'));
252 | }
253 |
254 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
255 | req.method = 'GET';
256 | req.headers = {};
257 | //req.headers['host'] = '127.0.0.1:3000';
258 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
259 | req.query = url.parse(req.url, true).query;
260 | req.connection = { encrypted: false };
261 | process.nextTick(function () {
262 | strategy.authenticate(req);
263 | });
264 | },
265 |
266 | 'should not generate an error' : function(err, user, info) {
267 | assert.isNull(err);
268 | },
269 | 'should authenticate' : function(err, user, info) {
270 | assert.equal(user.username, 'bob');
271 | },
272 | 'should set scheme to OAuth' : function(err, user, info) {
273 | assert.equal(info.scheme, 'OAuth');
274 | },
275 | 'should set consumer' : function(err, user, info) {
276 | assert.equal(info.consumer.id, '1');
277 | assert.strictEqual(info.client, info.consumer);
278 | },
279 | },
280 | },
281 |
282 | 'strategy handling a valid request with credentials in header using PLAINTEXT signature': {
283 | topic: function() {
284 | var strategy = new TokenStrategy(
285 | // consumer callback
286 | function(consumerKey, done) {
287 | done(null, { id: '1' }, 'ssh-secret');
288 | },
289 | // verify callback
290 | function(accessToken, done) {
291 | done(null, { username: 'bob' }, 'mmyauoBm7rRv0kLsNKAicmtsxsxKWJDmoEo7obTqglkyGNHs8hn78pkTj70tXatl');
292 | }
293 | );
294 | return strategy;
295 | },
296 |
297 | 'after augmenting with actions': {
298 | topic: function(strategy) {
299 | var self = this;
300 | var req = {};
301 | strategy.success = function(user, info) {
302 | self.callback(null, user, info);
303 | }
304 | strategy.fail = function(challenge, status) {
305 | self.callback(new Error('should not be called'));
306 | }
307 | strategy.error = function(err) {
308 | self.callback(new Error('should not be called'));
309 | }
310 |
311 | req.url = '/api/userinfo';
312 | req.method = 'GET';
313 | req.headers = {};
314 | req.headers['host'] = '127.0.0.1:3000';
315 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="bSzaRm1X9uu6DwjAuAsOnn6cnxYoVibS",oauth_signature_method="PLAINTEXT",oauth_timestamp="1341195485",oauth_token="Xe4F8Cf5vw68BoZF",oauth_version="1.0",oauth_signature="ssh-secret%2526mmyauoBm7rRv0kLsNKAicmtsxsxKWJDmoEo7obTqglkyGNHs8hn78pkTj70tXatl"';
316 | req.query = url.parse(req.url, true).query;
317 | req.connection = { encrypted: false };
318 | process.nextTick(function () {
319 | strategy.authenticate(req);
320 | });
321 | },
322 |
323 | 'should not generate an error' : function(err, user, info) {
324 | assert.isNull(err);
325 | },
326 | 'should authenticate' : function(err, user, info) {
327 | assert.equal(user.username, 'bob');
328 | },
329 | 'should set scheme to OAuth' : function(err, user, info) {
330 | assert.equal(info.scheme, 'OAuth');
331 | },
332 | 'should set consumer' : function(err, user, info) {
333 | assert.equal(info.consumer.id, '1');
334 | assert.strictEqual(info.client, info.consumer);
335 | },
336 | },
337 | },
338 |
339 | 'strategy handling a valid request with credentials in header using HMAC-SHA256 signature': {
340 | topic: function() {
341 | var strategy = new TokenStrategy(
342 | // consumer callback
343 | function(consumerKey, done) {
344 | if (consumerKey == '1234') {
345 | done(null, { id: '1' }, 'keep-this-secret');
346 | } else {
347 | done(new Error('something is wrong'))
348 | }
349 | },
350 | // verify callback
351 | function(accessToken, done) {
352 | if (accessToken == 'abc-123-xyz-789') {
353 | done(null, { username: 'bob' }, 'lips-zipped');
354 | } else {
355 | done(new Error('something is wrong'))
356 | }
357 | }
358 | );
359 | return strategy;
360 | },
361 |
362 | 'after augmenting with actions': {
363 | topic: function(strategy) {
364 | var self = this;
365 | var req = {};
366 | strategy.success = function(user, info) {
367 | self.callback(null, user, info);
368 | }
369 | strategy.fail = function(challenge, status) {
370 | self.callback(new Error('should not be called'));
371 | }
372 | strategy.error = function(err) {
373 | self.callback(new Error('should not be called'));
374 | }
375 |
376 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
377 | req.method = 'GET';
378 | req.headers = {};
379 | req.headers['host'] = '127.0.0.1:3000';
380 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="IYG4DLnSdclIVr6rnq49cEjD7AaGqFiUGobOUAjDcpQ="';
381 | req.query = url.parse(req.url, true).query;
382 | req.connection = { encrypted: false };
383 | process.nextTick(function () {
384 | strategy.authenticate(req);
385 | });
386 | },
387 |
388 | 'should not generate an error' : function(err, user, info) {
389 | assert.isNull(err);
390 | },
391 | 'should authenticate' : function(err, user, info) {
392 | assert.equal(user.username, 'bob');
393 | },
394 | 'should set scheme to OAuth' : function(err, user, info) {
395 | assert.equal(info.scheme, 'OAuth');
396 | },
397 | 'should set consumer' : function(err, user, info) {
398 | assert.equal(info.consumer.id, '1');
399 | assert.strictEqual(info.client, info.consumer);
400 | },
401 | },
402 | },
403 |
404 | 'strategy handling a valid request with credentials in header with all-caps scheme': {
405 | topic: function() {
406 | var strategy = new TokenStrategy(
407 | // consumer callback
408 | function(consumerKey, done) {
409 | if (consumerKey == '1234') {
410 | done(null, { id: '1' }, 'keep-this-secret');
411 | } else {
412 | done(new Error('something is wrong'))
413 | }
414 | },
415 | // verify callback
416 | function(accessToken, done) {
417 | if (accessToken == 'abc-123-xyz-789') {
418 | done(null, { username: 'bob' }, 'lips-zipped');
419 | } else {
420 | done(new Error('something is wrong'))
421 | }
422 | }
423 | );
424 | return strategy;
425 | },
426 |
427 | 'after augmenting with actions': {
428 | topic: function(strategy) {
429 | var self = this;
430 | var req = {};
431 | strategy.success = function(user, info) {
432 | self.callback(null, user, info);
433 | }
434 | strategy.fail = function(challenge, status) {
435 | self.callback(new Error('should not be called'));
436 | }
437 | strategy.error = function(err) {
438 | self.callback(new Error('should not be called'));
439 | }
440 |
441 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
442 | req.method = 'GET';
443 | req.headers = {};
444 | req.headers['host'] = '127.0.0.1:3000';
445 | req.headers['authorization'] = 'OAUTH oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
446 | req.query = url.parse(req.url, true).query;
447 | req.connection = { encrypted: false };
448 | process.nextTick(function () {
449 | strategy.authenticate(req);
450 | });
451 | },
452 |
453 | 'should not generate an error' : function(err, user, info) {
454 | assert.isNull(err);
455 | },
456 | 'should authenticate' : function(err, user, info) {
457 | assert.equal(user.username, 'bob');
458 | },
459 | 'should set scheme to OAuth' : function(err, user, info) {
460 | assert.equal(info.scheme, 'OAuth');
461 | },
462 | 'should set consumer' : function(err, user, info) {
463 | assert.equal(info.consumer.id, '1');
464 | assert.strictEqual(info.client, info.consumer);
465 | },
466 | },
467 | },
468 |
469 | // TODO: Implement test case for request with params in body
470 |
471 | // TODO: Implement test case for request with params in query
472 |
473 | 'strategy handling a valid request where token callback supplies info': {
474 | topic: function() {
475 | var strategy = new TokenStrategy(
476 | // consumer callback
477 | function(consumerKey, done) {
478 | done(null, { id: '1' }, 'keep-this-secret');
479 | },
480 | // verify callback
481 | function(accessToken, done) {
482 | done(null, { username: 'bob' }, 'lips-zipped', { scope: 'write' });
483 | }
484 | );
485 | return strategy;
486 | },
487 |
488 | 'after augmenting with actions': {
489 | topic: function(strategy) {
490 | var self = this;
491 | var req = {};
492 | strategy.success = function(user, info) {
493 | self.callback(null, user, info);
494 | }
495 | strategy.fail = function(challenge, status) {
496 | self.callback(new Error('should not be called'));
497 | }
498 | strategy.error = function(err) {
499 | self.callback(new Error('should not be called'));
500 | }
501 |
502 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
503 | req.method = 'GET';
504 | req.headers = {};
505 | req.headers['host'] = '127.0.0.1:3000';
506 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
507 | req.query = url.parse(req.url, true).query;
508 | req.connection = { encrypted: false };
509 | process.nextTick(function () {
510 | strategy.authenticate(req);
511 | });
512 | },
513 |
514 | 'should not generate an error' : function(err, user, info) {
515 | assert.isNull(err);
516 | },
517 | 'should authenticate' : function(err, user, info) {
518 | assert.equal(user.username, 'bob');
519 | },
520 | 'should set scheme to OAuth' : function(err, user, info) {
521 | assert.equal(info.scheme, 'OAuth');
522 | },
523 | 'should set consumer' : function(err, user, info) {
524 | assert.equal(info.consumer.id, '1');
525 | assert.strictEqual(info.client, info.consumer);
526 | },
527 | 'should preserve scope' : function(err, user, info) {
528 | assert.equal(info.scope, 'write');
529 | },
530 | },
531 | },
532 |
533 | 'strategy handling a valid request where timestamp and nonce are validated': {
534 | topic: function() {
535 | var strategy = new TokenStrategy(
536 | // consumer callback
537 | function(consumerKey, done) {
538 | done(null, { id: '1' }, 'keep-this-secret');
539 | },
540 | // verify callback
541 | function(accessToken, done) {
542 | done(null, { username: 'bob' }, 'lips-zipped');
543 | },
544 | // validate callback
545 | function(timestamp, nonce, done) {
546 | if (timestamp == 1339004912 && nonce == 'A7E738D9A9684A60A40607017735ADAD') {
547 | done(null, true);
548 | } else {
549 | done(new Error('something is wrong'))
550 | }
551 | }
552 | );
553 | return strategy;
554 | },
555 |
556 | 'after augmenting with actions': {
557 | topic: function(strategy) {
558 | var self = this;
559 | var req = {};
560 | strategy.success = function(user, info) {
561 | self.callback(null, user, info);
562 | }
563 | strategy.fail = function(challenge, status) {
564 | self.callback(new Error('should not be called'));
565 | }
566 | strategy.error = function(err) {
567 | self.callback(new Error('should not be called'));
568 | }
569 |
570 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
571 | req.method = 'GET';
572 | req.headers = {};
573 | req.headers['host'] = '127.0.0.1:3000';
574 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
575 | req.query = url.parse(req.url, true).query;
576 | req.connection = { encrypted: false };
577 | process.nextTick(function () {
578 | strategy.authenticate(req);
579 | });
580 | },
581 |
582 | 'should not generate an error' : function(err, user, info) {
583 | assert.isNull(err);
584 | },
585 | 'should authenticate' : function(err, user, info) {
586 | assert.equal(user.username, 'bob');
587 | },
588 | 'should set info scheme to OAuth' : function(err, user, info) {
589 | assert.equal(info.scheme, 'OAuth');
590 | },
591 | 'should set consumer on info' : function(err, user, info) {
592 | assert.equal(info.consumer.id, '1');
593 | },
594 | },
595 | },
596 |
597 | 'strategy handling a valid request where consumer is not authenticated': {
598 | topic: function() {
599 | var strategy = new TokenStrategy(
600 | // consumer callback
601 | function(consumerKey, done) {
602 | done(null, false);
603 | },
604 | // verify callback
605 | function(accessToken, done) {
606 | done(null, { username: 'bob' }, 'lips-zipped');
607 | }
608 | );
609 | return strategy;
610 | },
611 |
612 | 'after augmenting with actions': {
613 | topic: function(strategy) {
614 | var self = this;
615 | var req = {};
616 | strategy.success = function(user, info) {
617 | self.callback(new Error('should not be called'));
618 | }
619 | strategy.fail = function(challenge, status) {
620 | self.callback(null, challenge, status);
621 | }
622 | strategy.error = function(err) {
623 | self.callback(new Error('should not be called'));
624 | }
625 |
626 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
627 | req.method = 'GET';
628 | req.headers = {};
629 | req.headers['host'] = '127.0.0.1:3000';
630 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
631 | req.query = url.parse(req.url, true).query;
632 | req.connection = { encrypted: false };
633 | process.nextTick(function () {
634 | strategy.authenticate(req);
635 | });
636 | },
637 |
638 | 'should not generate an error' : function(err, challenge, status) {
639 | assert.isNull(err);
640 | },
641 | 'should respond with challenge' : function(err, challenge, status) {
642 | assert.equal(challenge, 'OAuth realm="Users", oauth_problem="consumer_key_rejected"');
643 | },
644 | 'should respond with default status' : function(err, challenge, status) {
645 | assert.isUndefined(status);
646 | },
647 | },
648 | },
649 |
650 | 'strategy handling a valid request where user is not authenticated': {
651 | topic: function() {
652 | var strategy = new TokenStrategy(
653 | // consumer callback
654 | function(consumerKey, done) {
655 | done(null, { id: '1' }, 'keep-this-secret');
656 | },
657 | // verify callback
658 | function(accessToken, done) {
659 | done(null, false);
660 | }
661 | );
662 | return strategy;
663 | },
664 |
665 | 'after augmenting with actions': {
666 | topic: function(strategy) {
667 | var self = this;
668 | var req = {};
669 | strategy.success = function(user, info) {
670 | self.callback(new Error('should not be called'));
671 | }
672 | strategy.fail = function(challenge, status) {
673 | self.callback(null, challenge, status);
674 | }
675 | strategy.error = function(err) {
676 | self.callback(new Error('should not be called'));
677 | }
678 |
679 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
680 | req.method = 'GET';
681 | req.headers = {};
682 | req.headers['host'] = '127.0.0.1:3000';
683 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
684 | req.query = url.parse(req.url, true).query;
685 | req.connection = { encrypted: false };
686 | process.nextTick(function () {
687 | strategy.authenticate(req);
688 | });
689 | },
690 |
691 | 'should not generate an error' : function(err, challenge, status) {
692 | assert.isNull(err);
693 | },
694 | 'should respond with challenge' : function(err, challenge, status) {
695 | assert.equal(challenge, 'OAuth realm="Users", oauth_problem="token_rejected"');
696 | },
697 | 'should respond with default status' : function(err, challenge, status) {
698 | assert.isUndefined(status);
699 | },
700 | },
701 | },
702 |
703 | 'strategy handling a valid request where timestamp and nonce are not validated': {
704 | topic: function() {
705 | var strategy = new TokenStrategy(
706 | // consumer callback
707 | function(consumerKey, done) {
708 | done(null, { id: '1' }, 'keep-this-secret');
709 | },
710 | // verify callback
711 | function(accessToken, done) {
712 | done(null, { username: 'bob' }, 'lips-zipped');
713 | },
714 | // validate callback
715 | function(timestamp, nonce, done) {
716 | done(null, false);
717 | }
718 | );
719 | return strategy;
720 | },
721 |
722 | 'after augmenting with actions': {
723 | topic: function(strategy) {
724 | var self = this;
725 | var req = {};
726 | strategy.success = function(user, info) {
727 | self.callback(new Error('should not be called'));
728 | }
729 | strategy.fail = function(challenge, status) {
730 | self.callback(null, challenge, status);
731 | }
732 | strategy.error = function(err) {
733 | self.callback(new Error('should not be called'));
734 | }
735 |
736 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
737 | req.method = 'GET';
738 | req.headers = {};
739 | req.headers['host'] = '127.0.0.1:3000';
740 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
741 | req.query = url.parse(req.url, true).query;
742 | req.connection = { encrypted: false };
743 | process.nextTick(function () {
744 | strategy.authenticate(req);
745 | });
746 | },
747 |
748 | 'should not generate an error' : function(err, challenge, status) {
749 | assert.isNull(err);
750 | },
751 | 'should respond with challenge' : function(err, challenge, status) {
752 | assert.equal(challenge, 'OAuth realm="Users", oauth_problem="nonce_used"');
753 | },
754 | 'should respond with default status' : function(err, challenge, status) {
755 | assert.isUndefined(status);
756 | },
757 | },
758 | },
759 |
760 | 'strategy handling a request with invalid HMAC-SHA1 signature': {
761 | topic: function() {
762 | var strategy = new TokenStrategy(
763 | // consumer callback
764 | function(consumerKey, done) {
765 | done(null, { id: '1' }, 'keep-this-secret');
766 | },
767 | // verify callback
768 | function(accessToken, done) {
769 | done(null, { username: 'bob' }, 'lips-not-zipped');
770 | }
771 | );
772 | return strategy;
773 | },
774 |
775 | 'after augmenting with actions': {
776 | topic: function(strategy) {
777 | var self = this;
778 | var req = {};
779 | strategy.success = function(user, info) {
780 | self.callback(new Error('should not be called'));
781 | }
782 | strategy.fail = function(challenge, status) {
783 | self.callback(null, challenge, status);
784 | }
785 | strategy.error = function(err) {
786 | self.callback(new Error('should not be called'));
787 | }
788 |
789 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
790 | req.method = 'GET';
791 | req.headers = {};
792 | req.headers['host'] = '127.0.0.1:3000';
793 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
794 | req.query = url.parse(req.url, true).query;
795 | req.connection = { encrypted: false };
796 | process.nextTick(function () {
797 | strategy.authenticate(req);
798 | });
799 | },
800 |
801 | 'should not generate an error' : function(err, challenge, status) {
802 | assert.isNull(err);
803 | },
804 | 'should respond with challenge' : function(err, challenge, status) {
805 | assert.equal(challenge, 'OAuth realm="Users", oauth_problem="signature_invalid"');
806 | },
807 | 'should respond with default status' : function(err, challenge, status) {
808 | assert.isUndefined(status);
809 | },
810 | },
811 | },
812 |
813 | 'strategy handling a request with invalid PLAINTEXT signature': {
814 | topic: function() {
815 | var strategy = new TokenStrategy(
816 | // consumer callback
817 | function(consumerKey, done) {
818 | done(null, { id: '1' }, 'ssh-secret');
819 | },
820 | // verify callback
821 | function(accessToken, done) {
822 | done(null, { username: 'bob' }, 'not-mmyauoBm7rRv0kLsNKAicmtsxsxKWJDmoEo7obTqglkyGNHs8hn78pkTj70tXatl');
823 | }
824 | );
825 | return strategy;
826 | },
827 |
828 | 'after augmenting with actions': {
829 | topic: function(strategy) {
830 | var self = this;
831 | var req = {};
832 | strategy.success = function(user, info) {
833 | self.callback(new Error('should not be called'));
834 | }
835 | strategy.fail = function(challenge, status) {
836 | self.callback(null, challenge, status);
837 | }
838 | strategy.error = function(err) {
839 | self.callback(new Error('should not be called'));
840 | }
841 |
842 | req.url = '/api/userinfo';
843 | req.method = 'GET';
844 | req.headers = {};
845 | req.headers['host'] = '127.0.0.1:3000';
846 | req.headers['authorization'] = 'OAuth oauth_consumer_key="abc123",oauth_nonce="bSzaRm1X9uu6DwjAuAsOnn6cnxYoVibS",oauth_signature_method="PLAINTEXT",oauth_timestamp="1341195485",oauth_token="Xe4F8Cf5vw68BoZF",oauth_version="1.0",oauth_signature="ssh-secret%2526mmyauoBm7rRv0kLsNKAicmtsxsxKWJDmoEo7obTqglkyGNHs8hn78pkTj70tXatl"';
847 | req.query = url.parse(req.url, true).query;
848 | req.connection = { encrypted: false };
849 | process.nextTick(function () {
850 | strategy.authenticate(req);
851 | });
852 | },
853 |
854 | 'should not generate an error' : function(err, challenge, status) {
855 | assert.isNull(err);
856 | },
857 | 'should respond with challenge' : function(err, challenge, status) {
858 | assert.equal(challenge, 'OAuth realm="Users", oauth_problem="signature_invalid"');
859 | },
860 | 'should respond with default status' : function(err, challenge, status) {
861 | assert.isUndefined(status);
862 | },
863 | },
864 | },
865 |
866 | 'strategy handling a request with invalid HMAC-SHA256 signature': {
867 | topic: function() {
868 | var strategy = new TokenStrategy(
869 | // consumer callback
870 | function(consumerKey, done) {
871 | done(null, { id: '1' }, 'keep-this-secret');
872 | },
873 | // verify callback
874 | function(accessToken, done) {
875 | done(null, { username: 'bob' }, 'lips-not-zipped');
876 | }
877 | );
878 | return strategy;
879 | },
880 |
881 | 'after augmenting with actions': {
882 | topic: function(strategy) {
883 | var self = this;
884 | var req = {};
885 | strategy.success = function(user, info) {
886 | self.callback(new Error('should not be called'));
887 | }
888 | strategy.fail = function(challenge, status) {
889 | self.callback(null, challenge, status);
890 | }
891 | strategy.error = function(err) {
892 | self.callback(new Error('should not be called'));
893 | }
894 |
895 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
896 | req.method = 'GET';
897 | req.headers = {};
898 | req.headers['host'] = '127.0.0.1:3000';
899 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA256",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
900 | req.query = url.parse(req.url, true).query;
901 | req.connection = { encrypted: false };
902 | process.nextTick(function () {
903 | strategy.authenticate(req);
904 | });
905 | },
906 |
907 | 'should not generate an error' : function(err, challenge, status) {
908 | assert.isNull(err);
909 | },
910 | 'should respond with challenge' : function(err, challenge, status) {
911 | assert.equal(challenge, 'OAuth realm="Users", oauth_problem="signature_invalid"');
912 | },
913 | 'should respond with default status' : function(err, challenge, status) {
914 | assert.isUndefined(status);
915 | },
916 | },
917 | },
918 |
919 | 'strategy handling a request with unknown signature method': {
920 | topic: function() {
921 | var strategy = new TokenStrategy(
922 | // consumer callback
923 | function(consumerKey, done) {
924 | done(null, { id: '1' }, 'keep-this-secret');
925 | },
926 | // verify callback
927 | function(accessToken, done) {
928 | done(null, { username: 'bob' }, 'lips-zipped');
929 | }
930 | );
931 | return strategy;
932 | },
933 |
934 | 'after augmenting with actions': {
935 | topic: function(strategy) {
936 | var self = this;
937 | var req = {};
938 | strategy.success = function(user, info) {
939 | self.callback(new Error('should not be called'));
940 | }
941 | strategy.fail = function(challenge, status) {
942 | self.callback(null, challenge, status);
943 | }
944 | strategy.error = function(err) {
945 | self.callback(new Error('should not be called'));
946 | }
947 |
948 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
949 | req.method = 'GET';
950 | req.headers = {};
951 | req.headers['host'] = '127.0.0.1:3000';
952 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="UNKNOWN",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
953 | req.query = url.parse(req.url, true).query;
954 | req.connection = { encrypted: false };
955 | process.nextTick(function () {
956 | strategy.authenticate(req);
957 | });
958 | },
959 |
960 | 'should not generate an error' : function(err, challenge, status) {
961 | assert.isNull(err);
962 | },
963 | 'should respond with challenge' : function(err, challenge, status) {
964 | assert.equal(challenge, 'OAuth realm="Users", oauth_problem="signature_method_rejected"');
965 | },
966 | 'should respond with 400 status' : function(err, challenge, status) {
967 | assert.equal(status, 400);
968 | },
969 | },
970 | },
971 |
972 | 'strategy handling a valid request where consumer callback fails with an error': {
973 | topic: function() {
974 | var strategy = new TokenStrategy(
975 | // consumer callback
976 | function(consumerKey, done) {
977 | done(new Error('consumer callback failure'));
978 | },
979 | // verify callback
980 | function(accessToken, done) {
981 | done(null, { username: 'bob' }, 'lips-zipped');
982 | }
983 | );
984 | return strategy;
985 | },
986 |
987 | 'after augmenting with actions': {
988 | topic: function(strategy) {
989 | var self = this;
990 | var req = {};
991 | strategy.success = function(user, info) {
992 | self.callback(new Error('should not be called'));
993 | }
994 | strategy.fail = function(challenge, status) {
995 | self.callback(new Error('should not be called'));
996 | }
997 | strategy.error = function(err) {
998 | self.callback(null, err);
999 | }
1000 |
1001 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
1002 | req.method = 'GET';
1003 | req.headers = {};
1004 | req.headers['host'] = '127.0.0.1:3000';
1005 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
1006 | req.query = url.parse(req.url, true).query;
1007 | req.connection = { encrypted: false };
1008 | process.nextTick(function () {
1009 | strategy.authenticate(req);
1010 | });
1011 | },
1012 |
1013 | 'should not call success or fail' : function(err, e) {
1014 | assert.isNull(err);
1015 | },
1016 | 'should call error' : function(err, e) {
1017 | assert.instanceOf(e, Error);
1018 | assert.equal(e.message, 'consumer callback failure');
1019 | },
1020 | },
1021 | },
1022 |
1023 | 'strategy handling a valid request where verify callback fails with an error': {
1024 | topic: function() {
1025 | var strategy = new TokenStrategy(
1026 | // consumer callback
1027 | function(consumerKey, done) {
1028 | done(null, { id: '1' }, 'keep-this-secret');
1029 | },
1030 | // verify callback
1031 | function(accessToken, done) {
1032 | done(new Error('verify callback failure'));
1033 | }
1034 | );
1035 | return strategy;
1036 | },
1037 |
1038 | 'after augmenting with actions': {
1039 | topic: function(strategy) {
1040 | var self = this;
1041 | var req = {};
1042 | strategy.success = function(user, info) {
1043 | self.callback(new Error('should not be called'));
1044 | }
1045 | strategy.fail = function(challenge, status) {
1046 | self.callback(new Error('should not be called'));
1047 | }
1048 | strategy.error = function(err) {
1049 | self.callback(null, err);
1050 | }
1051 |
1052 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
1053 | req.method = 'GET';
1054 | req.headers = {};
1055 | req.headers['host'] = '127.0.0.1:3000';
1056 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
1057 | req.query = url.parse(req.url, true).query;
1058 | req.connection = { encrypted: false };
1059 | process.nextTick(function () {
1060 | strategy.authenticate(req);
1061 | });
1062 | },
1063 |
1064 | 'should not call success or fail' : function(err, e) {
1065 | assert.isNull(err);
1066 | },
1067 | 'should call error' : function(err, e) {
1068 | assert.instanceOf(e, Error);
1069 | assert.equal(e.message, 'verify callback failure');
1070 | },
1071 | },
1072 | },
1073 |
1074 | 'strategy handling a valid request where validate callback fails with an error': {
1075 | topic: function() {
1076 | var strategy = new TokenStrategy(
1077 | // consumer callback
1078 | function(consumerKey, done) {
1079 | done(null, { id: '1' }, 'keep-this-secret');
1080 | },
1081 | // verify callback
1082 | function(accessToken, done) {
1083 | done(null, { username: 'bob' }, 'lips-zipped');
1084 | },
1085 | // validate callback
1086 | function(timestamp, nonce, done) {
1087 | done(new Error('validate callback failure'));
1088 | }
1089 | );
1090 | return strategy;
1091 | },
1092 |
1093 | 'after augmenting with actions': {
1094 | topic: function(strategy) {
1095 | var self = this;
1096 | var req = {};
1097 | strategy.success = function(user, info) {
1098 | self.callback(new Error('should not be called'));
1099 | }
1100 | strategy.fail = function(challenge, status) {
1101 | self.callback(new Error('should not be called'));
1102 | }
1103 | strategy.error = function(err) {
1104 | self.callback(null, err);
1105 | }
1106 |
1107 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
1108 | req.method = 'GET';
1109 | req.headers = {};
1110 | req.headers['host'] = '127.0.0.1:3000';
1111 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
1112 | req.query = url.parse(req.url, true).query;
1113 | req.connection = { encrypted: false };
1114 | process.nextTick(function () {
1115 | strategy.authenticate(req);
1116 | });
1117 | },
1118 |
1119 | 'should not call success or fail' : function(err, e) {
1120 | assert.isNull(err);
1121 | },
1122 | 'should call error' : function(err, e) {
1123 | assert.instanceOf(e, Error);
1124 | assert.equal(e.message, 'validate callback failure');
1125 | },
1126 | },
1127 | },
1128 |
1129 | 'strategy handling a request without authentication credentials': {
1130 | topic: function() {
1131 | var strategy = new TokenStrategy(
1132 | // consumer callback
1133 | function(consumerKey, done) {
1134 | done(null, { id: '1' }, 'keep-this-secret');
1135 | },
1136 | // verify callback
1137 | function(accessToken, done) {
1138 | done(null, { username: 'bob' }, 'lips-zipped');
1139 | }
1140 | );
1141 | return strategy;
1142 | },
1143 |
1144 | 'after augmenting with actions': {
1145 | topic: function(strategy) {
1146 | var self = this;
1147 | var req = {};
1148 | strategy.success = function(user, info) {
1149 | self.callback(new Error('should not be called'));
1150 | }
1151 | strategy.fail = function(challenge, status) {
1152 | self.callback(null, challenge, status);
1153 | }
1154 | strategy.error = function(err) {
1155 | self.callback(new Error('should not be called'));
1156 | }
1157 |
1158 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
1159 | req.method = 'GET';
1160 | req.headers = {};
1161 | req.headers['host'] = '127.0.0.1:3000';
1162 | req.query = url.parse(req.url, true).query;
1163 | req.connection = { encrypted: false };
1164 | process.nextTick(function () {
1165 | strategy.authenticate(req);
1166 | });
1167 | },
1168 |
1169 | 'should not generate an error' : function(err, challenge, status) {
1170 | assert.isNull(err);
1171 | },
1172 | 'should respond with challenge' : function(err, challenge, status) {
1173 | assert.equal(challenge, 'OAuth realm="Users"');
1174 | },
1175 | 'should respond with default status' : function(err, challenge, status) {
1176 | assert.isUndefined(status);
1177 | },
1178 | },
1179 | },
1180 |
1181 | 'strategy handling a request without authentication credentials and realm option': {
1182 | topic: function() {
1183 | var strategy = new TokenStrategy({ realm: 'Foo' },
1184 | // consumer callback
1185 | function(consumerKey, done) {
1186 | done(null, { id: '1' }, 'keep-this-secret');
1187 | },
1188 | // verify callback
1189 | function(accessToken, done) {
1190 | done(null, { username: 'bob' }, 'lips-zipped');
1191 | }
1192 | );
1193 | return strategy;
1194 | },
1195 |
1196 | 'after augmenting with actions': {
1197 | topic: function(strategy) {
1198 | var self = this;
1199 | var req = {};
1200 | strategy.success = function(user, info) {
1201 | self.callback(new Error('should not be called'));
1202 | }
1203 | strategy.fail = function(challenge, status) {
1204 | self.callback(null, challenge, status);
1205 | }
1206 | strategy.error = function(err) {
1207 | self.callback(new Error('should not be called'));
1208 | }
1209 |
1210 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
1211 | req.method = 'GET';
1212 | req.headers = {};
1213 | req.headers['host'] = '127.0.0.1:3000';
1214 | req.query = url.parse(req.url, true).query;
1215 | req.connection = { encrypted: false };
1216 | process.nextTick(function () {
1217 | strategy.authenticate(req);
1218 | });
1219 | },
1220 |
1221 | 'should not generate an error' : function(err, challenge, status) {
1222 | assert.isNull(err);
1223 | },
1224 | 'should respond with challenge' : function(err, challenge, status) {
1225 | assert.equal(challenge, 'OAuth realm="Foo"');
1226 | },
1227 | 'should respond with default status' : function(err, challenge, status) {
1228 | assert.isUndefined(status);
1229 | },
1230 | },
1231 | },
1232 |
1233 | 'strategy handling a request with non-OAuth scheme': {
1234 | topic: function() {
1235 | var strategy = new TokenStrategy(
1236 | // consumer callback
1237 | function(consumerKey, done) {
1238 | done(null, { id: '1' }, 'keep-this-secret');
1239 | },
1240 | // verify callback
1241 | function(accessToken, done) {
1242 | done(null, { username: 'bob' }, 'lips-zipped');
1243 | }
1244 | );
1245 | return strategy;
1246 | },
1247 |
1248 | 'after augmenting with actions': {
1249 | topic: function(strategy) {
1250 | var self = this;
1251 | var req = {};
1252 | strategy.success = function(user, info) {
1253 | self.callback(new Error('should not be called'));
1254 | }
1255 | strategy.fail = function(challenge, status) {
1256 | self.callback(null, challenge, status);
1257 | }
1258 | strategy.error = function(err) {
1259 | self.callback(new Error('should not be called'));
1260 | }
1261 |
1262 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
1263 | req.method = 'GET';
1264 | req.headers = {};
1265 | req.headers['host'] = '127.0.0.1:3000';
1266 | req.headers['authorization'] = 'FooBar vF9dft4qmT';
1267 | req.query = url.parse(req.url, true).query;
1268 | req.connection = { encrypted: false };
1269 | process.nextTick(function () {
1270 | strategy.authenticate(req);
1271 | });
1272 | },
1273 |
1274 | 'should not generate an error' : function(err, challenge, status) {
1275 | assert.isNull(err);
1276 | },
1277 | 'should respond with challenge' : function(err, challenge, status) {
1278 | assert.equal(challenge, 'OAuth realm="Users"');
1279 | },
1280 | 'should respond with default status' : function(err, challenge, status) {
1281 | assert.isUndefined(status);
1282 | },
1283 | },
1284 | },
1285 |
1286 | 'strategy handling a request with malformed-OAuth scheme': {
1287 | topic: function() {
1288 | var strategy = new TokenStrategy(
1289 | // consumer callback
1290 | function(consumerKey, done) {
1291 | done(null, { id: '1' }, 'keep-this-secret');
1292 | },
1293 | // verify callback
1294 | function(accessToken, done) {
1295 | done(null, { username: 'bob' }, 'lips-zipped');
1296 | }
1297 | );
1298 | return strategy;
1299 | },
1300 |
1301 | 'after augmenting with actions': {
1302 | topic: function(strategy) {
1303 | var self = this;
1304 | var req = {};
1305 | strategy.success = function(user, info) {
1306 | self.callback(new Error('should not be called'));
1307 | }
1308 | strategy.fail = function(challenge, status) {
1309 | self.callback(null, challenge, status);
1310 | }
1311 | strategy.error = function(err) {
1312 | self.callback(new Error('should not be called'));
1313 | }
1314 |
1315 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
1316 | req.method = 'GET';
1317 | req.headers = {};
1318 | req.headers['host'] = '127.0.0.1:3000';
1319 | req.headers['authorization'] = 'OAuth';
1320 | req.query = url.parse(req.url, true).query;
1321 | req.connection = { encrypted: false };
1322 | process.nextTick(function () {
1323 | strategy.authenticate(req);
1324 | });
1325 | },
1326 |
1327 | 'should not generate an error' : function(err, challenge, status) {
1328 | assert.isNull(err);
1329 | },
1330 | 'should respond without bad request status' : function(err, challenge, status) {
1331 | assert.strictEqual(challenge, 400);
1332 | },
1333 | },
1334 | },
1335 |
1336 | 'strategy handling a request with OAuth scheme with missing parameters': {
1337 | topic: function() {
1338 | var strategy = new TokenStrategy(
1339 | // consumer callback
1340 | function(consumerKey, done) {
1341 | done(null, { id: '1' }, 'keep-this-secret');
1342 | },
1343 | // verify callback
1344 | function(accessToken, done) {
1345 | done(null, { username: 'bob' }, 'lips-zipped');
1346 | }
1347 | );
1348 | return strategy;
1349 | },
1350 |
1351 | 'after augmenting with actions': {
1352 | topic: function(strategy) {
1353 | var self = this;
1354 | var req = {};
1355 | strategy.success = function(user, info) {
1356 | self.callback(new Error('should not be called'));
1357 | }
1358 | strategy.fail = function(challenge, status) {
1359 | self.callback(null, challenge, status);
1360 | }
1361 | strategy.error = function(err) {
1362 | self.callback(new Error('should not be called'));
1363 | }
1364 |
1365 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
1366 | req.method = 'GET';
1367 | req.headers = {};
1368 | req.headers['host'] = '127.0.0.1:3000';
1369 | req.headers['authorization'] = 'OAuth oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.0",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
1370 | req.query = url.parse(req.url, true).query;
1371 | req.connection = { encrypted: false };
1372 | process.nextTick(function () {
1373 | strategy.authenticate(req);
1374 | });
1375 | },
1376 |
1377 | 'should not generate an error' : function(err, challenge, status) {
1378 | assert.isNull(err);
1379 | },
1380 | 'should respond with challenge' : function(err, challenge, status) {
1381 | assert.equal(challenge, 'OAuth realm="Users", oauth_problem="parameter_absent"');
1382 | },
1383 | 'should respond with 400 status' : function(err, challenge, status) {
1384 | assert.equal(status, 400);
1385 | },
1386 | },
1387 | },
1388 |
1389 | 'strategy handling a request with OAuth scheme with bad version': {
1390 | topic: function() {
1391 | var strategy = new TokenStrategy(
1392 | // consumer callback
1393 | function(consumerKey, done) {
1394 | done(null, { id: '1' }, 'keep-this-secret');
1395 | },
1396 | // verify callback
1397 | function(accessToken, done) {
1398 | done(null, { username: 'bob' }, 'lips-zipped');
1399 | }
1400 | );
1401 | return strategy;
1402 | },
1403 |
1404 | 'after augmenting with actions': {
1405 | topic: function(strategy) {
1406 | var self = this;
1407 | var req = {};
1408 | strategy.success = function(user, info) {
1409 | self.callback(new Error('should not be called'));
1410 | }
1411 | strategy.fail = function(challenge, status) {
1412 | self.callback(null, challenge, status);
1413 | }
1414 | strategy.error = function(err) {
1415 | self.callback(new Error('should not be called'));
1416 | }
1417 |
1418 | req.url = '/1/users/show.json?screen_name=jaredhanson&user_id=1705';
1419 | req.method = 'GET';
1420 | req.headers = {};
1421 | req.headers['host'] = '127.0.0.1:3000';
1422 | req.headers['authorization'] = 'OAuth oauth_consumer_key="1234",oauth_nonce="A7E738D9A9684A60A40607017735ADAD",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1339004912",oauth_token="abc-123-xyz-789",oauth_version="1.1",oauth_signature="TBrJJJWS896yWrbklSbhEd9MGQc%3D"';
1423 | req.query = url.parse(req.url, true).query;
1424 | req.connection = { encrypted: false };
1425 | process.nextTick(function () {
1426 | strategy.authenticate(req);
1427 | });
1428 | },
1429 |
1430 | 'should not generate an error' : function(err, challenge, status) {
1431 | assert.isNull(err);
1432 | },
1433 | 'should respond with challenge' : function(err, challenge, status) {
1434 | assert.equal(challenge, 'OAuth realm="Users", oauth_problem="version_rejected"');
1435 | },
1436 | 'should respond with 400 status' : function(err, challenge, status) {
1437 | assert.equal(status, 400);
1438 | },
1439 | },
1440 | },
1441 |
1442 | // TODO: Add test case for bad request with OAuth params in multiple locations
1443 |
1444 | 'strategy constructed without a consumer callback or verify callback': {
1445 | 'should throw an error': function (strategy) {
1446 | assert.throws(function() { new TokenStrategy() });
1447 | },
1448 | },
1449 |
1450 | 'strategy constructed without a verify callback': {
1451 | 'should throw an error': function (strategy) {
1452 | assert.throws(function() { new TokenStrategy(function() {}) });
1453 | },
1454 | },
1455 |
1456 | }).export(module);
1457 |
--------------------------------------------------------------------------------
/test/strategies/utils-test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows');
2 | var assert = require('assert');
3 | var util = require('util');
4 | var utils = require('passport-http-oauth/strategies/utils');
5 |
6 |
7 | vows.describe('utils').addBatch({
8 |
9 | 'parseHeader': {
10 | 'should filter out realm' : function(hash) {
11 | var params = utils.parseHeader('realm="Example",oauth_consumer_key="9djdj82h48djs9d2",oauth_token="kkk9d7dh3k39sjv7",oauth_signature_method="HMAC-SHA1",oauth_timestamp="137131201",oauth_nonce="7d8f3e4a",oauth_signature="bYT5CMsGcbgUdFHObYMEfcx6bsw%3D"')
12 | assert.lengthOf(Object.keys(params), 6);
13 | assert.equal(params['oauth_consumer_key'], '9djdj82h48djs9d2');
14 | assert.equal(params['oauth_token'], 'kkk9d7dh3k39sjv7');
15 | assert.equal(params['oauth_signature_method'], 'HMAC-SHA1');
16 | assert.equal(params['oauth_timestamp'], '137131201');
17 | assert.equal(params['oauth_nonce'], '7d8f3e4a');
18 | assert.equal(params['oauth_signature'], 'bYT5CMsGcbgUdFHObYMEfcx6bsw=');
19 | },
20 | 'should filter out non-protocol params (screen_name, user_id)' : function(hash) {
21 | var params = utils.parseHeader('oauth_consumer_key="1234",oauth_nonce="F4D6ADD34F9A45049E5D77F8BBDEEBD0",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1338958007",oauth_token="abc-123-xyz-789",oauth_version="1.0",screen_name="jaredhanson",user_id="1705",oauth_signature="sIoSDfr8zKm2H9fXvYqFclrwsQA%3D"')
22 | assert.lengthOf(Object.keys(params), 7);
23 | assert.equal(params['oauth_consumer_key'], '1234');
24 | assert.equal(params['oauth_nonce'], 'F4D6ADD34F9A45049E5D77F8BBDEEBD0');
25 | assert.equal(params['oauth_signature_method'], 'HMAC-SHA1');
26 | assert.equal(params['oauth_timestamp'], '1338958007');
27 | assert.equal(params['oauth_token'], 'abc-123-xyz-789');
28 | assert.equal(params['oauth_version'], '1.0');
29 | assert.equal(params['oauth_signature'], 'sIoSDfr8zKm2H9fXvYqFclrwsQA=');
30 | },
31 | },
32 |
33 | 'constructBaseString': {
34 | 'should construct base string' : function() {
35 | var method = 'GET';
36 | var uri = 'http://127.0.0.1:3000/1/users/show.json';
37 | var params = 'oauth_consumer_key=1234&oauth_nonce=C6E7591E09FA460BA847B25EBB2899B5&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1338961349&oauth_token=abc-123-xyz-789&oauth_version=1.0&screen_name=jaredhanson&user_id=1705';
38 | var base = utils.constructBaseString(method, uri, params);
39 |
40 | assert.equal(base, 'GET&http%3A%2F%2F127.0.0.1%3A3000%2F1%2Fusers%2Fshow.json&oauth_consumer_key%3D1234%26oauth_nonce%3DC6E7591E09FA460BA847B25EBB2899B5%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1338961349%26oauth_token%3Dabc-123-xyz-789%26oauth_version%3D1.0%26screen_name%3Djaredhanson%26user_id%3D1705');
41 | },
42 | },
43 |
44 | 'normalizeURI': {
45 | 'should be aliased to normalizeURL' : function() {
46 | assert.strictEqual(utils.normalizeURI, utils.normalizeURL);
47 | },
48 | 'should normalize URL' : function() {
49 | var n = utils.normalizeURI('http://127.0.0.1:3000/1/users/show.json?screen_name=jaredhanson&user_id=1705');
50 | assert.equal(n, 'http://127.0.0.1:3000/1/users/show.json');
51 | },
52 | },
53 |
54 | 'normalizeParams': {
55 | 'should normalize params from header and query' : function() {
56 | var header = { oauth_consumer_key: '1234',
57 | oauth_nonce: '49F30A4585984533ACC7E750876685B4',
58 | oauth_signature_method: 'HMAC-SHA1',
59 | oauth_timestamp: '1338964785',
60 | oauth_token: 'abc-123-xyz-789',
61 | oauth_version: '1.0',
62 | oauth_signature: '1kvC7Qsqd381RzXraWgNV5dkDPo=' };
63 | var query = { screen_name: 'jaredhanson', user_id: '1705' };
64 |
65 | var n = utils.normalizeParams(header, query);
66 | assert.equal(n, 'oauth_consumer_key=1234&oauth_nonce=49F30A4585984533ACC7E750876685B4&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1338964785&oauth_token=abc-123-xyz-789&oauth_version=1.0&screen_name=jaredhanson&user_id=1705');
67 | },
68 | 'should normalize params from null header and query' : function() {
69 | var header = undefined;
70 | var query = { screen_name: 'jaredhanson', user_id: '1705' };
71 |
72 | var n = utils.normalizeParams(header, query);
73 | assert.equal(n, 'screen_name=jaredhanson&user_id=1705');
74 | },
75 | 'should normalize example from RFC5849' : function() {
76 | var header = { oauth_consumer_key: '9djdj82h48djs9d2',
77 | oauth_token: 'kkk9d7dh3k39sjv7',
78 | oauth_signature_method: 'HMAC-SHA1',
79 | oauth_timestamp: '137131201',
80 | oauth_nonce: '7d8f3e4a',
81 | oauth_signature: 'djosJKDKJSD8743243%2Fjdk33klY=' };
82 |
83 | var query = { b5: '=%3D', a3: 'a', 'c@': '', a2: 'r b' };
84 | var body = { c2: '', a3: '2 q' };
85 |
86 | var n = utils.normalizeParams(header, body, query);
87 | assert.equal(n, 'a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9djdj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1&oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7');
88 | },
89 | 'should normalize example from RFC5849 with null params' : function() {
90 | var header = { oauth_consumer_key: '9djdj82h48djs9d2',
91 | oauth_token: 'kkk9d7dh3k39sjv7',
92 | oauth_signature_method: 'HMAC-SHA1',
93 | oauth_timestamp: '137131201',
94 | oauth_nonce: '7d8f3e4a',
95 | oauth_signature: 'djosJKDKJSD8743243%2Fjdk33klY=' };
96 |
97 | var query = { b5: '=%3D', a3: 'a', 'c@': null, a2: 'r b' };
98 | var body = { c2: '', a3: '2 q' };
99 |
100 | var n = utils.normalizeParams(header, body, query);
101 | assert.equal(n, 'a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9djdj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1&oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7');
102 | },
103 | 'should normalize example from RFC5849 with undefined params' : function() {
104 | var header = { oauth_consumer_key: '9djdj82h48djs9d2',
105 | oauth_token: 'kkk9d7dh3k39sjv7',
106 | oauth_signature_method: 'HMAC-SHA1',
107 | oauth_timestamp: '137131201',
108 | oauth_nonce: '7d8f3e4a',
109 | oauth_signature: 'djosJKDKJSD8743243%2Fjdk33klY=' };
110 |
111 | var query = { b5: '=%3D', a3: 'a', 'c@': undefined, a2: 'r b' };
112 | var body = { c2: '', a3: '2 q' };
113 |
114 | var n = utils.normalizeParams(header, body, query);
115 | assert.equal(n, 'a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9djdj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1&oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7');
116 | },
117 | },
118 |
119 | 'hmacsha1': {
120 | 'should encrypt key and text' : function() {
121 | var key = 'keep-this-secret&lips-zipped';
122 | var text = 'GET&http%3A%2F%2F127.0.0.1%3A3000%2F1%2Fusers%2Fshow.json&oauth_consumer_key%3D1234%26oauth_nonce%3D90662EEC0DB144DA8F08461DD5632284%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1339096128%26oauth_token%3Dabc-123-xyz-789%26oauth_version%3D1.0%26screen_name%3Djaredhanson%26user_id%3D1705';
123 | var enc = utils.hmacsha1(key, text);
124 |
125 | assert.equal(enc, 'aOzxBKR9w/DqjOYbn4H1czq/b4s=');
126 | },
127 | },
128 |
129 | 'hmacsha256': {
130 | 'should encrypt key and text' : function() {
131 | var key = 'keep-this-secret&lips-zipped';
132 | var text = 'GET&http%3A%2F%2F127.0.0.1%3A3000%2F1%2Fusers%2Fshow.json&oauth_consumer_key%3D1234%26oauth_nonce%3D90662EEC0DB144DA8F08461DD5632284%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1339096128%26oauth_token%3Dabc-123-xyz-789%26oauth_version%3D1.0%26screen_name%3Djaredhanson%26user_id%3D1705';
133 | var enc = utils.hmacsha256(key, text);
134 |
135 | assert.equal(enc, 'yUZX6eXOaaSCddDcL//G2/paQGlUflhcoDtJgRELpvg=');
136 | },
137 |
138 | },
139 |
140 | 'plaintext': {
141 | 'should encode secrets 1' : function() {
142 | var enc = utils.plaintext('djr9rjt0jd78jf88', 'jjd999tj88uiths3');
143 | assert.equal(enc, 'djr9rjt0jd78jf88%26jjd999tj88uiths3');
144 | },
145 | 'should encode secrets 2' : function() {
146 | var enc = utils.plaintext('djr9rjt0jd78jf88', 'jjd99$tj88uiths3');
147 | assert.equal(enc, 'djr9rjt0jd78jf88%26jjd99%2524tj88uiths3');
148 | },
149 | 'should encode secrets 3' : function() {
150 | var enc = utils.plaintext('djr9rjt0jd78jf88', '');
151 | assert.equal(enc, 'djr9rjt0jd78jf88%26');
152 | },
153 | },
154 |
155 | }).export(module);
156 |
--------------------------------------------------------------------------------