├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── debian ├── changelog ├── compat ├── control ├── copyright ├── dirs ├── install ├── rules └── source │ ├── format │ └── include-binaries ├── examples ├── .gitignore ├── app-sina.js ├── app.js ├── example_keys_file.js ├── in_memory_oauth_data_provider.js ├── mac │ ├── app.js │ └── files │ │ ├── client.html │ │ └── mac.js ├── oauthapp.js ├── oauthapp_2legged.js ├── oauthclient_2legged.js ├── oauthclientapp.js └── public │ ├── BluePigment.css │ ├── authenticated.html │ ├── boxbg.jpg │ ├── clock.gif │ ├── comment.gif │ ├── firefox-gray.jpg │ ├── footer-dots.jpg │ ├── headerbg.jpg │ ├── menubg.jpg │ ├── nav-current.jpg │ ├── page.gif │ ├── persona_sign_in_blue.png │ ├── rssfeed.gif │ ├── sidebarsep.jpg │ ├── sidebullet.gif │ ├── sign-in-with-twitter-d.png │ ├── skyrock-connect-black.png │ └── unauthenticated.html ├── index.js ├── lib ├── auth.strategies │ ├── _authutils.js │ ├── anonymous.js │ ├── bitbucket.js │ ├── facebook.js │ ├── foursquare.js │ ├── getglue.js │ ├── github.js │ ├── google.js │ ├── google2.js │ ├── http │ │ ├── base.js │ │ ├── base64.js │ │ ├── basic.js │ │ ├── digest.js │ │ ├── http.js │ │ └── mac.js │ ├── janrain.js │ ├── linkedin.js │ ├── never.js │ ├── oauth │ │ ├── _oauth_error.js │ │ ├── _oauthservices.js │ │ └── oauth.js │ ├── openid.js │ ├── persona.js │ ├── sina.js │ ├── skyrock.js │ ├── twitter.js │ ├── yahoo.js │ └── yammer.js ├── authExecutionScope.js ├── auth_middleware.js ├── events.js ├── index.js ├── requestMethods.js ├── strategyExecutor.js └── tracing.js ├── package.json ├── spec ├── lib │ ├── images │ │ ├── bg.png │ │ ├── hr.png │ │ ├── loading.gif │ │ ├── sprites.bg.png │ │ ├── sprites.png │ │ └── vr.png │ ├── jspec.css │ ├── jspec.growl.js │ ├── jspec.jquery.js │ ├── jspec.js │ ├── jspec.shell.js │ ├── jspec.timers.js │ └── jspec.xhr.js ├── node.js ├── spec.auth.core.js ├── spec.auth.strategies.anonymous.js ├── spec.auth.strategies.http.digest.js ├── spec.auth.strategies.never.js ├── spec.auth.strategy.base.js └── spec.strategies.js └── test └── coookieDecoder.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2010-2013 Ciaran Jessup 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NODE = node 2 | TEST = support/expresso/bin/expresso 3 | TESTS ?= test/*.test.js 4 | 5 | test: 6 | @CONNECT_ENV=test ./$(TEST) -I lib $(TEST_FLAGS) $(TESTS) 7 | 8 | test-cov: 9 | @$(MAKE) test TEST_FLAGS="--cov" 10 | 11 | app: 12 | @$(NODE) examples/app.js 13 | 14 | .PHONY: test test-cov app 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build status](https://secure.travis-ci.org/ciaranj/connect-auth.png)](http://travis-ci.org/ciaranj/connect-auth) 2 | connect-auth 3 | ============ 4 | 5 | Useful authentication strategies based on [warden]. Available as a [npm] package. 6 | 7 | Provides out of the box authentication strategies for: 8 | 9 | * HTTP Basic - sole or negotiated 10 | * HTTP Digest - sole or negotiated 11 | * Anonymous 12 | * Never 13 | * Facebook connect (OAuth 2) 14 | * Github (OAuth 2) 15 | * Yahoo (OAuth 1.0A) 16 | * Twitter (OAuth 1.0) 17 | * RPXNow / janrain SSO 18 | * BitBucket ( thanks to http://github.com/fjakobs ) 19 | * Foursquare (thanks to http://github.com/stunti) 20 | * Custom OAuth Provider Strategy (2-Legged & 3-Legged)(Be your own 1.0A OAuth provider!) (Christian Amor Kvalheim & Evan Prodromou) 21 | * Sina (Danny Siu) 22 | * Google (Oauth 1 & OAuth 2 clients) 23 | * Yammer (Stephen Belanger) 24 | * Linkedin (Stephen Belanger) 25 | * Skyrock.com (Nicolas Quiénot) 26 | * Mozilla Persona 27 | 28 | 29 | Running with npm 30 | ================= 31 | 32 | % npm install connect-auth 33 | % cp examples/example_keys_file.js examples/keys_file.js 34 | 35 | Edit keys_file.js 36 | 37 | Run 38 | 39 | % node examples/app.js 40 | % open http://localhost 41 | 42 | 43 | [warden]: http://github.com/hassox/warden 44 | [npm]: http://github.com/isaacs/npm 45 | 46 | 47 | Changelog 48 | ========= 49 | * **0.6.0** 50 | Google OAuth2 strategy now supports offline access (Oz Katz) 51 | New Mozilla Persona Strategy (verifier, not IdP) 52 | BREAKING: Moves dependencies to connect 2.7.x and node >=0.8.0. This should make modern installations easier to keep up to date. (Evan Prodromou ) 53 | * **0.5.4** 54 | Fixes Twitter strategy, they now require the oauth_verifier query parameter to be sent (Michael "Z" Goddard)_ 55 | * **0.5.3** 56 | Fix GitHub strategy to work with v3 of their API (older API versions no longer work!) (Dennis Reimann) 57 | Added new Skyrock.com provider (Nicolas Quiénot) 58 | * **0.5.2** 59 | Allow multiple users per application in Oauth Provider (3 legged) (Evan Prodromou) 60 | Improved the behaviour of the OAuth Provider's Form/POST signing behaviours ( Evan Prodromou ) 61 | Fix broken 3-legged OAuth provider support ( Jason Chu ) 62 | * **0.5.1** 63 | Change Google OAuth2 strategy to only request (and retrieve) the authenticating user's profile information (and optionally their e-mail address.) 64 | * **0.5.0** 65 | Update to support connect 2.0.0 66 | New 2-legged OAuth provider support ( Jason Chu ) 67 | Yammer Support added (Stephen Belanger) 68 | Linkedin Support added (Stephen Belanger) 69 | Support for configuring Facebook's OAuth dialog mode ( Barada Sahu ) 70 | Stopped some global scope pollution ( Fabian Jakobs ) 71 | * **0.4.1** 72 | Provide support to allow the authentication scope to 'survive' authentication redirects e.g. twitter, facebook etc. Allowing for scope usage with these strategies. 73 | * **0.4.0** 74 | Introduce new tracing capabilities (provide an option of trace:true/function when constructing the auth middleware) 75 | Introduce 2 new 'events/callbacks' : firstLoginHandler and logoutHandler to allow fairly standard authentication strategies. 76 | Restructured the code to help with others reading it :) 77 | Although I'm bumping the version number this release is still backwards compatible with 0.3.x, it just introduces significant new functionality. 78 | * **0.3.2** 79 | Fixed Google OAuth Strategy 80 | Provided *new* Google OAuth2 Strategy 81 | * **0.3.1** 82 | Fixing package.json (no real changes) 83 | * **0.3.0** 84 | Modified 'request.Authenticate(...)' to pass back 'undefined' when an active authentication strategy has required a communication with the browser to fully complete the authentication process. - *Possible Breaking change* 85 | Fixed various failure cases for nearly all strategies (utilising the new 'undefined' authentication type) 86 | Migrated Foursquare strategy to OAuth2 (requires at least v0.9.3 of node-oauth) 87 | New getglue strategy 88 | logout now takes an (optional) callback [this should be the default that is used.] 89 | * **0.2.3** 90 | Added support for BitBucket (Thanks http://github.com/fjakobs) 91 | Fixed bug introduced in 0.2.2 when dealing with strategies that 'fail' 92 | * **0.2.2** 93 | Added index.js ( Pau Ramon Revilla ) to root folder for easier inclusion 94 | Added support for SSO with http://t.sina.com.cn/ ( Danny Siu ) 95 | Added hd query parameter to allow Google Hosted Domain for google sSO strategy ( Olmo Maldonado ) 96 | Adds prelimenary support for the new HTTP MAC authentication scheme as defined by RFC-to-be: 97 | http://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token ( Eran Hammer-Lahav ) 98 | Added support for SSO with google (not using OpenId, but OAuth, so an intermediary approach) (Masahiro Hayashi) 99 | Twitter strategy now supports OAuth Callback Urls (Ben Marvell) 100 | Added option 'isAutoRespond' to handle authentication errors by the application with the HTTP Schemes. (Eran Hammer-Lahav) 101 | Support for 'scoped' users (aka multiple con-current authentications) (Logan Aube) 102 | * **0.2.1** 103 | Removed dead file that was seemingly breaking nDistro 104 | * **0.2.0** 105 | Updated HTTP strategies c/o Robbie Clutton no longer require passwords to be stored in the plain. - *Breaking change* 106 | Changed the default javascript file from auth.js to index.js. - *Breaking change* 107 | Fixed the isAuthenticated mechanism to work with mongodb (Lakin Wecker, Richard Walsh) 108 | Realm parameter now ignored in the Authorization header for the OAuth Provider strategy (Wade Simmons) 109 | * **0.1.3** 110 | Strategies can now be written that do not require the session middleware. 111 | * **0.1.2** 112 | Added in new strategy that allows your authentication strategy to be a custom OAuth provider. 113 | * **0.1.0** 114 | New simplified configuration (connect idiomatic) of strategies implemented. 115 | 116 | License 117 | ======= 118 | 119 | Released under the MIT license (see LICENSE file) 120 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | nodejs-connect-auth (0.2.1-0sg17) UNRELEASED; urgency=low 2 | 3 | * UNRELEASED 4 | 5 | -- SimpleGeo Nerds Mon, 02 May 2011 23:40:54 +0000 6 | 7 | nodejs-connect-auth (0.2.1-0sg16) UNRELEASED; urgency=low 8 | 9 | * UNRELEASED 10 | 11 | -- SimpleGeo Nerds Fri, 15 Apr 2011 21:02:02 +0000 12 | 13 | nodejs-connect-auth (0.2.1-0sg15) UNRELEASED; urgency=low 14 | 15 | * UNRELEASED 16 | 17 | -- SimpleGeo Nerds Tue, 05 Apr 2011 22:39:10 +0000 18 | 19 | nodejs-connect-auth (0.2.1-0sg14) UNRELEASED; urgency=low 20 | 21 | * UNRELEASED 22 | 23 | -- SimpleGeo Nerds Thu, 17 Feb 2011 21:49:14 +0000 24 | 25 | nodejs-connect-auth (0.2.1-0sg13) UNRELEASED; urgency=low 26 | 27 | * UNRELEASED 28 | 29 | -- SimpleGeo Nerds Tue, 01 Feb 2011 01:54:06 +0000 30 | 31 | nodejs-connect-auth (0.2.1-0sg12) UNRELEASED; urgency=low 32 | 33 | [ Wade Simmons ] 34 | * ignore the support folder when debianizing 35 | 36 | [ SimpleGeo Nerds ] 37 | 38 | -- SimpleGeo Nerds Tue, 11 Jan 2011 01:27:16 +0000 39 | 40 | nodejs-connect-auth (0.2.1-0sg0) unstable; urgency=low 41 | 42 | * New upstream release 43 | 44 | -- Wade Simmons Tue, 11 Jan 2011 01:23:37 +0000 45 | 46 | nodejs-connect-auth (0.2.0-0sg2) UNRELEASED; urgency=low 47 | 48 | [ Wade Simmons ] 49 | * add debian/source/include-binaries 50 | 51 | [ SimpleGeo Nerds ] 52 | 53 | -- SimpleGeo Nerds Thu, 14 Oct 2010 19:29:41 +0000 54 | 55 | nodejs-connect-auth (0.2.0-0sg0) unstable; urgency=low 56 | 57 | * Debianized 58 | 59 | -- Wade Simmons Wed, 13 Oct 2010 17:31:52 -0600 60 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: nodejs-connect-auth 2 | Section: misc 3 | Priority: extra 4 | Uploaders: Wade Simmons 5 | Maintainer: SimpleGeo Nerds 6 | build-Depends: cdbs, debhelper (>= 7) 7 | Standards-Version: 3.9.1 8 | 9 | Package: nodejs-connect-auth 10 | Depends: nodejs (>= 0.2.3), ${misc:Depends} 11 | Architecture: all 12 | Description: node-connect-auth 13 | http://github.com/ciaranj/connect-auth 14 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | This package was debianised by Wade Simmons 2 | 3 | The upstream repo is: 4 | http://github.com/ciaranj/connect-auth 5 | MIT license 6 | 7 | The Debian packaging is 8 | Copyright 2010, SimpleGeo, Inc. 9 | MIT license 10 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | usr/lib/nodejs/connect-auth 2 | -------------------------------------------------------------------------------- /debian/install: -------------------------------------------------------------------------------- 1 | lib/* usr/lib/nodejs/connect-auth 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | include /usr/share/cdbs/1/rules/debhelper.mk 4 | 5 | clean:: 6 | rm -rf support 7 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/source/include-binaries: -------------------------------------------------------------------------------- 1 | support/oauth/spec/lib/images/hr.png 2 | support/oauth/spec/lib/images/sprites.png 3 | support/oauth/spec/lib/images/loading.gif 4 | support/oauth/spec/lib/images/vr.png 5 | support/oauth/spec/lib/images/bg.png 6 | support/oauth/spec/lib/images/sprites.bg.png 7 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | keys_file.js -------------------------------------------------------------------------------- /examples/app-sina.js: -------------------------------------------------------------------------------- 1 | var connect = require('connect'); 2 | var MemoryStore = require('connect/middleware/session/memory'); 3 | var auth = require('../lib/index'); 4 | var util = require('util'); 5 | 6 | // We let the example run without npm, by setting up the require paths 7 | // so the node-oauth submodule inside of git is used. You do *NOT* 8 | // need to bother with this line if you're using npm ... 9 | require.paths.unshift('support'); 10 | var OAuth = require('oauth').OAuth; 11 | 12 | 13 | // N.B. TO USE Any of the OAuth or RPX strategies you will need to provide 14 | // a copy of the example_keys_file (named keys_file) 15 | try { 16 | var example_keys = require('./keys_file'); 17 | for (var key in example_keys) { 18 | global[key] = example_keys[key]; 19 | } 20 | } 21 | catch(e) { 22 | console.log('Unable to locate the keys_file.js file. Please copy and ammend the example_keys_file.js as appropriate'); 23 | return; 24 | } 25 | 26 | var sinaWBOAuth = new OAuth("http://api.t.sina.com.cn/oauth/request_token", 27 | "http://api.t.sina.com.cn/oauth/access_token", 28 | sinaConsumerKey, sinaConsumerSecret, 29 | "1.0", sinaCallbackAddress, "HMAC-SHA1"); 30 | 31 | function routes(app) { 32 | app.get('/auth/sina', function(req, res, params) { 33 | req.authenticate(['sina'], function(error, authenticated) { 34 | res.writeHead(200, {'Content-Type': 'text/html'}); 35 | if (authenticated) { 36 | res.end("

Hello Sina user:" + JSON.stringify(req.getAuthDetails().user) + ".

") 37 | } 38 | else { 39 | res.end("

Sina authentication failed :(

") 40 | } 41 | }); 42 | }); 43 | 44 | app.get('/sina/user_timeline', function(req, res, params) { 45 | req.authenticate((['sina']), function(error, authenticated) { 46 | res.writeHead(200, {'Content-Type': 'text/html'}); 47 | if (authenticated) { 48 | sinaWBOAuth.getProtectedResource('http://api.t.sina.com.cn/statuses/user_timeline.json', 'GET', 49 | req.getAuthDetails()['sina_oauth_token'], 50 | req.getAuthDetails()['sina_oauth_token_secret'], 51 | function (error, data) { 52 | if (error) { 53 | var r = ["
",
 54 |                                                       "error = " + util.inspect(error),
 55 |                                                       "
"]; 56 | res.end(r.join("")); 57 | } else { 58 | var utl = JSON.parse(data); 59 | res.write(""); 60 | res.write(""); 61 | 62 | res.write("

Fetch user_timeline for Sina user:" + JSON.stringify(req.getAuthDetails().user) + ".

"); 63 | res.write(util.inspect(utl)); 64 | res.end(""); 65 | } 66 | } 67 | ) 68 | } 69 | else { 70 | res.end("

You are not logged in to Sina

"); 71 | } 72 | }); 73 | }); 74 | 75 | app.get('/sina/post_current_time', function(req, res, params) { 76 | req.authenticate((['sina']), function(error, authenticated) { 77 | res.writeHead(200, {'Content-Type': 'text/html'}); 78 | if (authenticated) { 79 | var s = "The time now is: " + new Date(); 80 | sinaWBOAuth.getProtectedResource('http://api.t.sina.com.cn/statuses/update.json?status=' + encodeURIComponent(s), 'POST', 81 | req.getAuthDetails()['sina_oauth_token'], 82 | req.getAuthDetails()['sina_oauth_token_secret'], 83 | function (error, data) { 84 | if (error) { 85 | var r = ["
",
 86 |                                                       "error = " + util.inspect(error),
 87 |                                                       "
"]; 88 | res.end(r.join("")); 89 | } 90 | else { 91 | var result = JSON.parse(data); 92 | res.write(""); 93 | res.write(""); 94 | res.write("

Message Posted for Sina user:" + JSON.stringify(req.getAuthDetails().user) + ".

"); 95 | res.write(""); 96 | res.write("
    "); 97 | res.write("
  1. Message = " + s); 98 | res.write("
  2. "); 99 | res.write("
  3. Result = " + JSON.stringify(util.inspect(result))); 100 | res.write("
"); 101 | res.write(""); 102 | res.end(""); 103 | } 104 | } 105 | ) 106 | } 107 | else { 108 | res.end("

You are not logged in to Sina

"); 109 | } 110 | }); 111 | }); 112 | 113 | 114 | app.get('/auth/anon', function(req, res, params) { 115 | req.authenticate(['anon'], function(error, authenticated) { 116 | res.writeHead(200, {'Content-Type': 'text/html'}) 117 | res.end("

Hello! Full anonymous access

") 118 | }); 119 | }) 120 | 121 | app.get('/auth/never', function(req, res, params) { 122 | req.authenticate(['anon'], function(error, authenticated) { 123 | res.writeHead(200, {'Content-Type': 'text/html'}) 124 | res.end("

Hello! Authenticated: " + authenticated + "

") 125 | }); 126 | }) 127 | 128 | app.get('/logout', function(req, res, params) { 129 | req.logout(); 130 | res.writeHead(303, { 'Location': "/" }); 131 | res.end(''); 132 | }) 133 | 134 | app.get('/', function(req, res, params) { 135 | var self = this; 136 | res.writeHead(200, {'Content-Type': 'text/html'}) 137 | if (!req.isAuthenticated()) { 138 | res.end(' \n\ 139 | \n\ 140 | connect Auth -- Not Authenticated \n\ 141 | \n\ 142 | \n\ 143 |
\n\ 144 |

Not authenticated

\n\ 145 |
\n\ 146 | \n\ 147 | \n\ 148 | \n\ 149 |
\n\ 150 |
\n\ 151 | \n\ 152 | ') 153 | } 154 | else { 155 | res.end(' \n\ 156 | \n\ 157 | Express Auth -- Authenticated\n\ 158 | \n\ 159 | \n\ 160 |
\n\ 161 |

Authenticated

\n\ 162 | ' + JSON.stringify(req.getAuthDetails().user) + ' \n\ 163 |

Logout

\n\ 164 |
\n\ 165 | \n\ 166 | ') 167 | } 168 | }) 169 | } 170 | 171 | var server = connect.createServer( 172 | connect.cookieDecoder(), 173 | connect.session({ store: new MemoryStore({ reapInterval: -1 }) }), 174 | connect.bodyDecoder() /* Only required for the janrain strategy*/, 175 | auth([ 176 | auth.Anonymous(), 177 | auth.Never(), 178 | auth.Sina({consumerKey: sinaConsumerKey, consumerSecret: sinaConsumerSecret, callback: sinaCallbackAddress}) 179 | ]), 180 | connect.router(routes)); 181 | 182 | server.listen(80); -------------------------------------------------------------------------------- /examples/app.js: -------------------------------------------------------------------------------- 1 | var connect = require('connect') 2 | ,auth= require('../lib/index') 3 | ,url = require('url') 4 | ,fs = require('fs'); 5 | 6 | var OAuth= require('oauth').OAuth; 7 | 8 | var getSharedSecretForUserFunction = function(user, callback) { 9 | var result; 10 | if(user == 'foo') 11 | result= 'bar'; 12 | callback(null, result); 13 | }; 14 | 15 | var validatePasswordFunction = function(username, password, successCallback, failureCallback){ 16 | if (username === 'foo' && password === "bar"){ 17 | successCallback(); 18 | } else { 19 | failureCallback(); 20 | } 21 | }; 22 | 23 | // N.B. TO USE Any of the OAuth or RPX strategies you will need to provide 24 | // a copy of the example_keys_file (named keys_file) 25 | try { 26 | var example_keys= require('./keys_file'); 27 | for(var key in example_keys) { 28 | global[key]= example_keys[key]; 29 | } 30 | } 31 | catch(e) { 32 | console.log('Unable to locate the keys_file.js file. Please copy and ammend the example_keys_file.js as appropriate'); 33 | return; 34 | } 35 | 36 | // Setup the 'template' pages (don't use sync calls generally, but meh.) 37 | var authenticatedContent= fs.readFileSync( __dirname+"/public/authenticated.html", "utf8" ); 38 | var unAuthenticatedContent= fs.readFileSync( __dirname+"/public/unauthenticated.html", "utf8" ); 39 | 40 | // There appear to be Scurrilous ;) rumours abounding that connect-auth 41 | // doesn't 'work with connect' as it does not act like an 'onion skin' 42 | // to address this I'm showing how one might extend the *PRIMITIVES* 43 | // provided by connect-auth to simplify a middleware layer. 44 | 45 | // This middleware detects login requests (in this case requests with a query param of ?login_with=xxx where xxx is a known strategy) 46 | var example_auth_middleware= function() { 47 | return function(req, res, next) { 48 | var urlp= url.parse(req.originalUrl, true) 49 | if( urlp.query.login_with ) { 50 | req.authenticate([urlp.query.login_with], function(error, authenticated) { 51 | if( error ) { 52 | // Something has gone awry, behave as you wish. 53 | console.log( error ); 54 | res.end(); 55 | } 56 | else { 57 | if( authenticated === undefined ) { 58 | // The authentication strategy requires some more browser interaction, suggest you do nothing here! 59 | } 60 | else { 61 | if( urlp.query.login_with == "persona" && authenticated === false ) { 62 | // persona behaves differently to the other strategies as it is async-to-the-page POST. 63 | res.writeHead(401, "" ); 64 | res.end() 65 | } 66 | else { 67 | if( authenticated == true ) { 68 | req.getAuthDetails().activeStrategy= urlp.query.login_with; 69 | } 70 | // We've either failed to authenticate, or succeeded (req.isAuthenticated() will confirm, as will the value of the received argument) 71 | next(); 72 | } 73 | } 74 | }}); 75 | } 76 | else { 77 | next(); 78 | } 79 | } 80 | }; 81 | 82 | process.on('uncaughtException', function (err) { 83 | console.log('Caught exception: ' + err.stack); 84 | }); 85 | 86 | var app= connect(); 87 | app.use(connect.static(__dirname + '/public')) 88 | .use(connect.cookieParser('my secret here')) 89 | .use(connect.session()) 90 | .use(connect.bodyParser()) 91 | .use(auth({strategies:[ auth.Anonymous() 92 | , auth.Basic({validatePassword: validatePasswordFunction}) 93 | , auth.Bitbucket({consumerKey: bitbucketConsumerKey, consumerSecret: bitbucketConsumerSecret, callback: bitbucketCallbackAddress}) 94 | , auth.Digest({getSharedSecretForUser: getSharedSecretForUserFunction}) 95 | , auth.Http({validatePassword: validatePasswordFunction, getSharedSecretForUser: getSharedSecretForUserFunction}) 96 | , auth.Never() 97 | , auth.Twitter({consumerKey: twitterConsumerKey, consumerSecret: twitterConsumerSecret}) 98 | , auth.Skyrock({consumerKey: skyrockConsumerKey, consumerSecret: skyrockConsumerSecret, callback: skyrockCallbackAddress}) 99 | , auth.Facebook({appId : fbId, appSecret: fbSecret, scope: "email", callback: fbCallbackAddress}) 100 | , auth.Persona({audience: personaAudience}) 101 | , auth.Github({appId : ghId, appSecret: ghSecret, callback: ghCallbackAddress}) 102 | , auth.Yahoo({consumerKey: yahooConsumerKey, consumerSecret: yahooConsumerSecret, callback: yahooCallbackAddress}) 103 | , auth.Google({consumerKey: googleConsumerKey, consumerSecret: googleConsumerSecret, scope: "", callback: googleCallbackAddress}) 104 | , auth.Google2({appId : google2Id, appSecret: google2Secret, callback: google2CallbackAddress, requestEmailPermission: true}) 105 | , auth.Foursquare({appId: foursquareId, appSecret: foursquareSecret, callback: foursquareCallbackAddress}) 106 | , auth.Janrain({apiKey: janrainApiKey, appDomain: janrainAppDomain, callback: janrainCallbackUrl}) 107 | , auth.Getglue({appId : getGlueId, appSecret: getGlueSecret, callback: getGlueCallbackAddress}) 108 | , auth.Openid({callback: openIdCallback}) 109 | , auth.Yammer({consumerKey: yammerConsumerKey, yammerSecret: yammerConsumerSecret, callback: yammerCallback}) 110 | , auth.Linkedin({consumerKey: linkedinConsumerKey, consumerSecret: linkedinConsumerSecret, callback: linkedinCallback})], 111 | trace: true, 112 | logoutHandler: require('../lib/events').redirectOnLogout("/")})) 113 | .use(example_auth_middleware()) 114 | .use('/logout', function(req, res, params) { 115 | req.logout(); // Using the 'event' model to do a redirect on logout. 116 | }) 117 | .use("/", function(req, res, params) { 118 | res.writeHead(200, {'Content-Type': 'text/html'}) 119 | if( req.isAuthenticated() ) { 120 | var logoutApproach= ""; 121 | if( req.getAuthDetails().activeStrategy == "persona" ) { 122 | logoutApproach= "Logout"; 123 | } 124 | else { 125 | logoutApproach= "Logout"; 126 | } 127 | res.end( authenticatedContent.replace("#USER#", JSON.stringify( req.getAuthDetails().user ) ) 128 | .replace("#LOGOUTAPPROACH#", logoutApproach) ); 129 | } 130 | else { 131 | res.end( unAuthenticatedContent.replace("#PAGE#", req.originalUrl)); 132 | } 133 | }) 134 | .listen(80); 135 | -------------------------------------------------------------------------------- /examples/example_keys_file.js: -------------------------------------------------------------------------------- 1 | exports.bitbucketConsumerKey= ""; 2 | exports.bitbucketConsumerSecret= ""; 3 | exports.bitbucketCallbackAddress= "http://yourtesthost.com/auth/bitbucket_callback"; 4 | exports.fbId= ""; 5 | exports.fbSecret= ""; 6 | exports.fbCallbackAddress= "http://yourtesthost.com/auth/facebook_callback"; 7 | exports.getGlueId= ""; 8 | exports.getGlueSecret= ""; 9 | exports.getGlueCallbackAddress= "http://yourtesthost.com/auth/getglue_callback"; 10 | exports.ghId= ""; 11 | exports.ghSecret= ""; 12 | exports.ghCallbackAddress= "http://yourtesthost.com/auth/github_callback"; 13 | exports.twitterConsumerKey= ""; 14 | exports.twitterConsumerSecret= ""; 15 | exports.yahooConsumerKey= ""; 16 | exports.yahooConsumerSecret= ""; 17 | exports.yahooCallbackAddress= "http://yourtesthost.com/auth/yahoo_callback"; 18 | exports.googleConsumerKey= ""; 19 | exports.googleConsumerSecret= ""; 20 | exports.googleCallbackAddress= "http://yourtesthost.com/auth/google_callback"; 21 | exports.google2Id= ""; 22 | exports.google2Secret= ""; 23 | exports.google2CallbackAddress= "http://yourtesthost.com/oauth2callback"; 24 | exports.foursquareId= ""; 25 | exports.foursquareSecret= ""; 26 | exports.foursquareCallbackAddress= "http://yourtesthost.com/auth/foursquare_callback"; 27 | exports.janrainApiKey= ""; 28 | exports.janrainAppDomain= "yourrpxnowsubdomain"; 29 | exports.janrainCallbackUrl= "http://localhost/auth/janrain_callback"; 30 | exports.openIdCallback= "http://yourtesthost.com/verify"; 31 | exports.linkedinConsumerKey= ""; 32 | exports.linkedinConsumerSecret= ""; 33 | exports.linkedinCallback= "http://localhost/auth/linkedin_callback"; 34 | exports.personaAudience="yourtesthost.com"; 35 | exports.yammerConsumerKey= ""; 36 | exports.yammerConsumerSecret= ""; 37 | exports.yammerCallback= "http://localhost/auth/yammer_callback"; 38 | exports.skyrockConsumerKey= ""; 39 | exports.skyrockConsumerSecret= ""; 40 | exports.skyrockCallbackAddress= "http://local.host/auth/skyrock_callback"; 41 | -------------------------------------------------------------------------------- /examples/mac/app.js: -------------------------------------------------------------------------------- 1 | // Load modules 2 | 3 | var express = require('express'); 4 | var auth = require('connect-auth'); 5 | 6 | 7 | // Create and configure server instance 8 | 9 | function createServer () { 10 | 11 | // Create server 12 | 13 | var server = express.createServer(); 14 | 15 | // Configure Server 16 | 17 | server.configure(function () { 18 | 19 | // Built-in 20 | 21 | server.use(express.methodOverride()); // Allow method override using _method form parameter 22 | server.use(express.bodyDecoder()); // Parse application/x-www-form-urlencoded 23 | server.use(express.staticProvider(__dirname + '/files')); // Serve client documents in local directory 24 | 25 | // Local 26 | 27 | server.use(setResponseHeader()); // Set default response headers for CORS 28 | server.use(logConsole()); // Display incoming requests 29 | 30 | // Authentication 31 | 32 | server.use(auth([auth.Mac({ realm: "Example", // Set realm, typically a domain name or application name 33 | getTokenAttributes: getToken, // Function used to fetch the access token record, typically from a database 34 | // hostHeader: 'x-forwarded-host', // Needed when running behind a proxy such as Apache2 35 | // isHTTPS: true, // Uncomment for HTTPS 36 | checkNonce: nonceCheck, // Optional nonce checking function 37 | bodyHashMode: "require" })])); // Require body hash validation for all non GET/HEAD/OPTIONS requests 38 | }); 39 | 40 | // Setup generic OPTIONS route 41 | 42 | server.options(/.+/, function (req, res) { 43 | 44 | res.send(' '); 45 | }); 46 | 47 | return server; 48 | } 49 | 50 | 51 | // Set default response headers to enable CORS 52 | 53 | function setResponseHeader() { 54 | 55 | return function (req, res, next) { 56 | 57 | res.header('Access-Control-Allow-Origin', '*'); 58 | res.header('Access-Control-Allow-Methods', 'GET, HEAD, POST, PUT, DELETE, OPTIONS'); 59 | res.header('Access-Control-Allow-Headers', 'Authorization'); 60 | res.header('Access-Control-Max-Age', '86400'); // One day 61 | 62 | next(); 63 | }; 64 | }; 65 | 66 | 67 | // Log requests to console 68 | 69 | function logConsole() { 70 | 71 | return function (req, res, next) { 72 | 73 | console.log('Received request: ' + req.method + ' ' + req.originalUrl); 74 | 75 | next(); 76 | }; 77 | } 78 | 79 | 80 | // MAC Authentication 81 | 82 | function getToken (token, callback) { 83 | 84 | if (token != null && 85 | token != '') { 86 | 87 | // Fetch token from database or cache 88 | 89 | if (token == 'h328hdkasjd3') { 90 | 91 | var record = { algorithm: 'hmac-sha-256', // Required 92 | secret: '23hdkdho2893hdkjd', // Required 93 | userId: 'johndoe' // Application-specific field 94 | }; 95 | 96 | callback(record); 97 | } 98 | else { 99 | 100 | // Token not found 101 | 102 | callback({}); 103 | } 104 | } 105 | else { 106 | 107 | // Invalid token request 108 | 109 | callback({}); 110 | } 111 | } 112 | 113 | 114 | var nonceCache = {}; 115 | 116 | function nonceCheck(token, timestamp, nonce, callback) { 117 | 118 | // Warning: Don't use this in production as it will grow until out of memory 119 | 120 | if (nonceCache[token] == null) { 121 | 122 | nonceCache[token] = {}; 123 | } 124 | 125 | if (nonceCache[token][timestamp] == null) { 126 | 127 | nonceCache[token][timestamp] = {}; 128 | } 129 | 130 | if (nonceCache[token][timestamp][nonce]) { 131 | 132 | // Replay 133 | 134 | callback(false); 135 | } 136 | else { 137 | 138 | // Never used before 139 | 140 | nonceCache[token][timestamp][nonce] = true; 141 | callback(true); 142 | } 143 | } 144 | 145 | 146 | function authenticate (req, res, next) { 147 | 148 | req.authenticate(['mac'], function (error, authenticated) { 149 | 150 | if (authenticated) { 151 | 152 | var userId = req.getAuthDetails().user.userId; 153 | 154 | if (userId == null || 155 | userId == '') { 156 | 157 | // Internal Error 158 | res.send("Bad Token User ID"); 159 | } 160 | else { 161 | 162 | req.example = {}; 163 | req.example.userId = userId; 164 | next(); 165 | } 166 | } 167 | }); 168 | } 169 | 170 | 171 | // Create server 172 | 173 | var app = module.exports = createServer(); 174 | 175 | 176 | // Routing rules 177 | 178 | app.get('/test', authenticate, function (req, res) { 179 | 180 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 181 | res.end("GET Received from: " + req.example.userId); 182 | }); 183 | 184 | 185 | app.post('/test', authenticate, function (req, res) { 186 | 187 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 188 | res.end("POST Received from: " + req.example.userId); 189 | }); 190 | 191 | 192 | // Start server 193 | 194 | app.listen(8000, "postmile.net"); 195 | console.log('Example Server started at http://localhost:8000'); 196 | 197 | -------------------------------------------------------------------------------- /examples/mac/files/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | API Test Utility 5 | 6 | 46 | 47 | 48 | Token: 49 | Secret: 50 | Algorithm: 51 |

