UNPKG

passport-cmushib

Version:

Passport authentication strategy for Carnegie Mellon University's Shibboleth service

214 lines (173 loc) 7.87 kB
"use strict;" /* CMU Shibboleth Passport Authentication Module This module exposes a passport Strategy object that is pre-configured to work with the CMU's Shibboleth identity provider (IdP). To use this, you must register your server with the CMU IdP, and you can use the metadataRoute() method below to provide the metadata necessary for registration via the standard metadata url (urls.metadata). author: Dave Stearns Modified for use at Carnegie Mellon University by Artur Zayats */ //const passport = require('passport'); const saml = require('passport-saml'); const util = require('util'); const idPCert = 'MIIDLDCCAhSgAwIBAgIJAO1Zt6Sg0xhmMA0GCSqGSIb3DQEBBQUAMBgxFjAUBgNVBAMTDWxvZ2luLmNtdS5lZHUwHhcNMTQwMTIyMTkzMDM2WhcNMzAwNjI5MTkzMDM2WjAYMRYwFAYDVQQDEw1sb2dpbi5jbXUuZWR1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4yIV5lVs9/7jdmRTi23AINTzGQTrL+p8EXmV1iL48YAZ36T+xnDpTXt2RDaioI34/P9vHYpSKY6C5gDNyXGQZYTrgJQHQRgJAGTsXshYoDeBboZZ9ax+7m86rKqmHZAprHALONubY0UtPDEGQKdMeeetAUAOh8kIKpGvKp96I+4pIT6S/p5VtBB80veOK6woqbzU0Qr9q1FbcZfJ6AjG8as9lBa9Si6vc/fGvFrjsJL3+cpvECuyG/yHp9obdwXLgxlQNPtXNeBgclgiaJJE8zWcZBUxWPboVeuC2Jfv7spIOcCyKPKTGUlobBoANGHqGMqbK+/7YzQ+J/s/4n0tvwIDAQABo3kwdzAdBgNVHQ4EFgQUoZye8kn1Hznd+tCaxJ3elowNIbYwSAYDVR0jBEEwP4AUoZye8kn1Hznd+tCaxJ3elowNIbahHKQaMBgxFjAUBgNVBAMTDWxvZ2luLmNtdS5lZHWCCQDtWbekoNMYZjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCp51hb/WPVfRtdQNZm6OQj8I6HwDGWmu5PzUycJAkD/VYd3wCM1zLwd32LMbxbA2ArKWBstErEsUog94zvMBWyAeT3Q5Gyghji0emF0nbZpNjPjE9bXavMbUppXF2/VHbuBtzEMBxIKV53X2et2MMc9mnNzZN1rofuIB//W9Fg9IWV5PLVbsvEYI98IkJ5t4JP92/V5p497O8jMj6oLhy7mI4FNx0pQnirAvrQxxgFTwVV5SEm87DBYRblUb4ba0yYVSBQg0EVbIb7QEDxHFWbzt4+NLolAQAMSQW+SJKf9V7+6+4uhMwpJxQwezzn41u9kGTIg9F8/s0IrgsTlAm3'; //"https://login.cmu.edu/idp/profile/SAML2/Redirect/SSO" //const idPEntryPoint = 'https://login.cmu.edu/idp/shibboleth'; const idPEntryPoint = 'https://login.cmu.edu/idp/profile/SAML2/Redirect/SSO'; //standard login, callback, logout, and meta-data URLs //these will be exposed from module.exports so that //clients can refer to them //the metadata one in particular is important to get right //as the auto-regisration process requires that exact URL const urls = { metadata: '/Shibboleth.sso/Metadata', logoutUrl: 'https://s3.as.cmu.edu/Shibboleth.sso/Logout' }; //export the urls map module.exports.urls = urls; //map of possible profile attributes and what name //we should give them on the resulting user object //add to this with other attrs if you request them const profileAttrs = { 'urn:oid:0.9.2342.19200300.100.1.3': 'email', 'urn:oid:2.5.4.4': 'lastname', 'urn:oid:2.5.4.42': 'firstname', 'urn:oid:2.5.4.3': 'sn', 'urn:oid:2.16.840.1.113730.3.1.241': 'displayName', 'urn:oid:1.3.6.1.4.1.5923.1.1.1.9': 'eduPersonScopedAffiliation', 'urn:oid:0.9.2342.19200300.100.1.1': 'netId', 'urn:oid:1.3.6.1.4.1.5923.1.1.1.1': 'affiliation', 'urn:oid:2.16.840.1.113730.3.1.3': 'empNum', 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6': 'principalName', 'urn:oid:2.5.4.18': 'box', 'urn:oid:2.5.4.20': 'phone', 'urn:oid:2.5.4.12': 'title', 'urn:oid:1.2.840.113994.200.21': 'studentId', 'urn:oid:1.2.840.113994.200.24': 'regId', 'urn:oid:0.9.2342.19200300.100.1.1': 'Shib-uid', 'urn:oid:0.9.2342.19200300.100.1.3': 'Shib-mail', 'urn:oid:1.3.6.1.4.1.5643.10.0.1': 'Shib-uaId' }; /* Passport Strategy for CMU Shibboleth Authentication This class extends passport-saml's Strategy, providing the necessary options and handling the conversion of the returned profile into a sensible user object. options should contain: entityId: your server's entity id, domain: your server's domain name, callbackUrl: login callback url (relative to domain), privateKey: your private key for signing requests (optional) */ function Strategy(options, verify) { var self = this; samlOptions = { entryPoint: idPEntryPoint, cert: idPCert, identifierFormat: null, issuer: options.entityId || options.domain, callbackUrl: 'https://' + options.domain + options.callbackUrl, decryptionPvk: options.privateKey, privateCert: options.privateKey, acceptedClockSkewMs: 180000, passReqToCallback: true }; function convertProfileToUser(req, profile) { var user = {}; var niceName; var attr; for (attr in profile) { niceName = profileAttrs[attr]; if (niceName !== undefined && profile[attr]) { user[niceName] = profile[attr]; } } var email = user.email || user.principalName || ''; user.id = email; user.email = email; user.name = email.split('@')[0]; if (user.displayName){ var words = user.displayName.split(' '); var firstname = words.shift(); var lastname = words.join(' '); user.firstname = user.firstname || firstname; user.lastname = user.lastname || lastname; user.name = user.displayName; } user.provider = self.name; return user; } function _verify(req, profile, done) { if (!profile) return done(new Error('Empty SAML profile returned!')); else profile = convertProfileToUser(req, profile); if (!verify) return done(null, profile); if (options.passReqToCallback) { verify(req, undefined, undefined, profile, done); } else { verify(undefined, undefined, profile, done); } } saml.Strategy.call(this, samlOptions, _verify); this.name = options.name || 'cmushib'; } util.inherits(Strategy, saml.Strategy); //expose the Strategy module.exports.Strategy = Strategy; /* Route implementation for the standard Shibboleth metadata route usage: var uwshib = require(...); var strategy = new uwshib.Strategy({...}); app.get(uwshib.urls.metadata, uwshib.metadataRoute(strategy, myPublicCert)); */ module.exports.metadataRoute = function(strategy, publicCert) { return function(req, res) { res.type('application/xml'); res.status(200).send(strategy.generateServiceProviderMetadata(publicCert)); } } //metadataRoute /* Middleware for ensuring that the user has authenticated. You can use this in two different ways. If you pass this to app.use(), it will secure all routes added after that. Or you can use it selectively on routes that require authentication like so: app.get('/foo/bar', ensureAuth(loginUrl), function(req, res) { //route implementation }); where loginUrl is the url to your login route where you call passport.authenticate() */ module.exports.ensureAuth = function(loginUrl) { return function(req, res, next) { if (req.isAuthenticated()) return next(); else { req.session.authRedirectUrl = req.url; res.redirect(loginUrl); } } }; /* Middleware for redirecting back to the originally requested URL after a successful authentication. The ensureAuth() middleware above will capture the current URL in session state, and when your callback route is called, you can use this to get back to the originally-requested URL. usage: var uwshib = require(...); var strategy = new uwshib.Strategy({...}); app.get('/login', passport.authenticate(strategy.name)); app.post('/login/callback', passport.authenticate(strategy.name), uwshib.backtoUrl()); app.use(uwshib.ensureAuth('/login')); */ module.exports.backToUrl = function(defaultUrl) { return function(req, res) { var url = req.session.authRedirectUrl; delete req.session.authRedirectUrl; res.redirect(url || defaultUrl || '/'); } };