Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

migration of change password to graphql #3

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion .env.test
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DATABASE_URL=postgres://test:test@localhost:5432/test
DATABASE_URL=postgres://demo:demo@localhost:5432/test
NODE_ENV=test
NO_FE=1

Expand Down
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ Main techonologies:

### 🛠️ Prerequisites

- All environment variables are read using [dotenv](https://www.npmjs.com/package/dotenv). You will need to create `.env` file in project root. Example data for this file can be found in `.env.sample` already located inside root of the project or using the following info:

```sh
cp .env.sample .env
```
- Please make sure to have an accessible SQL database instance (Postgresql recommended) and a Redis instance to store cache and sessions.
Both can be installed locally running
```
Expand All @@ -20,12 +24,6 @@ Edit your `/etc/hosts` file and add an entry to enable access to `admin.localhos
```
127.0.0.1 admin.localhost
```

- All environment variables are read using [dotenv](https://www.npmjs.com/package/dotenv). You will need to create `.env` file in project root. Example data for this file can be found in `.env.sample` already located inside root of the project or using the following info:

```sh
cp .env.sample .env
```
---

### 🚀 Development
Expand Down
7 changes: 0 additions & 7 deletions client/_core/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,6 @@ export const sendWelcomeEmail = (body) => async (dispatch, getState) => {
return r.payload;
};

export const updatePassword = (newPassword) => async (dispatch, getState) => {
const r = await fetchApi({ getState, dispatch }, '/v2/auth/me/change-password', {
body: { newPassword },
method: 'PUT',
});
return r.payload;
};

export const setNewPassword = ({ email, resetToken, newPassword }) => async (dispatch, getState) => {
const r = await fetchApi({ getState, dispatch }, '/v2/auth/reset-password', {
Expand Down
101 changes: 101 additions & 0 deletions client/_gen/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export type AuthResult = {
type?: Maybe<Scalars['String']>;
};

export type ChangePasswordResult = {
__typename?: 'ChangePasswordResult';
success: Scalars['Boolean'];
};

export type Company = {
__typename?: 'Company';
id: Scalars['Int'];
Expand Down Expand Up @@ -60,10 +65,16 @@ export type Message = {

export type Mutation = {
__typename?: 'Mutation';
changePassword: ChangePasswordResult;
login: AuthResult;
};


export type MutationChangePasswordArgs = {
newPassword: Scalars['String'];
};


export type MutationLoginArgs = {
email: Scalars['String'];
password: Scalars['String'];
Expand Down Expand Up @@ -93,6 +104,19 @@ export type User = {
phone?: Maybe<Scalars['String']>;
};

export type ChangePasswordMutationVariables = Exact<{
newPassword: Scalars['String'];
}>;


export type ChangePasswordMutation = (
{ __typename?: 'Mutation' }
& { changePassword: (
{ __typename?: 'ChangePasswordResult' }
& Pick<ChangePasswordResult, 'success'>
) }
);

export type GlobalSearchQueryVariables = Exact<{
query: Scalars['String'];
}>;
Expand Down Expand Up @@ -135,6 +159,17 @@ export type LoginMutation = (
) }
);

export type MeQueryVariables = Exact<{ [key: string]: never; }>;


export type MeQuery = (
{ __typename?: 'Query' }
& { me: (
{ __typename?: 'User' }
& Pick<User, 'id' | 'firstName' | 'lastName'>
) }
);

export type MyCompanyQueryVariables = Exact<{ [key: string]: never; }>;


Expand All @@ -150,6 +185,38 @@ export type MyCompanyQuery = (
);


export const ChangePasswordDocument = gql`
mutation changePassword($newPassword: String!) {
changePassword(newPassword: $newPassword) {
success
}
}
`;
export type ChangePasswordMutationFn = Apollo.MutationFunction<ChangePasswordMutation, ChangePasswordMutationVariables>;

/**
* __useChangePasswordMutation__
*
* To run a mutation, you first call `useChangePasswordMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useChangePasswordMutation` 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 [changePasswordMutation, { data, loading, error }] = useChangePasswordMutation({
* variables: {
* newPassword: // value for 'newPassword'
* },
* });
*/
export function useChangePasswordMutation(baseOptions?: Apollo.MutationHookOptions<ChangePasswordMutation, ChangePasswordMutationVariables>) {
return Apollo.useMutation<ChangePasswordMutation, ChangePasswordMutationVariables>(ChangePasswordDocument, baseOptions);
}
export type ChangePasswordMutationHookResult = ReturnType<typeof useChangePasswordMutation>;
export type ChangePasswordMutationResult = Apollo.MutationResult<ChangePasswordMutation>;
export type ChangePasswordMutationOptions = Apollo.BaseMutationOptions<ChangePasswordMutation, ChangePasswordMutationVariables>;
export const GlobalSearchDocument = gql`
query globalSearch($query: String!) {
globalSearch(query: $query) {
Expand Down Expand Up @@ -265,6 +332,40 @@ export function useLoginMutation(baseOptions?: Apollo.MutationHookOptions<LoginM
export type LoginMutationHookResult = ReturnType<typeof useLoginMutation>;
export type LoginMutationResult = Apollo.MutationResult<LoginMutation>;
export type LoginMutationOptions = Apollo.BaseMutationOptions<LoginMutation, LoginMutationVariables>;
export const MeDocument = gql`
query me {
me {
id
firstName
lastName
}
}
`;

/**
* __useMeQuery__
*
* To run a query within a React component, call `useMeQuery` and pass it any options that fit your needs.
* When your component renders, `useMeQuery` 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 } = useMeQuery({
* variables: {
* },
* });
*/
export function useMeQuery(baseOptions?: Apollo.QueryHookOptions<MeQuery, MeQueryVariables>) {
return Apollo.useQuery<MeQuery, MeQueryVariables>(MeDocument, baseOptions);
}
export function useMeLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MeQuery, MeQueryVariables>) {
return Apollo.useLazyQuery<MeQuery, MeQueryVariables>(MeDocument, baseOptions);
}
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
export type MeQueryResult = Apollo.QueryResult<MeQuery, MeQueryVariables>;
export const MyCompanyDocument = gql`
query myCompany {
me {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { gql } from '@apollo/client';

export const changePassword = gql`
mutation changePassword($newPassword: String!) {
changePassword(newPassword: $newPassword) {
success
}
}
`;
51 changes: 24 additions & 27 deletions client/components/AccountPasswordForm/AccountPasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
import React, { useState } from 'react';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import Button from '../Button';
import Input from '../Forms/Input';
import styles from './AccountPasswordForm.module.scss';
import FormError from '../Forms/Error';
import { fieldRequired, validPassword } from '../../utils/validators';
import { Form, Formik, Field } from 'formik';
import { updatePassword } from '../../_core/api';
import { useChangePasswordMutation } from '../../_gen/graphql';
import { toast } from 'react-toastify';

export const AccountPasswordForm = () => {
const [changePasswordMutation] = useChangePasswordMutation();
const [error, setError] = useState();

const submitChangePassword = async (values, { setSubmitting, resetForm }) => {
const { newPassword } = values;
try {
await changePasswordMutation({
variables: {
newPassword
}
});
resetForm();
toast.success('Password updated!');
} catch (e) {
setError(e.message);
}
setSubmitting(false);
}

export const PasswordForm = ({ handler, error }: any) => {
return (
<Formik
initialValues={{ newPassword: '', newPasswordConfirm: '' }}
Expand All @@ -24,15 +42,7 @@ export const PasswordForm = ({ handler, error }: any) => {
}
return errors;
}}
onSubmit={async (values, { setSubmitting, resetForm }) => {
try {
await handler(values);
resetForm();
} catch (e) {
// console.log(e);
}
setSubmitting(false);
}}
onSubmit={submitChangePassword}
>
{({ isSubmitting, isValid, values }) => (
<Form>
Expand Down Expand Up @@ -80,17 +90,4 @@ export const PasswordForm = ({ handler, error }: any) => {
);
};

const AccountPasswordForm = ({ updatePassword }: any) => {
const [error, setError] = useState('');
const handler = async (values) => {
try {
await updatePassword(values.newPassword);
} catch (e) {
setError(e.message);
throw e;
}
};
return <PasswordForm handler={handler} error={error} />;
};

export default compose(connect(null, { updatePassword }))(AccountPasswordForm);
export default AccountPasswordForm;
2 changes: 2 additions & 0 deletions client/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import '../scss/main.scss';
import 'react-toastify/dist/ReactToastify.css';
import '../scss/toastify.scss';

import { ApolloProvider } from '@apollo/client';
import jwt_decode from 'jwt-decode';
Expand Down
14 changes: 14 additions & 0 deletions client/scss/toastify.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@import '../constants/colors.scss';

.Toastify__toast--info {
background-color: $lightYellow !important;
}
.Toastify__toast--success {
background-color: $primary !important;
}
.Toastify__toast--warning {
background-color: $lightRed !important;
}
.Toastify__toast--error {
background-color: $accentDark !important;
}
62 changes: 31 additions & 31 deletions server/modules/user/auth.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,35 +519,35 @@ describe('#Auth', () => {
});
});

describe('change Password', () => {
it('should change the password and be able to login', async () => {
const newPassword = 'Aa123456!';
const res = await request(api)
.put('/v2/auth/me/change-password')
.set(await getAuthHeaders(app, data.user))
.send({ newPassword })
.expect(200);
expect(res.body).toStrictEqual({ success: true });

await request(api)
.post('/v2/auth/login')
.send({
email: data.user.email,
password: newPassword,
})
.expect(200);
});

it(' should not allow invalid passwords', async () => {
const newPassword = '123';
const res = await request(api)
.put('/v2/auth/me/change-password')
.set(await getAuthHeaders(app, data.user))
.send({ newPassword })
.expect(400);
expect(res.body.message).toEqual(
'Password must be minimum 8 characters, must contain an uppercase letter, a lower case letter, at least one number and one special character.',
);
});
});
// describe('change Password', () => {
// // it('should change the password and be able to login', async () => {
// // const newPassword = 'Aa123456!';
// // const res = await request(api)
// // .put('/v2/auth/me/change-password')
// // .set(await getAuthHeaders(app, data.user))
// // .send({ newPassword })
// // .expect(200);
// // expect(res.body).toStrictEqual({ success: true });

// // await request(api)
// // .post('/v2/auth/login')
// // .send({
// // email: data.user.email,
// // password: newPassword,
// // })
// // .expect(200);
// // });

// // it(' should not allow invalid passwords', async () => {
// // const newPassword = '123';
// // const res = await request(api)
// // .put('/v2/auth/me/change-password')
// // .set(await getAuthHeaders(app, data.user))
// // .send({ newPassword })
// // .expect(400);
// // expect(res.body.message).toEqual(
// // 'Password must be minimum 8 characters, must contain an uppercase letter, a lower case letter, at least one number and one special character.',
// // );
// // });
// });
});
10 changes: 1 addition & 9 deletions server/modules/user/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Body, Controller, HttpCode, Post, Req, Request, Put, Get } from '@nestjs/common';
import { Body, Controller, HttpCode, Post, Req, Request, Get } from '@nestjs/common';

import { AppLogger } from '../infra/logger/app.logger';
import { AuthService } from './auth.service';
import { Protected } from './lib/auth.decorator';
import {
ChangePasswordDto,
EnableSMSDto,
ForgotPasswordDto,
ImpersonateDto,
Expand Down Expand Up @@ -110,13 +109,6 @@ export class AuthController {
});
}

@Put('me/change-password')
@HttpCode(200)
@Protected()
async changePassword(@Req() req, @Body() body: ChangePasswordDto): Promise<any> {
return await this.authService.changePassword(req.user, body.newPassword);
}

@Get('me')
@HttpCode(200)
async getMe(@Req() req): Promise<any> {
Expand Down
Loading