Optimize code and streamline flow.
- Optimize code and streamline flow. - Add return statements for error display. - Invalidate status vars and TTLs on User Model. - Build Observable for findOrCreate
This commit is contained in:
committed by
Berkeley Martinez
parent
f8c818e7e7
commit
e006f5c97d
@ -45,6 +45,18 @@ function nextTick(fn) {
|
|||||||
return process.nextTick(fn);
|
return process.nextTick(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWaitPeriod(ttl) {
|
||||||
|
const fiveMinutesAgo = moment().subtract(5, 'minutes');
|
||||||
|
const lastEmailSentAt = moment(new Date(ttl || null));
|
||||||
|
const isWaitPeriodOver = ttl ?
|
||||||
|
lastEmailSentAt.isBefore(fiveMinutesAgo) : true;
|
||||||
|
if (!isWaitPeriodOver) {
|
||||||
|
const minutesLeft = 5 -
|
||||||
|
(moment().minutes() - lastEmailSentAt.minutes());
|
||||||
|
return minutesLeft;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
module.exports = function(User) {
|
module.exports = function(User) {
|
||||||
// NOTE(berks): user email validation currently not needed but build in. This
|
// NOTE(berks): user email validation currently not needed but build in. This
|
||||||
// work around should let us sneak by
|
// work around should let us sneak by
|
||||||
@ -75,6 +87,7 @@ module.exports = function(User) {
|
|||||||
User.findOne$ = Observable.fromNodeCallback(User.findOne, User);
|
User.findOne$ = Observable.fromNodeCallback(User.findOne, User);
|
||||||
User.update$ = Observable.fromNodeCallback(User.updateAll, User);
|
User.update$ = Observable.fromNodeCallback(User.updateAll, User);
|
||||||
User.count$ = Observable.fromNodeCallback(User.count, User);
|
User.count$ = Observable.fromNodeCallback(User.count, User);
|
||||||
|
User.findOrCreate$ = Observable.fromCallback(User.findOrCreate, User);
|
||||||
});
|
});
|
||||||
|
|
||||||
User.beforeRemote('create', function({ req }) {
|
User.beforeRemote('create', function({ req }) {
|
||||||
@ -488,43 +501,40 @@ module.exports = function(User) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
User.requestAuthLink = function requestAuthLink(email, emailTemplate) {
|
User.requestAuthLink = function requestAuthLink(email) {
|
||||||
if (!isEmail(email)) {
|
if (!isEmail(email)) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error('The submitted email not valid.')
|
new Error('The submitted email not valid.')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filter = {
|
var userObj = {
|
||||||
where: { email },
|
username: 'fcc' + uuid.v4().slice(0, 8),
|
||||||
// remove password from the query
|
email: email,
|
||||||
fields: { password: null }
|
emailVerified: false
|
||||||
};
|
};
|
||||||
return User.findOne$(filter)
|
return User.findOrCreate$({ where: { email: userObj.email }}, userObj)
|
||||||
.map(user => {
|
.map(([ err, user, isCreated ]) => {
|
||||||
if (!user) {
|
if (err) {
|
||||||
debug(`no user found with the email ${email}.`);
|
|
||||||
// do not let the user know if an email is not found
|
|
||||||
// this is to avoid sending spam requests to valid users
|
|
||||||
return dedent`
|
return dedent`
|
||||||
If you entered a valid email, a magic link is on its way.
|
Oops, something is not right, please try again later.`;
|
||||||
Please click that link to sign in.`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo : Break this below chunk to a separate function
|
if (!isDev) {
|
||||||
const fiveMinutesAgo = moment().subtract(5, 'minutes');
|
const minutesLeft = getWaitPeriod(user.emailAuthLinkTTL);
|
||||||
const lastEmailSentAt = moment(new Date(user.emailAuthLinkTTL || null));
|
if (minutesLeft) {
|
||||||
const isWaitPeriodOver = user.emailAuthLinkTTL ?
|
|
||||||
lastEmailSentAt.isBefore(fiveMinutesAgo) : true;
|
|
||||||
if (!isWaitPeriodOver) {
|
|
||||||
const minutesLeft = 5 -
|
|
||||||
(moment().minutes() - lastEmailSentAt.minutes());
|
|
||||||
const timeToWait = minutesLeft ?
|
const timeToWait = minutesLeft ?
|
||||||
`${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}` :
|
`${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}` :
|
||||||
'a few seconds';
|
'a few seconds';
|
||||||
debug('request before wait time : ' + timeToWait);
|
debug('request before wait time : ' + timeToWait);
|
||||||
return dedent`
|
return dedent`
|
||||||
Please wait ${timeToWait} to resend email verification.`;
|
Please wait ${timeToWait} to resend an authentication link.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let emailTemplate = 'user-request-sign-in.ejs';
|
||||||
|
if (isCreated) {
|
||||||
|
emailTemplate = 'user-request-sign-up.ejs';
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a temporary access token with ttl for 1 hour
|
// create a temporary access token with ttl for 1 hour
|
||||||
@ -546,15 +556,19 @@ module.exports = function(User) {
|
|||||||
type: 'email',
|
type: 'email',
|
||||||
to: user.email,
|
to: user.email,
|
||||||
from: 'Team@freecodecamp.com',
|
from: 'Team@freecodecamp.com',
|
||||||
subject: 'Free Code Camp - Sign in Request!',
|
subject: 'Free Code Camp - Authentication Request!',
|
||||||
text: renderAuthEmail({
|
text: renderAuthEmail({
|
||||||
loginEmail,
|
loginEmail,
|
||||||
loginToken
|
loginToken
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
if (!isDev) {
|
||||||
this.email.send(mailOptions, err =>{
|
this.email.send(mailOptions, err =>{
|
||||||
if (err) { throw err; }
|
if (err) { throw err; }
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.log('~~~~\n' + mailOptions.text + '~~~~\n');
|
||||||
|
}
|
||||||
user.emailAuthLinkTTL = token.created;
|
user.emailAuthLinkTTL = token.created;
|
||||||
user.save(err =>{ if (err) { throw err; }});
|
user.save(err =>{ if (err) { throw err; }});
|
||||||
});
|
});
|
||||||
@ -583,8 +597,6 @@ module.exports = function(User) {
|
|||||||
description: 'request a link on email with temporary token to sign in',
|
description: 'request a link on email with temporary token to sign in',
|
||||||
accepts: [{
|
accepts: [{
|
||||||
arg: 'email', type: 'string', required: true
|
arg: 'email', type: 'string', required: true
|
||||||
}, {
|
|
||||||
arg: 'emailTemplate', type: 'string', required: true
|
|
||||||
}],
|
}],
|
||||||
returns: [{
|
returns: [{
|
||||||
arg: 'message', type: 'string'
|
arg: 'message', type: 'string'
|
||||||
@ -596,13 +608,7 @@ module.exports = function(User) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
User.prototype.updateEmail = function updateEmail(email) {
|
User.prototype.updateEmail = function updateEmail(email) {
|
||||||
const fiveMinutesAgo = moment().subtract(5, 'minutes');
|
|
||||||
const lastEmailSentAt = moment(new Date(this.emailVerifyTTL || null));
|
|
||||||
const ownEmail = email === this.email;
|
const ownEmail = email === this.email;
|
||||||
const isWaitPeriodOver = this.emailVerifyTTL ?
|
|
||||||
lastEmailSentAt.isBefore(fiveMinutesAgo) :
|
|
||||||
true;
|
|
||||||
|
|
||||||
if (!isEmail('' + email)) {
|
if (!isEmail('' + email)) {
|
||||||
return Observable.throw(createEmailError());
|
return Observable.throw(createEmailError());
|
||||||
}
|
}
|
||||||
@ -613,18 +619,18 @@ module.exports = function(User) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ownEmail && !isWaitPeriodOver) {
|
if (!isDev) {
|
||||||
const minutesLeft = 5 -
|
const minutesLeft = getWaitPeriod(this.emailVerifyTTL);
|
||||||
(moment().minutes() - lastEmailSentAt.minutes());
|
if (ownEmail && minutesLeft) {
|
||||||
|
|
||||||
const timeToWait = minutesLeft ?
|
const timeToWait = minutesLeft ?
|
||||||
`${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}` :
|
`${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}` :
|
||||||
'a few seconds';
|
'a few seconds';
|
||||||
|
debug('request before wait time : ' + timeToWait);
|
||||||
return Observable.throw(new Error(
|
return Observable.throw(new Error(
|
||||||
`Please wait ${timeToWait} to resend email verification.`
|
`Please wait ${timeToWait} to resend email verification.`
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Observable.fromPromise(User.doesExist(null, email))
|
return Observable.fromPromise(User.doesExist(null, email))
|
||||||
.flatMap(exists => {
|
.flatMap(exists => {
|
||||||
|
@ -139,8 +139,16 @@ function buildDisplayChallenges(
|
|||||||
module.exports = function(app) {
|
module.exports = function(app) {
|
||||||
const router = app.loopback.Router();
|
const router = app.loopback.Router();
|
||||||
const api = app.loopback.Router();
|
const api = app.loopback.Router();
|
||||||
|
<<<<<<< HEAD
|
||||||
const { User, Email } = app.models;
|
const { User, Email } = app.models;
|
||||||
const map$ = cachedMap(app.models);
|
const map$ = cachedMap(app.models);
|
||||||
|
=======
|
||||||
|
const User = app.models.User;
|
||||||
|
const AccessToken = app.models.AccessToken;
|
||||||
|
const Block = app.models.Block;
|
||||||
|
const { Email } = app.models;
|
||||||
|
const map$ = cachedMap(Block);
|
||||||
|
>>>>>>> Optimize code and streamline flow.
|
||||||
function findUserByUsername$(username, fields) {
|
function findUserByUsername$(username, fields) {
|
||||||
return observeQuery(
|
return observeQuery(
|
||||||
User,
|
User,
|
||||||
@ -152,6 +160,9 @@ module.exports = function(app) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AccessToken.findOne$ = Observable.fromNodeCallback(
|
||||||
|
AccessToken.findOne, AccessToken);
|
||||||
|
|
||||||
router.get('/login', function(req, res) {
|
router.get('/login', function(req, res) {
|
||||||
res.redirect(301, '/signin');
|
res.redirect(301, '/signin');
|
||||||
});
|
});
|
||||||
@ -169,10 +180,8 @@ module.exports = function(app) {
|
|||||||
router.get('/email-signin', getEmailSignin);
|
router.get('/email-signin', getEmailSignin);
|
||||||
router.get('/deprecated-signin', getDepSignin);
|
router.get('/deprecated-signin', getDepSignin);
|
||||||
router.get('/update-email', getUpdateEmail);
|
router.get('/update-email', getUpdateEmail);
|
||||||
router.get('/passwordless-signin', getPasswordlessSignin);
|
router.get('/passwordless-auth', getPasswordlessAuth);
|
||||||
router.get('/passwordless-signup', getPasswordlessSignup);
|
api.post('/passwordless-auth', postPasswordlessAuth);
|
||||||
api.post('/passwordless-signin', postPasswordlessSignin);
|
|
||||||
api.post('/passwordless-signup', postPasswordlessSignup);
|
|
||||||
router.get(
|
router.get(
|
||||||
'/delete-my-account',
|
'/delete-my-account',
|
||||||
sendNonUserToMap,
|
sendNonUserToMap,
|
||||||
@ -252,117 +261,24 @@ module.exports = function(app) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function postPasswordlessSignup(req, res) {
|
function postPasswordlessAuth(req, res) {
|
||||||
if (req.user) {
|
if (req.user || !(req.body && req.body.email)) {
|
||||||
return res.redirect('/');
|
return res.redirect('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body && req.body.email) {
|
return User.requestAuthLink(req.body.email, 'user-request-sign-in.ejs')
|
||||||
var userObj = {
|
.then(msg => {
|
||||||
username: 'fcc' + uuid.v4().slice(0, 8),
|
return res.status(200).send({ message: msg });
|
||||||
email: req.body.email,
|
|
||||||
emailVerified: false
|
|
||||||
};
|
|
||||||
var data = { or: [
|
|
||||||
{ username: userObj.username },
|
|
||||||
{ email: userObj.email },
|
|
||||||
{ emailVerified: userObj.emailVerified }
|
|
||||||
]};
|
|
||||||
return User.findOrCreate({where: data}, userObj, function(err, user) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
User.requestAuthLink(user.email, 'user-request-sign-up.ejs');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return res.redirect('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function postPasswordlessSignin(req, res) {
|
|
||||||
if (req.user) {
|
|
||||||
return res.redirect('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.body && req.body.email) {
|
|
||||||
var data = { or: [
|
|
||||||
{ email: req.body.email },
|
|
||||||
{ emailVerified: true }
|
|
||||||
]};
|
|
||||||
return User.findOne$({ where: { data }})
|
|
||||||
.map(user => {
|
|
||||||
User.requestAuthLink(user.email, 'user-request-sign-in.ejs');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return res.redirect('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPasswordlessSignup(req, res, next) {
|
|
||||||
if (req.user) {
|
|
||||||
req.flash('info', {
|
|
||||||
msg: 'Hey, looks like you’re already signed in.'
|
|
||||||
});
|
|
||||||
return res.redirect('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultErrorMsg = [
|
|
||||||
'Oops, something is not right, ',
|
|
||||||
'please request a fresh link to sign in.'].join('');
|
|
||||||
|
|
||||||
if (!req.query || !req.query.email || !req.query.token) {
|
|
||||||
req.flash('info', { msg: defaultErrorMsg });
|
|
||||||
return res.redirect('/email-signup');
|
|
||||||
}
|
|
||||||
|
|
||||||
const email = req.query.email;
|
|
||||||
/* const tokenId = req.query.token; */
|
|
||||||
|
|
||||||
return User.findOne$({ where: { email }})
|
|
||||||
.map(user => {
|
|
||||||
return user.createAccessToken(
|
|
||||||
{ ttl: User.settings.ttl }, (err, accessToken) => {
|
|
||||||
if (err) { throw err; }
|
|
||||||
|
|
||||||
var config = {
|
|
||||||
signed: !!req.signedCookies,
|
|
||||||
maxAge: accessToken.ttl
|
|
||||||
};
|
|
||||||
|
|
||||||
if (accessToken && accessToken.id) {
|
|
||||||
debug('setting cookies');
|
|
||||||
res.cookie('access_token', accessToken.id, config);
|
|
||||||
res.cookie('userId', accessToken.userId, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
return req.logIn({
|
|
||||||
id: accessToken.userId.toString() }, err => {
|
|
||||||
if (err) { return next(err); }
|
|
||||||
|
|
||||||
debug('user logged in');
|
|
||||||
|
|
||||||
if (req.session && req.session.returnTo) {
|
|
||||||
var redirectTo = req.session.returnTo;
|
|
||||||
if (redirectTo === '/map-aside') {
|
|
||||||
redirectTo = '/map';
|
|
||||||
}
|
|
||||||
return res.redirect(redirectTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
req.flash('success', { msg:
|
|
||||||
'Success! You have signed in to your account. Happy Coding!'
|
|
||||||
});
|
|
||||||
return res.redirect('/');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.subscribe(
|
.catch(err => {
|
||||||
() => {},
|
debug(err);
|
||||||
next
|
return res.status(200).send({
|
||||||
);
|
message: 'Oops, something is not right, please try again later.'
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPasswordlessSignin(req, res, next) {
|
function getPasswordlessAuth(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
req.flash('info', {
|
req.flash('info', {
|
||||||
msg: 'Hey, looks like you’re already signed in.'
|
msg: 'Hey, looks like you’re already signed in.'
|
||||||
@ -370,9 +286,8 @@ module.exports = function(app) {
|
|||||||
return res.redirect('/');
|
return res.redirect('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultErrorMsg = [
|
const defaultErrorMsg = [ 'Oops, something is not right, please request a ',
|
||||||
'Oops, something is not right, ',
|
'fresh link to sign in / sign up.' ].join('');
|
||||||
'please request a fresh link to sign in.'].join('');
|
|
||||||
|
|
||||||
if (!req.query || !req.query.email || !req.query.token) {
|
if (!req.query || !req.query.email || !req.query.token) {
|
||||||
req.flash('info', { msg: defaultErrorMsg });
|
req.flash('info', { msg: defaultErrorMsg });
|
||||||
@ -380,10 +295,28 @@ module.exports = function(app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const email = req.query.email;
|
const email = req.query.email;
|
||||||
/* const tokenId = req.query.token; */
|
|
||||||
|
|
||||||
return User.findOne$({ where: { email }})
|
return User.findOne$({ where: { email }})
|
||||||
.map(user => {
|
.map(user => {
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
debug(`did not find a valid user with email: ${email}`);
|
||||||
|
req.flash('info', { msg: defaultErrorMsg });
|
||||||
|
return res.redirect('/email-signin');
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailVerified = true;
|
||||||
|
const emailAuthLinkTTL = null;
|
||||||
|
const emailVerifyTTL = null;
|
||||||
|
user.update$({
|
||||||
|
emailVerified, emailAuthLinkTTL, emailVerifyTTL
|
||||||
|
})
|
||||||
|
.do((user) => {
|
||||||
|
user.emailVerified = emailVerified;
|
||||||
|
user.emailAuthLinkTTL = emailAuthLinkTTL;
|
||||||
|
user.emailVerifyTTL = emailVerifyTTL;
|
||||||
|
});
|
||||||
|
|
||||||
return user.createAccessToken(
|
return user.createAccessToken(
|
||||||
{ ttl: User.settings.ttl }, (err, accessToken) => {
|
{ ttl: User.settings.ttl }, (err, accessToken) => {
|
||||||
if (err) { throw err; }
|
if (err) { throw err; }
|
||||||
|
@ -13,7 +13,7 @@ block content
|
|||||||
h2 Sign in with your Email here:
|
h2 Sign in with your Email here:
|
||||||
.button-spacer
|
.button-spacer
|
||||||
.col-sm-6.col-sm-offset-3
|
.col-sm-6.col-sm-offset-3
|
||||||
form(method='POST', action='/passwordless-signin')
|
form(method='POST', action='/passwordless-auth')
|
||||||
input(type='hidden', name='_csrf', value=_csrf)
|
input(type='hidden', name='_csrf', value=_csrf)
|
||||||
.form-group
|
.form-group
|
||||||
input.input-lg.form-control(type='email', name='email', id='email', placeholder='Email', autofocus=true)
|
input.input-lg.form-control(type='email', name='email', id='email', placeholder='Email', autofocus=true)
|
||||||
|
@ -16,7 +16,7 @@ block content
|
|||||||
| Sign up with an Email here:
|
| Sign up with an Email here:
|
||||||
.button-spacer
|
.button-spacer
|
||||||
.col-sm-6.col-sm-offset-3
|
.col-sm-6.col-sm-offset-3
|
||||||
form(method='POST', action='/passwordless-signup')
|
form(method='POST', action='/passwordless-auth')
|
||||||
input(type='hidden', name='_csrf', value=_csrf)
|
input(type='hidden', name='_csrf', value=_csrf)
|
||||||
.form-group
|
.form-group
|
||||||
input.input-lg.form-control(type='email', name='email', id='email', placeholder='Email', autofocus=true)
|
input.input-lg.form-control(type='email', name='email', id='email', placeholder='Email', autofocus=true)
|
||||||
|
@ -2,7 +2,7 @@ Greetings from San Francisco!
|
|||||||
|
|
||||||
You can now sign in to Free Code Camp, without a password. Just follow the link below:
|
You can now sign in to Free Code Camp, without a password. Just follow the link below:
|
||||||
|
|
||||||
https://freecodecamp.com/passwordless-signin?email=<%= loginEmail %>&token=<%= loginToken %>
|
https://freecodecamp.com/passwordless-auth?email=<%= loginEmail %>&token=<%= loginToken %>
|
||||||
|
|
||||||
IMPORTANT NOTE:
|
IMPORTANT NOTE:
|
||||||
You can simply delete or ignore this email, if you did not make any such request.
|
You can simply delete or ignore this email, if you did not make any such request.
|
||||||
|
@ -3,7 +3,7 @@ Greetings from San Francisco!
|
|||||||
Welcome to Free Code Camp. We've created a new account for you.
|
Welcome to Free Code Camp. We've created a new account for you.
|
||||||
To verify and start using your profile just follow the link below:
|
To verify and start using your profile just follow the link below:
|
||||||
|
|
||||||
https://freecodecamp.com/passwordless-signup?email=<%= loginEmail %>&token=<%= loginToken %>
|
https://freecodecamp.com/passwordless-auth?email=<%= loginEmail %>&token=<%= loginToken %>
|
||||||
|
|
||||||
Next steps:
|
Next steps:
|
||||||
1. Visit the settings page and link your account to Github.
|
1. Visit the settings page and link your account to Github.
|
||||||
|
Reference in New Issue
Block a user