import { Observable } from 'rx'; // import debug from 'debug'; import dedent from 'dedent'; import { isEmail } from 'validator'; 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'); }); UserIdent.login = function( _provider, authScheme, profile, credentials, options, cb ) { const User = UserIdent.app.models.User; const AccessToken = UserIdent.app.models.AccessToken; 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 [provider, socialExtId] = auth0IdString.split('|'); const query = { where: { provider: provider, 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 === 'email') { return User.findOne$({ where: { email } }) .flatMap(user => { return user ? Observable.of(user) : User.create$({ email }).toPromise(); }) .flatMap(user => { if (!user) { throw wrapHandledError( 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. `, type: 'info', redirectTo: '/' } ); } const createToken = observeQuery(AccessToken, 'create', { userId: user.id, created: new Date(), ttl: user.constructor.settings.ttl }); const updateUserPromise = new Promise((resolve, reject) => user.updateAttributes( { emailVerified: true, emailAuthLinkTTL: null, emailVerifyTTL: null }, err => { if (err) { return reject(err); } return resolve(); } ) ); return Observable.combineLatest( Observable.of(user), createToken, Observable.fromPromise(updateUserPromise), (user, token) => ({ user, token }) ); }) .subscribe(({ user, token }) => cb(null, user, null, token), cb); } else { return UserIdent.findOne$(query) .flatMap(identity => { return identity ? Observable.of(identity.user()) : User.findOne$({ where: { email } }).flatMap(user => { return user ? Observable.of(user) : User.create$({ email }).toPromise(); }); }) .flatMap(user => { const createToken = observeQuery(AccessToken, 'create', { userId: user.id, created: new Date(), ttl: user.constructor.settings.ttl }); const updateUser = new Promise((resolve, reject) => user.updateAttributes( { email: email, emailVerified: true, emailAuthLinkTTL: null, emailVerifyTTL: null }, err => { if (err) { return reject(err); } return resolve(); } ) ); return Observable.combineLatest( Observable.of(user), createToken, Observable.fromPromise(updateUser), (user, token) => ({ user, token }) ); }) .subscribe(({ user, token }) => cb(null, user, null, token), cb); } }; }