Fix: Restore email change functionality

This commit is contained in:
Bouncey
2019-09-09 22:43:51 +01:00
committed by mrugesh
parent 41c9ff4cb4
commit 7271e82503
7 changed files with 392 additions and 3 deletions

View File

@ -1,4 +1,7 @@
import passport from 'passport';
import dedent from 'dedent';
import { check } from 'express-validator/check';
import { isEmail } from 'validator';
import { homeLocation } from '../../../config/env';
import {
@ -6,20 +9,34 @@ import {
saveResponseAuthCookies,
loginRedirect
} from '../component-passport';
import { ifUserRedirectTo } from '../utils/middleware';
import { ifUserRedirectTo, ifNoUserRedirectTo } from '../utils/middleware';
import { wrapHandledError } from '../utils/create-handled-error.js';
import { removeCookies } from '../utils/getSetAccessToken';
import { decodeEmail } from '../../common/utils';
const isSignUpDisabled = !!process.env.DISABLE_SIGNUP;
if (isSignUpDisabled) {
console.log('fcc:boot:auth - Sign up is disabled');
}
const passwordlessGetValidators = [
check('email')
.isBase64()
.withMessage('Email should be a base64 encoded string.'),
check('token')
.exists()
.withMessage('Token should exist.')
// based on strongloop/loopback/common/models/access-token.js#L15
.isLength({ min: 64, max: 64 })
.withMessage('Token is not the right length.')
];
module.exports = function enableAuthentication(app) {
// enable loopback access control authentication. see:
// loopback.io/doc/en/lb2/Authentication-authorization-and-permissions.html
app.enableAuth();
const ifUserRedirect = ifUserRedirectTo();
const ifNoUserRedirectHome = ifNoUserRedirectTo(homeLocation);
const saveAuthCookies = saveResponseAuthCookies();
const loginSuccessRedirect = loginRedirect();
const api = app.loopback.Router();
@ -62,5 +79,110 @@ module.exports = function enableAuthentication(app) {
});
});
api.get(
'/confirm-email',
ifNoUserRedirectHome,
passwordlessGetValidators,
createGetPasswordlessAuth(app)
);
app.use(api);
};
const defaultErrorMsg = dedent`
Oops, something is not right,
please request a fresh link to sign in / sign up.
`;
function createGetPasswordlessAuth(app) {
const {
models: { AuthToken, User }
} = app;
return function getPasswordlessAuth(req, res, next) {
const {
query: { email: encodedEmail, token: authTokenId, emailChange } = {}
} = req;
const email = decodeEmail(encodedEmail);
if (!isEmail(email)) {
return next(
wrapHandledError(new TypeError('decoded email is invalid'), {
type: 'info',
message: 'The email encoded in the link is incorrectly formatted',
redirectTo: `${homeLocation}/signin`
})
);
}
// first find
return (
AuthToken.findOne$({ where: { id: authTokenId } })
.flatMap(authToken => {
if (!authToken) {
throw wrapHandledError(
new Error(`no token found for id: ${authTokenId}`),
{
type: 'info',
message: defaultErrorMsg,
redirectTo: `${homeLocation}/signin`
}
);
}
// find user then validate and destroy email validation token
// finally retun user instance
return User.findOne$({ where: { id: authToken.userId } }).flatMap(
user => {
if (!user) {
throw wrapHandledError(
new Error(`no user found for token: ${authTokenId}`),
{
type: 'info',
message: defaultErrorMsg,
redirectTo: `${homeLocation}/signin`
}
);
}
if (user.email !== email) {
if (!emailChange || (emailChange && user.newEmail !== email)) {
throw wrapHandledError(
new Error('user email does not match'),
{
type: 'info',
message: defaultErrorMsg,
redirectTo: `${homeLocation}/signin`
}
);
}
}
return authToken
.validate$()
.map(isValid => {
if (!isValid) {
throw wrapHandledError(new Error('token is invalid'), {
type: 'info',
message: `
Looks like the link you clicked has expired,
please request a fresh link, to sign in.
`,
redirectTo: `${homeLocation}/signin`
});
}
return authToken.destroy$();
})
.map(() => user);
}
);
})
// at this point token has been validated and destroyed
// update user and log them in
.map(user => user.loginByRequest(req, res))
.do(() => {
req.flash(
'success',
'Success! You have signed in to your account. Happy Coding!'
);
return res.redirectWithFlash(`${homeLocation}`);
})
.subscribe(() => {}, next)
);
};
}