Skip to content

Commit

Permalink
feat(Auth): Add Passwordless Sign In with OTP implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
harsh62 committed Nov 28, 2023
1 parent 3ad7de2 commit 7a979c7
Show file tree
Hide file tree
Showing 8 changed files with 416 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation

/// Delivery destination for the Auth Passwordless flows
///
public enum AuthPasswordlessDeliveryDestination {
case sms
case email
public enum AuthPasswordlessDeliveryDestination: String {
case sms = "SMS"
case email = "EMAIL"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation

enum PasswordlessCustomAuthNextStep: String {

case provideAuthParameters = "PROVIDE_AUTH_PARAMETERS"

case provideChallengeResponse = "PROVIDE_CHALLENGE_RESPONSE"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Amplify
import Foundation

enum PasswordlessCustomAuthSignInMethod: String {
case otp = "OTP"
case magicLink = "MAGIC_LINK"
}

enum PasswordlessCustomAuthRequestAction: String {
case request = "REQUEST"
case confirm = "CONFIRM"
}

struct PasswordlessCustomAuthRequest {

let signInMethod: PasswordlessCustomAuthSignInMethod
let action: PasswordlessCustomAuthRequestAction
let deliveryMedium: AuthPasswordlessDeliveryDestination?

init(signInMethod: PasswordlessCustomAuthSignInMethod,
action: PasswordlessCustomAuthRequestAction,
deliveryMedium: AuthPasswordlessDeliveryDestination? = nil) {
self.signInMethod = signInMethod
self.action = action
self.deliveryMedium = deliveryMedium
}

func toDictionary() -> [String: String] {
var dictionary = [
"signInMethod": signInMethod.rawValue,
"action": action.rawValue
]
if let deliveryMedium = deliveryMedium {
dictionary["deliveryMedium"] = deliveryMedium.rawValue
}
return dictionary
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct SignInEventData {
let signInMethod: SignInMethod

init(username: String?,
password: String?,
password: String? = nil,
clientMetadata: [String: String] = [:],
signInMethod: SignInMethod) {
self.username = username
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class AWSAuthConfirmSignInWithOTPTask: AuthConfirmSignInWithOTPTask, DefaultLogg
private let authStateMachine: AuthStateMachine
private let taskHelper: AWSAuthTaskHelper
private let authConfiguration: AuthConfiguration
private let confirmSignInRequestMetadata: PasswordlessCustomAuthRequest

var eventName: HubPayloadEventName {
HubPayload.EventName.Auth.confirmSignInWithOTPAPI
Expand All @@ -26,21 +27,13 @@ class AWSAuthConfirmSignInWithOTPTask: AuthConfirmSignInWithOTPTask, DefaultLogg
self.authStateMachine = stateMachine
self.taskHelper = AWSAuthTaskHelper(authStateMachine: authStateMachine)
self.authConfiguration = configuration
self.confirmSignInRequestMetadata = .init(signInMethod: .otp, action: .confirm)
}

func execute() async throws -> AuthSignInResult {
log.verbose("Starting execution")
await taskHelper.didStateMachineConfigured()

//Check if we have a user pool configuration
guard authConfiguration.getUserPoolConfiguration() != nil else {
let message = AuthPluginErrorConstants.configurationError
let authError = AuthError.configuration(
"Could not find user pool configuration",
message)
throw authError
}

if let validationError = request.hasError() {
throw validationError
}
Expand All @@ -53,11 +46,6 @@ class AWSAuthConfirmSignInWithOTPTask: AuthConfirmSignInWithOTPTask, DefaultLogg
throw invalidStateError
}

// [HS] TODO: Following implementations need to be complete
// 1. Validate it is the correct state to confirm Sign In With OTP
// 2. Send event
// 3. Listent to events
// 4. Complete the task
guard case .resolvingChallenge(let challengeState, _, _) = signInState else {
throw invalidStateError
}
Expand All @@ -70,7 +58,6 @@ class AWSAuthConfirmSignInWithOTPTask: AuthConfirmSignInWithOTPTask, DefaultLogg
throw invalidStateError
}


let stateSequences = await authStateMachine.listen()
log.verbose("Waiting for response")
for await state in stateSequences {
Expand Down Expand Up @@ -110,14 +97,21 @@ class AWSAuthConfirmSignInWithOTPTask: AuthConfirmSignInWithOTPTask, DefaultLogg
}

private func createConfirmSignInEventData() -> ConfirmSignInEventData {

// [HS] TODO: Confirm if any metadata needs to be passed during confirm sign in
/*
* Attributes
* Metadata
* Device Name
*/
// TODO:
// Discuss if we should have dedicated options for ConfirmSignWith OTP
// Because `AWSAuthConfirmSignInOptions` has `friendlyDeviceName` and `userAttributes`
// that is not supported by this task. Customers might get confused that these are supported fields.
var passwordlessMetadata = confirmSignInRequestMetadata.toDictionary()
if let customerMetadata = (request.options.pluginOptions as? AWSAuthConfirmSignInOptions)?.metadata {
passwordlessMetadata.merge(customerMetadata, uniquingKeysWith: { passwordlessMetadata, customerMetadata in
// TODO: Discuss with team to namespace passwordless metadata
// Giving precedence to passwordless metadata.
passwordlessMetadata

})
}
return ConfirmSignInEventData(
answer: self.request.challengeResponse)
answer: self.request.challengeResponse,
metadata: passwordlessMetadata)
}
}
Loading

0 comments on commit 7a979c7

Please sign in to comment.