Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/backend/app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def send_test_email(email_to: str) -> None:
email_to=email_to,
subject_template=subject,
html_template=template_str,
current_environment={"project_name": settings.PROJECT_NAME, "email": email_to},
environment={"project_name": settings.PROJECT_NAME, "email": email_to},
)


Expand All @@ -58,7 +58,7 @@ def send_reset_password_email(email_to: str, email: str, token: str) -> None:
email_to=email_to,
subject_template=subject,
html_template=template_str,
current_environment={
environment={
"project_name": settings.PROJECT_NAME,
"username": email,
"email": email_to,
Expand All @@ -78,7 +78,7 @@ def send_new_account_email(email_to: str, username: str, password: str) -> None:
email_to=email_to,
subject_template=subject,
html_template=template_str,
current_environment={
environment={
"project_name": settings.PROJECT_NAME,
"username": username,
"password": password,
Expand All @@ -104,6 +104,6 @@ def generate_password_reset_token(email: str) -> str:
def verify_password_reset_token(token: str) -> str | None:
try:
decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
return decoded_token["email"]
return decoded_token["sub"]
except jwt.JWTError:
return None
26 changes: 10 additions & 16 deletions src/new-frontend/src/pages/RecoverPassword.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";

import { Button, Container, FormControl, Heading, Input, Text } from "@chakra-ui/react";
import { Button, Container, FormControl, FormErrorMessage, Heading, Input, Text } from "@chakra-ui/react";
import { SubmitHandler, useForm } from "react-hook-form";

import { LoginService } from "../client";
Expand All @@ -11,14 +11,13 @@ interface FormData {
}

const RecoverPassword: React.FC = () => {
const { register, handleSubmit } = useForm<FormData>();
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>();
const showToast = useCustomToast();

const onSubmit: SubmitHandler<FormData> = async (data) => {
const response = await LoginService.recoverPassword({
await LoginService.recoverPassword({
email: data.email,
});
console.log(response)

showToast("Email sent.", "We sent an email with a link to get back into your account.", "success");
};
Expand All @@ -37,19 +36,14 @@ const RecoverPassword: React.FC = () => {
<Heading size="xl" color="ui.main" textAlign="center" mb={2}>
Password Recovery
</Heading>
<FormControl id="username">
<Text align="center">
A password recovery email will be sent to the registered account.
</Text>
<Input
{...register("email")}

mt={4}
placeholder="Enter your email"
type="text"
/>
<Text align="center">
A password recovery email will be sent to the registered account.
</Text>
<FormControl isInvalid={!!errors.email}>
<Input id='email' {...register('email', { required: 'Email is required', pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i, message: 'Invalid email address' } })} placeholder='Email' type='email' />
{errors.email && <FormErrorMessage>{errors.email.message}</FormErrorMessage>}
</FormControl>
<Button bg="ui.main" color="white" _hover={{ opacity: 0.8 }} type="submit">
<Button bg="ui.main" color="white" _hover={{ opacity: 0.8 }} type="submit" isLoading={isSubmitting}>
Continue
</Button>
</Container>
Expand Down
72 changes: 72 additions & 0 deletions src/new-frontend/src/pages/ResetPassword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from "react";

import { Button, Container, FormControl, FormErrorMessage, FormLabel, Heading, Input, Text } from "@chakra-ui/react";
import { SubmitHandler, useForm } from "react-hook-form";

import { LoginService, NewPassword } from "../client";
import useCustomToast from "../hooks/useCustomToast";

interface NewPasswordForm extends NewPassword {
confirm_password: string;
}

const ResetPassword: React.FC = () => {
const { register, handleSubmit, getValues, formState: { errors } } = useForm<NewPasswordForm>({
mode: 'onBlur',
criteriaMode: 'all',
defaultValues: {
new_password: '',
}
});
const showToast = useCustomToast();

const onSubmit: SubmitHandler<NewPasswordForm> = async (data) => {
try {
const token = new URLSearchParams(window.location.search).get('token');
await LoginService.resetPassword({
requestBody: { new_password: data.new_password, token: token! }
});
showToast("Password reset.", "Your password has been reset successfully.", "success");
} catch (error) {
showToast("Error", "An error occurred while resetting your password.", "error");
}
};

return (
<Container
as="form"
onSubmit={handleSubmit(onSubmit)}
h="100vh"
maxW="sm"
alignItems="stretch"
justifyContent="center"
gap={4}
centerContent
>
<Heading size="xl" color="ui.main" textAlign="center" mb={2}>
Reset Password
</Heading>
<Text textAlign="center">
Please enter your new password and confirm it to reset your password.
</Text>
<FormControl mt={4} isInvalid={!!errors.new_password}>
<FormLabel htmlFor='password'>Set Password</FormLabel>
<Input id='password' {...register('new_password', { required: 'Password is required', minLength: { value: 8, message: 'Password must be at least 8 characters' } })} placeholder='Password' type='password' />
{errors.new_password && <FormErrorMessage>{errors.new_password.message}</FormErrorMessage>}
</FormControl>
<FormControl mt={4} isInvalid={!!errors.confirm_password}>
<FormLabel htmlFor='confirm_password'>Confirm Password</FormLabel>
<Input id='confirm_password' {...register('confirm_password', {
required: 'Please confirm your password',
validate: value => value === getValues().new_password || 'The passwords do not match'
})} placeholder='Password' type='password' />
{errors.confirm_password && <FormErrorMessage>{errors.confirm_password.message}</FormErrorMessage>}
</FormControl>
<Button bg="ui.main" color="white" _hover={{ opacity: 0.8 }} type="submit">
Reset Password
</Button>
</Container>
);
};

export default ResetPassword;
2 changes: 2 additions & 0 deletions src/new-frontend/src/routes/public_route.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import ErrorPage from '../pages/ErrorPage';
import Login from '../pages/Login';
import RecoverPassword from '../pages/RecoverPassword';
import ResetPassword from '../pages/ResetPassword';

export default function publicRoutes() {
return [
{ path: '/login', element: <Login />, errorElement: <ErrorPage /> },
{ path: 'recover-password', element: <RecoverPassword />, errorElement: <ErrorPage /> },
{ path: 'reset-password', element: <ResetPassword />, errorElement: <ErrorPage /> },
// TODO: complete this
// { path: '*', element: <Navigate to='/login' replace /> }
];
Expand Down