52 | URI: 53 |
60 | Body: 61 |
62 |

63 | Result:

64 | 65 | -------------------------------------------------------------------------------- /examples/oauthapp.js: -------------------------------------------------------------------------------- 1 | var connect = require('connect'); 2 | //var MemoryStore = require('connect/middleware/session/memory'); 3 | var auth= require('../lib'); 4 | var url= require('url'); 5 | var OAuthDataProvider= require('./in_memory_oauth_data_provider').OAuthDataProvider; 6 | 7 | var renderAuthenticationForm= function(res, token, flash) { 8 | res.writeHead(200, {'Content-Type':'text/html'}) 9 | var error= ''; 10 | if( flash ) { 11 | error= '

' + flash + '

'; 12 | } 13 | res.end(' \n\ 14 | \n\ 15 |

Login

\n\ 16 | '+error+' \n\ 17 |
\n\ 18 | \n\ 19 | \n\ 20 | \n\ 21 | \n\ 22 |
\n\ 24 | \n\ 25 | \n\ 26 | '); 27 | }; 28 | 29 | var authenticateProvider= function(req, res) { 30 | var parsedUrl= url.parse(req.originalUrl, true); 31 | renderAuthenticationForm(res, parsedUrl.query.oauth_token ); 32 | }; 33 | 34 | /** 35 | Handle the post back from the oauth authentication session (here you can build additional leves such as 36 | handling authorization for the application) 37 | **/ 38 | var authorizeProvider = function(err, req, res, authorized, authResults, application, user) { 39 | var self = this; 40 | 41 | if(err) { 42 | renderAuthenticationForm(res, authResults.token, 'No such user or wrong password' ); 43 | } else { 44 | res.writeHead(200, {'Content-Type':'text/html'}) 45 | res.end(' \n\ 46 | \n\ 47 |

Login

\n\ 48 | \n\ 49 | \n\ 50 | \n\ 51 | \n\ 52 | \n\ 53 | \n\ 54 | \n\ 55 |
Application Title' + application.title + '
Application Description' + application.description + '
User name' + user.username + '
\n\ 57 | \n\ 58 | \n\ 59 | '); 60 | } 61 | }; 62 | /** 63 | Handle the successful authentication and authorization 64 | **/ 65 | var authorizationFinishedProvider = function(err, req, res, result) { 66 | 67 | res.writeHead(200, {'Content-Type':'text/html'}) 68 | res.end(' \n\ 69 | \n\ 70 |

Authentication and Authorization Finished, Application can now access

\n\ 71 | \n\ 72 | \n\ 73 | \n\ 74 | \n\ 75 | \n\ 76 |
Token' + result.token + '
Verifier' + result.verifier + '
\n\ 78 | '); 79 | } 80 | 81 | var app= connect(); 82 | app.use(connect.bodyParser()) 83 | .use(connect.logger()) 84 | .use(auth({strategies: [ 85 | auth.Oauth({oauth_provider: new OAuthDataProvider({ applications:[{title:'Test', description:'Test App', consumer_key:"JiYmll7CX3AXDgasnnIDeg",secret:"mWPBRK5kG2Tkthuf5zRV1jYWOEwnjI6xs3QVRqOOg"}] 86 | , users:[{username:'foo', password:'bar'}] }), 87 | authenticate_provider: authenticateProvider, 88 | authorize_provider: authorizeProvider, 89 | authorization_finished_provider: authorizationFinishedProvider 90 | }) 91 | ], 92 | trace: true 93 | })) 94 | .use('/fetch/unicorns', function(req, res, params) { 95 | req.authenticate(['oauth'], function(error, authenticated) { 96 | if( authenticated ) { 97 | res.writeHead(200, {'Content-Type': 'text/plain'}) 98 | res.end('The unicorns fly free tonight'); 99 | } 100 | else { 101 | res.writeHead(401, {'Content-Type': 'text/plain'}) 102 | res.end('Doubt you\'ll ever see this.'); 103 | } 104 | }); 105 | }) 106 | .listen(3000); -------------------------------------------------------------------------------- /examples/oauthapp_2legged.js: -------------------------------------------------------------------------------- 1 | var connect = require('connect'); 2 | var auth= require('../lib'); 3 | var url= require('url'); 4 | var OAuthDataProvider= require('./in_memory_oauth_data_provider').OAuthDataProvider; 5 | 6 | var app= connect(); 7 | app.use(connect.bodyParser()) 8 | .use(connect.logger()) 9 | .use(auth( [ 10 | auth.Oauth({oauth_provider: new OAuthDataProvider({ applications:[{title:'Test', description:'Test App', consumer_key:"JiYmll7CX3AXDgasnnIDeg",secret:"mWPBRK5kG2Tkthuf5zRV1jYWOEwnjI6xs3QVRqOOg"}]}), 11 | authenticate_provider: null, 12 | authorize_provider: null, 13 | authorization_finished_provider: null 14 | }) 15 | ])) 16 | .use ('/fetch/unicorns', function(req, res, params) { 17 | req.authenticate(['oauth'], function(error, authenticated) { 18 | if( authenticated ) { 19 | res.writeHead(200, {'Content-Type': 'text/plain'}) 20 | res.end('The unicorns fly free tonight'); 21 | } 22 | else { 23 | res.writeHead(401, {'Content-Type': 'text/plain'}) 24 | res.end('Doubt you\'ll ever see this.'); 25 | } 26 | }); 27 | }) 28 | .listen( 3000 ); 29 | -------------------------------------------------------------------------------- /examples/oauthclient_2legged.js: -------------------------------------------------------------------------------- 1 | var OAuth= require('oauth').OAuth; 2 | 3 | var oa= new OAuth("http://localhost:3000/oauth/request_token", 4 | "http://localhost:3000/oauth/access_token", 5 | "JiYmll7CX3AXDgasnnIDeg", 6 | "mWPBRK5kG2Tkthuf5zRV1jYWOEwnjI6xs3QVRqOOg", 7 | "1.0", 8 | null, 9 | "HMAC-SHA1") 10 | 11 | oa.getProtectedResource("http://localhost:3000/fetch/unicorns", "GET", '', '', function (error, data, response) { 12 | console.log(data); 13 | }); 14 | -------------------------------------------------------------------------------- /examples/oauthclientapp.js: -------------------------------------------------------------------------------- 1 | var connect = require('connect'); 2 | var url= require('url') 3 | 4 | // We let the example run without npm, by setting up the require paths 5 | // so the node-oauth submodule inside of git is used. You do *NOT* 6 | // need to bother with this line if you're using npm ... 7 | //require.paths.unshift('support') 8 | var OAuth= require('oauth').OAuth; 9 | var oa= new OAuth("http://localhost:3000/oauth/request_token", 10 | "http://localhost:3000/oauth/access_token", 11 | "JiYmll7CX3AXDgasnnIDeg", "mWPBRK5kG2Tkthuf5zRV1jYWOEwnjI6xs3QVRqOOg", 12 | "1.0A", "http://localhost:4000/oauth/callback", "HMAC-SHA1"); 13 | 14 | var app= connect(); 15 | app.use(connect.logger()) 16 | .use(connect.cookieParser("secret")) 17 | .use(connect.session()) 18 | .use ('/oauth/callback', function(req, res, params) { 19 | var parsedUrl= url.parse(req.originalUrl, true); 20 | console.log(require('util').inspect(req.session)) 21 | oa.getOAuthAccessToken(parsedUrl.query.oauth_token, req.session.oauth_token_secret, parsedUrl.query.oauth_verifier, 22 | function(error, oauth_access_token, oauth_access_token_secret, results) { 23 | oa.getProtectedResource("http://localhost:3000/fetch/unicorns", "GET", oauth_access_token, oauth_access_token_secret, function(error, data){ 24 | res.writeHead(200, {'Content-type': 'text/html'}) 25 | res.end(data); 26 | }) 27 | }) 28 | }) 29 | .use ('/', function(req, res, params) { 30 | oa.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, results){ 31 | console.log( error ) 32 | req.session.oauth_token_secret= oauth_token_secret; 33 | console.log(require('util').inspect(req.session)) 34 | 35 | res.writeHead(303, { 'Location': "http://localhost:3000/oauth/authorize?oauth_token=" + oauth_token }); 36 | res.end(''); 37 | }); 38 | }) 39 | .listen(4000); 40 | -------------------------------------------------------------------------------- /examples/public/BluePigment.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------- 2 | 3 | AUTHOR : Erwin Aligam 4 | WEBSITE : http://www.styleshout.com/ 5 | TEMPLATE NAME : BluePigment 6 | TEMPLATE CODE : S-0016 7 | VERSION : 1.1 8 | DATE : January 18, 2010 9 | 10 | ---------------------------------------------------- */ 11 | 12 | /* ------------------------------------------------- 13 | HTML ELEMENTS 14 | ---------------------------------------------------- */ 15 | 16 | /* top elements */ 17 | * { padding: 0; margin: 0; outline: 0 } 18 | 19 | body { 20 | margin: 0; padding: 0; 21 | font: normal 12px/1.7em verdana, tahoma, sans-serif; 22 | text-align: center; 23 | background: #001342 url(headerbg.jpg) repeat-x 0 0; 24 | color: #F2F9FF; 25 | } 26 | 27 | /* links */ 28 | a { 29 | color: #003366; 30 | background-color: inherit; 31 | text-decoration: none; 32 | } 33 | a:hover { 34 | color: #FAA34B; 35 | background-color: inherit; 36 | text-decoration: underline; 37 | border: none; 38 | } 39 | 40 | /* headers */ 41 | h1, h2, h3 { 42 | font: bold 1em 'Trebuchet MS', Tahoma, Arial, Sans-serif; 43 | color: #fff; 44 | } 45 | h1 { font-size: 2.5em; } 46 | h2 { font-size: 2em; text-transform:uppercase;} 47 | h3 { font-size: 1.8em; } 48 | 49 | p, h1, h2, h3 { 50 | margin: 0; 51 | padding: 10px 15px; 52 | } 53 | 54 | ul, ol { 55 | margin: 10px 30px; 56 | padding: 0 15px; 57 | } 58 | 59 | /* images */ 60 | img.float-right { 61 | margin: .5em 0 1em 1em; 62 | } 63 | img.float-left { 64 | margin: .5em 1em 1em 0; 65 | } 66 | 67 | code { 68 | margin: .5em 0; 69 | display: block; 70 | padding: 20px; 71 | text-align: left; 72 | overflow: auto; 73 | font: 500 1em/1.5em 'Lucida Console', 'Courier New', monospace ; 74 | /* white-space: pre; */ 75 | background: #1E89DC; 76 | border: 1px solid #0065C6; 77 | } 78 | acronym { 79 | cursor: help; 80 | border-bottom: 1px solid #0065C6; 81 | } 82 | blockquote { 83 | margin: 10px 15px; 84 | padding: 10px 0 10px 28px; 85 | border: 1px solid #0065C6; 86 | background: #1E89DC; 87 | font: bold 1.3em/1.5em "Century Gothic", "Trebuchet MS", Helvetica, Arial, Geneva, sans-serif; 88 | } 89 | 90 | /* start - table */ 91 | table { 92 | margin: 10px 15px; 93 | border-collapse: collapse; 94 | } 95 | th strong { 96 | color: #fff; 97 | } 98 | th { 99 | background: #93BC0C; 100 | height: 3em; 101 | padding-left: 12px; 102 | padding-right: 12px; 103 | color: #FFF; 104 | text-align: left; 105 | border-left: 1px solid #B6D59A; 106 | border-bottom: solid 2px #8EB200; 107 | border-top: solid 2px #8EB200; 108 | } 109 | tr { 110 | color: #707070; 111 | height: 2.5em; 112 | } 113 | td { 114 | padding-left: 12px; 115 | padding-right: 12px; 116 | border-left: 1px solid #FFF; 117 | border-bottom: solid 1px #ffffff; 118 | } 119 | td.first,th.first { 120 | border-left: 0px; 121 | } 122 | tr.row-a { 123 | background: #F8F8F8; 124 | } 125 | tr.row-b { 126 | background: #EFEFEF; 127 | } 128 | /* end - table */ 129 | 130 | /* form elements */ 131 | form { 132 | margin: 10px 15px; 133 | padding: 10px; 134 | border: 1px solid #0065C6; 135 | background-color: #1E89DC; 136 | } 137 | fieldset { 138 | margin: 0; 139 | padding: 0; 140 | border: none; 141 | } 142 | label { 143 | display:block; 144 | font-weight:bold; 145 | margin: .4em 0; 146 | } 147 | input { 148 | padding: .3em; 149 | border: 1px solid #eee; 150 | font: normal 1em Verdana, sans-serif; 151 | color:#777; 152 | } 153 | textarea { 154 | width: 55%; 155 | padding: .3em; 156 | font: normal 1em/1.5em Verdana, sans-serif; 157 | border: 1px solid #eee; 158 | height: 10em; 159 | display:block; 160 | color:#777; 161 | } 162 | input.button { 163 | font: bold 1em Arial, Sans-serif; 164 | margin: 0; 165 | padding: .25em .3em; 166 | color: #FFF; 167 | background: #A2CC00; 168 | border: 2px solid #8EB200; 169 | } 170 | 171 | /* search form */ 172 | .searchform { 173 | background-color: transparent; 174 | border: none; 175 | margin: 0 0 0 10px; padding: 0 0 1.5em 0; 176 | width: 18em; 177 | } 178 | .searchform p { margin: 0; padding: 0; } 179 | .searchform input.textbox { 180 | width: 11em; 181 | color: #777; 182 | padding: .4em; 183 | border: 1px solid #E5E5E5; 184 | vertical-align: top; 185 | } 186 | .searchform input.button { 187 | width: 60px; 188 | vertical-align: top; 189 | } 190 | 191 | /*********************** 192 | LAYOUT 193 | ************************/ 194 | 195 | #header-content, #content, #nav { 196 | /* 197 | The width value below sets the total width of the design. It's default value is set to 93% 198 | which means that it will take up 93% of the browser window's width. You can also set it to a 199 | different percentage value (90%, 85%, etc.). This design is fluid layout by default, but you 200 | can turn it into a fixed width layout by setting a pixel value to the width (e.g. 900px, 950px). 201 | */ 202 | width: 93%; 203 | } 204 | 205 | /* box */ 206 | .box { 207 | margin: 10px 0; 208 | padding: 10px 10px 20px 10px; 209 | border: 5px solid #1F8ADE; 210 | background: url(boxbg.jpg); 211 | } 212 | 213 | /* header */ 214 | #header { 215 | height: 178px; 216 | text-align: left; 217 | } 218 | #header-content { 219 | position: relative; 220 | margin: 0 auto; padding: 0; 221 | } 222 | #header-content h1#logo-text a { 223 | position: absolute; 224 | margin: 0; padding: 0; 225 | font: bold 58px 'Trebuchet MS', Tahoma, Arial, Sans-serif; 226 | letter-spacing: -1px; 227 | color: #fff; 228 | text-decoration: none; 229 | 230 | /* change the values of top and left to adjust the position of the logo*/ 231 | top: 30px; left: 10px; 232 | } 233 | #header-content h1#logo-text span { 234 | color: #68B5F0; 235 | } 236 | #header-content #slogan { 237 | position: absolute; 238 | font: bold 16px 'Trebuchet Ms', Sans-serif; 239 | text-transform: none; 240 | color: #FFF; 241 | margin: 0; padding: 0; 242 | 243 | /* change the values of left and top to adjust the position of the slogan */ 244 | top: 100px; left: 210px; 245 | } 246 | 247 | /* header links */ 248 | #header-content #header-links { 249 | position: absolute; 250 | top: 25px; right: 10px; 251 | color: #fff; 252 | font: bold 15px "Trebuchet MS", Tahoma, Sans-serif; 253 | } 254 | #header-content #header-links a { 255 | color: #93C9F4; 256 | text-decoration: none; 257 | } 258 | #header-content #header-links a:hover { 259 | color: #fff; 260 | } 261 | 262 | 263 | /* Navigation */ 264 | #nav-wrap { 265 | float: left; 266 | width: 100%; 267 | background: url(menubg.jpg) repeat-x left bottom; 268 | clear: both; 269 | } 270 | #nav { 271 | clear: both; 272 | margin: 0 auto; 273 | padding: 0; 274 | } 275 | #nav ul { 276 | float: left; 277 | list-style: none; 278 | text-transform: uppercase; 279 | margin: 0; 280 | padding: 0; 281 | height: 64px; 282 | } 283 | #nav ul li { 284 | float: left; 285 | margin: 0; padding: 0; 286 | height: 64px; 287 | } 288 | #nav ul li a { 289 | display: block; 290 | float: left; 291 | width: auto; 292 | margin: 0; 293 | padding: 0 15px; 294 | color: #FFF; 295 | font: bold 15px "Century Gothic", "Trebuchet MS", Helvetica, Arial, Geneva, sans-serif; 296 | text-decoration: none; 297 | letter-spacing: 1px; 298 | } 299 | #nav ul li a:hover, 300 | #nav ul li a:active { 301 | color: #333; 302 | } 303 | #nav ul li#current { 304 | background: url(nav-current.jpg) no-repeat center bottom; 305 | } 306 | #nav ul li#current a { 307 | color: #333; 308 | } 309 | 310 | /* content */ 311 | #content-wrap { 312 | clear: both; 313 | float: left; 314 | background: #1183DA; 315 | width: 100%; 316 | } 317 | #content { 318 | text-align: left; 319 | padding: 0; 320 | margin: 0 auto; 321 | } 322 | 323 | /* sidebar */ 324 | #sidebar { 325 | float: right; 326 | width: 21em; 327 | margin: 10px 0 10px -21em; padding: 0; 328 | } 329 | #sidebar h1 { 330 | font: bold 1.75em 'Trebuchet MS', Tahoma, Arial, Sans-serif; 331 | padding: .3em 0 .5em 10px; 332 | color: #002368; 333 | } 334 | #sidebar ul.sidemenu { 335 | list-style:none; 336 | margin: 0; 337 | padding: .3em 0 1em 5px; 338 | font-family: 'Trebuchet MS', Tahoma, Sans-serif; 339 | } 340 | #sidebar ul.sidemenu li { 341 | padding: 0; 342 | background: url(sidebullet.gif) no-repeat .3em .5em; 343 | } 344 | 345 | * html body #sidebar ul.sidemenu li { height: 1%; } 346 | 347 | #sidebar ul.sidemenu li a { 348 | display: block; 349 | font-weight: bold; 350 | color: #E8F4FF; 351 | text-decoration: none; 352 | padding: .2em 0 .2em 30px; 353 | line-height: 1.5em; 354 | font-size: 1.35em; 355 | } 356 | #sidebar ul.sidemenu li a:hover { 357 | background: #0F7ACC url(sidebullet.gif) no-repeat .25em .45em; 358 | color: #FFF; 359 | } 360 | #sidebar ul.sidemenu ul{ 361 | margin-left: 15px; 362 | } 363 | 364 | #sidebar .sidebox { 365 | margin: 5px 15px 5px 0; 366 | padding: 0; 367 | background: url(sidebarsep.jpg) repeat-x left bottom; 368 | } 369 | #sidebar .sep{ 370 | background: url(sidebarsep.jpg) repeat-x left bottom; 371 | height: 2px; 372 | margin: 0px 15px 10px 0; 373 | clear: both; 374 | } 375 | 376 | /* main */ 377 | #main { 378 | margin: 10px 23em 10px 0; 379 | padding: 0; 380 | } 381 | #main h1 { 382 | font: bold 2.8em 'Trebuchet MS', Arial, Sans-serif; 383 | color: #B1E100; 384 | letter-spacing: -2px; 385 | padding-bottom: 0; 386 | } 387 | #main h1 a { 388 | color: #B1E100; 389 | text-decoration: none; 390 | } 391 | 392 | /* footer */ 393 | #footer-wrap { 394 | clear: both; 395 | border-top: 5px solid #86CC15; 396 | text-align: left; 397 | padding: 1.6em 0; 398 | } 399 | #footer-wrap a { 400 | text-decoration: none; 401 | color: #5B9CFF; 402 | 403 | } 404 | #footer-wrap a:hover { 405 | color: #E8F4FF; 406 | } 407 | #footer-wrap p { 408 | padding: 10px 0; 409 | } 410 | #footer-wrap h2 { 411 | color: #E8F4FF; 412 | margin: 0; 413 | padding: 0 10px; 414 | text-transform: none; 415 | } 416 | 417 | /* footer columns */ 418 | #footer-columns { 419 | color: #5B9CFF; 420 | margin: 0 auto; 421 | padding: 0; 422 | width: 90%; 423 | } 424 | #footer-columns ul { 425 | list-style: none; 426 | margin: 10px 0 0 0; 427 | padding: 0; 428 | background: url(footer-dots.jpg) repeat-x left top; 429 | } 430 | #footer-columns li { 431 | background: url(footer-dots.jpg) repeat-x left bottom; 432 | } 433 | #footer-columns li a { 434 | display: block; 435 | font-weight: normal; 436 | padding: .5em 0 .5em 1em; 437 | width: 96%; 438 | } 439 | #footer-columns .col3, .col3-center { 440 | float: left; 441 | width: 32%; 442 | } 443 | #footer-columns .col3-center { 444 | margin: 0 15px; 445 | } 446 | 447 | /* bottom */ 448 | #footer-bottom { 449 | clear: both; 450 | color: #E8F4FF; 451 | margin: 0 auto; 452 | padding: 1em 0; 453 | text-align: center; 454 | font-size: .9em; 455 | } 456 | 457 | /* alignment classes */ 458 | .float-left { float: left; } 459 | .float-right { float: right; } 460 | .align-left { text-align: left; } 461 | .align-right { text-align: right; } 462 | 463 | /* additional classes */ 464 | .clear { clear: both; } 465 | .white { color: #E8F4FF; } 466 | 467 | img.rssfeed { 468 | border: none; 469 | padding: 0 0 5px 0; 470 | background: transparent; 471 | } 472 | 473 | .post-by { 474 | font-size: .95em; 475 | padding-top: 0; 476 | } 477 | .post-footer { 478 | text-align: right; 479 | background: #1E89DC; 480 | border: 1px solid #0065C6; 481 | padding: 8px 10px; 482 | margin: 20px 15px 10px 15px; 483 | } 484 | .post-footer .date { 485 | background: url(clock.gif) no-repeat left center; 486 | padding-left: 20px; margin: 0 3px 0 3px; 487 | } 488 | .post-footer .comments { 489 | background: url(comment.gif) no-repeat left center; 490 | padding-left: 20px; margin: 0 3px 0 3px; 491 | } 492 | .post-footer .readmore { 493 | background: url(page.gif) no-repeat left center; 494 | padding-left: 20px; margin: 0 3px 0 3px; 495 | } 496 | 497 | 498 | 499 | -------------------------------------------------------------------------------- /examples/public/authenticated.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Connect Auth -- Authenticated 4 | 5 | 6 | 7 | 8 |
9 |

