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

fix(passcodes): user enumeration on invalid passcodes #3054

Closed
wants to merge 3 commits into from
Closed
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
118 changes: 118 additions & 0 deletions cypress/integration/ete/registration_1.2.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,63 @@ describe('Registration flow - Split 1/2', () => {
},
);
});

it('should redirect with error when multiple passcode attempts fail', () => {
const unregisteredEmail = randomMailosaurEmail();
cy.visit(`/register/email`);

const timeRequestWasMade = new Date();
cy.get('input[name=email]').type(unregisteredEmail);
cy.get('[data-cy="main-form-submit-button"]').click();

cy.contains('Enter your code');
cy.contains(unregisteredEmail);
cy.contains('send again');
cy.contains('try another address');

cy.checkForEmailAndGetDetails(unregisteredEmail, timeRequestWasMade).then(
({ body, codes }) => {
// email
expect(body).to.have.string('Your verification code');
expect(codes?.length).to.eq(1);
const code = codes?.[0].value;
expect(code).to.match(/^\d{6}$/);

// passcode page
cy.url().should('include', '/register/email-sent');

// attempt 1
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit verification code').click();
cy.url().should('include', '/register/code');
cy.contains('Incorrect code');

// attempt 2
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit verification code').click();
cy.url().should('include', '/register/code');
cy.contains('Incorrect code');

// attempt 3
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit verification code').click();
cy.url().should('include', '/register/code');
cy.contains('Incorrect code');

// attempt 4
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit verification code').click();
cy.url().should('include', '/register/code');
cy.contains('Incorrect code');

// attempt 5
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit verification code').click();
cy.url().should('include', '/register/email');
cy.contains('Your code has expired');
},
);
});
});

context('existing user going through registration flow', () => {
Expand Down Expand Up @@ -601,6 +658,67 @@ describe('Registration flow - Split 1/2', () => {
});
});
});

it('should redirect with error when multiple passcode attempts fail', () => {
cy
.createTestUser({
isUserEmailValidated: true,
})
?.then(({ emailAddress }) => {
cy.setCookie('cypress-mock-state', '1'); // passcode send again timer

cy.visit(`/register/email`);
cy.get('input[name=email]').clear().type(emailAddress);

const timeRequestWasMade = new Date();
cy.get('[data-cy="main-form-submit-button"]').click();

cy.checkForEmailAndGetDetails(
emailAddress,
timeRequestWasMade,
).then(({ body, codes }) => {
// email
expect(body).to.have.string('Your one-time passcode');
expect(codes?.length).to.eq(1);
const code = codes?.[0].value;
expect(code).to.match(/^\d{6}$/);

// passcode page
cy.url().should('include', '/register/email-sent');
cy.contains('Enter your code');

// attempt 1
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit verification code').click();
cy.url().should('include', '/register/code');
cy.contains('Incorrect code');

// attempt 2
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit verification code').click();
cy.url().should('include', '/register/code');
cy.contains('Incorrect code');

// attempt 3
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit verification code').click();
cy.url().should('include', '/register/code');
cy.contains('Incorrect code');

// attempt 4
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit verification code').click();
cy.url().should('include', '/register/code');
cy.contains('Incorrect code');

// attempt 5
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit verification code').click();
cy.url().should('include', '/register/email');
cy.contains('Your code has expired');
});
});
});
});

context('ACTIVE user - with only password authenticator', () => {
Expand Down
107 changes: 107 additions & 0 deletions cypress/integration/ete/reset_password_passcode.7.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,69 @@ describe('Password reset recovery flows - with Passcodes', () => {
});
});

it('should redirect with error when multiple passcode attempts fail', () => {
cy
.createTestUser({
isUserEmailValidated: true,
})
?.then(({ emailAddress }) => {
cy.visit(`/reset-password`);

const timeRequestWasMade = new Date();
cy.get('input[name=email]').type(emailAddress);
cy.get('[data-cy="main-form-submit-button"]').click();

cy.contains('Enter your one-time code');
cy.contains(emailAddress);
cy.contains('send again');
cy.contains('try another address');

cy.checkForEmailAndGetDetails(emailAddress, timeRequestWasMade).then(
({ body, codes }) => {
// email
expect(body).to.have.string('Your one-time passcode');
expect(codes?.length).to.eq(1);
const code = codes?.[0].value;
expect(code).to.match(/^\d{6}$/);

// passcode page
cy.url().should('include', '/reset-password/email-sent');
cy.contains('Enter your one-time code');

// attempt 1
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit one-time code').click();
cy.url().should('include', '/reset-password/code');
cy.contains('Incorrect code');

// attempt 2
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit one-time code').click();
cy.url().should('include', '/reset-password/code');
cy.contains('Incorrect code');

// attempt 3
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit one-time code').click();
cy.url().should('include', '/reset-password/code');
cy.contains('Incorrect code');

// attempt 4
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit one-time code').click();
cy.url().should('include', '/reset-password/code');
cy.contains('Incorrect code');

// attempt 5
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Submit one-time code').click();
cy.url().should('include', '/reset-password');
cy.contains('Your code has expired');
},
);
});
});

it('ACTIVE user with only password authenticator - allow the user to change thier password and authenticate', () => {
const emailAddress = randomMailosaurEmail();
cy.visit(`/register/email`);
Expand Down Expand Up @@ -742,4 +805,48 @@ describe('Password reset recovery flows - with Passcodes', () => {
cy.contains('Incorrect code');
});
});

