├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── examples └── signon │ ├── app.js │ ├── package.json │ └── views │ ├── account.ejs │ ├── index.ejs │ ├── layout.ejs │ └── login.ejs ├── lib └── passport-openid │ ├── errors │ ├── badrequesterror.js │ └── internalopeniderror.js │ ├── index.js │ └── strategy.js ├── package.json └── test ├── errors └── internalopeniderror-test.js ├── index-test.js └── strategy-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | .DS_Store 3 | 4 | # Node.js 5 | node_modules 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | README.md 2 | Makefile 3 | doc/ 4 | examples/ 5 | test/ 6 | 7 | # Mac OS X 8 | .DS_Store 9 | 10 | # Node.js 11 | .npmignore 12 | node_modules/ 13 | npm-debug.log 14 | 15 | # Git 16 | .git* 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: "node_js" 2 | node_js: 3 | - 0.10 4 | - 0.12 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2011-2013 Jared Hanson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCES = lib/**/*.js 2 | 3 | # ============================================================================== 4 | # Node Tests 5 | # ============================================================================== 6 | 7 | VOWS = ./node_modules/.bin/vows 8 | TESTS ?= test/*-test.js test/**/*-test.js 9 | 10 | test: 11 | @NODE_ENV=test NODE_PATH=lib $(VOWS) $(TESTS) 12 | 13 | # ============================================================================== 14 | # Static Analysis 15 | # ============================================================================== 16 | 17 | JSHINT = jshint 18 | 19 | hint: lint 20 | lint: 21 | $(JSHINT) $(SOURCES) 22 | 23 | 24 | .PHONY: test hint lint 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Passport-OpenID 2 | 3 | [Passport](https://github.com/jaredhanson/passport) strategy for authenticating 4 | with [OpenID](http://openid.net/). 5 | 6 | This module lets you authenticate using OpenID in your Node.js applications. By 7 | plugging into Passport, OpenID authentication can be easily and unobtrusively 8 | integrated into any application or framework that supports 9 | [Connect](http://www.senchalabs.org/connect/)-style middleware, including 10 | [Express](http://expressjs.com/). 11 | 12 |
13 | 14 | :heart: [Sponsors](https://www.passportjs.org/sponsors/?utm_source=github&utm_medium=referral&utm_campaign=passport-openid&utm_content=nav-sponsors) 15 | 16 |
17 | 18 | ## Install 19 | 20 | $ npm install passport-openid 21 | 22 | ## Usage 23 | 24 | #### Configure Strategy 25 | 26 | The OpenID authentication strategy authenticates users using an OpenID 27 | identifier. The strategy requires a `validate` callback, which accepts this 28 | identifier and calls `done` providing a user. Additionally, options can be 29 | supplied to specify a return URL and realm. 30 | 31 | passport.use(new OpenIDStrategy({ 32 | returnURL: 'http://localhost:3000/auth/openid/return', 33 | realm: 'http://localhost:3000/' 34 | }, 35 | function(identifier, done) { 36 | User.findByOpenID({ openId: identifier }, function (err, user) { 37 | return done(err, user); 38 | }); 39 | } 40 | )); 41 | 42 | #### Authenticate Requests 43 | 44 | Use `passport.authenticate()`, specifying the `'openid'` strategy, to 45 | authenticate requests. 46 | 47 | For example, as route middleware in an [Express](http://expressjs.com/) 48 | application: 49 | 50 | app.post('/auth/openid', 51 | passport.authenticate('openid')); 52 | 53 | app.get('/auth/openid/return', 54 | passport.authenticate('openid', { failureRedirect: '/login' }), 55 | function(req, res) { 56 | // Successful authentication, redirect home. 57 | res.redirect('/'); 58 | }); 59 | 60 | #### Saving Associations 61 | 62 | Associations between a relying party and an OpenID provider are used to verify 63 | subsequent protocol messages and reduce round trips. In order to take advantage 64 | of this, an application must store these associations. This can be done by 65 | registering functions with `saveAssociation` and `loadAssociation`. 66 | 67 | strategy.saveAssociation(function(handle, provider, algorithm, secret, expiresIn, done) { 68 | // custom storage implementation 69 | saveAssoc(handle, provider, algorithm, secret, expiresIn, function(err) { 70 | if (err) { return done(err) } 71 | return done(); 72 | }); 73 | }); 74 | 75 | strategy.loadAssociation(function(handle, done) { 76 | // custom retrieval implementation 77 | loadAssoc(handle, function(err, provider, algorithm, secret) { 78 | if (err) { return done(err) } 79 | return done(null, provider, algorithm, secret) 80 | }); 81 | }); 82 | 83 | ## Examples 84 | 85 | For a complete, working example, refer to the [signon example](https://github.com/jaredhanson/passport-openid/tree/master/examples/signon). 86 | 87 | ## Strategies using OpenID 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
Strategy
Cloud Foundry UAA (OpenID Connect)
Google
Steam
Yahoo!
100 | 101 | ## Tests 102 | 103 | $ npm install --dev 104 | $ make test 105 | 106 | [![Build Status](https://secure.travis-ci.org/jaredhanson/passport-openid.png)](http://travis-ci.org/jaredhanson/passport-openid) 107 | 108 | ## Credits 109 | 110 | - [Jared Hanson](http://github.com/jaredhanson) 111 | 112 | ## License 113 | 114 | [The MIT License](http://opensource.org/licenses/MIT) 115 | 116 | Copyright (c) 2011-2013 Jared Hanson <[http://jaredhanson.net/](http://jaredhanson.net/)> 117 | 118 | Sponsor 119 | -------------------------------------------------------------------------------- /examples/signon/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , passport = require('passport') 3 | , util = require('util') 4 | , OpenIDStrategy = require('passport-openid').Strategy; 5 | 6 | 7 | // Passport session setup. 8 | // To support persistent login sessions, Passport needs to be able to 9 | // serialize users into and deserialize users out of the session. Typically, 10 | // this will be as simple as storing the user ID when serializing, and finding 11 | // the user by ID when deserializing. However, since this example does not 12 | // have a database of user records, the OpenID identifier is serialized and 13 | // deserialized. 14 | passport.serializeUser(function(user, done) { 15 | done(null, user.identifier); 16 | }); 17 | 18 | passport.deserializeUser(function(identifier, done) { 19 | done(null, { identifier: identifier }); 20 | }); 21 | 22 | 23 | // Use the OpenIDStrategy within Passport. 24 | // Strategies in passport require a `validate` function, which accept 25 | // credentials (in this case, an OpenID identifier), and invoke a callback 26 | // with a user object. 27 | passport.use(new OpenIDStrategy({ 28 | returnURL: 'http://localhost:3000/auth/openid/return', 29 | realm: 'http://localhost:3000/' 30 | }, 31 | function(identifier, done) { 32 | // asynchronous verification, for effect... 33 | process.nextTick(function () { 34 | 35 | // To keep the example simple, the user's OpenID identifier is returned to 36 | // represent the logged-in user. In a typical application, you would want 37 | // to associate the OpenID identifier with a user record in your database, 38 | // and return that user instead. 39 | return done(null, { identifier: identifier }) 40 | }); 41 | } 42 | )); 43 | 44 | 45 | 46 | 47 | var app = express.createServer(); 48 | 49 | // configure Express 50 | app.configure(function() { 51 | app.set('views', __dirname + '/views'); 52 | app.set('view engine', 'ejs'); 53 | app.use(express.logger()); 54 | app.use(express.cookieParser()); 55 | app.use(express.bodyParser()); 56 | app.use(express.methodOverride()); 57 | app.use(express.session({ secret: 'keyboard cat' })); 58 | // Initialize Passport! Also use passport.session() middleware, to support 59 | // persistent login sessions (recommended). 60 | app.use(passport.initialize()); 61 | app.use(passport.session()); 62 | app.use(app.router); 63 | app.use(express.static(__dirname + '/../../public')); 64 | }); 65 | 66 | 67 | app.get('/', function(req, res){ 68 | res.render('index', { user: req.user }); 69 | }); 70 | 71 | app.get('/account', ensureAuthenticated, function(req, res){ 72 | res.render('account', { user: req.user }); 73 | }); 74 | 75 | app.get('/login', function(req, res){ 76 | res.render('login', { user: req.user }); 77 | }); 78 | 79 | // POST /auth/openid 80 | // Use passport.authenticate() as route middleware to authenticate the 81 | // request. The first step in OpenID authentication will involve redirecting 82 | // the user to their OpenID provider. After authenticating, the OpenID 83 | // provider will redirect the user back to this application at 84 | // /auth/openid/return 85 | app.post('/auth/openid', 86 | passport.authenticate('openid', { failureRedirect: '/login' }), 87 | function(req, res) { 88 | res.redirect('/'); 89 | }); 90 | 91 | // GET /auth/openid/return 92 | // Use passport.authenticate() as route middleware to authenticate the 93 | // request. If authentication fails, the user will be redirected back to the 94 | // login page. Otherwise, the primary route function function will be called, 95 | // which, in this example, will redirect the user to the home page. 96 | app.get('/auth/openid/return', 97 | passport.authenticate('openid', { failureRedirect: '/login' }), 98 | function(req, res) { 99 | res.redirect('/'); 100 | }); 101 | 102 | app.get('/logout', function(req, res){ 103 | req.logout(); 104 | res.redirect('/'); 105 | }); 106 | 107 | app.listen(3000); 108 | 109 | 110 | // Simple route middleware to ensure user is authenticated. 111 | // Use this route middleware on any resource that needs to be protected. If 112 | // the request is authenticated (typically via a persistent login session), 113 | // the request will proceed. Otherwise, the user will be redirected to the 114 | // login page. 115 | function ensureAuthenticated(req, res, next) { 116 | if (req.isAuthenticated()) { return next(); } 117 | res.redirect('/login') 118 | } 119 | -------------------------------------------------------------------------------- /examples/signon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passport-openid-examples-signon", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "express": ">= 0.0.0", 6 | "ejs": ">= 0.0.0", 7 | "passport": ">= 0.0.0", 8 | "passport-openid": ">= 0.0.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/signon/views/account.ejs: -------------------------------------------------------------------------------- 1 |

OpenID: <%= user.identifier %>

2 | -------------------------------------------------------------------------------- /examples/signon/views/index.ejs: -------------------------------------------------------------------------------- 1 | <% if (!user) { %> 2 |

Welcome! Please log in.

3 | <% } else { %> 4 |

Hello, <%= user.identifier %>.

5 | <% } %> 6 | -------------------------------------------------------------------------------- /examples/signon/views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Passport-OpenID Example 5 | 6 | 7 | <% if (!user) { %> 8 |

9 | Home | 10 | Log In 11 |

12 | <% } else { %> 13 |

14 | Home | 15 | Account | 16 | Log Out 17 |

18 | <% } %> 19 | <%- body %> 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/signon/views/login.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 | 8 |
9 |
10 | -------------------------------------------------------------------------------- /lib/passport-openid/errors/badrequesterror.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `BadRequestError` error. 3 | * 4 | * @api public 5 | */ 6 | function BadRequestError(message) { 7 | Error.call(this); 8 | Error.captureStackTrace(this, arguments.callee); 9 | this.name = 'BadRequestError'; 10 | this.message = message || null; 11 | }; 12 | 13 | /** 14 | * Inherit from `Error`. 15 | */ 16 | BadRequestError.prototype.__proto__ = Error.prototype; 17 | 18 | 19 | /** 20 | * Expose `BadRequestError`. 21 | */ 22 | module.exports = BadRequestError; 23 | -------------------------------------------------------------------------------- /lib/passport-openid/errors/internalopeniderror.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `InternalOpenIDError` error. 3 | * 4 | * InternalOpenIDError wraps errors generated by node-openid. By wrapping these 5 | * objects, error messages can be formatted in a manner that aids in debugging 6 | * OpenID issues. 7 | * 8 | * @api public 9 | */ 10 | function InternalOpenIDError(message, err) { 11 | Error.call(this); 12 | Error.captureStackTrace(this, arguments.callee); 13 | this.name = 'InternalOpenIDError'; 14 | this.message = message; 15 | this.openidError = err; 16 | }; 17 | 18 | /** 19 | * Inherit from `Error`. 20 | */ 21 | InternalOpenIDError.prototype.__proto__ = Error.prototype; 22 | 23 | /** 24 | * Returns a string representing the error. 25 | * 26 | * @return {String} 27 | * @api public 28 | */ 29 | InternalOpenIDError.prototype.toString = function() { 30 | var m = this.message; 31 | if (this.openidError) { 32 | if (this.openidError instanceof Error) { 33 | m += ' (' + this.openidError + ')'; 34 | } 35 | else if (this.openidError.message) { 36 | m += ' (message: ' + this.openidError.message + ')'; 37 | } 38 | } 39 | return m; 40 | } 41 | 42 | 43 | /** 44 | * Expose `InternalOpenIDError`. 45 | */ 46 | module.exports = InternalOpenIDError; 47 | -------------------------------------------------------------------------------- /lib/passport-openid/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var openid = require('openid') 5 | , Strategy = require('./strategy') 6 | , BadRequestError = require('./errors/badrequesterror') 7 | , InternalOpenIDError = require('./errors/internalopeniderror'); 8 | 9 | 10 | /** 11 | * Expose `Strategy` directly from package. 12 | */ 13 | exports = module.exports = Strategy; 14 | 15 | /** 16 | * Expose constructors. 17 | */ 18 | exports.Strategy = Strategy; 19 | 20 | exports.BadRequestError = BadRequestError; 21 | exports.InternalOpenIDError = InternalOpenIDError; 22 | 23 | 24 | /** 25 | * Register a discovery function. 26 | * 27 | * Under most circumstances, registering a discovery function is not necessary, 28 | * due to the fact that the OpenID specification standardizes a discovery 29 | * procedure. 30 | * 31 | * When authenticating against a set of pre-approved OpenID providers, assisting 32 | * the discovery process with this information is an optimization that avoids 33 | * network requests for well-known endpoints. It is also useful in 34 | * circumstances where work-arounds need to be put in place to address issues 35 | * with buggy OpenID providers or the underlying openid module. 36 | * 37 | * Discovery functions accept an `identifier` and `done` callback, which should 38 | * be invoked with a `provider` object containing `version` and `endpoint` 39 | * properties (or an `err` if an exception occurred). 40 | * 41 | * Example: 42 | * 43 | * openid.discover(function(identifier, done) { 44 | * if (identifier.indexOf('https://openid.example.com/id/') == 0) { 45 | * var provider = {}; 46 | * provider.version = 'http://specs.openid.net/auth/2.0'; 47 | * provider.endpoint = 'https://openid.examle.com/api/auth'; 48 | * return done(null, provider); 49 | * } 50 | * return done(null, null); 51 | * }) 52 | * 53 | * @param {Function} fn 54 | * @api public 55 | */ 56 | exports.discover = function(fn) { 57 | discoverers.push(fn); 58 | }; 59 | 60 | var discoverers = []; 61 | 62 | /** 63 | * Swizzle the underlying loadDiscoveredInformation function in the openid 64 | * module. 65 | */ 66 | var loadDiscoveredInformation = openid.loadDiscoveredInformation; 67 | openid.loadDiscoveredInformation = function(key, callback) { 68 | var stack = discoverers; 69 | (function pass(i, err, provider) { 70 | // an error occurred or a provider was found, done 71 | if (err || provider) { return callback(err, provider); } 72 | 73 | var discover = stack[i]; 74 | if (!discover) { 75 | // The list of custom discovery functions has been exhausted. Call the 76 | // original implementation provided by the openid module. 77 | return loadDiscoveredInformation(key, callback); 78 | } 79 | 80 | try { 81 | discover(key, function(e, p) { pass(i + 1, e, p); }); 82 | } catch(e) { 83 | return callback(e); 84 | } 85 | })(0); 86 | } 87 | -------------------------------------------------------------------------------- /lib/passport-openid/strategy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var passport = require('passport-strategy') 5 | , openid = require('openid') 6 | , util = require('util') 7 | , BadRequestError = require('./errors/badrequesterror') 8 | , InternalOpenIDError = require('./errors/internalopeniderror'); 9 | 10 | 11 | /** 12 | * `Strategy` constructor. 13 | * 14 | * The OpenID authentication strategy authenticates requests using the OpenID 15 | * 2.0 or 1.1 protocol. 16 | * 17 | * OpenID provides a decentralized authentication protocol, whereby users can 18 | * authenticate using their choice of OpenID provider. Authenticating in this 19 | * this manner involves a sequence of events, including prompting the user to 20 | * enter their OpenID identifer and redirecting the user to their OpenID 21 | * provider for authentication. Once authenticated, the user is redirected back 22 | * to the application with an assertion regarding the identifier. 23 | * 24 | * Applications must supply a `verify` callback which accepts an `identifier`, 25 | * an optional service-specific `profile`, an optional set of policy extensions 26 | * and then calls the `done` callback supplying a `user`, which should be set to 27 | * `false` if the credentials are not valid. If an exception occured, `err` 28 | * should be set. 29 | * 30 | * Options: 31 | * - `returnURL` URL to which the OpenID provider will redirect the user after authentication 32 | * - `realm` the part of URL-space for which an OpenID authentication request is valid 33 | * - `profile` enable profile exchange, defaults to _false_ 34 | * - `pape` when present, enables the OpenID Provider Authentication Policy Extension 35 | * (http://openid.net/specs/openid-provider-authentication-policy-extension-1_0.html) 36 | * - `pape.maxAuthAge` sets the PAPE maximum authentication age in seconds 37 | * - `pape.preferredAuthPolicies` sets the preferred set of PAPE authentication policies for the 38 | * relying party to use for example `multi-factor`, `multi-factor-physical` 39 | * or `phishing-resistant` (either an array or a string) 40 | * - `identifierField` field name where the OpenID identifier is found, defaults to 'openid_identifier' 41 | * - `passReqToCallback` when `true`, `req` is the first argument to the verify callback (default: `false`) 42 | * 43 | * Examples: 44 | * 45 | * passport.use(new OpenIDStrategy({ 46 | * returnURL: 'http://localhost:3000/auth/openid/return', 47 | * realm: 'http://localhost:3000/' 48 | * }, 49 | * function(identifier, done) { 50 | * User.findByOpenID(identifier, function (err, user) { 51 | * done(err, user); 52 | * }); 53 | * } 54 | * )); 55 | * 56 | * passport.use(new OpenIDStrategy({ 57 | * returnURL: 'http://localhost:3000/auth/openid/return', 58 | * realm: 'http://localhost:3000/', 59 | * profile: true, 60 | * pape: { maxAuthAge : 600 } 61 | * }, 62 | * function(identifier, profile, done) { 63 | * User.findByOpenID(identifier, function (err, user) { 64 | * done(err, user); 65 | * }); 66 | * } 67 | * )); 68 | * 69 | * @param {Object} options 70 | * @param {Function} verify 71 | * @api public 72 | */ 73 | function Strategy(options, verify) { 74 | if (!options.returnURL) throw new Error('OpenID authentication requires a returnURL option'); 75 | if (!verify) throw new Error('OpenID authentication strategy requires a verify callback'); 76 | 77 | passport.Strategy.call(this); 78 | this.name = 'openid'; 79 | this._verify = verify; 80 | this._profile = options.profile; 81 | this._pape = options.pape; 82 | this._passReqToCallback = options.passReqToCallback; 83 | 84 | var extensions = []; 85 | if (options.profile) { 86 | var sreg = new openid.SimpleRegistration({ 87 | "fullname" : true, 88 | "nickname" : true, 89 | "email" : true, 90 | "dob" : true, 91 | "gender" : true, 92 | "postcode" : true, 93 | "country" : true, 94 | "timezone" : true, 95 | "language" : true 96 | }); 97 | extensions.push(sreg); 98 | } 99 | if (options.profile) { 100 | var ax = new openid.AttributeExchange({ 101 | "http://axschema.org/namePerson" : "required", 102 | "http://axschema.org/namePerson/first": "required", 103 | "http://axschema.org/namePerson/last": "required", 104 | "http://axschema.org/contact/email": "required" 105 | }); 106 | extensions.push(ax); 107 | } 108 | 109 | if (options.ui) { 110 | // ui: { mode: 'popup', icon: true, lang: 'fr-FR' } 111 | var ui = new openid.UserInterface(options.ui); 112 | extensions.push(ui); 113 | } 114 | 115 | if (options.pape) { 116 | var papeOptions = {}; 117 | if (options.pape.hasOwnProperty("maxAuthAge")) { 118 | papeOptions.max_auth_age = options.pape.maxAuthAge; 119 | } 120 | if (options.pape.preferredAuthPolicies) { 121 | if (typeof options.pape.preferredAuthPolicies === "string") { 122 | papeOptions.preferred_auth_policies = options.pape.preferredAuthPolicies; 123 | } else if (Array.isArray(options.pape.preferredAuthPolicies)) { 124 | papeOptions.preferred_auth_policies = options.pape.preferredAuthPolicies.join(" "); 125 | } 126 | } 127 | var pape = new openid.PAPE(papeOptions); 128 | extensions.push(pape); 129 | } 130 | 131 | if (options.oauth) { 132 | var oauthOptions = {}; 133 | oauthOptions.consumerKey = options.oauth.consumerKey; 134 | oauthOptions.scope = options.oauth.scope; 135 | 136 | var oauth = new openid.OAuthHybrid(oauthOptions); 137 | extensions.push(oauth); 138 | } 139 | 140 | this._relyingParty = new openid.RelyingParty( 141 | options.returnURL, 142 | options.realm, 143 | (options.stateless === undefined) ? false : options.stateless, 144 | (options.secure === undefined) ? true : options.secure, 145 | extensions); 146 | 147 | this._providerURL = options.providerURL; 148 | this._identifierField = options.identifierField || 'openid_identifier'; 149 | } 150 | 151 | /** 152 | * Inherit from `passport.Strategy`. 153 | */ 154 | util.inherits(Strategy, passport.Strategy); 155 | 156 | 157 | /** 158 | * Authenticate request by delegating to an OpenID provider using OpenID 2.0 or 159 | * 1.1. 160 | * 161 | * @param {Object} req 162 | * @api protected 163 | */ 164 | Strategy.prototype.authenticate = function(req) { 165 | 166 | if (req.query && req.query['openid.mode']) { 167 | // The request being authenticated contains an `openid.mode` parameter in 168 | // the query portion of the URL. This indicates that the OpenID Provider 169 | // is responding to a prior authentication request with either a positive or 170 | // negative assertion. If a positive assertion is received, it will be 171 | // verified according to the rules outlined in the OpenID 2.0 specification. 172 | 173 | // NOTE: node-openid (0.3.1), which is used internally, will treat a cancel 174 | // response as an error, setting `err` in the verifyAssertion 175 | // callback. However, for consistency with Passport semantics, a 176 | // cancel response should be treated as an authentication failure, 177 | // rather than an exceptional error. As such, this condition is 178 | // trapped and handled prior to being given to node-openid. 179 | 180 | if (req.query['openid.mode'] === 'cancel') { return this.fail({ message: 'OpenID authentication canceled' }); } 181 | 182 | var self = this; 183 | this._relyingParty.verifyAssertion(req.url, function(err, result) { 184 | if (err) { return self.error(new InternalOpenIDError('Failed to verify assertion', err)); } 185 | if (!result.authenticated) { return self.error(new Error('OpenID authentication failed')); } 186 | 187 | var profile = self._parseProfileExt(result); 188 | var pape = self._parsePAPEExt(result); 189 | var oauth = self._parseOAuthExt(result); 190 | 191 | function verified(err, user, info) { 192 | if (err) { return self.error(err); } 193 | if (!user) { return self.fail(info); } 194 | self.success(user, info); 195 | } 196 | 197 | 198 | var arity = self._verify.length; 199 | if (self._passReqToCallback) { 200 | if (arity == 6) { 201 | self._verify(req, result.claimedIdentifier, profile, pape, oauth, verified); 202 | } else if (arity == 5) { 203 | self._verify(req, result.claimedIdentifier, profile, pape, verified); 204 | } else if (arity == 4 || self._profile) { 205 | // self._profile check covers the case where callback uses `arguments` 206 | // and arity == 0 207 | self._verify(req, result.claimedIdentifier, profile, verified); 208 | } else { 209 | self._verify(req, result.claimedIdentifier, verified); 210 | } 211 | } else { 212 | if (arity == 5) { 213 | self._verify(result.claimedIdentifier, profile, pape, oauth, verified); 214 | } else if (arity == 4) { 215 | self._verify(result.claimedIdentifier, profile, pape, verified); 216 | } else if (arity == 3 || self._profile) { 217 | // self._profile check covers the case where callback uses `arguments` 218 | // and arity == 0 219 | self._verify(result.claimedIdentifier, profile, verified); 220 | } else { 221 | self._verify(result.claimedIdentifier, verified); 222 | } 223 | } 224 | }); 225 | } else { 226 | // The request being authenticated is initiating OpenID authentication. By 227 | // default, an `openid_identifier` parameter is expected as a parameter, 228 | // typically input by a user into a form. 229 | // 230 | // During the process of initiating OpenID authentication, discovery will be 231 | // performed to determine the endpoints used to authenticate with the user's 232 | // OpenID provider. Optionally, and by default, an association will be 233 | // established with the OpenID provider which is used to verify subsequent 234 | // protocol messages and reduce round trips. 235 | 236 | var identifier = undefined; 237 | if (req.body && req.body[this._identifierField]) { 238 | identifier = req.body[this._identifierField]; 239 | } else if (req.query && req.query[this._identifierField]) { 240 | identifier = req.query[this._identifierField]; 241 | } else if (this._providerURL) { 242 | identifier = this._providerURL; 243 | } 244 | 245 | if (!identifier) { return this.fail(new BadRequestError('Missing OpenID identifier')); } 246 | 247 | var self = this; 248 | this._relyingParty.authenticate(identifier, false, function(err, providerUrl) { 249 | if (err || !providerUrl) { return self.error(new InternalOpenIDError('Failed to discover OP endpoint URL', err)); } 250 | self.redirect(providerUrl); 251 | }); 252 | } 253 | } 254 | 255 | /** 256 | * Register a function used to save associations. 257 | * 258 | * An association establishes a shared secret between a relying party and an 259 | * OpenID provider, which is used to verify subsequent protocol messages and 260 | * reduce round trips. Registering a function allows an application to 261 | * implement storage of associations as necessary. 262 | * 263 | * The function accepts six arguments: `handle`, `provider`, `algorithm`, 264 | * `secret`, `expiresIn`, and `done` a callback to invoke when the association 265 | * has been saved. 266 | * 267 | * After the association has been saved, the corresponding `loadAssociation` 268 | * function will be used to load it when needed. 269 | * 270 | * Internally, this function makes use of `saveAssociation` in the underlying 271 | * node-openid module. Refer to that for more information. Note, however, that 272 | * the argument order has been modified to pass `handle` as the first argument, 273 | * as it is naturally the key used to later load the association. 274 | * 275 | * Examples: 276 | * 277 | * strategy.saveAssociation(function(handle, provider, algorithm, secret, expiresIn, done) { 278 | * saveAssoc(handle, provider, algorithm, secret, expiresIn, function(err) { 279 | * if (err) { return done(err) } 280 | * return done(); 281 | * }); 282 | * }); 283 | * 284 | * References: 285 | * - [Establishing Associations](http://openid.net/specs/openid-authentication-2_0.html#associations) 286 | * 287 | * @param {Function} fn 288 | * @return {Strategy} for chaining 289 | * @api public 290 | */ 291 | Strategy.prototype.saveAssociation = function(fn) { 292 | // wrap to make `handle` the first argument to `fn`. this order is more 293 | // natural due to the fact that `handle` this is the "key" when subsequently 294 | // loading the association. 295 | openid.saveAssociation = function(provider, type, handle, secret, expiry, callback) { 296 | fn(handle, provider, type, secret, expiry, callback) 297 | } 298 | return this; // return this for chaining 299 | } 300 | 301 | /** 302 | * Register a function used to load associations. 303 | * 304 | * An association establishes a shared secret between a relying party and an 305 | * OpenID provider, which is used to verify subsequent protocol messages and 306 | * reduce round trips. Registering a function allows an application to 307 | * implement loading of associations as necessary. 308 | * 309 | * The function accepts two arguments: `handle` and `done` a callback to invoke 310 | * when the association has been loaded. `done` should be invoked with a 311 | * `provider`, `algorithm`, and `secret` (or `err` if an exception occurred). 312 | * 313 | * This function is used to retrieve associations previously saved with the 314 | * corresponding `saveAssociation` function. 315 | * 316 | * Internally, this function makes use of `loadAssociation` in the underlying 317 | * node-openid module. Refer to that for more information. Note, however, that 318 | * the callback is supplied with `provider`, `algorithm`, and `secret` as 319 | * individual arguments, rather than a single object containing them as 320 | * properties. 321 | * 322 | * Examples: 323 | * 324 | * strategy.loadAssociation(function(handle, done) { 325 | * loadAssoc(handle, function(err, provider, algorithm, secret) { 326 | * if (err) { return done(err) } 327 | * return done(null, provider, algorithm, secret) 328 | * }); 329 | * }); 330 | * 331 | * References: 332 | * - [Establishing Associations](http://openid.net/specs/openid-authentication-2_0.html#associations) 333 | * 334 | * @param {Function} fn 335 | * @return {Strategy} for chaining 336 | * @api public 337 | */ 338 | Strategy.prototype.loadAssociation = function(fn) { 339 | // wrap to allow individual arguments to `done` callback. this seems more 340 | // natural since these were individual arguments to the corresponding 341 | // `saveAssociation` function. 342 | openid.loadAssociation = function(handle, callback) { 343 | fn(handle, function(err, provider, algorithm, secret) { 344 | if (err) { return callback(err, null); } 345 | var obj = { 346 | provider: provider, 347 | type: algorithm, 348 | secret: secret 349 | } 350 | return callback(null, obj); 351 | }); 352 | } 353 | return this; // return this for chaining 354 | } 355 | 356 | /** 357 | * Register a function used to cache discovered info. 358 | * 359 | * Caching discovered information about a provider can significantly speed up 360 | * the verification of positive assertions. Registering a function allows an 361 | * application to implement storage of this info as necessary. 362 | * 363 | * The function accepts three arguments: `identifier` (which serves as a key to 364 | * the provider information), `provider` (the provider information being 365 | * cached), and `done` a callback to invoke when the information has been 366 | * stored. 367 | * 368 | * After the data has been cached, the corresponding `loadDiscoveredInfo` 369 | * function will be used to look it up when needed. 370 | * 371 | * This corresponds directly to the `saveDiscoveredInformation` provided by the 372 | * underlying node-openid module. Refer to that for more information. 373 | * 374 | * Examples: 375 | * 376 | * strategy.saveDiscoveredInfo(function(identifier, provider, done) { 377 | * saveInfo(identifier, provider, function(err) { 378 | * if (err) { return done(err) } 379 | * return done(); 380 | * }); 381 | * }; 382 | * 383 | * @param {Function} fn 384 | * @return {Strategy} for chaining 385 | * @api public 386 | */ 387 | Strategy.prototype.saveDiscoveredInfo = 388 | Strategy.prototype.saveDiscoveredInformation = function(fn) { 389 | openid.saveDiscoveredInformation = fn; 390 | return this; // return this for chaining 391 | } 392 | 393 | /** 394 | * Register a function used to load discovered info from cache. 395 | * 396 | * Caching discovered information about a provider can significantly speed up 397 | * the verification of positive assertions. Registering a function allows an 398 | * application to implement laoding of this info as necessary. 399 | * 400 | * The function accepts two arguments: `identifier` (which serves as a key to 401 | * the provider information), and `done` a callback to invoke when the 402 | * information has been loaded. 403 | * 404 | * This function is used to retrieve data previously cached with the 405 | * corresponding `saveDiscoveredInfo` function. 406 | * 407 | * This corresponds directly to the `loadDiscoveredInformation` provided by the 408 | * underlying node-openid module. Refer to that for more information. 409 | * 410 | * Examples: 411 | * 412 | * strategy.loadDiscoveredInfo(function(identifier, done) { 413 | * loadInfo(identifier, function(err, provider) { 414 | * if (err) { return done(err) } 415 | * return done(); 416 | * }); 417 | * }); 418 | * 419 | * @param {Function} fn 420 | * @return {Strategy} for chaining 421 | * @api public 422 | */ 423 | Strategy.prototype.loadDiscoveredInfo = 424 | Strategy.prototype.loadDiscoveredInformation = function(fn) { 425 | openid.loadDiscoveredInformation = fn; 426 | return this; // return this for chaining 427 | } 428 | 429 | /** 430 | * Parse user profile from OpenID response. 431 | * 432 | * Profile exchange can take place via OpenID extensions, the two common ones in 433 | * use are Simple Registration and Attribute Exchange. If an OpenID provider 434 | * supports these extensions, the parameters will be parsed to build the user's 435 | * profile. 436 | * 437 | * @param {Object} params 438 | * @api private 439 | */ 440 | Strategy.prototype._parseProfileExt = function(params) { 441 | var profile = {}; 442 | 443 | // parse simple registration parameters 444 | profile.displayName = params['fullname']; 445 | profile.emails = [{ value: params['email'] }]; 446 | 447 | // parse attribute exchange parameters 448 | profile.name = { familyName: params['lastname'], 449 | givenName: params['firstname'] }; 450 | if (!profile.displayName) { 451 | if (params['firstname'] && params['lastname']) { 452 | profile.displayName = params['firstname'] + ' ' + params['lastname']; 453 | } 454 | } 455 | if (!profile.emails) { 456 | profile.emails = [{ value: params['email'] }]; 457 | } 458 | 459 | return profile; 460 | } 461 | 462 | Strategy.prototype._parsePAPEExt = function(params) { 463 | var pape = {}; 464 | // parse PAPE parameters 465 | if (params['auth_policies']) { 466 | pape.authPolicies = params['auth_policies'].split(' '); 467 | } 468 | if (params['auth_time']) { 469 | pape.authTime = new Date(params['auth_time']); 470 | } 471 | return pape; 472 | } 473 | 474 | Strategy.prototype._parseOAuthExt = function(params) { 475 | var oauth = {}; 476 | // parse OAuth parameters 477 | if (params['request_token']) { 478 | oauth.requestToken = params['request_token']; 479 | } 480 | return oauth; 481 | } 482 | 483 | 484 | /** 485 | * Expose `Strategy`. 486 | */ 487 | module.exports = Strategy; 488 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passport-openid", 3 | "version": "0.4.0", 4 | "description": "OpenID authentication strategy for Passport.", 5 | "keywords": [ 6 | "passport", 7 | "openid", 8 | "auth", 9 | "authn", 10 | "authentication", 11 | "identity" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/jaredhanson/passport-openid.git" 16 | }, 17 | "bugs": { 18 | "url": "http://github.com/jaredhanson/passport-openid/issues" 19 | }, 20 | "author": { 21 | "name": "Jared Hanson", 22 | "email": "jaredhanson@gmail.com", 23 | "url": "http://www.jaredhanson.net/" 24 | }, 25 | "licenses": [ 26 | { 27 | "type": "MIT", 28 | "url": "http://www.opensource.org/licenses/MIT" 29 | } 30 | ], 31 | "main": "./lib/passport-openid", 32 | "dependencies": { 33 | "passport-strategy": "1.x.x", 34 | "openid": "1.x.x" 35 | }, 36 | "devDependencies": { 37 | "vows": "^0.8.1" 38 | }, 39 | "scripts": { 40 | "test": "NODE_PATH=lib node_modules/.bin/vows test/*-test.js test/**/*-test.js" 41 | }, 42 | "engines": { 43 | "node": ">= 0.10.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/errors/internalopeniderror-test.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'); 2 | var assert = require('assert'); 3 | var util = require('util'); 4 | var InternalOpenIDError = require('passport-openid/errors/internalopeniderror'); 5 | 6 | 7 | vows.describe('InternalOpenIDError').addBatch({ 8 | 9 | 'when constructed with only a message': { 10 | topic: function() { 11 | return new InternalOpenIDError('oops'); 12 | }, 13 | 14 | 'should format message properly': function (err) { 15 | assert.equal(err.toString(), 'oops'); 16 | }, 17 | }, 18 | 19 | 'when constructed with a message and error': { 20 | topic: function() { 21 | return new InternalOpenIDError('oops', new Error('something is wrong')); 22 | }, 23 | 24 | 'should format message properly': function (err) { 25 | assert.equal(err.toString(), 'oops (Error: something is wrong)'); 26 | }, 27 | }, 28 | 29 | 'when constructed with a message and object with message': { 30 | topic: function() { 31 | return new InternalOpenIDError('oops', { message: 'invalid OpenID provider' }); 32 | }, 33 | 34 | 'should format message properly': function (err) { 35 | assert.equal(err.toString(), 'oops (message: invalid OpenID provider)'); 36 | }, 37 | }, 38 | 39 | }).export(module); 40 | -------------------------------------------------------------------------------- /test/index-test.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'); 2 | var assert = require('assert'); 3 | var util = require('util'); 4 | var openid = require('passport-openid'); 5 | 6 | 7 | vows.describe('passport-openid').addBatch({ 8 | 9 | 'module': { 10 | 'should export BadRequestError': function (x) { 11 | assert.isFunction(openid.BadRequestError); 12 | assert.isFunction(openid.InternalOpenIDError); 13 | }, 14 | }, 15 | 16 | }).export(module); 17 | -------------------------------------------------------------------------------- /test/strategy-test.js: -------------------------------------------------------------------------------- 1 | var vows = require('vows'); 2 | var assert = require('assert'); 3 | var util = require('util'); 4 | var openid = require('openid'); 5 | var OpenIDStrategy = require('passport-openid/strategy'); 6 | var BadRequestError = require('passport-openid/errors/badrequesterror'); 7 | 8 | vows.describe('OpenIDStrategy').addBatch({ 9 | 10 | 'strategy': { 11 | topic: function() { 12 | return new OpenIDStrategy({ 13 | returnURL: 'https://www.example.com/auth/openid/return', 14 | }, 15 | function() {} 16 | ); 17 | }, 18 | 19 | 'should be named session': function (strategy) { 20 | assert.equal(strategy.name, 'openid'); 21 | }, 22 | }, 23 | 24 | 'strategy handling an authorized request': { 25 | topic: function() { 26 | var strategy = new OpenIDStrategy({ 27 | returnURL: 'https://www.example.com/auth/openid/return', 28 | }, 29 | function(identifier, done) { 30 | done(null, { identifier: identifier }); 31 | } 32 | ); 33 | 34 | // mock 35 | strategy._relyingParty.verifyAssertion = function(url, callback) { 36 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username' }); 37 | } 38 | 39 | return strategy; 40 | }, 41 | 42 | 'after augmenting with actions': { 43 | topic: function(strategy) { 44 | var self = this; 45 | var req = {}; 46 | strategy.success = function(user) { 47 | req.user = user; 48 | self.callback(null, req); 49 | } 50 | strategy.fail = function() { 51 | self.callback(new Error('should not be called')); 52 | } 53 | 54 | req.query = {}; 55 | req.query['openid.mode'] = 'id_res' 56 | process.nextTick(function () { 57 | strategy.authenticate(req); 58 | }); 59 | }, 60 | 61 | 'should not call fail' : function(err, req) { 62 | assert.isNull(err); 63 | }, 64 | 'should authenticate' : function(err, req) { 65 | assert.equal(req.user.identifier, 'http://www.example.com/profiles/username'); 66 | }, 67 | }, 68 | }, 69 | 70 | 'strategy handling an authorized request using req argument to callback': { 71 | topic: function() { 72 | var strategy = new OpenIDStrategy({ 73 | returnURL: 'https://www.example.com/auth/openid/return', 74 | passReqToCallback: true 75 | }, 76 | function(req, identifier, done) { 77 | done(null, { foo: req.foo, identifier: identifier }); 78 | } 79 | ); 80 | 81 | // mock 82 | strategy._relyingParty.verifyAssertion = function(url, callback) { 83 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username' }); 84 | } 85 | 86 | return strategy; 87 | }, 88 | 89 | 'after augmenting with actions': { 90 | topic: function(strategy) { 91 | var self = this; 92 | var req = {}; 93 | req.foo = 'bar'; 94 | strategy.success = function(user) { 95 | req.user = user; 96 | self.callback(null, req); 97 | } 98 | strategy.fail = function() { 99 | self.callback(new Error('should not be called')); 100 | } 101 | 102 | req.query = {}; 103 | req.query['openid.mode'] = 'id_res' 104 | process.nextTick(function () { 105 | strategy.authenticate(req); 106 | }); 107 | }, 108 | 109 | 'should not call fail' : function(err, req) { 110 | assert.isNull(err); 111 | }, 112 | 'should authenticate' : function(err, req) { 113 | assert.equal(req.user.identifier, 'http://www.example.com/profiles/username'); 114 | }, 115 | 'should have request details' : function(err, req) { 116 | assert.equal(req.user.foo, 'bar'); 117 | }, 118 | }, 119 | }, 120 | 121 | 'strategy handling an authorized request with addtional info': { 122 | topic: function() { 123 | var strategy = new OpenIDStrategy({ 124 | returnURL: 'https://www.example.com/auth/openid/return', 125 | }, 126 | function(identifier, done) { 127 | done(null, { identifier: identifier }, { message: 'Welcome!' }); 128 | } 129 | ); 130 | 131 | // mock 132 | strategy._relyingParty.verifyAssertion = function(url, callback) { 133 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username' }); 134 | } 135 | 136 | return strategy; 137 | }, 138 | 139 | 'after augmenting with actions': { 140 | topic: function(strategy) { 141 | var self = this; 142 | var req = {}; 143 | strategy.success = function(user, info) { 144 | req.user = user; 145 | self.callback(null, req, info); 146 | } 147 | strategy.fail = function() { 148 | self.callback(new Error('should not be called')); 149 | } 150 | 151 | req.query = {}; 152 | req.query['openid.mode'] = 'id_res' 153 | process.nextTick(function () { 154 | strategy.authenticate(req); 155 | }); 156 | }, 157 | 158 | 'should not call fail' : function(err, req) { 159 | assert.isNull(err); 160 | }, 161 | 'should authenticate' : function(err, req) { 162 | assert.equal(req.user.identifier, 'http://www.example.com/profiles/username'); 163 | }, 164 | 'should pass additional info' : function(err, user, info) { 165 | assert.equal(info.message, 'Welcome!'); 166 | }, 167 | }, 168 | }, 169 | 170 | 'strategy handling an authorized request with simple registration extensions': { 171 | topic: function() { 172 | var strategy = new OpenIDStrategy({ 173 | returnURL: 'https://www.example.com/auth/openid/return' 174 | }, 175 | function(identifier, profile, done) { 176 | done(null, { identifier: identifier, displayName: profile.displayName, emails: profile.emails }); 177 | } 178 | ); 179 | 180 | // mock 181 | strategy._relyingParty.verifyAssertion = function(url, callback) { 182 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username', 183 | nickname: 'Johnny', 184 | email: 'username@example.com', 185 | fullname: 'John Doe', 186 | dob: '1955-05-25', 187 | gender: 'M', 188 | postcode: '90210', 189 | country: 'US', 190 | language: 'EN', 191 | timezone: 'America/Los_Angeles' 192 | }); 193 | } 194 | 195 | return strategy; 196 | }, 197 | 198 | 'after augmenting with actions': { 199 | topic: function(strategy) { 200 | var self = this; 201 | var req = {}; 202 | strategy.success = function(user) { 203 | req.user = user; 204 | self.callback(null, req); 205 | } 206 | strategy.fail = function() { 207 | self.callback(new Error('should not be called')); 208 | } 209 | 210 | req.query = {}; 211 | req.query['openid.mode'] = 'id_res' 212 | process.nextTick(function () { 213 | strategy.authenticate(req); 214 | }); 215 | }, 216 | 217 | 'should not call fail' : function(err, req) { 218 | assert.isNull(err); 219 | }, 220 | 'should authenticate' : function(err, req) { 221 | assert.equal(req.user.identifier, 'http://www.example.com/profiles/username'); 222 | }, 223 | 'should parse profile' : function(err, req) { 224 | assert.equal(req.user.displayName, 'John Doe'); 225 | assert.lengthOf(req.user.emails, 1); 226 | assert.equal(req.user.emails[0].value, 'username@example.com'); 227 | }, 228 | }, 229 | }, 230 | 231 | 'strategy handling an authorized request with attribute exchange extensions': { 232 | topic: function() { 233 | var strategy = new OpenIDStrategy({ 234 | returnURL: 'https://www.example.com/auth/openid/return' 235 | }, 236 | function(identifier, profile, done) { 237 | done(null, { identifier: identifier, displayName: profile.displayName, name: profile.name, emails: profile.emails }); 238 | } 239 | ); 240 | 241 | // mock 242 | strategy._relyingParty.verifyAssertion = function(url, callback) { 243 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username', 244 | firstname: 'John', 245 | lastname: 'Doe', 246 | email: 'username@example.com' 247 | }); 248 | } 249 | 250 | return strategy; 251 | }, 252 | 253 | 'after augmenting with actions': { 254 | topic: function(strategy) { 255 | var self = this; 256 | var req = {}; 257 | strategy.success = function(user) { 258 | req.user = user; 259 | self.callback(null, req); 260 | } 261 | strategy.fail = function() { 262 | self.callback(new Error('should not be called')); 263 | } 264 | 265 | req.query = {}; 266 | req.query['openid.mode'] = 'id_res' 267 | process.nextTick(function () { 268 | strategy.authenticate(req); 269 | }); 270 | }, 271 | 272 | 'should not call fail' : function(err, req) { 273 | assert.isNull(err); 274 | }, 275 | 'should authenticate' : function(err, req) { 276 | assert.equal(req.user.identifier, 'http://www.example.com/profiles/username'); 277 | }, 278 | 'should parse profile' : function(err, req) { 279 | assert.equal(req.user.displayName, 'John Doe'); 280 | assert.equal(req.user.name.familyName, 'Doe'); 281 | assert.equal(req.user.name.givenName, 'John'); 282 | assert.lengthOf(req.user.emails, 1); 283 | assert.equal(req.user.emails[0].value, 'username@example.com'); 284 | }, 285 | }, 286 | }, 287 | 288 | 'strategy handling an authorized request with profile option using arguments rather than arity': { 289 | topic: function() { 290 | var strategy = new OpenIDStrategy({ 291 | returnURL: 'https://www.example.com/auth/openid/return', 292 | profile: true 293 | }, 294 | function() { 295 | // identifier, profile, done 296 | var identifier = arguments[0]; 297 | var profile = arguments[1]; 298 | var done = arguments[2]; 299 | done(null, { identifier: identifier, displayName: profile.displayName, name: profile.name, emails: profile.emails }); 300 | } 301 | ); 302 | 303 | // mock 304 | strategy._relyingParty.verifyAssertion = function(url, callback) { 305 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username', 306 | firstname: 'John', 307 | lastname: 'Doe', 308 | email: 'username@example.com' 309 | }); 310 | } 311 | 312 | return strategy; 313 | }, 314 | 315 | 'after augmenting with actions': { 316 | topic: function(strategy) { 317 | var self = this; 318 | var req = {}; 319 | strategy.success = function(user) { 320 | req.user = user; 321 | self.callback(null, req); 322 | } 323 | strategy.fail = function() { 324 | self.callback(new Error('should not be called')); 325 | } 326 | 327 | req.query = {}; 328 | req.query['openid.mode'] = 'id_res' 329 | process.nextTick(function () { 330 | strategy.authenticate(req); 331 | }); 332 | }, 333 | 334 | 'should not call fail' : function(err, req) { 335 | assert.isNull(err); 336 | }, 337 | 'should authenticate' : function(err, req) { 338 | assert.equal(req.user.identifier, 'http://www.example.com/profiles/username'); 339 | }, 340 | 'should parse profile' : function(err, req) { 341 | assert.equal(req.user.displayName, 'John Doe'); 342 | assert.equal(req.user.name.familyName, 'Doe'); 343 | assert.equal(req.user.name.givenName, 'John'); 344 | assert.lengthOf(req.user.emails, 1); 345 | assert.equal(req.user.emails[0].value, 'username@example.com'); 346 | }, 347 | }, 348 | }, 349 | 350 | 'strategy handling an authorized request with provider authentication policy extensions (pape)': { 351 | topic: function() { 352 | var strategy = new OpenIDStrategy({ 353 | returnURL: 'https://www.example.com/auth/openid/return', 354 | pape : { 'maxAuthAge' : 600, 'preferredAuthPolicies' : 'multi-factor multi-factor-physical' } 355 | }, 356 | function(identifier, profile, pape, done) { 357 | done(null, { identifier: identifier, profile: profile, pape: pape }); 358 | } 359 | ); 360 | 361 | // mock 362 | strategy._relyingParty.verifyAssertion = function(url, callback) { 363 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username', 364 | 'auth_policies' : 'none', 'auth_time': new Date() } ); 365 | } 366 | 367 | return strategy; 368 | }, 369 | 370 | 'after augmenting with actions': { 371 | topic: function(strategy) { 372 | var self = this; 373 | var req = {}; 374 | strategy.success = function(user) { 375 | req.user = user; 376 | self.callback(null, req); 377 | } 378 | strategy.fail = function() { 379 | self.callback(new Error('should not be called')); 380 | } 381 | 382 | req.query = {}; 383 | req.query['openid.mode'] = 'id_res' 384 | process.nextTick(function () { 385 | strategy.authenticate(req); 386 | }); 387 | }, 388 | 389 | 'should not call fail' : function(err, req) { 390 | assert.isNull(err); 391 | }, 392 | 'should authenticate' : function(err, req) { 393 | assert.equal(req.user.identifier, 'http://www.example.com/profiles/username'); 394 | }, 395 | 'should not parse profile' : function(err, req) { 396 | assert.equal(req.user.profile.displayName, undefined); 397 | }, 398 | 'should parse pape' : function(err, req) { 399 | assert.lengthOf(req.user.pape.authPolicies, 1); 400 | assert.instanceOf(req.user.pape.authTime, Date); 401 | }, 402 | }, 403 | }, 404 | 405 | 'strategy handling an authorized request with profile and provider authentication policy extensions (pape)': { 406 | topic: function() { 407 | var strategy = new OpenIDStrategy({ 408 | returnURL: 'https://www.example.com/auth/openid/return', 409 | profile: true, 410 | pape : { 'maxAuthAge' : 600, 'preferredAuthPolicies' : 'multi-factor multi-factor-physical' } 411 | }, 412 | function(identifier, profile, pape, done) { 413 | done(null, { identifier: identifier, profile: profile, pape: pape }); 414 | } 415 | ); 416 | 417 | // mock 418 | strategy._relyingParty.verifyAssertion = function(url, callback) { 419 | callback(null, { authenticated: true, 420 | claimedIdentifier: 'http://jaredhanson.net', 421 | firstname: 'Jared', 422 | lastname: 'Hanson', 423 | email: 'jaredhanson@example.com', 424 | auth_time: '2013-02-11T02:01:47Z', 425 | auth_policies: 'none' } ); 426 | } 427 | 428 | return strategy; 429 | }, 430 | 431 | 'after augmenting with actions': { 432 | topic: function(strategy) { 433 | var self = this; 434 | var req = {}; 435 | strategy.success = function(user) { 436 | req.user = user; 437 | self.callback(null, req); 438 | } 439 | strategy.fail = function() { 440 | self.callback(new Error('should not be called')); 441 | } 442 | 443 | req.query = {}; 444 | req.query['openid.mode'] = 'id_res' 445 | process.nextTick(function () { 446 | strategy.authenticate(req); 447 | }); 448 | }, 449 | 450 | 'should not call fail' : function(err, req) { 451 | assert.isNull(err); 452 | }, 453 | 'should authenticate' : function(err, req) { 454 | assert.equal(req.user.identifier, 'http://jaredhanson.net'); 455 | }, 456 | 'should parse profile' : function(err, req) { 457 | assert.equal(req.user.profile.displayName, 'Jared Hanson'); 458 | }, 459 | 'should parse pape' : function(err, req) { 460 | assert.lengthOf(req.user.pape.authPolicies, 1); 461 | assert.instanceOf(req.user.pape.authTime, Date); 462 | }, 463 | }, 464 | }, 465 | 466 | 'strategy handling an authorized request with oauth extensions': { 467 | topic: function() { 468 | var strategy = new OpenIDStrategy({ 469 | returnURL: 'https://www.example.com/auth/openid/return', 470 | oauth: { consumerKey: 'foo', scope: 'https://www.google.com/m8/feeds' } 471 | }, 472 | function(identifier, profile, pape, oauth, done) { 473 | done(null, { identifier: identifier, profile: profile, pape: pape, oauth: oauth }); 474 | } 475 | ); 476 | 477 | // mock 478 | strategy._relyingParty.verifyAssertion = function(url, callback) { 479 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username', 480 | 'request_token' : 'some-secret-token' } ); 481 | } 482 | 483 | return strategy; 484 | }, 485 | 486 | 'after augmenting with actions': { 487 | topic: function(strategy) { 488 | var self = this; 489 | var req = {}; 490 | strategy.success = function(user) { 491 | req.user = user; 492 | self.callback(null, req); 493 | } 494 | strategy.fail = function() { 495 | self.callback(new Error('should not be called')); 496 | } 497 | 498 | req.query = {}; 499 | req.query['openid.mode'] = 'id_res' 500 | process.nextTick(function () { 501 | strategy.authenticate(req); 502 | }); 503 | }, 504 | 505 | 'should not call fail' : function(err, req) { 506 | assert.isNull(err); 507 | }, 508 | 'should authenticate' : function(err, req) { 509 | assert.equal(req.user.identifier, 'http://www.example.com/profiles/username'); 510 | }, 511 | 'should not parse profile' : function(err, req) { 512 | assert.equal(req.user.profile.displayName, undefined); 513 | }, 514 | 'should not parse pape' : function(err, req) { 515 | assert.lengthOf(Object.keys(req.user.pape), 0); 516 | }, 517 | 'should parse oauth' : function(err, req) { 518 | assert.equal(req.user.oauth.requestToken, 'some-secret-token'); 519 | }, 520 | }, 521 | }, 522 | 523 | 'strategy handling an authorized request with attribute exchange extensions using req argument to callback': { 524 | topic: function() { 525 | var strategy = new OpenIDStrategy({ 526 | returnURL: 'https://www.example.com/auth/openid/return', 527 | passReqToCallback: true 528 | }, 529 | function(req, identifier, profile, done) { 530 | done(null, { foo: req.foo, identifier: identifier, displayName: profile.displayName, name: profile.name, emails: profile.emails }); 531 | } 532 | ); 533 | 534 | // mock 535 | strategy._relyingParty.verifyAssertion = function(url, callback) { 536 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username', 537 | firstname: 'John', 538 | lastname: 'Doe', 539 | email: 'username@example.com' 540 | }); 541 | } 542 | 543 | return strategy; 544 | }, 545 | 546 | 'after augmenting with actions': { 547 | topic: function(strategy) { 548 | var self = this; 549 | var req = {}; 550 | req.foo = 'bar'; 551 | strategy.success = function(user) { 552 | req.user = user; 553 | self.callback(null, req); 554 | } 555 | strategy.fail = function() { 556 | self.callback(new Error('should not be called')); 557 | } 558 | 559 | req.query = {}; 560 | req.query['openid.mode'] = 'id_res' 561 | process.nextTick(function () { 562 | strategy.authenticate(req); 563 | }); 564 | }, 565 | 566 | 'should not call fail' : function(err, req) { 567 | assert.isNull(err); 568 | }, 569 | 'should authenticate' : function(err, req) { 570 | assert.equal(req.user.identifier, 'http://www.example.com/profiles/username'); 571 | }, 572 | 'should have request details' : function(err, req) { 573 | assert.equal(req.user.foo, 'bar'); 574 | }, 575 | 'should parse profile' : function(err, req) { 576 | assert.equal(req.user.displayName, 'John Doe'); 577 | assert.equal(req.user.name.familyName, 'Doe'); 578 | assert.equal(req.user.name.givenName, 'John'); 579 | assert.lengthOf(req.user.emails, 1); 580 | assert.equal(req.user.emails[0].value, 'username@example.com'); 581 | }, 582 | }, 583 | }, 584 | 585 | 'strategy handling an authorized request with profile option and req argument to callback using arguments rather than arity': { 586 | topic: function() { 587 | var strategy = new OpenIDStrategy({ 588 | returnURL: 'https://www.example.com/auth/openid/return', 589 | profile: true, 590 | passReqToCallback: true 591 | }, 592 | function() { 593 | // identifier, profile, done 594 | var req = arguments[0]; 595 | var identifier = arguments[1]; 596 | var profile = arguments[2]; 597 | var done = arguments[3]; 598 | 599 | done(null, { foo: req.foo, identifier: identifier, displayName: profile.displayName, name: profile.name, emails: profile.emails }); 600 | } 601 | ); 602 | 603 | // mock 604 | strategy._relyingParty.verifyAssertion = function(url, callback) { 605 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username', 606 | firstname: 'John', 607 | lastname: 'Doe', 608 | email: 'username@example.com' 609 | }); 610 | } 611 | 612 | return strategy; 613 | }, 614 | 615 | 'after augmenting with actions': { 616 | topic: function(strategy) { 617 | var self = this; 618 | var req = {}; 619 | req.foo = 'bar'; 620 | strategy.success = function(user) { 621 | req.user = user; 622 | self.callback(null, req); 623 | } 624 | strategy.fail = function() { 625 | self.callback(new Error('should not be called')); 626 | } 627 | 628 | req.query = {}; 629 | req.query['openid.mode'] = 'id_res' 630 | process.nextTick(function () { 631 | strategy.authenticate(req); 632 | }); 633 | }, 634 | 635 | 'should not call fail' : function(err, req) { 636 | assert.isNull(err); 637 | }, 638 | 'should authenticate' : function(err, req) { 639 | assert.equal(req.user.identifier, 'http://www.example.com/profiles/username'); 640 | }, 641 | 'should have request details' : function(err, req) { 642 | assert.equal(req.user.foo, 'bar'); 643 | }, 644 | 'should parse profile' : function(err, req) { 645 | assert.equal(req.user.displayName, 'John Doe'); 646 | assert.equal(req.user.name.familyName, 'Doe'); 647 | assert.equal(req.user.name.givenName, 'John'); 648 | assert.lengthOf(req.user.emails, 1); 649 | assert.equal(req.user.emails[0].value, 'username@example.com'); 650 | }, 651 | }, 652 | }, 653 | 654 | 'strategy handling an authorized request with provider authentication policy extensions (pape) using req argument to callback': { 655 | topic: function() { 656 | var strategy = new OpenIDStrategy({ 657 | returnURL: 'https://www.example.com/auth/openid/return', 658 | pape : { 'maxAuthAge' : 600, 'preferredAuthPolicies' : 'multi-factor multi-factor-physical' }, 659 | passReqToCallback: true 660 | }, 661 | function(req, identifier, profile, pape, done) { 662 | done(null, { identifier: identifier, profile: profile, pape: pape }); 663 | } 664 | ); 665 | 666 | // mock 667 | strategy._relyingParty.verifyAssertion = function(url, callback) { 668 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username', 669 | 'auth_policies' : 'none', 'auth_time': new Date() } ); 670 | } 671 | 672 | return strategy; 673 | }, 674 | 675 | 'after augmenting with actions': { 676 | topic: function(strategy) { 677 | var self = this; 678 | var req = {}; 679 | strategy.success = function(user) { 680 | req.user = user; 681 | self.callback(null, req); 682 | } 683 | strategy.fail = function() { 684 | self.callback(new Error('should not be called')); 685 | } 686 | 687 | req.query = {}; 688 | req.query['openid.mode'] = 'id_res' 689 | process.nextTick(function () { 690 | strategy.authenticate(req); 691 | }); 692 | }, 693 | 694 | 'should not call fail' : function(err, req) { 695 | assert.isNull(err); 696 | }, 697 | 'should authenticate' : function(err, req) { 698 | assert.equal(req.user.identifier, 'http://www.example.com/profiles/username'); 699 | }, 700 | 'should not parse profile' : function(err, req) { 701 | assert.equal(req.user.profile.displayName, undefined); 702 | }, 703 | 'should parse pape' : function(err, req) { 704 | assert.lengthOf(req.user.pape.authPolicies, 1); 705 | assert.instanceOf(req.user.pape.authTime, Date); 706 | }, 707 | }, 708 | }, 709 | 710 | 'strategy handling an authorized request with oauth extensions using req argument to callback': { 711 | topic: function() { 712 | var strategy = new OpenIDStrategy({ 713 | returnURL: 'https://www.example.com/auth/openid/return', 714 | oauth: { consumerKey: 'foo', scope: 'https://www.google.com/m8/feeds' }, 715 | passReqToCallback: true 716 | }, 717 | function(req, identifier, profile, pape, oauth, done) { 718 | done(null, { identifier: identifier, profile: profile, pape: pape, oauth: oauth }); 719 | } 720 | ); 721 | 722 | // mock 723 | strategy._relyingParty.verifyAssertion = function(url, callback) { 724 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username', 725 | 'request_token' : 'some-secret-token' } ); 726 | } 727 | 728 | return strategy; 729 | }, 730 | 731 | 'after augmenting with actions': { 732 | topic: function(strategy) { 733 | var self = this; 734 | var req = {}; 735 | strategy.success = function(user) { 736 | req.user = user; 737 | self.callback(null, req); 738 | } 739 | strategy.fail = function() { 740 | self.callback(new Error('should not be called')); 741 | } 742 | 743 | req.query = {}; 744 | req.query['openid.mode'] = 'id_res' 745 | process.nextTick(function () { 746 | strategy.authenticate(req); 747 | }); 748 | }, 749 | 750 | 'should not call fail' : function(err, req) { 751 | assert.isNull(err); 752 | }, 753 | 'should authenticate' : function(err, req) { 754 | assert.equal(req.user.identifier, 'http://www.example.com/profiles/username'); 755 | }, 756 | 'should not parse profile' : function(err, req) { 757 | assert.equal(req.user.profile.displayName, undefined); 758 | }, 759 | 'should not parse pape' : function(err, req) { 760 | assert.lengthOf(Object.keys(req.user.pape), 0); 761 | }, 762 | 'should parse oauth' : function(err, req) { 763 | assert.equal(req.user.oauth.requestToken, 'some-secret-token'); 764 | }, 765 | }, 766 | }, 767 | 768 | 'strategy handling an authorized request that encounters an error while verifying assertion': { 769 | topic: function() { 770 | var strategy = new OpenIDStrategy({ 771 | returnURL: 'https://www.example.com/auth/openid/return', 772 | }, 773 | function(identifier, done) { 774 | done(null, { identifier: identifier }); 775 | } 776 | ); 777 | 778 | // mock 779 | strategy._relyingParty.verifyAssertion = function(url, callback) { 780 | callback(new Error('something went wrong')); 781 | } 782 | 783 | return strategy; 784 | }, 785 | 786 | 'after augmenting with actions': { 787 | topic: function(strategy) { 788 | var self = this; 789 | var req = {}; 790 | strategy.success = function(user) { 791 | self.callback(new Error('should not be called')); 792 | } 793 | strategy.fail = function() { 794 | self.callback(new Error('should not be called')); 795 | } 796 | strategy.error = function(err) { 797 | self.callback(null, req); 798 | } 799 | 800 | req.query = {}; 801 | req.query['openid.mode'] = 'id_res' 802 | process.nextTick(function () { 803 | strategy.authenticate(req); 804 | }); 805 | }, 806 | 807 | 'should not call success or fail' : function(err, req) { 808 | assert.isNull(err); 809 | }, 810 | 'should call error' : function(err, req) { 811 | assert.isNotNull(req); 812 | }, 813 | }, 814 | }, 815 | 816 | 'strategy handling an authorized request that is not authenticated after verifying assertion': { 817 | topic: function() { 818 | var strategy = new OpenIDStrategy({ 819 | returnURL: 'https://www.example.com/auth/openid/return', 820 | }, 821 | function(identifier, done) { 822 | done(null, { identifier: identifier }); 823 | } 824 | ); 825 | 826 | // mock 827 | strategy._relyingParty.verifyAssertion = function(url, callback) { 828 | callback(null, { authenticated: false }); 829 | } 830 | 831 | return strategy; 832 | }, 833 | 834 | 'after augmenting with actions': { 835 | topic: function(strategy) { 836 | var self = this; 837 | var req = {}; 838 | strategy.success = function(user) { 839 | self.callback(new Error('should not be called')); 840 | } 841 | strategy.fail = function() { 842 | self.callback(new Error('should not be called')); 843 | } 844 | strategy.error = function(err) { 845 | self.callback(null, req); 846 | } 847 | 848 | req.query = {}; 849 | req.query['openid.mode'] = 'id_res' 850 | process.nextTick(function () { 851 | strategy.authenticate(req); 852 | }); 853 | }, 854 | 855 | 'should not call success or fail' : function(err, req) { 856 | assert.isNull(err); 857 | }, 858 | 'should call error' : function(err, req) { 859 | assert.isNotNull(req); 860 | }, 861 | }, 862 | }, 863 | 864 | 'strategy handling an authorized request that is not validated': { 865 | topic: function() { 866 | var strategy = new OpenIDStrategy({ 867 | returnURL: 'https://www.example.com/auth/openid/return', 868 | }, 869 | function(identifier, done) { 870 | done(null, false); 871 | } 872 | ); 873 | 874 | // mock 875 | strategy._relyingParty.verifyAssertion = function(url, callback) { 876 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username' }); 877 | } 878 | 879 | return strategy; 880 | }, 881 | 882 | 'after augmenting with actions': { 883 | topic: function(strategy) { 884 | var self = this; 885 | var req = {}; 886 | strategy.success = function(user) { 887 | self.callback(new Error('should not be called')); 888 | } 889 | strategy.fail = function() { 890 | self.callback(null, req); 891 | } 892 | 893 | req.query = {}; 894 | req.query['openid.mode'] = 'id_res' 895 | process.nextTick(function () { 896 | strategy.authenticate(req); 897 | }); 898 | }, 899 | 900 | 'should not call success' : function(err, req) { 901 | assert.isNull(err); 902 | }, 903 | 'should call fail' : function(err, req) { 904 | assert.isNotNull(req); 905 | }, 906 | }, 907 | }, 908 | 909 | 'strategy handling an authorized request that is not validated with addtional info': { 910 | topic: function() { 911 | var strategy = new OpenIDStrategy({ 912 | returnURL: 'https://www.example.com/auth/openid/return', 913 | }, 914 | function(identifier, done) { 915 | done(null, false, { message: 'Unwelcome.' }); 916 | } 917 | ); 918 | 919 | // mock 920 | strategy._relyingParty.verifyAssertion = function(url, callback) { 921 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username' }); 922 | } 923 | 924 | return strategy; 925 | }, 926 | 927 | 'after augmenting with actions': { 928 | topic: function(strategy) { 929 | var self = this; 930 | var req = {}; 931 | strategy.success = function(user) { 932 | self.callback(new Error('should not be called')); 933 | } 934 | strategy.fail = function(info) { 935 | self.callback(null, req, info); 936 | } 937 | 938 | req.query = {}; 939 | req.query['openid.mode'] = 'id_res' 940 | process.nextTick(function () { 941 | strategy.authenticate(req); 942 | }); 943 | }, 944 | 945 | 'should not call success' : function(err, req) { 946 | assert.isNull(err); 947 | }, 948 | 'should call fail' : function(err, req) { 949 | assert.isNotNull(req); 950 | }, 951 | 'should pass additional info' : function(err, req, info) { 952 | assert.equal(info.message, 'Unwelcome.'); 953 | }, 954 | }, 955 | }, 956 | 957 | 'strategy handling an authorized request that encounters an error during validation': { 958 | topic: function() { 959 | var strategy = new OpenIDStrategy({ 960 | returnURL: 'https://www.example.com/auth/openid/return', 961 | }, 962 | function(identifier, done) { 963 | done(new Error('something went wrong')); 964 | } 965 | ); 966 | 967 | // mock 968 | strategy._relyingParty.verifyAssertion = function(url, callback) { 969 | callback(null, { authenticated: true, claimedIdentifier: 'http://www.example.com/profiles/username' }); 970 | } 971 | 972 | return strategy; 973 | }, 974 | 975 | 'after augmenting with actions': { 976 | topic: function(strategy) { 977 | var self = this; 978 | var req = {}; 979 | strategy.success = function(user) { 980 | self.callback(new Error('should not be called')); 981 | } 982 | strategy.fail = function() { 983 | self.callback(new Error('should not be called')); 984 | } 985 | strategy.error = function(err) { 986 | self.callback(null, req); 987 | } 988 | 989 | req.query = {}; 990 | req.query['openid.mode'] = 'id_res' 991 | process.nextTick(function () { 992 | strategy.authenticate(req); 993 | }); 994 | }, 995 | 996 | 'should not call success or fail' : function(err, req) { 997 | assert.isNull(err); 998 | }, 999 | 'should call error' : function(err, req) { 1000 | assert.isNotNull(req); 1001 | }, 1002 | }, 1003 | }, 1004 | 1005 | 'strategy handling an authentication canceled request': { 1006 | topic: function() { 1007 | var strategy = new OpenIDStrategy({ 1008 | returnURL: 'https://www.example.com/auth/openid/return', 1009 | }, 1010 | function(identifier, done) { 1011 | done(null, { identifier: identifier }); 1012 | } 1013 | ); 1014 | 1015 | return strategy; 1016 | }, 1017 | 1018 | 'after augmenting with actions': { 1019 | topic: function(strategy) { 1020 | var self = this; 1021 | var req = {}; 1022 | strategy.success = function(user) { 1023 | self.callback(new Error('should not be called')); 1024 | } 1025 | strategy.fail = function(info) { 1026 | self.callback(null, req, info); 1027 | } 1028 | 1029 | req.query = {}; 1030 | req.query['openid.mode'] = 'cancel' 1031 | process.nextTick(function () { 1032 | strategy.authenticate(req); 1033 | }); 1034 | }, 1035 | 1036 | 'should not call success' : function(err, req) { 1037 | assert.isNull(err); 1038 | }, 1039 | 'should call fail' : function(err, req) { 1040 | assert.isNotNull(req); 1041 | }, 1042 | 'should pass canceled message as additional info' : function(err, req, info) { 1043 | assert.equal(info.message, 'OpenID authentication canceled'); 1044 | }, 1045 | }, 1046 | }, 1047 | 1048 | 'strategy handling a request to be redirected for authentication with identifier in body': { 1049 | topic: function() { 1050 | var strategy = new OpenIDStrategy({ 1051 | returnURL: 'https://www.example.com/auth/openid/return', 1052 | }, 1053 | function(identifier, done) { 1054 | done(null, { identifier: identifier }); 1055 | } 1056 | ); 1057 | 1058 | // mock 1059 | strategy._relyingParty.authenticate = function(identifier, immediate, callback) { 1060 | callback(null, 'http://provider.example.com/openid' + '#' + identifier); 1061 | } 1062 | 1063 | return strategy; 1064 | }, 1065 | 1066 | 'after augmenting with actions': { 1067 | topic: function(strategy) { 1068 | var self = this; 1069 | var req = {}; 1070 | strategy.success = function(user) { 1071 | self.callback(new Error('should not be called')); 1072 | } 1073 | strategy.fail = function() { 1074 | self.callback(new Error('should not be called')); 1075 | } 1076 | strategy.redirect = function(url) { 1077 | req.redirectURL = url; 1078 | self.callback(null, req); 1079 | } 1080 | 1081 | req.body = {}; 1082 | req.body['openid_identifier'] = 'http://www.example.me/' 1083 | process.nextTick(function () { 1084 | strategy.authenticate(req); 1085 | }); 1086 | }, 1087 | 1088 | 'should not call success or fail' : function(err, req) { 1089 | assert.isNull(err); 1090 | }, 1091 | 'should redirect to user OpenID provider URL' : function(err, req) { 1092 | assert.equal(req.redirectURL, 'http://provider.example.com/openid#http://www.example.me/'); 1093 | }, 1094 | }, 1095 | }, 1096 | 1097 | 'strategy handling a request to be redirected for authentication with identifier in query': { 1098 | topic: function() { 1099 | var strategy = new OpenIDStrategy({ 1100 | returnURL: 'https://www.example.com/auth/openid/return', 1101 | }, 1102 | function(identifier, done) { 1103 | done(null, { identifier: identifier }); 1104 | } 1105 | ); 1106 | 1107 | // mock 1108 | strategy._relyingParty.authenticate = function(identifier, immediate, callback) { 1109 | callback(null, 'http://provider.example.com/openid' + '#' + identifier); 1110 | } 1111 | 1112 | return strategy; 1113 | }, 1114 | 1115 | 'after augmenting with actions': { 1116 | topic: function(strategy) { 1117 | var self = this; 1118 | var req = {}; 1119 | strategy.success = function(user) { 1120 | self.callback(new Error('should not be called')); 1121 | } 1122 | strategy.fail = function() { 1123 | self.callback(new Error('should not be called')); 1124 | } 1125 | strategy.redirect = function(url) { 1126 | req.redirectURL = url; 1127 | self.callback(null, req); 1128 | } 1129 | 1130 | req.query = {}; 1131 | req.query['openid_identifier'] = 'http://www.example.me/' 1132 | process.nextTick(function () { 1133 | strategy.authenticate(req); 1134 | }); 1135 | }, 1136 | 1137 | 'should not call success or fail' : function(err, req) { 1138 | assert.isNull(err); 1139 | }, 1140 | 'should redirect to user OpenID provider URL' : function(err, req) { 1141 | assert.equal(req.redirectURL, 'http://provider.example.com/openid#http://www.example.me/'); 1142 | }, 1143 | }, 1144 | }, 1145 | 1146 | 'strategy handling a request to be redirected for authentication with identifier in body and identifierField option set': { 1147 | topic: function() { 1148 | var strategy = new OpenIDStrategy({ 1149 | identifierField: 'identifier', 1150 | returnURL: 'https://www.example.com/auth/openid/return', 1151 | }, 1152 | function(identifier, done) { 1153 | done(null, { identifier: identifier }); 1154 | } 1155 | ); 1156 | 1157 | // mock 1158 | strategy._relyingParty.authenticate = function(identifier, immediate, callback) { 1159 | callback(null, 'http://provider.example.com/openid' + '#' + identifier); 1160 | } 1161 | 1162 | return strategy; 1163 | }, 1164 | 1165 | 'after augmenting with actions': { 1166 | topic: function(strategy) { 1167 | var self = this; 1168 | var req = {}; 1169 | strategy.success = function(user) { 1170 | self.callback(new Error('should not be called')); 1171 | } 1172 | strategy.fail = function() { 1173 | self.callback(new Error('should not be called')); 1174 | } 1175 | strategy.redirect = function(url) { 1176 | req.redirectURL = url; 1177 | self.callback(null, req); 1178 | } 1179 | 1180 | req.body = {}; 1181 | req.body['identifier'] = 'http://www.example.me/' 1182 | process.nextTick(function () { 1183 | strategy.authenticate(req); 1184 | }); 1185 | }, 1186 | 1187 | 'should not call success or fail' : function(err, req) { 1188 | assert.isNull(err); 1189 | }, 1190 | 'should redirect to user OpenID provider URL' : function(err, req) { 1191 | assert.equal(req.redirectURL, 'http://provider.example.com/openid#http://www.example.me/'); 1192 | }, 1193 | }, 1194 | }, 1195 | 1196 | 'strategy handling a request to be redirected for authentication with provider URL option set': { 1197 | topic: function() { 1198 | var strategy = new OpenIDStrategy({ 1199 | providerURL: 'http://provider.example.net/openid', 1200 | returnURL: 'https://www.example.com/auth/openid/return' 1201 | }, 1202 | function(identifier, done) { 1203 | done(null, { identifier: identifier }); 1204 | } 1205 | ); 1206 | 1207 | // mock 1208 | strategy._relyingParty.authenticate = function(identifier, immediate, callback) { 1209 | callback(null, 'http://provider.example.com/openid' + '#' + identifier); 1210 | } 1211 | 1212 | return strategy; 1213 | }, 1214 | 1215 | 'after augmenting with actions': { 1216 | topic: function(strategy) { 1217 | var self = this; 1218 | var req = {}; 1219 | strategy.success = function(user) { 1220 | self.callback(new Error('should not be called')); 1221 | } 1222 | strategy.fail = function() { 1223 | self.callback(new Error('should not be called')); 1224 | } 1225 | strategy.redirect = function(url) { 1226 | req.redirectURL = url; 1227 | self.callback(null, req); 1228 | } 1229 | 1230 | process.nextTick(function () { 1231 | strategy.authenticate(req); 1232 | }); 1233 | }, 1234 | 1235 | 'should not call success or fail' : function(err, req) { 1236 | assert.isNull(err); 1237 | }, 1238 | 'should redirect to user OpenID provider URL' : function(err, req) { 1239 | assert.equal(req.redirectURL, 'http://provider.example.com/openid#http://provider.example.net/openid'); 1240 | }, 1241 | }, 1242 | }, 1243 | 1244 | 'strategy handling a request to be redirected with an undefined identifier': { 1245 | topic: function() { 1246 | var strategy = new OpenIDStrategy({ 1247 | returnURL: 'https://www.example.com/auth/openid/return', 1248 | }, 1249 | function(identifier, done) { 1250 | done(null, { identifier: identifier }); 1251 | } 1252 | ); 1253 | 1254 | // mock 1255 | strategy._relyingParty.authenticate = function(identifier, immediate, callback) { 1256 | callback(null, 'http://provider.example.com/openid'); 1257 | } 1258 | 1259 | return strategy; 1260 | }, 1261 | 1262 | 'after augmenting with actions': { 1263 | topic: function(strategy) { 1264 | var self = this; 1265 | var req = {}; 1266 | strategy.success = function(user) { 1267 | self.callback(new Error('should not be called')); 1268 | } 1269 | strategy.fail = function(info) { 1270 | self.callback(null, req, info); 1271 | } 1272 | strategy.error = function(err) { 1273 | self.callback(new Error('should not be called')); 1274 | } 1275 | 1276 | process.nextTick(function () { 1277 | strategy.authenticate(req); 1278 | }); 1279 | }, 1280 | 1281 | 'should not call success or error' : function(err, req) { 1282 | assert.isNull(err); 1283 | }, 1284 | 'should call fail' : function(err, req) { 1285 | assert.isNotNull(req); 1286 | }, 1287 | 'should pass BadReqestError as additional info' : function(err, req, info) { 1288 | assert.instanceOf(info, Error); 1289 | assert.instanceOf(info, BadRequestError); 1290 | }, 1291 | }, 1292 | }, 1293 | 1294 | 'strategy handling a request to be redirected that encouters an error during discovery': { 1295 | topic: function() { 1296 | var strategy = new OpenIDStrategy({ 1297 | returnURL: 'https://www.example.com/auth/openid/return', 1298 | }, 1299 | function(identifier, done) { 1300 | done(null, { identifier: identifier }); 1301 | } 1302 | ); 1303 | 1304 | // mock 1305 | strategy._relyingParty.authenticate = function(identifier, immediate, callback) { 1306 | callback(new Error('something went wrong')); 1307 | } 1308 | 1309 | return strategy; 1310 | }, 1311 | 1312 | 'after augmenting with actions': { 1313 | topic: function(strategy) { 1314 | var self = this; 1315 | var req = {}; 1316 | strategy.success = function(user) { 1317 | self.callback(new Error('should not be called')); 1318 | } 1319 | strategy.fail = function() { 1320 | self.callback(new Error('should not be called')); 1321 | } 1322 | strategy.error = function(err) { 1323 | self.callback(null, req); 1324 | } 1325 | 1326 | req.body = {}; 1327 | req.body['openid_identifier'] = 'http://www.example.me/' 1328 | process.nextTick(function () { 1329 | strategy.authenticate(req); 1330 | }); 1331 | }, 1332 | 1333 | 'should not call success or fail' : function(err, req) { 1334 | assert.isNull(err); 1335 | }, 1336 | 'should call error' : function(err, req) { 1337 | assert.isNotNull(req); 1338 | }, 1339 | }, 1340 | }, 1341 | 1342 | 'strategy handling a request to be redirected that does not find a provider during discovery': { 1343 | topic: function() { 1344 | var strategy = new OpenIDStrategy({ 1345 | returnURL: 'https://www.example.com/auth/openid/return', 1346 | }, 1347 | function(identifier, done) { 1348 | done(null, { identifier: identifier }); 1349 | } 1350 | ); 1351 | 1352 | // mock 1353 | strategy._relyingParty.authenticate = function(identifier, immediate, callback) { 1354 | callback(null, null); 1355 | } 1356 | 1357 | return strategy; 1358 | }, 1359 | 1360 | 'after augmenting with actions': { 1361 | topic: function(strategy) { 1362 | var self = this; 1363 | var req = {}; 1364 | strategy.success = function(user) { 1365 | self.callback(new Error('should not be called')); 1366 | } 1367 | strategy.fail = function() { 1368 | self.callback(new Error('should not be called')); 1369 | } 1370 | strategy.error = function(err) { 1371 | self.callback(null, req); 1372 | } 1373 | 1374 | req.body = {}; 1375 | req.body['openid_identifier'] = 'http://www.example.me/' 1376 | process.nextTick(function () { 1377 | strategy.authenticate(req); 1378 | }); 1379 | }, 1380 | 1381 | 'should not call success or fail' : function(err, req) { 1382 | assert.isNull(err); 1383 | }, 1384 | 'should call error' : function(err, req) { 1385 | assert.isNotNull(req); 1386 | }, 1387 | }, 1388 | }, 1389 | 1390 | 'strategy with saveAssociation function': { 1391 | topic: function() { 1392 | var strategy = new OpenIDStrategy({ 1393 | returnURL: 'https://www.example.com/auth/openid/return', 1394 | }, 1395 | function(identifier, done) { 1396 | done(null, { identifier: identifier }); 1397 | } 1398 | ); 1399 | 1400 | strategy.saveAssociation(function(handle, provider, algorithm, secret, expiresIn, done) { 1401 | strategy._args = {}; 1402 | strategy._args.handle = handle; 1403 | strategy._args.provider = provider; 1404 | strategy._args.algorithm = algorithm; 1405 | strategy._args.secret = secret; 1406 | strategy._args.expiresIn = expiresIn; 1407 | done(); 1408 | }); 1409 | return strategy; 1410 | }, 1411 | 1412 | 'after calling openid.saveAssociation': { 1413 | topic: function(strategy) { 1414 | var self = this; 1415 | var provider = { endpoint: 'https://www.google.com/accounts/o8/ud', 1416 | version: 'http://specs.openid.net/auth/2.0', 1417 | localIdentifier: 'http://www.google.com/profiles/jaredhanson', 1418 | claimedIdentifier: 'http://jaredhanson.net' }; 1419 | var hashAlgorithm = 'sha256'; 1420 | var handle = 'foo-xyz-123'; 1421 | var secret = 'shh-its-secret'; 1422 | var expires = 46799; 1423 | 1424 | openid.saveAssociation(provider, hashAlgorithm, handle, secret, expires, function() { 1425 | self.callback(null, strategy); 1426 | }); 1427 | }, 1428 | 1429 | 'should call registered function' : function(err, strategy) { 1430 | assert.equal(strategy._args.handle, 'foo-xyz-123'); 1431 | assert.equal(strategy._args.provider.endpoint, 'https://www.google.com/accounts/o8/ud'); 1432 | assert.equal(strategy._args.algorithm, 'sha256'); 1433 | assert.equal(strategy._args.secret, 'shh-its-secret'); 1434 | assert.equal(strategy._args.expiresIn, 46799); 1435 | }, 1436 | }, 1437 | }, 1438 | 1439 | 'strategy with loadAssociation function': { 1440 | topic: function() { 1441 | var strategy = new OpenIDStrategy({ 1442 | returnURL: 'https://www.example.com/auth/openid/return', 1443 | }, 1444 | function(identifier, done) { 1445 | done(null, { identifier: identifier }); 1446 | } 1447 | ); 1448 | 1449 | strategy.loadAssociation(function(handle, done) { 1450 | var provider = { endpoint: 'https://www.google.com/accounts/o8/ud', 1451 | version: 'http://specs.openid.net/auth/2.0', 1452 | localIdentifier: 'http://www.google.com/profiles/jaredhanson', 1453 | claimedIdentifier: 'http://jaredhanson.net' }; 1454 | 1455 | strategy._args = {}; 1456 | strategy._args.handle = handle; 1457 | done(null, provider, 'sha256', 'shh-its-secret'); 1458 | }); 1459 | return strategy; 1460 | }, 1461 | 1462 | 'after calling openid.loadAssociation': { 1463 | topic: function(strategy) { 1464 | var self = this; 1465 | var handle = 'foo-xyz-123'; 1466 | 1467 | openid.loadAssociation(handle, function(err, association) { 1468 | self.callback(null, strategy, association); 1469 | }); 1470 | }, 1471 | 1472 | 'should call registered function' : function(err, strategy) { 1473 | assert.equal(strategy._args.handle, 'foo-xyz-123'); 1474 | }, 1475 | 'should supply provider' : function(err, strategy, association) { 1476 | assert.equal(association.provider.endpoint, 'https://www.google.com/accounts/o8/ud'); 1477 | assert.equal(association.type, 'sha256'); 1478 | assert.equal(association.secret, 'shh-its-secret'); 1479 | }, 1480 | }, 1481 | }, 1482 | 1483 | 'strategy with saveDiscoveredInfo function': { 1484 | topic: function() { 1485 | var strategy = new OpenIDStrategy({ 1486 | returnURL: 'https://www.example.com/auth/openid/return', 1487 | }, 1488 | function(identifier, done) { 1489 | done(null, { identifier: identifier }); 1490 | } 1491 | ); 1492 | 1493 | strategy.saveDiscoveredInfo(function(identifier, provider, done) { 1494 | strategy._args = {}; 1495 | strategy._args.identifier = identifier; 1496 | strategy._args.provider = provider; 1497 | done(); 1498 | }); 1499 | return strategy; 1500 | }, 1501 | 1502 | 'should alias function' : function(strategy) { 1503 | assert.strictEqual(strategy.saveDiscoveredInfo, strategy.saveDiscoveredInformation); 1504 | }, 1505 | 1506 | 'after calling openid.saveDiscoveredInformation': { 1507 | topic: function(strategy) { 1508 | var self = this; 1509 | var key = 'http://jaredhanson.net'; 1510 | var provider = { endpoint: 'https://www.google.com/accounts/o8/ud', 1511 | version: 'http://specs.openid.net/auth/2.0', 1512 | localIdentifier: 'http://www.google.com/profiles/jaredhanson', 1513 | claimedIdentifier: 'http://jaredhanson.net' }; 1514 | 1515 | openid.saveDiscoveredInformation(key, provider, function() { 1516 | self.callback(null, strategy); 1517 | }); 1518 | }, 1519 | 1520 | 'should call registered function' : function(err, strategy) { 1521 | assert.equal(strategy._args.identifier, 'http://jaredhanson.net'); 1522 | assert.equal(strategy._args.provider.endpoint, 'https://www.google.com/accounts/o8/ud'); 1523 | }, 1524 | }, 1525 | }, 1526 | 1527 | 'strategy with loadDiscoveredInfo function': { 1528 | topic: function() { 1529 | var strategy = new OpenIDStrategy({ 1530 | returnURL: 'https://www.example.com/auth/openid/return', 1531 | }, 1532 | function(identifier, done) { 1533 | done(null, { identifier: identifier }); 1534 | } 1535 | ); 1536 | 1537 | strategy.loadDiscoveredInfo(function(identifier, done) { 1538 | var provider = { endpoint: 'https://www.google.com/accounts/o8/ud', 1539 | version: 'http://specs.openid.net/auth/2.0', 1540 | localIdentifier: 'http://www.google.com/profiles/jaredhanson', 1541 | claimedIdentifier: 'http://jaredhanson.net' }; 1542 | 1543 | strategy._args = {}; 1544 | strategy._args.identifier = identifier; 1545 | done(null, provider); 1546 | }); 1547 | return strategy; 1548 | }, 1549 | 1550 | 'should alias function' : function(strategy) { 1551 | assert.strictEqual(strategy.loadDiscoveredInfo, strategy.loadDiscoveredInformation); 1552 | }, 1553 | 1554 | 'after calling openid.loadDiscoveredInformation': { 1555 | topic: function(strategy) { 1556 | var self = this; 1557 | var key = 'http://jaredhanson.net'; 1558 | 1559 | openid.loadDiscoveredInformation(key, function(err, provider) { 1560 | self.callback(null, strategy, provider); 1561 | }); 1562 | }, 1563 | 1564 | 'should call registered function' : function(err, strategy) { 1565 | assert.equal(strategy._args.identifier, 'http://jaredhanson.net'); 1566 | }, 1567 | 'should supply provider' : function(err, strategy, provider) { 1568 | assert.equal(provider.endpoint, 'https://www.google.com/accounts/o8/ud'); 1569 | }, 1570 | }, 1571 | }, 1572 | 1573 | 'strategy constructed without a validate callback': { 1574 | 'should throw an error': function (strategy) { 1575 | assert.throws(function() { 1576 | new OAuthStrategy({ 1577 | returnURL: 'https://www.example.com/auth/openid/return', 1578 | }); 1579 | }); 1580 | }, 1581 | }, 1582 | 1583 | }).export(module); 1584 | --------------------------------------------------------------------------------