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: add support for email mfa #94

Merged
merged 35 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9faafb5
add support for email mfa code
harsh62 Sep 10, 2024
0b859e0
adding support for mfa setup selection and setting up email
harsh62 Sep 10, 2024
b00f24b
Apply suggestions from code review
harsh62 Sep 13, 2024
c0cf3c1
worked on review comments
harsh62 Sep 17, 2024
b8a60ad
update strings and some logics
harsh62 Sep 26, 2024
7aaee26
missed update
harsh62 Sep 26, 2024
9e3c844
Merge branch 'main' into feat/email-mfa
harsh62 Sep 26, 2024
482fbdd
fix conflicts
harsh62 Sep 26, 2024
b855272
pushing a test.
harsh62 Sep 27, 2024
3b4e8ce
update sign in step to confirmSignInWithOTP
harsh62 Oct 15, 2024
e59b4a1
Update Sources/Authenticator/Views/ConfirmSignInWithOTPView.swift
harsh62 Oct 29, 2024
4fcb712
Update Sources/Authenticator/Views/ConfirmSignInWithOTPView.swift
harsh62 Oct 29, 2024
b864f8c
Update Sources/Authenticator/Views/ConfirmSignInWithOTPView.swift
harsh62 Oct 29, 2024
08fb4e5
worked on review comment
harsh62 Oct 30, 2024
e7d458f
updated to match Android
harsh62 Oct 30, 2024
6ae18a5
fix failing tests
harsh62 Oct 30, 2024
524cc99
add/update UI Tests and snapshot images
harsh62 Oct 30, 2024
43f3ec8
trying out ui tests
harsh62 Oct 30, 2024
d06db62
update
harsh62 Oct 30, 2024
cd6c328
missing added file
harsh62 Oct 30, 2024
4d99f42
adding ui tests for next steps
harsh62 Oct 30, 2024
9727af3
revert the package swift for release
harsh62 Oct 30, 2024
40ffa8e
Update Sources/Authenticator/Views/Internal/ConfirmSignInWithCodeView…
ruisebas Oct 30, 2024
9f540ad
Update Sources/Authenticator/Views/Internal/ConfirmSignInWithCodeView…
ruisebas Oct 30, 2024
b4b4668
Update Sources/Authenticator/Views/Internal/ConfirmSignInWithCodeView…
ruisebas Oct 30, 2024
c3bfde4
Update Sources/Authenticator/Views/Internal/ConfirmSignInWithCodeView…
ruisebas Oct 30, 2024
a6e90ed
update workflow to get the the latest package dependencies
harsh62 Oct 31, 2024
b992c8f
Merge branch 'feat/email-mfa' of github.com:aws-amplify/amplify-ui-sw…
harsh62 Oct 31, 2024
8e150ff
update workflow
harsh62 Oct 31, 2024
6edc916
another update
harsh62 Oct 31, 2024
21d8aff
update
harsh62 Oct 31, 2024
3a8f206
update
harsh62 Oct 31, 2024
7306bb1
update
harsh62 Oct 31, 2024
1286bad
update package.resolved to remove email mfa support branch reference
harsh62 Oct 31, 2024
6880d18
update amplify version dependency
harsh62 Oct 31, 2024
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
31 changes: 29 additions & 2 deletions Sources/Authenticator/Authenticator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public struct Authenticator<LoadingContent: View,
ConfirmSignInWithMFACodeContent: View,
ConfirmSignInWithTOTPCodeContent: View,
ContinueSignInWithMFASelectionContent: View,
ContinueSignInWithMFASetupSelectionContent: View,
ContinueSignInWithEmailMFASetupContent: View,
ContinueSignInWithTOTPSetupContent: View,
ConfirmSignInWithCustomChallengeContent: View,
SignUpContent: View,
Expand All @@ -40,7 +42,9 @@ public struct Authenticator<LoadingContent: View,
private let confirmSignInContentWithMFACodeContent: ConfirmSignInWithMFACodeContent
private let confirmSignInWithTOTPCodeContent: (ConfirmSignInWithCodeState) -> ConfirmSignInWithTOTPCodeContent
private let continueSignInWithMFASelectionContent: (ContinueSignInWithMFASelectionState) -> ContinueSignInWithMFASelectionContent
private let continueSignInWithMFASetupSelectionContent: (ContinueSignInWithMFASetupSelectionState) -> ContinueSignInWithMFASetupSelectionContent
private let continueSignInWithTOTPSetupContent: (ContinueSignInWithTOTPSetupState) -> ContinueSignInWithTOTPSetupContent
private let continueSignInWithEmailMFASetupContent: (ContinueSignInWithEmailMFASetupState) -> ContinueSignInWithEmailMFASetupContent
private let confirmSignInContentWithCustomChallengeContent: ConfirmSignInWithCustomChallengeContent
private let confirmSignInContentWithNewPasswordContent: ConfirmSignInWithNewPasswordContent
private let signUpContent: SignUpContent
Expand Down Expand Up @@ -69,8 +73,12 @@ public struct Authenticator<LoadingContent: View,
/// Defaults to a ``ConfirmSignInWithMFACodeView``.
///- Parameter continueSignInWithMFASelectionContent: The content associated with the ``AuthenticatorStep/continueSignInWithMFASelection`` step.
/// Defaults to a ``ContinueSignInWithMFASelectionView``.
///- Parameter continueSignInWithMFASetupSelectionContent: The content associated with the ``AuthenticatorStep/continueSignInWithMFASetupSelection`` step.
/// Defaults to a ``ContinueSignInWithMFASetupSelectionView``.
///- Parameter continueSignInWithTOTPSetupContent: The content associated with the ``AuthenticatorStep/continueSignInWithTOTPSetup`` step.
/// Defaults to a ``ContinueSignInWithTOTPSetupView``.
///- Parameter continueSignInWithEmailMFASetupContent: The content associated with the ``AuthenticatorStep/continueSignInWithEmailMFASetup`` step.
/// Defaults to a ``ContinueSignInWithEmailMFASetupView``.
/// - Parameter confirmSignInWithCustomChallengeContent: The content associated with the ``AuthenticatorStep/confirmSignInWithCustomChallenge`` step.
/// Defaults to a ``ConfirmSignInWithCustomChallengeView``.
/// - Parameter confirmSignInWithNewPasswordContent: The content associated with the ``AuthenticatorStep/confirmSignInWithNewPassword`` step.
Expand Down Expand Up @@ -112,9 +120,15 @@ public struct Authenticator<LoadingContent: View,
@ViewBuilder continueSignInWithMFASelectionContent: @escaping (ContinueSignInWithMFASelectionState) -> ContinueSignInWithMFASelectionContent = { state in
ContinueSignInWithMFASelectionView(state: state)
},
@ViewBuilder continueSignInWithMFASetupSelectionContent: @escaping (ContinueSignInWithMFASetupSelectionState) -> ContinueSignInWithMFASetupSelectionContent = { state in
ContinueSignInWithMFASetupSelectionView(state: state)
},
@ViewBuilder continueSignInWithTOTPSetupContent: @escaping (ContinueSignInWithTOTPSetupState) -> ContinueSignInWithTOTPSetupContent = { state in
ContinueSignInWithTOTPSetupView(state: state)
},
@ViewBuilder continueSignInWithEmailMFASetupContent: @escaping (ContinueSignInWithEmailMFASetupState) -> ContinueSignInWithEmailMFASetupContent = { state in
ContinueSignInWithEmailMFASetupView(state: state)
},
@ViewBuilder confirmSignInWithCustomChallengeContent: (ConfirmSignInWithCodeState) -> ConfirmSignInWithCustomChallengeContent = { state in
ConfirmSignInWithCustomChallengeView(state: state)
},
Expand Down Expand Up @@ -163,7 +177,9 @@ public struct Authenticator<LoadingContent: View,

