GH-3245 Change password from settings page (#3538)

* GH-3245 add passwordResetToken and passwordResetTokenExpiresAt column on user entity

* Add password reset token expiry delay env variable

* Add generatePasswordResetToken mutation resolver

* Update .env.sample file on server

* Add password reset token and expiry migration script

* Add validate password reset token query and a dummy password update (WIP) resolver

* Fix bug in password reset token generate

* add update password mutation

* Update name and add email password reset link

* Add change password UI on settings page

* Add reset password route on frontend

* Add reset password form UI

* sign in user on password reset

* format code

* make PASSWORD_RESET_TOKEN_EXPIRES_IN optional

* add email template for password reset

* Improve error message

* Rename methods and DTO to improve naming

* fix formatting of backend code

* Update change password component

* Update password reset via token component

* update graphql files

* spelling fix

* Make password-reset route authless on frontend

* show token generation wait time

* remove constant from .env.example

* Add PASSWORD_RESET_TOKEN_EXPIRES_IN in docs

* refactor emails module in reset password

* update Graphql generated file

* update email template of password reset

* add space between date and text

* update method name

* fix lint issues

* remove unused code, fix indentation, and email link color

* update test file for auth and token service

* Fix ci: build twenty-emails when running tests

---------

Co-authored-by: martmull <martmull@hotmail.fr>
This commit is contained in:
Deepak Kumar
2024-01-25 14:58:48 +05:30
committed by GitHub
parent 21f342c5ea
commit 46f0eb522f
37 changed files with 1015 additions and 11 deletions

View File

@ -42,7 +42,8 @@ export const useApolloFactory = () => {
!isMatchingLocation(AppPath.Verify) &&
!isMatchingLocation(AppPath.SignIn) &&
!isMatchingLocation(AppPath.SignUp) &&
!isMatchingLocation(AppPath.Invite)
!isMatchingLocation(AppPath.Invite) &&
!isMatchingLocation(AppPath.ResetPassword)
) {
navigate(AppPath.SignIn);
}

View File

@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const EMAIL_PASSWORD_RESET_Link = gql`
mutation EmailPasswordResetLink {
emailPasswordResetLink {
success
}
}
`;

View File

@ -0,0 +1,12 @@
import { gql } from '@apollo/client';
export const UPDATE_PASSWORD_VIA_RESET_TOKEN = gql`
mutation UpdatePasswordViaResetToken($token: String!, $newPassword: String!) {
updatePasswordViaResetToken(
passwordResetToken: $token
newPassword: $newPassword
) {
success
}
}
`;

View File

@ -0,0 +1,10 @@
import { gql } from '@apollo/client';
export const VALIDATE_PASSWORD_RESET_TOKEN = gql`
query validatePasswordResetToken($token: String!) {
validatePasswordResetToken(passwordResetToken: $token) {
id
email
}
}
`;

View File

@ -0,0 +1,45 @@
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Button } from '@/ui/input/button/components/Button';
import { useEmailPasswordResetLinkMutation } from '~/generated/graphql';
import { logError } from '~/utils/logError';
export const ChangePassword = () => {
const { enqueueSnackBar } = useSnackBar();
const [emailPasswordResetLink] = useEmailPasswordResetLinkMutation();
const handlePasswordResetClick = async () => {
try {
const { data } = await emailPasswordResetLink();
if (data?.emailPasswordResetLink?.success) {
enqueueSnackBar('Password reset link has been sent to the email', {
variant: 'success',
});
} else {
enqueueSnackBar('There was some issue', {
variant: 'error',
});
}
} catch (error) {
logError(error);
enqueueSnackBar((error as Error).message, {
variant: 'error',
});
}
};
return (
<>
<H2Title
title="Change Password"
description="Receive an email containing password update link"
/>
<Button
onClick={handlePasswordResetClick}
variant="secondary"
title="Change Password"
/>
</>
);
};

View File

@ -4,6 +4,7 @@ export enum AppPath {
SignIn = '/sign-in',
SignUp = '/sign-up',
Invite = '/invite/:workspaceInviteHash',
ResetPassword = '/reset-password/:passwordResetToken',
// Onboarding
CreateWorkspace = '/create/workspace',