Link to chapter - http://serverless-stack.com/chapters/handle-forgot-and-reset-password.html
I just went through this chapter and in testing it with google chrome, everytime I reset my password chrome saves the confirmation code as a username in my saved passwords. Looking at the code, I have not yet determined how to prevent this. Has anyone else encountered this?
Iām guessing the password manager thinks that the confirmation code field is a password field? There should be a way to prevent this. If you find something please report back and weāll add it to the guide.
Is there a way to have the user click on a verify link from the email sent to them instead of having to enter a confirmation code and then change their password?
That would also be easier during the signup up process, the user just click a verify link and is logged in automatically.
AFAIK, Cognito does not support this. I havenāt check recently. But if somebody else has looked into it, hopefully they can chime in.
Is there a way to make this work with the new code? Most of this code is 2 years old and doesnāt match the updated tutorial and Iād love to be able to use this same code but Iām hitting some issues in the UnauthenticatedRoute.js file and Iām not sure why.
When I followed the code at least from the chapter listed Iām getting this when I try to go to the /login/reset
path and Iām not sure why the isAuthenticated part isnāt being set in the appProps for some reason.
i change legacy code(class component to function component(hooks)). i PR to github repo.
change code works fine and match the updated tutorial.
// in Routes.js
<UnauthenticatedRoute
path="/login/reset"
exact
component={ResetPassword}
appProps={appProps}
/>
<AuthenticatedRoute
path="/settings/password"
exact
component={ChangePassword}
appProps={appProps}
/>
<AuthenticatedRoute
path="/settings/email"
exact
component={ChangeEmail}
appProps={appProps}
/>
// in ChangePassword.js
import React, { useState } from "react";
import { Auth } from "aws-amplify";
import { FormGroup, FormControl, ControlLabel } from "react-bootstrap";
import LoaderButton from "../components/LoaderButton";
import "./ChangePassword.css";
import { useFormFields } from "../libs/hooksLib";
export default function ChangePassword(props) {
const [fields, setFields] = useFormFields({
password: "",
oldPassword: "",
confirmPassword: ""
});
const [isChanging, setIsChanging] = useState(false);
function validateForm() {
return (
fields.oldPassword.length > 0 &&
fields.password.length > 0 &&
fields.password === fields.confirmPassword
);
}
async function handleChangeClick(event) {
event.preventDefault();
setIsChanging(true);
try {
const currentUser = await Auth.currentAuthenticatedUser();
await Auth.changePassword(
currentUser,
fields.oldPassword,
fields.password
);
props.history.push("/settings");
} catch (error) {
alert(error.message);
setIsChanging(false);
}
}
return (
<div className="ChangePassword">
<form onSubmit={handleChangeClick}>
<FormGroup bsSize="large" controlId="oldPassword">
<ControlLabel>Old Password</ControlLabel>
<FormControl
type="password"
onChange={setFields}
value={fields.oldPassword}
/>
</FormGroup>
<hr />
<FormGroup bsSize="large" controlId="password">
<ControlLabel>New Password</ControlLabel>
<FormControl
type="password"
onChange={setFields}
value={fields.password}
/>
</FormGroup>
<FormGroup bsSize="large" controlId="confirmPassword">
<ControlLabel>Confirm Password</ControlLabel>
<FormControl
type="password"
onChange={setFields}
value={fields.confirmPassword}
/>
</FormGroup>
<LoaderButton
block
type="submit"
bsSize="large"
disabled={!validateForm()}
isLoading={isChanging}
>
Change Password
</LoaderButton>
</form>
</div>
);
}
// in ChangeEmail.js
import React, { useState } from "react";
import { Auth } from "aws-amplify";
import {
HelpBlock,
FormGroup,
FormControl,
ControlLabel
} from "react-bootstrap";
import LoaderButton from "../components/LoaderButton";
import "./ChangeEmail.css";
import { useFormFields } from "../libs/hooksLib";
export default function ChangeEmail(props) {
const [fields, setFields] = useFormFields({
code: "",
email: ""
});
const [codeSent, setCodeSent] = useState(false);
const [isConfirming, setIsConfirming] = useState(false);
const [isSendingCode, setIsSendingCode] = useState(false);
function validateEmailForm() {
return fields.email.length > 0;
}
function validateConfirmForm() {
return fields.code.length > 0;
}
async function handleUpdateClick(event) {
event.preventDefault();
setIsSendingCode(true);
try {
const user = await Auth.currentAuthenticatedUser();
await Auth.updateUserAttributes(user, { email: fields.email });
setCodeSent(true);
} catch (error) {
alert(error.message);
setIsSendingCode(false);
}
}
async function handleConfirmClick(event) {
event.preventDefault();
setIsConfirming(true);
try {
await Auth.verifyCurrentUserAttributeSubmit("email", fields.code);
props.history.push("/settings");
} catch (error) {
alert(error.message);
setIsConfirming(false);
}
}
function renderUpdateForm() {
return (
<form onSubmit={handleUpdateClick}>
<FormGroup bsSize="large" controlId="email">
<ControlLabel>Email</ControlLabel>
<FormControl
autoFocus
type="email"
value={fields.email}
onChange={setFields}
/>
</FormGroup>
<LoaderButton
block
type="submit"
bsSize="large"
isLoading={isSendingCode}
disabled={!validateEmailForm()}
>
Update Email
</LoaderButton>
</form>
);
}
function renderConfirmationForm() {
return (
<form onSubmit={handleConfirmClick}>
<FormGroup bsSize="large" controlId="code">
<ControlLabel>Confirmation Code</ControlLabel>
<FormControl
autoFocus
type="tel"
value={fields.code}
onChange={setFields}
/>
<HelpBlock>
Please check your email ({fields.email}) for the confirmation code.
</HelpBlock>
</FormGroup>
<LoaderButton
block
type="submit"
bsSize="large"
isLoading={isConfirming}
disabled={!validateConfirmForm()}
>
Confirm
</LoaderButton>
</form>
);
}
return (
<div className="ChangeEmail">
{!codeSent ? renderUpdateForm() : renderConfirmationForm()}
</div>
);
}
// in ResetPassword.js
import React, { useState } from "react";
import { Auth } from "aws-amplify";
import { Link } from "react-router-dom";
import {
HelpBlock,
FormGroup,
Glyphicon,
FormControl,
ControlLabel
} from "react-bootstrap";
import LoaderButton from "../components/LoaderButton";
import "./ResetPassword.css";
import { useFormFields } from "../libs/hooksLib";
export default function ResetPassword() {
const [fields, setFields] = useFormFields({
code: "",
email: "",
password: "",
confirmPassword: ""
});
const [codeSent, setCodeSent] = useState(false);
const [confirmed, setConfirmed] = useState(false);
const [isConfirming, setIsConfirming] = useState(false);
const [isSendingCode, setIsSendingCode] = useState(false);
function validateCodeForm() {
return fields.email.length > 0;
}
function validateResetForm() {
return (
fields.code.length > 0 &&
fields.password.length > 0 &&
fields.password === fields.confirmPassword
);
}
async function handleSendCodeClick(event) {
event.preventDefault();
setIsSendingCode(true);
try {
await Auth.forgotPassword(fields.email);
setCodeSent(true);
} catch (error) {
alert(error.message);
setIsSendingCode(false);
}
}
async function handleConfirmClick(event) {
event.preventDefault();
setIsConfirming(true);
try {
await Auth.forgotPasswordSubmit(
fields.email,
fields.code,
fields.password
);
setConfirmed(true);
} catch (error) {
alert(error.message);
setIsConfirming(false);
}
}
function renderRequestCodeForm() {
return (
<form onSubmit={handleSendCodeClick}>
<FormGroup bsSize="large" controlId="email">
<ControlLabel>Email</ControlLabel>
<FormControl
autoFocus
type="email"
value={fields.email}
onChange={setFields}
/>
</FormGroup>
<LoaderButton
block
type="submit"
bsSize="large"
isLoading={isSendingCode}
disabled={!validateCodeForm()}
>
Send Confirmation
</LoaderButton>
</form>
);
}
function renderConfirmationForm() {
return (
<form onSubmit={handleConfirmClick}>
<FormGroup bsSize="large" controlId="code">
<ControlLabel>Confirmation Code</ControlLabel>
<FormControl
autoFocus
type="tel"
value={fields.code}
onChange={setFields}
/>
<HelpBlock>
Please check your email ({fields.email}) for the confirmation code.
</HelpBlock>
</FormGroup>
<hr />
<FormGroup bsSize="large" controlId="password">
<ControlLabel>New Password</ControlLabel>
<FormControl
type="password"
value={fields.password}
onChange={setFields}
/>
</FormGroup>
<FormGroup bsSize="large" controlId="confirmPassword">
<ControlLabel>Confirm Password</ControlLabel>
<FormControl
type="password"
value={fields.confirmPassword}
onChange={setFields}
/>
</FormGroup>
<LoaderButton
block
type="submit"
bsSize="large"
isLoading={isConfirming}
disabled={!validateResetForm()}
>
Confirm
</LoaderButton>
</form>
);
}
function renderSuccessMessage() {
return (
<div className="success">
<Glyphicon glyph="ok" />
<p>Your password has been reset.</p>
<p>
<Link to="/login">
Click here to login with your new credentials.
</Link>
</p>
</div>
);
}
return (
<div className="ResetPassword">
{!codeSent
? renderRequestCodeForm()
: !confirmed
? renderConfirmationForm()
: renderSuccessMessage()}
</div>
);
}
Thanks @jeongwoo!
I need to spend some time looking at your PR. Now that we pushed out this update, Iāll have some time to do that.
i applied recently serverless-stack update version too. youāre welcome! Thanks more!
very good tutorial, but what if user refreshes the page before submitting the new password? he would need to re-submit his mail for code. Iām wondering if thereās a cleaner way
Yeah we didnāt go into these details in the chapter. But you could hang on to the email address in the URL. So if the page is refreshed, you could load the email from the URL.
For those of you that are stuck on this step, I had to make a few changes to the Boostrap sections of this project. Mainly
Glyphicon was not importing, so changed to <span glyph="ok" />
and
Form.Helpbox was dying so changed to Form.Text.
Hope this helps someone
import { Auth } from "aws-amplify";
import { Link } from "react-router-dom";
import Form from "react-bootstrap/Form";
import LoaderButton from "../components/LoaderButton";
import { useFormFields } from "../libs/hooksLib";
import { onError } from "../libs/errorLib";
import "./ResetPassword.css";
import { Alert } from "react-bootstrap";
export default function ResetPassword() {
const [fields, handleFieldChange] = useFormFields({
code: "",
email: "",
password: "",
confirmPassword: "",
});
const [codeSent, setCodeSent] = useState(false);
const [confirmed, setConfirmed] = useState(false);
const [isConfirming, setIsConfirming] = useState(false);
const [isSendingCode, setIsSendingCode] = useState(false);
function validateCodeForm() {
return fields.email.length > 0;
}
function validateResetForm() {
return (
fields.code.length > 0 &&
fields.password.length > 0 &&
fields.password === fields.confirmPassword
);
}
async function handleSendCodeClick(event) {
event.preventDefault();
setIsSendingCode(true);
try {
await Auth.forgotPassword(fields.email.toString());
setCodeSent(true);
} catch (error) {
onError(error);
}
setIsSendingCode(false);
}
async function handleConfirmClick(event) {
event.preventDefault();
setIsConfirming(true);
try {
await Auth.forgotPasswordSubmit(
fields.email,
fields.code,
fields.password
);
setConfirmed(true);
} catch (error) {
onError(error);
setIsConfirming(false);
}
}
function renderRequestCodeForm() {
return (
<Form onSubmit={handleSendCodeClick}>
<Form.Group size="lg" controlId="email">
<Form.Label>Email</Form.Label>
<Form.Control
autoFocus
type="email"
value={fields.email}
onChange={handleFieldChange}
/>
</Form.Group>
<LoaderButton
block
type="submit"
size="lg"
isLoading={isSendingCode}
disabled={!validateCodeForm()}
>
Send Confirmation
</LoaderButton>
</Form>
);
}
function renderConfirmationForm() {
console.log("test");
return (
<Form onSubmit={handleConfirmClick}>
<Form.Group size="lg" controlId="code">
<Form.Label>Confirmation Code</Form.Label>
<Form.Control
autoFocus
type="tel"
value={fields.code}
onChange={handleFieldChange}
/>
<Form.Text>
Please check your email ({fields.email}) for the confirmation code.
</Form.Text>
</Form.Group>
<hr />
<Form.Group size="lg" controlId="password">
<Form.Label>New Password</Form.Label>
<Form.Control
type="password"
value={fields.password}
onChange={handleFieldChange}
/>
</Form.Group>
<Form.Group size="lg" controlId="confirmPassword">
<Form.Label>Confirm Password</Form.Label>
<Form.Control
type="password"
value={fields.confirmPassword}
onChange={handleFieldChange}
/>
</Form.Group>
<LoaderButton
block
type="submit"
size="lg"
isLoading={isConfirming}
disabled={!validateResetForm()}
>
Confirm
</LoaderButton>
</Form>
);
}
function renderSuccessMessage() {
return (
<div className="success">
<span glyph="ok" />
<p>Your password has been reset.</p>
<p>
<Link to="/login">
Click here to login with your new credentials.
</Link>
</p>
</div>
);
}
return (
<div className="ResetPassword">
{!codeSent
? renderRequestCodeForm()
: !confirmed
? renderConfirmationForm()
: renderSuccessMessage()}
</div>
);
}