it('should redirect with error when multiple passcode attempts fail', () => {
const emailAddress = randomMailosaurEmail();
cy.visit(`/reset-password`);

cy.contains('Reset password');
cy.get('input[name=email]').type(emailAddress);
cy.get('[data-cy="main-form-submit-button"]').click();

// passcode page
cy.url().should('include', '/reset-password/email-sent');
cy.contains('Enter your one-time code');
cy.contains('Don’t have an account?');

// attempt 1
cy.get('input[name=code]').type('123456');
cy.contains('Submit one-time code').click();
cy.url().should('include', '/reset-password/code');
cy.contains('Incorrect code');

// attempt 2
cy.get('input[name=code]').type('123456');
cy.contains('Submit one-time code').click();
cy.url().should('include', '/reset-password/code');
cy.contains('Incorrect code');

// attempt 3
cy.get('input[name=code]').type('123456');
cy.contains('Submit one-time code').click();
cy.url().should('include', '/reset-password/code');
cy.contains('Incorrect code');

// attempt 4
cy.get('input[name=code]').type('123456');
cy.contains('Submit one-time code').click();
cy.url().should('include', '/reset-password/code');
cy.contains('Incorrect code');

// attempt 5
cy.get('input[name=code]').type('123456');
cy.contains('Submit one-time code').click();
cy.url().should('include', '/reset-password');
cy.contains('Your code has expired');
});
});
103 changes: 103 additions & 0 deletions cypress/integration/ete/sign_in_passcode.8.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,65 @@ describe('Sign In flow, with passcode', () => {
});
});
});

it('should redirect with error when multiple passcode attempts fail', () => {
cy
.createTestUser({
isUserEmailValidated: true,
})
?.then(({ emailAddress }) => {
cy.setCookie('cypress-mock-state', '1'); // passcode send again timer
cy.visit(`/signin?usePasscodeSignIn=true`);
cy.get('input[name=email]').clear().type(emailAddress);

const timeRequestWasMade = new Date();
cy.get('[data-cy="main-form-submit-button"]').click();

cy.checkForEmailAndGetDetails(emailAddress, timeRequestWasMade).then(
({ body, codes }) => {
// email
expect(body).to.have.string('Your one-time passcode');
expect(codes?.length).to.eq(1);
const code = codes?.[0].value;
expect(code).to.match(/^\d{6}$/);

// passcode page
cy.url().should('include', '/signin/code');
cy.contains('Enter your one-time code');

// attempt 1
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Sign in').click();
cy.url().should('include', '/signin/code');
cy.contains('Incorrect code');

// attempt 2
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Sign in').click();
cy.url().should('include', '/signin/code');
cy.contains('Incorrect code');

// attempt 3
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Sign in').click();
cy.url().should('include', '/signin/code');
cy.contains('Incorrect code');

// attempt 4
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Sign in').click();
cy.url().should('include', '/signin/code');
cy.contains('Incorrect code');

// attempt 5
cy.get('input[name=code]').type(`${+code! + 1}`);
cy.contains('Sign in').click();
cy.url().should('include', '/signin');
cy.contains('Your code has expired');
},
);
});
});
});

context('ACTIVE user - with only password authenticator', () => {
Expand Down Expand Up @@ -379,5 +438,49 @@ describe('Sign In flow, with passcode', () => {

cy.contains('Incorrect code');
});

it('NON_EXISTENT user - should redirect with error when multiple passcode attempts fail', () => {
const emailAddress = randomMailosaurEmail();
cy.visit(`/signin?usePasscodeSignIn=true`);

cy.contains('Sign in');
cy.get('input[name=email]').type(emailAddress);
cy.get('[data-cy="main-form-submit-button"]').click();

// passcode page
cy.url().should('include', '/signin/code');
cy.contains('Enter your one-time code');
cy.contains('Don’t have an account?');

// attempt 1
cy.get('input[name=code]').type('123456');
cy.contains('Sign in').click();
cy.url().should('include', '/signin/code');
cy.contains('Incorrect code');

// attempt 2
cy.get('input[name=code]').type('123456');
cy.contains('Sign in').click();
cy.url().should('include', '/signin/code');
cy.contains('Incorrect code');

// attempt 3
cy.get('input[name=code]').type('123456');
cy.contains('Sign in').click();
cy.url().should('include', '/signin/code');
cy.contains('Incorrect code');

// attempt 4
cy.get('input[name=code]').type('123456');
cy.contains('Sign in').click();
cy.url().should('include', '/signin/code');
cy.contains('Incorrect code');

// attempt 5
cy.get('input[name=code]').type('123456');
cy.contains('Sign in').click();
cy.url().should('include', '/signin');
cy.contains('Your code has expired');
});
});
});
12 changes: 12 additions & 0 deletions src/client/pages/RegisterWithEmail.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Meta } from '@storybook/react';

import { RegisterWithEmail } from '@/client/pages/RegisterWithEmail';
import { RegistrationProps } from '@/client/pages/Registration';
import { PasscodeErrors } from '@/shared/model/Errors';

export default {
title: 'Pages/RegisterWithEmail',
Expand Down Expand Up @@ -111,3 +112,14 @@ export const JobsSite = (args: RegistrationProps) => (
JobsSite.story = {
name: 'with Jobs site',
};

export const WithPasscodeExpiredError = (args: RegistrationProps) => (
<RegisterWithEmail
{...args}
pageError={PasscodeErrors.PASSCODE_EXPIRED}
shortRequestId="123e4567"
/>
);
WithPasscodeExpiredError.story = {
name: 'with defaults',
};
Loading