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

added UI for change password form in user profile #2023

Merged
merged 17 commits into from
Aug 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
51 changes: 48 additions & 3 deletions verification/curator-service/api/src/controllers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,51 @@ export class AuthController {
},
);

/**
* Update user's password
*/
this.router.post(
'/change-password',
mustBeAuthenticated,
async (req: Request, res: Response) => {
const oldPassword = req.body.oldPassword as string;
const newPassword = req.body.newPassword as string;
const user = req.user as UserDocument;

if (!user) {
return res.sendStatus(403);
}

try {
const currentUser = await User.findById(user.id);
if (!currentUser) {
return res.sendStatus(403);
}

const isValidPassword = await currentUser.isValidPassword(
oldPassword,
);

if (!isValidPassword) {
return res
.status(403)
.json({ message: 'Old password is incorrect' });
}

const hashedPassword = await bcrypt.hash(newPassword, 10);
await User.findByIdAndUpdate(user.id, {
password: hashedPassword,
});

return res
.status(200)
.json({ message: 'Password changed successfully' });
} catch (error) {
return res.status(500).json(error);
}
},
);

/**
* Generate reset password link
*/
Expand Down Expand Up @@ -373,12 +418,12 @@ export class AuthController {
this.router.post(
'/register',
async (req: Request, res: Response): Promise<void> => {
const removeGoogleID = req.body.removeGoogleID as boolean;
const user = await User.create({
name: req.body.name,
email: req.body.email,
// Necessary to pass mongoose validation.
googleID: '42',
roles: req.body.roles,
...(removeGoogleID !== true && { googleID: '42' }),
});
req.login(user, (err: Error) => {
if (!err) {
Expand Down Expand Up @@ -414,7 +459,7 @@ export class AuthController {
// Cf. https://github.com/jaredhanson/passport/issues/6#issuecomment-4857287
// This doesn't work however for now as per, if you hit this bug, you have to manually clear the cookies.
// Cf https://github.com/jaredhanson/passport/issues/776
done(null, user || undefined);
done(null, user?.publicFields() || undefined);
return;
})
.catch((e) => {
Expand Down
4 changes: 3 additions & 1 deletion verification/curator-service/api/src/controllers/cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ export default class CasesController {
const sourceAddress = process.env.AWS_SES_SENDER;
const correlationId = crypto.randomBytes(16).toString('hex');

logger.info(`Starting worker for asynchronous download with id ${correlationId}`);
logger.info(
`Starting worker for asynchronous download with id ${correlationId}`,
);
const worker = new Worker('./src/workers/downloadAsync.js', {
workerData: {
query: req.body.query as string,
Expand Down
2 changes: 1 addition & 1 deletion verification/curator-service/api/src/model/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import bcrypt from 'bcrypt';
export const userRoles = ['admin', 'curator'];

export type UserDocument = Document & {
googleID?: string;
googleID?: string | undefined;
name?: string;
email: string;
password?: string;
Expand Down
9 changes: 6 additions & 3 deletions verification/curator-service/api/src/util/validate-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ export default function validateEnv(): Readonly<{
AWS_ACCESS_KEY_ID: str({
desc: 'ID for AWS access key credential',
devDefault: 'fakeAccessKeyId',
docs: 'https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html',
docs:
'https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html',
}),
AWS_SECRET_ACCESS_KEY: str({
desc: 'Secret for AWS access key credential',
devDefault: 'fakeSecretKey',
docs: 'https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html',
docs:
'https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html',
}),
AWS_SERVICE_REGION: str({
desc: 'AWS region in which to interact with services/resources',
Expand All @@ -57,7 +59,8 @@ export default function validateEnv(): Readonly<{
devDefault: '',
}),
EMAIL_USER_PASSWORD: str({
desc: 'Password of the email address account used to send notification emails.',
desc:
'Password of the email address account used to send notification emails.',
devDefault: '',
}),
ENABLE_LOCAL_AUTH: bool({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,29 @@ describe('LandingPage', function () {
cy.get('input').should('have.length', 6);
});

it('Checks if the password validation works well in the SignUp page', function () {
cy.visit('/');
cy.contains('Welcome to G.h Data.');
cy.contains('Sign up!').click();
cy.contains('SignUp form');

cy.get('#password').type('tsgasdgasd');
cy.get('button[data-testid="sign-up-button"]').click();
cy.contains('one uppercase required!');
cy.contains('Passwords must match');

cy.get('#password').focus().clear();
cy.get('#password').type('tsgasdgGasd');
cy.get('button[data-testid="sign-up-button"]').click();
cy.contains('one number required!');

cy.get('#password').focus().clear();
cy.get('#password').type('tT$5');
cy.get('button[data-testid="sign-up-button"]').click();
cy.contains('Minimum 8 characters required!');

});

it('Validates emails', function () {
cy.visit('/');
cy.contains('Welcome to G.h Data.');
Expand Down Expand Up @@ -78,15 +101,30 @@ describe('LandingPage', function () {
cy.get('button[data-testid="change-password-button"]');
});

it('Validates passwords in the change password page', function () {
it.only('Validates passwords in the change password page', function () {
cy.visit('/');
cy.visit('/reset-password/sampletoken/tokenid');
cy.get('#password').type('tsgasdgasd');
cy.get('#passwordConfirmation').type('uu');

cy.get('button[data-testid="change-password-button"]').click();
cy.contains('Passwords must match');

cy.get('#password').type('tsgasdgasd');
cy.get('button[data-testid="change-password-button"]').click();
cy.contains('one uppercase required!');
cy.contains('Passwords must match');

cy.get('#password').focus().clear();
cy.get('#password').type('tsgasdgGasd');
cy.get('button[data-testid="change-password-button"]').click();
cy.contains('one number required!');

cy.get('#password').focus().clear();
cy.get('#password').type('tT$5');
cy.get('button[data-testid="change-password-button"]').click();
cy.contains('Minimum 8 characters required!');

});

it('Homepage with logged out user', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,70 @@ describe('Profile', function () {
email: 'alice@test.com',
roles: ['curator'],
});
cy.visit('/')
cy.visit('/');
cy.visit('/profile');

cy.contains('Alice Smith');
cy.contains('alice@test.com');
cy.contains('curator');
});

it('Checks if the change pass form is visible only for non-Google users', function () {
cy.login({
name: 'Alice Smith',
email: 'alice@test.com',
roles: ['curator'],
});
cy.visit('/')
cy.visit('/profile');

cy.get('[data-testid="change-your-password-title"]').should('not.exist');
});

it('Checks if the change pass form validation works well', function () {
cy.login({
name: 'Alice Smith',
email: 'alice@test.com',
roles: ['curator'],
removeGoogleID: true,
});
cy.visit('/');
cy.visit('/profile');

cy.contains('Change your password');

cy.get('#password').type('tsgasdgasd');
cy.get('button[data-testid="change-password-button"]').click();
cy.contains('one uppercase required!');
cy.contains('Passwords must match');

cy.get('#password').focus().clear();
cy.get('#password').type('tsgasdgGasd');
cy.get('button[data-testid="change-password-button"]').click();
cy.contains('one number required!');

cy.get('#password').focus().clear();
cy.get('#password').type('tT$5');
cy.get('button[data-testid="change-password-button"]').click();
cy.contains('Minimum 8 characters required!');
});

it.only('Checks if the validates the repeated password', function () {
cy.login({
name: 'Alice Smith',
email: 'alice@test.com',
roles: ['curator'],
removeGoogleID: true,
});
cy.visit('/');
cy.visit('/profile');

cy.contains('Change your password');

cy.get('#password').type('tsgas%dFg9asd');
cy.get('#passwordConfirmation').type('ts');
cy.get('button[data-testid="change-password-button"]').click();
cy.contains('This field is required');
cy.contains('Passwords must match');
});
});
3 changes: 3 additions & 0 deletions verification/curator-service/ui/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ declare global {
name: string;
email: string;
roles: string[];
removeGoogleID?: boolean;
}) => void;
addSource: (name: string, url: string, uploads?: []) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -99,6 +100,7 @@ export function login(opts?: {
name: string;
email: string;
roles: string[];
removeGoogleID: boolean;
}): void {
cy.request({
method: 'POST',
Expand All @@ -107,6 +109,7 @@ export function login(opts?: {
name: opts?.name ?? 'superuser',
email: opts?.email ?? 'superuser@test.com',
roles: opts?.roles ?? ['admin', 'curator'],
removeGoogleID: opts?.removeGoogleID ?? undefined,
},
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,7 @@ export function DownloadButton({
<>
<SnackbarAlert
isOpen={snackbarOpen}
setIsOpen={setSnackbarOpen}
onClose={setSnackbarOpen}
message="Email sent successfully. Please check your inbox."
type="success"
/>
Expand Down
69 changes: 68 additions & 1 deletion verification/curator-service/ui/src/components/Profile.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import React from 'react';
import { render, screen } from './util/test-utils';
import { render, screen, waitFor } from './util/test-utils';
import userEvent from '@testing-library/user-event';
import Profile from './Profile';
import { RootState } from '../redux/store';
import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer();

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

const initialLoggedInState: RootState = {
app: {
Expand Down Expand Up @@ -70,4 +79,62 @@ describe('<Profile />', () => {
screen.getByText(/Login required to view this page/i),
).toBeInTheDocument();
});

it('checks if the old password is right', async () => {
server.use(
rest.post('/auth/change-password', (req, res, ctx) => {
return res(
ctx.status(403),
ctx.json({ message: 'Old password is incorrect' }),
);
}),
);
render(<Profile />, { initialState: noUserInfoState });


userEvent.type(screen.getByLabelText('Old Password'), '1234567');
userEvent.type(screen.getByLabelText('New password'), 'asdD?234');
userEvent.type(screen.getByLabelText('Repeat new password'), 'asdD?234');

userEvent.click(screen.getByRole('button', { name: 'Change password' }));

await waitFor(
() => {
expect(
screen.getByText(/Old password is incorrect/i),
).toBeInTheDocument();
},
{ timeout: 15000 },
);
});


it('checks if the password was changed successfully', async () => {
server.use(
rest.post('/auth/change-password', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({ message: 'Password changed successfull' }),
);
}),
);
render(<Profile />, { initialState: noUserInfoState });


userEvent.type(screen.getByLabelText('Old Password'), '1234567');
userEvent.type(screen.getByLabelText('New password'), 'asdD?234');
userEvent.type(screen.getByLabelText('Repeat new password'), 'asdD?234');

userEvent.click(screen.getByRole('button', { name: 'Change password' }));

await waitFor(
() => {
expect(
screen.getByText(/Password changed successfull/i),
).toBeInTheDocument();
},
{ timeout: 15000 },
);
});

});
Loading