├── .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 |
100 |
101 | ## Tests
102 |
103 | $ npm install --dev
104 | $ make test
105 |
106 | [](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 |
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 |
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 |
--------------------------------------------------------------------------------