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

@ -97,6 +97,12 @@ export type CursorPaging = {
last?: InputMaybe<Scalars['Int']>;
};
export type EmailPasswordResetLink = {
__typename?: 'EmailPasswordResetLink';
/** Boolean that confirms query was dispatched */
success: Scalars['Boolean'];
};
export type FeatureFlag = {
__typename?: 'FeatureFlag';
id: Scalars['ID'];
@ -199,6 +205,12 @@ export type IdFilterComparison = {
notLike?: InputMaybe<Scalars['ID']>;
};
export type InvalidatePassword = {
__typename?: 'InvalidatePassword';
/** Boolean that confirms query was dispatched */
success: Scalars['Boolean'];
};
export type LoginToken = {
__typename?: 'LoginToken';
loginToken: AuthToken;
@ -213,12 +225,14 @@ export type Mutation = {
deleteCurrentWorkspace: Workspace;
deleteOneObject: ObjectDeleteResponse;
deleteUser: User;
emailPasswordResetLink: EmailPasswordResetLink;
generateApiKeyToken: ApiKeyToken;
generateTransientToken: TransientToken;
impersonate: Verify;
renewToken: AuthTokens;
signUp: LoginToken;
updateOneObject: Object;
updatePasswordViaResetToken: InvalidatePassword;
updateWorkspace: Workspace;
uploadFile: Scalars['String'];
uploadImage: Scalars['String'];
@ -268,6 +282,12 @@ export type MutationSignUpArgs = {
};
export type MutationUpdatePasswordViaResetTokenArgs = {
newPassword: Scalars['String'];
passwordResetToken: Scalars['String'];
};
export type MutationUpdateWorkspaceArgs = {
data: UpdateWorkspaceInput;
};
@ -362,6 +382,7 @@ export type Query = {
getTimelineThreadsFromPersonId: Array<TimelineThread>;
object: Object;
objects: ObjectConnection;
validatePasswordResetToken: ValidatePasswordResetToken;
};
@ -389,6 +410,11 @@ export type QueryGetTimelineThreadsFromPersonIdArgs = {
personId: Scalars['String'];
};
export type QueryValidatePasswordResetTokenArgs = {
passwordResetToken: Scalars['String'];
};
export type RefreshToken = {
__typename?: 'RefreshToken';
createdAt: Scalars['DateTime'];
@ -500,6 +526,8 @@ export type User = {
id: Scalars['ID'];
lastName: Scalars['String'];
passwordHash?: Maybe<Scalars['String']>;
passwordResetToken?: Maybe<Scalars['String']>;
passwordResetTokenExpiresAt?: Maybe<Scalars['DateTime']>;
supportUserHash?: Maybe<Scalars['String']>;
updatedAt: Scalars['DateTime'];
workspaceMember: WorkspaceMember;
@ -518,6 +546,12 @@ export type UserExists = {
exists: Scalars['Boolean'];
};
export type ValidatePasswordResetToken = {
__typename?: 'ValidatePasswordResetToken';
email: Scalars['String'];
id: Scalars['String'];
};
export type Verify = {
__typename?: 'Verify';
tokens: AuthTokenPair;
@ -694,6 +728,11 @@ export type ChallengeMutationVariables = Exact<{
export type ChallengeMutation = { __typename?: 'Mutation', challenge: { __typename?: 'LoginToken', loginToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } };
export type EmailPasswordResetLinkMutationVariables = Exact<{ [key: string]: never; }>;
export type EmailPasswordResetLinkMutation = { __typename?: 'Mutation', emailPasswordResetLink: { __typename?: 'EmailPasswordResetLink', success: boolean } };
export type GenerateApiKeyTokenMutationVariables = Exact<{
apiKeyId: Scalars['String'];
expiresAt: Scalars['String'];
@ -730,6 +769,14 @@ export type SignUpMutationVariables = Exact<{
export type SignUpMutation = { __typename?: 'Mutation', signUp: { __typename?: 'LoginToken', loginToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } };
export type UpdatePasswordViaResetTokenMutationVariables = Exact<{
token: Scalars['String'];
newPassword: Scalars['String'];
}>;
export type UpdatePasswordViaResetTokenMutation = { __typename?: 'Mutation', updatePasswordViaResetToken: { __typename?: 'InvalidatePassword', success: boolean } };
export type VerifyMutationVariables = Exact<{
loginToken: Scalars['String'];
}>;
@ -744,6 +791,13 @@ export type CheckUserExistsQueryVariables = Exact<{
export type CheckUserExistsQuery = { __typename?: 'Query', checkUserExists: { __typename?: 'UserExists', exists: boolean } };
export type ValidatePasswordResetTokenQueryVariables = Exact<{
token: Scalars['String'];
}>;
export type ValidatePasswordResetTokenQuery = { __typename?: 'Query', validatePasswordResetToken: { __typename?: 'ValidatePasswordResetToken', id: string, email: string } };
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
@ -1008,6 +1062,38 @@ export function useChallengeMutation(baseOptions?: Apollo.MutationHookOptions<Ch
export type ChallengeMutationHookResult = ReturnType<typeof useChallengeMutation>;
export type ChallengeMutationResult = Apollo.MutationResult<ChallengeMutation>;
export type ChallengeMutationOptions = Apollo.BaseMutationOptions<ChallengeMutation, ChallengeMutationVariables>;
export const EmailPasswordResetLinkDocument = gql`
mutation EmailPasswordResetLink {
emailPasswordResetLink {
success
}
}
`;
export type EmailPasswordResetLinkMutationFn = Apollo.MutationFunction<EmailPasswordResetLinkMutation, EmailPasswordResetLinkMutationVariables>;
/**
* __useEmailPasswordResetLinkMutation__
*
* To run a mutation, you first call `useEmailPasswordResetLinkMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useEmailPasswordResetLinkMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [emailPasswordResetLinkMutation, { data, loading, error }] = useEmailPasswordResetLinkMutation({
* variables: {
* },
* });
*/
export function useEmailPasswordResetLinkMutation(baseOptions?: Apollo.MutationHookOptions<EmailPasswordResetLinkMutation, EmailPasswordResetLinkMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<EmailPasswordResetLinkMutation, EmailPasswordResetLinkMutationVariables>(EmailPasswordResetLinkDocument, options);
}
export type EmailPasswordResetLinkMutationHookResult = ReturnType<typeof useEmailPasswordResetLinkMutation>;
export type EmailPasswordResetLinkMutationResult = Apollo.MutationResult<EmailPasswordResetLinkMutation>;
export type EmailPasswordResetLinkMutationOptions = Apollo.BaseMutationOptions<EmailPasswordResetLinkMutation, EmailPasswordResetLinkMutationVariables>;
export const GenerateApiKeyTokenDocument = gql`
mutation GenerateApiKeyToken($apiKeyId: String!, $expiresAt: String!) {
generateApiKeyToken(apiKeyId: $apiKeyId, expiresAt: $expiresAt) {
@ -1191,6 +1277,43 @@ export function useSignUpMutation(baseOptions?: Apollo.MutationHookOptions<SignU
export type SignUpMutationHookResult = ReturnType<typeof useSignUpMutation>;
export type SignUpMutationResult = Apollo.MutationResult<SignUpMutation>;
export type SignUpMutationOptions = Apollo.BaseMutationOptions<SignUpMutation, SignUpMutationVariables>;
export const UpdatePasswordViaResetTokenDocument = gql`
mutation UpdatePasswordViaResetToken($token: String!, $newPassword: String!) {
updatePasswordViaResetToken(
passwordResetToken: $token
newPassword: $newPassword
) {
success
}
}
`;
export type UpdatePasswordViaResetTokenMutationFn = Apollo.MutationFunction<UpdatePasswordViaResetTokenMutation, UpdatePasswordViaResetTokenMutationVariables>;
/**
* __useUpdatePasswordViaResetTokenMutation__
*
* To run a mutation, you first call `useUpdatePasswordViaResetTokenMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdatePasswordViaResetTokenMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [updatePasswordViaResetTokenMutation, { data, loading, error }] = useUpdatePasswordViaResetTokenMutation({
* variables: {
* token: // value for 'token'
* newPassword: // value for 'newPassword'
* },
* });
*/
export function useUpdatePasswordViaResetTokenMutation(baseOptions?: Apollo.MutationHookOptions<UpdatePasswordViaResetTokenMutation, UpdatePasswordViaResetTokenMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<UpdatePasswordViaResetTokenMutation, UpdatePasswordViaResetTokenMutationVariables>(UpdatePasswordViaResetTokenDocument, options);
}
export type UpdatePasswordViaResetTokenMutationHookResult = ReturnType<typeof useUpdatePasswordViaResetTokenMutation>;
export type UpdatePasswordViaResetTokenMutationResult = Apollo.MutationResult<UpdatePasswordViaResetTokenMutation>;
export type UpdatePasswordViaResetTokenMutationOptions = Apollo.BaseMutationOptions<UpdatePasswordViaResetTokenMutation, UpdatePasswordViaResetTokenMutationVariables>;
export const VerifyDocument = gql`
mutation Verify($loginToken: String!) {
verify(loginToken: $loginToken) {
@ -1265,6 +1388,42 @@ export function useCheckUserExistsLazyQuery(baseOptions?: Apollo.LazyQueryHookOp
export type CheckUserExistsQueryHookResult = ReturnType<typeof useCheckUserExistsQuery>;
export type CheckUserExistsLazyQueryHookResult = ReturnType<typeof useCheckUserExistsLazyQuery>;
export type CheckUserExistsQueryResult = Apollo.QueryResult<CheckUserExistsQuery, CheckUserExistsQueryVariables>;
export const ValidatePasswordResetTokenDocument = gql`
query validatePasswordResetToken($token: String!) {
validatePasswordResetToken(passwordResetToken: $token) {
id
email
}
}
`;
/**
* __useValidatePasswordResetTokenQuery__
*
* To run a query within a React component, call `useValidatePasswordResetTokenQuery` and pass it any options that fit your needs.
* When your component renders, `useValidatePasswordResetTokenQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useValidatePasswordResetTokenQuery({
* variables: {
* token: // value for 'token'
* },
* });
*/
export function useValidatePasswordResetTokenQuery(baseOptions: Apollo.QueryHookOptions<ValidatePasswordResetTokenQuery, ValidatePasswordResetTokenQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ValidatePasswordResetTokenQuery, ValidatePasswordResetTokenQueryVariables>(ValidatePasswordResetTokenDocument, options);
}
export function useValidatePasswordResetTokenLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ValidatePasswordResetTokenQuery, ValidatePasswordResetTokenQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ValidatePasswordResetTokenQuery, ValidatePasswordResetTokenQueryVariables>(ValidatePasswordResetTokenDocument, options);
}
export type ValidatePasswordResetTokenQueryHookResult = ReturnType<typeof useValidatePasswordResetTokenQuery>;
export type ValidatePasswordResetTokenLazyQueryHookResult = ReturnType<typeof useValidatePasswordResetTokenLazyQuery>;
export type ValidatePasswordResetTokenQueryResult = Apollo.QueryResult<ValidatePasswordResetTokenQuery, ValidatePasswordResetTokenQueryVariables>;
export const GetClientConfigDocument = gql`
query GetClientConfig {
clientConfig {