From a77dcaa6c29de2942117b7aec8fd893cda95ae1b Mon Sep 17 00:00:00 2001 From: James Date: Tue, 11 Aug 2020 15:51:36 -0400 Subject: [PATCH] implements proper reset password workflow within admin panel and sets cookie properly --- src/auth/getExtractJWT.js | 2 +- src/auth/graphql/resolvers/resetPassword.js | 1 + src/auth/operations/login.js | 3 +- src/auth/operations/logout.js | 12 +------ src/auth/operations/resetPassword.js | 26 ++++++++++++--- src/auth/requestHandlers/resetPassword.js | 3 +- .../field-types/ConfirmPassword/index.js | 1 + .../forms/field-types/HiddenInput/index.js | 15 ++++++--- .../components/views/ForgotPassword/index.js | 8 ----- .../components/views/ResetPassword/index.js | 33 +++++++++---------- src/collections/init.js | 2 +- 11 files changed, 56 insertions(+), 50 deletions(-) diff --git a/src/auth/getExtractJWT.js b/src/auth/getExtractJWT.js index 6c1a006c3f..909c7387f0 100644 --- a/src/auth/getExtractJWT.js +++ b/src/auth/getExtractJWT.js @@ -1,6 +1,6 @@ const parseCookies = require('../utilities/parseCookies'); -const getExtractJWT = config => (req) => { +const getExtractJWT = (config) => (req) => { const jwtFromHeader = req.get('Authorization'); if (jwtFromHeader && jwtFromHeader.indexOf('JWT ') === 0) { diff --git a/src/auth/graphql/resolvers/resetPassword.js b/src/auth/graphql/resolvers/resetPassword.js index d57a2e8b26..c3f266aadf 100644 --- a/src/auth/graphql/resolvers/resetPassword.js +++ b/src/auth/graphql/resolvers/resetPassword.js @@ -8,6 +8,7 @@ function resetPassword(collection) { collection, data: args, req: context.req, + res: context.res, api: 'GraphQL', }; diff --git a/src/auth/operations/login.js b/src/auth/operations/login.js index 9f9c397976..5eb9647d1a 100644 --- a/src/auth/operations/login.js +++ b/src/auth/operations/login.js @@ -65,10 +65,9 @@ async function login(args) { }, { email, id: user.id, + collection: collectionConfig.slug, }); - fieldsToSign.collection = collectionConfig.slug; - const token = jwt.sign( fieldsToSign, config.secret, diff --git a/src/auth/operations/logout.js b/src/auth/operations/logout.js index 6a8bc0d741..ba6f831ed6 100644 --- a/src/auth/operations/logout.js +++ b/src/auth/operations/logout.js @@ -2,31 +2,21 @@ async function logout(args) { const { config } = this; const { - collection: { - config: collectionConfig, - }, res, req, } = args; const cookieOptions = { - expires: new Date(0), - httpOnly: true, path: '/', - overwrite: true, }; - if (collectionConfig.auth && collectionConfig.auth.secureCookie) { - cookieOptions.secure = true; - } - if (req.headers.origin && req.headers.origin.indexOf('localhost') === -1) { let domain = req.headers.origin.replace('https://', ''); domain = domain.replace('http://', ''); cookieOptions.domain = domain; } - res.cookie(`${config.cookiePrefix}-token`, '', cookieOptions); + res.clearCookie(`${config.cookiePrefix}-token`, cookieOptions); return 'Logged out successfully.'; } diff --git a/src/auth/operations/resetPassword.js b/src/auth/operations/resetPassword.js index 1914c071ef..e3d9cbeab0 100644 --- a/src/auth/operations/resetPassword.js +++ b/src/auth/operations/resetPassword.js @@ -33,8 +33,6 @@ async function resetPassword(args) { data, } = options; - const { email } = data; - const user = await Model.findOne({ resetPasswordToken: data.token, resetPasswordExpiration: { $gt: Date.now() }, @@ -42,7 +40,6 @@ async function resetPassword(args) { if (!user) throw new APIError('Token is either invalid or has expired.'); - await user.setPassword(data.password); user.resetPasswordExpiration = Date.now(); @@ -60,7 +57,9 @@ async function resetPassword(args) { } return signedFields; }, { - email, + email: user.email, + id: user.id, + collection: collectionConfig.slug, }); const token = jwt.sign( @@ -71,6 +70,25 @@ async function resetPassword(args) { }, ); + if (args.res) { + const cookieOptions = { + path: '/', + httpOnly: true, + }; + + if (collectionConfig.auth.secureCookie) { + cookieOptions.secure = true; + } + + if (args.req.headers.origin && args.req.headers.origin.indexOf('localhost') === -1) { + let domain = args.req.headers.origin.replace('https://', ''); + domain = domain.replace('http://', ''); + cookieOptions.domain = domain; + } + + args.res.cookie(`${config.cookiePrefix}-token`, token, cookieOptions); + } + // ///////////////////////////////////// // 3. Execute after reset password hook // ///////////////////////////////////// diff --git a/src/auth/requestHandlers/resetPassword.js b/src/auth/requestHandlers/resetPassword.js index a35f24df3c..769a01769b 100644 --- a/src/auth/requestHandlers/resetPassword.js +++ b/src/auth/requestHandlers/resetPassword.js @@ -4,13 +4,14 @@ async function resetPassword(req, res, next) { try { const token = await this.operations.collections.auth.resetPassword({ req, + res, collection: req.collection, data: req.body, }); return res.status(httpStatus.OK) .json({ - message: 'Password reset', + message: 'Password reset successfully.', token, }); } catch (error) { diff --git a/src/client/components/forms/field-types/ConfirmPassword/index.js b/src/client/components/forms/field-types/ConfirmPassword/index.js index c02b45703d..2b0f45aff6 100644 --- a/src/client/components/forms/field-types/ConfirmPassword/index.js +++ b/src/client/components/forms/field-types/ConfirmPassword/index.js @@ -14,6 +14,7 @@ const ConfirmPassword = () => { if (value === password?.value) { return true; } + return 'Passwords do not match.'; }, [password]); diff --git a/src/client/components/forms/field-types/HiddenInput/index.js b/src/client/components/forms/field-types/HiddenInput/index.js index 3eed61c58d..853dce9195 100644 --- a/src/client/components/forms/field-types/HiddenInput/index.js +++ b/src/client/components/forms/field-types/HiddenInput/index.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import useFieldType from '../../useFieldType'; import withCondition from '../../withCondition'; @@ -7,16 +7,21 @@ const HiddenInput = (props) => { const { name, path: pathFromProps, - required, + value: valueFromProps, } = props; const path = pathFromProps || name; const { value, setValue } = useFieldType({ path, - required, }); + useEffect(() => { + if (valueFromProps !== undefined) { + setValue(valueFromProps); + } + }, [valueFromProps, setValue]); + return ( { }; HiddenInput.defaultProps = { - required: false, path: '', + value: undefined, }; HiddenInput.propTypes = { name: PropTypes.string.isRequired, path: PropTypes.string, - required: PropTypes.bool, + value: PropTypes.string, }; export default withCondition(HiddenInput); diff --git a/src/client/components/views/ForgotPassword/index.js b/src/client/components/views/ForgotPassword/index.js index 589b4e4757..d9da003ed0 100644 --- a/src/client/components/views/ForgotPassword/index.js +++ b/src/client/components/views/ForgotPassword/index.js @@ -69,14 +69,6 @@ const ForgotPassword = () => {

Check your email for a link that will allow you to securely reset your password.

-
- ); } diff --git a/src/client/components/views/ResetPassword/index.js b/src/client/components/views/ResetPassword/index.js index eff81ad1c0..e0eb5a48b1 100644 --- a/src/client/components/views/ResetPassword/index.js +++ b/src/client/components/views/ResetPassword/index.js @@ -1,9 +1,10 @@ import React from 'react'; import { Link, useHistory, useParams } from 'react-router-dom'; import config from 'payload/config'; -import StatusList from '../../elements/Status'; +import MinimalTemplate from '../../templates/Minimal'; import Form from '../../forms/Form'; import Password from '../../forms/field-types/Password'; +import ConfirmPassword from '../../forms/field-types/ConfirmPassword'; import FormSubmit from '../../forms/Submit'; import Button from '../../elements/Button'; import { useUser } from '../../data/User'; @@ -20,19 +21,16 @@ const ResetPassword = () => { const history = useHistory(); const { user, setToken } = useUser(); - const handleResponse = (res) => { - res.json() - .then((data) => { - if (data.token) { - setToken(data.token); - history.push(`${admin}`); - } - }); + const onSuccess = (data) => { + if (data.token) { + setToken(data.token); + history.push(`${admin}`); + } }; if (user) { return ( -
+

Already logged in

@@ -51,34 +49,35 @@ const ResetPassword = () => { Back to Dashboard

-
+ ); } return ( -
+
- +

Reset Password

+ Reset Password
-
+ ); }; diff --git a/src/collections/init.js b/src/collections/init.js index 7966e9f9fe..04dbc2ab8d 100644 --- a/src/collections/init.js +++ b/src/collections/init.js @@ -93,7 +93,7 @@ function registerCollections() { .post(forgotPassword); router - .route(`${slug}/reset-password`) + .route(`/${slug}/reset-password`) .post(resetPassword); router