| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  | import _ from 'lodash'; | 
					
						
							|  |  |  | import { Observable } from 'rx'; | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  | import dedent from 'dedent'; | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  | import passport from 'passport'; | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  | import { isEmail } from 'validator'; | 
					
						
							| 
									
										
										
										
											2018-01-22 17:08:33 -08:00
										 |  |  | import { check } from 'express-validator/check'; | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-31 16:04:04 +01:00
										 |  |  | import { homeLocation } from '../../../config/env'; | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  | import { createCookieConfig } from '../utils/cookieConfig'; | 
					
						
							|  |  |  | import { createPassportCallbackAuthenticator } from '../component-passport'; | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  | import { | 
					
						
							| 
									
										
										
										
											2018-01-22 17:08:33 -08:00
										 |  |  |   ifUserRedirectTo, | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |   ifNoUserRedirectTo, | 
					
						
							| 
									
										
										
										
											2018-01-22 17:08:33 -08:00
										 |  |  |   createValidatorErrorHandler | 
					
						
							|  |  |  | } from '../utils/middleware'; | 
					
						
							|  |  |  | import { wrapHandledError } from '../utils/create-handled-error.js'; | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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(); | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |   const ifNoUserRedirectHome = ifNoUserRedirectTo(homeLocation); | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  |   const api = app.loopback.Router(); | 
					
						
							| 
									
										
										
										
											2017-12-29 09:59:27 -08:00
										 |  |  |   const { AuthToken, User } = app.models; | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  |   api.get('/signin', ifUserRedirect, passport.authenticate('auth0-login', {})); | 
					
						
							|  |  |  |   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', | 
					
						
							|  |  |  |           message: 'Oops, something is not right.', | 
					
						
							|  |  |  |           redirectTo: homeLocation | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2018-05-25 23:14:09 +05:30
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-10-24 00:24:48 +01:00
										 |  |  |       const config = createCookieConfig(req); | 
					
						
							| 
									
										
										
										
											2018-05-26 18:28:20 +05:30
										 |  |  |       res.clearCookie('jwt_access_token', config); | 
					
						
							|  |  |  |       res.clearCookie('access_token', config); | 
					
						
							|  |  |  |       res.clearCookie('userId', config); | 
					
						
							|  |  |  |       res.clearCookie('_csrf', config); | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							|  |  |  |   const defaultErrorMsg = dedent`
 | 
					
						
							|  |  |  |     Oops, something is not right, | 
					
						
							|  |  |  |     please request a fresh link to sign in / sign up. | 
					
						
							|  |  |  |   `;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |   const passwordlessGetValidators = [ | 
					
						
							|  |  |  |     check('email') | 
					
						
							|  |  |  |       .isBase64() | 
					
						
							| 
									
										
										
										
											2018-01-01 15:39:14 -08:00
										 |  |  |       .withMessage('Email should be a base64 encoded string.'), | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |     check('token') | 
					
						
							|  |  |  |       .exists() | 
					
						
							| 
									
										
										
										
											2018-01-01 15:39:14 -08:00
										 |  |  |       .withMessage('Token should exist.') | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |       // based on strongloop/loopback/common/models/access-token.js#L15
 | 
					
						
							|  |  |  |       .isLength({ min: 64, max: 64 }) | 
					
						
							| 
									
										
										
										
											2018-01-01 15:39:14 -08:00
										 |  |  |       .withMessage('Token is not the right length.') | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |   ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  |   function getPasswordlessAuth(req, res, next) { | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |     const { | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |       query: { email: encodedEmail, token: authTokenId, emailChange } = {} | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |     } = req; | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |     const email = User.decodeEmail(encodedEmail); | 
					
						
							|  |  |  |     if (!isEmail(email)) { | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |       return next( | 
					
						
							|  |  |  |         wrapHandledError(new TypeError('decoded email is invalid'), { | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |           type: 'info', | 
					
						
							|  |  |  |           message: 'The email encoded in the link is incorrectly formatted', | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |           redirectTo: `${homeLocation}/signin` | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |     // first find
 | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |     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) { | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |                 throw wrapHandledError( | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |                   new Error(`no user found for token: ${authTokenId}`), | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |                   { | 
					
						
							|  |  |  |                     type: 'info', | 
					
						
							|  |  |  |                     message: defaultErrorMsg, | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |                     redirectTo: `${homeLocation}/signin` | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |                   } | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |               } | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |               if (user.email !== email) { | 
					
						
							|  |  |  |                 if (!emailChange || (emailChange && user.newEmail !== email)) { | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |                   throw wrapHandledError( | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |                     new Error('user email does not match'), | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |                     { | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |                       type: 'info', | 
					
						
							|  |  |  |                       message: defaultErrorMsg, | 
					
						
							|  |  |  |                       redirectTo: `${homeLocation}/signin` | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                   ); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               return authToken | 
					
						
							|  |  |  |                 .validate$() | 
					
						
							|  |  |  |                 .map(isValid => { | 
					
						
							|  |  |  |                   if (!isValid) { | 
					
						
							|  |  |  |                     throw wrapHandledError(new Error('token is invalid'), { | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |                       type: 'info', | 
					
						
							|  |  |  |                       message: `
 | 
					
						
							|  |  |  |                         Looks like the link you clicked has expired, | 
					
						
							|  |  |  |                         please request a fresh link, to sign in. | 
					
						
							|  |  |  |                       `,
 | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |                       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!' | 
					
						
							|  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2018-08-30 15:36:26 +01:00
										 |  |  |           return res.redirectWithFlash(`${homeLocation}/welcome`); | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |         }) | 
					
						
							|  |  |  |         .subscribe(() => {}, next) | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |   api.get( | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |     '/passwordless-auth', | 
					
						
							|  |  |  |     ifUserRedirect, | 
					
						
							|  |  |  |     passwordlessGetValidators, | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |     createValidatorErrorHandler('errors', `${homeLocation}/signin`), | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |     getPasswordlessAuth | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |   api.get('/passwordless-change', (req, res) => | 
					
						
							|  |  |  |     res.redirect(301, '/confirm-email') | 
					
						
							| 
									
										
										
										
											2018-07-28 12:34:27 +05:30
										 |  |  |   ); | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   api.get( | 
					
						
							| 
									
										
										
										
											2018-07-28 12:34:27 +05:30
										 |  |  |     '/confirm-email', | 
					
						
							| 
									
										
										
										
											2018-02-16 23:18:53 +00:00
										 |  |  |     ifNoUserRedirectHome, | 
					
						
							|  |  |  |     passwordlessGetValidators, | 
					
						
							|  |  |  |     getPasswordlessAuth | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |   const passwordlessPostValidators = [ | 
					
						
							|  |  |  |     check('email') | 
					
						
							|  |  |  |       .isEmail() | 
					
						
							| 
									
										
										
										
											2018-01-01 15:39:14 -08:00
										 |  |  |       .withMessage('Email is not a valid email address.') | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |   ]; | 
					
						
							|  |  |  |   function postPasswordlessAuth(req, res, next) { | 
					
						
							|  |  |  |     const { body: { email } = {} } = req; | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |     return User.findOne$({ where: { email } }) | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |       .flatMap(_user => | 
					
						
							|  |  |  |         Observable.if( | 
					
						
							| 
									
										
										
										
											2017-12-28 20:38:16 -08:00
										 |  |  |           // if no user found create new user and save to db
 | 
					
						
							|  |  |  |           _.constant(_user), | 
					
						
							|  |  |  |           Observable.of(_user), | 
					
						
							|  |  |  |           User.create$({ email }) | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |         ).flatMap(user => user.requestAuthEmail(!_user)) | 
					
						
							| 
									
										
										
										
											2017-12-28 20:38:16 -08:00
										 |  |  |       ) | 
					
						
							| 
									
										
										
										
											2018-04-13 20:18:10 +05:30
										 |  |  |       .do(msg => { | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |         let redirectTo = homeLocation; | 
					
						
							| 
									
										
										
										
											2018-04-13 20:18:10 +05:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |         if (req.session && req.session.returnTo) { | 
					
						
							| 
									
										
										
										
											2018-04-13 20:18:10 +05:30
										 |  |  |           redirectTo = req.session.returnTo; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         req.flash('info', msg); | 
					
						
							|  |  |  |         return res.redirect(redirectTo); | 
					
						
							|  |  |  |       }) | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |       .subscribe(_.noop, next); | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |   api.post( | 
					
						
							|  |  |  |     '/passwordless-auth', | 
					
						
							|  |  |  |     ifUserRedirect, | 
					
						
							|  |  |  |     passwordlessPostValidators, | 
					
						
							| 
									
										
										
										
											2018-08-29 20:52:41 +01:00
										 |  |  |     createValidatorErrorHandler('errors', `${homeLocation}/signin`), | 
					
						
							| 
									
										
										
										
											2017-12-27 10:11:17 -08:00
										 |  |  |     postPasswordlessAuth | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2017-12-26 13:20:03 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   app.use(api); | 
					
						
							| 
									
										
										
										
											2015-06-02 17:27:02 -07:00
										 |  |  | }; |