fix(auth0): Unify authentication via auth0 as provider
This commit is contained in:
		
				
					committed by
					
						 Stuart Taylor
						Stuart Taylor
					
				
			
			
				
	
			
			
			
						parent
						
							38bcc2e250
						
					
				
				
					commit
					ebba8e9c64
				
			| @@ -1,28 +1,19 @@ | ||||
| import { Observable } from 'rx'; | ||||
| // import debug from 'debug'; | ||||
| import dedent from 'dedent'; | ||||
| import { isEmail } from 'validator'; | ||||
|  | ||||
| import { | ||||
|   getSocialProvider, | ||||
|   getUsernameFromProvider | ||||
| } from '../../server/utils/auth'; | ||||
| import { observeMethod, observeQuery } from '../../server/utils/rx'; | ||||
| import { wrapHandledError } from '../../server/utils/create-handled-error.js'; | ||||
|  | ||||
| // const log = debug('fcc:models:userIdent'); | ||||
|  | ||||
| export default function(UserIdent) { | ||||
|  | ||||
|   UserIdent.on('dataSourceAttached', () => { | ||||
|     UserIdent.findOne$ = observeMethod(UserIdent, 'findOne'); | ||||
|   }); | ||||
|   // original source | ||||
|   // github.com/strongloop/loopback-component-passport | ||||
|   // find identity if it exist | ||||
|   // if not redirect to email signup | ||||
|   // if yes and github | ||||
|   //   update profile | ||||
|   //   update username | ||||
|   //   update picture | ||||
|  | ||||
|   UserIdent.login = function( | ||||
|     _provider, | ||||
|     authScheme, | ||||
| @@ -33,24 +24,44 @@ export default function(UserIdent) { | ||||
|   ) { | ||||
|     const User = UserIdent.app.models.User; | ||||
|     const AccessToken = UserIdent.app.models.AccessToken; | ||||
|     const provider = getSocialProvider(_provider); | ||||
|     options = options || {}; | ||||
|     if (typeof options === 'function' && !cb) { | ||||
|       cb = options; | ||||
|       options = {}; | ||||
|     } | ||||
|  | ||||
|     // get the social provider data and the external id from auth0 | ||||
|     profile.id = profile.id || profile.openid; | ||||
|     const auth0IdString = '' + profile.id; | ||||
|     const socialExtId = auth0IdString.substring(auth0IdString.indexOf('|') + 1); | ||||
|     const provider = auth0IdString.substring(0, auth0IdString.indexOf('|')); | ||||
|     const query = { | ||||
|       where: { | ||||
|         provider: provider, | ||||
|         externalId: profile.id | ||||
|         externalId: socialExtId | ||||
|       }, | ||||
|       include: 'user' | ||||
|     }; | ||||
|     // get the email from the auth0 (its expected from social providers) | ||||
|     const email = (profile && profile.emails && profile.emails[0]) ? | ||||
|                     profile.emails[0].value : ''; | ||||
|     if (!isEmail('' + email)) { | ||||
|       throw wrapHandledError( | ||||
|         new Error('invalid or empty email recieved from auth0'), | ||||
|         { | ||||
|           message: dedent` | ||||
|     Oops... something is not right. We did not find a valid email from your | ||||
|     ${provider} account. Please try again with a different provider that has an | ||||
|     email available with it. | ||||
|           `, | ||||
|           type: 'info', | ||||
|           redirectTo: '/' | ||||
|         } | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     if (provider === 'auth0') { | ||||
|     if (provider === 'email') { | ||||
|  | ||||
|       const email = profile.emails[0].value; | ||||
|       return User.findOne$({ where: { email } }) | ||||
|         .flatMap(user => { | ||||
|           return user ? | ||||
| @@ -63,8 +74,8 @@ export default function(UserIdent) { | ||||
|               new Error('could not find or create a user'), | ||||
|               { | ||||
|                 message: dedent` | ||||
|                   Oops... something is not right. We could not find or create a | ||||
|                   user with that email. | ||||
|     Oops... something is not right. We could not find or create a | ||||
|     user with that email. | ||||
|                 `, | ||||
|                 type: 'info', | ||||
|                 redirectTo: '/' | ||||
| @@ -101,61 +112,17 @@ export default function(UserIdent) { | ||||
|  | ||||
|       return UserIdent.findOne$(query) | ||||
|         .flatMap(identity => { | ||||
|           if (!identity) { | ||||
|             throw wrapHandledError( | ||||
|               new Error('user identity account not found'), | ||||
|               { | ||||
|                 message: dedent` | ||||
|     We cannot create new user accounts with the deprecated social sign in | ||||
|     methods. If you already have an account with us, please sign in with your | ||||
|     email address instead. | ||||
|                 `, | ||||
|                 type: 'info', | ||||
|                 redirectTo: '/' | ||||
|               } | ||||
|             ); | ||||
|           } | ||||
|           const modified = new Date(); | ||||
|           const user = identity.user(); | ||||
|           if (!user) { | ||||
|             const username = getUsernameFromProvider(provider, profile); | ||||
|             return observeQuery( | ||||
|               identity, | ||||
|               'updateAttributes', | ||||
|               { | ||||
|                 isOrphaned: username || true | ||||
|               } | ||||
|             ) | ||||
|               .do(() => { | ||||
|                 throw wrapHandledError( | ||||
|                   new Error('user identity is not associated with a user'), | ||||
|                   { | ||||
|                     type: 'info', | ||||
|                     redirectTo: '/', | ||||
|                     message: dedent` | ||||
|     The user account associated with the ${provider} user ${username || 'Anon'} | ||||
|     no longer exists. | ||||
|                     ` | ||||
|                   } | ||||
|                 ); | ||||
|           return identity ? | ||||
|             Observable.of(identity.user()) : | ||||
|             User.findOne$({ where: { email } }) | ||||
|               .flatMap(user => { | ||||
|                 return user ? | ||||
|                   Observable.of(user) : | ||||
|                   User.create$({ email }).toPromise(); | ||||
|               }); | ||||
|           } | ||||
|         }) | ||||
|         .flatMap(user => { | ||||
|  | ||||
|           // identity already exists | ||||
|           // find user and log them in | ||||
|           identity.credentials = credentials; | ||||
|           const attributes = { | ||||
|             // we no longer want to keep the profile | ||||
|             // this is information we do not need or use | ||||
|             profile: null, | ||||
|             credentials: credentials, | ||||
|             modified | ||||
|           }; | ||||
|           const updateIdentity = observeQuery( | ||||
|             identity, | ||||
|             'updateAttributes', | ||||
|             attributes | ||||
|           ); | ||||
|           const createToken = observeQuery( | ||||
|             AccessToken, | ||||
|             'create', | ||||
| @@ -165,17 +132,24 @@ export default function(UserIdent) { | ||||
|               ttl: user.constructor.settings.ttl | ||||
|             } | ||||
|           ); | ||||
|           const updateUser = user.update$({ | ||||
|             email: email, | ||||
|             emailVerified: true, | ||||
|             emailAuthLinkTTL: null, | ||||
|             emailVerifyTTL: null | ||||
|           }); | ||||
|           return Observable.combineLatest( | ||||
|             Observable.of(user), | ||||
|             updateIdentity, | ||||
|             createToken, | ||||
|             (user, identity, token) => ({ user, identity, token }) | ||||
|             updateUser, | ||||
|             (user, token) => ({ user, token }) | ||||
|           ); | ||||
|         }) | ||||
|         .subscribe( | ||||
|           ({ user, identity, token }) => cb(null, user, identity, token), | ||||
|           ({ user, token }) => cb(null, user, null, token), | ||||
|           cb | ||||
|         ); | ||||
|  | ||||
|     } | ||||
|   }; | ||||
| } | ||||
|   | ||||
							
								
								
									
										17
									
								
								sample.env
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								sample.env
									
									
									
									
									
								
							| @@ -3,23 +3,6 @@ MONGOHQ_URL='mongodb://localhost:27017/freecodecamp' | ||||
|  | ||||
| ROLLBAR_APP_ID='my-rollbar-app-id' | ||||
|  | ||||
| FACEBOOK_ID=stuff | ||||
| FACEBOOK_SECRET=stuff | ||||
|  | ||||
| GITHUB_ID=stuff | ||||
| GITHUB_SECRET=stuff | ||||
|  | ||||
| GOOGLE_ID=stuff | ||||
| GOOGLE_SECRET=stuff | ||||
|  | ||||
| LINKEDIN_ID=stuff | ||||
| LINKEDIN_SECRET=stuff | ||||
|  | ||||
| TWITTER_KEY=stuff | ||||
| TWITTER_SECRET=stuff | ||||
| TWITTER_TOKEN=stuff | ||||
| TWITTER_TOKEN_SECRET=stuff | ||||
|  | ||||
| AUTH0_CLIENT_ID=stuff | ||||
| AUTH0_CLIENT_SECRET=stuff | ||||
| AUTH0_DOMAIN=stuff | ||||
|   | ||||
| @@ -13,63 +13,6 @@ export default { | ||||
|     session: true, | ||||
|     failureFlash: true | ||||
|   }, | ||||
|   'facebook-login': { | ||||
|     provider: 'facebook', | ||||
|     module: 'passport-facebook', | ||||
|     clientID: process.env.FACEBOOK_ID, | ||||
|     clientSecret: process.env.FACEBOOK_SECRET, | ||||
|     authPath: '/auth/facebook', | ||||
|     callbackURL: '/auth/facebook/callback', | ||||
|     callbackPath: '/auth/facebook/callback', | ||||
|     useCustomCallback: true, | ||||
|     successRedirect: successRedirect, | ||||
|     failureRedirect: failureRedirect, | ||||
|     scope: ['email'], | ||||
|     failureFlash: true | ||||
|   }, | ||||
|   'google-login': { | ||||
|     provider: 'google', | ||||
|     authScheme: 'oauth2', | ||||
|     module: 'passport-google-oauth2', | ||||
|     clientID: process.env.GOOGLE_ID, | ||||
|     clientSecret: process.env.GOOGLE_SECRET, | ||||
|     authPath: '/auth/google', | ||||
|     callbackURL: '/auth/google/callback', | ||||
|     callbackPath: '/auth/google/callback', | ||||
|     useCustomCallback: true, | ||||
|     successRedirect: successRedirect, | ||||
|     failureRedirect: failureRedirect, | ||||
|     scope: ['email', 'profile'], | ||||
|     failureFlash: true | ||||
|   }, | ||||
|   'twitter-login': { | ||||
|     provider: 'twitter', | ||||
|     authScheme: 'oauth', | ||||
|     module: 'passport-twitter', | ||||
|     authPath: '/auth/twitter', | ||||
|     callbackURL: '/auth/twitter/callback', | ||||
|     callbackPath: '/auth/twitter/callback', | ||||
|     useCustomCallback: true, | ||||
|     successRedirect: successRedirect, | ||||
|     failureRedirect: failureRedirect, | ||||
|     consumerKey: process.env.TWITTER_KEY, | ||||
|     consumerSecret: process.env.TWITTER_SECRET, | ||||
|     failureFlash: true | ||||
|   }, | ||||
|   'github-login': { | ||||
|     provider: 'github', | ||||
|     authScheme: 'oauth2', | ||||
|     module: 'passport-github', | ||||
|     authPath: '/auth/github', | ||||
|     callbackURL: '/auth/github/callback', | ||||
|     callbackPath: '/auth/github/callback', | ||||
|     useCustomCallback: true, | ||||
|     successRedirect: successRedirect, | ||||
|     failureRedirect: failureRedirect, | ||||
|     clientID: process.env.GITHUB_ID, | ||||
|     clientSecret: process.env.GITHUB_SECRET, | ||||
|     failureFlash: true | ||||
|   }, | ||||
|   'auth0-login': { | ||||
|     provider: 'auth0', | ||||
|     module: 'passport-auth0', | ||||
| @@ -83,7 +26,7 @@ export default { | ||||
|     useCustomCallback: true, | ||||
|     successRedirect: successRedirect, | ||||
|     failureRedirect: failureRedirect, | ||||
|     scope: ['openid email'], | ||||
|     scope: ['openid profile email'], | ||||
|     failureFlash: true | ||||
|   } | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user