Authenticated

10 |
#USER#
11 |

#LOGOUTAPPROACH#

12 |
13 | 34 | 35 | -------------------------------------------------------------------------------- /examples/public/boxbg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/boxbg.jpg -------------------------------------------------------------------------------- /examples/public/clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/clock.gif -------------------------------------------------------------------------------- /examples/public/comment.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/comment.gif -------------------------------------------------------------------------------- /examples/public/firefox-gray.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/firefox-gray.jpg -------------------------------------------------------------------------------- /examples/public/footer-dots.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/footer-dots.jpg -------------------------------------------------------------------------------- /examples/public/headerbg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/headerbg.jpg -------------------------------------------------------------------------------- /examples/public/menubg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/menubg.jpg -------------------------------------------------------------------------------- /examples/public/nav-current.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/nav-current.jpg -------------------------------------------------------------------------------- /examples/public/page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/page.gif -------------------------------------------------------------------------------- /examples/public/persona_sign_in_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/persona_sign_in_blue.png -------------------------------------------------------------------------------- /examples/public/rssfeed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/rssfeed.gif -------------------------------------------------------------------------------- /examples/public/sidebarsep.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/sidebarsep.jpg -------------------------------------------------------------------------------- /examples/public/sidebullet.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/sidebullet.gif -------------------------------------------------------------------------------- /examples/public/sign-in-with-twitter-d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/sign-in-with-twitter-d.png -------------------------------------------------------------------------------- /examples/public/skyrock-connect-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/examples/public/skyrock-connect-black.png -------------------------------------------------------------------------------- /examples/public/unauthenticated.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Connect Auth -- Not Authenticated 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 34 | 35 | 36 |
37 |
38 |

Bundled Client Strategies

39 |
40 |

Miscellaneous

41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 63 | 64 | 65 | 66 | 67 | 68 |
NameTry it!
AnonymousTry It!
NeverTry It!
OpenID 57 | 58 | 59 | 60 | 61 | 62 |
Mozilla Persona
69 |
70 |
71 |

HTTP

72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |
NameTry it!
Http Basic (Only)Try It!
Http Digest (Only)Try It!
Http (Digest then Basic)Try It!
90 |
91 |
92 |

OAuth

93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 125 | 126 | 127 | 128 | 133 | 134 | 135 | 136 | 141 | 142 | 143 | 144 | 145 | 146 |
NameTry it!
BitBucketTry It!
GetglueTry It!
Google (OAuth 1.0)
109 | 112 |
Linkedin
Skyrock
121 | 122 | 123 | 124 |
Twitter
129 | 130 | 131 | 132 |
Yahoo!
137 | 138 | 139 | 140 |
YammerTry It!
147 |
148 |
149 |

OAuth 2

