| 
									
										
										
										
											2016-02-11 22:33:54 -08:00
										 |  |  | import passport from 'passport'; | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  | import { | 
					
						
							|  |  |  |   PassportConfigurator | 
					
						
							|  |  |  | } from '@freecodecamp/loopback-component-passport'; | 
					
						
							| 
									
										
										
										
											2018-05-19 21:21:49 +05:30
										 |  |  | import url from 'url'; | 
					
						
							| 
									
										
										
										
											2018-05-23 21:10:56 +01:00
										 |  |  | import jwt from 'jsonwebtoken'; | 
					
						
							| 
									
										
										
										
											2018-06-01 02:29:27 +05:30
										 |  |  | import dedent from 'dedent'; | 
					
						
							| 
									
										
										
										
											2016-02-11 22:33:54 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  | import { homeLocation } from '../../config/env.json'; | 
					
						
							|  |  |  | import { jwtSecret } from '../../config/secrets'; | 
					
						
							|  |  |  | import passportProviders from './passport-providers'; | 
					
						
							|  |  |  | import { createCookieConfig } from './utils/cookieConfig'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-11 22:33:54 -08:00
										 |  |  | const passportOptions = { | 
					
						
							|  |  |  |   emailOptional: true, | 
					
						
							| 
									
										
										
										
											2017-07-13 11:39:07 -07:00
										 |  |  |   profileToUser: null | 
					
						
							| 
									
										
										
										
											2016-02-11 22:33:54 -08:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const fields = { | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |   progressTimestamps: false | 
					
						
							| 
									
										
										
										
											2016-02-11 22:33:54 -08:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-15 06:12:05 +01:00
										 |  |  | function getCompletedCertCount(user) { | 
					
						
							|  |  |  |   return [ | 
					
						
							|  |  |  |     'isApisMicroservicesCert', | 
					
						
							|  |  |  |     'is2018DataVisCert', | 
					
						
							|  |  |  |     'isFrontEndLibsCert', | 
					
						
							|  |  |  |     'isInfosecQaCert', | 
					
						
							|  |  |  |     'isJsAlgoDataStructCert', | 
					
						
							|  |  |  |     'isRespWebDesignCert' | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  |   ].reduce((sum, key) => (user[key] ? sum + 1 : sum), 0); | 
					
						
							| 
									
										
										
										
											2018-05-21 16:21:15 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-05-15 06:12:05 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-21 16:21:15 +01:00
										 |  |  | function getLegacyCertCount(user) { | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  |   return ['isFrontEndCert', 'isBackEndCert', 'isDataVisCert'].reduce( | 
					
						
							|  |  |  |     (sum, key) => (user[key] ? sum + 1 : sum), | 
					
						
							|  |  |  |     0 | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2018-05-15 06:12:05 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-11 22:33:54 -08:00
										 |  |  | PassportConfigurator.prototype.init = function passportInit(noSession) { | 
					
						
							|  |  |  |   this.app.middleware('session:after', passport.initialize()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (noSession) { | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   this.app.middleware('session:after', passport.session()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Serialization and deserialization is only required if passport session is
 | 
					
						
							|  |  |  |   // enabled
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   passport.serializeUser((user, done) => { | 
					
						
							|  |  |  |     done(null, user.id); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   passport.deserializeUser((id, done) => { | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |     this.userModel.findById(id, { fields }, (err, user) => { | 
					
						
							|  |  |  |       if (err || !user) { | 
					
						
							|  |  |  |         return done(err, user); | 
					
						
							| 
									
										
										
										
											2016-02-11 22:33:54 -08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-05-15 06:12:05 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |       return this.app.dataSources.db.connector | 
					
						
							|  |  |  |         .collection('user') | 
					
						
							|  |  |  |         .aggregate([ | 
					
						
							|  |  |  |           { $match: { _id: user.id } }, | 
					
						
							|  |  |  |           { $project: { points: { $size: '$progressTimestamps' } } } | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  |         ]) | 
					
						
							|  |  |  |         .get(function(err, [{ points = 1 } = {}]) { | 
					
						
							|  |  |  |           if (err) { | 
					
						
							|  |  |  |             console.error(err); | 
					
						
							|  |  |  |             return done(err); | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |           user.points = points; | 
					
						
							| 
									
										
										
										
											2018-05-15 06:12:05 +01:00
										 |  |  |           let completedChallengeCount = 0; | 
					
						
							|  |  |  |           let completedProjectCount = 0; | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |           if ('completedChallenges' in user) { | 
					
						
							|  |  |  |             completedChallengeCount = user.completedChallenges.length; | 
					
						
							|  |  |  |             user.completedChallenges.forEach(item => { | 
					
						
							|  |  |  |               if ( | 
					
						
							|  |  |  |                 'challengeType' in item && | 
					
						
							|  |  |  |                 (item.challengeType === 3 || item.challengeType === 4) | 
					
						
							|  |  |  |               ) { | 
					
						
							|  |  |  |                 completedProjectCount++; | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             }); | 
					
						
							| 
									
										
										
										
											2018-05-15 06:12:05 +01:00
										 |  |  |           } | 
					
						
							|  |  |  |           user.completedChallengeCount = completedChallengeCount; | 
					
						
							|  |  |  |           user.completedProjectCount = completedProjectCount; | 
					
						
							|  |  |  |           user.completedCertCount = getCompletedCertCount(user); | 
					
						
							| 
									
										
										
										
											2018-05-21 16:21:15 +01:00
										 |  |  |           user.completedLegacyCertCount = getLegacyCertCount(user); | 
					
						
							| 
									
										
										
										
											2018-05-15 14:56:26 +01:00
										 |  |  |           user.completedChallenges = []; | 
					
						
							| 
									
										
										
										
											2016-04-14 17:07:40 -07:00
										 |  |  |           return done(null, user); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2016-02-11 22:33:54 -08:00
										 |  |  |   }); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-23 16:29:26 +01:00
										 |  |  | export function setupPassport(app) { | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  |   // NOTE(Bouncey): Not sure this is doing much now
 | 
					
						
							|  |  |  |   // Loopback complains about userCredentialModle when this
 | 
					
						
							|  |  |  |   // setup is remoed from server/server.js
 | 
					
						
							|  |  |  |   //
 | 
					
						
							|  |  |  |   // I have split the custom callback in to it's own export that we can use both
 | 
					
						
							|  |  |  |   // here and in boot:auth
 | 
					
						
							|  |  |  |   //
 | 
					
						
							|  |  |  |   // Needs more investigation...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-11 22:33:54 -08:00
										 |  |  |   const configurator = new PassportConfigurator(app); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   configurator.setupModels({ | 
					
						
							|  |  |  |     userModel: app.models.user, | 
					
						
							|  |  |  |     userIdentityModel: app.models.userIdentity, | 
					
						
							|  |  |  |     userCredentialModel: app.models.userCredential | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   configurator.init(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Object.keys(passportProviders).map(function(strategy) { | 
					
						
							| 
									
										
										
										
											2018-05-19 21:21:49 +05:30
										 |  |  |     let config = passportProviders[strategy]; | 
					
						
							| 
									
										
										
										
											2016-02-11 22:33:54 -08:00
										 |  |  |     config.session = config.session !== false; | 
					
						
							| 
									
										
										
										
											2018-05-20 00:50:55 +05:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-20 13:40:15 +05:30
										 |  |  |     config.customCallback = !config.useCustomCallback | 
					
						
							| 
									
										
										
										
											2018-05-20 00:50:55 +05:30
										 |  |  |       ? null | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  |       : createPassportCallbackAuthenticator(strategy, config); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     configurator.configureProvider(strategy, { | 
					
						
							|  |  |  |       ...config, | 
					
						
							|  |  |  |       ...passportOptions | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2016-02-11 22:33:54 -08:00
										 |  |  |   }); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-30 18:17:07 -03:00
										 |  |  | export const saveResponseAuthCookies = () => { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return (req, res, next) => { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const user = req.user; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!user) { | 
					
						
							|  |  |  |       return res.redirect('/signin'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const { accessToken } = user; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const cookieConfig = { | 
					
						
							|  |  |  |       ...createCookieConfig(req), | 
					
						
							|  |  |  |       maxAge: 77760000000 | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     const jwtAccess = jwt.sign({ accessToken }, jwtSecret); | 
					
						
							|  |  |  |     res.cookie('jwt_access_token', jwtAccess, cookieConfig); | 
					
						
							|  |  |  |     res.cookie('access_token', accessToken.id, cookieConfig); | 
					
						
							|  |  |  |     res.cookie('userId', accessToken.userId, cookieConfig); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return next(); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const loginRedirect = () => { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return (req, res) => { | 
					
						
							|  |  |  |     const successRedirect = req => { | 
					
						
							|  |  |  |       if (!!req && req.session && req.session.returnTo) { | 
					
						
							|  |  |  |         delete req.session.returnTo; | 
					
						
							|  |  |  |         return `${homeLocation}/welcome`; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return `${homeLocation}/welcome`; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let redirect = url.parse(successRedirect(req), true); | 
					
						
							|  |  |  |     delete redirect.search; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     redirect = url.format(redirect); | 
					
						
							|  |  |  |     return res.redirect(redirect); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  | export const createPassportCallbackAuthenticator = (strategy, config) => ( | 
					
						
							|  |  |  |   req, | 
					
						
							|  |  |  |   res, | 
					
						
							|  |  |  |   next | 
					
						
							|  |  |  | ) => { | 
					
						
							|  |  |  |   // https://stackoverflow.com/q/37430452
 | 
					
						
							|  |  |  |   const successRedirect = req => { | 
					
						
							|  |  |  |     if (!!req && req.session && req.session.returnTo) { | 
					
						
							|  |  |  |       delete req.session.returnTo; | 
					
						
							|  |  |  |       return `${homeLocation}/welcome`; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return config.successRedirect || `${homeLocation}/welcome`; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   return passport.authenticate( | 
					
						
							|  |  |  |     strategy, | 
					
						
							|  |  |  |     { session: false }, | 
					
						
							|  |  |  |     (err, user, userInfo) => { | 
					
						
							|  |  |  |       if (err) { | 
					
						
							|  |  |  |         return next(err); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!user || !userInfo) { | 
					
						
							|  |  |  |         return res.redirect('/signin'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       let redirect = url.parse(successRedirect(req), true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       delete redirect.search; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const { accessToken } = userInfo; | 
					
						
							|  |  |  |       const { provider } = config; | 
					
						
							|  |  |  |       if (accessToken && accessToken.id) { | 
					
						
							|  |  |  |         if (provider === 'auth0') { | 
					
						
							|  |  |  |           req.flash( | 
					
						
							|  |  |  |             'success', | 
					
						
							|  |  |  |             dedent`
 | 
					
						
							|  |  |  |               Success! You have signed in to your account. Happy Coding! | 
					
						
							|  |  |  |             `
 | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         } else if (user.email) { | 
					
						
							|  |  |  |           req.flash( | 
					
						
							|  |  |  |             'info', | 
					
						
							|  |  |  |             dedent`
 | 
					
						
							|  |  |  | We are moving away from social authentication for privacy reasons. Next time | 
					
						
							|  |  |  | we recommend using your email address: ${user.email} to sign in instead. | 
					
						
							|  |  |  |             `
 | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const cookieConfig = { | 
					
						
							|  |  |  |           ...createCookieConfig(req), | 
					
						
							|  |  |  |           maxAge: accessToken.ttl | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         const jwtAccess = jwt.sign({ accessToken }, jwtSecret); | 
					
						
							|  |  |  |         res.cookie('jwt_access_token', jwtAccess, cookieConfig); | 
					
						
							|  |  |  |         res.cookie('access_token', accessToken.id, cookieConfig); | 
					
						
							|  |  |  |         res.cookie('userId', accessToken.userId, cookieConfig); | 
					
						
							|  |  |  |         req.login(user); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       redirect = url.format(redirect); | 
					
						
							|  |  |  |       return res.redirect(redirect); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   )(req, res, next); | 
					
						
							|  |  |  | }; |