From 1f96fbb11344c8c3c9effe8485ebbc3eb8cce4d0 Mon Sep 17 00:00:00 2001 From: Emily Jablonski <65367387+emilyjablonski@users.noreply.github.com> Date: Thu, 23 May 2024 16:48:13 -0600 Subject: [PATCH] fix: update password requirements (#4107) --- api/prisma/seed-dev.ts | 1 + api/prisma/seed-helpers/user-factory.ts | 5 +- api/prisma/seed-staging.ts | 1 + api/src/utilities/password-regex.ts | 3 +- api/test/integration/ami-chart.e2e-spec.ts | 2 +- .../integration/api-key-guard.e2e-spec.ts | 2 +- .../application-flagged-set.e2e-spec.ts | 2 +- api/test/integration/application.e2e-spec.ts | 2 +- api/test/integration/asset.e2e-spec.ts | 2 +- api/test/integration/auth.e2e-spec.ts | 14 +++--- api/test/integration/jurisdiction.e2e-spec.ts | 2 +- api/test/integration/listing.e2e-spec.ts | 6 +-- .../multiselect-question.e2e-spec.ts | 2 +- .../integration/permission-tests/helpers.ts | 4 +- .../permission-as-admin.e2e-spec.ts | 2 +- ...n-as-juris-admin-correct-juris.e2e-spec.ts | 2 +- ...ion-as-juris-admin-wrong-juris.e2e-spec.ts | 2 +- ...ion-as-partner-correct-listing.e2e-spec.ts | 2 +- ...ssion-as-partner-wrong-listing.e2e-spec.ts | 2 +- .../permission-as-public.e2e-spec.ts | 2 +- .../reserved-community-type.e2e-spec.ts | 2 +- ...it-accessibility-priority-type.e2e-spec.ts | 2 +- .../integration/unit-rent-type.e2e-spec.ts | 2 +- api/test/integration/unit-type.e2e-spec.ts | 2 +- api/test/integration/user.e2e-spec.ts | 8 +-- api/test/unit/passports/mfa.strategy.spec.ts | 48 +++++++++--------- .../single-use-code.strategy.spec.ts | 18 +++---- api/test/unit/services/auth.service.spec.ts | 24 ++++----- api/test/unit/services/user.service.spec.ts | 8 +-- .../unit/utilities/password-helper.spec.ts | 8 +-- .../unit/utilities/password-regex.spec.ts | 27 ++++++++-- shared-helpers/index.ts | 17 ++++--- shared-helpers/src/locales/es.json | 7 ++- shared-helpers/src/locales/general.json | 7 ++- shared-helpers/src/locales/tl.json | 7 ++- shared-helpers/src/locales/vi.json | 7 ++- shared-helpers/src/locales/zh.json | 7 ++- shared-helpers/src/utilities/passwordRegex.ts | 1 + .../cypress/e2e/default/02-mfa.spec.ts | 2 +- .../cypress/fixtures/jurisdictionalAdmin.json | 4 +- .../fixtures/jurisdictionalAdminUser.json | 2 +- .../cypress/fixtures/termsUnacceptedUser.json | 2 +- .../src/components/users/FormUserConfirm.tsx | 19 ++----- sites/public/src/pages/account/edit.tsx | 50 +++++++++++++++++-- sites/public/src/pages/create-account.tsx | 29 ++++++----- sites/public/src/pages/reset-password.tsx | 13 +++-- sites/public/src/pages/sign-in.tsx | 6 +-- 47 files changed, 226 insertions(+), 163 deletions(-) create mode 100644 shared-helpers/src/utilities/passwordRegex.ts diff --git a/api/prisma/seed-dev.ts b/api/prisma/seed-dev.ts index 1ef6491b50..41a4620357 100644 --- a/api/prisma/seed-dev.ts +++ b/api/prisma/seed-dev.ts @@ -57,6 +57,7 @@ export const devSeeding = async ( confirmedAt: new Date(), jurisdictionIds: [jurisdiction.id], acceptedTerms: true, + password: 'abcdef', }), }); await prismaClient.userAccounts.create({ diff --git a/api/prisma/seed-helpers/user-factory.ts b/api/prisma/seed-helpers/user-factory.ts index b25a3ef279..81e3be86cb 100644 --- a/api/prisma/seed-helpers/user-factory.ts +++ b/api/prisma/seed-helpers/user-factory.ts @@ -15,13 +15,16 @@ export const userFactory = async (optionalParams?: { jurisdictionIds?: string[]; listings?: string[]; acceptedTerms?: boolean; + password?: string; }): Promise => ({ email: optionalParams?.email?.toLocaleLowerCase() || `${randomNoun().toLowerCase()}${randomNoun().toLowerCase()}@${randomAdjective().toLowerCase()}.com`, firstName: optionalParams?.firstName || 'First', lastName: optionalParams?.lastName || 'Last', - passwordHash: await passwordToHash('abcdef'), + passwordHash: optionalParams?.password + ? await passwordToHash(optionalParams?.password) + : await passwordToHash('Abcdef12345!'), userRoles: { create: { isAdmin: optionalParams?.roles?.isAdmin || false, diff --git a/api/prisma/seed-staging.ts b/api/prisma/seed-staging.ts index 72237252fa..b5156b9708 100644 --- a/api/prisma/seed-staging.ts +++ b/api/prisma/seed-staging.ts @@ -62,6 +62,7 @@ export const stagingSeed = async ( confirmedAt: new Date(), jurisdictionIds: [jurisdiction.id, additionalJurisdiction.id], acceptedTerms: true, + password: 'abcdef', }), }); // create a jurisdictional admin diff --git a/api/src/utilities/password-regex.ts b/api/src/utilities/password-regex.ts index f7579d1961..f7194aa7d4 100644 --- a/api/src/utilities/password-regex.ts +++ b/api/src/utilities/password-regex.ts @@ -1 +1,2 @@ -export const passwordRegex = /^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]+).{7,}$/; +export const passwordRegex = + /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{12,}$/; diff --git a/api/test/integration/ami-chart.e2e-spec.ts b/api/test/integration/ami-chart.e2e-spec.ts index 81804cfc33..a04f3b3c4f 100644 --- a/api/test/integration/ami-chart.e2e-spec.ts +++ b/api/test/integration/ami-chart.e2e-spec.ts @@ -47,7 +47,7 @@ describe('AmiChart Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/api-key-guard.e2e-spec.ts b/api/test/integration/api-key-guard.e2e-spec.ts index bce4aad946..d7b85dfdf8 100644 --- a/api/test/integration/api-key-guard.e2e-spec.ts +++ b/api/test/integration/api-key-guard.e2e-spec.ts @@ -34,7 +34,7 @@ describe('API Key Guard Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/application-flagged-set.e2e-spec.ts b/api/test/integration/application-flagged-set.e2e-spec.ts index 2605ef9199..d78a41b7ce 100644 --- a/api/test/integration/application-flagged-set.e2e-spec.ts +++ b/api/test/integration/application-flagged-set.e2e-spec.ts @@ -107,7 +107,7 @@ describe('Application flagged set Controller Tests', () => { const res = await request(app.getHttpServer()) .post('/auth/login') .set({ passkey: process.env.API_PASS_KEY || '' }) - .send({ email: adminUser.email, password: 'abcdef' }) + .send({ email: adminUser.email, password: 'Abcdef12345!' }) .expect(201); adminAccessToken = res.header?.['set-cookie'].find((cookie) => cookie.startsWith('access-token='), diff --git a/api/test/integration/application.e2e-spec.ts b/api/test/integration/application.e2e-spec.ts index f045043ef2..09f9468da0 100644 --- a/api/test/integration/application.e2e-spec.ts +++ b/api/test/integration/application.e2e-spec.ts @@ -105,7 +105,7 @@ describe('Application Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/asset.e2e-spec.ts b/api/test/integration/asset.e2e-spec.ts index 5698f8773f..23ca421361 100644 --- a/api/test/integration/asset.e2e-spec.ts +++ b/api/test/integration/asset.e2e-spec.ts @@ -35,7 +35,7 @@ describe('Asset Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/auth.e2e-spec.ts b/api/test/integration/auth.e2e-spec.ts index c5be52057a..14d8bf5a08 100644 --- a/api/test/integration/auth.e2e-spec.ts +++ b/api/test/integration/auth.e2e-spec.ts @@ -55,7 +55,7 @@ describe('Auth Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: storedUser.singleUseCode, mfaType: MfaType.email, } as Login) @@ -98,7 +98,7 @@ describe('Auth Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); @@ -139,7 +139,7 @@ describe('Auth Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); @@ -192,7 +192,7 @@ describe('Auth Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', mfaType: MfaType.sms, } as RequestMfaCode) .expect(201); @@ -249,8 +249,8 @@ describe('Auth Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef123', - passwordConfirmation: 'abcdef123', + password: 'Abcdef12345!', + passwordConfirmation: 'Abcdef12345!', token, } as UpdatePassword) .expect(200); @@ -346,7 +346,7 @@ describe('Auth Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); await request(app.getHttpServer()) diff --git a/api/test/integration/jurisdiction.e2e-spec.ts b/api/test/integration/jurisdiction.e2e-spec.ts index 8547343318..6a74197d06 100644 --- a/api/test/integration/jurisdiction.e2e-spec.ts +++ b/api/test/integration/jurisdiction.e2e-spec.ts @@ -39,7 +39,7 @@ describe('Jurisdiction Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/listing.e2e-spec.ts b/api/test/integration/listing.e2e-spec.ts index 6b994b834b..9579bc9ffd 100644 --- a/api/test/integration/listing.e2e-spec.ts +++ b/api/test/integration/listing.e2e-spec.ts @@ -92,7 +92,7 @@ describe('Listing Controller Tests', () => { const res = await request(app.getHttpServer()) .post('/auth/login') .set({ passkey: process.env.API_PASS_KEY || '' }) - .send({ email: adminUser.email, password: 'abcdef' }) + .send({ email: adminUser.email, password: 'Abcdef12345!' }) .expect(201); adminAccessToken = res.header?.['set-cookie'].find((cookie) => cookie.startsWith('access-token='), @@ -811,7 +811,7 @@ describe('Listing Controller Tests', () => { const res = await request(app.getHttpServer()) .post('/auth/login') .set({ passkey: process.env.API_PASS_KEY || '' }) - .send({ email: adminUser.email, password: 'abcdef' }) + .send({ email: adminUser.email, password: 'Abcdef12345!' }) .expect(201); adminAccessToken = res.header?.['set-cookie'].find((cookie) => @@ -823,7 +823,7 @@ describe('Listing Controller Tests', () => { const res = await request(app.getHttpServer()) .post('/auth/login') .set({ passkey: process.env.API_PASS_KEY || '' }) - .send({ email: partnerUser.email, password: 'abcdef' }) + .send({ email: partnerUser.email, password: 'Abcdef12345!' }) .expect(201); const partnerAccessToken = res.header?.['set-cookie'].find((cookie) => diff --git a/api/test/integration/multiselect-question.e2e-spec.ts b/api/test/integration/multiselect-question.e2e-spec.ts index 32514f4a5b..de20dc2338 100644 --- a/api/test/integration/multiselect-question.e2e-spec.ts +++ b/api/test/integration/multiselect-question.e2e-spec.ts @@ -49,7 +49,7 @@ describe('MultiselectQuestion Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/permission-tests/helpers.ts b/api/test/integration/permission-tests/helpers.ts index 39cbf63ec3..f567da2a35 100644 --- a/api/test/integration/permission-tests/helpers.ts +++ b/api/test/integration/permission-tests/helpers.ts @@ -268,7 +268,7 @@ export const buildUserCreateMock = ( return { firstName: 'Public User firstName', lastName: 'Public User lastName', - password: 'example password 1', + password: 'Abcdef12345!', email, jurisdictions: [{ id: jurisId }], } as unknown as UserCreate; @@ -281,7 +281,7 @@ export const buildUserInviteMock = ( return { firstName: 'Partner User firstName', lastName: 'Partner User lastName', - password: 'example password 1', + password: 'Abcdef12345!', email, jurisdictions: [{ id: jurisId }], agreedToTermsOfService: true, diff --git a/api/test/integration/permission-tests/permission-as-admin.e2e-spec.ts b/api/test/integration/permission-tests/permission-as-admin.e2e-spec.ts index 02c4049ac9..953ba914a1 100644 --- a/api/test/integration/permission-tests/permission-as-admin.e2e-spec.ts +++ b/api/test/integration/permission-tests/permission-as-admin.e2e-spec.ts @@ -113,7 +113,7 @@ describe('Testing Permissioning of endpoints as Admin User', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts b/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts index 767ade0823..9ac9d01974 100644 --- a/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts +++ b/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts @@ -117,7 +117,7 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/permission-tests/permission-as-juris-admin-wrong-juris.e2e-spec.ts b/api/test/integration/permission-tests/permission-as-juris-admin-wrong-juris.e2e-spec.ts index 5def58fdc2..67ac3a1311 100644 --- a/api/test/integration/permission-tests/permission-as-juris-admin-wrong-juris.e2e-spec.ts +++ b/api/test/integration/permission-tests/permission-as-juris-admin-wrong-juris.e2e-spec.ts @@ -118,7 +118,7 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the wron .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/permission-tests/permission-as-partner-correct-listing.e2e-spec.ts b/api/test/integration/permission-tests/permission-as-partner-correct-listing.e2e-spec.ts index 1f2c514f8d..63d8ba1c64 100644 --- a/api/test/integration/permission-tests/permission-as-partner-correct-listing.e2e-spec.ts +++ b/api/test/integration/permission-tests/permission-as-partner-correct-listing.e2e-spec.ts @@ -162,7 +162,7 @@ describe('Testing Permissioning of endpoints as partner with correct listing', ( .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/permission-tests/permission-as-partner-wrong-listing.e2e-spec.ts b/api/test/integration/permission-tests/permission-as-partner-wrong-listing.e2e-spec.ts index e672de2733..abd4efc468 100644 --- a/api/test/integration/permission-tests/permission-as-partner-wrong-listing.e2e-spec.ts +++ b/api/test/integration/permission-tests/permission-as-partner-wrong-listing.e2e-spec.ts @@ -169,7 +169,7 @@ describe('Testing Permissioning of endpoints as partner with wrong listing', () .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/permission-tests/permission-as-public.e2e-spec.ts b/api/test/integration/permission-tests/permission-as-public.e2e-spec.ts index 2bec399867..322bdc38b9 100644 --- a/api/test/integration/permission-tests/permission-as-public.e2e-spec.ts +++ b/api/test/integration/permission-tests/permission-as-public.e2e-spec.ts @@ -108,7 +108,7 @@ describe('Testing Permissioning of endpoints as public user', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); storedUserId = storedUser.id; diff --git a/api/test/integration/reserved-community-type.e2e-spec.ts b/api/test/integration/reserved-community-type.e2e-spec.ts index 5964586119..2cdf3a429f 100644 --- a/api/test/integration/reserved-community-type.e2e-spec.ts +++ b/api/test/integration/reserved-community-type.e2e-spec.ts @@ -50,7 +50,7 @@ describe('ReservedCommunityType Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/unit-accessibility-priority-type.e2e-spec.ts b/api/test/integration/unit-accessibility-priority-type.e2e-spec.ts index 59bdfdac66..2f160c992b 100644 --- a/api/test/integration/unit-accessibility-priority-type.e2e-spec.ts +++ b/api/test/integration/unit-accessibility-priority-type.e2e-spec.ts @@ -41,7 +41,7 @@ describe('UnitAccessibilityPriorityType Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/unit-rent-type.e2e-spec.ts b/api/test/integration/unit-rent-type.e2e-spec.ts index c5ce82079f..78191b5693 100644 --- a/api/test/integration/unit-rent-type.e2e-spec.ts +++ b/api/test/integration/unit-rent-type.e2e-spec.ts @@ -38,7 +38,7 @@ describe('UnitRentType Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/unit-type.e2e-spec.ts b/api/test/integration/unit-type.e2e-spec.ts index e13d1f199d..374184a3a5 100644 --- a/api/test/integration/unit-type.e2e-spec.ts +++ b/api/test/integration/unit-type.e2e-spec.ts @@ -43,7 +43,7 @@ describe('UnitType Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); diff --git a/api/test/integration/user.e2e-spec.ts b/api/test/integration/user.e2e-spec.ts index 14be93f48b..39c7be979c 100644 --- a/api/test/integration/user.e2e-spec.ts +++ b/api/test/integration/user.e2e-spec.ts @@ -71,7 +71,7 @@ describe('User Controller Tests', () => { .set({ passkey: process.env.API_PASS_KEY || '' }) .send({ email: storedUser.email, - password: 'abcdef', + password: 'Abcdef12345!', } as Login) .expect(201); @@ -576,7 +576,7 @@ describe('User Controller Tests', () => { .send({ firstName: 'Public User firstName', lastName: 'Public User lastName', - password: 'example password 1', + password: 'Abcdef12345!', email: 'publicUser@email.com', jurisdictions: [{ id: juris.id }], } as UserCreate) @@ -602,7 +602,7 @@ describe('User Controller Tests', () => { ); }); - it('should create parter user', async () => { + it('should create partner user', async () => { const juris = await prisma.jurisdictions.create({ data: jurisdictionFactory(), }); @@ -613,7 +613,7 @@ describe('User Controller Tests', () => { .send({ firstName: 'Partner User firstName', lastName: 'Partner User lastName', - password: 'example password 1', + password: 'Abcdef12345!', email: 'partnerUser@email.com', jurisdictions: [{ id: juris.id }], agreedToTermsOfService: true, diff --git a/api/test/unit/passports/mfa.strategy.spec.ts b/api/test/unit/passports/mfa.strategy.spec.ts index 35bad3d0e5..49ce90c6df 100644 --- a/api/test/unit/passports/mfa.strategy.spec.ts +++ b/api/test/unit/passports/mfa.strategy.spec.ts @@ -25,7 +25,7 @@ describe('Testing mfa strategy', () => { const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: 'zyxwv', mfaType: MfaType.sms, } as Login, @@ -59,7 +59,7 @@ describe('Testing mfa strategy', () => { const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: 'zyxwv', mfaType: MfaType.sms, } as Login, @@ -88,13 +88,13 @@ describe('Testing mfa strategy', () => { lastLoginAt: new Date(), failedLoginAttemptsCount: 0, confirmedAt: null, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), }); const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: 'zyxwv', mfaType: MfaType.sms, } as Login, @@ -128,13 +128,13 @@ describe('Testing mfa strategy', () => { passwordValidForDays: 0, passwordUpdatedAt: new Date(0), userRoles: { isAdmin: true }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), }); const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: 'zyxwv', mfaType: MfaType.sms, } as Login, @@ -168,7 +168,7 @@ describe('Testing mfa strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef123'), + passwordHash: await passwordToHash('Abcdef67890!'), }); prisma.userAccounts.update = jest.fn().mockResolvedValue({ id }); @@ -176,7 +176,7 @@ describe('Testing mfa strategy', () => { const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: 'zyxwv', mfaType: MfaType.sms, } as Login, @@ -218,7 +218,7 @@ describe('Testing mfa strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: false, phoneNumberVerified: false, }); @@ -228,7 +228,7 @@ describe('Testing mfa strategy', () => { const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: 'zyxwv', mfaType: MfaType.sms, } as Login, @@ -271,7 +271,7 @@ describe('Testing mfa strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, mfaCodeUpdatedAt: new Date(), @@ -282,7 +282,7 @@ describe('Testing mfa strategy', () => { const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: 'zyxwv', mfaType: MfaType.sms, } as Login, @@ -323,7 +323,7 @@ describe('Testing mfa strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, mfaCode: 'zyxwv', @@ -334,7 +334,7 @@ describe('Testing mfa strategy', () => { const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: 'zyxwv', mfaType: MfaType.sms, } as Login, @@ -375,7 +375,7 @@ describe('Testing mfa strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, mfaCode: 'zyxwv', @@ -387,7 +387,7 @@ describe('Testing mfa strategy', () => { const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaType: MfaType.sms, } as Login, }; @@ -427,7 +427,7 @@ describe('Testing mfa strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, singleUseCode: 'zyxwv', @@ -439,7 +439,7 @@ describe('Testing mfa strategy', () => { const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: 'zyxwv1', mfaType: MfaType.sms, } as Login, @@ -484,7 +484,7 @@ describe('Testing mfa strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, singleUseCode: 'zyxwv', @@ -496,7 +496,7 @@ describe('Testing mfa strategy', () => { const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: 'zyxwv', mfaType: MfaType.sms, } as Login, @@ -541,7 +541,7 @@ describe('Testing mfa strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, singleUseCode: 'zyxwv', @@ -553,7 +553,7 @@ describe('Testing mfa strategy', () => { const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: 'zyxwv', mfaType: MfaType.sms, } as Login, @@ -596,7 +596,7 @@ describe('Testing mfa strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, singleUseCode: 'zyxwv', @@ -608,7 +608,7 @@ describe('Testing mfa strategy', () => { const request = { body: { email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaCode: 'zyxwv', mfaType: MfaType.email, } as Login, diff --git a/api/test/unit/passports/single-use-code.strategy.spec.ts b/api/test/unit/passports/single-use-code.strategy.spec.ts index f230f3b000..7caf3daa7a 100644 --- a/api/test/unit/passports/single-use-code.strategy.spec.ts +++ b/api/test/unit/passports/single-use-code.strategy.spec.ts @@ -121,7 +121,7 @@ describe('Testing single-use-code strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, mfaCodeUpdatedAt: new Date(), @@ -190,7 +190,7 @@ describe('Testing single-use-code strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, mfaCode: 'zyxwv', @@ -259,7 +259,7 @@ describe('Testing single-use-code strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, mfaCode: 'zyxwv', @@ -328,7 +328,7 @@ describe('Testing single-use-code strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, singleUseCode: 'zyxwv', @@ -401,7 +401,7 @@ describe('Testing single-use-code strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, singleUseCode: 'zyxwv', @@ -474,7 +474,7 @@ describe('Testing single-use-code strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, singleUseCode: 'zyxwv', @@ -525,7 +525,7 @@ describe('Testing single-use-code strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, singleUseCode: 'zyxwv', @@ -579,7 +579,7 @@ describe('Testing single-use-code strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, singleUseCode: 'zyxwv', @@ -620,7 +620,7 @@ describe('Testing single-use-code strategy', () => { passwordValidForDays: 100, passwordUpdatedAt: new Date(), userRoles: { isAdmin: false }, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), mfaEnabled: true, phoneNumberVerified: false, singleUseCode: 'zyxwv', diff --git a/api/test/unit/services/auth.service.spec.ts b/api/test/unit/services/auth.service.spec.ts index 02b2fb641d..bc38c17617 100644 --- a/api/test/unit/services/auth.service.spec.ts +++ b/api/test/unit/services/auth.service.spec.ts @@ -457,7 +457,7 @@ describe('Testing auth service', () => { prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({ id: id, mfaEnabled: true, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), email: 'example@exygy.com', phoneNumberVerified: false, }); @@ -467,7 +467,7 @@ describe('Testing auth service', () => { const res = await authService.requestMfaCode({ email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaType: MfaType.email, }); @@ -502,7 +502,7 @@ describe('Testing auth service', () => { prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({ id: id, mfaEnabled: true, - passwordHash: await passwordToHash('abcdef'), + passwordHash: await passwordToHash('Abcdef12345!'), email: 'example@exygy.com', phoneNumberVerified: false, phoneNumber: '520-781-8711', @@ -516,7 +516,7 @@ describe('Testing auth service', () => { const res = await authService.requestMfaCode({ email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaType: MfaType.sms, }); @@ -557,7 +557,7 @@ describe('Testing auth service', () => { prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({ id: id, mfaEnabled: false, - passwordHash: await hashPassword('abcdef', generateSalt()), + passwordHash: await hashPassword('Abcdef12345!', generateSalt()), email: 'example@exygy.com', phoneNumberVerified: false, phoneNumber: '520-781-8711', @@ -570,7 +570,7 @@ describe('Testing auth service', () => { async () => await await authService.requestMfaCode({ email: 'example@exygy.com', - password: 'abcdef', + password: 'Abcdef12345!', mfaType: MfaType.sms, }), ).rejects.toThrowError( @@ -585,7 +585,7 @@ describe('Testing auth service', () => { prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({ id: id, mfaEnabled: true, - passwordHash: await hashPassword('abcdef', generateSalt()), + passwordHash: await hashPassword('Abcdef12345!', generateSalt()), email: 'example@exygy.com', phoneNumberVerified: false, phoneNumber: '520-781-8711', @@ -624,8 +624,8 @@ describe('Testing auth service', () => { await authService.updatePassword( { - password: 'abcdef', - passwordConfirmation: 'abcdef', + password: 'Abcdef12345!', + passwordConfirmation: 'Abcdef12345!', token, }, response as unknown as Response, @@ -697,8 +697,8 @@ describe('Testing auth service', () => { async () => await authService.updatePassword( { - password: 'abcdef', - passwordConfirmation: 'abcdef', + password: 'Abcdef12345!', + passwordConfirmation: 'Abcdef12345!', token, }, response as unknown as Response, @@ -792,7 +792,7 @@ describe('Testing auth service', () => { await authService.confirmUser( { token, - password: 'abcdef', + password: 'Abcdef12345!', }, response, ); diff --git a/api/test/unit/services/user.service.spec.ts b/api/test/unit/services/user.service.spec.ts index f575a110fe..f19199236e 100644 --- a/api/test/unit/services/user.service.spec.ts +++ b/api/test/unit/services/user.service.spec.ts @@ -1423,7 +1423,7 @@ describe('Testing user service', () => { { firstName: 'Partner User firstName', lastName: 'Partner User lastName', - password: 'example password 1', + password: 'Abcdef12345!', email: 'partnerUser@email.com', jurisdictions: [{ id: jurisId }], userRoles: { @@ -1497,7 +1497,7 @@ describe('Testing user service', () => { { firstName: 'Partner User firstName', lastName: 'Partner User lastName', - password: 'example password 1', + password: 'Abcdef12345!', email: 'partnerUser@email.com', jurisdictions: [{ id: jurisId }], userRoles: { @@ -1580,7 +1580,7 @@ describe('Testing user service', () => { { firstName: 'Partner User firstName', lastName: 'Partner User lastName', - password: 'example password 1', + password: 'Abcdef12345!', email: 'partnerUser@email.com', jurisdictions: [{ id: jurisId }], userRoles: { @@ -1648,7 +1648,7 @@ describe('Testing user service', () => { { firstName: 'public User firstName', lastName: 'public User lastName', - password: 'example password 1', + password: 'Abcdef12345!', email: 'publicUser@email.com', jurisdictions: [{ id: jurisId }], }, diff --git a/api/test/unit/utilities/password-helper.spec.ts b/api/test/unit/utilities/password-helper.spec.ts index 4e2dd228d2..a8a9cdc38c 100644 --- a/api/test/unit/utilities/password-helper.spec.ts +++ b/api/test/unit/utilities/password-helper.spec.ts @@ -10,14 +10,14 @@ describe('Testing password helpers', () => { }); it('should create a hash from password and verify that the password is valid', async () => { - const hash = await passwordToHash('abcdef123'); - const isValid = await isPasswordValid(hash, 'abcdef123'); + const hash = await passwordToHash('Abcdef12345!'); + const isValid = await isPasswordValid(hash, 'Abcdef12345!'); expect(isValid).toBe(true); }); it('should return false when incorrect password is provided', async () => { - const hash = await passwordToHash('abcdef123'); - const isValid = await isPasswordValid(hash, 'abcdef'); + const hash = await passwordToHash('Abcdef12345!'); + const isValid = await isPasswordValid(hash, 'Abcdef67890!'); expect(isValid).toBe(false); }); }); diff --git a/api/test/unit/utilities/password-regex.spec.ts b/api/test/unit/utilities/password-regex.spec.ts index 195f24fb5d..e957a64e6f 100644 --- a/api/test/unit/utilities/password-regex.spec.ts +++ b/api/test/unit/utilities/password-regex.spec.ts @@ -1,9 +1,28 @@ import { passwordRegex } from '../../../src/utilities/password-regex'; -describe('Testing password regex', () => { - it('should not match for weak password', () => { +describe('passwordRegex', () => { + it('should identify weak passwords', () => { + // too short, missing all expect(passwordRegex.test('abcdef')).toBe(false); + // too short, includes all + expect(passwordRegex.test('Abc2@')).toBe(false); + // missing uppercase, number, special + expect(passwordRegex.test('abcdefghijkl')).toBe(false); + // missing uppercase, special + expect(passwordRegex.test('abcdefghijkl123')).toBe(false); + // missing special + expect(passwordRegex.test('Abcdefghijkl123')).toBe(false); + // missing uppercase + expect(passwordRegex.test('abcdefghijkl123&')).toBe(false); + // missing number + expect(passwordRegex.test('Abcdefghijkl$')).toBe(false); + // missing lowercase + expect(passwordRegex.test('ABCDEFGHIJKL^')).toBe(false); }); - it('should match for strong password', () => { - expect(passwordRegex.test('abcdef123')).toBe(true); + it('should identify strong passwords', () => { + expect(passwordRegex.test('Abcdef12345!')).toBe(true); + expect(passwordRegex.test('Abcdefghijkl1!')).toBe(true); + expect(passwordRegex.test('2$Iz3S7]$oME')).toBe(true); + expect(passwordRegex.test('dC135@AY5n+t')).toBe(true); + expect(passwordRegex.test('.;^xJ8d37Na7')).toBe(true); }); }); diff --git a/shared-helpers/index.ts b/shared-helpers/index.ts index ac15c326f0..96b0a368a6 100644 --- a/shared-helpers/index.ts +++ b/shared-helpers/index.ts @@ -1,34 +1,35 @@ export * from "./src/auth/AuthContext" +export * from "./src/auth/catchNetworkError" export * from "./src/auth/ConfigContext" export * from "./src/auth/RequireLogin" export * from "./src/auth/Timeout" export * from "./src/auth/UseRequireLoggedInUser" -export * from "./src/auth/catchNetworkError" export * from "./src/utilities/blankApplication" +export * from "./src/utilities/constants" +export * from "./src/utilities/DateFormat" export * from "./src/utilities/events" export * from "./src/utilities/formKeys" export * from "./src/utilities/getListingRedirectUrl" export * from "./src/utilities/gtm" +export * from "./src/utilities/maskData" +export * from "./src/utilities/MessageContext" export * from "./src/utilities/nextjs" +export * from "./src/utilities/passwordRegex" export * from "./src/utilities/pdfs" export * from "./src/utilities/photos" export * from "./src/utilities/postmarkString" export * from "./src/utilities/stringFormatting" export * from "./src/utilities/token" export * from "./src/utilities/unitTypes" -export * from "./src/utilities/DateFormat" -export * from "./src/utilities/constants" -export * from "./src/utilities/maskData" export * from "./src/views/components/BloomCard" export * from "./src/views/CustomIconMap" -export * from "./src/views/multiselectQuestions" -export * from "./src/views/occupancyFormatting" -export * from "./src/views/summaryTables" export * from "./src/views/forgot-password/FormForgotPassword" export * from "./src/views/layout/ExygyFooter" +export * from "./src/views/multiselectQuestions" +export * from "./src/views/occupancyFormatting" export * from "./src/views/sign-in/FormSignIn" export * from "./src/views/sign-in/FormSignInDefault" export * from "./src/views/sign-in/FormSignInErrorBox" export * from "./src/views/sign-in/FormSignInPwdless" export * from "./src/views/sign-in/ResendConfirmationModal" -export * from "./src/utilities/MessageContext" +export * from "./src/views/summaryTables" diff --git a/shared-helpers/src/locales/es.json b/shared-helpers/src/locales/es.json index e8eb483555..e85ea67503 100644 --- a/shared-helpers/src/locales/es.json +++ b/shared-helpers/src/locales/es.json @@ -363,12 +363,11 @@ "authentication.createAccount.errors.errorSaving": "Parece que algo salió mal. Inténtalo de nuevo. \n\n Comuníquese con el departamento de vivienda de su zona si aún tiene problemas.", "authentication.createAccount.errors.generic": "Algo salió mal al crear su cuenta. Vuelva a intentarlo.\n\n Comuníquese con el departamento de vivienda de su zona si aún tiene problemas.", "authentication.createAccount.errors.passwordMismatch": "las contraseñas no coinciden", - "authentication.createAccount.errors.passwordTooWeak": "La contraseña es demasiado débil. Debe tener al menos 8 caracteres y deberá incluir por lo menos 1 letra y 1 número.", + "authentication.createAccount.errors.passwordTooWeak": "La contraseña es demasiado débil. Debe tener al menos 12 caracteres e incluir al menos una letra minúscula, una letra mayúscula, un número y un carácter especial (#?!@$%^&*-).", "authentication.createAccount.errors.tokenExpired": "Su enlace ha caducado.", "authentication.createAccount.linkExpired": "Su enlace ha caducado", - "authentication.createAccount.mustBe8Chars": "debe tener 8 caracteres", "authentication.createAccount.noAccount": "No tienes una cuenta?", - "authentication.createAccount.passwordInfo": "Debe tener al menos 8 caracteres e incluir al menos 1 letra y al menos un número", + "authentication.createAccount.passwordInfo": "Debe tener al menos 12 caracteres e incluir al menos una letra minúscula, una letra mayúscula, un número y un carácter especial (#?!@$%^&*-).", "authentication.createAccount.password": "Contraseña", "authentication.createAccount.reEnterEmail": "Vuelva a introducir la dirección de correo electrónico", "authentication.createAccount.reEnterPassword": "Reingresa tu contraseña", @@ -377,7 +376,7 @@ "authentication.createAccount.resendTheEmail": "Volver a enviar el email", "authentication.forgotPassword.changePassword": "Cambia la contraseña", "authentication.forgotPassword.errors.emailNotFound": "El correo electrónico no fue encontrado. Por favor, revise que si ha creado una cuenta con nosotros con ese correo electrónico y haya sido confirmado confirmado.", - "authentication.forgotPassword.errors.passwordTooWeak": "La contraseña es demasiado débil. Debe tener al menos 8 caracteres y deberá incluir por lo menos 1 letra y 1 número.", + "authentication.forgotPassword.errors.passwordTooWeak": "La contraseña es demasiado débil. Debe tener al menos 12 caracteres e incluir al menos una letra minúscula, una letra mayúscula, un número y un carácter especial (#?!@$%^&*-).", "authentication.forgotPassword.errors.tokenExpired": "El token de restablecimiento de contraseña caducó. Por favor, solicite uno nuevo.", "authentication.forgotPassword.errors.tokenMissing": "El token no fue encontrado. Por favor, solicite uno nuevo.", "authentication.forgotPassword.sendEmail": "Enviar correo electrónico", diff --git a/shared-helpers/src/locales/general.json b/shared-helpers/src/locales/general.json index fbe99470ad..11c1f1c091 100644 --- a/shared-helpers/src/locales/general.json +++ b/shared-helpers/src/locales/general.json @@ -523,13 +523,12 @@ "authentication.createAccount.errors.errorSaving": "Oops! Looks like something went wrong. Please try again. \n\nContact your housing department if you're still experiencing issues.", "authentication.createAccount.errors.generic": "Something went wrong while creating your account. Please try again.\n\nContact your housing department if you're still experiencing issues.", "authentication.createAccount.errors.passwordMismatch": "The passwords do not match", - "authentication.createAccount.errors.passwordTooWeak": "Password is too weak. Must be at least 8 characters and include at least 1 letter and at least one number.", + "authentication.createAccount.errors.passwordTooWeak": "Password is too weak. Must be at least 12 characters and include at least one lowercase letter, one uppercase letter, one number, and one special character (#?!@$%^&*-).", "authentication.createAccount.errors.tokenExpired": "Your link has expired.", "authentication.createAccount.linkExpired": "Your link has expired", - "authentication.createAccount.mustBe8Chars": "Must be 8 characters", "authentication.createAccount.noAccount": "Don't have an account?", "authentication.createAccount.password": "Password", - "authentication.createAccount.passwordInfo": "Must be at least 8 characters and include at least 1 letter and at least one number.", + "authentication.createAccount.passwordInfo": "Must be at least 12 characters and include at least one lowercase letter, one uppercase letter, one number, and one special character (#?!@$%^&*-).", "authentication.createAccount.reEnterEmail": "Re-enter email address", "authentication.createAccount.reEnterPassword": "Re-enter your password", "authentication.createAccount.resendAnEmailTo": "Resend an email to", @@ -539,7 +538,7 @@ "authentication.forgotPassword.enterNewLoginPassword": "Please enter new login password", "authentication.forgotPassword.changePassword": "Change Password", "authentication.forgotPassword.errors.emailNotFound": "Email not found. Please make sure your email has an account with us and is confirmed.", - "authentication.forgotPassword.errors.passwordTooWeak": "Password is too weak. Must be at least 8 characters and include at least 1 letter and at least one number.", + "authentication.forgotPassword.errors.passwordTooWeak": "Password is too weak. Must be at least 12 characters and include at least one lowercase letter, one uppercase letter, one number, and one special character (#?!@$%^&*-).", "authentication.forgotPassword.errors.tokenExpired": "Reset password token expired. Please request for a new one.", "authentication.forgotPassword.errors.tokenMissing": "Token not found. Please request for a new one.", "authentication.forgotPassword.sendEmail": "Send email", diff --git a/shared-helpers/src/locales/tl.json b/shared-helpers/src/locales/tl.json index f998a3aedf..81e5e22fcb 100644 --- a/shared-helpers/src/locales/tl.json +++ b/shared-helpers/src/locales/tl.json @@ -316,12 +316,11 @@ "authentication.createAccount.errors.errorSaving": "Teka! Parang may mali. Pakisubukan muli. \n\nMakipag-ugnayan sa iyong departamento ng pabahay kung nakakaranas ka pa rin ng mga isyu.", "authentication.createAccount.errors.generic": "Nagkaproblema habang ginagawa ang iyong account. Pakisubukang muli.\n\nMakipag-ugnayan sa iyong departamento ng pabahay kung nakakaranas ka pa rin ng mga isyu.", "authentication.createAccount.errors.passwordMismatch": "Hindi tugma ang mga password.", - "authentication.createAccount.errors.passwordTooWeak": "Masyadong mahina ang password. Dapat ay hindi bababa sa 8 mga character at may kasamang hindi bababa sa 1 titik at hindi bababa sa isang numero.", + "authentication.createAccount.errors.passwordTooWeak": "Masyadong mahina ang password. Dapat ay hindi bababa sa 12 character at may kasamang hindi bababa sa isang maliit na titik, isang malaking titik, isang numero, at isang espesyal na character (#?!@$%^&*-).", "authentication.createAccount.errors.tokenExpired": " Nag-expire na ang iyong link.", "authentication.createAccount.linkExpired": " Nag-expire na ang iyong link", - "authentication.createAccount.mustBe8Chars": "Dapat 8 character", "authentication.createAccount.noAccount": "Wala pang account?", - "authentication.createAccount.passwordInfo": "Dapat may 8 character man lang at kasama ang 1 letra at isang numero man lang.", + "authentication.createAccount.passwordInfo": "Dapat ay hindi bababa sa 12 character at may kasamang hindi bababa sa isang maliit na titik, isang malaking titik, isang numero, at isang espesyal na character (#?!@$%^&*-).", "authentication.createAccount.password": "Password", "authentication.createAccount.reEnterEmail": "Ilagay muli ang email address", "authentication.createAccount.reEnterPassword": "Ilagay muli ang iyong password", @@ -330,7 +329,7 @@ "authentication.createAccount.resendTheEmail": "Ipadalang muli ang email", "authentication.forgotPassword.changePassword": "Palitan ang Password", "authentication.forgotPassword.errors.emailNotFound": "Hindi nahanap ang email. Pakitiyak na ang iyong email ay may account sa amin at nakumpirma.", - "authentication.forgotPassword.errors.passwordTooWeak": "Masyadong mahina ang password. Dapat ay hindi bababa sa 8 mga character at may kasamang hindi bababa sa 1 titik at hindi bababa sa isang numero.", + "authentication.forgotPassword.errors.passwordTooWeak": "Masyadong mahina ang password. Dapat ay hindi bababa sa 12 character at may kasamang hindi bababa sa isang maliit na titik, isang malaking titik, isang numero, at isang espesyal na character (#?!@$%^&*-).", "authentication.forgotPassword.errors.tokenExpired": "Nag-expire na ang token ng pag-reset ng password. Humiling ng bago.", "authentication.forgotPassword.errors.tokenMissing": "Hindi nahanap ang token. Humiling ng bago.", "authentication.forgotPassword.sendEmail": "Magpadala ng email", diff --git a/shared-helpers/src/locales/vi.json b/shared-helpers/src/locales/vi.json index bef713010c..3c01813535 100644 --- a/shared-helpers/src/locales/vi.json +++ b/shared-helpers/src/locales/vi.json @@ -363,12 +363,11 @@ "authentication.createAccount.errors.errorSaving": "Rất tiếc! Có vẻ như đã xảy ra lỗi. Vui lòng thử lại. \n\nLiên hệ với bộ phận nhà ở của quý vị nếu quý vị vẫn gặp sự cố.", "authentication.createAccount.errors.generic": "Đã xảy ra lỗi khi tạo tài khoản của quý vị. Vui lòng thử lại.\n\nLiên hệ với bộ phận nhà ở của quý vị nếu quý vị vẫn gặp sự cố.", "authentication.createAccount.errors.passwordMismatch": "Mật khẩu không khớp", - "authentication.createAccount.errors.passwordTooWeak": "Mật khẩu quá yếu. Phải có ít nhất 8 ký tự và bao gồm ít nhất 1 chữ cái và ít nhất một số.", + "authentication.createAccount.errors.passwordTooWeak": "Mật khẩu quá yếu. Phải có ít nhất 12 ký tự và bao gồm ít nhất một chữ cái viết thường, một chữ cái viết hoa, một số và một ký tự đặc biệt (#?!@$%^&*-).", "authentication.createAccount.errors.tokenExpired": "Liên kết của quý vị đã hết hạn.", "authentication.createAccount.linkExpired": "Liên kết của quý vị đã hết hạn", - "authentication.createAccount.mustBe8Chars": "phải có 8 ký tự", "authentication.createAccount.noAccount": "Không có tài khoản?", - "authentication.createAccount.passwordInfo": "Phải có ít nhất 8 ký tự và bao gồm ít nhất 1 chữ cái và ít nhất một số", + "authentication.createAccount.passwordInfo": "Phải có ít nhất 12 ký tự và bao gồm ít nhất một chữ cái viết thường, một chữ cái viết hoa, một số và một ký tự đặc biệt (#?!@$%^&*-).", "authentication.createAccount.password": "Mật khẩu", "authentication.createAccount.reEnterEmail": "Nhập lại địa chỉ Email", "authentication.createAccount.reEnterPassword": "Nhập lại mật khẩu của bạn", @@ -378,7 +377,7 @@ "authentication.createAccount.resendTheEmail": "Gửi lại Email", "authentication.forgotPassword.changePassword": "Đổi mật khẩu", "authentication.forgotPassword.errors.emailNotFound": "Không tìm thấy email. Vui lòng đảm bảo email của quý vị có tài khoản với chúng tôi và đã được xác nhận.", - "authentication.forgotPassword.errors.passwordTooWeak": "Mật khẩu quá yếu. Phải có ít nhất 8 ký tự và bao gồm ít nhất 1 chữ cái và ít nhất một số.", + "authentication.forgotPassword.errors.passwordTooWeak": "Mật khẩu quá yếu. Phải có ít nhất 12 ký tự và bao gồm ít nhất một chữ cái viết thường, một chữ cái viết hoa, một số và một ký tự đặc biệt (#?!@$%^&*-).", "authentication.forgotPassword.errors.tokenExpired": "Mã thông báo đặt lại mật khẩu đã hết hạn. Vui lòng yêu cầu mã mới.", "authentication.forgotPassword.errors.tokenMissing": "Không tìm thấy mã thông báo. Vui lòng yêu cầu mã mới.", "authentication.forgotPassword.sendEmail": "Gửi email", diff --git a/shared-helpers/src/locales/zh.json b/shared-helpers/src/locales/zh.json index 2bcfc03a44..b3b60bd55e 100644 --- a/shared-helpers/src/locales/zh.json +++ b/shared-helpers/src/locales/zh.json @@ -363,12 +363,11 @@ "authentication.createAccount.errors.errorSaving": "糟糕!似乎發生錯誤。請再試一次。\n\n如果您持續遇到問題,請聯絡您的住房部門。", "authentication.createAccount.errors.generic": "建立您的帳戶時發生錯誤。請再試一次。\n\n如果您持續遇到問題,請聯絡您的住房部門。", "authentication.createAccount.errors.passwordMismatch": "密碼不符", - "authentication.createAccount.errors.passwordTooWeak": "密碼強度太弱。至少必須為 8 個字元,並至少包含 1 個字母及 1 個數字。", + "authentication.createAccount.errors.passwordTooWeak": "密碼強度太弱。必須至少 12 個字符,並且至少包含 1 個小寫字母、1 個大寫字母、1 個數字和 1 個特殊字符 (#?!@$%^&*-)。", "authentication.createAccount.errors.tokenExpired": "您的連結已到期。", "authentication.createAccount.linkExpired": "您的連結已到期", - "authentication.createAccount.mustBe8Chars": "必須是 8 個字符", "authentication.createAccount.noAccount": "沒有賬戶?", - "authentication.createAccount.passwordInfo": "必須至少為 8 個字符,並且至少包含 1 個字母和至少一個數字", + "authentication.createAccount.passwordInfo": "必須至少 12 個字符,並且至少包含 1 個小寫字母、1 個大寫字母、1 個數字和 1 個特殊字符 (#?!@$%^&*-)。", "authentication.createAccount.password": "密碼", "authentication.createAccount.reEnterEmail": "重新輸入電子郵件地址", "authentication.createAccount.reEnterPassword": "重新輸入您的密碼", @@ -377,7 +376,7 @@ "authentication.createAccount.resendTheEmail": "重新發送電子郵件", "authentication.forgotPassword.changePassword": "變更密碼", "authentication.forgotPassword.errors.emailNotFound": "找不到電子郵件。請確保您以此電子郵件向我們註冊帳戶並確認完畢。", - "authentication.forgotPassword.errors.passwordTooWeak": "密碼強度太弱。至少必須為 8 個字元,並至少包含 1 個字母及 1 個數字。", + "authentication.forgotPassword.errors.passwordTooWeak": "密碼強度太弱。必須至少 12 個字符,並且至少包含 1 個小寫字母、1 個大寫字母、1 個數字和 1 個特殊字符 (#?!@$%^&*-)。", "authentication.forgotPassword.errors.tokenExpired": "重設密碼權杖已到期。請要求新的權杖。", "authentication.forgotPassword.errors.tokenMissing": "找不到權杖。請要求新的權杖。", "authentication.forgotPassword.sendEmail": "傳送電子郵件", diff --git a/shared-helpers/src/utilities/passwordRegex.ts b/shared-helpers/src/utilities/passwordRegex.ts new file mode 100644 index 0000000000..fabdec06a5 --- /dev/null +++ b/shared-helpers/src/utilities/passwordRegex.ts @@ -0,0 +1 @@ +export const passwordRegex = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{12,}$/ diff --git a/sites/partners/cypress/e2e/default/02-mfa.spec.ts b/sites/partners/cypress/e2e/default/02-mfa.spec.ts index 89cb91e13a..f30d1514c7 100644 --- a/sites/partners/cypress/e2e/default/02-mfa.spec.ts +++ b/sites/partners/cypress/e2e/default/02-mfa.spec.ts @@ -8,7 +8,7 @@ describe("Log in using MFA Tests", () => { }) cy.visit("/") cy.get("input#email").type("mfauser@bloom.com") - cy.get("input#password").type("abcdef") + cy.get("input#password").type("Abcdef12345!") cy.get("button").contains("Sign In").click() cy.getByID("verify-by-email").click() cy.getByTestId("sign-in-mfa-code-field").type("12345") diff --git a/sites/partners/cypress/fixtures/jurisdictionalAdmin.json b/sites/partners/cypress/fixtures/jurisdictionalAdmin.json index b87bf3d598..8695a87f97 100644 --- a/sites/partners/cypress/fixtures/jurisdictionalAdmin.json +++ b/sites/partners/cypress/fixtures/jurisdictionalAdmin.json @@ -5,6 +5,6 @@ "middleName": "Admin", "lastName": "MFA", "dob": "1980-01-01", - "password": "abcdef", - "passwordConfirmation": "abcdef" + "password": "Abcdef12345!", + "passwordConfirmation": "Abcdef12345!" } diff --git a/sites/partners/cypress/fixtures/jurisdictionalAdminUser.json b/sites/partners/cypress/fixtures/jurisdictionalAdminUser.json index 845b65f4e5..b0ea4cf0a6 100644 --- a/sites/partners/cypress/fixtures/jurisdictionalAdminUser.json +++ b/sites/partners/cypress/fixtures/jurisdictionalAdminUser.json @@ -1,4 +1,4 @@ { "email": "jurisdiction-admin@example.com", - "password": "abcdef" + "password": "Abcdef12345!" } diff --git a/sites/partners/cypress/fixtures/termsUnacceptedUser.json b/sites/partners/cypress/fixtures/termsUnacceptedUser.json index 5b4a8f8627..ea8a753784 100644 --- a/sites/partners/cypress/fixtures/termsUnacceptedUser.json +++ b/sites/partners/cypress/fixtures/termsUnacceptedUser.json @@ -1,4 +1,4 @@ { "email": "unverified@example.com", - "password": "abcdef" + "password": "Abcdef12345!" } diff --git a/sites/partners/src/components/users/FormUserConfirm.tsx b/sites/partners/src/components/users/FormUserConfirm.tsx index e100a8d8c8..3d05081371 100644 --- a/sites/partners/src/components/users/FormUserConfirm.tsx +++ b/sites/partners/src/components/users/FormUserConfirm.tsx @@ -1,17 +1,8 @@ import React, { useRef, useContext, useEffect, useState } from "react" import { useRouter } from "next/router" -import { - t, - FormCard, - Form, - Field, - passwordRegex, - useMutate, - AlertBox, - Modal, -} from "@bloom-housing/ui-components" +import { t, FormCard, Form, Field, useMutate, AlertBox, Modal } from "@bloom-housing/ui-components" import { Button, Icon } from "@bloom-housing/ui-seeds" -import { AuthContext, MessageContext } from "@bloom-housing/shared-helpers" +import { AuthContext, MessageContext, passwordRegex } from "@bloom-housing/shared-helpers" import { useForm } from "react-hook-form" import { ReRequestConfirmation } from "./ReRequestConfirmation" import { SuccessDTO } from "@bloom-housing/shared-helpers/src/types/backend-swagger" @@ -135,7 +126,6 @@ const FormUserConfirm = () => { name="password" label={t("account.settings.newPassword")} note={t("authentication.createAccount.passwordInfo")} - placeholder={t("authentication.createAccount.mustBe8Chars")} validation={{ required: true, minLength: MIN_PASSWORD_LENGTH, @@ -156,7 +146,6 @@ const FormUserConfirm = () => { type="password" name="passwordConfirmation" label={t("account.settings.confirmNewPassword")} - placeholder={t("authentication.createAccount.mustBe8Chars")} validation={{ required: true, validate: (value) => @@ -181,7 +170,9 @@ const FormUserConfirm = () => { type="submit" variant="primary" className={"items-center"} - loadingMessage={(isConfirmLoading || loading) && t("t.formSubmitted")} + loadingMessage={ + (isConfirmLoading || loading || isSubmitting) && t("t.formSubmitted") + } > {t("users.confirmAccount")} diff --git a/sites/public/src/pages/account/edit.tsx b/sites/public/src/pages/account/edit.tsx index 2b8cacbde8..2504bdfd69 100644 --- a/sites/public/src/pages/account/edit.tsx +++ b/sites/public/src/pages/account/edit.tsx @@ -12,7 +12,6 @@ import { t, AlertBox, AlertTypes, - passwordRegex, DOBField, DOBFieldValues, } from "@bloom-housing/ui-components" @@ -24,6 +23,7 @@ import { AuthContext, RequireLogin, BloomCard, + passwordRegex, } from "@bloom-housing/shared-helpers" import { UserStatus } from "../../lib/constants" import FormsLayout from "../../layouts/forms" @@ -44,6 +44,11 @@ const Edit = () => { const [nameAlert, setNameAlert] = useState() const [dobAlert, setDobAlert] = useState() const [emailAlert, setEmailAlert] = useState() + const [nameLoading, setNameLoading] = useState(false) + const [birthdateLoading, setBirthdateLoading] = useState(false) + const [emailLoading, setEmailLoading] = useState(false) + const [passwordLoading, setPasswordLoading] = useState(false) + const MIN_PASSWORD_LENGTH = 8 const password = useRef({}) password.current = watch("password", "") @@ -63,6 +68,7 @@ const Edit = () => { middleName: string lastName: string }) => { + setNameLoading(true) const { firstName, middleName, lastName } = data setNameAlert(null) try { @@ -70,13 +76,16 @@ const Edit = () => { body: { ...profile, firstName, middleName, lastName }, }) setNameAlert({ type: "success", message: `${t("account.settings.alerts.nameSuccess")}` }) + setNameLoading(false) } catch (err) { + setNameLoading(false) setNameAlert({ type: "alert", message: `${t("account.settings.alerts.genericError")}` }) console.warn(err) } } const onBirthdateSubmit = async (data: { dateOfBirth: DOBFieldValues }) => { + setBirthdateLoading(true) const { dateOfBirth } = data setDobAlert(null) try { @@ -89,13 +98,16 @@ const Edit = () => { }, }) setDobAlert({ type: "success", message: `${t("account.settings.alerts.dobSuccess")}` }) + setBirthdateLoading(false) } catch (err) { + setBirthdateLoading(false) setDobAlert({ type: "alert", message: `${t("account.settings.alerts.genericError")}` }) console.warn(err) } } const onEmailSubmit = async (data: { email: string }) => { + setEmailLoading(true) const { email } = data setEmailAlert(null) try { @@ -107,7 +119,9 @@ const Edit = () => { }, }) setEmailAlert({ type: "success", message: `${t("account.settings.alerts.emailSuccess")}` }) + setEmailLoading(false) } catch (err) { + setEmailLoading(false) console.log("err = ", err) setEmailAlert({ type: "alert", message: `${t("account.settings.alerts.genericError")}` }) console.warn(err) @@ -119,14 +133,17 @@ const Edit = () => { passwordConfirmation: string currentPassword: string }) => { + setPasswordLoading(true) const { password, passwordConfirmation, currentPassword } = data setPasswordAlert(null) if (passwordConfirmation === "" || password === "") { setPasswordAlert({ type: "alert", message: `${t("account.settings.alerts.passwordEmpty")}` }) + setPasswordLoading(false) return } if (passwordConfirmation !== password) { setPasswordAlert({ type: "alert", message: `${t("account.settings.alerts.passwordMatch")}` }) + setPasswordLoading(false) return } try { @@ -137,7 +154,9 @@ const Edit = () => { type: "success", message: `${t("account.settings.alerts.passwordSuccess")}`, }) + setPasswordLoading(false) } catch (err) { + setPasswordLoading(false) const { status } = err.response || {} if (status === 401) { setPasswordAlert({ @@ -219,7 +238,12 @@ const Edit = () => { : t("errors.lastNameError") } /> - @@ -255,7 +279,13 @@ const Edit = () => { label={t("application.name.yourDateOfBirth")} />

{t("application.name.dobHelper")}

- @@ -289,7 +319,12 @@ const Edit = () => { register={register} defaultValue={profile ? profile.email : null} /> - @@ -360,7 +395,12 @@ const Edit = () => { register={register} /> - diff --git a/sites/public/src/pages/create-account.tsx b/sites/public/src/pages/create-account.tsx index f57d891be6..8f352ce56c 100644 --- a/sites/public/src/pages/create-account.tsx +++ b/sites/public/src/pages/create-account.tsx @@ -1,22 +1,19 @@ import React, { useEffect, useContext, useRef, useState } from "react" import { useForm } from "react-hook-form" -import { - Field, - Form, - emailRegex, - t, - DOBField, - AlertBox, - Modal, - passwordRegex, -} from "@bloom-housing/ui-components" +import { Field, Form, emailRegex, t, DOBField, AlertBox, Modal } from "@bloom-housing/ui-components" import { Button, Heading } from "@bloom-housing/ui-seeds" import { CardSection } from "@bloom-housing/ui-seeds/src/blocks/Card" import dayjs from "dayjs" import customParseFormat from "dayjs/plugin/customParseFormat" dayjs.extend(customParseFormat) import { useRouter } from "next/router" -import { PageView, pushGtmEvent, AuthContext, BloomCard } from "@bloom-housing/shared-helpers" +import { + PageView, + pushGtmEvent, + AuthContext, + BloomCard, + passwordRegex, +} from "@bloom-housing/shared-helpers" import { UserStatus } from "../lib/constants" import FormsLayout from "../layouts/forms" import accountCardStyles from "./account/account.module.scss" @@ -34,6 +31,7 @@ export default () => { const { register, handleSubmit, errors, watch } = useForm() const [requestError, setRequestError] = useState() const [openModal, setOpenModal] = useState(false) + const [loading, setLoading] = useState(false) const router = useRouter() const language = router.locale const listingId = router.query?.listingId as string @@ -51,6 +49,7 @@ export default () => { }, []) const onSubmit = async (data) => { + setLoading(true) try { const { dob, ...rest } = data const listingIdRedirect = @@ -78,7 +77,9 @@ export default () => { } else { setOpenModal(true) } + setLoading(false) } catch (err) { + setLoading(false) const { status, data } = err.response || {} if (status === 400) { setRequestError(`${t(`authentication.createAccount.errors.${data.message}`)}`) @@ -254,7 +255,11 @@ export default () => { label={t("authentication.createAccount.reEnterPassword")} readerOnly /> - diff --git a/sites/public/src/pages/reset-password.tsx b/sites/public/src/pages/reset-password.tsx index 9a01258293..8bf1ec36a9 100644 --- a/sites/public/src/pages/reset-password.tsx +++ b/sites/public/src/pages/reset-password.tsx @@ -25,6 +25,7 @@ const ResetPassword = () => { // eslint-disable-next-line @typescript-eslint/unbound-method const { register, handleSubmit, errors, watch } = useForm() const [requestError, setRequestError] = useState() + const [loading, setLoading] = useState(false) const passwordValue = useRef({}) passwordValue.current = watch("password", "") @@ -38,12 +39,11 @@ const ResetPassword = () => { }, []) const onSubmit = async (data: { password: string; passwordConfirmation: string }) => { + setLoading(true) const { password, passwordConfirmation } = data try { const user = await resetPassword(token.toString(), password, passwordConfirmation) - addToast(t(`authentication.signIn.success`, { name: user.firstName }), { variant: "success" }) - const redirectUrl = router.query?.redirectUrl as string const listingId = router.query?.listingId as string @@ -51,9 +51,10 @@ const ResetPassword = () => { process.env.showMandatedAccounts && redirectUrl && listingId ? `${redirectUrl}?listingId=${listingId}` : "/account/applications" - + addToast(t(`authentication.signIn.success`, { name: user.firstName }), { variant: "success" }) await router.push(routerRedirectUrl) } catch (err) { + setLoading(false) const { status, data } = err.response || {} if (status === 400) { setRequestError(`${t(`authentication.forgotPassword.errors.${data.message}`)}`) @@ -101,7 +102,11 @@ const ResetPassword = () => { labelClassName={"text__caps-spaced"} /> - diff --git a/sites/public/src/pages/sign-in.tsx b/sites/public/src/pages/sign-in.tsx index e9c2a4f649..7e14b3ffdb 100644 --- a/sites/public/src/pages/sign-in.tsx +++ b/sites/public/src/pages/sign-in.tsx @@ -172,14 +172,14 @@ const SignIn = () => { return ( <> - -
+ +
{signUpCopy && (
)} -
+