Skip to content

Commit

Permalink
chore: Migrate password validators
Browse files Browse the repository at this point in the history
  • Loading branch information
ioanabrooks committed Oct 10, 2023
1 parent 4163705 commit a1cc8e3
Show file tree
Hide file tree
Showing 15 changed files with 334 additions and 175 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Feature: Change Password With Overrides
Background:
Given I'm running the example "ui/components/account-settings/change-password-with-overrides"

@todo-migration @react
@react
Scenario: Customize defsault components
When I type my "email" with status "CONFIRMED"
Then I type my password
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Feature: Change Password
Background:
Given I'm running the example "ui/components/account-settings/change-password"

@todo-migration @react
@react
Scenario: Change password of an authenticated user
When I type my "email" with status "CONFIRMED"
Then I type my password
Expand All @@ -19,7 +19,7 @@ Feature: Change Password
Then I click the "Sign out" button
Then I see "Sign in"

@todo-migration @react
@react
Scenario: Change password with wrong password requirements
When I type my "email" with status "CONFIRMED"
Then I type my password
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Feature: Delete User With Overrides
Given I'm running the example "ui/components/account-settings/delete-user-with-overrides"
Given I intercept '{ "headers": { "X-Amz-Target": "AWSCognitoIdentityProviderService.DeleteUser" } }' with fixture "delete-user"

@todo-migration @react
@react
Scenario: Customize warning view
When I type my "email" with status "CONFIRMED"
Then I type my password
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Feature: Delete User
Background:
Given I'm running the example "ui/components/account-settings/delete-user"

@todo-migration @react
@react
Scenario: Delete an authenticated user
Given I intercept '{ "headers": { "X-Amz-Target": "AWSCognitoIdentityProviderService.DeleteUser" } }' with fixture "delete-user"
When I type my "email" with status "CONFIRMED"
Expand All @@ -18,7 +18,7 @@ Feature: Delete User
Then I click the "Delete" button
Then I see "Sign in"

@todo-migration @react
@react
Scenario: Initiate delete but don't confirm deletion
Given I intercept '{ "headers": { "X-Amz-Target": "AWSCognitoIdentityProviderService.DeleteUser" } }' with fixture "delete-user"
When I type my "email" with status "CONFIRMED"
Expand All @@ -32,7 +32,7 @@ Feature: Delete User
Then I click the "Sign Out" button
Then I see "Sign in"

@todo-migration @react
@react
Scenario: Delete fails due to cognito error
Given I intercept '{ "headers": { "X-Amz-Target": "AWSCognitoIdentityProviderService.DeleteUser" } }' with error fixture "delete-user-failure"
When I type my "email" with status "CONFIRMED"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { ChangePasswordProps, ValidateParams } from './types';
import DEFAULTS from './defaults';
import { defaultChangePasswordDisplayText } from '../utils';

const logger = getLogger('ChangePassword');
const logger = getLogger('AccountSettings');

