Skip to content

Commit

Permalink
Merge branch 'main' into type-guards
Browse files Browse the repository at this point in the history
  • Loading branch information
ioanabrooks authored Jan 26, 2023
2 parents d80dc7c + 6140ada commit 4069dba
Show file tree
Hide file tree
Showing 42 changed files with 456 additions and 365 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-maps-speak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@aws-amplify/ui-react': patch
---

fix: Swapped save and cancel buttons.
10 changes: 10 additions & 0 deletions .changeset/bright-worms-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@aws-amplify/ui-react-core": patch
"@aws-amplify/ui-react-native": patch
"@aws-amplify/ui-react": patch
"@aws-amplify/ui": patch
"@aws-amplify/ui-vue": patch
"@aws-amplify/ui-angular": patch
---

fix(authenticator): migrate totpSecretCode generation to state machine
9 changes: 5 additions & 4 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [
"amplify-ui-angular-mono",
"angular-example",
"docs",
"e2e",
"environments",
"angular-example",
"next-example",
"vue-example",
"amplify-ui-angular-mono",
"e2e"
"react-native-example",
"vue-example"
]
}
5 changes: 5 additions & 0 deletions .changeset/purple-hotels-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@aws-amplify/ui-react': patch
---

fix: Updated error text for max file count to be more explicit.
5 changes: 5 additions & 0 deletions .changeset/thirty-kangaroos-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@aws-amplify/ui-react': patch
---

