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

feat(authenticator): add react native support for email mfa #6344

Open
wants to merge 24 commits into
base: james/feat-email-mfa/react-updates
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2550161
chore: add email mfa env / example route
jjarvisp Jan 27, 2025
1f713fb
feat: adding react support for email mfa
jjarvisp Jan 28, 2025
835511c
chore: update env to gen2 path
jjarvisp Jan 28, 2025
a42f63d
fix: add validation errors to radio group
jjarvisp Jan 29, 2025
8b306c6
chore: initial state is sign in
jjarvisp Jan 31, 2025
82e7b21
feat: enable autoSignIn as state machine service
jjarvisp Jan 29, 2025
9b1dd60
chore: add email mfa examples
jjarvisp Jan 29, 2025
d4b8e56
feat: adding email mfa test specs
jjarvisp Jan 29, 2025
21caf4e
chore: default state is sign in
jjarvisp Jan 31, 2025
3d5123d
chore: update react-core tests
jjarvisp Jan 31, 2025
dd1fb6b
fix: allow labelled radio options
jjarvisp Jan 31, 2025
84552bf
chore: tmp RN type fix
jjarvisp Jan 31, 2025
e8f9747
chore: update component to use text util
jjarvisp Jan 31, 2025
cda3633
test: update react e2e and unit tests
jjarvisp Feb 11, 2025
b3ca471
fix: avoid mutate xstate context
jjarvisp Feb 11, 2025
8811b5c
chore: add missing unit tests
jjarvisp Feb 17, 2025
1734172
fix: avoid mutate xstate context
jjarvisp Feb 11, 2025
982cb7e
chore: add email mfa example for RN
jjarvisp Feb 11, 2025
de69b22
feat: add email mfa screens
jjarvisp Feb 11, 2025
9af4a42
test: add unit tests
jjarvisp Feb 11, 2025
511ef64
test: add e2e test apps
jjarvisp Feb 11, 2025
4439bd6
fix: e2e test selector
jjarvisp Feb 11, 2025
2706ceb
fix: customize sign up service
jjarvisp Feb 11, 2025
b13c415
fix: address PR feedback
jjarvisp Feb 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import awsExports from '@environments/auth/gen2/auth-with-email-mfa/amplify_outputs.json';
export default awsExports;
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Amplify } from 'aws-amplify';

import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';

import awsExports from './aws-exports';
import { AuthContext } from '@aws-amplify/ui';
Amplify.configure(awsExports);

const customServices: AuthContext['services'] = {
handleSignIn: async () => {
return {
isSignedIn: false,
nextStep: {
signInStep: 'CONTINUE_SIGN_IN_WITH_MFA_SELECTION',
allowedMFATypes: ['EMAIL', 'TOTP'],
},
};
},
handleConfirmSignIn: async ({ challengeResponse }) => {
if (challengeResponse === 'EMAIL') {
return {
isSignedIn: false,
nextStep: {
signInStep: 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE',
},
};
}

if (/^\d{6}$/.test(challengeResponse)) {
return {
isSignedIn: true,
nextStep: {
signInStep: 'DONE',
},
};
}
throw new Error('Invalid code or auth state for the user.');
},
getCurrentUser: async () => {
return {
userId: '******************',
username: 'james',
};
},
};