150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 176 | 177 | 178 | 179 | 184 | 185 | 186 | 187 | 188 | 189 |
NameTry it!
Facebook
FourSquareTry It!
Github
172 | 173 | 174 | 175 |
Google (OAuth 2)
180 | 183 |
JanrainTry It!
190 |
191 |
192 |
193 | 194 |
195 | 196 | 197 | 208 | 209 | 235 | 236 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /lib/auth.strategies/_authutils.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var NONCE_CHARS= ['a','b','c','d','e','f','g','h','i','j','k','l','m','n', 6 | 'o','p','q','r','s','t','u','v','w','x','y','z','A','B', 7 | 'C','D','E','F','G','H','I','J','K','L','M','N','O','P', 8 | 'Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3', 9 | '4','5','6','7','8','9']; 10 | 11 | exports.getNonce= function(nonceSize) { 12 | var result = []; 13 | var chars= NONCE_CHARS; 14 | var char_pos; 15 | var nonce_chars_length= chars.length; 16 | 17 | for (var i = 0; i < nonceSize; i++) { 18 | char_pos= Math.floor(Math.random() * nonce_chars_length); 19 | result[i]= chars[char_pos]; 20 | } 21 | return result.join(''); 22 | }; 23 | 24 | /** 25 | * Given a valid HTTP Authorization HTTP will return an object literal 26 | * that contains the passed credentials. 27 | * 28 | * @return {object} The Authorization credentials, un-encoded and un-quoted. 29 | * @api private 30 | */ 31 | exports.splitAuthorizationHeader= function( authorizationHeader ) { 32 | 33 | var results= {}; 34 | 35 | var parameterPairs= []; 36 | var isInQuotes= false; 37 | var lastStringStartingBoundary= 0; 38 | 39 | //Need to pull off authentication type first 40 | results.type= /^([a-zA-Z]+)\s/.exec(authorizationHeader)[1]; 41 | authorizationHeader= authorizationHeader.substring( results.type.length + 1 ) // type + 1 whitespace 42 | 43 | for(var i=0;i< authorizationHeader.length;i++) { 44 | if( authorizationHeader[i] == "\"" && authorizationHeader[i-1] != "\\" ) { 45 | // WE've found an un-escaped quote (do escaped quotes exist, need to check the RFC) 46 | isInQuotes= !isInQuotes; 47 | } 48 | if( authorizationHeader[i] == "," && !isInQuotes ) { 49 | var credentialsPart= authorizationHeader.substr(lastStringStartingBoundary, (i-lastStringStartingBoundary)); 50 | //Strip whitespace.. 51 | credentialsPart= credentialsPart.replace(/^\s+|\s+$/g,'') 52 | 53 | 54 | parameterPairs[parameterPairs.length]= credentialsPart; 55 | lastStringStartingBoundary= i+1; // skip the comma. 56 | } 57 | } 58 | 59 | // Refactor this code. 60 | if( lastStringStartingBoundary < authorizationHeader.length ) { 61 | var credentialsPart= authorizationHeader.substr(lastStringStartingBoundary, (authorizationHeader.length-lastStringStartingBoundary)); 62 | //Strip whitespace.. 63 | credentialsPart= credentialsPart.replace(/^\s+|\s+$/g,'') 64 | parameterPairs[parameterPairs.length]= credentialsPart; 65 | lastStringStartingBoundary= i+1; // skip the comma. 66 | } 67 | 68 | 69 | for(var key in parameterPairs) { 70 | var pair= /^([^=]+)?=(.+)/.exec(parameterPairs[key]) 71 | 72 | //de-code quotes and un-escape inter-stitial quotes if appropriate 73 | // I'm lost as to the correct behaviour of this bit tbh, the rfcs don't seem to be specifc 74 | // around whether quoted strings need to quote the quotes or not!! (that I can find anyway :) ) 75 | var value= pair[2].replace(/^"|"$/g, '') 76 | value= value.replace(/\\"/g, '"') 77 | value= unescape(value) 78 | 79 | results[pair[1]]= value 80 | } 81 | 82 | return results; 83 | } -------------------------------------------------------------------------------- /lib/auth.strategies/anonymous.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | module.exports= function(options) { 6 | options= options || {}; 7 | var that= {}; 8 | var my= {}; 9 | 10 | my.anonUser = options.anonymousUser || {username: "anonymous"} 11 | that.name = options.name || "anon"; 12 | 13 | that.authenticate= function(request, response, callback) { 14 | this.success( my.anonUser, callback ); 15 | } 16 | 17 | return that; 18 | }; -------------------------------------------------------------------------------- /lib/auth.strategies/bitbucket.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Fabian Jakobs 3 | * MIT Licensed 4 | */ 5 | var OAuth= require("oauth").OAuth, 6 | url = require("url"), 7 | http = require('http'); 8 | 9 | module.exports= function(options, server) { 10 | options= options || {} 11 | var that= {}; 12 | var my= {}; 13 | 14 | // Give the strategy a name 15 | that.name = options.name || "bitbucket"; 16 | 17 | // Build the authentication routes required 18 | that.setupRoutes= function(app) { 19 | app.use('/auth/bitbucket_callback', function(req, res){ 20 | req.authenticate([that.name], function(error, authenticated) { 21 | res.writeHead(303, { 'Location': req.session.bitbucket_redirect_url }); 22 | res.end(''); 23 | }); 24 | }); 25 | } 26 | 27 | // Construct the internal OAuth client 28 | my._oAuth= new OAuth( 29 | "https://bitbucket.org/api/1.0/oauth/request_token/", 30 | "https://bitbucket.org/api/1.0/oauth/access_token/", 31 | options.consumerKey, 32 | options.consumerSecret, 33 | options.version || "1.0", 34 | options.callback, 35 | 'HMAC-SHA1' 36 | ); 37 | 38 | // Declare the method that actually does the authentication 39 | that.authenticate = function(request, response, callback) { 40 | var parsedUrl = url.parse(request.originalUrl, true); 41 | //todo: makw the call timeout .... 42 | var self = this; 43 | if (parsedUrl.query && 44 | parsedUrl.query.oauth_token && 45 | parsedUrl.query.oauth_verifier && 46 | request.session.auth["bitbucket_oauth_token_secret"] && 47 | parsedUrl.query.oauth_token == request.session.auth["bitbucket_oauth_token"]) { 48 | self.trace( 'Phase 2/2 : Requesting an OAuth access token.' ); 49 | my._oAuth.getOAuthAccessToken(parsedUrl.query.oauth_token, 50 | request.session.auth["bitbucket_oauth_token_secret"], 51 | parsedUrl.query.oauth_verifier, 52 | function(error, oauth_token, oauth_token_secret, additionalParameters) { 53 | if (error) { 54 | callback(error); 55 | } 56 | else { 57 | request.session.auth["bitbucket_oauth_token_secret"] = oauth_token_secret; 58 | request.session.auth["bitbucket_oauth_token"] = oauth_token; 59 | request.session.auth["bitbucket_oauth_app_id"] = options.appId; 60 | request.session.auth["bitbucket_oauth_app_secret"] = options.appSecret; 61 | 62 | my._oAuth.getProtectedResource("https://bitbucket.org/api/1.0/user", "GET", oauth_token, oauth_token_secret, function (error, data, response) { 63 | if( error ) { 64 | self.fail(callback); 65 | }else { 66 | self.success(JSON.parse(data).user, callback); 67 | } 68 | }); 69 | } 70 | }); 71 | } 72 | else { 73 | self.trace( 'Phase 1/2 - Requesting an OAuth Request Token' ) 74 | my._oAuth.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, results) { 75 | if (error) { 76 | self.trace( 'Error retrieving the OAuth Request Token: ' + JSON.stringify(error) ); 77 | callback(null); // Ignore the error upstream, treat as validation failure. 78 | } else { 79 | request.session['bitbucket_redirect_url'] = request.originalUrl; 80 | request.session.auth["bitbucket_oauth_token_secret"] = oauth_token_secret; 81 | request.session.auth["bitbucket_oauth_token"] = oauth_token; 82 | 83 | self.redirect(response, 84 | my._oAuth.signUrl("https://bitbucket.org/api/1.0/oauth/authenticate/", oauth_token, oauth_token_secret, "GET"), 85 | callback); 86 | } 87 | }); 88 | } 89 | }; 90 | return that; 91 | }; -------------------------------------------------------------------------------- /lib/auth.strategies/facebook.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var OAuth= require("oauth").OAuth2, 6 | url = require("url"), 7 | http = require('http'); 8 | 9 | module.exports= function(options, server) { 10 | options= options || {} 11 | var that= {}; 12 | var my= {}; 13 | 14 | // Construct the internal OAuth client 15 | my._oAuth= new OAuth(options.appId, options.appSecret, "https://graph.facebook.com"); 16 | my._redirectUri= options.callback; 17 | my.scope= options.scope || ""; 18 | my.display =options.display || "page"; 19 | 20 | // Give the strategy a name 21 | that.name = options.name || "facebook"; 22 | 23 | // Build the authentication routes required 24 | that.setupRoutes= function(app) { 25 | app.use('/auth/facebook_callback', function(req, res){ 26 | req.authenticate([that.name], function(error, authenticated) { 27 | res.writeHead(303, { 'Location': req.session.facebook_redirect_url }); 28 | res.end(''); 29 | }); 30 | }); 31 | } 32 | 33 | // Declare the method that actually does the authentication 34 | that.authenticate= function(request, response, callback) { 35 | //todo: makw the call timeout .... 36 | var parsedUrl= url.parse(request.originalUrl, true); 37 | var self= this; 38 | this._facebook_fail= function(callback) { 39 | request.getAuthDetails()['facebook_login_attempt_failed'] = true; 40 | this.fail(callback); 41 | } 42 | 43 | if( request.getAuthDetails()['facebook_login_attempt_failed'] === true ) { 44 | // Because we bounce through authentication calls across multiple requests 45 | // we use this to keep track of the fact we *Really* have failed to authenticate 46 | // so that we don't keep re-trying to authenticate forever. 47 | delete request.getAuthDetails()['facebook_login_attempt_failed']; 48 | self.fail( callback ); 49 | } 50 | else { 51 | if( parsedUrl.query && ( parsedUrl.query.code || parsedUrl.query.error_reason === 'user_denied' ) ) { 52 | if( parsedUrl.query.error_reason == 'user_denied' ) { 53 | self._facebook_fail(callback); 54 | } else { 55 | my._oAuth.getOAuthAccessToken(parsedUrl.query && parsedUrl.query.code , 56 | {redirect_uri: my._redirectUri}, function( error, access_token, refresh_token ){ 57 | if( error ) callback(error) 58 | else { 59 | request.session["access_token"]= access_token; 60 | if( refresh_token ) request.session["refresh_token"]= refresh_token; 61 | my._oAuth.getProtectedResource("https://graph.facebook.com/me", request.session["access_token"], function (error, data, response) { 62 | if( error ) { 63 | self._facebook_fail(callback); 64 | }else { 65 | self.success(JSON.parse(data), callback) 66 | } 67 | }) 68 | } 69 | }); 70 | } 71 | } 72 | else { 73 | request.session['facebook_redirect_url']= request.originalUrl; 74 | var redirectUrl= my._oAuth.getAuthorizeUrl({redirect_uri : my._redirectUri, scope: my.scope, display:my.display}) 75 | self.redirect(response, redirectUrl, callback); 76 | } 77 | } 78 | } 79 | return that; 80 | }; 81 | -------------------------------------------------------------------------------- /lib/auth.strategies/foursquare.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * Derived from work by 'Stunti' 4 | * MIT Licensed 5 | */ 6 | var OAuth= require("oauth").OAuth2, 7 | url = require("url"), 8 | connect = require("connect"), 9 | http = require('http'); 10 | 11 | Facebook= module.exports= function(options, server) { 12 | options= options || {} 13 | var that= {}; 14 | var my= {}; 15 | 16 | // Construct the internal OAuth client 17 | my._oAuth= new OAuth(options.appId, options.appSecret, "https://foursquare.com", "/oauth2/authenticate", "/oauth2/access_token"); 18 | my._oAuth.setAccessTokenName("oauth_token"); 19 | my._redirectUri= options.callback; 20 | 21 | // Give the strategy a name 22 | that.name = options.name || "foursquare"; 23 | 24 | // Build the authentication routes required 25 | that.setupRoutes= function( app ) { 26 | app.use('/auth/foursquare_callback', function(req, res){ 27 | req.authenticate([that.name], function(error, authenticated) { 28 | res.writeHead(303, { 'Location': req.session.foursquare_redirect_url }); 29 | res.end(''); 30 | }); 31 | }); 32 | } 33 | 34 | // Declare the method that actually does the authentication 35 | that.authenticate= function(request, response, callback) { 36 | //todo: makw the call timeout .... 37 | var parsedUrl= url.parse(request.originalUrl, true); 38 | var self= this; 39 | this._foursquare_fail= function(callback) { 40 | request.getAuthDetails()['foursquare_login_attempt_failed']= true; 41 | this.fail(callback); 42 | } 43 | if( request.getAuthDetails()['foursquare_login_attempt_failed'] === true ) { 44 | // Because we bounce through authentication calls across multiple requests 45 | // we use this to keep track of the fact we *Really* have failed to authenticate 46 | // so that we don't keep re-trying to authenticate forever. 47 | delete request.getAuthDetails()['foursquare_login_attempt_failed']; 48 | self.fail( callback ); 49 | } 50 | else { 51 | if( parsedUrl.query && ( parsedUrl.query.code || parsedUrl.query.error === 'access_denied' ) ) { 52 | if( parsedUrl.query.error == 'access_denied' ) { 53 | self._foursquare_fail(callback); 54 | } else { 55 | my._oAuth.getOAuthAccessToken(parsedUrl.query && parsedUrl.query.code , 56 | {redirect_uri: my._redirectUri,grant_type : "authorization_code"}, 57 | function( error, access_token, refresh_token ){ 58 | if( error ) callback(error) 59 | else { 60 | request.session["access_token"]= access_token; 61 | if( refresh_token ) request.session["refresh_token"]= refresh_token; 62 | my._oAuth.getProtectedResource("https://api.foursquare.com/v2/users/self", request.session["access_token"], function (error, data, response) { 63 | if( error ) { 64 | self._foursquare_fail(callback); 65 | }else { 66 | self.success(JSON.parse(data), callback) 67 | } 68 | }) 69 | } 70 | }); 71 | } 72 | } 73 | else { 74 | request.session['foursquare_redirect_url']= request.originalUrl; 75 | var redirectUrl= my._oAuth.getAuthorizeUrl({redirect_uri : my._redirectUri, response_type: "code" }) 76 | self.redirect(response, redirectUrl, callback); 77 | } 78 | } 79 | } 80 | return that; 81 | }; -------------------------------------------------------------------------------- /lib/auth.strategies/getglue.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2011 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var OAuth= require("oauth").OAuth, 6 | url = require("url"), 7 | http = require('http'), 8 | querystring= require('querystring'); 9 | 10 | module.exports= function(options, server) { 11 | options= options || {} 12 | var that= {}; 13 | var my= {}; 14 | 15 | // Give the strategy a name 16 | that.name = options.name || "getglue"; 17 | 18 | // Build the authentication routes required 19 | that.setupRoutes= function( app ) { 20 | app.use('/auth/getglue_callback', function(req, res){ 21 | req.authenticate([that.name], function(error, authenticated) { 22 | res.writeHead(303, { 'Location': req.session.getglue_redirect_url }); 23 | res.end(''); 24 | delete req.session['getglue_redirect_url']; 25 | }); 26 | }); 27 | } 28 | my._authorizeUrl= "http://getglue.com/oauth/authorize" 29 | if( options.callback ) { 30 | my._authorizeUrl= my._authorizeUrl+ "?" + querystring.stringify({oauth_callback: options.callback}) 31 | } 32 | 33 | // Construct the internal OAuth client 34 | my._oAuth= new OAuth( 35 | "http://api.getglue.com/oauth/request_token", 36 | "http://api.getglue.com/oauth/access_token", 37 | options.appId, 38 | options.appSecret, 39 | "1.0", 40 | undefined, 41 | 'HMAC-SHA1' 42 | ); 43 | 44 | // Declare the method that actually does the authentication 45 | that.authenticate = function(request, response, callback) { 46 | //todo: if multiple connect middlewares were doing this, it would be more efficient to do it in the stack?? 47 | var parsedUrl = url.parse(request.originalUrl, true); 48 | //todo: make the call timeout .... 49 | var self = this; 50 | 51 | if( request.getAuthDetails()['getglue_login_attempt_failed'] === true ) { 52 | self.trace( 'GetGlue login attempt failed.' ); 53 | // Because we bounce through authentication calls across multiple requests 54 | // we use this to keep track of the fact we *Really* have failed to authenticate 55 | // so that we don't keep re-trying to authenticate forever. 56 | delete request.session.auth["getglue_oauth_token"]; 57 | delete request.session.auth["getglue_oauth_token_secret"]; 58 | delete request.getAuthDetails()['getglue_login_attempt_failed']; 59 | self.fail( callback ); 60 | } 61 | else { 62 | 63 | // SCope broken here (not using one) 64 | if ( request.session.auth["getglue_oauth_token_secret"] ) { 65 | self.trace( 'Phase 2/2 : Requesting an OAuth access token.' ); 66 | // Convert a request token to an access token. 67 | my._oAuth.getOAuthAccessToken(request.session.auth["getglue_oauth_token"], 68 | request.session.auth["getglue_oauth_token_secret"], 69 | function(error, oauth_token, oauth_token_secret, additionalParameters) { 70 | if (error) { 71 | self.trace( 'Error retrieving the OAuth Access Token: ' + JSON.stringify(error) ); 72 | delete request.session.auth["getglue_oauth_token"]; 73 | delete request.session.auth["getglue_oauth_token_secret"]; 74 | request.getAuthDetails()['getglue_login_attempt_failed'] = true; 75 | self.fail(callback) 76 | } 77 | else { 78 | request.session.auth["getglue_oauth_token"] = oauth_token; 79 | request.session.auth["getglue_oauth_token_secret"] = oauth_token_secret; 80 | self.success({glue_userId: additionalParameters.glue_userId}, callback); 81 | } 82 | }); 83 | } 84 | else { 85 | self.trace( 'Phase 1/2 - Requesting an OAuth Request Token' ) 86 | my._oAuth.getOAuthRequestToken( function(error, oauth_token, oauth_token_secret, results) { 87 | if (error) { 88 | self.trace( 'Error retrieving the OAuth Request Token: ' + JSON.stringify(error) ); 89 | callback(null); // Ignore the error upstream, treat as validation failure. 90 | } else { 91 | request.session.auth["getglue_oauth_token"] = oauth_token; 92 | request.session.auth["getglue_oauth_token_secret"] = oauth_token_secret; 93 | request.session['getglue_redirect_url'] = request.originalUrl; 94 | self.redirect(response, my._oAuth.signUrl(my._authorizeUrl, oauth_token, oauth_token_secret, "GET"), callback); 95 | } 96 | }); 97 | } 98 | } 99 | }; 100 | return that; 101 | }; -------------------------------------------------------------------------------- /lib/auth.strategies/github.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var OAuth= require("oauth").OAuth2, 6 | url = require("url"), 7 | http = require('http'); 8 | 9 | module.exports= function(options, server) { 10 | options= options || {} 11 | var that= {}; 12 | var my= {}; 13 | 14 | // Give the strategy a name 15 | that.name = options.name || "github"; 16 | 17 | // Build the authentication routes required 18 | that.setupRoutes= function( app ) { 19 | app.use('/auth/github_callback', function(req, res){ 20 | req.authenticate([that.name], function(error, authenticated) { 21 | res.writeHead(303, { 'Location': req.session.github_redirect_url }); 22 | res.end(''); 23 | }); 24 | }); 25 | } 26 | 27 | // Construct the internal OAuth client 28 | my._oAuth= new OAuth(options.appId, 29 | options.appSecret, 30 | "https://github.com/", 31 | "login/oauth/authorize", 32 | "login/oauth/access_token"); 33 | my._redirectUri= options.callback; 34 | my.scope= options.scope || ""; 35 | 36 | // Declare the method that actually does the authentication 37 | that.authenticate= function(request, response, callback) { 38 | //todo: makw the call timeout .... 39 | var parsedUrl= url.parse(request.originalUrl, true); 40 | var self= this; 41 | if( request.getAuthDetails()['github_login_attempt_failed'] === true ) { 42 | // Because we bounce through authentication calls across multiple requests 43 | // we use this to keep track of the fact we *Really* have failed to authenticate 44 | // so that we don't keep re-trying to authenticate forever. 45 | // (To clarify this infinite retry that we're stopping here would only 46 | // occur when the attempt has failed, not when it has succeeded!!!) 47 | delete request.getAuthDetails()['github_login_attempt_failed']; 48 | self.fail( callback ); 49 | } 50 | else { 51 | if( parsedUrl.query && parsedUrl.query.code ) { 52 | my._oAuth.getOAuthAccessToken(parsedUrl.query.code, 53 | {redirect_uri: my._redirectUri}, function( error, access_token, refresh_token ){ 54 | if( error ) callback(error) 55 | else { 56 | request.session["access_token"]= access_token; 57 | if( refresh_token ) request.session["refresh_token"]= refresh_token; 58 | my._oAuth.getProtectedResource("https://api.github.com/user", request.session["access_token"], function (error, data, response) { 59 | if( error ) { 60 | request.getAuthDetails()['github_login_attempt_failed'] = true; 61 | self.fail(callback); 62 | }else { 63 | self.success(JSON.parse(data), callback) 64 | } 65 | }) 66 | } 67 | }); 68 | } 69 | else if( parsedUrl.query && parsedUrl.query.error ) { 70 | request.getAuthDetails()['github_login_attempt_failed'] = true; 71 | self.fail(callback); 72 | } 73 | else { 74 | request.session['github_redirect_url']= request.originalUrl; 75 | var redirectUrl= my._oAuth.getAuthorizeUrl({redirect_uri : my._redirectUri, scope: my.scope }) 76 | self.redirect(response, redirectUrl, callback); 77 | } 78 | } 79 | } 80 | return that; 81 | }; -------------------------------------------------------------------------------- /lib/auth.strategies/google.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var OAuth= require("oauth").OAuth, 6 | url = require("url"), 7 | connect = require("connect"), 8 | http = require('http'); 9 | 10 | module.exports = function(options, server) { 11 | options = options || {}; 12 | var that = {}; 13 | var my = {}; 14 | 15 | // Construct the internal OAuth client 16 | my._oAuth = new OAuth("https://www.google.com/accounts/OAuthGetRequestToken", 17 | "https://www.google.com/accounts/OAuthGetAccessToken", 18 | options.consumerKey, options.consumerSecret, 19 | "1.0", options.callback, "HMAC-SHA1"); 20 | 21 | // Give the strategy a name 22 | that.name = options.name || "google"; 23 | 24 | // Build the authentication routes required 25 | that.setupRoutes = function( app ) { 26 | app.use('/auth/google_callback', function(req, res){ 27 | req.authenticate([that.name], function(error, authenticated) { 28 | res.writeHead(303, { 'Location': req.session.google_redirect_url }); 29 | res.end(''); 30 | }); 31 | }); 32 | }; 33 | 34 | // Declare the method that actually does the authentication 35 | that.authenticate = function(request, response, callback) { 36 | //todo: if multiple connect middlewares were doing this, it would be more efficient to do it in the stack?? 37 | var parsedUrl = url.parse(request.originalUrl, true); 38 | //todo: makw the call timeout .... 39 | var self= this; 40 | if( request.getAuthDetails()['google_login_attempt_failed'] === true ) { 41 | // Because we bounce through authentication calls across multiple requests 42 | // we use this to keep track of the fact we *Really* have failed to authenticate 43 | // so that we don't keep re-trying to authenticate forever. 44 | // (To clarify this infinite retry that we're stopping here would only 45 | // occur when the attempt has failed, not when it has succeeded!!!) 46 | delete request.getAuthDetails()['google_login_attempt_failed']; 47 | self.fail( callback ); 48 | } 49 | else { 50 | // get oauth access token 51 | if( parsedUrl.query && 52 | parsedUrl.query.oauth_token && 53 | parsedUrl.query.oauth_verifier && 54 | request.session.auth["google_oauth_token_secret"] && 55 | parsedUrl.query.oauth_token == request.session.auth['google_oauth_token'] ) { 56 | 57 | my._oAuth.getOAuthAccessToken( 58 | parsedUrl.query.oauth_token, 59 | request.session.auth["google_oauth_token_secret"], 60 | parsedUrl.query.oauth_verifier, 61 | function(error, oauth_token, oauth_token_secret, additionalParameters) { 62 | if( error ) { 63 | request.getAuthDetails()['google_login_attempt_failed'] = true; 64 | self.fail(callback); 65 | } 66 | else { 67 | my._oAuth.get("https://www.google.com/m8/feeds/contacts/default/full/0?alt=json", oauth_token, oauth_token_secret, function(error, data){ 68 | if( error ) callback(null); 69 | else { 70 | var profile = { 'username': JSON.parse(data).entry.id.$t }; 71 | request.session.auth["google_oauth_token_secret"] = oauth_token_secret; 72 | request.session.auth["google_oauth_token"] = oauth_token; 73 | self.success(profile, callback); 74 | } 75 | }); 76 | } 77 | }); 78 | } 79 | // get oauth request token 80 | else { 81 | var scope = "https://www.google.com/m8/feeds/ " + options.scope; 82 | my._oAuth.getOAuthRequestToken( 83 | { "scope": scope }, 84 | function(error, oauth_token, oauth_token_secret, oauth_authorize_url, additionalParameters ) { 85 | if(error) { 86 | callback(null); // Ignore the error upstream, treat as validation failure. 87 | } else { 88 | request.session['google_redirect_url'] = request.originalUrl; 89 | request.session.auth["google_oauth_token_secret"] = oauth_token_secret; 90 | request.session.auth["google_oauth_token"] = oauth_token; 91 | var authTokenURL = "https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token=" + oauth_token; 92 | if (options.hd) authTokenURL += '&hd=' + options.hd; 93 | self.redirect(response, authTokenURL, callback); 94 | } 95 | }); 96 | } 97 | } 98 | }; 99 | return that; 100 | }; 101 | -------------------------------------------------------------------------------- /lib/auth.strategies/google2.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var OAuth= require("oauth").OAuth2, 6 | url = require("url"), 7 | http = require('http'); 8 | 9 | module.exports= function(options, server) { 10 | options= options || {} 11 | var that= {}; 12 | var my= {}; 13 | 14 | // Construct the internal OAuth client 15 | my._oAuth= new OAuth(options.appId, options.appSecret, "", "https://accounts.google.com/o/oauth2/auth", "https://accounts.google.com/o/oauth2/token"); 16 | my._redirectUri= options.callback; 17 | my.scope= options.scope || "https://www.googleapis.com/auth/userinfo.profile"; 18 | my.accessType = options.accessType || null; 19 | my.forceApproval = options.forceApproval || false; 20 | 21 | // Ensure we have the correct scopes to match what the consumer really wants. 22 | if( options.requestEmailPermission === true && my.scope.indexOf("auth/userinfo.email") == -1 ) { 23 | my.scope+= " https://www.googleapis.com/auth/userinfo.email"; 24 | } 25 | if( my.scope.indexOf("auth/userinfo.profile") == -1 ) { 26 | my.scope+= " https://www.googleapis.com/auth/userinfo.profile"; 27 | } 28 | 29 | // Give the strategy a name 30 | that.name = options.name || "google2"; 31 | 32 | // Build the authentication routes required 33 | that.setupRoutes= function( app ) { 34 | app.use('/oauth2callback', function(req, res){ 35 | req.authenticate([that.name], function(error, authenticated) { 36 | res.writeHead(303, { 'Location': req.session.google2_redirect_url }); 37 | res.end(''); 38 | }); 39 | }); 40 | } 41 | 42 | // Declare the method that actually does the authentication 43 | that.authenticate= function(request, response, callback) { 44 | //todo: makw the call timeout .... 45 | var parsedUrl= url.parse(request.originalUrl, true); 46 | var self= this; 47 | this._google2_fail= function(callback) { 48 | request.getAuthDetails()['google2_login_attempt_failed'] = true; 49 | this.fail(callback); 50 | } 51 | 52 | if( request.getAuthDetails()['google2_login_attempt_failed'] === true ) { 53 | // Because we bounce through authentication calls across multiple requests 54 | // we use this to keep track of the fact we *Really* have failed to authenticate 55 | // so that we don't keep re-trying to authenticate forever. 56 | delete request.getAuthDetails()['google2_login_attempt_failed']; 57 | self.fail( callback ); 58 | } 59 | else { 60 | if( parsedUrl.query && ( parsedUrl.query.code || parsedUrl.query.error === 'access_denied' ) ) { 61 | if( parsedUrl.query.error == 'access_denied' ) { 62 | self.trace( 'User denied OAuth Access' ); 63 | self._google2_fail(callback); 64 | } else { 65 | self.trace( 'Phase 2/2 : Requesting an OAuth access token.' ); 66 | my._oAuth.getOAuthAccessToken(parsedUrl.query.code , 67 | {redirect_uri: my._redirectUri, grant_type: 'authorization_code'}, function( error, access_token, refresh_token, results ){ 68 | if( error ) { 69 | self.trace( 'Error retrieving the OAuth Access Token: ' + error ); 70 | callback(error) 71 | } 72 | else { 73 | request.session["access_token"]= access_token; 74 | if( refresh_token ) request.session["refresh_token"]= refresh_token; 75 | if(!!results.expires_in) { 76 | // save the access token's expiration date. 77 | // useful for offline access, where we need to regenerate the access token 78 | // before it expires (using refresh_token). 79 | var now = +new Date(); 80 | request.session["access_token_expiry"] = now + (parseInt(results.expires_in, 10) * 1000); 81 | } 82 | my._oAuth.get("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", 83 | access_token, 84 | function(error, profileData){ 85 | if( error ) { 86 | self.trace( 'Error retrieving the profile data =>' + JSON.stringify(error) ); 87 | self._google2_fail(callback); 88 | } else { 89 | var profile= JSON.parse(profileData); 90 | self.success(profile, callback); 91 | } 92 | }); 93 | } 94 | }); 95 | } 96 | } 97 | else { 98 | self.trace( 'Phase 1/2 - Redirecting to Google Authorizing url' ) 99 | request.session['google2_redirect_url']= request.originalUrl; 100 | var urlParams = { 101 | redirect_uri : my._redirectUri, 102 | scope: my.scope, 103 | response_type: 'code' 104 | }; 105 | // support offline access as per https://developers.google.com/accounts/docs/OAuth2WebServer#offline 106 | if(my.accessType !== null) 107 | urlParams.access_type = my.accessType; // access_type=offline 108 | // force displaying the approval prompt to the user. In such a case, a refresh_token will be regenerated. 109 | if (my.forceApproval) 110 | urlParams.approval_prompt = 'force'; 111 | var redirectUrl= my._oAuth.getAuthorizeUrl(urlParams); 112 | self.redirect(response, redirectUrl, callback); 113 | } 114 | } 115 | } 116 | return that; 117 | }; 118 | -------------------------------------------------------------------------------- /lib/auth.strategies/http/base.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | module.exports= function (options) { 6 | var that = {}; 7 | var my = {}; 8 | my._isAutoRespond = (options.isAutoRespond == null ? true : options.isAutoRespond); 9 | 10 | that._badRequest = function (executionScope, req, res, callback, attributes) { 11 | var authHeader = this.getAuthenticateResponseHeader(executionScope, attributes); 12 | if (my._isAutoRespond) { 13 | res.writeHead(400, { 'Content-Type': 'text/plain', 14 | 'WWW-Authenticate': authHeader }); 15 | res.end('Bad Request'); 16 | } 17 | executionScope.executionResult.errorResponse = { code: 400, header: authHeader, attributes: attributes }; 18 | executionScope.halt(callback); 19 | }; 20 | that._unAuthenticated= function(executionScope, req, res, callback, attributes) { 21 | var authHeader = this.getAuthenticateResponseHeader(executionScope, attributes); 22 | if (my._isAutoRespond) { 23 | res.writeHead(401, { 'Content-Type': 'text/plain', 24 | 'WWW-Authenticate': authHeader 25 | }); 26 | res.end('Authorization Required'); 27 | } 28 | executionScope.executionResult.errorResponse = { code: 401, header: authHeader, attributes: attributes }; 29 | executionScope.halt(callback); 30 | }; 31 | return that; 32 | }; 33 | -------------------------------------------------------------------------------- /lib/auth.strategies/http/base64.js: -------------------------------------------------------------------------------- 1 | /* adapted by Jed Schmidt for use as a node.js module */ 2 | 3 | exports.encode = base64encode; 4 | exports.decode = base64decode; 5 | 6 | var base64EncodeChars, base64DecodeChars; 7 | 8 | this.chars = function( string ) { 9 | base64EncodeChars = string || "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 10 | base64DecodeChars = []; 11 | 12 | for ( var i = 128; i--; ) { 13 | if ( base64DecodeChars[ i ] === undefined ) 14 | base64DecodeChars[ i ] = -1; 15 | 16 | base64DecodeChars[ base64EncodeChars.charCodeAt( i ) ] = i; 17 | } 18 | 19 | return this; 20 | 21 | }; 22 | 23 | this.chars(); 24 | 25 | /* Copyright (C) 1999 Masanao Izumo 26 | * Version: 1.0 27 | * LastModified: Dec 25 1999 28 | * This library is free. You can redistribute it and/or modify it. 29 | */ 30 | 31 | function base64encode( str ) { 32 | var out, i, len; 33 | var c1, c2, c3; 34 | 35 | len = str.length; 36 | i = 0; 37 | out = ""; 38 | while(i < len) { 39 | c1 = str.charCodeAt(i++) & 0xff; 40 | if(i == len) 41 | { 42 | out += base64EncodeChars.charAt(c1 >> 2); 43 | out += base64EncodeChars.charAt((c1 & 0x3) << 4); 44 | out += "=="; 45 | break; 46 | } 47 | c2 = str.charCodeAt(i++); 48 | if(i == len) 49 | { 50 | out += base64EncodeChars.charAt(c1 >> 2); 51 | out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); 52 | out += base64EncodeChars.charAt((c2 & 0xF) << 2); 53 | out += "="; 54 | break; 55 | } 56 | c3 = str.charCodeAt(i++); 57 | out += base64EncodeChars.charAt(c1 >> 2); 58 | out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); 59 | out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)); 60 | out += base64EncodeChars.charAt(c3 & 0x3F); 61 | } 62 | return out; 63 | } 64 | 65 | function base64decode( str ) { 66 | var c1, c2, c3, c4; 67 | var i, len, out; 68 | 69 | len = str.length; 70 | i = 0; 71 | out = ""; 72 | while(i < len) { 73 | /* c1 */ 74 | do { 75 | c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; 76 | } while(i < len && c1 == -1); 77 | if(c1 == -1) 78 | break; 79 | 80 | /* c2 */ 81 | do { 82 | c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; 83 | } while(i < len && c2 == -1); 84 | if(c2 == -1) 85 | break; 86 | 87 | out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); 88 | 89 | /* c3 */ 90 | do { 91 | c3 = str.charCodeAt(i++) & 0xff; 92 | if(c3 == 61) 93 | return out; 94 | c3 = base64DecodeChars[c3]; 95 | } while(i < len && c3 == -1); 96 | if(c3 == -1) 97 | break; 98 | 99 | out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)); 100 | 101 | /* c4 */ 102 | do { 103 | c4 = str.charCodeAt(i++) & 0xff; 104 | if(c4 == 61) 105 | return out; 106 | c4 = base64DecodeChars[c4]; 107 | } while(i < len && c4 == -1); 108 | if(c4 == -1) 109 | break; 110 | out += String.fromCharCode(((c3 & 0x03) << 6) | c4); 111 | } 112 | return out; 113 | } -------------------------------------------------------------------------------- /lib/auth.strategies/http/basic.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | 6 | var Base= require("./base"); 7 | var Base64= require("./base64"); 8 | var basicMatchRegex = /^[Bb]asic\s([a-zA-z0-9=]+)/; 9 | 10 | module.exports= function (options) { 11 | options= options || {}; 12 | var that= Base(options); 13 | var my= {}; 14 | my._realm= options.realm || "test"; 15 | my._validatePassword= options.validatePassword; 16 | 17 | that.name = options.name || "basic"; 18 | 19 | that.authenticate= function(request, response, callback) { 20 | var self= this; 21 | var username,password; 22 | var authHeader= request.headers.authorization; 23 | if( authHeader ) this.trace( 'Authorization Header Received: ' + authHeader ); 24 | var credentials= basicMatchRegex.exec(authHeader); 25 | 26 | if( credentials && credentials[1] ) { 27 | var providedCredentials= Base64.decode(credentials[1]); 28 | var splitCredentials= providedCredentials.split(":"); 29 | username= splitCredentials[0]; 30 | password= splitCredentials[1]; 31 | 32 | my._validatePassword(username, password, function (custom) { 33 | var result = custom || { "username": username }; 34 | self.success(result, callback); 35 | }, function(error){ 36 | if (error) 37 | callback(error); 38 | else 39 | that._unAuthenticated(self, request, response, callback); 40 | }); 41 | 42 | } 43 | else { 44 | that._unAuthenticated(self, request, response, callback); 45 | } 46 | }; 47 | 48 | that.getAuthenticateResponseHeader= function( ) { 49 | return "Basic realm=" + my._realm; 50 | }; 51 | 52 | return that; 53 | }; -------------------------------------------------------------------------------- /lib/auth.strategies/http/digest.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var Base= require("./base") 6 | ,crypto= require('crypto') 7 | ,authutils= require('../_authutils'); 8 | 9 | var md5= function(str) { 10 | return crypto.createHash('md5').update(str).digest('hex'); 11 | }; 12 | 13 | module.exports= function (options) { 14 | options= options || {}; 15 | var that= Base(options); 16 | var my= {}; 17 | my._realm= options.realm || "secure"; 18 | my._getSharedSecretForUser= options.getSharedSecretForUser; 19 | 20 | that.name = options.name || "digest"; 21 | 22 | that.authenticate= function(req, res, callback) { 23 | var self= this; 24 | var authHeader= req.headers.authorization; 25 | if( authHeader ) this.trace( 'Authorization Header Received: ' + authHeader ); 26 | 27 | var isDigest= /^[D]igest\s.+"/.exec(authHeader); 28 | if(isDigest) { 29 | var credentials= authutils.splitAuthorizationHeader(authHeader); 30 | var method= req.method; 31 | var href= req.originalUrl; 32 | my._getSharedSecretForUser(credentials.username, function(error, password){ 33 | if(error) callback(error); 34 | else { 35 | var HA1= md5( credentials.username+":"+ my._realm + ":"+ password) ; 36 | var HA2= md5( method+ ":" + href ); 37 | var myResponse= md5(HA1 + ":"+ credentials.nonce + ":"+ HA2 ); 38 | 39 | if( myResponse == credentials.response ) { 40 | self.success({ username : credentials.username}, callback); 41 | } 42 | else { 43 | that._unAuthenticated(self, req, res, callback); 44 | } 45 | } 46 | }); 47 | } else { 48 | that._unAuthenticated(self, req, res, callback); 49 | } 50 | }; 51 | 52 | that.getAuthenticateResponseHeader= function( executionScope ) { 53 | return "Digest realm=\"" + my._realm.replace("\"","\\\"") + "\", nonce=\""+ authutils.getNonce(32)+"\""; 54 | }; 55 | 56 | return that; 57 | }; -------------------------------------------------------------------------------- /lib/auth.strategies/http/http.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | 6 | var Base= require("./base"); 7 | var Digest= require('./digest'); 8 | var Basic= require('./basic'); 9 | 10 | module.exports= function (options) { 11 | options= options || {} 12 | var that= Base(options); 13 | var my= {}; 14 | 15 | that.name = options.name || "http"; 16 | if( options.basicStrategy ) { 17 | my.basicStrategy= options.basicStrategy; 18 | } 19 | else if( options.useBasic !== false ) { 20 | my.basicStrategy= Basic(options); 21 | } 22 | 23 | if( options.digestStrategy ) { 24 | my.digestStrategy= options.digestStrategy; 25 | } 26 | else if( options.useDigest !== false ) { 27 | my.digestStrategy= Digest(options); 28 | } 29 | if( my.basicStrategy ) my.basicStrategy.embedded= true; 30 | if( my.digestStrategy ) my.digestStrategy.embedded= true; 31 | 32 | that.isValid = function() { 33 | return ( my.digestStrategy !== undefined || my.basicStrategy !== undefined ) 34 | }; 35 | 36 | that.getAuthenticateResponseHeader = function( executionScope ) { 37 | var challenges= ""; 38 | if( my.digestStrategy ) challenges+= my.digestStrategy.getAuthenticateResponseHeader( executionScope ); 39 | if( my.digestStrategy && my.basicStrategy ) challenges+= ", "; 40 | if( my.basicStrategy ) challenges+= my.basicStrategy.getAuthenticateResponseHeader( executionScope ); 41 | return challenges; 42 | } 43 | 44 | that.authenticate= function(req, res, callback) { 45 | var authHeader= req.headers.authorization; 46 | if( authHeader ) { 47 | if( authHeader.match(/^[Bb]asic.*/) && my.basicStrategy ) { 48 | this.trace( 'Selecting Basic Authentication' ); 49 | my.basicStrategy.authenticate.call(this, req, res, callback); 50 | } 51 | else if( authHeader.match(/^[Dd]igest.*/) && my.digestStrategy ) { 52 | this.trace( 'Selecting Digest Authentication' ); 53 | my.digestStrategy.authenticate.call(this, req, res, callback); 54 | } 55 | else { 56 | this.trace( 'Bad Http Request' ); 57 | that._badRequest( this, req, res, callback ); 58 | } 59 | } 60 | else { 61 | this.trace( 'Un-Authenticated' ); 62 | that._unAuthenticated( this, req, res, callback ); 63 | } 64 | } 65 | 66 | return that; 67 | }; -------------------------------------------------------------------------------- /lib/auth.strategies/janrain.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var OAuth= require("oauth").OAuth2, 6 | https = require('https'); 7 | 8 | /* 9 | * Provides basic support for Janrain / RPX SSO 10 | * Would work best when using a dedicated authentication-app page 11 | * 12 | * Please note this strategy requires there to be a bodyDecoder module 13 | * in the connect stack prior to it. 14 | */ 15 | module.exports= function(options, server) { 16 | options= options || {} 17 | var that= {}; 18 | var my= {}; 19 | that.name = options.name || "janrain"; 20 | 21 | // Todo: connect-auth should really have a global auth failure app associated with it. 22 | my.failedLoginPath= options.failedLoginPath || '/'; 23 | my.appDomain= options.appDomain; 24 | my.callback= options.callback; 25 | my.signInUrl= "https://"+ my.appDomain+".rpxnow.com/openid/v2/signin?token_url="+ escape(my.callback) 26 | my.apiKey= options.apiKey; 27 | 28 | // Build the authentication routes required 29 | that.setupRoutes= function( app ) { 30 | app.use( '/auth/janrain_callback', function(req,res) { 31 | if( req.method == 'GET' ) req.getAuthDetails().janrain_came_back_with_get= true; // If we get a GET to this url it suggests a login failure. 32 | req.authenticate([that.name], function(error, authenticated) { 33 | res.writeHead(303, { 'Location': req.getAuthDetails().janrain_redirect_url }); 34 | res.end(''); 35 | }) 36 | }); 37 | } 38 | // Declare the method that actually does the authentication 39 | that.authenticate= function(req, res, callback) { 40 | var self= this; 41 | 42 | this._janrain_fail= function() { 43 | req.getAuthDetails().janrain_login_attempt_failed= true; 44 | this.fail(callback); 45 | } 46 | if( req.getAuthDetails().janrain_login_attempt_failed === true ) { // Phase 3 [Fail scenario where an immediaet re-test occurs in the consumer code] 47 | delete req.getAuthDetails().janrain_login_attempt_failed; 48 | self.fail( callback ); 49 | } 50 | else if( req.getAuthDetails().janrain_came_back_with_get === true ) { // Phase 2 (Fail) 51 | delete req.getAuthDetails().janrain_came_back_with_get; 52 | self._janrain_fail( callback ); 53 | } 54 | else if( req.body && req.body.token ) { // Phase 2 (Succeed) 55 | var options = { 56 | host: 'rpxnow.com', 57 | port: 443, 58 | path:'/api/v2/auth_info?apiKey=' + my.apiKey + '&token=' + req.body.token, 59 | method: 'GET', 60 | headers: {'host' : 'rpxnow.com'} 61 | }; 62 | 63 | var request = https.request(options, function (response) { 64 | var result= ""; 65 | response.setEncoding('utf8'); 66 | response.addListener('data', function (chunk) { 67 | result += chunk; 68 | }); 69 | response.addListener('end', function () { 70 | if( response.statusCode != 200 ) { 71 | self._janrain_fail( callback ); 72 | } else { 73 | var data= JSON.parse(result); 74 | self.success(data.profile, callback) 75 | } 76 | }); 77 | }); 78 | request.end(); 79 | } 80 | else { // Phase 1 81 | req.getAuthDetails()['janrain_redirect_url']= req.originalUrl; 82 | self.redirect(res, my.signInUrl, callback); 83 | } 84 | } 85 | return that; 86 | }; -------------------------------------------------------------------------------- /lib/auth.strategies/linkedin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Stephen Belanger 3 | * MIT Licensed 4 | */ 5 | var OAuth = require("oauth").OAuth, 6 | url = require("url"), 7 | connect = require("connect"), 8 | http = require('http'); 9 | 10 | Linkedin = module.exports = function(options, server) { 11 | options = options || {} 12 | var that = {}; 13 | var my = {}; 14 | 15 | // Construct the internal OAuth client 16 | my._oAuth = new OAuth( 17 | "https://api.linkedin.com/uas/oauth/requestToken" 18 | , "https://api.linkedin.com/uas/oauth/accessToken" 19 | , options.consumerKey 20 | , options.consumerSecret 21 | , "1.0" 22 | , options.callback || null 23 | , "HMAC-SHA1" 24 | ); 25 | 26 | // Give the strategy a name 27 | that.name = options.name || "linkedin"; 28 | 29 | // Build the authentication routes required 30 | that.setupRoutes= function( app ) { 31 | app.use('/auth/linkedin_callback', function(req, res) { 32 | req.authenticate([that.name], function(error, authenticated) { 33 | res.writeHead(303, { 34 | 'Location': req.session.linkedin_redirect_url 35 | }); 36 | res.end(''); 37 | }); 38 | }); 39 | } 40 | 41 | // Declare the method that actually does the authentication 42 | that.authenticate = function(request, response, callback) { 43 | //todo: if multiple connect middlewares were doing this, it would be more efficient to do it in the stack?? 44 | var parsedUrl = url.parse(request.originalUrl, true); 45 | 46 | //todo: makw the call timeout .... 47 | var self = this; 48 | if (parsedUrl.query && parsedUrl.query.oauth_token && parsedUrl.query.oauth_verifier && request.session.auth["linkedin_oauth_token_secret"]) { 49 | self.trace( 'Phase 2/2 : Requesting an OAuth access token.' ); 50 | my._oAuth.getOAuthAccessToken(parsedUrl.query.oauth_token, request.session.auth["linkedin_oauth_token_secret"], parsedUrl.query.oauth_verifier, function(error, token, secret, params) { 51 | if (error) { 52 | callback(null); 53 | } else { 54 | request.session.auth["linkedin_oauth_token_secret"] = secret; 55 | request.session.auth["linkedin_oauth_token"] = token; 56 | 57 | // Get user profile data. 58 | my._oAuth.getProtectedResource("https://api.linkedin.com/v1/people/~?format=json", 'get', token, secret, function (error, data, response) { 59 | if (error) { 60 | self.fail(callback); 61 | } else { 62 | var result = JSON.parse(data); 63 | var user = { 64 | first_name: result.firstName 65 | , last_name: result.lastName 66 | , url: result.siteStandardProfileRequest.url 67 | , headline: result.headline 68 | }; 69 | self.executionResult.user = user; 70 | self.success(user, callback); 71 | } 72 | }) 73 | } 74 | }); 75 | } else { 76 | self.trace( 'Phase 1/2 - Requesting an OAuth Request Token' ) 77 | my._oAuth.getOAuthRequestToken(function(error, token, secret, auth_url, params) { 78 | if (error) { 79 | callback(null); // Ignore the error upstream, treat as validation failure. 80 | } else { 81 | request.session['linkedin_redirect_url'] = request.originalUrl; 82 | request.session.auth["linkedin_oauth_token_secret"] = secret; 83 | request.session.auth["linkedin_oauth_token"] = token; 84 | self.redirect(response, "https://api.linkedin.com/uas/oauth/authorize?oauth_token=" + token, callback); 85 | } 86 | }); 87 | } 88 | } 89 | return that; 90 | }; 91 | 92 | -------------------------------------------------------------------------------- /lib/auth.strategies/never.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | module.exports= function(options) { 6 | var that= {} 7 | options= options || {}; 8 | that.name= options.name || "never"; 9 | that.authenticate= function(request, response, callback) { 10 | this.fail(callback); 11 | } 12 | return that; 13 | }; 14 | -------------------------------------------------------------------------------- /lib/auth.strategies/oauth/_oauth_error.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Christian Amor Kvalheim 3 | * 4 | * MIT Licensed 5 | */ 6 | exports.OAuthBadRequestError = function(msg) { 7 | this.statusCode = 400; 8 | this.message = msg; 9 | } 10 | 11 | exports.OAuthUnauthorizedError = function(msg) { 12 | this.statusCode = 401; 13 | this.message = msg; 14 | } 15 | 16 | exports.OAuthProviderError = function(msg) { 17 | this.statusCode = 400; 18 | this.message = msg; 19 | } -------------------------------------------------------------------------------- /lib/auth.strategies/oauth/oauth.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | * 5 | * Provides an OAuth 1.0A authentication strategy 6 | */ 7 | var connect= require("connect") 8 | ,OAuthServices= require('./_oauthservices').OAuthServices; 9 | 10 | /** 11 | * 12 | * Initialize Oauth options. 13 | * 14 | * Options: 15 | * 16 | * - request_token_url 'web path for the request token url endpoint, default: /oauth/request_token' (get/post) 17 | * - authorize_url 'web path for the authorize form, default: /oauth/authorize' (get/post) 18 | * - access_token_url 'web path for the access token url endpoint, default: /oauth/access_token' (get/post) 19 | * 20 | * - authenticate_provider 'function to render a authentication form' 21 | * - authorize_provider 'function to handle the authorization of the user and application' 22 | * 23 | * - oauth_provider 'db instance providing needed authentication mechanisms' 24 | * - oauth_protocol 'the protocol (http or https) that this oauth provider is being served from' 25 | * - realm 'realm for WWW-Authenticate header, default: oauth' 26 | * 27 | * @param {hash} options 28 | * @api private 29 | **/ 30 | module.exports= function(options) { 31 | var that= {} 32 | options= options || {}; 33 | that.name= options.name || "oauth"; 34 | var my= {}; 35 | var legs = null; 36 | 37 | // Ensure we have default values and legal options 38 | my['request_token_url']= options['request_token_url'] || '/oauth/request_token'; 39 | my['authorize_url']= options['authorize_url'] || '/oauth/authorize'; 40 | my['access_token_url']= options['access_token_url'] || '/oauth/access_token'; 41 | my['oauth_protocol']= options['oauth_protocol'] || 'http'; 42 | my['realm']= options['realm'] || 'oauth'; 43 | my['realm']= my['realm'].replace("\"","\\\""); 44 | 45 | my['authenticate_provider']= options['authenticate_provider']; 46 | my['authorize_provider']= options['authorize_provider']; 47 | my['oauth_provider']= options['oauth_provider']; 48 | my['authorization_finished_provider']= options['authorization_finished_provider']; 49 | 50 | // If authorize handlers are null, we are using 2-legged auth 51 | // Oauth provider must be provided 52 | if(!my['oauth_provider'] ) throw Error("No OAuth provider provided"); 53 | // We must either receive all providers or none 54 | if (my['authenticate_provider'] == null && 55 | my['authorize_provider'] == null && 56 | my['authorization_finished_provider'] == null) { 57 | legs = 2; 58 | } 59 | else if (my['authenticate_provider'] != null && 60 | my['authorize_provider'] != null && 61 | my['authorization_finished_provider'] != null) { 62 | legs = 3; 63 | } 64 | else { 65 | throw Error("Either provide authenticate_provider, authorize_provider, and authorization_finished_provider or provide none of them"); 66 | } 67 | 68 | // Set up the OAuth provider and data source 69 | my['oauth_service'] = new OAuthServices(my['oauth_provider'], legs); 70 | 71 | that.authenticate= function(req, res, callback) { 72 | var self= this; 73 | my['oauth_service'].authorize(req, my['oauth_protocol'], function(error, result) { 74 | if( error ) { 75 | res.writeHead(error.statusCode, {'Content-Type': 'text/plain', 76 | 'WWW-Authenticate': 'OAuth realm="'+my.realm+'"'}) 77 | res.end(error.message); 78 | self.fail(callback); 79 | } 80 | else { 81 | self.success(result, callback); 82 | } 83 | }); 84 | } 85 | 86 | // If we are using 2-legged auth, we don't need anything to do with request tokens 87 | if (legs == 3) { 88 | var requestTokenMethod= function(req, res) { 89 | my['oauth_service'].requestToken(req, my['oauth_protocol'], function(error, result) { 90 | if( error ) { 91 | res.writeHead(error.statusCode, {'Content-Type': 'text/plain'}) 92 | res.end(error.message); 93 | } 94 | else { 95 | res.writeHead(200, {'Content-Type': 'text/plain'}) 96 | res.end(["oauth_token=" + result["token"], "oauth_token_secret=" + result["token_secret"], "oauth_callback_confirmed=" + result["oauth_callback_confirmed"]].join("&")); 97 | } 98 | }); 99 | } 100 | 101 | var accessTokenMethod= function(req, res) { 102 | my['oauth_service'].accessToken(req, my['oauth_protocol'], function(error, result) { 103 | if( error ) { 104 | res.writeHead(error.statusCode, {'Content-Type': 'text/plain'}) 105 | res.end(error.message); 106 | } 107 | else { 108 | res.writeHead(200, {'Content-Type': 'text/plain'}) 109 | res.end(["oauth_token=" + result["access_token"], "oauth_token_secret=" + result["token_secret"]].join("&")); 110 | } 111 | }); 112 | } 113 | 114 | var authorizeUrlMethod= function( req, res ) { 115 | if( req.method == 'GET' ) { 116 | // Should render the form that allows users to authenticate themselves 117 | my['authenticate_provider'](req, res); 118 | } 119 | else if( req.method == 'POST' ) { 120 | // Handles the post from the authentication form. 121 | var self = this; 122 | 123 | if(req.body['verifier'] == null) { 124 | my['oauth_service'].authenticateUser(req.body['username'], req.body['password'], req.body['oauth_token'], function(err, result) { 125 | if(err) { 126 | // Delegate to the function of the user 127 | my.authorize_provider.call(self, err, req, res, false, {token:req.body['oauth_token']}); 128 | } else { 129 | // Fetch the needed data 130 | my['oauth_service'].fetchAuthorizationInformation(req.body['username'], result.token, function(err, application, user) { 131 | if(err) { 132 | // Delegate to the function of the user 133 | my.authorize_provider.call(self, err, req, res, false, {token:req.body['oauth_token']}); 134 | } else { 135 | // Signal callback about finish authorization 136 | my.authorize_provider.call(self, null, req, res, true, result, application, user); 137 | } 138 | }); 139 | } 140 | }); 141 | } else { 142 | var oauth_token= req.body['oauth_token']; 143 | var verifier= req.body['verifier']; 144 | 145 | // Check if there is an entry for this token and verifier 146 | my['oauth_service'].verifyToken(oauth_token, verifier, function(err, result) { 147 | if(err) { 148 | // Delegate to the function of the user 149 | my.authorize_provider.call(self, err, req, res, false, {token:oauth_token}); 150 | } else { 151 | if(result.callback != null && result.callback != "oob") { 152 | var callback = result.callback; 153 | // Correctly add the tokens if the callback has a ? allready 154 | var redirect_url = callback.match(/\?/) != null ? "&oauth_token=" + result.token + "&oauth_verifier=" + result.verifier : "?oauth_token=" + result.token + "&oauth_verifier=" + result.verifier; 155 | // Signal that a redirect is in order after finished process 156 | res.writeHead(303, { 'Location': result.callback + redirect_url }); 157 | res.end(''); 158 | 159 | } else { 160 | my.authorization_finished_provider.call(self, err, req, res, result); 161 | } 162 | } 163 | }); 164 | } 165 | } 166 | else 167 | throw new Error("Unknown HTTP method "+ req.method ); 168 | } 169 | 170 | // Build the authentication routes required 171 | that.setupRoutes= function( app ) { 172 | app.use(my['request_token_url'], requestTokenMethod); 173 | app.use(my['access_token_url'], accessTokenMethod); 174 | app.use(my['authorize_url'], authorizeUrlMethod); 175 | } 176 | } 177 | return that; 178 | }; 179 | -------------------------------------------------------------------------------- /lib/auth.strategies/openid.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2011 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var openid = require('openid') 6 | , url = require('url'); 7 | 8 | module.exports= function(options) { 9 | options= options || {}; 10 | var that= {}; 11 | var my= {}; 12 | 13 | that.name = options.name || "openid"; 14 | that.callback = options.callback; 15 | that.realm = options.realm || null; 16 | that.stateless = options.statelessVerification || false; 17 | that.strictMode = options.strictMode || false; 18 | that.extensions = options.extensions || []; 19 | 20 | that.verifyPath = url.parse(that.callback, false).pathname; 21 | 22 | // Build the authentication routes required 23 | that.setupRoutes = function( app ) { 24 | app.use( that.verifyPath, function(req, res){ 25 | req.authenticate([that.name], function(error, authenticated) { 26 | res.writeHead(303, { 'Location': req.session.openid_redirect_url }); 27 | res.end(''); 28 | delete req.session['openid_redirect_url'] 29 | }); 30 | }); 31 | }; 32 | 33 | var relyingParty = new openid.RelyingParty( 34 | that.callback, // Verification URL (yours) 35 | that.realm, // Realm (optional, specifies realm for OpenID authentication) 36 | that.stateless, // Use stateless verification 37 | that.strictMode, // Strict mode 38 | that.extensions); // List of extensions to enable and include 39 | 40 | 41 | that.authenticate= function(request, response, callback) { 42 | 43 | var self= this; 44 | var parsedUrl= url.parse(request.originalUrl, true); 45 | if( request.getAuthDetails()['openid_login_attempt_failed'] === true ) { 46 | // Because we bounce through authentication calls across multiple requests 47 | // we use this to keep track of the fact we *Really* have failed to authenticate 48 | // so that we don't keep re-trying to authenticate forever. 49 | delete request.getAuthDetails()['openid_login_attempt_failed']; 50 | self.fail( callback ); 51 | } 52 | else if( parsedUrl.pathname == that.verifyPath ) { 53 | self.trace( 'Phase 2/2 - Verifying OpenId' ) 54 | var result = relyingParty.verifyAssertion(request.originalUrl, function( error, result ) { 55 | console.log( error) 56 | if( result && result.authenticated ) { 57 | var user= {user_id: result.claimedIdentifier }; 58 | self.success( user, callback ); 59 | } 60 | else { 61 | if( error ) self.trace( 'Error Verifying OpenId - ' + JSON.stringify(error) ) 62 | else self.trace( 'Verifying OpenId Failed - ' + JSON.stringify(result) ) 63 | request.getAuthDetails()['openid_login_attempt_failed'] = true; 64 | self.fail( callback ); 65 | } 66 | }); 67 | } 68 | else { 69 | // User supplied identifier 70 | var identifier = parsedUrl.query.openid_identifier; 71 | 72 | self.trace( 'Phase 1/2 - Authenticating for OpenId identifier: '+ identifier ); 73 | // Resolve identifier, associate, and build authentication URL 74 | relyingParty.authenticate(identifier, false, function(error, authUrl) { 75 | if (error) { 76 | self.trace( 'error - ' + JSON.stringify(error) ); 77 | self.fail( callback ); 78 | } 79 | else if (!authUrl) { 80 | self.fail( callback ); 81 | } 82 | else { 83 | delete request.getAuthDetails()['openid_login_attempt_failed']; 84 | request.session['openid_redirect_url']= request.originalUrl; 85 | self.redirect(response, authUrl, callback); 86 | } 87 | }); 88 | } 89 | }; 90 | return that; 91 | }; -------------------------------------------------------------------------------- /lib/auth.strategies/persona.js: -------------------------------------------------------------------------------- 1 | var https= require('https') 2 | , querystring= require('querystring'); 3 | /* 4 | * Copyright(c) 2010 Ciaran Jessup 5 | * MIT Licensed 6 | * 7 | * Verification routines for mozilla persona. 8 | */ 9 | module.exports= function(options) { 10 | var that= {} 11 | var my= {}; 12 | options= options || {}; 13 | that.name= options.name || "persona"; 14 | my.audience= options.audience || ""; 15 | that.authenticate= function(request, response, callback) { 16 | var self= this; 17 | if(!request.body || !request.body.assertion) { 18 | this.trace( "No persona Assertion supplied." ); 19 | this.fail(callback); 20 | } else { 21 | this.trace( "Verifying Assertion." ); 22 | if( my.audience == "" ) { 23 | this.trace( "No persona audience configured." ); 24 | this.fail(callback); 25 | } 26 | else { 27 | var vreq = https.request({host: "verifier.login.persona.org", path: "/verify", method: "POST"}, function(vres) { 28 | var body = ""; 29 | vres.on('data', function(chunk) { body+=chunk; } ) 30 | .on('end', function() { 31 | try { 32 | var verifierResp = JSON.parse(body); 33 | var valid = verifierResp && verifierResp.status === "okay"; 34 | var email = valid ? verifierResp.email : null; 35 | if (valid) { 36 | self.trace("assertion verified successfully for email:", email); 37 | self.success({"email":email}, callback); 38 | } else { 39 | self.trace("failed to verify assertion:", verifierResp.reason); 40 | self.fail() 41 | } 42 | } catch(e) { 43 | // bogus response from verifier! 44 | self.trace("non-JSON response from verifier: " + body); 45 | self.fail( callback ); 46 | } 47 | }); 48 | }); 49 | vreq.setHeader('Content-Type', 'application/x-www-form-urlencoded'); 50 | 51 | var data = querystring.stringify({ 52 | assertion: request.body.assertion, 53 | audience: my.audience 54 | }); 55 | 56 | vreq.setHeader('Content-Length', data.length); 57 | vreq.write(data); 58 | vreq.end(); 59 | } 60 | } 61 | } 62 | return that; 63 | }; 64 | -------------------------------------------------------------------------------- /lib/auth.strategies/sina.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Danny Siu 3 | */ 4 | var OAuth = require("oauth").OAuth, 5 | url = require("url"), 6 | connect = require("connect"), 7 | util = require("util"), 8 | http = require('http'); 9 | 10 | module.exports = function(options, server) { 11 | options = options || {}; 12 | var that = {}; 13 | var my = {}; 14 | 15 | // Construct the internal OAuth client 16 | my._oAuth = new OAuth("http://api.t.sina.com.cn/oauth/request_token", 17 | "http://api.t.sina.com.cn/oauth/access_token", 18 | options.consumerKey, options.consumerSecret, 19 | "1.0", options.callback, "HMAC-SHA1"); 20 | 21 | // Give the strategy a name 22 | that.name = options.name || "sina"; 23 | 24 | // Build the authentication routes required 25 | that.setupRoutes = function(server) { 26 | server.use('/', connect.router(function routes(app) { 27 | app.get('/auth/sina_callback', function(req, res) { 28 | req.authenticate([that.name], function(error, authenticated) { 29 | res.writeHead(303, { 'Location': req.session.sina_redirect_url }); 30 | res.end(''); 31 | }); 32 | }); 33 | })); 34 | }; 35 | 36 | 37 | // Declare the method that actually does the authentication 38 | that.authenticate = function(request, response, callback) { 39 | //todo: if multiple connect middlewares were doing this, it would be more efficient to do it in the stack?? 40 | 41 | var parsedUrl = url.parse(request.originalUrl, true); 42 | 43 | //todo: makw the call timeout .... 44 | var self = this; 45 | if (parsedUrl.query && 46 | parsedUrl.query.oauth_token && 47 | parsedUrl.query.oauth_verifier && 48 | request.session.auth["sina_oauth_token_secret"] && 49 | parsedUrl.query.oauth_token == request.session.auth["sina_oauth_token"]) { 50 | 51 | my._oAuth.getOAuthAccessToken(parsedUrl.query.oauth_token, 52 | request.session.auth["sina_oauth_token_secret"], 53 | parsedUrl.query.oauth_verifier, 54 | function(error, oauth_token, oauth_token_secret, additionalParameters) { 55 | if (error) { 56 | callback(null); 57 | } 58 | else { 59 | request.session.auth["sina_oauth_token_secret"] = oauth_token_secret; 60 | request.session.auth["sina_oauth_token"] = oauth_token; 61 | self.success(additionalParameters, callback); 62 | } 63 | }) 64 | } 65 | else { 66 | my._oAuth.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, results) { 67 | if (error) { 68 | callback(null); // Ignore the error upstream, treat as validation failure. 69 | } else { 70 | request.session['sina_redirect_url'] = request.originalUrl; 71 | request.session.auth["sina_oauth_token_secret"] = oauth_token_secret; 72 | request.session.auth["sina_oauth_token"] = oauth_token; 73 | 74 | self.redirect(response, 75 | "http://api.t.sina.com.cn/oauth/authorize?oauth_token=" + oauth_token 76 | + "&oauth_callback=" + encodeURIComponent(options.callback), 77 | callback); 78 | } 79 | }); 80 | } 81 | }; 82 | return that; 83 | }; -------------------------------------------------------------------------------- /lib/auth.strategies/skyrock.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Skyrock.com auth strategie 3 | * see http://www.skyrock.com/developer/ 4 | * MIT Licensed 5 | */ 6 | var OAuth= require("oauth").OAuth, 7 | url = require("url"), 8 | http = require('http'); 9 | 10 | Skyrock = module.exports= function(options, server) { 11 | options= options || {} 12 | var that= {}; 13 | var my= {}; 14 | 15 | // Construct the internal OAuth client 16 | my._oAuth = new OAuth( 17 | "https://api.skyrock.com/v2/oauth/initiate" 18 | , "https://api.skyrock.com/v2/oauth/token" 19 | , options.consumerKey 20 | , options.consumerSecret 21 | , "1.0" 22 | , options.callback || null 23 | , "HMAC-SHA1" 24 | ); 25 | 26 | // Give the strategy a name 27 | that.name = options.name || "skyrock"; 28 | 29 | // Build the authentication routes required 30 | that.setupRoutes= function(app) { 31 | app.use('/auth/skyrock_callback', function(req, res){ 32 | req.authenticate([that.name], function(error, authenticated) { 33 | res.writeHead(303, { 'Location': req.session.skyrock_redirect_url }); 34 | res.end(''); 35 | }); 36 | }); 37 | } 38 | 39 | // Declare the method that actually does the authentication 40 | that.authenticate= function(request, response, callback) { 41 | //todo: if multiple connect middlewares were doing this, it would be more efficient to do it in the stack?? 42 | var parsedUrl= url.parse(request.originalUrl, true); 43 | 44 | //todo: makw the call timeout .... 45 | var self= this; 46 | if( request.getAuthDetails()['skyrock_login_attempt_failed'] === true ) { 47 | // Because we bounce through authentication calls across multiple requests 48 | // we use this to keep track of the fact we *Really* have failed to authenticate 49 | // so that we don't keep re-trying to authenticate forever. 50 | delete request.getAuthDetails()['skyrock_login_attempt_failed']; 51 | self.fail( callback ); 52 | } else { 53 | if( parsedUrl.query && parsedUrl.query.denied ) { 54 | self.trace( 'User denied OAuth Access' ); 55 | request.getAuthDetails()['skyrock_login_attempt_failed'] = true; 56 | self.fail(callback); 57 | } else if( parsedUrl.query && parsedUrl.query.oauth_token && request.session.auth["skyrock_oauth_token_secret"] ) { 58 | self.trace( 'Phase 2/2 : Requesting an OAuth access token.' ); 59 | my._oAuth.getOAuthAccessToken(parsedUrl.query.oauth_token, request.session.auth["skyrock_oauth_token_secret"], parsedUrl.query.oauth_verifier, 60 | function( error, oauth_token, oauth_token_secret, additionalParameters ) { 61 | if( error ) { 62 | self.trace( 'Error retrieving the OAuth Access Token: ' + JSON.stringify(error) ); 63 | request.getAuthDetails()['skyrock_login_attempt_failed'] = true; 64 | self.fail(callback); 65 | } else { 66 | self.trace( 'Successfully retrieved the OAuth Access Token' ); 67 | request.session.auth["skyrock_oauth_token_secret"]= oauth_token_secret; 68 | request.session.auth["skyrock_oauth_token"]= oauth_token; 69 | 70 | // Get user profile data. 71 | my._oAuth.getProtectedResource("https://api.skyrock.com/v2/user/get.json", 'get', oauth_token, oauth_token_secret, function (error, data, response) { 72 | if (error) { 73 | self.fail(callback); 74 | } else { 75 | var result = JSON.parse(data); 76 | var user = { 77 | username: result.username 78 | , firstname: result.firstname 79 | , name: result.name 80 | , user_url: result.user_url 81 | }; 82 | self.executionResult.user = user; 83 | self.success(user, callback); 84 | } 85 | }) 86 | } 87 | }); 88 | } else { 89 | self.trace( 'Phase 1/2 - Requesting an OAuth Request Token' ) 90 | my._oAuth.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, oauth_authorize_url, additionalParameters ) { 91 | if(error) { 92 | self.trace( 'Error retrieving the OAuth Request Token: ' + JSON.stringify(error) ); 93 | callback(null); // Ignore the error upstream, treat as validation failure. 94 | } else { 95 | self.trace( 'Successfully retrieved the OAuth Request Token' ); 96 | request.session['skyrock_redirect_url']= request.originalUrl; 97 | request.session.auth["skyrock_oauth_token_secret"]= oauth_token_secret; 98 | request.session.auth["skyrock_oauth_token"]= oauth_token; 99 | self.redirect(response, "https://api.skyrock.com/v2/oauth/authenticate?oauth_token=" + oauth_token, callback); 100 | } 101 | }); 102 | } 103 | } 104 | } 105 | return that; 106 | }; 107 | -------------------------------------------------------------------------------- /lib/auth.strategies/twitter.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var OAuth= require("oauth").OAuth, 6 | url = require("url"), 7 | http = require('http'); 8 | 9 | module.exports= function(options, server) { 10 | options= options || {} 11 | var that= {}; 12 | var my= {}; 13 | 14 | // Construct the internal OAuth client 15 | my._oAuth= new OAuth("https://api.twitter.com/oauth/request_token", 16 | "https://api.twitter.com/oauth/access_token", 17 | options.consumerKey, options.consumerSecret, 18 | "1.0A", options.callback || null, "HMAC-SHA1"); 19 | 20 | // Give the strategy a name 21 | that.name = options.name || "twitter"; 22 | 23 | // Build the authentication routes required 24 | that.setupRoutes= function(app) { 25 | app.use('/auth/twitter_callback', function(req, res){ 26 | req.authenticate([that.name], function(error, authenticated) { 27 | res.writeHead(303, { 'Location': req.session.twitter_redirect_url }); 28 | res.end(''); 29 | }); 30 | }); 31 | } 32 | 33 | // Declare the method that actually does the authentication 34 | that.authenticate= function(request, response, callback) { 35 | //todo: if multiple connect middlewares were doing this, it would be more efficient to do it in the stack?? 36 | var parsedUrl= url.parse(request.originalUrl, true); 37 | 38 | //todo: makw the call timeout .... 39 | var self= this; 40 | if( request.getAuthDetails()['twitter_login_attempt_failed'] === true ) { 41 | // Because we bounce through authentication calls across multiple requests 42 | // we use this to keep track of the fact we *Really* have failed to authenticate 43 | // so that we don't keep re-trying to authenticate forever. 44 | delete request.getAuthDetails()['twitter_login_attempt_failed']; 45 | self.fail( callback ); 46 | } 47 | else { 48 | if( parsedUrl.query && parsedUrl.query.denied ) { 49 | self.trace( 'User denied OAuth Access' ); 50 | request.getAuthDetails()['twitter_login_attempt_failed'] = true; 51 | self.fail(callback); 52 | } 53 | else if( parsedUrl.query && parsedUrl.query.oauth_token && request.session.auth["twitter_oauth_token_secret"] ) { 54 | self.trace( 'Phase 2/2 : Requesting an OAuth access token.' ); 55 | my._oAuth.getOAuthAccessToken(parsedUrl.query.oauth_token, request.session.auth["twitter_oauth_token_secret"], parsedUrl.query.oauth_verifier, 56 | function( error, oauth_token, oauth_token_secret, additionalParameters ) { 57 | if( error ) { 58 | self.trace( 'Error retrieving the OAuth Access Token: ' + error ); 59 | request.getAuthDetails()['twitter_login_attempt_failed'] = true; 60 | self.fail(callback); 61 | } 62 | else { 63 | self.trace( 'Successfully retrieved the OAuth Access Token' ); 64 | request.session.auth["twitter_oauth_token_secret"]= oauth_token_secret; 65 | request.session.auth["twitter_oauth_token"]= oauth_token; 66 | var user= { user_id: additionalParameters.user_id, 67 | username: additionalParameters.screen_name } 68 | self.executionResult.user= user; 69 | self.success(user, callback) 70 | } 71 | }); 72 | } 73 | else { 74 | self.trace( 'Phase 1/2 - Requesting an OAuth Request Token' ) 75 | my._oAuth.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, oauth_authorize_url, additionalParameters ) { 76 | if(error) { 77 | self.trace( 'Error retrieving the OAuth Request Token: ' + JSON.stringify(error) ); 78 | callback(null); // Ignore the error upstream, treat as validation failure. 79 | } else { 80 | self.trace( 'Successfully retrieved the OAuth Request Token' ); 81 | request.session['twitter_redirect_url']= request.originalUrl; 82 | request.session.auth["twitter_oauth_token_secret"]= oauth_token_secret; 83 | request.session.auth["twitter_oauth_token"]= oauth_token; 84 | self.redirect(response, "http://twitter.com/oauth/authenticate?oauth_token=" + oauth_token, callback); 85 | } 86 | }); 87 | } 88 | } 89 | } 90 | return that; 91 | }; 92 | -------------------------------------------------------------------------------- /lib/auth.strategies/yahoo.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var OAuth= require("oauth").OAuth, 6 | url = require("url"), 7 | http = require('http'); 8 | 9 | module.exports= function(options, server) { 10 | options= options || {} 11 | var that= {}; 12 | var my= {}; 13 | 14 | // Construct the internal OAuth client 15 | my._oAuth= new OAuth( "https://api.login.yahoo.com/oauth/v2/get_request_token", 16 | "https://api.login.yahoo.com/oauth/v2/get_token", 17 | options.consumerKey, options.consumerSecret, 18 | "1.0", options.callback, "HMAC-SHA1"); 19 | 20 | // Give the strategy a name 21 | that.name = options.name || "yahoo"; 22 | 23 | // Build the authentication routes required 24 | that.setupRoutes= function( app ) { 25 | app.use('/auth/yahoo_callback', function(req, res){ 26 | req.authenticate(['yahoo'], function(error, authenticated) { 27 | res.writeHead(303, { 'Location': req.session.yahoo_redirect_url }); 28 | res.end(''); 29 | }); 30 | }); 31 | } 32 | 33 | // Declare the method that actually does the authentication 34 | that.authenticate= function(request, response, callback) { 35 | //todo: if multiple connect middlewares were doing this, it would be more efficient to do it in the stack?? 36 | var parsedUrl= url.parse(request.originalUrl, true); 37 | //todo: makw the call timeout .... 38 | var self= this; 39 | if( parsedUrl.query && parsedUrl.query.oauth_token 40 | && parsedUrl.query.oauth_verifier 41 | && request.session.auth["yahoo_oauth_token_secret"] 42 | && parsedUrl.query.oauth_token == request.session.auth["yahoo_oauth_token"] ) { 43 | self.trace( 'Phase 2/2 : Requesting an OAuth access token.' ); 44 | my._oAuth.getOAuthAccessToken(parsedUrl.query.oauth_token, request.session.auth["yahoo_oauth_token_secret"], parsedUrl.query.oauth_verifier, 45 | function( error, oauth_token, oauth_token_secret, additionalParameters ) { 46 | if( error ) { 47 | self.trace( 'Error retrieving the OAuth Access Token: ' + error ); 48 | callback(null); 49 | } 50 | else { 51 | self.trace( 'Successfully retrieved the OAuth Access Token' ); 52 | self.trace( 'Retrieving user details from yahooapis' ); 53 | my._oAuth.getProtectedResource("http://social.yahooapis.com/v1/user/" + additionalParameters.xoauth_yahoo_guid + "/profile?format=json", "GET", 54 | oauth_token, oauth_token_secret, 55 | function(error, data){ 56 | if( error ) { 57 | self.trace( 'Error retrieving user details from yahooapis: ' + error ); 58 | callback(null); 59 | } else { 60 | self.trace( 'User details retrieved' ); 61 | var profile= JSON.parse(data).profile; 62 | request.session.auth["yahoo_oauth_token_secret"]= oauth_token_secret; 63 | request.session.auth["yahoo_oauth_token"]= oauth_token; 64 | self.success(profile, callback) 65 | } 66 | }); 67 | } 68 | }); 69 | } 70 | else { 71 | self.trace( 'Phase 1/2 - Requesting an OAuth Request Token' ) 72 | my._oAuth.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, oauth_authorize_url, additionalParameters ) { 73 | if(error) { 74 | self.trace( 'Error retrieving the OAuth Request Token: ' + error ); 75 | callback(null); // Ignore the error upstream, treat as validation failure. 76 | } else { 77 | self.trace( 'Successfully retrieved the OAuth Request Token' ); 78 | request.session['yahoo_redirect_url']= request.originalUrl; 79 | request.session.auth["yahoo_oauth_token_secret"]= oauth_token_secret; 80 | request.session.auth["yahoo_oauth_token"]= oauth_token; 81 | self.redirect(response, "https://api.login.yahoo.com/oauth/v2/request_auth?oauth_token=" + oauth_token, callback); 82 | } 83 | }); 84 | } 85 | 86 | } 87 | return that; 88 | }; -------------------------------------------------------------------------------- /lib/auth.strategies/yammer.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Stephen Belanger 3 | * MIT Licensed 4 | */ 5 | var OAuth = require("oauth").OAuth, 6 | url = require("url"), 7 | connect = require("connect"), 8 | http = require('http'); 9 | 10 | yammer = module.exports = function(options, server) { 11 | options = options || {} 12 | var that = {}; 13 | var my = {}; 14 | 15 | // Construct the internal OAuth client 16 | my._oAuth = new OAuth( 17 | "https://www.yammer.com/oauth/request_token" 18 | , "https://www.yammer.com/oauth/access_token" 19 | , options.consumerKey 20 | , options.consumerSecret 21 | , "1.0" 22 | , options.callback || null 23 | , "HMAC-SHA1" 24 | ); 25 | 26 | // Give the strategy a name 27 | that.name = options.name || "yammer"; 28 | 29 | // Build the authentication routes required 30 | that.setupRoutes= function( app ) { 31 | app.use('/auth/yammer_callback', function(req, res) { 32 | req.authenticate([that.name], function(error, authenticated) { 33 | res.writeHead(303, { 34 | 'Location': req.session.yammer_redirect_url 35 | }); 36 | res.end(''); 37 | }); 38 | }); 39 | } 40 | 41 | // Declare the method that actually does the authentication 42 | that.authenticate = function(request, response, callback) { 43 | //todo: if multiple connect middlewares were doing this, it would be more efficient to do it in the stack?? 44 | var parsedUrl = url.parse(request.originalUrl, true); 45 | 46 | //todo: makw the call timeout .... 47 | var self = this; 48 | if (parsedUrl.query && parsedUrl.query.oauth_token && parsedUrl.query.oauth_verifier && request.session.auth["yammer_oauth_token_secret"]) { 49 | self.trace( 'Phase 2/2 : Requesting an OAuth access token.' ); 50 | my._oAuth.getOAuthAccessToken(parsedUrl.query.oauth_token, request.session.auth["yammer_oauth_token_secret"], parsedUrl.query.oauth_verifier, function(error, token, secret, params) { 51 | if (error) { 52 | callback(null); 53 | } else { 54 | request.session.auth["yammer_oauth_token_secret"] = secret; 55 | request.session.auth["yammer_oauth_token"] = token; 56 | 57 | // Get user profile data. 58 | my._oAuth.getProtectedResource("https://www.yammer.com/api/v1/groups.json", 'get', token, secret, function (error, data, response) { 59 | if (error) { 60 | self.fail(callback); 61 | } else { 62 | var result = JSON.parse(data); 63 | self.executionResult.user = result; 64 | self.success(result, callback); 65 | } 66 | }) 67 | } 68 | }); 69 | } else { 70 | self.trace( 'Phase 1/2 - Requesting an OAuth Request Token' ) 71 | my._oAuth.getOAuthRequestToken(function(error, token, secret, auth_url, params) { 72 | if (error) { 73 | callback(null); // Ignore the error upstream, treat as validation failure. 74 | } else { 75 | request.session['yammer_redirect_url'] = request.originalUrl; 76 | request.session.auth["yammer_oauth_token_secret"] = secret; 77 | request.session.auth["yammer_oauth_token"] = token; 78 | self.redirect(response, "https://www.yammer.com/oauth/authorize?oauth_token=" + token, callback); 79 | } 80 | }); 81 | } 82 | } 83 | return that; 84 | }; 85 | 86 | -------------------------------------------------------------------------------- /lib/authExecutionScope.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var AuthExecutionScope= module.exports = function( authContext ) { 6 | this.executionResult= { authenticated: false }; 7 | this.authContext= authContext; 8 | this._trace= authContext.request.getAuthDetails().trace; 9 | this._scope= authContext.scope; 10 | } 11 | 12 | /** 13 | * Utility method for providing tracing functionality within an autthenitcation strategy 14 | * Takes a 'message' to log out 15 | */ 16 | AuthExecutionScope.prototype.trace= function( message ) { 17 | var messagePrefix= ""; 18 | if( this.executionResult.currentStrategy ) { 19 | messagePrefix= this.executionResult.currentStrategy + ": "; 20 | } 21 | this._trace( messagePrefix + message, this._scope, "***" ) 22 | } 23 | 24 | AuthExecutionScope.prototype.fail= function(callback) { 25 | this.trace( "Failed", "***" ); 26 | this.executionResult.authenticated= false; 27 | callback(); 28 | } 29 | AuthExecutionScope.prototype.redirect= function(response, url, callback) { 30 | this.trace( "Redirecting to: "+ url, "***" ); 31 | response.writeHead(303, { 'Location': url }); 32 | response.end(''); 33 | this.executionResult.authenticated= undefined; 34 | this._halt(callback); 35 | }; 36 | 37 | AuthExecutionScope.prototype.success= function(user, callback) { 38 | this.trace( "Succeeded", "***" ); 39 | this.executionResult.user= user; 40 | this.executionResult.authenticated= true; 41 | this._halt(callback); 42 | }; 43 | 44 | AuthExecutionScope.prototype._halt= function(callback) { 45 | this.executionResult.halted= true; 46 | // We don't set a value for this.executionResult.authenticated 47 | // as it has either been set as a result of a call to fail/redirect/success or 48 | // is using the default value of 'false' 49 | callback(); 50 | }; 51 | 52 | AuthExecutionScope.prototype.halt= function(callback) { 53 | this.trace( "Halted", "***" ); 54 | this.executionResult.authenticated= undefined; 55 | this._halt(callback); 56 | }; 57 | AuthExecutionScope.prototype.pass= function (callback) { 58 | this.trace( "Skipped", "***" ); 59 | callback(); 60 | }; -------------------------------------------------------------------------------- /lib/auth_middleware.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var connect= require('connect') 6 | , Events= require('./events') 7 | , RequestMethods= require('./requestMethods') 8 | , StrategyExecutor= require('./strategyExecutor') 9 | , Tracing= require('./tracing'); 10 | 11 | /** 12 | * Construct the authentication middleware. 13 | * Construction can take 2 forms: 14 | * auth(()|[()]) - A single configured strategy, or array of strategies. 15 | * auth({ strategies:()|[()...] 16 | * [trace: true|false|function(message, req, [scope])}]) - More powerful variant that allows for passing in other configuration options, none yet defined. 17 | */ 18 | module.exports = function(optionsOrStrategy) { 19 | 20 | var i, strategies, strategyExecutor, options, traceFunction, server; 21 | 22 | if( !optionsOrStrategy ) throw new Error("You must specify at least one strategy to use the authentication middleware, even if it is anonymous."); 23 | // Constructor form 1 24 | if( Array.isArray(optionsOrStrategy) || ( optionsOrStrategy.authenticate !== undefined && optionsOrStrategy.strategies === undefined ) ) { 25 | strategies= Array.isArray(optionsOrStrategy) ? optionsOrStrategy : [optionsOrStrategy]; 26 | options= {trace: false}; 27 | } 28 | else { 29 | options= optionsOrStrategy 30 | strategies= Array.isArray(optionsOrStrategy.strategies) ? optionsOrStrategy.strategies : [optionsOrStrategy.strategies]; 31 | } 32 | 33 | if( !options.trace ) { // If options.trace is specified as false or undefined we no-op the messages. 34 | traceFunction= Tracing.nullTrace; 35 | } 36 | else if( options.trace === true ) { // If options.trace is really true then we log out to console 37 | traceFunction= Tracing.standardTrace; 38 | } 39 | else { // Custom provided trace function 40 | traceFunction= options.trace; 41 | } 42 | 43 | var logoutHandler= options.logoutHandler || Events.defaultLogoutHandler; 44 | var firstLoginHandler= options.firstLoginHandler || Events.defaultFirstLoginHandler; 45 | 46 | // Construct the strategy executor. 47 | strategyExecutor= new StrategyExecutor( strategies ) 48 | 49 | // Construct the middleware that adapts the request object to provide authentication primitives. 50 | 51 | var internalApp= connect(); 52 | internalApp.use( function auth(req, res, next) { 53 | 54 | // Mix-in the static utility methods (the methods are directly on the request, and don't need the response object). 55 | req.getAuthDetails= RequestMethods.getAuthDetails; 56 | req.isAuthenticated= RequestMethods.isAuthenticated; 57 | req.isUnAuthenticated= RequestMethods.isUnAuthenticated; 58 | 59 | // If there is a session middleware, use it. 60 | if( req.session && req.session.auth ) { 61 | req._connect_auth= req.session.auth; 62 | } 63 | else { 64 | // Create the auth holder if needed. 65 | if( ! req.getAuthDetails() ) { 66 | createAuthDetails(req); 67 | } 68 | } 69 | // Assign a tracer so if needed routes can trace. 70 | req.getAuthDetails().trace= function( message, scope, linePrefix ) { 71 | traceFunction( message, {scope:scope, request:req, response:res}, linePrefix ); 72 | }; 73 | 74 | // These methods require the request & response to be in their closures. 75 | req.authenticate= function(strategy, opts, middlewareCallback) { 76 | RequestMethods.authenticate.call( this, strategy, opts, middlewareCallback, strategyExecutor, res, firstLoginHandler ); 77 | }; 78 | 79 | req.logout= function( scope, middlewareCallback ) { 80 | if( typeof scope === 'function' && middlewareCallback === undefined ) { 81 | middlewareCallback= scope; 82 | scope= undefined; 83 | } 84 | RequestMethods.logout.call( this, {scope:scope, request:req, response:res}, logoutHandler, function() { 85 | //Clear out the saved auth details 86 | //TODO: this should be scope-aware. 87 | createAuthDetails( req ); 88 | // Assign a tracer so if needed routes can trace. 89 | req.getAuthDetails().trace= function( message, scope, linePrefix ) { 90 | traceFunction( message, {scope:scope, request:req, response:res}, linePrefix ); 91 | }; 92 | 93 | if( middlewareCallback) middlewareCallback(); 94 | }) 95 | }; 96 | 97 | // Now we've added our requisite methods to the request, call the next part of the middleware chain 98 | // (which may in fact be a middleware piece that enforces authentication!) 99 | next(); 100 | }); 101 | 102 | // Some strategies require routes to be defined, so give them a chance to do so. 103 | for(i=0;i< strategies.length; i++ ) { 104 | if( strategies[i].setupRoutes ) { 105 | strategies[i].setupRoutes(internalApp); 106 | } 107 | } 108 | 109 | return internalApp; 110 | }; 111 | 112 | // Utility functions 113 | function createAuthDetails( request ) { 114 | var auth= { scopedUsers: {} }; 115 | request._connect_auth= auth; 116 | if( request.session ) { 117 | request.session.auth= auth; 118 | } 119 | }; -------------------------------------------------------------------------------- /lib/events.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | module.exports.defaultLogoutHandler= function( authContext, loggedOutUser, callback ) { 6 | if( callback ) callback(); 7 | } 8 | 9 | /** 10 | * Provides a basic 'out of the box' factory function for 11 | * redirecting a user to a specified url on logout 12 | */ 13 | module.exports.redirectOnLogout= function( redirectUrl ) { 14 | return function( authContext, loggedOutUser, callback ) { 15 | authContext.response.writeHead(303, { 'Location': redirectUrl }); 16 | authContext.response.end(''); 17 | if( callback ) callback(); 18 | }; 19 | } 20 | 21 | module.exports.defaultFirstLoginHandler= function( authContext, executionResult, callback ) { 22 | if( callback ) callback( null, true ); 23 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | 6 | /** 7 | * Module dependencies. 8 | */ 9 | var Auth= require('./auth_middleware') 10 | , fs= require('fs'); 11 | 12 | module.exports= Auth; 13 | 14 | /** 15 | * Auto-load bundled strategies with getters. 16 | */ 17 | var STRATEGY_EXCLUSIONS= {"base.js" : true, 18 | "base64.js" : true}; 19 | 20 | function augmentAuthWithStrategy(filename, path) { 21 | if (/\.js$/.test(filename) && !STRATEGY_EXCLUSIONS[filename] && filename[0] != '_') { 22 | var name = filename.substr(0, filename.lastIndexOf('.')); 23 | var camelCaseName= name.charAt(0).toUpperCase() + name.substr(1).toLowerCase(); 24 | Object.defineProperty(Auth, camelCaseName, { 25 | get: function() { 26 | return require('./' + path+ '/' + name); 27 | }, 28 | enumerable:true}); 29 | } 30 | } 31 | 32 | //TODO: Meh could make this recurse neatly over directories, but I'm lazy. 33 | fs.readdirSync(__dirname + '/auth.strategies').forEach(function(filename){ 34 | augmentAuthWithStrategy(filename, '/auth.strategies') 35 | }); 36 | fs.readdirSync(__dirname + '/auth.strategies/http').forEach(function(filename){ 37 | augmentAuthWithStrategy(filename, '/auth.strategies/http') 38 | }); 39 | fs.readdirSync(__dirname + '/auth.strategies/oauth').forEach(function(filename){ 40 | augmentAuthWithStrategy(filename, '/auth.strategies/oauth') 41 | }); 42 | -------------------------------------------------------------------------------- /lib/requestMethods.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | 6 | /* 7 | * This file contains the methods that will become 'mixed-in' with the connect request object, namely: 8 | * 9 | * authenticate( [strategy|options], callback(err, succcessFailOngoing) ) 10 | * getAuthDetails 11 | * isAuthenticated( [scope] ) 12 | * isUnAuthenticated( [scope] ) 13 | * logout( [scope], [callback(err)]) 14 | */ 15 | module.exports.authenticate= function(strategy, opts, callback, strategyExecutor, res, firstLoginHandler) { 16 | var strategy, opts, callback; 17 | var scope; 18 | 19 | var trace= this.getAuthDetails().trace; 20 | var req= this; 21 | 22 | //ughhh pull this rubbish somewhere tidy... 23 | if( strategy && opts && callback ) { 24 | var type= typeof strategy; 25 | if( strategy.constructor != Array ) { 26 | strategy= [strategy] 27 | } 28 | scope= opts.scope; 29 | } 30 | else if( strategy && opts ) { 31 | callback= opts; 32 | var type= typeof strategy; 33 | if( strategy.constructor == Array ) { 34 | // do nothing 35 | } 36 | else if( type == 'string' ) { 37 | strategy= [strategy]; 38 | } 39 | else if( type == 'object') { 40 | scope= strategy.scope 41 | strategy= undefined; 42 | } 43 | } 44 | else if( strategy ) { 45 | callback= strategy; 46 | strategy= undefined; 47 | } 48 | // Choose the first strategy defined if no strategy provided 49 | if( !strategy && strategyExecutor.strategies ) { 50 | for( var k in strategyExecutor.strategies ) { 51 | strategy= [strategyExecutor.strategies[k].name]; 52 | break; 53 | } 54 | } 55 | 56 | // Sometimes the authentication scope needs to passed between requests, we store this information 57 | // transiently on the session. 58 | if( scope === undefined && req.getAuthDetails().__performingAuthentication && req.getAuthDetails().__originalScope ) { 59 | scope= req.getAuthDetails().__originalScope; 60 | } 61 | 62 | trace( "Authenticating ("+this.headers.host + this.originalUrl+")", scope, ">>>" ); 63 | if( req.isAuthenticated(scope) ) { 64 | delete req.getAuthDetails().__performingAuthentication; 65 | delete req.getAuthDetails().__originalUrl; 66 | delete req.getAuthDetails().__originalScope; 67 | trace( "Authentication successful (Already Authenticated)", scope, "<<<" ); 68 | callback(null, true); 69 | } 70 | else { 71 | var authContext= {scope:scope, request:req, response:res}; 72 | strategyExecutor.authenticate(strategy, authContext, function (error, executionResult) { 73 | //TODO: This needs tidying up, the HTTP strategies have bled... 74 | if( executionResult) { 75 | req.getAuthDetails().errorResponse= executionResult.errorResponse; 76 | if( req.getAuthDetails().__originalUrl ) { 77 | executionResult.originalUrl= req.getAuthDetails().__originalUrl; 78 | } else { 79 | executionResult.originalUrl= req.originalUrl; 80 | } 81 | } 82 | if(error) { 83 | delete req.getAuthDetails().__performingAuthentication; 84 | delete req.getAuthDetails().__originalUrl; 85 | delete req.getAuthDetails().__originalScope 86 | trace( "Authentication error: "+ error, scope, "<<<" ); 87 | callback(error); 88 | } 89 | else { 90 | if( executionResult.authenticated === true ) { 91 | trace( "Authentication successful", scope, "<<<" ); 92 | executionResult.originalUrl= req.getAuthDetails().__originalUrl; 93 | delete req.getAuthDetails().__originalUrl; 94 | delete req.getAuthDetails().__originalScope 95 | 96 | if( scope === undefined) { 97 | req.getAuthDetails().user= executionResult.user; 98 | } 99 | else { 100 | if( req.getAuthDetails().scopedUsers[scope] === undefined ) { 101 | req.getAuthDetails().scopedUsers[scope] = {}; 102 | } 103 | req.getAuthDetails().scopedUsers[scope].user= executionResult.user; 104 | } 105 | 106 | if( req.getAuthDetails().__performingAuthentication ) { 107 | try { 108 | delete req.getAuthDetails().__performingAuthentication; 109 | trace( "Firing 'FirstLogin' Handler", scope, "$$$" ); 110 | firstLoginHandler( authContext, executionResult, callback ); 111 | } 112 | catch(err) { 113 | trace( "error: With executing firstLoginHandler" + err.stack ); 114 | } 115 | } 116 | else { 117 | callback(null, executionResult.authenticated) 118 | } 119 | } 120 | else if( executionResult.authenticated === false ) { 121 | delete req.getAuthDetails().__performingAuthentication; 122 | delete req.getAuthDetails().__originalUrl; 123 | delete req.getAuthDetails().__originalScope; 124 | trace( "Authentication failed", scope, "<<<" ); 125 | callback(null, executionResult.authenticated) 126 | } 127 | else { 128 | req.getAuthDetails().__performingAuthentication= true; 129 | req.getAuthDetails().__originalUrl= req.originalUrl; 130 | req.getAuthDetails().__originalScope= scope; 131 | trace( "Authentication ongoing (Requires browser interaction)", scope, "<<<" ); 132 | callback(null, executionResult.authenticated) 133 | } 134 | } 135 | }); 136 | } 137 | }; 138 | 139 | // mixins... 140 | module.exports.getAuthDetails= function() { 141 | return this._connect_auth 142 | }; 143 | 144 | module.exports.isAuthenticated= function(scope) { 145 | if( scope === undefined ) { 146 | return (this.getAuthDetails().user) ? true : false; 147 | } 148 | else { 149 | return (this.getAuthDetails().scopedUsers[scope] && this.getAuthDetails().scopedUsers[scope].user) ? true : false; 150 | } 151 | }; 152 | 153 | module.exports.isUnAuthenticated= function(scope) { 154 | return !this.isAuthenticated( scope ); 155 | }; 156 | 157 | module.exports.logout= function( authContext, logoutHandler, middlewareCallback ) { 158 | var ad= this.getAuthDetails(); 159 | ad.trace( "Logout", authContext.scope, "!!!" ); 160 | var user; 161 | if( authContext.scope === undefined) { 162 | user= ad.user; 163 | delete ad.user; 164 | ad.scopedUsers= {}; 165 | } 166 | else { 167 | user= ad.scopedUsers[authContext.scope].user; 168 | delete ad.scopedUsers[authContext.scope].user; 169 | } 170 | logoutHandler( authContext, user, middlewareCallback ); 171 | }; -------------------------------------------------------------------------------- /lib/strategyExecutor.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright(c) 2010 Ciaran Jessup 3 | * MIT Licensed 4 | */ 5 | var AuthExecutionScope= require('./authExecutionScope') 6 | , util= require('util'); 7 | 8 | module.exports= function(strategies) { 9 | this.strategies= {}; 10 | for(var i=0; i 3 | * MIT Licensed 4 | */ 5 | 6 | // Silly little function nabbed from: http://www.irt.org/script/183.htm 7 | function pad(number,length) { 8 | var str = '' + number; 9 | while (str.length < length) 10 | str = '0' + str; 11 | return str; 12 | } 13 | 14 | // Dead end for when no tracing mechanism specified 15 | module.exports.nullTrace= function() {} 16 | 17 | /* 18 | * The standard tracing function, provides information in the form of: 19 | * 20 | * 17:54:40-647 [e78ocZ] (Scope) >>> Authenticating (testtwitter.com/?login_with=never) 21 | * 22 | * Viewing these lines offer useful diagnostics to determine why authentication is failing (the session id within the square brackets 23 | * and the provided url giving the greatest clues!) 24 | */ 25 | module.exports.standardTrace= function( message, authContext, linePrefix ) { 26 | var d= new Date(); 27 | var id; 28 | if( authContext.request.sessionID ) { 29 | id= authContext.request.sessionID.substring(0,6); 30 | } else { 31 | id= authContext.request.socket.remoteAddress; 32 | } 33 | var scope= (authContext.scope? " (" + authContext.scope +")" : ""); 34 | var linePrefix= (linePrefix? " " + linePrefix : ""); 35 | message= message.replace(/\n/g, "\n " + linePrefix); 36 | console.log( pad(d.getHours(),2) + ":"+ pad(d.getMinutes(),2) + ':' + pad(d.getSeconds(),2) + '-' + pad(d.getMilliseconds(),3) + " ["+id+"]" + scope + linePrefix+ " "+ message); 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "connect-auth", 3 | "description" : "Middleware for Connect (node.js) for handling your authentication needs.", 4 | "version" : "0.6.0", 5 | "author" : "Ciaran Jessup ", 6 | "engines" : {"node" : ">=0.8.0"}, 7 | "main" : "lib/index.js", 8 | "directories" : { "lib" : "./lib" }, 9 | "dependencies" : { 10 | "connect": "2.7.x" 11 | , "oauth": "0.9.7" 12 | , "openid": "0.4.1" 13 | }, 14 | "repository" : { 15 | "type":"git" 16 | , "url":"http://github.com/ciaranj/connect-auth.git" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spec/lib/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/spec/lib/images/bg.png -------------------------------------------------------------------------------- /spec/lib/images/hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/spec/lib/images/hr.png -------------------------------------------------------------------------------- /spec/lib/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/spec/lib/images/loading.gif -------------------------------------------------------------------------------- /spec/lib/images/sprites.bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/spec/lib/images/sprites.bg.png -------------------------------------------------------------------------------- /spec/lib/images/sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/spec/lib/images/sprites.png -------------------------------------------------------------------------------- /spec/lib/images/vr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciaranj/connect-auth/9b7c06461f58309136bace9c1abe9709dfb763f6/spec/lib/images/vr.png -------------------------------------------------------------------------------- /spec/lib/jspec.css: -------------------------------------------------------------------------------- 1 | body.jspec { 2 | margin: 45px 0; 3 | font: 12px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; 4 | background: #efefef url(images/bg.png) top left repeat-x; 5 | text-align: center; 6 | } 7 | #jspec { 8 | margin: 0 auto; 9 | padding-top: 30px; 10 | width: 1008px; 11 | background: url(images/vr.png) top left repeat-y; 12 | text-align: left; 13 | } 14 | #jspec-top { 15 | position: relative; 16 | margin: 0 auto; 17 | width: 1008px; 18 | height: 40px; 19 | background: url(images/sprites.bg.png) top left no-repeat; 20 | } 21 | #jspec-bottom { 22 | margin: 0 auto; 23 | width: 1008px; 24 | height: 15px; 25 | background: url(images/sprites.bg.png) bottom left no-repeat; 26 | } 27 | #jspec .loading { 28 | margin-top: -45px; 29 | width: 1008px; 30 | height: 80px; 31 | background: url(images/loading.gif) 50% 50% no-repeat; 32 | } 33 | #jspec-title { 34 | position: absolute; 35 | top: 15px; 36 | left: 20px; 37 | width: 160px; 38 | font-size: 22px; 39 | font-weight: normal; 40 | background: url(images/sprites.png) 0 -126px no-repeat; 41 | text-align: center; 42 | } 43 | #jspec-title em { 44 | font-size: 10px; 45 | font-style: normal; 46 | color: #BCC8D1; 47 | } 48 | #jspec-report * { 49 | margin: 0; 50 | padding: 0; 51 | background: none; 52 | border: none; 53 | } 54 | #jspec-report { 55 | padding: 15px 40px; 56 | font: 11px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; 57 | color: #7B8D9B; 58 | } 59 | #jspec-report.has-failures { 60 | padding-bottom: 30px; 61 | } 62 | #jspec-report .hidden { 63 | display: none; 64 | } 65 | #jspec-report .heading { 66 | margin-bottom: 15px; 67 | } 68 | #jspec-report .heading span { 69 | padding-right: 10px; 70 | } 71 | #jspec-report .heading .passes em { 72 | color: #0ea0eb; 73 | } 74 | #jspec-report .heading .failures em { 75 | color: #FA1616; 76 | } 77 | #jspec-report table { 78 | font-size: 11px; 79 | border-collapse: collapse; 80 | } 81 | #jspec-report td { 82 | padding: 8px; 83 | text-indent: 30px; 84 | color: #7B8D9B; 85 | } 86 | #jspec-report tr.body { 87 | display: none; 88 | } 89 | #jspec-report tr.body pre { 90 | margin: 0; 91 | padding: 0 0 5px 25px; 92 | } 93 | #jspec-report tr.even:hover + tr.body, 94 | #jspec-report tr.odd:hover + tr.body { 95 | display: block; 96 | } 97 | #jspec-report tr td:first-child em { 98 | display: block; 99 | clear: both; 100 | font-style: normal; 101 | font-weight: normal; 102 | color: #7B8D9B; 103 | } 104 | #jspec-report tr.even:hover, 105 | #jspec-report tr.odd:hover { 106 | text-shadow: 1px 1px 1px #fff; 107 | background: #F2F5F7; 108 | } 109 | #jspec-report td + td { 110 | padding-right: 0; 111 | width: 15px; 112 | } 113 | #jspec-report td.pass { 114 | background: url(images/sprites.png) 3px -7px no-repeat; 115 | } 116 | #jspec-report td.fail { 117 | background: url(images/sprites.png) 3px -158px no-repeat; 118 | font-weight: bold; 119 | color: #FC0D0D; 120 | } 121 | #jspec-report td.requires-implementation { 122 | background: url(images/sprites.png) 3px -333px no-repeat; 123 | } 124 | #jspec-report tr.description td { 125 | margin-top: 25px; 126 | padding-top: 25px; 127 | font-size: 12px; 128 | font-weight: bold; 129 | text-indent: 0; 130 | color: #1a1a1a; 131 | } 132 | #jspec-report tr.description:first-child td { 133 | border-top: none; 134 | } 135 | #jspec-report .assertion { 136 | display: block; 137 | float: left; 138 | margin: 0 0 0 1px; 139 | padding: 0; 140 | width: 1px; 141 | height: 5px; 142 | background: #7B8D9B; 143 | } 144 | #jspec-report .assertion.failed { 145 | background: red; 146 | } 147 | .jspec-sandbox { 148 | display: none; 149 | } -------------------------------------------------------------------------------- /spec/lib/jspec.growl.js: -------------------------------------------------------------------------------- 1 | 2 | // JSpec - Growl - Copyright TJ Holowaychuk (MIT Licensed) 3 | 4 | ;(function(){ 5 | 6 | Growl = { 7 | 8 | // --- Version 9 | 10 | version: '1.0.0', 11 | 12 | /** 13 | * Execute the given _cmd_, returning an array of lines from stdout. 14 | * 15 | * Examples: 16 | * 17 | * Growl.exec('growlnotify', '-m', msg) 18 | * 19 | * @param {string ...} cmd 20 | * @return {array} 21 | * @api public 22 | */ 23 | 24 | exec: function(cmd) { 25 | var lines = [], line 26 | with (JavaImporter(java.lang, java.io)) { 27 | var proccess = Runtime.getRuntime().exec(Array.prototype.slice.call(arguments)) 28 | var stream = new DataInputStream(proccess.getInputStream()) 29 | while (line = stream.readLine()) 30 | lines.push(line + '') 31 | stream.close() 32 | } 33 | return lines 34 | }, 35 | 36 | /** 37 | * Return the extension of the given _path_ or null. 38 | * 39 | * @param {string} path 40 | * @return {string} 41 | * @api private 42 | */ 43 | 44 | extname: function(path) { 45 | return path.lastIndexOf('.') != -1 ? 46 | path.slice(path.lastIndexOf('.') + 1, path.length) : 47 | null 48 | }, 49 | 50 | /** 51 | * Version of the 'growlnotify' binary. 52 | * 53 | * @return {string} 54 | * @api private 55 | */ 56 | 57 | binVersion: function() { 58 | try { return this.exec('growlnotify', '-v')[0].split(' ')[1] } catch (e) {} 59 | }, 60 | 61 | /** 62 | * Send growl notification _msg_ with _options_. 63 | * 64 | * Options: 65 | * 66 | * - title Notification title 67 | * - sticky Make the notification stick (defaults to false) 68 | * - name Application name (defaults to growlnotify) 69 | * - image 70 | * - path to an icon sets --iconpath 71 | * - path to an image sets --image 72 | * - capitalized word sets --appIcon 73 | * - filename uses extname as --icon 74 | * - otherwise treated as --icon 75 | * 76 | * Examples: 77 | * 78 | * Growl.notify('New email') 79 | * Growl.notify('5 new emails', { title: 'Thunderbird' }) 80 | * 81 | * @param {string} msg 82 | * @param {options} hash 83 | * @api public 84 | */ 85 | 86 | notify: function(msg, options) { 87 | options = options || {} 88 | var args = ['growlnotify', '-m', msg] 89 | if (!this.binVersion()) throw new Error('growlnotify executable is required') 90 | if (image = options.image) { 91 | var flag, ext = this.extname(image) 92 | flag = flag || ext == 'icns' && 'iconpath' 93 | flag = flag || /^[A-Z]/.test(image) && 'appIcon' 94 | flag = flag || /^png|gif|jpe?g$/.test(ext) && 'image' 95 | flag = flag || ext && (image = ext) && 'icon' 96 | flag = flag || 'icon' 97 | args.push('--' + flag, image) 98 | } 99 | if (options.sticky) args.push('--sticky') 100 | if (options.name) args.push('--name', options.name) 101 | if (options.title) args.push(options.title) 102 | this.exec.apply(this, args) 103 | } 104 | } 105 | 106 | JSpec.include({ 107 | name: 'Growl', 108 | reporting: function(options){ 109 | var stats = JSpec.stats 110 | if (stats.failures) Growl.notify('failed ' + stats.failures + ' assertions', { title: 'JSpec'}) 111 | else Growl.notify('passed ' + stats.passes + ' assertions', { title: 'JSpec' }) 112 | } 113 | }) 114 | 115 | })() -------------------------------------------------------------------------------- /spec/lib/jspec.jquery.js: -------------------------------------------------------------------------------- 1 | 2 | // JSpec - jQuery - Copyright TJ Holowaychuk (MIT Licensed) 3 | 4 | JSpec 5 | .requires('jQuery', 'when using jspec.jquery.js') 6 | .include({ 7 | name: 'jQuery', 8 | 9 | // --- Initialize 10 | 11 | init : function() { 12 | jQuery.ajaxSetup({ async: false }) 13 | }, 14 | 15 | // --- Utilities 16 | 17 | utilities : { 18 | element: jQuery, 19 | elements: jQuery, 20 | sandbox : function() { 21 | return jQuery('
') 22 | } 23 | }, 24 | 25 | // --- Matchers 26 | 27 | matchers : { 28 | have_tag : "jQuery(expected, actual).length == 1", 29 | have_one : "alias have_tag", 30 | have_tags : "jQuery(expected, actual).length > 1", 31 | have_many : "alias have_tags", 32 | have_child : "jQuery(actual).children(expected).length == 1", 33 | have_children : "jQuery(actual).children(expected).length > 1", 34 | have_text : "jQuery(actual).text() == expected", 35 | have_value : "jQuery(actual).val() == expected", 36 | be_enabled : "!jQuery(actual).attr('disabled')", 37 | have_class : "jQuery(actual).hasClass(expected)", 38 | 39 | be_visible : function(actual) { 40 | return jQuery(actual).css('display') != 'none' && 41 | jQuery(actual).css('visibility') != 'hidden' && 42 | jQuery(actual).attr('type') != 'hidden' 43 | }, 44 | 45 | be_hidden : function(actual) { 46 | return !JSpec.does(actual, 'be_visible') 47 | }, 48 | 49 | have_classes : function(actual) { 50 | return !JSpec.any(JSpec.toArray(arguments, 1), function(arg){ 51 | return !JSpec.does(actual, 'have_class', arg) 52 | }) 53 | }, 54 | 55 | have_attr : function(actual, attr, value) { 56 | return value ? jQuery(actual).attr(attr) == value: 57 | jQuery(actual).attr(attr) 58 | }, 59 | 60 | 'be disabled selected checked' : function(attr) { 61 | return 'jQuery(actual).attr("' + attr + '")' 62 | }, 63 | 64 | 'have type id title alt href src sel rev name target' : function(attr) { 65 | return function(actual, value) { 66 | return JSpec.does(actual, 'have_attr', attr, value) 67 | } 68 | } 69 | } 70 | }) 71 | 72 | -------------------------------------------------------------------------------- /spec/lib/jspec.shell.js: -------------------------------------------------------------------------------- 1 | 2 | // JSpec - Shell - Copyright TJ Holowaychuk (MIT Licensed) 3 | 4 | ;(function(){ 5 | 6 | var _quit = quit 7 | 8 | Shell = { 9 | 10 | // --- Global 11 | 12 | main: this, 13 | 14 | // --- Commands 15 | 16 | commands: { 17 | quit: ['Terminate the shell', function(){ _quit() }], 18 | exit: ['Terminate the shell', function(){ _quit() }], 19 | p: ['Inspect an object', function(o){ return o.toSource() }] 20 | }, 21 | 22 | /** 23 | * Start the interactive shell. 24 | * 25 | * @api public 26 | */ 27 | 28 | start : function() { 29 | for (var name in this.commands) 30 | if (this.commands.hasOwnProperty(name)) 31 | this.commands[name][1].length ? 32 | this.main[name] = this.commands[name][1] : 33 | this.main.__defineGetter__(name, this.commands[name][1]) 34 | } 35 | } 36 | 37 | Shell.start() 38 | 39 | })() -------------------------------------------------------------------------------- /spec/lib/jspec.timers.js: -------------------------------------------------------------------------------- 1 | 2 | // JSpec - Mock Timers - Copyright TJ Holowaychuk (MIT Licensed) 3 | 4 | ;(function(){ 5 | 6 | /** 7 | * Version. 8 | */ 9 | 10 | mockTimersVersion = '1.0.2' 11 | 12 | /** 13 | * Localized timer stack. 14 | */ 15 | 16 | var timers = [] 17 | 18 | /** 19 | * Set mock timeout with _callback_ and timeout of _ms_. 20 | * 21 | * @param {function} callback 22 | * @param {int} ms 23 | * @return {int} 24 | * @api public 25 | */ 26 | 27 | setTimeout = function(callback, ms) { 28 | var id 29 | return id = setInterval(function(){ 30 | callback() 31 | clearInterval(id) 32 | }, ms) 33 | } 34 | 35 | /** 36 | * Set mock interval with _callback_ and interval of _ms_. 37 | * 38 | * @param {function} callback 39 | * @param {int} ms 40 | * @return {int} 41 | * @api public 42 | */ 43 | 44 | setInterval = function(callback, ms) { 45 | callback.step = ms, callback.current = callback.last = 0 46 | return timers[timers.length] = callback, timers.length 47 | } 48 | 49 | /** 50 | * Destroy timer with _id_. 51 | * 52 | * @param {int} id 53 | * @return {bool} 54 | * @api public 55 | */ 56 | 57 | clearInterval = clearTimeout = function(id) { 58 | return delete timers[--id] 59 | } 60 | 61 | /** 62 | * Reset timers. 63 | * 64 | * @return {array} 65 | * @api public 66 | */ 67 | 68 | resetTimers = function() { 69 | return timers = [] 70 | } 71 | 72 | /** 73 | * Increment each timers internal clock by _ms_. 74 | * 75 | * @param {int} ms 76 | * @api public 77 | */ 78 | 79 | tick = function(ms) { 80 | for (var i = 0, len = timers.length; i < len; ++i) 81 | if (timers[i] && (timers[i].current += ms)) 82 | if (timers[i].current - timers[i].last >= timers[i].step) { 83 | var times = Math.floor((timers[i].current - timers[i].last) / timers[i].step) 84 | var remainder = (timers[i].current - timers[i].last) % timers[i].step 85 | timers[i].last = timers[i].current - remainder 86 | while (times-- && timers[i]) timers[i]() 87 | } 88 | } 89 | 90 | })() -------------------------------------------------------------------------------- /spec/lib/jspec.xhr.js: -------------------------------------------------------------------------------- 1 | 2 | // JSpec - XHR - Copyright TJ Holowaychuk (MIT Licensed) 3 | 4 | (function(){ 5 | 6 | // --- Original XMLHttpRequest 7 | 8 | var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ? 9 | XMLHttpRequest : 10 | function(){} 11 | var OriginalActiveXObject = 'ActiveXObject' in this ? 12 | ActiveXObject : 13 | undefined 14 | 15 | // --- MockXMLHttpRequest 16 | 17 | var MockXMLHttpRequest = function() { 18 | this.requestHeaders = {} 19 | } 20 | 21 | MockXMLHttpRequest.prototype = { 22 | status: 0, 23 | async: true, 24 | readyState: 0, 25 | responseText: '', 26 | abort: function(){}, 27 | onreadystatechange: function(){}, 28 | 29 | /** 30 | * Return response headers hash. 31 | */ 32 | 33 | getAllResponseHeaders : function(){ 34 | return this.responseHeaders 35 | }, 36 | 37 | /** 38 | * Return case-insensitive value for header _name_. 39 | */ 40 | 41 | getResponseHeader : function(name) { 42 | return this.responseHeaders[name.toLowerCase()] 43 | }, 44 | 45 | /** 46 | * Set case-insensitive _value_ for header _name_. 47 | */ 48 | 49 | setRequestHeader : function(name, value) { 50 | this.requestHeaders[name.toLowerCase()] = value 51 | }, 52 | 53 | /** 54 | * Open mock request. 55 | */ 56 | 57 | open : function(method, url, async, user, password) { 58 | this.user = user 59 | this.password = password 60 | this.url = url 61 | this.readyState = 1 62 | this.method = method.toUpperCase() 63 | if (async != undefined) this.async = async 64 | if (this.async) this.onreadystatechange() 65 | }, 66 | 67 | /** 68 | * Send request _data_. 69 | */ 70 | 71 | send : function(data) { 72 | var self = this 73 | this.data = data 74 | this.readyState = 4 75 | if (this.method == 'HEAD') this.responseText = null 76 | this.responseHeaders['content-length'] = (this.responseText || '').length 77 | if(this.async) this.onreadystatechange() 78 | lastRequest = function(){ 79 | return self 80 | } 81 | } 82 | } 83 | 84 | // --- Response status codes 85 | 86 | JSpec.statusCodes = { 87 | 100: 'Continue', 88 | 101: 'Switching Protocols', 89 | 200: 'OK', 90 | 201: 'Created', 91 | 202: 'Accepted', 92 | 203: 'Non-Authoritative Information', 93 | 204: 'No Content', 94 | 205: 'Reset Content', 95 | 206: 'Partial Content', 96 | 300: 'Multiple Choice', 97 | 301: 'Moved Permanently', 98 | 302: 'Found', 99 | 303: 'See Other', 100 | 304: 'Not Modified', 101 | 305: 'Use Proxy', 102 | 307: 'Temporary Redirect', 103 | 400: 'Bad Request', 104 | 401: 'Unauthorized', 105 | 402: 'Payment Required', 106 | 403: 'Forbidden', 107 | 404: 'Not Found', 108 | 405: 'Method Not Allowed', 109 | 406: 'Not Acceptable', 110 | 407: 'Proxy Authentication Required', 111 | 408: 'Request Timeout', 112 | 409: 'Conflict', 113 | 410: 'Gone', 114 | 411: 'Length Required', 115 | 412: 'Precondition Failed', 116 | 413: 'Request Entity Too Large', 117 | 414: 'Request-URI Too Long', 118 | 415: 'Unsupported Media Type', 119 | 416: 'Requested Range Not Satisfiable', 120 | 417: 'Expectation Failed', 121 | 422: 'Unprocessable Entity', 122 | 500: 'Internal Server Error', 123 | 501: 'Not Implemented', 124 | 502: 'Bad Gateway', 125 | 503: 'Service Unavailable', 126 | 504: 'Gateway Timeout', 127 | 505: 'HTTP Version Not Supported' 128 | } 129 | 130 | /** 131 | * Mock XMLHttpRequest requests. 132 | * 133 | * mockRequest().and_return('some data', 'text/plain', 200, { 'X-SomeHeader' : 'somevalue' }) 134 | * 135 | * @return {hash} 136 | * @api public 137 | */ 138 | 139 | function mockRequest() { 140 | return { and_return : function(body, type, status, headers) { 141 | XMLHttpRequest = MockXMLHttpRequest 142 | ActiveXObject = false 143 | status = status || 200 144 | headers = headers || {} 145 | headers['content-type'] = type 146 | JSpec.extend(XMLHttpRequest.prototype, { 147 | responseText: body, 148 | responseHeaders: headers, 149 | status: status, 150 | statusText: JSpec.statusCodes[status] 151 | }) 152 | }} 153 | } 154 | 155 | /** 156 | * Unmock XMLHttpRequest requests. 157 | * 158 | * @api public 159 | */ 160 | 161 | function unmockRequest() { 162 | XMLHttpRequest = OriginalXMLHttpRequest 163 | ActiveXObject = OriginalActiveXObject 164 | } 165 | 166 | JSpec.include({ 167 | name: 'Mock XHR', 168 | 169 | // --- Utilities 170 | 171 | utilities : { 172 | mockRequest: mockRequest, 173 | unmockRequest: unmockRequest 174 | }, 175 | 176 | // --- Hooks 177 | 178 | afterSpec : function() { 179 | unmockRequest() 180 | }, 181 | 182 | // --- DSLs 183 | 184 | DSLs : { 185 | snake : { 186 | mock_request: mockRequest, 187 | unmock_request: unmockRequest, 188 | last_request: function(){ return lastRequest() } 189 | } 190 | } 191 | 192 | }) 193 | })() -------------------------------------------------------------------------------- /spec/node.js: -------------------------------------------------------------------------------- 1 | var kiwi= require('kiwi'); 2 | kiwi.require('express') 3 | require.paths.unshift('spec', 'lib', 'spec/lib') 4 | 5 | require("jspec") 6 | require("express") 7 | require("express/spec") 8 | require("express/plugins") 9 | 10 | print = require('util').puts 11 | quit = process.exit 12 | readFile = require('fs').readFileSync 13 | 14 | function run(specs) { 15 | specs.forEach(function(spec){ 16 | JSpec.exec('spec/spec.' + spec + '.js') 17 | }) 18 | } 19 | 20 | specs = { 21 | independant: [ 22 | /* 'auth.core', */ 23 | 'strategies', 24 | 'auth.strategies.anonymous', 25 | 'auth.strategies.never', 26 | 'auth.strategy.base', 27 | 'auth.strategies.http.digest' 28 | ] 29 | } 30 | 31 | switch (process.ARGV[2]) { 32 | case 'all': 33 | run(specs.independant) 34 | break 35 | default: 36 | run([process.ARGV[2]]) 37 | } 38 | 39 | Express.environment = 'test' 40 | JSpec.run({ reporter: JSpec.reporters.Terminal, failuresOnly: true }).report() -------------------------------------------------------------------------------- /spec/spec.auth.core.js: -------------------------------------------------------------------------------- 1 | describe 'Express' 2 | before_each 3 | reset() 4 | use(Cookie) 5 | use(Session) 6 | use(Basic= require('express/plugins/auth').Basic, {getPasswordForUser: function(error, callback) { callback(null, 'bar')} }) 7 | end 8 | describe 'Auth' 9 | describe 'on' 10 | describe 'request' 11 | describe 'given no authorisation header' 12 | it 'should set auth provided false' 13 | var auth; 14 | get('/', function(){ 15 | auth= this.session.auth; 16 | }) 17 | get('/') 18 | auth.provided.should.eql( false ) 19 | end 20 | it 'should set REMOTE_USER to be undefined' 21 | var remoteUser; 22 | get('/', function(){ 23 | remoteUser= this.session.auth.REMOTE_USER; 24 | }) 25 | get('/') 26 | remoteUser.should_be undefined 27 | end 28 | describe 'when a method requires authorization' 29 | it 'should set the WWW-Authenticate header' 30 | var auth; 31 | get('/', function() { 32 | this.isAuthorized( function(error, authorized) { } ) 33 | }); 34 | var response= get('/', {}) 35 | response.headers['www-authenticate'].should.eql "Basic realm=test" 36 | response.status.should.eql 401 37 | response.body.should.eql "Authorization Required" 38 | end 39 | end 40 | end 41 | describe 'given an invalid authorisation header' 42 | 43 | it 'should set auth provided false' 44 | var auth; 45 | get('/', function(){ 46 | auth= this.session.auth; 47 | }) 48 | get('/', { headers: { authorization : "Basic Zm9vOsdsmJhcg==" } } ) 49 | auth.provided.should.eql false 50 | end 51 | it 'should set REMOTE_USER to be undefined' 52 | var remoteUser; 53 | get('/', function(){ 54 | remoteUser= this.session.auth.REMOTE_USER; 55 | }) 56 | get('/', { headers: { authorization : "Basic Zm9sdsdvOmJhcg==" } } ) 57 | remoteUser.should_be undefined 58 | end 59 | 60 | it 'isAuthorized should pass back false' 61 | var authed; 62 | get('/', function() { 63 | var self=this; 64 | this.isAuthorized(function(error, authorized) { 65 | authed= authorized}) 66 | }); 67 | var response= get('/', { headers: { authorization : "Basic Zm9vOmsdsdJhcg==" } } ) 68 | authed.should_be false 69 | end 70 | end 71 | 72 | describe 'given a valid authorisation header' 73 | it 'should set auth provided true' 74 | var auth; 75 | get('/', function(){ 76 | auth= this.session.auth; 77 | }) 78 | get('/', { headers: { authorization : "Basic Zm9vOmJhcg==" } } ) 79 | auth.provided.should.eql( true ) 80 | end 81 | it 'should set REMOTE_USER to be undefined' 82 | var remoteUser; 83 | get('/', function(){ 84 | remoteUser= this.session.auth.REMOTE_USER; 85 | }) 86 | get('/', { headers: { authorization : "Basic Zm9vOmJhcg==" } } ) 87 | remoteUser.should_be undefined 88 | end 89 | describe 'when a method requires authorization' 90 | it 'should request the password for the given username from the getPasswordForUser callback' 91 | var user; 92 | use(BasicAuth, {getPasswordForUser: function(u, callback) { 93 | user= u; 94 | callback(null,'bar') 95 | }} ) 96 | get('/', function() { 97 | this.isAuthorized(function(error, authorized) {}) 98 | }); 99 | var response= get('/', { headers: { authorization : "Basic Zm9vOmJhcg==" } } ) 100 | user.should.eql 'foo' 101 | end 102 | describe 'and the getPasswordForUser function returns the expected password' 103 | before_each 104 | use(BasicAuth, {getPasswordForUser: function(user, callback) { callback(null, 'bar')} } ) 105 | end 106 | it 'should not set the WWW-Authenticate header' 107 | get('/', function() { 108 | this.isAuthorized( function(error, authorized) { } ) 109 | }); 110 | var response= get('/', { headers: { authorization : "Basic Zm9vOmJhcg==" } } ) 111 | response.headers['www-authenticate'].should_be undefined 112 | end 113 | it 'should set REMOTE_USER to be the passed credential\'s username' 114 | var remoteUser; 115 | get('/', function() { 116 | var self=this; 117 | this.isAuthorized(function(error, authorized) { 118 | remoteUser= self.session.auth.REMOTE_USER}) 119 | }); 120 | var response= get('/', { headers: { authorization : "Basic Zm9vOmJhcg==" } } ) 121 | remoteUser.should.eql "foo" 122 | end 123 | it 'isAuthorized should pass back true' 124 | var authed; 125 | get('/', function() { 126 | var self=this; 127 | this.isAuthorized(function(error, authorized) { 128 | authed= authorized}) 129 | }); 130 | var response= get('/', { headers: { authorization : "Basic Zm9vOmJhcg==" } } ) 131 | authed.should_be true 132 | end 133 | end 134 | 135 | describe 'and the onAuthorize function returns a mis-matching password' 136 | before_each 137 | use(BasicAuth, {getPasswordForUser: function(error, callback) { callback(null, null)} } ) 138 | end 139 | it 'should set the WWW-Authenticate header' 140 | get('/', function() { 141 | this.isAuthorized( function(error, authorized) { } ) 142 | }); 143 | var response= get('/', { headers: { authorization : "Basic Zm9vOmJhcg==" } } ) 144 | response.headers['www-authenticate'].should_be "Basic realm=test" 145 | end 146 | it 'should set REMOTE_USER to be undefined' 147 | var remoteUser; 148 | get('/', function() { 149 | var self=this; 150 | this.isAuthorized(function(error, authorized) { 151 | remoteUser= self.session.auth.REMOTE_USER}) 152 | }); 153 | var response= get('/', { headers: { authorization : "Basic Zm9vOmJhcg==" } } ) 154 | remoteUser.should_be undefined 155 | end 156 | it 'isAuthorized should pass back false' 157 | var authed; 158 | get('/', function() { 159 | var self=this; 160 | this.isAuthorized(function(error, authorized) { 161 | authed= authorized}) 162 | }); 163 | var response= get('/', { headers: { authorization : "Basic Zm9vOmJhcg==" } } ) 164 | authed.should_be false 165 | end 166 | end 167 | end 168 | end 169 | end 170 | end 171 | end 172 | end -------------------------------------------------------------------------------- /spec/spec.auth.strategies.anonymous.js: -------------------------------------------------------------------------------- 1 | describe 'Express' 2 | before_each 3 | AuthStrategy= require('express/plugins/auth.strategy.base').AuthStrategy 4 | Anonymous= require('express/plugins/auth.strategies/anonymous').Anonymous 5 | end 6 | describe 'Auth' 7 | describe 'Anonymous' 8 | describe 'when authenticated' 9 | it 'should always pass back a user object' 10 | var anonymousStrategy= new Anonymous(); 11 | var u 12 | anonymousStrategy.executionResult={} 13 | anonymousStrategy.authenticate( null, function(error) {}); 14 | anonymousStrategy.executionResult.user.username.should_be 'anonymous' 15 | end 16 | end 17 | describe 'when constructed with a custom user' 18 | it 'should always pass back the custom user object' 19 | var anonymousUser= {username: 'test' } 20 | var anonymousStrategy= new Anonymous({"anonymousUser":anonymousUser}); 21 | anonymousStrategy.executionResult={} 22 | anonymousStrategy.authenticate( null, function(error) {}); 23 | anonymousStrategy.executionResult.user.should.eql anonymousUser 24 | end 25 | end 26 | end 27 | end 28 | end -------------------------------------------------------------------------------- /spec/spec.auth.strategies.http.digest.js: -------------------------------------------------------------------------------- 1 | describe 'Express' 2 | before_each 3 | Digest= require('express/plugins/auth.strategies/http/digest').Digest 4 | end 5 | describe 'Auth' 6 | describe 'Digest Strategy' 7 | it 'should split a complex Authorization header into its constituent credentials parts' 8 | var mockHeader= "Digest username=foo, realm=\"test\", nonce=\"b343d03296358b5d7f985500568b\", uri=\"/\", response=\"52bc08c966a3b16bedb62f1b4a5b40f8\"" 9 | 10 | var credentials= new Digest({})._splitAuthorizationHeader(mockHeader); 11 | credentials.username.should_be "foo" 12 | credentials.realm.should_be "test" 13 | credentials.nonce.should_be "b343d03296358b5d7f985500568b" 14 | credentials.uri.should_be "/" 15 | credentials.response.should_be "52bc08c966a3b16bedb62f1b4a5b40f8" 16 | end 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /spec/spec.auth.strategies.never.js: -------------------------------------------------------------------------------- 1 | describe 'Express' 2 | before_each 3 | AuthStrategy= require('express/plugins/auth.strategy.base').AuthStrategy 4 | Never= require('express/plugins/auth.strategies/never').Never 5 | end 6 | describe 'Auth' 7 | describe 'Never' 8 | describe 'when authenticated' 9 | it 'should not pass back a user object' 10 | var never= new Never(); 11 | var u 12 | never.authenticate( null, function(error, user) { 13 | u=user; 14 | }); 15 | u.should_be undefined 16 | end 17 | end 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /spec/spec.auth.strategy.base.js: -------------------------------------------------------------------------------- 1 | describe 'Express' 2 | before_each 3 | AuthStrategy= require('express/plugins/auth.strategy.base').AuthStrategy 4 | end 5 | describe 'Auth' 6 | describe 'Base Strategy' 7 | it 'should return nonces of the requested size correctly' 8 | var as= new AuthStrategy(); 9 | as._getNonce(1).length.should_be 1 10 | as._getNonce(2).length.should_be 2 11 | as._getNonce(5).length.should_be 5 12 | as._getNonce(10).length.should_be 10 13 | as._getNonce(100).length.should_be 100 14 | as._getNonce(1000).length.should_be 1000 15 | end 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /spec/spec.strategies.js: -------------------------------------------------------------------------------- 1 | describe 'Express' 2 | before_each 3 | Strategies= require('express/plugins/strategies').Strategies 4 | StrategyDefinition= require('express/plugins/strategyDefinition').StrategyDefinition 5 | AuthStrategy= require('express/plugins/auth.strategy.base').AuthStrategy 6 | 7 | MockStrategy= AuthStrategy.extend({ 8 | authenticate: function() {} 9 | }); 10 | MockStrategyB= AuthStrategy.extend({ 11 | authenticate: function() {} 12 | }); 13 | end 14 | describe 'Auth' 15 | describe 'Strategies' 16 | describe 'when constructed' 17 | describe 'with an option declaring a strategy definition' 18 | it 'should add that strategy to the list of available strategies' 19 | var strategies= new Strategies({'strategy1' : new StrategyDefinition(MockStrategy)}); 20 | strategies.get( 'strategy1' ).should_be_an_instance_of MockStrategy 21 | end 22 | end 23 | describe 'with an option declaring an array of strategy definitions' 24 | it 'should add those strategies to the list of available strategies' 25 | var strategies= new Strategies({'strategy1' : new StrategyDefinition(MockStrategy), 26 | 'strategy2' : new StrategyDefinition(MockStrategyB)}); 27 | strategies.get( 'strategy1' ).should_be_an_instance_of MockStrategy 28 | strategies.get( 'strategy2' ).should_be_an_instance_of MockStrategyB 29 | end 30 | end 31 | end 32 | describe 'when add is called' 33 | describe 'with a label and a valid Strategy' 34 | it 'should add that strategy to the list of available strategies' 35 | var strategies= new Strategies(); 36 | strategies.add( 'test', new StrategyDefinition(MockStrategy) ) 37 | strategies.get( 'test' ).should_be_an_instance_of MockStrategy 38 | 39 | expect(function(){ strategies.get('xxx') }).to( throw_error ) 40 | end 41 | end 42 | describe 'with an object literal of strategy definitions' 43 | it 'should add those strategies to the list of available strategies' 44 | var strategies= new Strategies(); 45 | strategies.add( {'strategy1' : new StrategyDefinition(MockStrategy), 46 | 'strategy2' : new StrategyDefinition(MockStrategyB)} ) 47 | 48 | strategies.get( 'strategy1' ).should_be_an_instance_of MockStrategy 49 | strategies.get( 'strategy2' ).should_be_an_instance_of MockStrategyB 50 | 51 | expect(function(){ strategies.get('xxx') }).to( throw_error ) 52 | end 53 | end 54 | describe 'with an invalid strategy' 55 | it 'should raise an error' 56 | var strategies= new Strategies(); 57 | expect(function(){ strategies.add('test', {} ) }).to( throw_error ) 58 | end 59 | end 60 | end 61 | describe 'when clear is called' 62 | it 'shold remove any known strategies' 63 | var strategies= new Strategies(); 64 | 65 | strategies.add( {'strategy1' : new StrategyDefinition(MockStrategy), 66 | 'strategy2' : new StrategyDefinition(MockStrategyB) } ) 67 | 68 | strategies.get('strategy1').should_be_an_instance_of MockStrategy 69 | strategies.get('strategy2').should_be_an_instance_of MockStrategyB 70 | strategies.clear(); 71 | expect(function(){ strategies.get('strategy1') }).to( throw_error ) 72 | expect(function(){ strategies.get('strategy2') }).to( throw_error ) 73 | end 74 | end 75 | describe 'when get is called' 76 | describe 'with an unknown strategy' 77 | it 'should raise an error' 78 | var strategies= new Strategies(); 79 | expect(function(){ strategies.get('sdads') }).to( throw_error ) 80 | end 81 | end 82 | describe 'with a known strategy' 83 | it 'should return the strategy' 84 | var strategies= new Strategies(); 85 | strategies.add( 'test', new StrategyDefinition(MockStrategy) ) 86 | strategies.get( 'test' ).should_be_an_instance_of MockStrategy 87 | end 88 | it 'should always return new instances of the strategy' 89 | var strategies= new Strategies(); 90 | strategies.add( 'test', new StrategyDefinition(MockStrategy) ) 91 | var get1= strategies.get( 'test' ) 92 | get1.should_be_an_instance_of MockStrategy 93 | var get2= strategies.get( 'test' ) 94 | var get3= strategies.get( 'test' ) 95 | ( ( get1 === get2) && (get1 === get3) && (get2 === get3) ).should_be false 96 | end 97 | end 98 | end 99 | end 100 | end 101 | end -------------------------------------------------------------------------------- /test/coookieDecoder.test.js: -------------------------------------------------------------------------------- 1 | var connect = require('connect'); 2 | 3 | exports['test something'] = function(assert) { 4 | console.log(require('util').inspect(assert)) 5 | } 6 | --------------------------------------------------------------------------------