| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  | import passport from 'passport'; | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  | import dedent from 'dedent'; | 
					
						
							|  |  |  | import { check } from 'express-validator/check'; | 
					
						
							|  |  |  | import { isEmail } from 'validator'; | 
					
						
							| 
									
										
										
										
											2020-11-07 09:05:25 +01:00
										 |  |  | import jwt from 'jsonwebtoken'; | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-31 16:04:04 +01:00
										 |  |  | import { homeLocation } from '../../../config/env'; | 
					
						
							| 
									
										
										
										
											2020-11-07 09:05:25 +01:00
										 |  |  | import { jwtSecret } from '../../../config/secrets'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-16 00:31:05 +00:00
										 |  |  | import { | 
					
						
							| 
									
										
										
										
											2018-10-30 18:17:07 -03:00
										 |  |  |   createPassportCallbackAuthenticator, | 
					
						
							| 
									
										
										
										
											2020-11-07 09:05:25 +01:00
										 |  |  |   devSaveResponseAuthCookies, | 
					
						
							|  |  |  |   devLoginRedirect | 
					
						
							| 
									
										
										
										
											2018-10-30 18:17:07 -03:00
										 |  |  | } from '../component-passport'; | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  | import { ifUserRedirectTo, ifNoUserRedirectTo } from '../utils/middleware'; | 
					
						
							| 
									
										
										
										
											2018-01-22 17:08:33 -08:00
										 |  |  | import { wrapHandledError } from '../utils/create-handled-error.js'; | 
					
						
							| 
									
										
										
										
											2019-02-20 23:07:12 +00:00
										 |  |  | import { removeCookies } from '../utils/getSetAccessToken'; | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  | import { decodeEmail } from '../../common/utils'; | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | const isSignUpDisabled = !!process.env.DISABLE_SIGNUP; | 
					
						
							| 
									
										
										
										
											2018-01-01 15:01:50 -08:00
										 |  |  | if (isSignUpDisabled) { | 
					
						
							|  |  |  |   console.log('fcc:boot:auth - Sign up is disabled'); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  | const passwordlessGetValidators = [ | 
					
						
							|  |  |  |   check('email') | 
					
						
							|  |  |  |     .isBase64() | 
					
						
							|  |  |  |     .withMessage('Email should be a base64 encoded string.'), | 
					
						
							|  |  |  |   check('token') | 
					
						
							|  |  |  |     .exists() | 
					
						
							|  |  |  |     .withMessage('Token should exist.') | 
					
						
							|  |  |  |     // based on strongloop/loopback/common/models/access-token.js#L15
 | 
					
						
							|  |  |  |     .isLength({ min: 64, max: 64 }) | 
					
						
							|  |  |  |     .withMessage('Token is not the right length.') | 
					
						
							|  |  |  | ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-03 12:26:11 -07:00
										 |  |  | module.exports = function enableAuthentication(app) { | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  |   // enable loopback access control authentication. see:
 | 
					
						
							| 
									
										
										
										
											2018-06-28 15:02:22 +05:30
										 |  |  |   // loopback.io/doc/en/lb2/Authentication-authorization-and-permissions.html
 | 
					
						
							| 
									
										
										
										
											2015-06-03 12:26:11 -07:00
										 |  |  |   app.enableAuth(); | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |   const ifUserRedirect = ifUserRedirectTo(); | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  |   const ifNoUserRedirectHome = ifNoUserRedirectTo(homeLocation); | 
					
						
							| 
									
										
										
										
											2020-11-07 09:05:25 +01:00
										 |  |  |   const devSaveAuthCookies = devSaveResponseAuthCookies(); | 
					
						
							|  |  |  |   const devLoginSuccessRedirect = devLoginRedirect(); | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  |   const api = app.loopback.Router(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-30 18:17:07 -03:00
										 |  |  |   // Use a local mock strategy for signing in if we are in dev mode.
 | 
					
						
							|  |  |  |   // Otherwise we use auth0 login. We use a string for 'true' because values
 | 
					
						
							|  |  |  |   // set in the env file will always be strings and never boolean.
 | 
					
						
							|  |  |  |   if (process.env.LOCAL_MOCK_AUTH === 'true') { | 
					
						
							|  |  |  |     api.get( | 
					
						
							|  |  |  |       '/signin', | 
					
						
							|  |  |  |       passport.authenticate('devlogin'), | 
					
						
							| 
									
										
										
										
											2020-11-07 09:05:25 +01:00
										 |  |  |       devSaveAuthCookies, | 
					
						
							|  |  |  |       devLoginSuccessRedirect | 
					
						
							| 
									
										
										
										
											2018-10-30 18:17:07 -03:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } else { | 
					
						
							| 
									
										
										
										
											2020-11-07 09:05:25 +01:00
										 |  |  |     api.get('/signin', ifUserRedirect, (req, res, next) => { | 
					
						
							|  |  |  |       const state = jwt.sign({ returnTo: req.query.returnTo }, jwtSecret); | 
					
						
							|  |  |  |       return passport.authenticate('auth0-login', { state })(req, res, next); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2018-10-30 18:17:07 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     api.get( | 
					
						
							|  |  |  |       '/auth/auth0/callback', | 
					
						
							|  |  |  |       createPassportCallbackAuthenticator('auth0-login', { provider: 'auth0' }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |   api.get('/signout', (req, res) => { | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  |     req.logout(); | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |     req.session.destroy(err => { | 
					
						
							| 
									
										
										
										
											2018-05-25 23:14:09 +05:30
										 |  |  |       if (err) { | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |         throw wrapHandledError(new Error('could not destroy session'), { | 
					
						
							|  |  |  |           type: 'info', | 
					
						
							| 
									
										
										
										
											2019-06-19 15:31:03 +01:00
										 |  |  |           message: 'We could not log you out, please try again in a moment.', | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |           redirectTo: homeLocation | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2018-05-25 23:14:09 +05:30
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-02-20 23:07:12 +00:00
										 |  |  |       removeCookies(req, res); | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |       res.redirect(homeLocation); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  |   api.get( | 
					
						
							|  |  |  |     '/confirm-email', | 
					
						
							|  |  |  |     ifNoUserRedirectHome, | 
					
						
							|  |  |  |     passwordlessGetValidators, | 
					
						
							|  |  |  |     createGetPasswordlessAuth(app) | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  |   app.use(api); | 
					
						
							| 
									
										
										
										
											2015-06-02 17:27:02 -07:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | const defaultErrorMsg = dedent`
 | 
					
						
							|  |  |  |     Oops, something is not right, | 
					
						
							|  |  |  |     please request a fresh link to sign in / sign up. | 
					
						
							|  |  |  |   `;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function createGetPasswordlessAuth(app) { | 
					
						
							|  |  |  |   const { | 
					
						
							|  |  |  |     models: { AuthToken, User } | 
					
						
							|  |  |  |   } = app; | 
					
						
							|  |  |  |   return function getPasswordlessAuth(req, res, next) { | 
					
						
							|  |  |  |     const { | 
					
						
							|  |  |  |       query: { email: encodedEmail, token: authTokenId, emailChange } = {} | 
					
						
							|  |  |  |     } = req; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const email = decodeEmail(encodedEmail); | 
					
						
							|  |  |  |     if (!isEmail(email)) { | 
					
						
							|  |  |  |       return next( | 
					
						
							|  |  |  |         wrapHandledError(new TypeError('decoded email is invalid'), { | 
					
						
							|  |  |  |           type: 'info', | 
					
						
							|  |  |  |           message: 'The email encoded in the link is incorrectly formatted', | 
					
						
							|  |  |  |           redirectTo: `${homeLocation}/signin` | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // first find
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       AuthToken.findOne$({ where: { id: authTokenId } }) | 
					
						
							|  |  |  |         .flatMap(authToken => { | 
					
						
							|  |  |  |           if (!authToken) { | 
					
						
							|  |  |  |             throw wrapHandledError( | 
					
						
							|  |  |  |               new Error(`no token found for id: ${authTokenId}`), | 
					
						
							|  |  |  |               { | 
					
						
							|  |  |  |                 type: 'info', | 
					
						
							|  |  |  |                 message: defaultErrorMsg, | 
					
						
							|  |  |  |                 redirectTo: `${homeLocation}/signin` | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           // find user then validate and destroy email validation token
 | 
					
						
							|  |  |  |           // finally retun user instance
 | 
					
						
							|  |  |  |           return User.findOne$({ where: { id: authToken.userId } }).flatMap( | 
					
						
							|  |  |  |             user => { | 
					
						
							|  |  |  |               if (!user) { | 
					
						
							|  |  |  |                 throw wrapHandledError( | 
					
						
							|  |  |  |                   new Error(`no user found for token: ${authTokenId}`), | 
					
						
							|  |  |  |                   { | 
					
						
							|  |  |  |                     type: 'info', | 
					
						
							|  |  |  |                     message: defaultErrorMsg, | 
					
						
							|  |  |  |                     redirectTo: `${homeLocation}/signin` | 
					
						
							|  |  |  |                   } | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               if (user.email !== email) { | 
					
						
							|  |  |  |                 if (!emailChange || (emailChange && user.newEmail !== email)) { | 
					
						
							|  |  |  |                   throw wrapHandledError( | 
					
						
							|  |  |  |                     new Error('user email does not match'), | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                       type: 'info', | 
					
						
							|  |  |  |                       message: defaultErrorMsg, | 
					
						
							|  |  |  |                       redirectTo: `${homeLocation}/signin` | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                   ); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               return authToken | 
					
						
							|  |  |  |                 .validate$() | 
					
						
							|  |  |  |                 .map(isValid => { | 
					
						
							|  |  |  |                   if (!isValid) { | 
					
						
							|  |  |  |                     throw wrapHandledError(new Error('token is invalid'), { | 
					
						
							|  |  |  |                       type: 'info', | 
					
						
							|  |  |  |                       message: `
 | 
					
						
							|  |  |  |                         Looks like the link you clicked has expired, | 
					
						
							|  |  |  |                         please request a fresh link, to sign in. | 
					
						
							|  |  |  |                       `,
 | 
					
						
							|  |  |  |                       redirectTo: `${homeLocation}/signin` | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                   } | 
					
						
							|  |  |  |                   return authToken.destroy$(); | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |                 .map(() => user); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         // at this point token has been validated and destroyed
 | 
					
						
							|  |  |  |         // update user and log them in
 | 
					
						
							|  |  |  |         .map(user => user.loginByRequest(req, res)) | 
					
						
							|  |  |  |         .do(() => { | 
					
						
							|  |  |  |           req.flash( | 
					
						
							|  |  |  |             'success', | 
					
						
							|  |  |  |             'Success! You have signed in to your account. Happy Coding!' | 
					
						
							|  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2019-10-08 08:15:36 -07:00
										 |  |  |           return res.redirectWithFlash(`${homeLocation}/learn`); | 
					
						
							| 
									
										
										
										
											2019-09-09 22:43:51 +01:00
										 |  |  |         }) | 
					
						
							|  |  |  |         .subscribe(() => {}, next) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } |