UNPKG

passport-indieauth

Version:

IndieAuth authentication strategy for Passport.

158 lines (146 loc) 4.79 kB
/** * Module dependencies. */ var mf = require('microformat-node'); var passport = require('passport-strategy'); var requester = require('request'); var querystring = require('querystring'); var util = require('util'); var _ = require('underscore'); /** * `Strategy` constructor. * * The IndieAuth authentication strategy authenticates requests by delegating to * an IndieAuth server using the IndieAuth protocol, which handles third-party * authentication validation itself. * * Options: * `service`: the URI for the IndieAuth service (by default `https://indieauth.com/auth`) * `client_id`: your client ID (i.e. your application's identity, typically your domain) * `redirect_uri`: your website's callback URL. * * Examples: * * passport.use(new IndieAuthStrategy({ * service: "https://indieauth.com/auth", * client_id: "https://yourdomain.com", * redirect_uri: "http://yourdomain.com/auth/indieauth/callback" * }, * function(profile, done) { * User.findOrCreate(..., function (err, user) { * done(err, user); * }); * } * )); * * @param {Object} options * @param {Function} verify * @api public */ function Strategy(options, verify) { options = options || {}; options.service = options.service || 'https://indieauth.com/auth'; passport.Strategy.call(this); this.name = 'indieauth'; this._verify = verify; this._service = options.service; this._clientID = options.clientID; this._callbackURL = options.callbackURL; this._passReqToCallback = options.passReqToCallback; } /** * Inherit from `passport.Strategy`. */ util.inherits(Strategy, passport.Strategy); Strategy.prototype.authenticate = function(req, options, verify) { var self = this; if(req.query && req.query.code) { var code = req.query.code; requester.post({ uri: self._service, headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: 'code=' + code + '&client_id=' + self._clientID + '&redirect_uri=' + self._callbackURL }, function(err, response, body) { if(response.statusCode!=200) { var errDesc = querystring.parse(body); errDesc = errDesc.error_description; return self.error(errDesc); } else { var domain = querystring.parse(body); domain = domain.me; self.userProfile(domain, function(err, profile) { function verified(err, user, info) { if (err) { return self.error(err); } if (!user) { return self.fail(info); } self.success(user, info); } try { if (self._passReqToCallback) { var arity = self._verify.length; if (arity == 4) { self._verify(req, params, profile, verified); } else { // arity == 3 self._verify(req, profile, verified); } } else { var arity = self._verify.length; if (arity == 3) { self._verify(params, profile, verified); } else { // arity == 2 self._verify(profile, verified); } } } catch (ex) { return self.error(ex); } }); } }); } } /** * Retrieve user profile from user's verified domain. * * This function constructs a normalized(ish) profile, with the following properties: * * - `provider` always set to `indieauth` * - `id` always set to the user's domain * - Whatever available h-card properties are appended to the user profile. * - No Portable Contacts Unifed Profile Schema * * @param {String} domain * @param {Function} done * @api protected */ Strategy.prototype.userProfile = function(domain, done) { mf.parseUrl(domain, { 'filters': ['h-card'] }, function(err, data) { if(err) { done(err, null); } else { var profile = { provider: 'indieauth', id: domain, }; var hcard; // Matches the user's domain name with the URL value of their h-card. hcard = _.findWhere(data.items, { url: [domain] }); // If it does not match, we will assume it is just the first h-card on the page. // Yes, this could result in false positives, but we have no other good way // to correlate h-cards with the author's domain otherwise. // Note: My anecdotal research shows this to be a pretty reliable assumption. if(!hcard) { hcard = data.items[0].properties; } else { hcard = { url: domain }; } profile = _.extend(profile, hcard); done(null, profile); } }); } /** * Expose `Strategy`. */ module.exports = Strategy;