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

chore: Migrate password validators #4529

Merged
merged 10 commits into from
Oct 13, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
withAuthenticator,
} from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
// @todo-migration zero config workaround
import { getAuthenticatorConfig } from '@aws-amplify/ui';

import awsExports from './aws-exports';
Amplify.configure(awsExports);
Expand Down Expand Up @@ -46,4 +48,6 @@ function App({ signOut }) {
);
}

export default withAuthenticator(App);
export default withAuthenticator(App, {
...getAuthenticatorConfig(awsExports),
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
withAuthenticator,
} from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
// @todo-migration zero config workaround
import { getAuthenticatorConfig } from '@aws-amplify/ui';

import awsExports from './aws-exports';
Amplify.configure(awsExports);
Expand Down Expand Up @@ -43,4 +45,6 @@ function App({ signOut }) {
);
}

export default withAuthenticator(App);
export default withAuthenticator(App, {
...getAuthenticatorConfig(awsExports),
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import { Amplify } from 'aws-amplify';
import * as Auth from '@aws-amplify/auth';
// @todo-migration zero config workaround
import { getAuthenticatorConfig } from '@aws-amplify/ui';

import {
Button,
Expand Down Expand Up @@ -72,4 +74,6 @@ function App() {
);
}

export default withAuthenticator(App);
export default withAuthenticator(App, {
...getAuthenticatorConfig(awsExports),
});
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 @@ -18,7 +18,7 @@ Feature: Delete User
Then I click the "Delete" button
Then I see "Sign in"

@todo-migration @react
@todo-migration @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 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('Auth');
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
5 changes: 0 additions & 5 deletions packages/ui/src/helpers/accountSettings/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ export const changePassword = async ({
}: ChangePasswordInput): Promise<void> => {
try {
logger.debug('calling Auth.updatePassword');
/**
* Auth.updatePassword returns `Promise<"SUCCESS">`. We're not interested
* in its resolved string value, so we just return Promise.resolve() on success.
*/
await updatePassword({
oldPassword: currentPassword,
newPassword,
Expand All @@ -34,7 +30,6 @@ export const changePassword = async ({
export const deleteUser = async () => {
try {
logger.debug('calling Auth.deleteUser');
await Promise.resolve();
await deleteAuthUser();
logger.debug('Auth.deleteUser was successful');
return Promise.resolve();
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}`);
Loading
Loading