export default function App() {
return (
<Authenticator services={customServices}>
{({ signOut, user }) => (
<main>
<h1>Hello {user.username}</h1>
<button onClick={signOut}>Sign out</button>
</main>
)}
</Authenticator>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import awsExports from '@environments/auth/gen2/auth-with-email-mfa/amplify_outputs.json';
export default awsExports;
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Amplify } from 'aws-amplify';

import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';

import awsExports from './aws-exports';
import { AuthContext, emailRegex } from '@aws-amplify/ui';
Amplify.configure(awsExports);

const customServices: AuthContext['services'] = {
handleSignUp: async () => {
return {
isSignUpComplete: true,
userId: '******************',
nextStep: {
signUpStep: 'COMPLETE_AUTO_SIGN_IN',
},
};
},
handleAutoSignIn: async () => {
return {
isSignedIn: false,
nextStep: {
signInStep: 'CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION',
allowedMFATypes: ['EMAIL', 'TOTP'],
},
};
},
handleSignIn: async () => {
return {
isSignedIn: false,
nextStep: {
signInStep: 'CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION',
allowedMFATypes: ['EMAIL', 'TOTP'],
},
};
},
handleConfirmSignIn: async ({ challengeResponse }) => {
if (challengeResponse === 'EMAIL') {
return {
isSignedIn: false,
nextStep: {
signInStep: 'CONTINUE_SIGN_IN_WITH_EMAIL_SETUP',
},
};
}
if (emailRegex.test(challengeResponse)) {
return {
isSignedIn: false,
nextStep: {
signInStep: 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE',
codeDeliveryDetails: {
destination: 'a***@e***.com',
deliveryMedium: 'EMAIL',
attributeName: 'email',
},
},
};
}
if (/^\d{6}$/.test(challengeResponse)) {
return {
isSignedIn: true,
nextStep: {
signInStep: 'DONE',
},
};
}
throw new Error('Invalid code or auth state for the user.');
},
getCurrentUser: async () => {
return {
userId: '******************',
username: 'james',
};
},
};

export default function App() {
return (
<Authenticator initialState="signUp" services={customServices}>
{({ signOut, user }) => (
<main>
<h1>Hello {user.username}</h1>
<button onClick={signOut}>Sign out</button>
</main>
)}
</Authenticator>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import awsExports from '@environments/auth/gen2/auth-with-email-mfa/amplify_outputs.json';
export default awsExports;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Amplify } from 'aws-amplify';

import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';

import awsExports from './aws-exports';
import { AuthContext } from '@aws-amplify/ui';
Amplify.configure(awsExports);

const customServices: AuthContext['services'] = {
handleSignIn: async () => {
return {
isSignedIn: false,
nextStep: {
signInStep: 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE',
codeDeliveryDetails: {
destination: 'a***@e***.com',
deliveryMedium: 'EMAIL',
attributeName: 'email',
},
},
};
},
handleConfirmSignIn: async ({ challengeResponse }) => {
if (/^\d{6}$/.test(challengeResponse)) {
return {
isSignedIn: true,
nextStep: {
signInStep: 'DONE',
},
};
}
throw new Error('Invalid code or auth state for the user.');
},
getCurrentUser: async () => {
return {
userId: '******************',
username: 'james',
};
},
};

export default function App() {
return (
<Authenticator services={customServices}>
{({ signOut, user }) => (
<main>
<h1>Hello {user.username}</h1>
<button onClick={signOut}>Sign out</button>
</main>
)}
</Authenticator>
);
}
2 changes: 1 addition & 1 deletion examples/react-native/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1 @@
EXAMPLE_APP_NAME='DemoExample|BasicExample|ComponentExample|ComponentSlotsExample|FieldsExample|LabelHiddenExample|SlotsExample|StylesExample|InAppMessaging|ThemingExample|DarkModeExample'
EXAMPLE_APP_NAME='DemoExample|BasicExample|ComponentExample|ComponentSlotsExample|FieldsExample|LabelHiddenExample|SlotsExample|StylesExample|InAppMessaging|ThemingExample|DarkModeExample|EmailMfaExample'
38 changes: 19 additions & 19 deletions examples/react-native/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -501,8 +501,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"

SPEC CHECKSUMS:
AmplifyRTNCore: 7aabcf40316c2f5c853c1ef73139af3b24a5406b
AmplifyRTNWebBrowser: c80e90e76b89ed768370e1a2d2400fe2fd672bfc
AmplifyRTNCore: 61f4fc669a2284d2cb50e8c3d4563fac1e4505bd
AmplifyRTNWebBrowser: c84ed53a38c31aede2a29ddc78184b3ef33b8549
boost: 7dcd2de282d72e344012f7d6564d024930a6a440
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: 9840513ec2766e31fb9e34c2dabb2c4671400fcd
Expand All @@ -511,38 +511,38 @@ SPEC CHECKSUMS:
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
hermes-engine: 2382506846564caf4152c45390dc24f08fce7057
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCT-Folly: 8dc08ca5a393b48b1c523ab6220dfdcc0fe000ad
RCTRequired: 44a3cda52ccac0be738fbf43fef90f3546a48c52
RCTTypeSafety: da7fbf9826fc898ca8b10dc840f2685562039a64
React: defd955b6d6ffb9791bb66dee08d90b87a8e2c0c
React-callinvoker: 39ea57213d56ec9c527d51bd0dfb45cbb12ef447
React-Codegen: 71cbc1bc384f9d19a41e4d00dfd0e7762ec5ef4a
React-Core: 898cb2f7785640e21d381b23fc64a2904628b368
React-CoreModules: 7f71e7054395d61585048061a66d67b58d3d27b7
React-cxxreact: 57fca29dd6995de0ee360980709c4be82d40cda1
React-hermes: 33229fc1867df496665b36b222a82a0f850bcae1
React-jsi: 3a55652789df6ddd777cce9601bf000e18d6b9df
React-jsiexecutor: 9b2a87f674f30da4706af52520e4860974cec149
React-Codegen: a383556237715e2f75fb0678a932bc5ad53995a5
React-Core: d28fd78dc9ba11686213ef1304b876fbe14504b0
React-CoreModules: f1e28e36e71add156b108ff1dd00cfdb5399da68
React-cxxreact: e0f18fd5ccd178950aeaca8e5e71bea4c1854f69
React-hermes: 82799e534d3329c041d7b736ea201e7b95da1112
React-jsi: 4f0c076e37f6c6b9e1ebf5783918a2b3de3697f7
React-jsiexecutor: 173c86f9ab3434c9134ade7294f8be06398b4f0a
React-jsinspector: b3b341764ccda14f3659c00a9bc5b098b334db2b
React-logger: dc96fadd2f7f6bc38efc3cfb5fef876d4e6290a2
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
react-native-launch-arguments: 8e21f656fb7ade515fd974209b06be1b9279c91e
react-native-netinfo: 1a6035d3b9780221d407c277ebfb5722ace00658
react-native-safe-area-context: 238cd8b619e05cb904ccad97ef42e84d1b5ae6ec
React-logger: 9fce62c1d7893429ce7558b9f6b83c5c79f946d1
react-native-get-random-values: 419569b6ed3d15bfb9b6781b2f2e058f8e8d2698
react-native-launch-arguments: c16edb82a61942e0be7c2542b8a0eae4ee501460
react-native-netinfo: 98ba850c436e81a5e811abb5055952db52d5d023
react-native-safe-area-context: 71b3a0d71549684af7f975f12f3bd7039ea54b14
React-perflogger: c944b06edad34f5ecded0f29a6e66290a005d365
React-RCTActionSheet: fa467f37777dacba2c72da4be7ae065da4482d7d
React-RCTAnimation: 0591ee5f9e3d8c864a0937edea2165fe968e7099
React-RCTAppDelegate: 8b7f60103a83ad1670bda690571e73efddba29a0
React-RCTBlob: 082e8612f48b0ec12ca6dc949fb7c310593eff83
React-RCTAppDelegate: 04d2661dee11a68f5fd32c4b5d7ffa0dc0721094
React-RCTBlob: 8f263e84a89652c58899d2444c2a915aa5057feb
React-RCTImage: 6300660ef04d0e8a710ad9ea5d2fb4d9521c200d
React-RCTLinking: 7703ee1b10d3568c143a489ae74adc419c3f3ef3
React-RCTNetwork: 5748c647e09c66b918f81ae15ed7474327f653be
React-RCTSettings: 8c8a84ee363db9cbc287c8b8f2fb782acf7ba414
React-RCTText: d5e53c0741e3e2df91317781e993b5e42bd70448
React-RCTVibration: 052dd488ba95f461a37c992b9e01bd4bcc59a7b6
React-runtimeexecutor: b5abe02558421897cd9f73d4f4b6adb4bc297083
ReactCommon: a1a263d94f02a0dc8442f341d5a11b3d7a9cd44d
RNCAsyncStorage: b90b71f45b8b97be43bc4284e71a6af48ac9f547
ReactCommon: a9f87edf02caaec81cc0873f8261c863db124663
RNCAsyncStorage: 01062b75ce749e3a18091a9ad7749effdf09ea43
Yoga: e29645ec5a66fb00934fad85338742d1c247d4cb

PODFILE CHECKSUM: 86255707601fa0f502375c1c40775dbd535ed624
Expand Down
26 changes: 26 additions & 0 deletions examples/react-native/src/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const SlotsExample = React.lazy(
const StylesExample = React.lazy(
() => import('../features/Authenticator/Styles/Example')
);
const EmailMfaExample = React.lazy(
() => import('../features/Authenticator/EmailMfa/Example')
);

/**
* `InAppMessaging` Example and Demo Apps
Expand Down Expand Up @@ -91,6 +94,21 @@ const ForgotPassword = React.lazy(
const WithAuthenticator = React.lazy(
() => import('../ui/components/authenticator/with-authenticator/Example')
);
const SignInWithEmailMfa = React.lazy(
() => import('../ui/components/authenticator/sign-in-with-email-mfa/Example')
);
const SignInWithEmailMfaSelection = React.lazy(
() =>
import(
'../ui/components/authenticator/sign-in-with-email-mfa-selection/Example'
)
);
const SignInWithEmailMfaSetupSelection = React.lazy(
() =>
import(
'../ui/components/authenticator/sign-in-with-email-mfa-setup-selection/Example'
)
);

export const ExampleComponent = () => {
const appName = getExampleAppName();
Expand Down Expand Up @@ -120,6 +138,8 @@ export const ExampleComponent = () => {
return <ThemingExample />;
case 'DarkModeExample':
return <DarkModeExample />;
case 'EmailMfaExample':
return <EmailMfaExample />;

// Detox-Cucumber e2e tests
// below apps are not meant to be run as example apps, they are part of integration testing in CI
Expand Down Expand Up @@ -148,6 +168,12 @@ export const ExampleComponent = () => {
return <WithAuthenticator />;
case 'ui/components/in-app-messaging/demo':
return <InAppMessaging />;
case 'ui/components/authenticator/sign-in-with-email-mfa':
return <SignInWithEmailMfa />;
case 'ui/components/authenticator/sign-in-with-email-mfa-selection':
return <SignInWithEmailMfaSelection />;
case 'ui/components/authenticator/sign-in-with-email-mfa-setup-selection':
return <SignInWithEmailMfaSetupSelection />;
default:
console.warn(
'EXAMPLE_APP_NAME environment variable not configured correctly, running default example app'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { Button, StyleSheet, View } from 'react-native';

import { Authenticator, useAuthenticator } from '@aws-amplify/ui-react-native';
import { Amplify } from 'aws-amplify';

import awsconfig from './aws-exports';
Amplify.configure(awsconfig);

function SignOutButton() {
const { signOut } = useAuthenticator();
return <Button onPress={signOut} title="Sign Out" />;
}

function App() {
return (
<Authenticator.Provider>
<Authenticator>
<View style={style.container}>
<SignOutButton />
</View>
</Authenticator>
</Authenticator.Provider>
);
}

const style = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
});

export default App;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import awsExports from '@aws-amplify/ui-environments/auth/gen2/auth-with-email-mfa/amplify_outputs.json';
export default awsExports;
Loading