self.confirmSignInWithTOTPCodeContent = confirmSignInWithTOTPCodeContent
self.continueSignInWithMFASelectionContent = continueSignInWithMFASelectionContent
self.continueSignInWithMFASetupSelectionContent = continueSignInWithMFASetupSelectionContent
self.continueSignInWithTOTPSetupContent = continueSignInWithTOTPSetupContent
self.continueSignInWithEmailMFASetupContent = continueSignInWithEmailMFASetupContent

let confirmSignInWithCustomChallengeState = ConfirmSignInWithCodeState(credentials: credentials)
contentStates.add(confirmSignInWithMFACodeState)
Expand Down Expand Up @@ -344,18 +360,29 @@ public struct Authenticator<LoadingContent: View,
allowedMFATypes: allowedMFATypes
)
continueSignInWithMFASelectionContent(continueSignInWithMFASelection)
case .continueSignInWithMFASetupSelection(let allowedMFATypes):
let continueSignInWithMFASetupSelection = ContinueSignInWithMFASetupSelectionState(
authenticatorState: state,
allowedMFATypes: allowedMFATypes
)
continueSignInWithMFASetupSelectionContent(continueSignInWithMFASetupSelection)
case .confirmSignInWithTOTPCode:
let confirmSignInWithCodeState = ConfirmSignInWithCodeState(
authenticatorState: state
)
confirmSignInWithTOTPCodeContent(confirmSignInWithCodeState)
case .continueSignInWithTOTPSetup(let totpSetupDetails):
let totpStupState = ContinueSignInWithTOTPSetupState(
let totpSetupState = ContinueSignInWithTOTPSetupState(
authenticatorState: state,
issuer: totpOptions.issuer,
totpSetupDetails: totpSetupDetails
)
continueSignInWithTOTPSetupContent(totpStupState)
continueSignInWithTOTPSetupContent(totpSetupState)
case .continueSignInWithEmailMFASetup:
let emailMFASetupState = ContinueSignInWithEmailMFASetupState(
authenticatorState: state
)
continueSignInWithEmailMFASetupContent(emailMFASetupState)
case .confirmSignInWithCustomChallenge:
confirmSignInContentWithCustomChallengeContent
case .resetPassword:
Expand Down
8 changes: 8 additions & 0 deletions Sources/Authenticator/Models/AuthenticatorStep.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ public struct AuthenticatorStep: Equatable {
/// so they are presented with the Confirm Sign In with MFA Selection View
public static let continueSignInWithMFASelection = AuthenticatorStep("continueSignInWithMFASelection")

/// A user has successfully provided valid Sign In credentials but is required to select a MFA type to continue
harsh62 marked this conversation as resolved.
Show resolved Hide resolved
/// so they are presented with the Confirm Sign In with MFA Selection View
public static let continueSignInWithMFASetupSelection = AuthenticatorStep("continueSignInWithMFASetupSelection")

/// A user has successfully provided valid Sign In credentials but is required to setup TOTP before continuing sign in
/// so they are presented with the TOTP Setup View
public static let continueSignInWithEmailMFASetup = AuthenticatorStep("continueSignInWithEmailMFASetup")

/// A user has successfully provided valid Sign In credentials but is required to provide a MFA code,
/// so they are presented with the Confirm Sign In with MFA Code view
public static let confirmSignInWithMFACode = AuthenticatorStep("confirmSignInWithMFACode")
Expand Down
6 changes: 6 additions & 0 deletions Sources/Authenticator/Models/Internal/Step.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ enum Step {
case confirmSignInWithCustomChallenge
case confirmSignInWithTOTPCode
case continueSignInWithMFASelection(allowedMFATypes: AllowedMFATypes)
case continueSignInWithMFASetupSelection(allowedMFATypes: AllowedMFATypes)
case continueSignInWithEmailMFASetup
case continueSignInWithTOTPSetup(totpSetupDetails: TOTPSetupDetails)
case confirmSignInWithMFACode(deliveryDetails: AuthCodeDeliveryDetails?)
case confirmSignInWithNewPassword
Expand Down Expand Up @@ -55,6 +57,10 @@ enum Step {
return .continueSignInWithTOTPSetup
case .continueSignInWithMFASelection:
return .continueSignInWithMFASelection
case .continueSignInWithMFASetupSelection:
return .continueSignInWithMFASetupSelection
case .continueSignInWithEmailMFASetup:
return .continueSignInWithEmailMFASetup
case .confirmSignInWithMFACode:
return .confirmSignInWithMFACode
case .confirmSignInWithNewPassword:
Expand Down
13 changes: 13 additions & 0 deletions Sources/Authenticator/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,23 @@
"authenticator.confirmSignInWithCode.totp.button.submit" = "Confirm";
"authenticator.confirmSignInWithCode.totp.button.backToSignIn" = "Back to Sign In";

/* Continue Sign In with MFA Setup Selection */
"authenticator.continueSignInWithMFASetupSelection.email.radioButton.title" = "Email";
"authenticator.continueSignInWithMFASetupSelection.totp.radioButton.title" = "Authenticator App (TOTP)";
"authenticator.continueSignInWithMFASetupSelection.button.continue" = "Continue";
"authenticator.continueSignInWithMFASetupSelection.title" = "Select your preferred Two-Factor Auth method to set up";
"authenticator.continueSignInWithMFASetupSelection.button.backToSignIn" = "Back to Sign In";

/* Continue Sign In with Email MFA Setup */
"authenticator.continueSignInWithEmailMFASetup.title" = "Enter your email for setting up Two-Factor Auth method";
"authenticator.continueSignInWithEmailMFASetup.button.continue" = "Continue";
"authenticator.continueSignInWithEmailMFASetup.button.backToSignIn" = "Back to Sign In";

/* Continue Sign In with MFA Selection */
"authenticator.continueSignInWithMFASelection.title" = "Select your preferred Two-Factor Auth method";
"authenticator.continueSignInWithMFASelection.sms.radioButton.title" = "Text Message (SMS)";
"authenticator.continueSignInWithMFASelection.totp.radioButton.title" = "Authenticator App (TOTP)";
"authenticator.continueSignInWithMFASelection.email.radioButton.title" = "Email";
"authenticator.continueSignInWithMFASelection.button.submit" = "Continue";
"authenticator.continueSignInWithMFASelection.button.backToSignIn" = "Back to Sign In";

Expand Down
7 changes: 6 additions & 1 deletion Sources/Authenticator/States/AuthenticatorBaseState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public class AuthenticatorBaseState: ObservableObject {
func nextStep(for result: AuthSignInResult) async throws -> Step {
log.verbose("Sign In next step is \(result.nextStep)")
switch result.nextStep {
case .confirmSignInWithSMSMFACode(let details, _):
case .confirmSignInWithSMSMFACode(let details, _),
.confirmSignInWithEmailMFACode(let details):
harsh62 marked this conversation as resolved.
Show resolved Hide resolved
return .confirmSignInWithMFACode(deliveryDetails: details)
case .confirmSignInWithCustomChallenge(_):
return .confirmSignInWithCustomChallenge
Expand Down Expand Up @@ -116,6 +117,10 @@ public class AuthenticatorBaseState: ObservableObject {
return .continueSignInWithMFASelection(allowedMFATypes: allowedMFATypes)
case .continueSignInWithTOTPSetup(let totpSetupDetails):
return .continueSignInWithTOTPSetup(totpSetupDetails: totpSetupDetails)
case .continueSignInWithMFASetupSelection(let allowedMFATypes):
return .continueSignInWithMFASetupSelection(allowedMFATypes: allowedMFATypes)
case .continueSignInWithEmailMFASetup:
return .continueSignInWithEmailMFASetup
default:
throw AuthError.unknown("Unsupported next step: \(result.nextStep)", nil)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
harsh62 marked this conversation as resolved.
Show resolved Hide resolved
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Amplify
import AWSCognitoAuthPlugin
import SwiftUI

/// The state observed by the Continue Sign In with Email MFA Setup Challenge, representing the ``Authenticator`` is in either the ``AuthenticatorStep/continueSignInWithEmailMFASetup``
harsh62 marked this conversation as resolved.
Show resolved Hide resolved
public class ContinueSignInWithEmailMFASetupState: AuthenticatorBaseState {
/// The confirmation code provided by the user
harsh62 marked this conversation as resolved.
Show resolved Hide resolved
@Published public var email: String = ""

override init(credentials: Credentials) {
super.init(credentials: credentials)
}

init(authenticatorState: AuthenticatorStateProtocol) {
super.init(authenticatorState: authenticatorState,
credentials: Credentials())
}

/// Attempts to continue user's sign by providing email.
///
/// Automatically sets the Authenticator's next step accordingly, as well as the
/// ``AuthenticatorBaseState/isBusy`` and ``AuthenticatorBaseState/message`` properties.
/// - Throws: An `Amplify.AuthenticationError` if the operation fails
public func continueSignIn() async throws {
setBusy(true)

do {
log.verbose("Attempting to continue Sign In with Email setup")
let result = try await authenticationService.confirmSignIn(
challengeResponse: email,
options: nil
)
let nextStep = try await nextStep(for: result)

setBusy(false)

authenticatorState.setCurrentStep(nextStep)
} catch {
log.error("Continue Sign In with Email MFA Setup failed")
let authenticationError = self.error(for: error)
setMessage(authenticationError)
throw authenticationError
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
harsh62 marked this conversation as resolved.
Show resolved Hide resolved
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Amplify
import AWSCognitoAuthPlugin
import SwiftUI

/// The state observed by the Continue Sign In With MFA Setup Selection content views, representing the ``Authenticator`` is in ``AuthenticatorStep/continueSignInWithMFASetupSelection`` step.
harsh62 marked this conversation as resolved.
Show resolved Hide resolved
public class ContinueSignInWithMFASetupSelectionState: AuthenticatorBaseState {

/// The MFA type selected by the user
@Published public var selectedMFATypeToSetup: MFAType?

init(authenticatorState: AuthenticatorStateProtocol,
allowedMFATypes: AllowedMFATypes) {
self.allowedMFATypes = allowedMFATypes
super.init(authenticatorState: authenticatorState,
credentials: Credentials())
}

/// The `Amplify.AllowedMFATypes` associated with this state.
public let allowedMFATypes: AllowedMFATypes

/// Attempts to continue the user's sign in using the provided MFA type to setup.
///
/// Automatically sets the Authenticator's next step accordingly, as well as the
/// ``AuthenticatorBaseState/isBusy`` and ``AuthenticatorBaseState/message`` properties.
/// - Throws: An `Amplify.AuthenticationError` if the operation fails
public func continueSignIn() async throws {
guard let selectedMFATypeToSetup = selectedMFATypeToSetup else {
log.error("MFA type not selected")
return
}

setBusy(true)
do {
log.verbose("Attempting to continue Sign In with selected MFA type to setup")
let result = try await authenticationService.confirmSignIn(
challengeResponse: selectedMFATypeToSetup.challengeResponse,
options: nil
)
let nextStep = try await nextStep(for: result)

setBusy(false)

authenticatorState.setCurrentStep(nextStep)
} catch {
log.error("Continue Sign In with MFA Setup Selection failed")
let authenticationError = self.error(for: error)
setMessage(authenticationError)
throw authenticationError
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,3 @@ public struct ConfirmSignInWithMFACodeFooter: View {
.buttonStyle(.link)
}
}

Loading
Loading