Skip to content

Commit

Permalink
update sign in step to confirmSignInWithOTP
Browse files Browse the repository at this point in the history
  • Loading branch information
harsh62 committed Oct 15, 2024
1 parent b855272 commit 3b4e8ce
Show file tree
Hide file tree
Showing 14 changed files with 225 additions and 29 deletions.
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"location" : "https://github.com/aws-amplify/amplify-swift",
"state" : {
"branch" : "feat/email-mfa-support-public",
"revision" : "fedf82a4b8f118f83decbdd321367fc98e9dbb41"
"revision" : "f332d126512fd9e28cbe07f4b3fdb36ab9cff4cb"
}
},
{
Expand Down
20 changes: 16 additions & 4 deletions Sources/Authenticator/Authenticator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public struct Authenticator<LoadingContent: View,
SignInContent: View,
ConfirmSignInWithNewPasswordContent: View,
ConfirmSignInWithMFACodeContent: View,
ConfirmSignInWithOTPContent: View,
ConfirmSignInWithTOTPCodeContent: View,
ContinueSignInWithMFASelectionContent: View,
ContinueSignInWithMFASetupSelectionContent: View,
Expand All @@ -39,7 +40,8 @@ public struct Authenticator<LoadingContent: View,
private var contentStates: NSHashTable<AuthenticatorBaseState> = .weakObjects()
private let loadingContent: LoadingContent
private let signInContent: SignInContent
private let confirmSignInContentWithMFACodeContent: ConfirmSignInWithMFACodeContent
private let confirmSignInWithMFACodeContent: ConfirmSignInWithMFACodeContent
private let confirmSignInWithOTPContent: (ConfirmSignInWithCodeState) -> ConfirmSignInWithOTPContent
private let confirmSignInWithTOTPCodeContent: (ConfirmSignInWithCodeState) -> ConfirmSignInWithTOTPCodeContent
private let continueSignInWithMFASelectionContent: (ContinueSignInWithMFASelectionState) -> ContinueSignInWithMFASelectionContent
private let continueSignInWithMFASetupSelectionContent: (ContinueSignInWithMFASetupSelectionState) -> ContinueSignInWithMFASetupSelectionContent
Expand Down Expand Up @@ -69,6 +71,8 @@ public struct Authenticator<LoadingContent: View,
/// Defaults to a ``SignInView``.
/// - Parameter confirmSignInWithMFACodeContent: The content associated with the ``AuthenticatorStep/confirmSignInWithMFACode`` step.
/// Defaults to a ``ConfirmSignInWithMFACodeView``.
/// - Parameter confirmSignInWithOTPContent: The content associated with the ``AuthenticatorStep/confirmSignInWithOTP`` step.
/// Defaults to a ``ConfirmSignInWithOTPView``.
///- Parameter confirmSignInWithTOTPCodeContent: The content associated with the ``AuthenticatorStep/confirmSignInWithTOTPCode`` step.
/// Defaults to a ``ConfirmSignInWithMFACodeView``.
///- Parameter continueSignInWithMFASelectionContent: The content associated with the ``AuthenticatorStep/continueSignInWithMFASelection`` step.
Expand Down Expand Up @@ -114,6 +118,9 @@ public struct Authenticator<LoadingContent: View,
@ViewBuilder confirmSignInWithMFACodeContent: (ConfirmSignInWithCodeState) -> ConfirmSignInWithMFACodeContent = { state in
ConfirmSignInWithMFACodeView(state: state)
},
@ViewBuilder confirmSignInWithOTPContent: @escaping (ConfirmSignInWithCodeState) -> ConfirmSignInWithOTPContent = { state in
ConfirmSignInWithOTPView(state: state)
},
@ViewBuilder confirmSignInWithTOTPCodeContent: @escaping (ConfirmSignInWithCodeState) -> ConfirmSignInWithTOTPCodeContent = { state in
ConfirmSignInWithTOTPView(state: state)
},
Expand Down Expand Up @@ -171,10 +178,10 @@ public struct Authenticator<LoadingContent: View,

let confirmSignInWithMFACodeState = ConfirmSignInWithCodeState(credentials: credentials)
contentStates.add(confirmSignInWithMFACodeState)
self.confirmSignInContentWithMFACodeContent = confirmSignInWithMFACodeContent(
self.confirmSignInWithMFACodeContent = confirmSignInWithMFACodeContent(
confirmSignInWithMFACodeState
)

self.confirmSignInWithOTPContent = confirmSignInWithOTPContent
self.confirmSignInWithTOTPCodeContent = confirmSignInWithTOTPCodeContent
self.continueSignInWithMFASelectionContent = continueSignInWithMFASelectionContent
self.continueSignInWithMFASetupSelectionContent = continueSignInWithMFASetupSelectionContent
Expand Down Expand Up @@ -353,7 +360,12 @@ public struct Authenticator<LoadingContent: View,
case .confirmSignInWithNewPassword:
confirmSignInContentWithNewPasswordContent
case .confirmSignInWithMFACode:
confirmSignInContentWithMFACodeContent
confirmSignInWithMFACodeContent
case .confirmSignInWithOTP(let deliveryDetails):
let confirmSignInWithCodeState = ConfirmSignInWithCodeState(
authenticatorState: state
)
confirmSignInWithOTPContent(confirmSignInWithCodeState)
case .continueSignInWithMFASelection(let allowedMFATypes):
let continueSignInWithMFASelection = ContinueSignInWithMFASelectionState(
authenticatorState: state,
Expand Down
4 changes: 4 additions & 0 deletions Sources/Authenticator/Models/AuthenticatorStep.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ public struct AuthenticatorStep: Equatable {
/// so they are presented with the Confirm Sign In with MFA Code view
public static let confirmSignInWithMFACode = AuthenticatorStep("confirmSignInWithMFACode")

/// A user has successfully provided valid Sign In credentials but is required to provide a OTP,
/// so they are presented with the Confirm Sign In with OTP view
public static let confirmSignInWithOTP = AuthenticatorStep("confirmSignInWithOTP")

/// A user has successfully provided valid Sign In credentials but is required to change their password,
/// so they are presented with the Confirm Sign In with New Password view
public static let confirmSignInWithNewPassword = AuthenticatorStep("confirmSignInWithNewPassword")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// SPDX-License-Identifier: Apache-2.0
//

enum AuthenticatorMFAType {
enum AuthenticatorFactorType {
case sms
case email
case totp
Expand Down
3 changes: 3 additions & 0 deletions Sources/Authenticator/Models/Internal/Step.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum Step {
case continueSignInWithEmailMFASetup
case continueSignInWithTOTPSetup(totpSetupDetails: TOTPSetupDetails)
case confirmSignInWithMFACode(deliveryDetails: AuthCodeDeliveryDetails?)
case confirmSignInWithOTP(deliveryDetails: AuthCodeDeliveryDetails?)
case confirmSignInWithNewPassword
case signUp
case confirmSignUp(deliveryDetails: AuthCodeDeliveryDetails?)
Expand Down Expand Up @@ -63,6 +64,8 @@ enum Step {
return .continueSignInWithEmailMFASetup
case .confirmSignInWithMFACode:
return .confirmSignInWithMFACode
case .confirmSignInWithOTP:
return .confirmSignInWithOTP
case .confirmSignInWithNewPassword:
return .confirmSignInWithNewPassword
case .signUp:
Expand Down
7 changes: 5 additions & 2 deletions Sources/Authenticator/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,20 @@
"authenticator.confirmSignInWithNewPassword.title" = "Set a new password";
"authenticator.confirmSignInWithNewPassword.button.submit" = "Submit";

/* Confirm Sign In with OTP view */
"authenticator.confirmSignInWithOTP.title" = "Verify your sign-in";

/* Confirm Sign In with MFA view */
"authenticator.confirmSignInWithMFACode.title" = "Verify your sign-in";
"authenticator.confirmSignInWithMFACode.field.email.code.label" = "Enter the verification code sent to your email to complete this sign-in.";
"authenticator.confirmSignInWithMFACode.field.phone.code.label" = "Enter the verification code sent to your phone to complete this sign-in.";

/* Confirm Sign In with Custom Challenge view */
"authenticator.confirmSignInWithCustomChallenge.title" = "Enter your Sign In code";

/* Confirm Sign In with Code */
"authenticator.confirmSignInWithCode.button.submit" = "Submit";
"authenticator.confirmSignInWithCode.button.backToSignIn" = "Back to Sign In";
"authenticator.confirmSignInWithCode.field.email.code.label" = "Enter the verification code sent to your email to complete this sign-in.";
"authenticator.confirmSignInWithCode.field.phone.code.label" = "Enter the verification code sent to your phone to complete this sign-in.";

/* Confirm Sign In with TOTP */
"authenticator.confirmSignInWithCode.totp.title" = "Enter your one-time passcode";
Expand Down
5 changes: 3 additions & 2 deletions Sources/Authenticator/States/AuthenticatorBaseState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ 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, _),
.confirmSignInWithEmailMFACode(let details):
case .confirmSignInWithSMSMFACode(let details, _):
return .confirmSignInWithMFACode(deliveryDetails: details)
case .confirmSignInWithOTP(let details):
return .confirmSignInWithOTP(deliveryDetails: details)
case .confirmSignInWithCustomChallenge(_):
return .confirmSignInWithCustomChallenge
case .confirmSignInWithNewPassword(_):
Expand Down
77 changes: 77 additions & 0 deletions Sources/Authenticator/Views/ConfirmSignInWithOTPView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Amplify
import SwiftUI

/// Represents the content being displayed when the ``Authenticator`` is in the ``AuthenticatorStep/confirmSignInWithOTP`` step.
public struct ConfirmSignInWithOTPView<Header: View,
Footer: View>: View {
@Environment(\.authenticatorState) private var authenticatorState
@ObservedObject private var state: ConfirmSignInWithCodeState
private let content: ConfirmSignInWithCodeView<Header, Footer>

/// Creates a `ConfirmSignInWithMFACodeView`
/// - Parameter state: The ``ConfirmSignInWithCodeState`` that is observed by this view
/// - Parameter headerContent: The content displayed above the fields. Defaults to ``ConfirmSignInWithMFACodeHeader``
/// - Parameter footerContent: The content displayed bellow the fields. Defaults to ``ConfirmSignInWithMFACodeFooter``
public init(
state: ConfirmSignInWithCodeState,
@ViewBuilder headerContent: () -> Header = {
ConfirmSignInWithOTPHeader()
},
@ViewBuilder footerContent: () -> Footer = {
ConfirmSignInWithOTPFooter()
}
) {
self.state = state
self.content = ConfirmSignInWithCodeView(
state: state,
headerContent: headerContent,
footerContent: footerContent
)
}

public var body: some View {
content
.onAppear {
state.message = .info(
message: state.localizedMessage(for: state.deliveryDetails)
)
}
}

/// Sets a custom error mapping function for the `AuthError`s that are displayed
/// - Parameter errorTransform: A closure that takes an `AuthError` and returns a ``AuthenticatorError`` that will be displayed.
public func errorMap(_ errorTransform: @escaping (AuthError) -> AuthenticatorError?) -> Self {
state.errorTransform = errorTransform
return self
}
}

/// Default header for the ``ConfirmSignInWithOTPView``. It displays the view's title
public struct ConfirmSignInWithOTPHeader: View {
public init() {}
public var body: some View {
DefaultHeader(
title: "authenticator.confirmSignInWithOTP.title".localized()
)
}
}

/// Default footer for the ``ConfirmSignInWithOTPView``. It displays the "Back to Sign In" button
public struct ConfirmSignInWithOTPFooter: View {
@Environment(\.authenticatorState) private var authenticatorState

public init() {}
public var body: some View {
Button("authenticator.confirmSignInWithCode.button.backToSignIn".localized()) {
authenticatorState.move(to: .signIn)
}
.buttonStyle(.link)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ struct ConfirmSignInWithCodeView<Header: View,
))
}

private func getCurrentMFAType() -> AuthenticatorMFAType {
private func getCurrentMFAType() -> AuthenticatorFactorType {
switch authenticatorState.step {
case .confirmSignInWithMFACode(let deliveryDetails):
case .confirmSignInWithMFACode(let deliveryDetails),
.confirmSignInWithOTP(let deliveryDetails):
switch deliveryDetails?.destination {
case .email:
return .email
Expand All @@ -55,9 +56,9 @@ struct ConfirmSignInWithCodeView<Header: View,
private var textFieldLabel: String {
switch getCurrentMFAType() {
case .sms, .none:
return "authenticator.confirmSignInWithMFACode.field.phone.code.label".localized()
return "authenticator.confirmSignInWithCode.field.phone.code.label".localized()
case .email:
return "authenticator.confirmSignInWithMFACode.field.email.code.label".localized()
return "authenticator.confirmSignInWithCode.field.email.code.label".localized()
case .totp:
return "authenticator.field.totp.code.label".localized()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"originHash" : "aa50950c4756f6cc1e41a45105a6b4bbed3e817c73e36d24f00ec955e5792ec9",
"originHash" : "2bc1bcce3d1d696209bfe1523e1c02fee4efba4d5519df843afdad7c30fc0bee",
"pins" : [
{
"identity" : "amplify-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/aws-amplify/amplify-swift",
"state" : {
"revision" : "fa6ca64f42c3e4e72cf6b62c30bc45b2be7b05bc",
"version" : "2.35.1"
"branch" : "feat/email-mfa-support-public",
"revision" : "f332d126512fd9e28cbe07f4b3fdb36ab9cff4cb"
}
},
{
Expand All @@ -24,26 +24,26 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/awslabs/aws-crt-swift",
"state" : {
"revision" : "0d0a0cf2e2cb780ceeceac190b4ede94f4f96902",
"version" : "0.26.0"
"revision" : "7b42e0343f28b3451aab20840dc670abd12790bd",
"version" : "0.36.0"
}
},
{
"identity" : "aws-sdk-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/awslabs/aws-sdk-swift.git",
"state" : {
"revision" : "47922c05dd66be717c7bce424651a534456717b7",
"version" : "0.36.2"
"revision" : "828358a2c39d138325b0f87a2d813f4b972e5f4f",
"version" : "1.0.0"
}
},
{
"identity" : "smithy-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/smithy-lang/smithy-swift",
"state" : {
"revision" : "8a5b0105c1b8a1d26a9435fb0af3959a7f5de578",
"version" : "0.41.1"
"revision" : "0ed3440f8c41e27a0937364d5035d2d4fefb8aa3",
"version" : "0.71.0"
}
},
{
Expand All @@ -60,8 +60,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5",
"version" : "1.5.4"
"revision" : "9cb486020ebf03bfa5b5df985387a14a98744537",
"version" : "1.6.1"
}
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ enum SignInNextStepForTesting: String, CaseIterable, Identifiable {
case .continueSignInWithMFASetupSelection:
return .continueSignInWithMFASetupSelection(.init([.email, .totp]))
case .confirmSignInWithEmailMFACode:
return .confirmSignInWithEmailMFACode(.init(destination: .email("h***@a***.com")))
return .confirmSignInWithOTP(.init(destination: .email("h***@a***.com")))
case .confirmSignInWithPhoneMFACode:
return .confirmSignInWithEmailMFACode(.init(destination: .phone("+11***")))
return .confirmSignInWithOTP(.init(destination: .phone("+11***")))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,22 @@ class ConfirmSignInWithCodeStateTests: XCTestCase {
}
}

func testDeliveryDetails_onConfirmSignInWithMFACodeStep_shouldReturnDetails() throws {
func testDeliveryDetails_onConfirmSignInWithSMSMFACodeStep_shouldReturnDetails() throws {
let destination = DeliveryDestination.sms("123456789")
authenticatorState.mockedStep = .confirmSignInWithMFACode(deliveryDetails: .init(destination: destination))

let deliveryDetails = try XCTUnwrap(state.deliveryDetails)
XCTAssertEqual(deliveryDetails.destination, destination)
}

func testDeliveryDetails_onConfirmSignInWithEmailMFACodeStep_shouldReturnDetails() throws {
let destination = DeliveryDestination.email("[email protected]")
authenticatorState.mockedStep = .confirmSignInWithMFACode(deliveryDetails: .init(destination: destination))

let deliveryDetails = try XCTUnwrap(state.deliveryDetails)
XCTAssertEqual(deliveryDetails.destination, destination)
}

func testDeliveryDetails_onUnexpectedStep_shouldReturnNil() throws {
let destination = DeliveryDestination.sms("123456789")
authenticatorState.mockedStep = .confirmSignUp(deliveryDetails: .init(destination: destination))
Expand Down
Loading

0 comments on commit 3b4e8ce

Please sign in to comment.