fix(signup): signup auth (#15628)
* fix(models.user): Colocate all user methods Moved user methods/extensions into one file. Tracked down `next method called more than once` error and setting headers after their sent. Let regular error handler handle api errors as well. * feat(server.auth): Disable github account creation We are no longer allowing account creation through github * refactor(Auth): Move user identity link into models dir * feat(Disable link account login): This removes the ability to use a linked account t * feat(errorhandlers): Add opbeat, filter out handled error
This commit is contained in:
committed by
mrugesh mohapatra
parent
7805d74ea7
commit
2fcd976700
88
common/models/User-Credential.js
Normal file
88
common/models/User-Credential.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { Observable } from 'rx';
|
||||||
|
import debug from 'debug';
|
||||||
|
|
||||||
|
import { observeMethod, observeQuery } from '../../server/utils/rx';
|
||||||
|
import {
|
||||||
|
createUserUpdatesFromProfile,
|
||||||
|
getSocialProvider
|
||||||
|
} from '../../server/utils/auth';
|
||||||
|
|
||||||
|
const log = debug('fcc:models:UserCredential');
|
||||||
|
module.exports = function(UserCredential) {
|
||||||
|
UserCredential.link = function(
|
||||||
|
userId,
|
||||||
|
_provider,
|
||||||
|
authScheme,
|
||||||
|
profile,
|
||||||
|
credentials,
|
||||||
|
options = {},
|
||||||
|
cb
|
||||||
|
) {
|
||||||
|
if (typeof options === 'function' && !cb) {
|
||||||
|
cb = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
const User = UserCredential.app.models.User;
|
||||||
|
const findCred = observeMethod(UserCredential, 'findOne');
|
||||||
|
const createCred = observeMethod(UserCredential, 'create');
|
||||||
|
|
||||||
|
const provider = getSocialProvider(_provider);
|
||||||
|
const query = {
|
||||||
|
where: {
|
||||||
|
provider: provider,
|
||||||
|
externalId: profile.id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// find createCred if they exist
|
||||||
|
// if not create it
|
||||||
|
// if yes, update credentials
|
||||||
|
// also if github
|
||||||
|
// update profile
|
||||||
|
// update username
|
||||||
|
// update picture
|
||||||
|
log('link query', query);
|
||||||
|
return findCred(query)
|
||||||
|
.flatMap(_credentials => {
|
||||||
|
const modified = new Date();
|
||||||
|
const updateUser = User.update$(
|
||||||
|
{ id: userId },
|
||||||
|
createUserUpdatesFromProfile(provider, profile)
|
||||||
|
);
|
||||||
|
let updateCredentials;
|
||||||
|
if (!_credentials) {
|
||||||
|
updateCredentials = createCred({
|
||||||
|
provider,
|
||||||
|
externalId: profile.id,
|
||||||
|
authScheme,
|
||||||
|
// we no longer want to keep the profile
|
||||||
|
// this is information we do not need or use
|
||||||
|
profile: null,
|
||||||
|
credentials,
|
||||||
|
userId,
|
||||||
|
created: modified,
|
||||||
|
modified
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_credentials.credentials = credentials;
|
||||||
|
updateCredentials = observeQuery(
|
||||||
|
_credentials,
|
||||||
|
'updateAttributes',
|
||||||
|
{
|
||||||
|
profile: null,
|
||||||
|
credentials,
|
||||||
|
modified
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return Observable.combineLatest(
|
||||||
|
updateUser,
|
||||||
|
updateCredentials,
|
||||||
|
(_, credentials) => credentials
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.subscribe(
|
||||||
|
credentials => cb(null, credentials),
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
@ -1,172 +1,131 @@
|
|||||||
import loopback from 'loopback';
|
import { Observable } from 'rx';
|
||||||
import debugFactory from 'debug';
|
// import debug from 'debug';
|
||||||
|
import dedent from 'dedent';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
setProfileFromGithub,
|
getSocialProvider,
|
||||||
getFirstImageFromProfile,
|
|
||||||
getUsernameFromProvider,
|
getUsernameFromProvider,
|
||||||
getSocialProvider
|
createUserUpdatesFromProfile
|
||||||
} from '../../server/utils/auth';
|
} from '../../server/utils/auth';
|
||||||
import { defaultProfileImage } from '../utils/constantStrings.json';
|
import { observeMethod, observeQuery } from '../../server/utils/rx';
|
||||||
|
import { wrapHandledError } from '../../server/utils/create-handled-error.js';
|
||||||
|
|
||||||
const githubRegex = (/github/i);
|
// const log = debug('fcc:models:userIdent');
|
||||||
const debug = debugFactory('fcc:models:userIdent');
|
|
||||||
|
|
||||||
export default function(UserIdent) {
|
export default function(UserIdent) {
|
||||||
|
UserIdent.on('dataSourceAttached', () => {
|
||||||
|
UserIdent.findOne$ = observeMethod(UserIdent, 'findOne');
|
||||||
|
});
|
||||||
// original source
|
// original source
|
||||||
// github.com/strongloop/loopback-component-passport
|
// github.com/strongloop/loopback-component-passport
|
||||||
const createAccountMessage =
|
// find identity if it exist
|
||||||
'Accounts can only be created using GitHub or though email';
|
// if not redirect to email signup
|
||||||
|
// if yes and github
|
||||||
|
// update profile
|
||||||
|
// update username
|
||||||
|
// update picture
|
||||||
UserIdent.login = function(
|
UserIdent.login = function(
|
||||||
provider,
|
_provider,
|
||||||
authScheme,
|
authScheme,
|
||||||
profile,
|
profile,
|
||||||
credentials,
|
credentials,
|
||||||
options,
|
options,
|
||||||
cb
|
cb
|
||||||
) {
|
) {
|
||||||
|
const User = UserIdent.app.models.User;
|
||||||
|
const AccessToken = UserIdent.app.models.AccessToken;
|
||||||
|
const provider = getSocialProvider(_provider);
|
||||||
options = options || {};
|
options = options || {};
|
||||||
if (typeof options === 'function' && !cb) {
|
if (typeof options === 'function' && !cb) {
|
||||||
cb = options;
|
cb = options;
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
const userIdentityModel = UserIdent;
|
|
||||||
profile.id = profile.id || profile.openid;
|
profile.id = profile.id || profile.openid;
|
||||||
const filter = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
provider: getSocialProvider(provider),
|
provider: provider,
|
||||||
externalId: profile.id
|
externalId: profile.id
|
||||||
}
|
},
|
||||||
|
include: 'user'
|
||||||
};
|
};
|
||||||
return userIdentityModel.findOne(filter)
|
return UserIdent.findOne$(query)
|
||||||
.then(identity => {
|
.flatMap(identity => {
|
||||||
|
if (!identity) {
|
||||||
|
throw wrapHandledError(
|
||||||
|
new Error('user identity account not found'),
|
||||||
|
{
|
||||||
|
message: dedent`
|
||||||
|
New accounts can only be created using an email address.
|
||||||
|
Please create an account below
|
||||||
|
`,
|
||||||
|
type: 'info',
|
||||||
|
redirectTo: '/signup'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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: '/signup',
|
||||||
|
message: dedent`
|
||||||
|
The user account associated with the ${provider} user ${username || 'Anon'}
|
||||||
|
no longer exists.
|
||||||
|
`
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const updateUser = User.update$(
|
||||||
|
{ id: user.id },
|
||||||
|
createUserUpdatesFromProfile(provider, profile)
|
||||||
|
).map(() => user);
|
||||||
// identity already exists
|
// identity already exists
|
||||||
// find user and log them in
|
// find user and log them in
|
||||||
if (identity) {
|
|
||||||
identity.credentials = credentials;
|
identity.credentials = credentials;
|
||||||
const options = {
|
const attributes = {
|
||||||
profile: profile,
|
// we no longer want to keep the profile
|
||||||
|
// this is information we do not need or use
|
||||||
|
profile: null,
|
||||||
credentials: credentials,
|
credentials: credentials,
|
||||||
modified: new Date()
|
modified
|
||||||
};
|
};
|
||||||
return identity.updateAttributes(options)
|
const updateIdentity = observeQuery(
|
||||||
// grab user associated with identity
|
identity,
|
||||||
.then(() => identity.user())
|
'updateAttributes',
|
||||||
.then(user => {
|
attributes
|
||||||
// Create access token for user
|
);
|
||||||
const options = {
|
const createToken = observeQuery(
|
||||||
created: new Date(),
|
AccessToken,
|
||||||
ttl: user.constructor.settings.ttl
|
'create',
|
||||||
};
|
{
|
||||||
return user.accessTokens.create(options)
|
|
||||||
.then(token => ({ user, token }));
|
|
||||||
})
|
|
||||||
.then(({ token, user })=> {
|
|
||||||
cb(null, user, identity, token);
|
|
||||||
})
|
|
||||||
.catch(err => cb(err));
|
|
||||||
}
|
|
||||||
// Find the user model
|
|
||||||
const userModel = userIdentityModel.relations.user &&
|
|
||||||
userIdentityModel.relations.user.modelTo ||
|
|
||||||
loopback.getModelByType(loopback.User);
|
|
||||||
|
|
||||||
const userObj = options.profileToUser(provider, profile, options);
|
|
||||||
if (getSocialProvider(provider) !== 'github') {
|
|
||||||
const err = new Error(createAccountMessage);
|
|
||||||
err.userMessage = createAccountMessage;
|
|
||||||
err.messageType = 'info';
|
|
||||||
err.redirectTo = '/signin';
|
|
||||||
return process.nextTick(() => cb(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
let query;
|
|
||||||
if (userObj.email) {
|
|
||||||
query = { or: [
|
|
||||||
{ username: userObj.username },
|
|
||||||
{ email: userObj.email }
|
|
||||||
]};
|
|
||||||
} else {
|
|
||||||
query = { username: userObj.username };
|
|
||||||
}
|
|
||||||
return userModel.findOrCreate({ where: query }, userObj)
|
|
||||||
.then(([ user ]) => {
|
|
||||||
const promises = [
|
|
||||||
userIdentityModel.create({
|
|
||||||
provider: getSocialProvider(provider),
|
|
||||||
externalId: profile.id,
|
|
||||||
authScheme: authScheme,
|
|
||||||
profile: profile,
|
|
||||||
credentials: credentials,
|
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
created: new Date(),
|
|
||||||
modified: new Date()
|
|
||||||
}),
|
|
||||||
user.accessTokens.create({
|
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
ttl: user.constructor.settings.ttl
|
ttl: user.constructor.settings.ttl
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return Observable.combineLatest(
|
||||||
|
updateUser,
|
||||||
|
updateIdentity,
|
||||||
|
createToken,
|
||||||
|
(user, identity, token) => ({ user, identity, token })
|
||||||
|
);
|
||||||
})
|
})
|
||||||
];
|
.subscribe(
|
||||||
return Promise.all(promises)
|
({ user, identity, token }) => cb(null, user, identity, token),
|
||||||
.then(([ identity, token ]) => ({ user, identity, token }));
|
cb
|
||||||
})
|
);
|
||||||
.then(({ user, token, identity }) => cb(null, user, identity, token))
|
|
||||||
.catch(err => cb(err));
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
UserIdent.observe('before save', function(ctx, next) {
|
|
||||||
const userIdent = ctx.currentInstance || ctx.instance;
|
|
||||||
if (!userIdent) {
|
|
||||||
debug('no user identity instance found');
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
return userIdent.user(function(err, user) {
|
|
||||||
let userChanged = false;
|
|
||||||
if (err) { return next(err); }
|
|
||||||
if (!user) {
|
|
||||||
debug('no user attached to identity!');
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { profile, provider } = userIdent;
|
|
||||||
const picture = getFirstImageFromProfile(profile);
|
|
||||||
|
|
||||||
debug('picture', picture, user.picture);
|
|
||||||
// check if picture was found
|
|
||||||
// check if user has no picture
|
|
||||||
// check if user has default picture
|
|
||||||
// set user.picture from oauth provider
|
|
||||||
if (
|
|
||||||
picture &&
|
|
||||||
(!user.picture || user.picture === defaultProfileImage)
|
|
||||||
) {
|
|
||||||
debug('setting user picture');
|
|
||||||
user.picture = picture;
|
|
||||||
userChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!githubRegex.test(provider) && profile) {
|
|
||||||
user[provider] = getUsernameFromProvider(provider, profile);
|
|
||||||
userChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if user signed in with github refresh their info
|
|
||||||
if (githubRegex.test(provider) && profile && profile._json) {
|
|
||||||
debug("user isn't github cool or username from github is different");
|
|
||||||
setProfileFromGithub(user, profile, profile._json);
|
|
||||||
userChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (userChanged) {
|
|
||||||
return user.save(function(err) {
|
|
||||||
if (err) { return next(err); }
|
|
||||||
return next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
debug('exiting after user identity before save');
|
|
||||||
return next();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,26 @@ import debugFactory from 'debug';
|
|||||||
import { isEmail } from 'validator';
|
import { isEmail } from 'validator';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { saveUser, observeMethod } from '../../server/utils/rx';
|
import { saveUser, observeMethod } from '../../server/utils/rx.js';
|
||||||
import { blacklistedUsernames } from '../../server/utils/constants';
|
import { blacklistedUsernames } from '../../server/utils/constants.js';
|
||||||
|
import { wrapHandledError } from '../../server/utils/create-handled-error.js';
|
||||||
|
|
||||||
const debug = debugFactory('fcc:user:remote');
|
const debug = debugFactory('fcc:user:remote');
|
||||||
const BROWNIEPOINTS_TIMEOUT = [1, 'hour'];
|
const BROWNIEPOINTS_TIMEOUT = [1, 'hour'];
|
||||||
const isDev = process.env.NODE_ENV !== 'production';
|
const isDev = process.env.NODE_ENV !== 'production';
|
||||||
const devHost = process.env.HOST || 'localhost';
|
const devHost = process.env.HOST || 'localhost';
|
||||||
|
|
||||||
|
const createEmailError = () => new Error(
|
||||||
|
'Please check to make sure the email is a valid email address.'
|
||||||
|
);
|
||||||
|
|
||||||
|
function destroyAll(id, Model) {
|
||||||
|
return Observable.fromNodeCallback(
|
||||||
|
Model.destroyAll,
|
||||||
|
Model
|
||||||
|
)({ userId: id });
|
||||||
|
}
|
||||||
|
|
||||||
function getAboutProfile({
|
function getAboutProfile({
|
||||||
username,
|
username,
|
||||||
githubProfile: github,
|
githubProfile: github,
|
||||||
@ -64,10 +76,99 @@ module.exports = function(User) {
|
|||||||
User.count$ = Observable.fromNodeCallback(User.count, User);
|
User.count$ = Observable.fromNodeCallback(User.count, User);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
User.beforeRemote('create', function({ req }) {
|
||||||
|
const body = req.body;
|
||||||
|
// note(berks): we now require all new users to supply an email
|
||||||
|
// this was not always the case
|
||||||
|
if (
|
||||||
|
typeof body.email !== 'string' ||
|
||||||
|
!isEmail(body.email)
|
||||||
|
) {
|
||||||
|
return Promise.reject(createEmailError());
|
||||||
|
}
|
||||||
|
// assign random username to new users
|
||||||
|
// actual usernames will come from github
|
||||||
|
body.username = 'fcc' + uuid.v4();
|
||||||
|
if (body) {
|
||||||
|
// this is workaround for preventing a server crash
|
||||||
|
// we do this on create and on save
|
||||||
|
// refer strongloop/loopback/#1364
|
||||||
|
if (body.password === '') {
|
||||||
|
body.password = null;
|
||||||
|
}
|
||||||
|
// set email verified false on user email signup
|
||||||
|
// should not be set with oauth signin methods
|
||||||
|
body.emailVerified = false;
|
||||||
|
}
|
||||||
|
return User.doesExist(null, body.email)
|
||||||
|
.catch(err => {
|
||||||
|
throw wrapHandledError(err, { redirectTo: '/email-signup' });
|
||||||
|
})
|
||||||
|
.then(exists => {
|
||||||
|
if (!exists) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const err = wrapHandledError(
|
||||||
|
new Error('user already exists'),
|
||||||
|
{
|
||||||
|
redirectTo: '/email-signin',
|
||||||
|
message: dedent`
|
||||||
|
The ${body.email} email address is already associated with an account.
|
||||||
|
Try signing in with it here instead.
|
||||||
|
`
|
||||||
|
}
|
||||||
|
);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// send welcome email to new camper
|
||||||
|
User.afterRemote('create', function({ req, res }, user, next) {
|
||||||
|
debug('user created, sending email');
|
||||||
|
if (!user.email || !isEmail(user.email)) { return next(); }
|
||||||
|
const redirect = req.session && req.session.returnTo ?
|
||||||
|
req.session.returnTo :
|
||||||
|
'/';
|
||||||
|
|
||||||
|
var mailOptions = {
|
||||||
|
type: 'email',
|
||||||
|
to: user.email,
|
||||||
|
from: 'team@freecodecamp.com',
|
||||||
|
subject: 'Welcome to freeCodeCamp!',
|
||||||
|
protocol: isDev ? null : 'https',
|
||||||
|
host: isDev ? devHost : 'freecodecamp.com',
|
||||||
|
port: isDev ? null : 443,
|
||||||
|
template: path.join(
|
||||||
|
__dirname,
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'server',
|
||||||
|
'views',
|
||||||
|
'emails',
|
||||||
|
'a-extend-user-welcome.ejs'
|
||||||
|
),
|
||||||
|
redirect: '/email-signin'
|
||||||
|
};
|
||||||
|
|
||||||
|
debug('sending welcome email');
|
||||||
|
return user.verify(mailOptions, function(err) {
|
||||||
|
if (err) { return next(err); }
|
||||||
|
req.flash('success', {
|
||||||
|
msg: [ 'Congratulations ! We\'ve created your account. ',
|
||||||
|
'Please check your email. We sent you a link that you can ',
|
||||||
|
'click to verify your email address and then login.'
|
||||||
|
].join('')
|
||||||
|
});
|
||||||
|
return res.redirect(redirect);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
User.observe('before save', function({ instance: user }, next) {
|
User.observe('before save', function({ instance: user }, next) {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
// Some old accounts will not have emails associated with theme
|
||||||
|
// we verify only if the email field is populated
|
||||||
if (user.email && !isEmail(user.email)) {
|
if (user.email && !isEmail(user.email)) {
|
||||||
return next(new Error('Email format is not valid'));
|
return next(createEmailError());
|
||||||
}
|
}
|
||||||
user.username = user.username.trim().toLowerCase();
|
user.username = user.username.trim().toLowerCase();
|
||||||
user.email = typeof user.email === 'string' ?
|
user.email = typeof user.email === 'string' ?
|
||||||
@ -82,6 +183,7 @@ module.exports = function(User) {
|
|||||||
user.progressTimestamps.push({ timestamp: Date.now() });
|
user.progressTimestamps.push({ timestamp: Date.now() });
|
||||||
}
|
}
|
||||||
// this is workaround for preventing a server crash
|
// this is workaround for preventing a server crash
|
||||||
|
// we do this on save and on create
|
||||||
// refer strongloop/loopback/#1364
|
// refer strongloop/loopback/#1364
|
||||||
if (user.password === '') {
|
if (user.password === '') {
|
||||||
user.password = null;
|
user.password = null;
|
||||||
@ -90,6 +192,40 @@ module.exports = function(User) {
|
|||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// remove lingering user identities before deleting user
|
||||||
|
User.observe('before delete', function(ctx, next) {
|
||||||
|
const UserIdentity = User.app.models.UserIdentity;
|
||||||
|
const UserCredential = User.app.models.UserCredential;
|
||||||
|
debug('removing user', ctx.where);
|
||||||
|
var id = ctx.where && ctx.where.id ? ctx.where.id : null;
|
||||||
|
if (!id) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
return Observable.combineLatest(
|
||||||
|
destroyAll(id, UserIdentity),
|
||||||
|
destroyAll(id, UserCredential),
|
||||||
|
function(identData, credData) {
|
||||||
|
return {
|
||||||
|
identData: identData,
|
||||||
|
credData: credData
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.subscribe(
|
||||||
|
function(data) {
|
||||||
|
debug('deleted', data);
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
|
debug('error deleting user %s stuff', id, err);
|
||||||
|
next(err);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
debug('user stuff deleted for user %s', id);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
debug('setting up user hooks');
|
debug('setting up user hooks');
|
||||||
|
|
||||||
User.beforeRemote('confirm', function(ctx, _, next) {
|
User.beforeRemote('confirm', function(ctx, _, next) {
|
||||||
@ -153,41 +289,9 @@ module.exports = function(User) {
|
|||||||
return ctx.res.redirect(redirect);
|
return ctx.res.redirect(redirect);
|
||||||
});
|
});
|
||||||
|
|
||||||
User.beforeRemote('create', function({ req, res }, _, next) {
|
|
||||||
req.body.username = 'fcc' + uuid.v4().slice(0, 8);
|
|
||||||
if (!req.body.email) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
if (!isEmail(req.body.email)) {
|
|
||||||
return next(new Error('Email format is not valid'));
|
|
||||||
}
|
|
||||||
return User.doesExist(null, req.body.email)
|
|
||||||
.then(exists => {
|
|
||||||
if (!exists) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
req.flash('error', {
|
|
||||||
msg: dedent`
|
|
||||||
The ${req.body.email} email address is already associated with an account.
|
|
||||||
Try signing in with it here instead.
|
|
||||||
`
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.redirect('/email-signin');
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
req.flash('error', {
|
|
||||||
msg: 'Oops, something went wrong, please try again later'
|
|
||||||
});
|
|
||||||
return res.redirect('/email-signup');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
User.on('resetPasswordRequest', function(info) {
|
User.on('resetPasswordRequest', function(info) {
|
||||||
if (!isEmail(info.email)) {
|
if (!isEmail(info.email)) {
|
||||||
console.error(new Error('Email format is not valid'));
|
console.error(createEmailError());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let url;
|
let url;
|
||||||
@ -232,7 +336,7 @@ module.exports = function(User) {
|
|||||||
const { body } = ctx.req;
|
const { body } = ctx.req;
|
||||||
if (body && typeof body.email === 'string') {
|
if (body && typeof body.email === 'string') {
|
||||||
if (!isEmail(body.email)) {
|
if (!isEmail(body.email)) {
|
||||||
return next(new Error('Email format is not valid'));
|
return next(createEmailError());
|
||||||
}
|
}
|
||||||
body.email = body.email.toLowerCase();
|
body.email = body.email.toLowerCase();
|
||||||
}
|
}
|
||||||
@ -392,9 +496,7 @@ module.exports = function(User) {
|
|||||||
true;
|
true;
|
||||||
|
|
||||||
if (!isEmail('' + email)) {
|
if (!isEmail('' + email)) {
|
||||||
return Observable.throw(
|
return Observable.throw(createEmailError());
|
||||||
new Error('The submitted email not valid.')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// email is already associated and verified with this account
|
// email is already associated and verified with this account
|
||||||
if (ownEmail && this.emailVerified) {
|
if (ownEmail && this.emailVerified) {
|
||||||
@ -588,11 +690,13 @@ module.exports = function(User) {
|
|||||||
|
|
||||||
User.prototype.updateTheme = function updateTheme(theme) {
|
User.prototype.updateTheme = function updateTheme(theme) {
|
||||||
if (!this.constructor.themes[theme]) {
|
if (!this.constructor.themes[theme]) {
|
||||||
const err = new Error(
|
const err = wrapHandledError(
|
||||||
'Theme is not valid.'
|
new Error('Theme is not valid.'),
|
||||||
|
{
|
||||||
|
Type: 'info',
|
||||||
|
message: err.message
|
||||||
|
}
|
||||||
);
|
);
|
||||||
err.messageType = 'info';
|
|
||||||
err.userMessage = err.message;
|
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
return this.update$({ theme })
|
return this.update$({ theme })
|
||||||
|
136
package-lock.json
generated
136
package-lock.json
generated
@ -124,6 +124,11 @@
|
|||||||
"integrity": "sha1-q11PuIP1loFtNRX495HAr0ht1ic=",
|
"integrity": "sha1-q11PuIP1loFtNRX495HAr0ht1ic=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"after-all-results": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/after-all-results/-/after-all-results-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-asL8ICtQD4jaj09VMM+hAPTGotA="
|
||||||
|
},
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.8",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
|
||||||
@ -303,6 +308,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz",
|
||||||
"integrity": "sha1-5YfGhYCZSsZ/xW/4bTrFa9voELw="
|
"integrity": "sha1-5YfGhYCZSsZ/xW/4bTrFa9voELw="
|
||||||
},
|
},
|
||||||
|
"async-cache": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/async-cache/-/async-cache-1.1.0.tgz",
|
||||||
|
"integrity": "sha1-SppaidBl7F2OUlS9nulrp2xTK1o="
|
||||||
|
},
|
||||||
"async-each": {
|
"async-each": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
|
||||||
@ -1737,6 +1747,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
|
||||||
"integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA="
|
"integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA="
|
||||||
},
|
},
|
||||||
|
"console-log-level": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.0.tgz",
|
||||||
|
"integrity": "sha1-QDWBi+6jflhQoMA8jUUMpfLNEhc="
|
||||||
|
},
|
||||||
"constantinople": {
|
"constantinople": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.0.2.tgz",
|
||||||
@ -2442,6 +2457,11 @@
|
|||||||
"integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=",
|
"integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"error-callsites": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/error-callsites/-/error-callsites-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-QoYWmt+PwSSC9VYRFyTFrthzppI="
|
||||||
|
},
|
||||||
"error-ex": {
|
"error-ex": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
|
||||||
@ -2875,6 +2895,11 @@
|
|||||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"fast-safe-stringify": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-69QmZv0Y/k8rpPDSlQZfP4XK3pY="
|
||||||
|
},
|
||||||
"fbjs": {
|
"fbjs": {
|
||||||
"version": "0.8.12",
|
"version": "0.8.12",
|
||||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz",
|
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz",
|
||||||
@ -4395,6 +4420,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hashlru": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.2.0.tgz",
|
||||||
|
"integrity": "sha1-eTpYlD+QKupXgXfXsDNfE/JpS3E="
|
||||||
|
},
|
||||||
"hawk": {
|
"hawk": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
|
||||||
@ -4578,6 +4608,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||||
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
|
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
|
||||||
},
|
},
|
||||||
|
"in-publish": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E="
|
||||||
|
},
|
||||||
"indent-string": {
|
"indent-string": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
|
||||||
@ -4752,12 +4787,27 @@
|
|||||||
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
|
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"is-integer": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.7.tgz",
|
||||||
|
"integrity": "sha1-a96Bqs3feLZZtmKdYpytxRqIbVw="
|
||||||
|
},
|
||||||
"is-my-json-valid": {
|
"is-my-json-valid": {
|
||||||
"version": "2.16.0",
|
"version": "2.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz",
|
||||||
"integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=",
|
"integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"is-native": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-native/-/is-native-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-zRjMFi6EUNaDtbq+eayZwUVElnU="
|
||||||
|
},
|
||||||
|
"is-nil": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-nil/-/is-nil-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-LauingtYUGOHXntTnQcfWxWTeWk="
|
||||||
|
},
|
||||||
"is-npm": {
|
"is-npm": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
|
||||||
@ -4865,6 +4915,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz",
|
||||||
"integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ="
|
"integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ="
|
||||||
},
|
},
|
||||||
|
"is-secret": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-secret/-/is-secret-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-KYig6bOU41YM1IBAbWHKz9dPH/k="
|
||||||
|
},
|
||||||
"is-stream": {
|
"is-stream": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||||
@ -5265,6 +5320,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
|
||||||
"integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ="
|
"integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ="
|
||||||
},
|
},
|
||||||
|
"load-source-map": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/load-source-map/-/load-source-map-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-MY9JkFzopwnft8w/FvPv47zx3QU="
|
||||||
|
},
|
||||||
"loader-utils": {
|
"loader-utils": {
|
||||||
"version": "0.2.17",
|
"version": "0.2.17",
|
||||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
|
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
|
||||||
@ -6071,6 +6131,11 @@
|
|||||||
"integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=",
|
"integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"module-details-from-path": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz",
|
||||||
|
"integrity": "sha1-EUyUlnPiqKNenTV4hSeqN7Z52is="
|
||||||
|
},
|
||||||
"module-not-found-error": {
|
"module-not-found-error": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz",
|
||||||
@ -6441,6 +6506,11 @@
|
|||||||
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
|
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"normalize-bool": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/normalize-bool/-/normalize-bool-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-RqVx7ZPqWrM3IfrM/FpZuGiQ2Fg="
|
||||||
|
},
|
||||||
"normalize-package-data": {
|
"normalize-package-data": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
|
||||||
@ -6569,6 +6639,28 @@
|
|||||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
||||||
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k="
|
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k="
|
||||||
},
|
},
|
||||||
|
"opbeat": {
|
||||||
|
"version": "4.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/opbeat/-/opbeat-4.14.0.tgz",
|
||||||
|
"integrity": "sha1-rpB3qvqRS3KkSAGQWjK8tT1+dd8=",
|
||||||
|
"dependencies": {
|
||||||
|
"end-of-stream": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz",
|
||||||
|
"integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opbeat-http-client": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/opbeat-http-client/-/opbeat-http-client-1.2.2.tgz",
|
||||||
|
"integrity": "sha1-itOZlp1QglTazi0IU5gTaBF9oz8="
|
||||||
|
},
|
||||||
|
"opbeat-release-tracker": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/opbeat-release-tracker/-/opbeat-release-tracker-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-L2V2clC5Va6YjtyodazYNhIOfgo="
|
||||||
|
},
|
||||||
"open": {
|
"open": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz",
|
||||||
@ -6613,7 +6705,8 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
|
||||||
"integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8="
|
"integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"orchestrator": {
|
"orchestrator": {
|
||||||
"version": "0.3.8",
|
"version": "0.3.8",
|
||||||
@ -6869,8 +6962,7 @@
|
|||||||
"path-parse": {
|
"path-parse": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
|
||||||
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
|
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"path-root": {
|
"path-root": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
@ -7364,6 +7456,11 @@
|
|||||||
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
|
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"redact-secrets": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redact-secrets/-/redact-secrets-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-YPHbVpJP6QogO6jMs5KDzbsNkHw="
|
||||||
|
},
|
||||||
"redent": {
|
"redent": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
|
||||||
@ -7512,6 +7609,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
|
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
|
||||||
},
|
},
|
||||||
|
"require-in-the-middle": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-2.1.2.tgz",
|
||||||
|
"integrity": "sha1-vduJMW1FvNsI4sYYa9Lm6Bmo7q4="
|
||||||
|
},
|
||||||
"require-main-filename": {
|
"require-main-filename": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
|
||||||
@ -7545,8 +7647,7 @@
|
|||||||
"resolve": {
|
"resolve": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz",
|
||||||
"integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=",
|
"integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"resolve-dir": {
|
"resolve-dir": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
@ -8259,6 +8360,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||||
},
|
},
|
||||||
|
"sql-summary": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sql-summary/-/sql-summary-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-OeOlHY2F5Gc5g2/H1n0GVLFzo58="
|
||||||
|
},
|
||||||
"sse": {
|
"sse": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/sse/-/sse-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/sse/-/sse-0.0.6.tgz",
|
||||||
@ -8281,6 +8387,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/stable/-/stable-0.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/stable/-/stable-0.1.6.tgz",
|
||||||
"integrity": "sha1-kQ9dKu17Ugxud3SZwfMuE5/eyxA="
|
"integrity": "sha1-kQ9dKu17Ugxud3SZwfMuE5/eyxA="
|
||||||
},
|
},
|
||||||
|
"stackman": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/stackman/-/stackman-2.0.1.tgz",
|
||||||
|
"integrity": "sha1-ztMJxmLpubZn79cYOxrjDFF8uqM="
|
||||||
|
},
|
||||||
"statuses": {
|
"statuses": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
|
||||||
@ -8669,6 +8780,11 @@
|
|||||||
"integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=",
|
"integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"to-source-code": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/to-source-code/-/to-source-code-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-3RNr2x4dvYC76s8IiZJnjpBwv+o="
|
||||||
|
},
|
||||||
"to-utf8": {
|
"to-utf8": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz",
|
||||||
@ -8870,6 +8986,16 @@
|
|||||||
"resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.4.tgz",
|
||||||
"integrity": "sha1-LCo/n4PmR2L9xF5s6sZRQoZCE9s="
|
"integrity": "sha1-LCo/n4PmR2L9xF5s6sZRQoZCE9s="
|
||||||
},
|
},
|
||||||
|
"unicode-byte-truncate": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unicode-byte-truncate/-/unicode-byte-truncate-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-qm8PNHUZP+IMMgrJIT425i6HZKc="
|
||||||
|
},
|
||||||
|
"unicode-substring": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unicode-substring/-/unicode-substring-0.1.0.tgz",
|
||||||
|
"integrity": "sha1-YSDOPDkDhdvND2DDK5BlxBgdSzY="
|
||||||
|
},
|
||||||
"unique-stream": {
|
"unique-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz",
|
||||||
|
@ -89,6 +89,7 @@
|
|||||||
"normalize-url": "^1.3.1",
|
"normalize-url": "^1.3.1",
|
||||||
"normalizr": "2.2.1",
|
"normalizr": "2.2.1",
|
||||||
"object.assign": "^4.0.3",
|
"object.assign": "^4.0.3",
|
||||||
|
"opbeat": "^4.14.0",
|
||||||
"passport": "^0.2.1",
|
"passport": "^0.2.1",
|
||||||
"passport-facebook": "^2.0.0",
|
"passport-facebook": "^2.0.0",
|
||||||
"passport-github": "^1.0.0",
|
"passport-github": "^1.0.0",
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
import { Observable } from 'rx';
|
|
||||||
import debugFactory from 'debug';
|
|
||||||
import { isEmail } from 'validator';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
const debug = debugFactory('fcc:user:remote');
|
|
||||||
const isDev = process.env.NODE_ENV !== 'production';
|
|
||||||
const devHost = process.env.HOST || 'localhost';
|
|
||||||
|
|
||||||
function destroyAllRelated(id, Model) {
|
|
||||||
return Observable.fromNodeCallback(
|
|
||||||
Model.destroyAll,
|
|
||||||
Model
|
|
||||||
)({ userId: id });
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function(app) {
|
|
||||||
var User = app.models.User;
|
|
||||||
var UserIdentity = app.models.UserIdentity;
|
|
||||||
var UserCredential = app.models.UserCredential;
|
|
||||||
User.observe('before delete', function(ctx, next) {
|
|
||||||
debug('removing user', ctx.where);
|
|
||||||
var id = ctx.where && ctx.where.id ? ctx.where.id : null;
|
|
||||||
if (!id) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
return Observable.combineLatest(
|
|
||||||
destroyAllRelated(id, UserIdentity),
|
|
||||||
destroyAllRelated(id, UserCredential),
|
|
||||||
function(identData, credData) {
|
|
||||||
return {
|
|
||||||
identData: identData,
|
|
||||||
credData: credData
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.subscribe(
|
|
||||||
function(data) {
|
|
||||||
debug('deleted', data);
|
|
||||||
},
|
|
||||||
function(err) {
|
|
||||||
debug('error deleting user %s stuff', id, err);
|
|
||||||
next(err);
|
|
||||||
},
|
|
||||||
function() {
|
|
||||||
debug('user stuff deleted for user %s', id);
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// set email varified false on user email signup
|
|
||||||
// should not be set with oauth signin methods
|
|
||||||
User.beforeRemote('create', function(ctx, user, next) {
|
|
||||||
var body = ctx.req.body;
|
|
||||||
if (body) {
|
|
||||||
// this is workaround for preventing a server crash
|
|
||||||
// refer strongloop/loopback/#1364
|
|
||||||
if (body.password === '') {
|
|
||||||
body.password = null;
|
|
||||||
}
|
|
||||||
body.emailVerified = false;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
// send welcome email to new camper
|
|
||||||
User.afterRemote('create', function({ req, res }, user, next) {
|
|
||||||
debug('user created, sending email');
|
|
||||||
if (!user.email || !isEmail(user.email)) { return next(); }
|
|
||||||
const redirect = req.session && req.session.returnTo ?
|
|
||||||
req.session.returnTo :
|
|
||||||
'/';
|
|
||||||
|
|
||||||
var mailOptions = {
|
|
||||||
type: 'email',
|
|
||||||
to: user.email,
|
|
||||||
from: 'Team@freecodecamp.com',
|
|
||||||
subject: 'Welcome to freeCodeCamp!',
|
|
||||||
protocol: isDev ? null : 'https',
|
|
||||||
host: isDev ? devHost : 'freecodecamp.com',
|
|
||||||
port: isDev ? null : 443,
|
|
||||||
template: path.join(
|
|
||||||
__dirname,
|
|
||||||
'..',
|
|
||||||
'views',
|
|
||||||
'emails',
|
|
||||||
'a-extend-user-welcome.ejs'
|
|
||||||
),
|
|
||||||
redirect: '/email-signin'
|
|
||||||
};
|
|
||||||
|
|
||||||
debug('sending welcome email');
|
|
||||||
return user.verify(mailOptions, function(err) {
|
|
||||||
if (err) { return next(err); }
|
|
||||||
req.flash('success', {
|
|
||||||
msg: [ 'Congratulations ! We\'ve created your account. ',
|
|
||||||
'Please check your email. We sent you a link that you can ',
|
|
||||||
'click to verify your email address and then login.'
|
|
||||||
].join('')
|
|
||||||
});
|
|
||||||
return res.redirect(redirect);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,81 +0,0 @@
|
|||||||
import { Observable } from 'rx';
|
|
||||||
import debugFactory from 'debug';
|
|
||||||
import dedent from 'dedent';
|
|
||||||
|
|
||||||
import { observeMethod, observeQuery } from '../utils/rx';
|
|
||||||
import { getSocialProvider } from '../utils/auth';
|
|
||||||
|
|
||||||
const debug = debugFactory('fcc:userIdent');
|
|
||||||
|
|
||||||
export default function({ models }) {
|
|
||||||
const { User, UserIdentity, UserCredential } = models;
|
|
||||||
const findUserById = observeMethod(User, 'findById');
|
|
||||||
const findIdent = observeMethod(UserIdentity, 'findOne');
|
|
||||||
|
|
||||||
UserIdentity.link = function(
|
|
||||||
userId,
|
|
||||||
provider,
|
|
||||||
authScheme,
|
|
||||||
profile,
|
|
||||||
credentials,
|
|
||||||
options = {},
|
|
||||||
cb
|
|
||||||
) {
|
|
||||||
if (typeof options === 'function' && !cb) {
|
|
||||||
cb = options;
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
const user$ = findUserById(userId);
|
|
||||||
const query = {
|
|
||||||
where: {
|
|
||||||
provider: getSocialProvider(provider),
|
|
||||||
externalId: profile.id
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
debug('link identity query', query);
|
|
||||||
findIdent(query)
|
|
||||||
.flatMap(identity => {
|
|
||||||
const modified = new Date();
|
|
||||||
if (!identity) {
|
|
||||||
return observeQuery(UserIdentity, 'create', {
|
|
||||||
provider: getSocialProvider(provider),
|
|
||||||
externalId: profile.id,
|
|
||||||
authScheme,
|
|
||||||
profile,
|
|
||||||
credentials,
|
|
||||||
userId,
|
|
||||||
created: modified,
|
|
||||||
modified
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (identity.userId.toString() !== userId.toString()) {
|
|
||||||
return Observable.throw(
|
|
||||||
new Error(
|
|
||||||
dedent`
|
|
||||||
Your GitHub is already associated with another account.
|
|
||||||
You may have accidentally created a duplicate account.
|
|
||||||
No worries, though. We can fix this real quick.
|
|
||||||
Please email us with your GitHub username: team@freecodecamp.com.
|
|
||||||
`.split('/n').join(' ')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
identity.credentials = credentials;
|
|
||||||
return observeQuery(identity, 'updateAttributes', {
|
|
||||||
profile,
|
|
||||||
credentials,
|
|
||||||
modified
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.withLatestFrom(user$, (identity, user) => ({ identity, user }))
|
|
||||||
.subscribe(
|
|
||||||
({ identity, user }) => {
|
|
||||||
cb(null, user, identity);
|
|
||||||
},
|
|
||||||
cb
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
UserCredential.link = UserIdentity.link.bind(UserIdentity);
|
|
||||||
}
|
|
@ -1,48 +1,10 @@
|
|||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
import { PassportConfigurator } from 'loopback-component-passport';
|
import { PassportConfigurator } from 'loopback-component-passport';
|
||||||
import passportProviders from './passport-providers';
|
import passportProviders from './passport-providers';
|
||||||
import uuid from 'uuid';
|
|
||||||
import { generateKey } from 'loopback-component-passport/lib/models/utils';
|
|
||||||
|
|
||||||
import {
|
|
||||||
setProfileFromGithub,
|
|
||||||
getSocialProvider,
|
|
||||||
getUsernameFromProvider
|
|
||||||
} from './utils/auth';
|
|
||||||
|
|
||||||
const passportOptions = {
|
const passportOptions = {
|
||||||
emailOptional: true,
|
emailOptional: true,
|
||||||
profileToUser(provider, profile) {
|
profileToUser: null
|
||||||
const emails = profile.emails;
|
|
||||||
// NOTE(berks): get email or set to null.
|
|
||||||
// MongoDB indexs email but can be sparse(blank)
|
|
||||||
const email = emails && emails[0] && emails[0].value ?
|
|
||||||
emails[0].value :
|
|
||||||
null;
|
|
||||||
|
|
||||||
// create random username
|
|
||||||
// username will be assigned when camper signups for Github
|
|
||||||
const username = 'fcc' + uuid.v4().slice(0, 8);
|
|
||||||
const password = generateKey('password');
|
|
||||||
let userObj = {
|
|
||||||
username: username,
|
|
||||||
password: password
|
|
||||||
};
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
userObj.email = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(/github/).test(provider)) {
|
|
||||||
userObj[getSocialProvider(provider)] = getUsernameFromProvider(
|
|
||||||
getSocialProvider(provider),
|
|
||||||
profile
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
userObj = setProfileFromGithub(userObj, profile, profile._json);
|
|
||||||
}
|
|
||||||
return userObj;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"enableHttpContext": false
|
"enableHttpContext": false
|
||||||
},
|
},
|
||||||
"rest": {
|
"rest": {
|
||||||
|
"handleErrors": false,
|
||||||
"normalizeHttpPath": false,
|
"normalizeHttpPath": false,
|
||||||
"xml": false
|
"xml": false
|
||||||
},
|
},
|
||||||
@ -22,9 +23,6 @@
|
|||||||
"cors": {
|
"cors": {
|
||||||
"origin": true,
|
"origin": true,
|
||||||
"credentials": true
|
"credentials": true
|
||||||
},
|
|
||||||
"errorHandler": {
|
|
||||||
"disableStackTrace": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
},
|
},
|
||||||
"files": {},
|
"files": {},
|
||||||
"final:after": {
|
"final:after": {
|
||||||
"./middlewares/keymetrics": {},
|
"./middlewares/error-reporter": {},
|
||||||
"./middlewares/error-handlers": {}
|
"./middlewares/error-handlers": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import errorHandler from 'errorhandler';
|
import errorHandler from 'errorhandler';
|
||||||
import accepts from 'accepts';
|
import accepts from 'accepts';
|
||||||
|
import { unwrapHandledError } from '../utils/create-handled-error.js';
|
||||||
|
|
||||||
export default function prodErrorHandler() {
|
export default function prodErrorHandler() {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
@ -21,20 +22,24 @@ export default function prodErrorHandler() {
|
|||||||
// parse res type
|
// parse res type
|
||||||
const accept = accepts(req);
|
const accept = accepts(req);
|
||||||
const type = accept.type('html', 'json', 'text');
|
const type = accept.type('html', 'json', 'text');
|
||||||
|
const handled = unwrapHandledError(err);
|
||||||
|
|
||||||
const message = 'Oops! Something went wrong. Please try again later';
|
const redirectTo = handled.redirectTo || '/map';
|
||||||
|
const message = handled.message ||
|
||||||
|
'Oops! Something went wrong. Please try again later';
|
||||||
if (type === 'html') {
|
if (type === 'html') {
|
||||||
if (typeof req.flash === 'function') {
|
if (typeof req.flash === 'function') {
|
||||||
req.flash(err.messageType || 'errors', {
|
req.flash(
|
||||||
msg: err.userMessage || message
|
handled.type || 'errors',
|
||||||
});
|
{ msg: message }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return res.redirect(err.redirectTo || '/map');
|
return res.redirect(redirectTo);
|
||||||
// json
|
// json
|
||||||
} else if (type === 'json') {
|
} else if (type === 'json') {
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
return res.send({
|
return res.send({
|
||||||
message: message
|
message
|
||||||
});
|
});
|
||||||
// plain text
|
// plain text
|
||||||
} else {
|
} else {
|
||||||
|
30
server/middlewares/error-reporter.js
Normal file
30
server/middlewares/error-reporter.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import opbeat from 'opbeat';
|
||||||
|
import debug from 'debug';
|
||||||
|
|
||||||
|
import {
|
||||||
|
isHandledError,
|
||||||
|
unwrapHandledError
|
||||||
|
} from '../utils/create-handled-error.js';
|
||||||
|
|
||||||
|
const log = debug('fcc:middlewares:error-reporter');
|
||||||
|
|
||||||
|
export default function keymetrics() {
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
return (err, req, res, next) => {
|
||||||
|
if (isHandledError(err)) {
|
||||||
|
// log out user messages in development
|
||||||
|
const handled = unwrapHandledError(err);
|
||||||
|
log(handled.message);
|
||||||
|
}
|
||||||
|
next(err);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (err, req, res, next) => {
|
||||||
|
// handled errors do not need to be reported
|
||||||
|
// the report a message and redirect the user
|
||||||
|
if (isHandledError(err)) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
return opbeat.captureError(err, { request: req }, () => next(err));
|
||||||
|
};
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
import pmx from 'pmx';
|
|
||||||
|
|
||||||
export default function keymetrics() {
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
return (err, req, res, next) => next(err);
|
|
||||||
}
|
|
||||||
return (err, req, res, next) => {
|
|
||||||
if (res.statusCode < 400) { res.statusCode = 500; }
|
|
||||||
|
|
||||||
err.url = req.url;
|
|
||||||
err.component = req.url;
|
|
||||||
err.action = req.method;
|
|
||||||
err.params = req.body;
|
|
||||||
err.session = req.session;
|
|
||||||
err.username = req.user ? req.user.username : 'anonymous';
|
|
||||||
err.userId = req.user ? req.user.id : 'anonymous';
|
|
||||||
|
|
||||||
return next(pmx.notify(err));
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
var successRedirect = '/';
|
const successRedirect = '/';
|
||||||
var failureRedirect = '/signin';
|
const failureRedirect = '/signin';
|
||||||
var linkFailureRedirect = '/account';
|
const linkSuccessRedirect = '/settings';
|
||||||
var githubProfileSuccessRedirect = '/settings';
|
const linkFailureRedirect = '/settings';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
local: {
|
local: {
|
||||||
@ -36,7 +36,7 @@ export default {
|
|||||||
authPath: '/link/facebook',
|
authPath: '/link/facebook',
|
||||||
callbackURL: '/link/facebook/callback',
|
callbackURL: '/link/facebook/callback',
|
||||||
callbackPath: '/link/facebook/callback',
|
callbackPath: '/link/facebook/callback',
|
||||||
successRedirect: successRedirect,
|
successRedirect: linkSuccessRedirect,
|
||||||
failureRedirect: linkFailureRedirect,
|
failureRedirect: linkFailureRedirect,
|
||||||
scope: ['email', 'user_likes'],
|
scope: ['email', 'user_likes'],
|
||||||
link: true,
|
link: true,
|
||||||
@ -65,7 +65,7 @@ export default {
|
|||||||
authPath: '/link/google',
|
authPath: '/link/google',
|
||||||
callbackURL: '/link/google/callback',
|
callbackURL: '/link/google/callback',
|
||||||
callbackPath: '/link/google/callback',
|
callbackPath: '/link/google/callback',
|
||||||
successRedirect: successRedirect,
|
successRedirect: linkSuccessRedirect,
|
||||||
failureRedirect: linkFailureRedirect,
|
failureRedirect: linkFailureRedirect,
|
||||||
scope: ['email', 'profile'],
|
scope: ['email', 'profile'],
|
||||||
link: true,
|
link: true,
|
||||||
@ -91,7 +91,7 @@ export default {
|
|||||||
authPath: '/link/twitter',
|
authPath: '/link/twitter',
|
||||||
callbackURL: '/link/twitter/callback',
|
callbackURL: '/link/twitter/callback',
|
||||||
callbackPath: '/link/twitter/callback',
|
callbackPath: '/link/twitter/callback',
|
||||||
successRedirect: successRedirect,
|
successRedirect: linkSuccessRedirect,
|
||||||
failureRedirect: linkFailureRedirect,
|
failureRedirect: linkFailureRedirect,
|
||||||
consumerKey: process.env.TWITTER_KEY,
|
consumerKey: process.env.TWITTER_KEY,
|
||||||
consumerSecret: process.env.TWITTER_SECRET,
|
consumerSecret: process.env.TWITTER_SECRET,
|
||||||
@ -122,7 +122,7 @@ export default {
|
|||||||
authPath: '/link/linkedin',
|
authPath: '/link/linkedin',
|
||||||
callbackURL: '/link/linkedin/callback',
|
callbackURL: '/link/linkedin/callback',
|
||||||
callbackPath: '/link/linkedin/callback',
|
callbackPath: '/link/linkedin/callback',
|
||||||
successRedirect: successRedirect,
|
successRedirect: linkSuccessRedirect,
|
||||||
failureRedirect: linkFailureRedirect,
|
failureRedirect: linkFailureRedirect,
|
||||||
clientID: process.env.LINKEDIN_ID,
|
clientID: process.env.LINKEDIN_ID,
|
||||||
clientSecret: process.env.LINKEDIN_SECRET,
|
clientSecret: process.env.LINKEDIN_SECRET,
|
||||||
@ -153,7 +153,7 @@ export default {
|
|||||||
authPath: '/link/github',
|
authPath: '/link/github',
|
||||||
callbackURL: '/auth/github/callback/link',
|
callbackURL: '/auth/github/callback/link',
|
||||||
callbackPath: '/auth/github/callback/link',
|
callbackPath: '/auth/github/callback/link',
|
||||||
successRedirect: githubProfileSuccessRedirect,
|
successRedirect: linkSuccessRedirect,
|
||||||
failureRedirect: linkFailureRedirect,
|
failureRedirect: linkFailureRedirect,
|
||||||
clientID: process.env.GITHUB_ID,
|
clientID: process.env.GITHUB_ID,
|
||||||
clientSecret: process.env.GITHUB_SECRET,
|
clientSecret: process.env.GITHUB_SECRET,
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
require('dotenv').load();
|
require('dotenv').load();
|
||||||
var pmx = require('pmx');
|
|
||||||
|
|
||||||
pmx.init();
|
if (process.env.OPBEAT_ID) {
|
||||||
|
console.log('loading opbeat');
|
||||||
|
require('opbeat').start({
|
||||||
|
appId: process.env.OPBEAT_ID,
|
||||||
|
organizationId: process.env.OPBEAT_ORG_ID,
|
||||||
|
secretToken: process.env.OPBEAT_SECRET
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var _ = require('lodash'),
|
var _ = require('lodash'),
|
||||||
Rx = require('rx'),
|
Rx = require('rx'),
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
const githubRegex = (/github/i);
|
||||||
const providerHash = {
|
const providerHash = {
|
||||||
facebook: ({ id }) => id,
|
facebook: ({ id }) => id,
|
||||||
|
github: ({ username }) => username,
|
||||||
twitter: ({ username }) => username,
|
twitter: ({ username }) => username,
|
||||||
linkedin({ _json }) {
|
linkedin({ _json }) {
|
||||||
return _json && _json.publicProfileUrl || null;
|
return _json && _json.publicProfileUrl || null;
|
||||||
@ -13,14 +15,25 @@ export function getUsernameFromProvider(provider, profile) {
|
|||||||
null;
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createProfileAttributes(provider: String, profile: {}) => Object
|
||||||
|
export function createUserUpdatesFromProfile(provider, profile) {
|
||||||
|
if (githubRegex.test(provider)) {
|
||||||
|
return createProfileAttributesFromGithub(profile);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
[getSocialProvider(provider)]: getUsernameFromProvider(
|
||||||
|
getSocialProvider(provider),
|
||||||
|
profile
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
// using es6 argument destructing
|
// using es6 argument destructing
|
||||||
export function setProfileFromGithub(
|
// createProfileAttributes(profile) => profileUpdate
|
||||||
user,
|
function createProfileAttributesFromGithub(profile) {
|
||||||
{
|
const {
|
||||||
profileUrl: githubURL,
|
profileUrl: githubURL,
|
||||||
username
|
username,
|
||||||
},
|
_json: {
|
||||||
{
|
|
||||||
id: githubId,
|
id: githubId,
|
||||||
avatar_url: picture,
|
avatar_url: picture,
|
||||||
email: githubEmail,
|
email: githubEmail,
|
||||||
@ -29,13 +42,10 @@ export function setProfileFromGithub(
|
|||||||
location,
|
location,
|
||||||
bio,
|
bio,
|
||||||
name
|
name
|
||||||
}
|
} = {}
|
||||||
) {
|
} = profile;
|
||||||
return Object.assign(
|
return {
|
||||||
user,
|
|
||||||
{
|
|
||||||
name,
|
name,
|
||||||
email: user.email || githubEmail,
|
|
||||||
username: username.toLowerCase(),
|
username: username.toLowerCase(),
|
||||||
location,
|
location,
|
||||||
bio,
|
bio,
|
||||||
@ -47,14 +57,7 @@ export function setProfileFromGithub(
|
|||||||
githubURL,
|
githubURL,
|
||||||
githubEmail,
|
githubEmail,
|
||||||
githubProfile: githubURL
|
githubProfile: githubURL
|
||||||
}
|
};
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFirstImageFromProfile(profile) {
|
|
||||||
return profile && profile.photos && profile.photos[0] ?
|
|
||||||
profile.photos[0].value :
|
|
||||||
null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSocialProvider(provider) {
|
export function getSocialProvider(provider) {
|
||||||
|
18
server/utils/create-handled-error.js
Normal file
18
server/utils/create-handled-error.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const _handledError = Symbol('handledError');
|
||||||
|
|
||||||
|
export function isHandledError(err) {
|
||||||
|
return !!err[_handledError];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unwrapHandledError(err) {
|
||||||
|
return err[_handledError] || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wrapHandledError(err, {
|
||||||
|
type,
|
||||||
|
message,
|
||||||
|
redirectTo
|
||||||
|
}) {
|
||||||
|
err[_handledError] = { type, message, redirectTo };
|
||||||
|
return err;
|
||||||
|
}
|
Reference in New Issue
Block a user