implements proper reset password workflow within admin panel and sets cookie properly

This commit is contained in:
James
2020-08-11 15:51:36 -04:00
parent dfd4f75c98
commit a77dcaa6c2
11 changed files with 56 additions and 50 deletions

View File

@@ -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) {

View File

@@ -8,6 +8,7 @@ function resetPassword(collection) {
collection,
data: args,
req: context.req,
res: context.res,
api: 'GraphQL',
};

View File

@@ -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,

View File

@@ -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.';
}

View File

@@ -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
// /////////////////////////////////////

View File

@@ -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) {

View File

@@ -14,6 +14,7 @@ const ConfirmPassword = () => {
if (value === password?.value) {
return true;
}
return 'Passwords do not match.';
}, [password]);

View File

@@ -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 (
<input
type="hidden"
@@ -28,14 +33,14 @@ const HiddenInput = (props) => {
};
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);

View File

@@ -69,14 +69,6 @@ const ForgotPassword = () => {
<p>
Check your email for a link that will allow you to securely reset your password.
</p>
<br />
<Button
el="link"
buttonStyle="secondary"
to={`${admin}/login`}
>
Go to login
</Button>
</MinimalTemplate>
);
}

View File

@@ -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 (
<div className={baseClass}>
<MinimalTemplate className={baseClass}>
<div className={`${baseClass}__wrap`}>
<h1>Already logged in</h1>
<p>
@@ -51,34 +49,35 @@ const ResetPassword = () => {
Back to Dashboard
</Button>
</div>
</div>
</MinimalTemplate>
);
}
return (
<div className={baseClass}>
<MinimalTemplate className={baseClass}>
<div className={`${baseClass}__wrap`}>
<StatusList />
<h1>Reset Password</h1>
<Form
handleResponse={handleResponse}
onSuccess={onSuccess}
method="POST"
action={`${serverURL}${api}/${userSlug}/reset-password`}
redirect={admin}
>
<Password
error="password"
label="Password"
label="New Password"
name="password"
required
/>
<ConfirmPassword />
<HiddenInput
name="token"
defaultValue={token}
value={token}
/>
<FormSubmit>Reset Password</FormSubmit>
</Form>
</div>
</div>
</MinimalTemplate>
);
};

View File

@@ -93,7 +93,7 @@ function registerCollections() {
.post(forgotPassword);
router
.route(`${slug}/reset-password`)
.route(`/${slug}/reset-password`)
.post(resetPassword);
router