diff --git a/cypress/deployed-and-local/integration/authentication/petitioner-account-creation.cy.ts b/cypress/deployed-and-local/integration/authentication/petitioner-account-creation.cy.ts index 23ebe8ce469..b94df3a7e23 100644 --- a/cypress/deployed-and-local/integration/authentication/petitioner-account-creation.cy.ts +++ b/cypress/deployed-and-local/integration/authentication/petitioner-account-creation.cy.ts @@ -87,11 +87,37 @@ describe('Petitioner Account Creation', () => { }); describe('Create Petitioner Account and login', () => { - const TEST_EMAIL = `cypress_test_account+success_${GUID}@example.com`; const TEST_NAME = 'Cypress Test'; const TEST_PASSWORD = generatePassword(VALID_PASSWORD_CONFIG); - it('should create an account and verify it using the verification link, then login and create an eletronic case', () => { + it('should prevent multiple submissions', () => { + const TEST_EMAIL = `cypress_test_account+no_multiple_submissions_${GUID}@example.com`; + cy.visit('/create-account/petitioner'); + cy.get('[data-testid="petitioner-account-creation-email"]').type( + TEST_EMAIL, + ); + cy.get('[data-testid="petitioner-account-creation-name"]').type( + TEST_NAME, + ); + cy.get('[data-testid="petitioner-account-creation-password"]').type( + TEST_PASSWORD, + ); + cy.get( + '[data-testid="petitioner-account-creation-confirm-password"]', + ).type(TEST_PASSWORD); + cy.intercept('POST', '/auth/account/create').as('accountCreationRequest'); + + // eslint-disable-next-line cypress/unsafe-to-chain-command + cy.get('[data-testid="petitioner-account-creation-submit-button"]') + .click({ force: true }) + .click({ force: true }); + + cy.wait('@accountCreationRequest'); + cy.get('@accountCreationRequest.all').should('have.length', 1); + }); + + it('should create an account and verify it using the verification link, then login and create an electronic case', () => { + const TEST_EMAIL = `cypress_test_account+success_${GUID}@example.com`; createAPetitioner({ email: TEST_EMAIL, name: TEST_NAME, diff --git a/web-api/src/business/useCases/auth/signUpUserInteractor.ts b/web-api/src/business/useCases/auth/signUpUserInteractor.ts index 87091c8894b..ab22dda09b1 100644 --- a/web-api/src/business/useCases/auth/signUpUserInteractor.ts +++ b/web-api/src/business/useCases/auth/signUpUserInteractor.ts @@ -28,6 +28,11 @@ export const signUpUserInteractor = async ( email: user.email, }); + // Note that this check can fail to catch two (nearly) simultaneous requests, + // and Cognito can therefore create accounts with the same email. + // (See https://stackoverflow.com/questions/50730759/user-pool-allows-two-users-with-same-email-despite-configuration) + // On the frontend, we can prevent multiple submissions; if we wanted to enforce non-duplicates on the backend, + // we would probably need to run an asynchronous task to delete duplicate records. if (existingAccount) { const accountUnconfirmed = existingAccount.accountStatus === UserStatusType.UNCONFIRMED; diff --git a/web-client/src/views/CreatePetitionerAccount/CreatePetitionerAccountForm.tsx b/web-client/src/views/CreatePetitionerAccount/CreatePetitionerAccountForm.tsx index c9971de0789..f3b47a340ab 100644 --- a/web-client/src/views/CreatePetitionerAccount/CreatePetitionerAccountForm.tsx +++ b/web-client/src/views/CreatePetitionerAccount/CreatePetitionerAccountForm.tsx @@ -1,11 +1,16 @@ import { Button } from '@web-client/ustc-ui/Button/Button'; import { RequirementsText } from '@web-client/views/CreatePetitionerAccount/RequirementsText'; import { connect } from '@web-client/presenter/shared.cerebral'; +import { debounce } from 'lodash'; import { sequences, state } from '@web-client/presenter/app.cerebral'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; + +const DEBOUNCE_TIME_MS = 500; export const CreatePetitionerAccountForm = connect( { + alertError: state.alertError, + alertWarning: state.alertWarning, confirmPassword: state.form.confirmPassword, createAccountHelper: state.createAccountHelper, navigateToLoginSequence: sequences.navigateToLoginSequence, @@ -18,6 +23,8 @@ export const CreatePetitionerAccountForm = connect( updateFormValueSequence: sequences.updateFormValueSequence, }, ({ + alertError, + alertWarning, confirmPassword, createAccountHelper, navigateToLoginSequence, @@ -30,6 +37,18 @@ export const CreatePetitionerAccountForm = connect( }) => { const [inFocusEmail, setInFocusEmail] = useState(true); const [inFocusName, setInFocusName] = useState(true); + const [submitDisabled, setSubmitDisabled] = useState(false); + + // Re-enabled submit button if submission was unsuccessful + useEffect(() => { + if (alertError || alertWarning) { + setSubmitDisabled(false); + } + }, [alertError, alertWarning]); + + const submitFunction = debounce(() => { + submitCreatePetitionerAccountFormSequence(); + }, DEBOUNCE_TIME_MS); return ( <> @@ -42,7 +61,8 @@ export const CreatePetitionerAccountForm = connect(
{ e.preventDefault(); - submitCreatePetitionerAccountFormSequence(); + setSubmitDisabled(true); + submitFunction(); }} >