Skip to content

Commit

Permalink
feat(auth): add URLSession client (#3387)
Browse files Browse the repository at this point in the history
* feat(auth): add URLSession client

* Add mocks

* Fix the unit test build

* Address review comments

* Address review comments

* Address review comments
  • Loading branch information
thisisabhash committed Dec 13, 2023
1 parent 214d0a9 commit f9282f8
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@ extension AWSCognitoAuthPlugin {
let credentialsClient = CredentialStoreOperationClient(
credentialStoreStateMachine: credentialStoreMachine)

let authPasswordlessClient = AWSAuthPasswordlessClient(urlSession: makeURLSession())

let authResolver = AuthState.Resolver().eraseToAnyResolver()
let authEnvironment = makeAuthEnvironment(
authConfiguration: authConfiguration,
credentialsClient: credentialsClient
credentialsClient: credentialsClient,
authPasswordlessClient: authPasswordlessClient
)

let authStateMachine = StateMachine(resolver: authResolver, environment: authEnvironment)
Expand Down Expand Up @@ -181,7 +184,8 @@ extension AWSCognitoAuthPlugin {

private func makeAuthEnvironment(
authConfiguration: AuthConfiguration,
credentialsClient: CredentialStoreStateBehavior
credentialsClient: CredentialStoreStateBehavior,
authPasswordlessClient: AuthPasswordlessBehavior
) -> AuthEnvironment {

switch authConfiguration {
Expand All @@ -196,6 +200,7 @@ extension AWSCognitoAuthPlugin {
authenticationEnvironment: authenticationEnvironment,
authorizationEnvironment: nil,
credentialsClient: credentialsClient,
authPasswordlessClient: authPasswordlessClient,
logger: log)

case .identityPools(let identityPoolConfigurationData):
Expand All @@ -208,6 +213,7 @@ extension AWSCognitoAuthPlugin {
authenticationEnvironment: nil,
authorizationEnvironment: authorizationEnvironment,
credentialsClient: credentialsClient,
authPasswordlessClient: authPasswordlessClient,
logger: log)

case .userPoolsAndIdentityPools(let userPoolConfigurationData,
Expand All @@ -223,6 +229,7 @@ extension AWSCognitoAuthPlugin {
authenticationEnvironment: authenticationEnvironment,
authorizationEnvironment: authorizationEnvironment,
credentialsClient: credentialsClient,
authPasswordlessClient: authPasswordlessClient,
logger: log)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct AuthEnvironment: Environment, LoggerProvider {
let authenticationEnvironment: AuthenticationEnvironment?
let authorizationEnvironment: AuthorizationEnvironment?
let credentialsClient: CredentialStoreStateBehavior
let authPasswordlessClient: AuthPasswordlessBehavior?
let logger: Logger
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import Amplify
import AWSPluginsCore

/// Concrete implementation of AuthPasswordlessBehavior
class AWSAuthPasswordlessClient : AuthPasswordlessBehavior {

let urlSession: URLSession
let userAgent: String

init(urlSession: URLSession) {
self.urlSession = urlSession
self.userAgent = "\(AmplifyAWSServiceConfiguration.userAgentLib) \(AmplifyAWSServiceConfiguration.userAgentOS)"
}

func preInitiateAuthSignUp(endpoint: URL,
payload: PreInitiateAuthSignUpPayload) async throws -> Result<Void, AuthError> {

var request = URLRequest(url: endpoint)
request.httpMethod = "POST"
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")

do {
let (_, response) = try await self.data(for: request)

guard let response = response as? HTTPURLResponse else {
throw AuthError.unknown("Response received is not a HTTPURLResponse")
}

if response.statusCode == 200 {
return .successfulVoid
} else {
throw AuthError.unknown("Response received with status code: \(response.statusCode)")
}
} catch {
throw AuthError.service(error.localizedDescription,
"API Gateway returned an error. Please check the error message for more details.",
error)
}
}

private func data(
for request: URLRequest,
delegate: (URLSessionTaskDelegate)? = nil)
async throws -> (Data, URLResponse) {
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
return try await self.urlSession.data(
for: request,
delegate: delegate
)
} else {
// Fallback on earlier versions
return try await withCheckedThrowingContinuation({ continuation in
let dataTask = urlSession.dataTask(with: request) { data, response, error in
if let data = data, let response = response {
continuation.resume(returning: (data, response))
} else {
continuation.resume(throwing: error ?? AuthError.unknown(
"""
An unknown error occurred with
data: \(String(describing: data))
response: \(String(describing: response))
error: \(String(describing: error))
""")
)
}
}
dataTask.resume()
})
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import Amplify

// Protocol for initiating sign up in a passwordless flow
protocol AuthPasswordlessBehavior {

func preInitiateAuthSignUp(
endpoint: URL,
payload: PreInitiateAuthSignUpPayload)
async throws -> Result<Void, AuthError>

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

import Foundation

// Payload data structure to send to the API Gateway endpoint
// for password sign up and sign in flow to initiate creating a new user
struct PreInitiateAuthSignUpPayload: Codable {
let username: String
let deliveryMedium: String
let userAttributes: [String:String]?

let userPoolId: String
let region: String

init(
username: String,
deliveryMedium: String,
userAttributes: [String : String]?,
userPoolId: String, region: String
) {
self.username = username
self.deliveryMedium = deliveryMedium
self.userAttributes = userAttributes
self.userPoolId = userPoolId
self.region = region
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import Amplify
@testable import AWSPluginsCore
@testable import AWSCognitoAuthPlugin

class MockAuthPasswordlessBehavior: AuthPasswordlessBehavior {

public var preInitiateAuthSignUpCallCount = 0

func preInitiateAuthSignUp(
endpoint: URL,
payload: PreInitiateAuthSignUpPayload)
async throws -> Result<Void, AuthError> {
preInitiateAuthSignUpCallCount += 1
return .successfulVoid
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ enum Defaults {
static func makeCredentialStoreOperationBehavior() -> CredentialStoreStateBehavior {
return MockCredentialStoreOperationClient()
}

static func makeURLSession() -> URLSession {
let configuration = URLSessionConfiguration.default
configuration.urlCache = nil
return URLSession(configuration: configuration)
}

static func makeDefaultUserPool() throws -> CognitoUserPoolBehavior {
return try CognitoIdentityProviderClient(region: regionString)
Expand Down Expand Up @@ -162,6 +168,7 @@ enum Defaults {
authenticationEnvironment: authenticationEnvironment,
authorizationEnvironment: authZEnvironment ?? authorizationEnvironment,
credentialsClient: makeCredentialStoreOperationBehavior(),
authPasswordlessClient: AWSAuthPasswordlessClient(urlSession: makeURLSession()),
logger: Amplify.Logging.logger(forCategory: "awsCognitoAuthPluginTest")
)
Amplify.Logging.logLevel = .verbose
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest {
authenticationEnvironment: nil,
authorizationEnvironment: authorizationEnvironment,
credentialsClient: Defaults.makeCredentialStoreOperationBehavior(),
authPasswordlessClient: nil,
logger: Amplify.Logging.logger(forCategory: "awsCognitoAuthPluginTest")
)
let stateMachine = Defaults.authStateMachineWith(environment: environment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ class ConfirmSignInTOTPTaskTests: BasePluginTest {
authenticationEnvironment: nil,
authorizationEnvironment: authorizationEnvironment,
credentialsClient: Defaults.makeCredentialStoreOperationBehavior(),
authPasswordlessClient: nil,
logger: Amplify.Logging.logger(forCategory: "awsCognitoAuthPluginTest")
)
let stateMachine = Defaults.authStateMachineWith(environment: environment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ class ConfirmSignInWithMFASelectionTaskTests: BasePluginTest {
authenticationEnvironment: nil,
authorizationEnvironment: authorizationEnvironment,
credentialsClient: Defaults.makeCredentialStoreOperationBehavior(),
authPasswordlessClient: nil,
logger: Amplify.Logging.logger(forCategory: "awsCognitoAuthPluginTest")
)
let stateMachine = Defaults.authStateMachineWith(environment: environment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ class ConfirmSignInWithSetUpMFATaskTests: BasePluginTest {
authenticationEnvironment: nil,
authorizationEnvironment: authorizationEnvironment,
credentialsClient: Defaults.makeCredentialStoreOperationBehavior(),
authPasswordlessClient: nil,
logger: Amplify.Logging.logger(forCategory: "awsCognitoAuthPluginTest")
)
let stateMachine = Defaults.authStateMachineWith(environment: environment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ struct MockedAuthCognitoPluginHelper {
authenticationEnvironment: authenticationEnvironment,
authorizationEnvironment: nil,
credentialsClient: makeCredentialStoreClient(),
authPasswordlessClient: AWSAuthPasswordlessClient(urlSession: makeURLSession()),
logger: log)

case .identityPools(let identityPoolConfigurationData):
Expand All @@ -136,6 +137,7 @@ struct MockedAuthCognitoPluginHelper {
authenticationEnvironment: nil,
authorizationEnvironment: authorizationEnvironment,
credentialsClient: makeCredentialStoreClient(),
authPasswordlessClient: AWSAuthPasswordlessClient(urlSession: makeURLSession()),
logger: log)

case .userPoolsAndIdentityPools(let userPoolConfigurationData,
Expand All @@ -151,6 +153,7 @@ struct MockedAuthCognitoPluginHelper {
authenticationEnvironment: authenticationEnvironment,
authorizationEnvironment: authorizationEnvironment,
credentialsClient: makeCredentialStoreClient(),
authPasswordlessClient: AWSAuthPasswordlessClient(urlSession: makeURLSession()),
logger: log)
}
}
Expand Down

0 comments on commit f9282f8

Please sign in to comment.