const getIsDisabled = (
formValues: FormValues,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import DEFAULTS from './defaults';
import { DeleteUserProps, DeleteUserState } from './types';
import { defaultDeleteUserDisplayText } from '../utils';

const logger = getLogger('Auth');
const logger = getLogger('AccountSettings');

function DeleteUser({
components,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import {
render,
screen,
fireEvent,
waitFor,
act,
} from '@testing-library/react';

import * as UIModule from '@aws-amplify/ui';

Expand Down Expand Up @@ -84,15 +90,15 @@ describe('DeleteUser', () => {
const deleteAccountButton = await screen.findByRole('button', {
name: deleteAccountButtonText,
});
await act(async () => {
fireEvent.click(deleteAccountButton);

fireEvent.click(deleteAccountButton);
const confirmDeleteButton = await screen.findByRole('button', {
name: confirmDeleteButtonText,
});

const confirmDeleteButton = await screen.findByRole('button', {
name: confirmDeleteButtonText,
fireEvent.click(confirmDeleteButton);
});

fireEvent.click(confirmDeleteButton);

expect(deleteUserSpy).toBeCalledTimes(1);
});

Expand Down Expand Up @@ -213,13 +219,15 @@ describe('DeleteUser', () => {
name: 'Custom Delete Button',
});

fireEvent.click(deleteAccountButton);
await act(async () => {
fireEvent.click(deleteAccountButton);

const confirmDeleteButton = await screen.findByRole('button', {
name: 'Custom Confirm Button',
});
const confirmDeleteButton = await screen.findByRole('button', {
name: 'Custom Confirm Button',
});

fireEvent.click(confirmDeleteButton);
fireEvent.click(confirmDeleteButton);
});

// submit handling is async, wait for onSuccess to be called
// https://testing-library.com/docs/dom-testing-library/api-async/#waitfor
Expand All @@ -235,15 +243,15 @@ describe('DeleteUser', () => {
const deleteAccountButton = await screen.findByRole('button', {
name: 'Custom Delete Button',
});
await act(async () => {
fireEvent.click(deleteAccountButton);

fireEvent.click(deleteAccountButton);
const confirmDeleteButton = await screen.findByRole('button', {
name: 'Custom Confirm Button',
});

const confirmDeleteButton = await screen.findByRole('button', {
name: 'Custom Confirm Button',
fireEvent.click(confirmDeleteButton);
});

fireEvent.click(confirmDeleteButton);

expect(deleteUserSpy).toBeCalledWith();
expect(deleteUserSpy).toBeCalledTimes(1);
});
Expand All @@ -256,15 +264,15 @@ describe('DeleteUser', () => {
const deleteAccountButton = await screen.findByRole('button', {
name: 'Custom Delete Button',
});
await act(async () => {
fireEvent.click(deleteAccountButton);

fireEvent.click(deleteAccountButton);
const confirmDeleteButton = await screen.findByRole('button', {
name: 'Custom Confirm Button',
});

const confirmDeleteButton = await screen.findByRole('button', {
name: 'Custom Confirm Button',
fireEvent.click(confirmDeleteButton);
});

fireEvent.click(confirmDeleteButton);

await screen.findByText('Mock Error');

expect(await screen.findByText('Custom Error Message')).toBeDefined();
Expand Down
46 changes: 27 additions & 19 deletions packages/ui/src/helpers/accountSettings/__tests__/validator.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Amplify } from 'aws-amplify';
import { Amplify, ResourcesConfig } from 'aws-amplify';

import {
getDefaultPasswordValidators,
Expand All @@ -16,24 +16,32 @@ import {
import { ValidatorOptions } from '../../../types';

const partialAmplifyPasswordConfig = {
aws_cognito_password_protection_settings: {
passwordPolicyCharacters: ['REQUIRES_LOWERCASE', 'REQUIRES_UPPERCASE'],
Auth: {
Cognito: {
passwordFormat: {
requireLowercase: true,
requireUppercase: true,
},
},
},
};
} as ResourcesConfig;

const fullAmplifyPasswordConfig = {
aws_cognito_password_protection_settings: {
passwordPolicyMinLength: 8,
passwordPolicyCharacters: [
'REQUIRES_LOWERCASE',
'REQUIRES_NUMBERS',
'REQUIRES_SYMBOLS',
'REQUIRES_UPPERCASE',
],
Auth: {
Cognito: {
passwordFormat: {
minLength: 8,
requireLowercase: true,
requireUppercase: true,
requireNumbers: true,
requireSpecialCharacters: true,
},
},
},
};
} as ResourcesConfig;

const configureSpy = jest.spyOn(Amplify, 'configure');
const getConfigSpy = jest.spyOn(Amplify, 'getConfig');

describe('getHasMinLength', () => {
beforeEach(() => {
Expand Down Expand Up @@ -124,13 +132,13 @@ describe('getPasswordRequirement', () => {
});

it('returns null if Amplify.configure() is empty', () => {
configureSpy.mockReturnValue({});
configureSpy.mockReturnValue();
const requirements = getPasswordRequirement();
expect(requirements).toBeNull();
});

it('returns expected password requirements if full amplify config', () => {
configureSpy.mockReturnValue(fullAmplifyPasswordConfig);
getConfigSpy.mockReturnValue(fullAmplifyPasswordConfig);
const requirements = getPasswordRequirement();
expect(requirements).toMatchObject({
minLength: 8,
Expand All @@ -142,7 +150,7 @@ describe('getPasswordRequirement', () => {
});

it('returns expected password requirements if partial amplify config', () => {
configureSpy.mockReturnValue(partialAmplifyPasswordConfig);
getConfigSpy.mockReturnValue(partialAmplifyPasswordConfig);
const requirements = getPasswordRequirement();
expect(requirements).toMatchObject({
needsLowerCase: true,
Expand All @@ -159,19 +167,19 @@ describe('getDefaultPasswordValidators', () => {
});

it('returns validators as expected for partial Amplify config ', () => {
configureSpy.mockReturnValue(partialAmplifyPasswordConfig);
getConfigSpy.mockReturnValue(partialAmplifyPasswordConfig);
const validators = getDefaultPasswordValidators();
expect(validators).toMatchSnapshot();
});

it('returns validators as expected for full Amplify config ', () => {
configureSpy.mockReturnValue(fullAmplifyPasswordConfig);
getConfigSpy.mockReturnValue(fullAmplifyPasswordConfig);
const validators = getDefaultPasswordValidators();
expect(validators).toMatchSnapshot();
});

it('returns empty array for empty amplify config', () => {
configureSpy.mockReturnValue({});
getConfigSpy.mockReturnValue({});
const validators = getDefaultPasswordValidators();
expect(validators).toStrictEqual([]);
});
Expand Down
24 changes: 9 additions & 15 deletions packages/ui/src/helpers/accountSettings/validator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Amplify } from 'aws-amplify';
import { Amplify, ResourcesConfig } from 'aws-amplify';
import { hasSpecialChars } from '../authenticator';
import {
ValidatorOptions,
Expand All @@ -10,26 +10,20 @@ import {

// gets password requirement from Amplify.configure data
export const getPasswordRequirement = (): PasswordRequirement | null => {
// need to cast to any because `Amplify.configure()` isn't typed properly
const config = Amplify.getConfig() as Record<string, any>;
const passwordSettings =
config?.aws_cognito_password_protection_settings as PasswordSettings;
const config: ResourcesConfig = Amplify.getConfig();
const passwordSettings = config?.Auth?.Cognito
.passwordFormat as PasswordSettings;

if (!passwordSettings) {
return null;
}

const {
passwordPolicyCharacters: characterPolicy = [],
passwordPolicyMinLength: minLength,
} = passwordSettings;

return {
minLength,
needsLowerCase: characterPolicy.includes('REQUIRES_LOWERCASE'),
needsUpperCase: characterPolicy.includes('REQUIRES_UPPERCASE'),
needsNumber: characterPolicy.includes('REQUIRES_NUMBERS'),
needsSpecialChar: characterPolicy.includes('REQUIRES_SYMBOLS'),
minLength: passwordSettings.minLength,
needsLowerCase: passwordSettings.requireLowercase ?? false,
needsUpperCase: passwordSettings.requireUppercase ?? false,
needsNumber: passwordSettings.requireNumbers ?? false,
needsSpecialChar: passwordSettings.requireSpecialCharacters ?? false,
};
};

Expand Down
7 changes: 6 additions & 1 deletion packages/ui/src/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { ConsoleLogger as Logger } from '@aws-amplify/core/internals/utils';

type LoggerCategory = 'Auth' | 'Geo' | 'Notifications' | 'Storage';
type LoggerCategory =
| 'Auth'
| 'AccountSettings'
| 'Geo'
| 'Notifications'
| 'Storage';

export const getLogger = (category: LoggerCategory) =>
new Logger(`AmplifyUI:${category}`);
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,15 @@ const untouched = { password: false };
const touched = { password: true };

const strictPasswordPolicy: PasswordSettings = {
passwordPolicyCharacters: [
'REQUIRES_LOWERCASE',
'REQUIRES_NUMBERS',
'REQUIRES_UPPERCASE',
'REQUIRES_SYMBOLS',
],
passwordPolicyMinLength: 8,
requireLowercase: true,
requireNumbers: true,
requireUppercase: true,
requireSpecialCharacters: true,
minLength: 8,
};

const lenientPasswordPolicy: PasswordSettings = {
passwordPolicyCharacters: [],
passwordPolicyMinLength: 4,
minLength: 4,
};

describe('validateFormPassword', () => {
Expand All @@ -56,7 +53,7 @@ describe('validateFormPassword', () => {
const passwordSettings: PasswordSettings = {
//@ts-expect-error
passwordPolicyCharacters: ['UNSUPPORTED'],
passwordPolicyMinLength: 8,
minLength: 8,
};
const result = await validateFormPassword(
{ password },
Expand Down Expand Up @@ -155,8 +152,8 @@ describe('validateFormPassword', () => {
async (character) => {
const password = `password${character}`;
const result = await validateFormPassword({ password }, touched, {
passwordPolicyMinLength: 4,
passwordPolicyCharacters: ['REQUIRES_SYMBOLS'],
minLength: 4,
requireSpecialCharacters: true,
});
expect(result).toBeNull();
}
Expand Down
Loading

0 comments on commit a1cc8e3

Please sign in to comment.