fix: Swap the upload button with the clear all button.
14 changes: 7 additions & 7 deletions examples/react-native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ From the monorepo root run the following commands:
yarn && yarn build
```

1. Install CocoaPod dependencies:
2. Install CocoaPod dependencies:

```bash
yarn react-native-example ios:pod-install
```

1. Build and install the Example App:
3. Build and install the Example App:

```bash
yarn react-native-example ios
Expand All @@ -50,7 +50,7 @@ yarn react-native-example ios
yarn && yarn build
```

1. Build and install the Example App:
2. Build and install the Example App:

```bash
yarn react-native-example android
Expand All @@ -70,19 +70,19 @@ All of the below commands should be run from the monorepo root.
yarn react-native dev
```

1. To optionally develop against `@aws-amplify/ui`:
2. To optionally develop against `@aws-amplify/ui`:

```bash
yarn ui dev
yarn ui build
```

1. Run:
3. Run:

```bash
yarn react-native-example dev
```

1. Open the app on the iOS simulator or Android emulator.
4. Open the app on the iOS simulator or Android emulator.

## Storybook

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ <h3 class="amplify-heading amplify-heading--3">{{ this.headerText }}</h3>
height="228"
/>
<div class="amplify-flex" data-amplify-copy>
<div>{{ secretKey }}</div>
<div>{{ totpSecretCode }}</div>
<div data-amplify-copy-svg (click)="copyText()">
<div data-amplify-copy-tooltip>{{ copyTextLabel }}</div>
<svg
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import { BaseFormFieldsComponent } from '../base-form-fields/base-form-fields.co
import { FormFieldComponent } from '../form-field/form-field.component';
import { ButtonComponent } from '../../../../primitives/button/button.component';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import QRCode from 'qrcode';
import { Auth } from 'aws-amplify';

import { MockComponent } from 'ng-mocks';
import { getTotpCodeURL } from '@aws-amplify/ui';

const mockUser = { username: 'username' };
const mockContext = {
Expand All @@ -18,16 +16,8 @@ const mockContext = {
user: mockUser,
};

const DEFAULT_TOTP_ISSUER = 'AWSCognito';
const SECRET_KEY = 'secretKey';

const setupTOTPSpy = jest.spyOn(Auth, 'setupTOTP');

const toDataURLSpy = jest.spyOn(QRCode, 'toDataURL');

describe('SetupTotpComponent', () => {
let fixture: ComponentFixture<SetupTotpComponent>;
let component: SetupTotpComponent;

beforeEach(async () => {
jest.resetAllMocks();
Expand All @@ -44,6 +34,7 @@ describe('SetupTotpComponent', () => {
submitForm: jest.fn(),
context: jest.fn().mockReturnValue({}),
slotContext: jest.fn().mockReturnValue({}),
totpSecretCode: 'Keep it quiet!',
};

await TestBed.configureTestingModule({
Expand All @@ -61,27 +52,9 @@ describe('SetupTotpComponent', () => {
}).compileComponents();

fixture = TestBed.createComponent(SetupTotpComponent);
component = fixture.componentInstance;
});

it('successfully mounts', () => {
expect(fixture).toBeTruthy();
});

it('validate generateQR Code generates correct code', async () => {
setupTOTPSpy.mockResolvedValue(SECRET_KEY);
const defaultTotpCode = getTotpCodeURL(
DEFAULT_TOTP_ISSUER,
mockUser.username,
SECRET_KEY
);
await fixture.detectChanges();

expect(setupTOTPSpy).toHaveBeenCalledTimes(1);
expect(setupTOTPSpy).toHaveBeenCalledWith(mockUser);

await fixture.detectChanges();

expect(toDataURLSpy).toHaveBeenCalledTimes(1);
expect(toDataURLSpy).toHaveBeenCalledWith(defaultTotpCode);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, HostBinding, OnInit } from '@angular/core';
import QRCode from 'qrcode';
import { Auth, Logger } from 'aws-amplify';
import { Logger } from 'aws-amplify';
import {
FormFieldsArray,
getActorContext,
Expand Down Expand Up @@ -29,7 +29,7 @@ export class SetupTotpComponent implements OnInit {
@HostBinding('attr.data-amplify-authenticator-setup-totp') dataAttr = '';
public headerText = getSetupTOTPText();
public qrCodeSource = '';
public secretKey = '';
public totpSecretCode = '';
public copyTextLabel = getCopyText();

// translated texts
Expand All @@ -48,15 +48,19 @@ export class SetupTotpComponent implements OnInit {
}

async generateQRCode() {
// TODO: This should be handled in core.
const state = this.authenticator.authState;
const actorContext = getActorContext(state) as SignInContext;
const { user, formFields } = actorContext;
const { authState: state, totpSecretCode, user } = this.authenticator;
const { formFields } = getActorContext(state) as SignInContext;
const { totpIssuer = 'AWSCognito', totpUsername = user?.username } =
formFields?.setupTOTP?.QR ?? {};

this.totpSecretCode = totpSecretCode;

try {
this.secretKey = await Auth.setupTOTP(user);
const totpCode = getTotpCodeURL(totpIssuer, totpUsername, this.secretKey);
const totpCode = getTotpCodeURL(
totpIssuer,
totpUsername,
this.totpSecretCode
);

logger.info('totp code was generated:', totpCode);
this.qrCodeSource = await QRCode.toDataURL(totpCode);
Expand All @@ -77,7 +81,7 @@ export class SetupTotpComponent implements OnInit {
}

copyText(): void {
navigator.clipboard.writeText(this.secretKey);
navigator.clipboard.writeText(this.totpSecretCode);
this.copyTextLabel = getCopiedText();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export class AuthenticatorService implements OnDestroy {
return this._facade?.codeDeliveryDetails;
}

public get totpSecretCode() {
return this._facade?.totpSecretCode;
}

/**
* Service facades
*/
Expand Down
54 changes: 27 additions & 27 deletions packages/react-core/src/Authenticator/hooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
LegacyFormFieldOptions,
} from '@aws-amplify/ui';

import { UseAuthenticator } from './useAuthenticator';

export type AuthenticatorRouteComponentKey =
| 'confirmResetPassword'
| 'confirmSignIn'
Expand All @@ -31,8 +33,6 @@ export type AuthenticatorMachineContextKey = keyof AuthenticatorMachineContext;
export type AuthenticatorRouteComponentName =
Capitalize<AuthenticatorRouteComponentKey>;

export type GetTotpSecretCode = () => Promise<string>;

interface HeaderProps {
children?: React.ReactNode;
}
Expand All @@ -42,8 +42,8 @@ interface FooterProps {
}

type FormFieldsProps = {
isPending: AuthenticatorMachineContext['isPending'];
validationErrors?: AuthenticatorMachineContext['validationErrors'];
isPending: UseAuthenticator['isPending'];
validationErrors?: UseAuthenticator['validationErrors'];
};

export type FooterComponent<Props = {}> = React.ComponentType<
Expand All @@ -70,74 +70,74 @@ export interface ComponentSlots<FieldType = {}> {
* Common component prop types used for both RWA and RNA implementations
*/
export type CommonRouteProps = {
error?: AuthenticatorMachineContext['error'];
isPending: AuthenticatorMachineContext['isPending'];
handleBlur: AuthenticatorMachineContext['updateBlur'];
handleChange: AuthenticatorMachineContext['updateForm'];
handleSubmit: AuthenticatorMachineContext['submitForm'];
error?: UseAuthenticator['error'];
isPending: UseAuthenticator['isPending'];
handleBlur: UseAuthenticator['updateBlur'];
handleChange: UseAuthenticator['updateForm'];
handleSubmit: UseAuthenticator['submitForm'];
};

/**
* Base Route component props
*/
export type ConfirmResetPasswordBaseProps<FieldType = {}> = {
resendCode: AuthenticatorMachineContext['resendCode'];
validationErrors?: AuthenticatorMachineContext['validationErrors'];
resendCode: UseAuthenticator['resendCode'];
validationErrors?: UseAuthenticator['validationErrors'];
} & CommonRouteProps &
ComponentSlots<FieldType>;

export type ConfirmSignInBaseProps<FieldType = {}> = {
challengeName: AuthChallengeName;
toSignIn: AuthenticatorMachineContext['toSignIn'];
toSignIn: UseAuthenticator['toSignIn'];
} & CommonRouteProps &
ComponentSlots<FieldType>;

export type ConfirmSignUpBaseProps<FieldType = {}> = {
codeDeliveryDetails: AuthenticatorMachineContext['codeDeliveryDetails'];
resendCode: AuthenticatorMachineContext['resendCode'];
codeDeliveryDetails: UseAuthenticator['codeDeliveryDetails'];
resendCode: UseAuthenticator['resendCode'];
} & CommonRouteProps &
ComponentSlots<FieldType>;

export type ConfirmVerifyUserProps<FieldType = {}> = {
skipVerification: AuthenticatorMachineContext['skipVerification'];
skipVerification: UseAuthenticator['skipVerification'];
} & CommonRouteProps &
ComponentSlots<FieldType>;

export type ForceResetPasswordBaseProps<FieldType = {}> = {
toSignIn: AuthenticatorMachineContext['toSignIn'];
validationErrors?: AuthenticatorMachineContext['validationErrors'];
toSignIn: UseAuthenticator['toSignIn'];
validationErrors?: UseAuthenticator['validationErrors'];
} & CommonRouteProps &
ComponentSlots<FieldType>;

export type ResetPasswordBaseProps<FieldType = {}> = {
toSignIn: AuthenticatorMachineContext['toSignIn'];
toSignIn: UseAuthenticator['toSignIn'];
} & CommonRouteProps &
ComponentSlots<FieldType>;

export type SetupTOTPBaseProps<FieldType = {}> = {
getTotpSecretCode: GetTotpSecretCode;
toSignIn: AuthenticatorMachineContext['toSignIn'];
toSignIn: UseAuthenticator['toSignIn'];
totpSecretCode: UseAuthenticator['totpSecretCode'];
} & CommonRouteProps &
ComponentSlots<FieldType>;

export type SignInBaseProps<FieldType = {}> = {
hideSignUp?: boolean;
toFederatedSignIn: AuthenticatorMachineContext['toFederatedSignIn'];
toResetPassword: AuthenticatorMachineContext['toResetPassword'];
toSignUp: AuthenticatorMachineContext['toSignUp'];
toFederatedSignIn: UseAuthenticator['toFederatedSignIn'];
toResetPassword: UseAuthenticator['toResetPassword'];
toSignUp: UseAuthenticator['toSignUp'];
} & CommonRouteProps &
ComponentSlots<FieldType>;

export type SignUpBaseProps<FieldType = {}> = {
hideSignIn?: boolean;
toFederatedSignIn: AuthenticatorMachineContext['toFederatedSignIn'];
toSignIn: AuthenticatorMachineContext['toSignIn'];
validationErrors?: AuthenticatorMachineContext['validationErrors'];
toFederatedSignIn: UseAuthenticator['toFederatedSignIn'];
toSignIn: UseAuthenticator['toSignIn'];
validationErrors?: UseAuthenticator['validationErrors'];
} & CommonRouteProps &
ComponentSlots<FieldType>;

export type VerifyUserProps<FieldType = {}> = {
skipVerification: AuthenticatorMachineContext['skipVerification'];
skipVerification: UseAuthenticator['skipVerification'];
} & CommonRouteProps &
ComponentSlots<FieldType>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const getTotpSecretCode = jest.fn();
const hasValidationErrors = false;
const initializeMachine = jest.fn();
const isPending = false;
const QRFields = null;
const resendCode = jest.fn();
const route = 'idle';
const skipVerification = jest.fn();
Expand All @@ -24,6 +25,7 @@ const toFederatedSignIn = jest.fn();
const toResetPassword = jest.fn();
const toSignIn = jest.fn();
const toSignUp = jest.fn();
const totpSecretCode = null;
const unverifiedContactMethods = {};
const updateBlur = jest.fn();
const updateForm = jest.fn();
Expand Down Expand Up @@ -53,6 +55,7 @@ export const mockMachineContext: AuthenticatorMachineContext = {
socialProviders,
toFederatedSignIn,
toResetPassword,
totpSecretCode,
unverifiedContactMethods,
validationErrors,
};
Expand All @@ -61,4 +64,5 @@ export const mockUseAuthenticatorOutput: UseAuthenticator = {
...mockMachineContext,
fields,
getTotpSecretCode,
} as unknown as UseAuthenticator;
QRFields,
};
Loading

0 comments on commit 4069dba

Please sign in to comment.