├── .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 | [![npm](https://img.shields.io/npm/v/passport-http-oauth.svg)](https://www.npmjs.com/package/passport-http-oauth) 28 | [![build](https://img.shields.io/travis/jaredhanson/passport-http-oauth.svg)](https://travis-ci.org/jaredhanson/passport-http-oauth) 29 | [![coverage](https://img.shields.io/coveralls/jaredhanson/passport-http-oauth.svg)](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 | [![Build Status](https://secure.travis-ci.org/jaredhanson/passport-http-oauth.png)](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 | --------------------------------------------------------------------------------