diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/AWSCognitoAuthPlugin.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/AWSCognitoAuthPlugin.xcscheme
index d92bafde07..96d011b6cc 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/AWSCognitoAuthPlugin.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/AWSCognitoAuthPlugin.xcscheme
@@ -48,11 +48,6 @@
BlueprintName = "AWSCognitoAuthPluginUnitTests"
ReferencedContainer = "container:">
-
-
-
-
diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSAuthCognitoSession.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSAuthCognitoSession.swift
index 6c46eceb40..59d799e963 100644
--- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSAuthCognitoSession.swift
+++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSAuthCognitoSession.swift
@@ -76,24 +76,6 @@ public struct AWSAuthCognitoSession: AuthSession,
}
-/// Internal Helpers for managing session tokens
-internal extension AWSAuthCognitoSession {
- func areTokensExpiring(in seconds: TimeInterval? = nil) -> Bool {
-
- guard let tokens = try? userPoolTokensResult.get(),
- let idTokenClaims = try? AWSAuthService().getTokenClaims(tokenString: tokens.idToken).get(),
- let accessTokenClaims = try? AWSAuthService().getTokenClaims(tokenString: tokens.idToken).get(),
- let idTokenExpiration = idTokenClaims["exp"]?.doubleValue,
- let accessTokenExpiration = accessTokenClaims["exp"]?.doubleValue else {
- return true
- }
-
- // If the session expires < X minutes return it
- return (Date(timeIntervalSince1970: idTokenExpiration).compare(Date(timeIntervalSinceNow: seconds ?? 0)) == .orderedDescending &&
- Date(timeIntervalSince1970: accessTokenExpiration).compare(Date(timeIntervalSinceNow: seconds ?? 0)) == .orderedDescending)
- }
-}
-
extension AWSAuthCognitoSession: Equatable {
public static func == (lhs: AWSAuthCognitoSession, rhs: AWSAuthCognitoSession) -> Bool {
switch (lhs.getCognitoTokens(), rhs.getCognitoTokens()) {
diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSCognitoUserPoolTokens.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSCognitoUserPoolTokens.swift
index af7d80f96a..c5f4daed06 100644
--- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSCognitoUserPoolTokens.swift
+++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AWSCognitoUserPoolTokens.swift
@@ -65,10 +65,10 @@ public struct AWSCognitoUserPoolTokens: AuthCognitoTokens {
case (.some(let idTokenValue), .none):
expirationDoubleValue = idTokenValue
case (.none, .none):
- expirationDoubleValue = 0
+ expirationDoubleValue = Date().timeIntervalSince1970
}
- self.expiration = Date().addingTimeInterval(TimeInterval((expirationDoubleValue ?? 0)))
+ self.expiration = Date(timeIntervalSince1970: TimeInterval(expirationDoubleValue))
}
}
diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/AuthCognitoSignedOutSessionHelper.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/AuthCognitoSignedOutSessionHelper.swift
index 9ded44922d..8e391355e5 100644
--- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/AuthCognitoSignedOutSessionHelper.swift
+++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/AuthCognitoSignedOutSessionHelper.swift
@@ -25,27 +25,6 @@ struct AuthCognitoSignedOutSessionHelper {
return authSession
}
- /// Guest/SignedOut session with any unhandled error
- ///
- /// The unhandled error is passed as identityId and aws credentials result. UserSub and Cognito Tokens will still
- /// have signOut error.
- ///
- /// - Parameter error: Unhandled error
- /// - Returns: Session will have isSignedIn = false
- private static func makeSignedOutSession(withUnhandledError error: AuthError) -> AWSAuthCognitoSession {
-
- let identityIdError = error
- let awsCredentialsError = error
-
- let tokensError = makeCognitoTokensSignedOutError()
-
- let authSession = AWSAuthCognitoSession(isSignedIn: false,
- identityIdResult: .failure(identityIdError),
- awsCredentialsResult: .failure(awsCredentialsError),
- cognitoTokensResult: .failure(tokensError))
- return authSession
- }
-
/// Guest/SignOut session when the guest access is not enabled.
/// - Returns: Session with isSignedIn = false
static func makeSessionWithNoGuestAccess() -> AWSAuthCognitoSession {
@@ -68,26 +47,6 @@ struct AuthCognitoSignedOutSessionHelper {
return authSession
}
- private static func makeOfflineSignedOutSession() -> AWSAuthCognitoSession {
- let identityIdError = AuthError.service(
- AuthPluginErrorConstants.identityIdOfflineError.errorDescription,
- AuthPluginErrorConstants.identityIdOfflineError.recoverySuggestion,
- AWSCognitoAuthError.network)
-
- let awsCredentialsError = AuthError.service(
- AuthPluginErrorConstants.awsCredentialsOfflineError.errorDescription,
- AuthPluginErrorConstants.awsCredentialsOfflineError.recoverySuggestion,
- AWSCognitoAuthError.network)
-
- let tokensError = makeCognitoTokensSignedOutError()
-
- let authSession = AWSAuthCognitoSession(isSignedIn: false,
- identityIdResult: .failure(identityIdError),
- awsCredentialsResult: .failure(awsCredentialsError),
- cognitoTokensResult: .failure(tokensError))
- return authSession
- }
-
/// Guest/SignedOut session with couldnot retreive either aws credentials or identity id.
/// - Returns: Session will have isSignedIn = false
private static func makeSignedOutSessionWithServiceIssue() -> AWSAuthCognitoSession {
@@ -109,13 +68,6 @@ struct AuthCognitoSignedOutSessionHelper {
return authSession
}
- private static func makeUserSubSignedOutError() -> AuthError {
- let userSubError = AuthError.signedOut(
- AuthPluginErrorConstants.userSubSignOutError.errorDescription,
- AuthPluginErrorConstants.userSubSignOutError.recoverySuggestion)
- return userSubError
- }
-
private static func makeCognitoTokensSignedOutError() -> AuthError {
let tokensError = AuthError.signedOut(
AuthPluginErrorConstants.cognitoTokensSignOutError.errorDescription,
diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift
index cd9760637a..9c225ec931 100644
--- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift
+++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift
@@ -22,7 +22,7 @@ class HostedUIASWebAuthenticationSession: NSObject, HostedUISessionBehavior {
callback: @escaping (Result<[URLQueryItem], HostedUIError>) -> Void) {
#if os(iOS) || os(macOS)
self.webPresentation = presentationAnchor
- let aswebAuthenticationSession = ASWebAuthenticationSession(
+ let aswebAuthenticationSession = createAuthenticationSession(
url: url,
callbackURLScheme: callbackScheme,
completionHandler: { url, error in
@@ -58,6 +58,16 @@ class HostedUIASWebAuthenticationSession: NSObject, HostedUISessionBehavior {
}
#if os(iOS) || os(macOS)
+ var authenticationSessionFactory = ASWebAuthenticationSession.init(url:callbackURLScheme:completionHandler:)
+
+ private func createAuthenticationSession(
+ url: URL,
+ callbackURLScheme: String?,
+ completionHandler: @escaping ASWebAuthenticationSession.CompletionHandler
+ ) -> ASWebAuthenticationSession {
+ return authenticationSessionFactory(url, callbackURLScheme, completionHandler)
+ }
+
private func convertHostedUIError(_ error: Error) -> HostedUIError {
if let asWebAuthError = error as? ASWebAuthenticationSessionError {
switch asWebAuthError.code {
diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MigrateLegacyCredentialStoreTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MigrateLegacyCredentialStoreTests.swift
index d4a0248e34..c7a475990a 100644
--- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MigrateLegacyCredentialStoreTests.swift
+++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MigrateLegacyCredentialStoreTests.swift
@@ -72,7 +72,7 @@ class MigrateLegacyCredentialStoreTests: XCTestCase {
let action = MigrateLegacyCredentialStore()
await action.execute(withDispatcher: MockDispatcher { _ in }, environment: environment)
- await waitForExpectations(timeout: 0.1)
+ await fulfillment(of: [saveCredentialHandlerInvoked], timeout: 0.1)
}
/// Test is responsible for making sure that the legacy credential store clearing up is getting called for user pool and identity pool
@@ -115,8 +115,109 @@ class MigrateLegacyCredentialStoreTests: XCTestCase {
let action = MigrateLegacyCredentialStore()
await action.execute(withDispatcher: MockDispatcher { _ in }, environment: environment)
- await waitForExpectations(timeout: 0.1)
-
+ await fulfillment(of: [migrationCompletionInvoked], timeout: 0.1)
}
+
+ func testInvalidEnvironment() async {
+ let expectation = expectation(description: "noEnvironment")
+ let action = MigrateLegacyCredentialStore()
+ await action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? CredentialStoreEvent,
+ case let .throwError(error) = event.eventType else {
+ XCTFail("Expected failure due to no CredentialEnvironment")
+ expectation.fulfill()
+ return
+ }
+ XCTAssertEqual(error, .configuration(message: AuthPluginErrorConstants.configurationError))
+ expectation.fulfill()
+ },
+ environment: MockInvalidEnvironment()
+ )
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testNoUserPoolWithoutLoginsTokens() async {
+ let expectation = expectation(description: "noUserPoolTokens")
+ let action = MigrateLegacyCredentialStore()
+ await action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? CredentialStoreEvent,
+ case .loadCredentialStore(let type) = event.eventType else {
+ XCTFail("Expected .loadCredentialStore")
+ expectation.fulfill()
+ return
+ }
+ XCTAssertEqual(type, .amplifyCredentials)
+ expectation.fulfill()
+ },
+ environment: CredentialEnvironment(
+ authConfiguration: .identityPools(.testData),
+ credentialStoreEnvironment: BasicCredentialStoreEnvironment(
+ amplifyCredentialStoreFactory: {
+ MockAmplifyCredentialStoreBehavior(
+ saveCredentialHandler: { codableCredentials in
+ guard let amplifyCredentials = codableCredentials as? AmplifyCredentials,
+ case .identityPoolOnly(_, let credentials) = amplifyCredentials else {
+ XCTFail("Expected .identityPoolOnly")
+ return
+ }
+ XCTAssertFalse(credentials.sessionToken.isEmpty)
+ }
+ )
+ },
+ legacyKeychainStoreFactory: { _ in
+ MockKeychainStoreBehavior(data: "hostedUI")
+ }),
+ logger: MigrateLegacyCredentialStore.log
+ )
+ )
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testNoUserPoolWithLoginsTokens() async {
+ let expectation = expectation(description: "noUserPoolTokens")
+ let action = MigrateLegacyCredentialStore()
+ await action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? CredentialStoreEvent,
+ case .loadCredentialStore(let type) = event.eventType else {
+ XCTFail("Expected .loadCredentialStore")
+ expectation.fulfill()
+ return
+ }
+ XCTAssertEqual(type, .amplifyCredentials)
+ expectation.fulfill()
+ },
+ environment: CredentialEnvironment(
+ authConfiguration: .identityPools(.testData),
+ credentialStoreEnvironment: BasicCredentialStoreEnvironment(
+ amplifyCredentialStoreFactory: {
+ MockAmplifyCredentialStoreBehavior(
+ saveCredentialHandler: { codableCredentials in
+ guard let amplifyCredentials = codableCredentials as? AmplifyCredentials,
+ case .identityPoolWithFederation(let token, _, _) = amplifyCredentials else {
+ XCTFail("Expected .identityPoolWithFederation")
+ return
+ }
+
+ XCTAssertEqual(token.token, "token")
+ XCTAssertEqual(token.provider.userPoolProviderName, "provider")
+ }
+ )
+ },
+ legacyKeychainStoreFactory: { _ in
+ let data = try! JSONEncoder().encode([
+ "provider": "token"
+ ])
+ return MockKeychainStoreBehavior(
+ data: String(decoding: data, as: UTF8.self)
+ )
+ }),
+ logger: action.log
+ )
+ )
+ await fulfillment(of: [expectation], timeout: 1)
+ }
}
diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchUserPoolTokens/RefreshHostedUITokensTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchUserPoolTokens/RefreshHostedUITokensTests.swift
new file mode 100644
index 0000000000..e00955e096
--- /dev/null
+++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/FetchAuthSession/FetchUserPoolTokens/RefreshHostedUITokensTests.swift
@@ -0,0 +1,289 @@
+//
+// Copyright Amazon.com Inc. or its affiliates.
+// All Rights Reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#if os(iOS) || os(macOS)
+
+@testable import AWSCognitoAuthPlugin
+import AWSCognitoIdentityProvider
+import AWSPluginsCore
+import XCTest
+
+class RefreshHostedUITokensTests: XCTestCase {
+ private let tokenResult: [String: Any] = [
+ "id_token": AWSCognitoUserPoolTokens.testData.idToken,
+ "access_token": AWSCognitoUserPoolTokens.testData.accessToken,
+ "refresh_token": AWSCognitoUserPoolTokens.testData.refreshToken,
+ "expires_in": 10
+ ]
+
+ override func setUp() {
+ let result = try! JSONSerialization.data(withJSONObject: tokenResult)
+ MockURLProtocol.requestHandler = { _ in
+ return (HTTPURLResponse(), result)
+ }
+ }
+
+ override func tearDown() {
+ MockURLProtocol.requestHandler = nil
+ }
+
+ func testValidSuccessfulResponse() async {
+ let expectation = expectation(description: "refreshHostedUITokens")
+ let action = RefreshHostedUITokens(existingSignedIndata: .testData)
+ action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? RefreshSessionEvent,
+ case .refreshIdentityInfo(let data, _) = event.eventType else {
+ XCTFail("Failed to refresh tokens")
+ expectation.fulfill()
+ return
+ }
+
+ XCTAssertEqual(data.cognitoUserPoolTokens.idToken, self.tokenResult["id_token"] as? String)
+ XCTAssertEqual(data.cognitoUserPoolTokens.accessToken, self.tokenResult["access_token"] as? String)
+ XCTAssertEqual(data.cognitoUserPoolTokens.refreshToken, self.tokenResult["refresh_token"] as? String)
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: hostedUIEnvironment
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testServiceError() async {
+ let expectedError = HostedUIError.serviceMessage("Something went wrong")
+ MockURLProtocol.requestHandler = { _ in
+ throw expectedError
+ }
+
+ let expectation = expectation(description: "refreshHostedUITokens")
+ let action = RefreshHostedUITokens(existingSignedIndata: .testData)
+ action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? RefreshSessionEvent,
+ case let .throwError(error) = event.eventType else {
+ XCTFail("Expected failure due to Service Error")
+ expectation.fulfill()
+ return
+ }
+
+ XCTAssertEqual(error, .service(expectedError))
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: hostedUIEnvironment
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testEmptyData() async {
+ MockURLProtocol.requestHandler = { _ in
+ return (HTTPURLResponse(), Data())
+ }
+
+ let expectation = expectation(description: "refreshHostedUITokens")
+ let action = RefreshHostedUITokens(existingSignedIndata: .testData)
+ action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? RefreshSessionEvent,
+ case let .throwError(error) = event.eventType else {
+ XCTFail("Expected failure due to Invalid Tokens")
+ expectation.fulfill()
+ return
+ }
+
+ guard case .service(let serviceError) = error else {
+ XCTFail("Expected FetchSessionError.service, got \(error)")
+ expectation.fulfill()
+ return
+ }
+
+
+ XCTAssertEqual((serviceError as NSError).code, NSPropertyListReadCorruptError)
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: hostedUIEnvironment
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testInvalidTokens() async {
+ let result: [String: Any] = [
+ "key": "value"
+ ]
+ MockURLProtocol.requestHandler = { _ in
+ return (HTTPURLResponse(), try! JSONSerialization.data(withJSONObject: result))
+ }
+
+ let expectation = expectation(description: "refreshHostedUITokens")
+ let action = RefreshHostedUITokens(existingSignedIndata: .testData)
+ action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? RefreshSessionEvent,
+ case let .throwError(error) = event.eventType else {
+ XCTFail("Expected failure due to Invalid Tokens")
+ expectation.fulfill()
+ return
+ }
+
+
+ XCTAssertEqual(error, .invalidTokens)
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: hostedUIEnvironment
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testErrorResponse() async {
+ let result: [String: Any] = [
+ "error": "Error.",
+ "error_description": "Something went wrong"
+ ]
+ MockURLProtocol.requestHandler = { _ in
+ return (HTTPURLResponse(), try! JSONSerialization.data(withJSONObject: result))
+ }
+
+ let expectation = expectation(description: "refreshHostedUITokens")
+ let action = RefreshHostedUITokens(existingSignedIndata: .testData)
+ action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? RefreshSessionEvent,
+ case let .throwError(error) = event.eventType else {
+ XCTFail("Expected failure due to Invalid Tokens")
+ expectation.fulfill()
+ return
+ }
+
+ guard case .service(let serviceError) = error,
+ case .serviceMessage(let errorMessage) = serviceError as? HostedUIError else {
+ XCTFail("Expected HostedUIError.serviceMessage, got \(error)")
+ expectation.fulfill()
+ return
+ }
+
+
+ XCTAssertEqual(errorMessage, "Error. Something went wrong")
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: hostedUIEnvironment
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testNoHostedUIEnvironment() async {
+ let expectation = expectation(description: "noHostedUIEnvironment")
+ let action = RefreshHostedUITokens(existingSignedIndata: .testData)
+ action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? RefreshSessionEvent,
+ case let .throwError(error) = event.eventType else {
+ XCTFail("Expected failure due to no HostedUIEnvironment")
+ expectation.fulfill()
+ return
+ }
+
+ XCTAssertEqual(error, .noUserPool)
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: nil
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testNoUserPoolEnvironment() async {
+ let expectation = expectation(description: "noUserPoolEnvironment")
+ let action = RefreshHostedUITokens(existingSignedIndata: .testData)
+ action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? RefreshSessionEvent,
+ case let .throwError(error) = event.eventType else {
+ XCTFail("Expected failure due to no UserPoolEnvironment")
+ expectation.fulfill()
+ return
+ }
+
+ XCTAssertEqual(error, .noUserPool)
+ expectation.fulfill()
+ },
+ environment: MockInvalidEnvironment()
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ private var hostedUIEnvironment: HostedUIEnvironment {
+ BasicHostedUIEnvironment(
+ configuration: .init(
+ clientId: "clientId",
+ oauth: .init(
+ domain: "cognitodomain",
+ scopes: ["name"],
+ signInRedirectURI: "myapp://",
+ signOutRedirectURI: "myapp://"
+ )
+ ),
+ hostedUISessionFactory: sessionFactory,
+ urlSessionFactory: urlSessionMock,
+ randomStringFactory: mockRandomString
+ )
+ }
+
+ private func identityProviderFactory() throws -> CognitoUserPoolBehavior {
+ return MockIdentityProvider(
+ mockInitiateAuthResponse: { _ in
+ return InitiateAuthOutputResponse(
+ authenticationResult: .init(
+ accessToken: "accessTokenNew",
+ expiresIn: 100,
+ idToken: "idTokenNew",
+ refreshToken: "refreshTokenNew")
+ )
+ }
+ )
+ }
+
+ private func urlSessionMock() -> URLSession {
+ let configuration = URLSessionConfiguration.ephemeral
+ configuration.protocolClasses = [MockURLProtocol.self]
+ return URLSession(configuration: configuration)
+ }
+
+ private func sessionFactory() -> HostedUISessionBehavior {
+ MockHostedUISession(result: .failure(.cancelled))
+ }
+
+ private func mockRandomString() -> RandomStringBehavior {
+ return MockRandomStringGenerator(
+ mockString: "mockString",
+ mockUUID: "mockUUID"
+ )
+ }
+}
+#endif
diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/VerifyDevicePasswordSRPSignatureTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/VerifyDevicePasswordSRPSignatureTests.swift
new file mode 100644
index 0000000000..495b2f52dd
--- /dev/null
+++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/InitiateAuthSRP/VerifyDevicePasswordSRPSignatureTests.swift
@@ -0,0 +1,162 @@
+//
+// Copyright Amazon.com Inc. or its affiliates.
+// All Rights Reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+
+@testable import AWSCognitoAuthPlugin
+import AWSCognitoIdentityProvider
+@testable import AWSPluginsTestCommon
+import XCTest
+
+class VerifyDevicePasswordSRPSignatureTests: XCTestCase {
+ private var srpClient: MockSRPClientBehavior!
+
+ override func setUp() async throws {
+ MockSRPClientBehavior.reset()
+ srpClient = MockSRPClientBehavior()
+ }
+
+ override func tearDown() {
+ MockSRPClientBehavior.reset()
+ srpClient = nil
+ }
+
+ func testSignature_withValidValues_shouldReturnSignature() async {
+ do {
+ let signature = try signature()
+ XCTAssertFalse(signature.isEmpty)
+ } catch {
+ XCTFail("Should not throw error: \(error)")
+ }
+ }
+
+ func testSignature_withSRPErrorOnSharedSecret_shouldThrowCalculationError() async {
+ srpClient.sharedSecret = .failure(SRPError.numberConversion)
+ do {
+ try signature()
+ XCTFail("Should not succeed")
+ } catch {
+ guard case .calculation(let srpError) = error as? SignInError else {
+ XCTFail("Expected SRPError.calculation, got \(error)")
+ return
+ }
+
+ XCTAssertEqual(srpError, .numberConversion)
+ }
+ }
+
+ func testSignature_withOtherErrorOnSharedSecret_shouldThrowCalculationError() async {
+ srpClient.sharedSecret = .failure(CancellationError())
+ do {
+ try signature()
+ XCTFail("Should not succeed")
+ } catch {
+ guard case .configuration(let message) = error as? SignInError else {
+ XCTFail("Expected SRPError.configuration, got \(error)")
+ return
+ }
+
+ XCTAssertEqual(message, "Could not calculate shared secret")
+ }
+ }
+
+ func testSignature_withSRPErrorOnAuthenticationKey_shouldThrowCalculationError() async {
+ MockSRPClientBehavior.authenticationKey = .failure(SRPError.numberConversion)
+ do {
+ try signature()
+ XCTFail("Should not succeed")
+ } catch {
+ guard case .calculation(let srpError) = error as? SignInError else {
+ XCTFail("Expected SRPError.calculation, got \(error)")
+ return
+ }
+
+ XCTAssertEqual(srpError, .numberConversion)
+ }
+ }
+
+ func testSignature_withOtherErrorOnAuthenticationKey_shouldThrowCalculationError() async {
+ MockSRPClientBehavior.authenticationKey = .failure(CancellationError())
+ do {
+ try signature()
+ XCTFail("Should not succeed")
+ } catch {
+ guard case .configuration(let message) = error as? SignInError else {
+ XCTFail("Expected SRPError.configuration, got \(error)")
+ return
+ }
+
+ XCTAssertEqual(message, "Could not calculate signature")
+ }
+ }
+
+ @discardableResult
+ private func signature() throws -> String {
+ let action = VerifyDevicePasswordSRP(
+ stateData: .testData,
+ authResponse: InitiateAuthOutputResponse.validTestData
+ )
+
+ return try action.signature(
+ deviceGroupKey: "deviceGroupKey",
+ deviceKey: "deviceKey",
+ deviceSecret: "deviceSecret",
+ saltHex: "saltHex",
+ secretBlock: "secretBlock".data(using: .utf8) ?? Data(),
+ serverPublicBHexString: "serverPublicBHexString",
+ srpClient: srpClient
+ )
+ }
+}
+
+private class MockSRPClientBehavior: SRPClientBehavior {
+ var kHexValue: String = "kHexValue"
+
+ static func calculateUHexValue(
+ clientPublicKeyHexValue: String,
+ serverPublicKeyHexValue: String
+ ) throws -> String {
+ return "UHexValue"
+ }
+
+ static var authenticationKey: Result = .success("AuthenticationKey".data(using: .utf8)!)
+ static func generateAuthenticationKey(
+ sharedSecretHexValue: String,
+ uHexValue: String
+ ) throws -> Data {
+ return try authenticationKey.get()
+ }
+
+ static func reset() {
+ authenticationKey = .success("AuthenticationKey".data(using: .utf8)!)
+ }
+
+ func generateClientKeyPair() -> SRPKeys {
+ return .init(
+ publicKeyHexValue: "publicKeyHexValue",
+ privateKeyHexValue: "privateKeyHexValue"
+ )
+ }
+
+ var sharedSecret: Result = .success("SharedSecret")
+ func calculateSharedSecret(
+ username: String,
+ password: String,
+ saltHexValue: String,
+ clientPrivateKeyHexValue: String,
+ clientPublicKeyHexValue: String,
+ serverPublicKeyHexValue: String
+ ) throws -> String {
+ return try sharedSecret.get()
+ }
+
+ func generateDevicePasswordVerifier(
+ deviceGroupKey: String,
+ deviceKey: String,
+ password: String
+ ) -> (salt: Data, passwordVerifier: Data) {
+ return (salt: Data(), passwordVerifier: Data())
+ }
+}
diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/ShowHostedUISignOutTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/ShowHostedUISignOutTests.swift
new file mode 100644
index 0000000000..abc6dafa87
--- /dev/null
+++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/SignOut/ShowHostedUISignOutTests.swift
@@ -0,0 +1,377 @@
+//
+// Copyright Amazon.com Inc. or its affiliates.
+// All Rights Reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+
+@testable import AWSCognitoAuthPlugin
+import AWSCognitoIdentityProvider
+import AWSPluginsCore
+import XCTest
+
+class ShowHostedUISignOutTests: XCTestCase {
+ private var mockHostedUIResult: Result<[URLQueryItem], HostedUIError>!
+ private var signOutRedirectURI: String!
+
+ override func setUp() {
+ signOutRedirectURI = "myapp://"
+ mockHostedUIResult = .success([.init(name: "key", value: "value")])
+ }
+
+ override func tearDown() {
+ signOutRedirectURI = nil
+ mockHostedUIResult = nil
+ }
+
+ func testExecute_withGlobalSignOut_andSuccessResult_shouldDispatchSignOutEvent() async {
+ let expectation = expectation(description: "showHostedUISignOut")
+ let signInData = SignedInData.testData
+ let action = ShowHostedUISignOut(
+ signOutEvent: SignOutEventData(globalSignOut: true),
+ signInData: signInData
+ )
+
+ await action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? SignOutEvent,
+ case .signOutGlobally(let data, let error) = event.eventType else {
+ XCTFail("Expected SignOutEvent.signOutGlobally, got \(event)")
+ expectation.fulfill()
+ return
+ }
+
+ XCTAssertNil(error)
+ XCTAssertEqual(data, signInData)
+ self.validateDebugInformation(signInData: signInData, action: action)
+
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: hostedUIEnvironment
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testExecute_withLocalSignOut_andSuccessResult_shouldDispatchSignOutEvent() async {
+ let expectation = expectation(description: "showHostedUISignOut")
+ let signInData = SignedInData.testData
+ let action = ShowHostedUISignOut(
+ signOutEvent: SignOutEventData(globalSignOut: false),
+ signInData: signInData
+ )
+
+ await action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? SignOutEvent,
+ case .revokeToken(let data, let error, let globalSignOutError) = event.eventType else {
+ XCTFail("Expected SignOutEvent.revokeToken, got \(event)")
+ expectation.fulfill()
+ return
+ }
+
+ XCTAssertNil(error)
+ XCTAssertNil(globalSignOutError)
+ XCTAssertEqual(data, signInData)
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: hostedUIEnvironment
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testExecute_withInvalidResult_shouldDispatchUserCancelledEvent() async {
+ mockHostedUIResult = .failure(.cancelled)
+ let signInData = SignedInData.testData
+
+ let action = ShowHostedUISignOut(
+ signOutEvent: .testData,
+ signInData: signInData
+ )
+
+ let expectation = expectation(description: "showHostedUISignOut")
+ await action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? SignOutEvent else {
+ XCTFail("Expected SignOutEvent, got \(event)")
+ expectation.fulfill()
+ return
+ }
+
+ XCTAssertEqual(event.eventType, .userCancelled)
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: hostedUIEnvironment
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testExecute_withSignOutURIError_shouldThrowConfigurationError() async {
+ mockHostedUIResult = .failure(HostedUIError.signOutURI)
+ let signInData = SignedInData.testData
+
+ let action = ShowHostedUISignOut(
+ signOutEvent: .testData,
+ signInData: signInData
+ )
+
+ let expectation = expectation(description: "showHostedUISignOut")
+ await action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? SignOutEvent,
+ case .signOutGlobally(let data, let hostedUIError) = event.eventType else {
+ XCTFail("Expected SignOutEvent.signOutGlobally, got \(event)")
+ expectation.fulfill()
+ return
+ }
+
+ guard let hostedUIError = hostedUIError,
+ case .configuration(let errorDescription, _, let serviceError) = hostedUIError.error else {
+ XCTFail("Expected AuthError.configuration")
+ expectation.fulfill()
+ return
+ }
+
+ XCTAssertEqual(errorDescription, "Could not create logout URL")
+ XCTAssertEqual(data, signInData)
+ XCTAssertNil(serviceError)
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: hostedUIEnvironment
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testExecute_withInvalidContext_shouldThrowInvalidStateError() async {
+ mockHostedUIResult = .failure(HostedUIError.invalidContext)
+ let signInData = SignedInData.testData
+
+ let action = ShowHostedUISignOut(
+ signOutEvent: .testData,
+ signInData: signInData
+ )
+
+ let expectation = expectation(description: "showHostedUISignOut")
+ await action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? SignOutEvent,
+ case .signOutGlobally(let data, let hostedUIError) = event.eventType else {
+ XCTFail("Expected SignOutEvent.signOutGlobally, got \(event)")
+ expectation.fulfill()
+ return
+ }
+
+ guard let hostedUIError = hostedUIError,
+ case .invalidState(let errorDescription, let recoverySuggestion, let serviceError) = hostedUIError.error else {
+ XCTFail("Expected AuthError.invalidState")
+ expectation.fulfill()
+ return
+ }
+
+ XCTAssertEqual(errorDescription, AuthPluginErrorConstants.hostedUIInvalidPresentation.errorDescription)
+ XCTAssertEqual(recoverySuggestion, AuthPluginErrorConstants.hostedUIInvalidPresentation.recoverySuggestion)
+ XCTAssertEqual(data, signInData)
+ XCTAssertNil(serviceError)
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: hostedUIEnvironment
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testExecute_withInvalidSignOutURI_shouldThrowConfigurationError() async {
+ signOutRedirectURI = "invalidURI"
+ let signInData = SignedInData.testData
+
+ let action = ShowHostedUISignOut(
+ signOutEvent: .testData,
+ signInData: signInData
+ )
+
+ let expectation = expectation(description: "showHostedUISignOut")
+ await action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? SignOutEvent,
+ case .signOutGlobally(let data, let hostedUIError) = event.eventType else {
+ XCTFail("Expected SignOutEvent.signOutGlobally, got \(event)")
+ expectation.fulfill()
+ return
+ }
+
+ guard let hostedUIError = hostedUIError,
+ case .configuration(let errorDescription, _, let serviceError) = hostedUIError.error else {
+ XCTFail("Expected AuthError.configuration")
+ expectation.fulfill()
+ return
+ }
+
+ XCTAssertEqual(errorDescription, "Callback URL could not be retrieved")
+ XCTAssertEqual(data, signInData)
+ XCTAssertNil(serviceError)
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: hostedUIEnvironment
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testExecute_withoutHostedUIEnvironment_shouldThrowConfigurationError() async {
+ let expectation = expectation(description: "noHostedUIEnvironment")
+ let signInData = SignedInData.testData
+ let action = ShowHostedUISignOut(
+ signOutEvent: .testData,
+ signInData: signInData
+ )
+ await action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? SignOutEvent,
+ case .signOutGlobally(let data, let hostedUIError) = event.eventType else {
+ XCTFail("Expected SignOutEvent.signOutGlobally, got \(event)")
+ expectation.fulfill()
+ return
+ }
+
+ guard let hostedUIError = hostedUIError,
+ case .configuration(let errorDescription, _, let serviceError) = hostedUIError.error else {
+ XCTFail("Expected AuthError.configuration")
+ expectation.fulfill()
+ return
+ }
+
+ XCTAssertEqual(data, signInData)
+ XCTAssertEqual(errorDescription, AuthPluginErrorConstants.configurationError)
+ XCTAssertNil(serviceError)
+ expectation.fulfill()
+ },
+ environment: Defaults.makeDefaultAuthEnvironment(
+ userPoolFactory: identityProviderFactory,
+ hostedUIEnvironment: nil
+ )
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ func testExecute_withInvalidUserPoolEnvironment_shouldThrowConfigurationError() async {
+ let expectation = expectation(description: "invalidUserPoolEnvironment")
+ let signInData = SignedInData.testData
+ let action = ShowHostedUISignOut(
+ signOutEvent: .testData,
+ signInData: signInData
+ )
+ await action.execute(
+ withDispatcher: MockDispatcher { event in
+ guard let event = event as? SignOutEvent,
+ case .signOutGlobally(let data, let hostedUIError) = event.eventType else {
+ XCTFail("Expected SignOutEvent.signOutGlobally, got \(event)")
+ expectation.fulfill()
+ return
+ }
+
+ guard let hostedUIError = hostedUIError,
+ case .configuration(let errorDescription, _, let serviceError) = hostedUIError.error else {
+ XCTFail("Expected AuthError.configuration")
+ expectation.fulfill()
+ return
+ }
+
+ XCTAssertEqual(data, signInData)
+ XCTAssertEqual(errorDescription, AuthPluginErrorConstants.configurationError)
+ XCTAssertNil(serviceError)
+ expectation.fulfill()
+ },
+ environment: MockInvalidEnvironment()
+ )
+
+ await fulfillment(of: [expectation], timeout: 1)
+ }
+
+ private func validateDebugInformation(signInData: SignedInData, action: ShowHostedUISignOut) {
+ XCTAssertFalse(action.debugDescription.isEmpty)
+ guard let signInDataDictionary = action.debugDictionary["signInData"] as? [String: Any] else {
+ XCTFail("Expected signInData dictionary")
+ return
+ }
+ XCTAssertEqual(signInDataDictionary.count, signInData.debugDictionary.count)
+
+ for key in signInDataDictionary.keys {
+ guard let left = signInDataDictionary[key] as? any Equatable,
+ let right = signInData.debugDictionary[key] as? any Equatable else {
+ continue
+ }
+ XCTAssertTrue(left.isEqual(to: right))
+ }
+ }
+
+ private var hostedUIEnvironment: HostedUIEnvironment {
+ BasicHostedUIEnvironment(
+ configuration: .init(
+ clientId: "clientId",
+ oauth: .init(
+ domain: "cognitodomain",
+ scopes: ["name"],
+ signInRedirectURI: "myapp://",
+ signOutRedirectURI: signOutRedirectURI
+ )
+ ),
+ hostedUISessionFactory: {
+ MockHostedUISession(result: self.mockHostedUIResult)
+ },
+ urlSessionFactory: {
+ URLSession.shared
+ },
+ randomStringFactory: {
+ MockRandomStringGenerator(
+ mockString: "mockString",
+ mockUUID: "mockUUID"
+ )
+ }
+ )
+ }
+
+ private func identityProviderFactory() throws -> CognitoUserPoolBehavior {
+ return MockIdentityProvider(
+ mockInitiateAuthResponse: { _ in
+ return InitiateAuthOutputResponse(
+ authenticationResult: .init(
+ accessToken: "accessTokenNew",
+ expiresIn: 100,
+ idToken: "idTokenNew",
+ refreshToken: "refreshTokenNew")
+ )
+ }
+ )
+ }
+}
+
+private extension Equatable {
+ func isEqual(to other: any Equatable) -> Bool {
+ guard let other = other as? Self else {
+ return false
+ }
+ return self == other
+ }
+}
diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CognitoASFTests/CognitoUserPoolASFTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CognitoASFTests/CognitoUserPoolASFTests.swift
new file mode 100644
index 0000000000..3e7a6e9110
--- /dev/null
+++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CognitoASFTests/CognitoUserPoolASFTests.swift
@@ -0,0 +1,45 @@
+//
+// Copyright Amazon.com Inc. or its affiliates.
+// All Rights Reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+
+@testable import AWSCognitoAuthPlugin
+import XCTest
+
+class CognitoUserPoolASFTests: XCTestCase {
+ private var pool: CognitoUserPoolASF!
+
+ override func setUp() {
+ pool = CognitoUserPoolASF()
+ }
+
+ override func tearDown() {
+ pool = nil
+ }
+
+ func testUserContextData_shouldReturnData() throws {
+ let result = try pool.userContextData(
+ for: "TestUser",
+ deviceInfo: ASFDeviceInfo(id: "mockedDevice"),
+ appInfo: ASFAppInfo(),
+ configuration: .testData
+ )
+ XCTAssertFalse(result.isEmpty)
+ }
+
+ func testcalculateSecretHash_withInvalidClientId_shouldThrowHashKeyError() {
+ do {
+ let result = try pool.calculateSecretHash(
+ contextJson: "contextJson",
+ clientId: "🕺🏼"
+ )
+ XCTFail("Expected ASFError.hashKey, got \(result)")
+ } catch let error as ASFError {
+ XCTAssertEqual(error, .hashKey)
+ } catch {
+ XCTFail("Expected ASFError.hashKey, for \(error)")
+ }
+ }
+}
diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ConfigurationTests/EscapeHatchTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ConfigurationTests/EscapeHatchTests.swift
index d385c628d8..acf3ec1c0d 100644
--- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ConfigurationTests/EscapeHatchTests.swift
+++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ConfigurationTests/EscapeHatchTests.swift
@@ -6,143 +6,116 @@
//
import XCTest
-@testable import Amplify
+@testable import func AmplifyTestCommon.XCTAssertThrowFatalError
+import enum Amplify.JSONValue
@testable import AWSCognitoAuthPlugin
-class EscapeHatchTests: XCTestCase {
-
- let skipBrokenTests = true
-
- override func tearDown() async throws {
- await Amplify.reset()
- }
+class EscapeHatchTests: XCTestCase {
/// Test escape hatch with valid config for user pool and identity pool
///
- /// - Given: Given valid config for user pool and identity pool
+ /// - Given: A AWSCognitoAuthPlugin configured with User Pool and Identity Pool
/// - When:
- /// - I configure auth with the given configuration and call getEscapeHatch
+ /// - I call getEscapeHatch
/// - Then:
- /// - I should get back user pool and identity pool clients
+ /// - I should get back both the User Pool and Identity Pool clients
///
func testEscapeHatchWithUserPoolAndIdentityPool() throws {
- if skipBrokenTests {
- throw XCTSkip("TODO: fix this test")
- }
-
- let plugin = AWSCognitoAuthPlugin()
- try Amplify.add(plugin: plugin)
-
- let expectation = expectation(description: "Should get service")
- let categoryConfig = AuthCategoryConfiguration(plugins: [
- "awsCognitoAuthPlugin": [
- "CredentialsProvider": ["CognitoIdentity": ["Default":
- ["PoolId": "xx",
- "Region": "us-east-1"]
- ]],
- "CognitoUserPool": ["Default": [
+ let configuration: JSONValue = [
+ "CredentialsProvider": [
+ "CognitoIdentity": [
+ "Default": [
+ "PoolId": "xx",
+ "Region": "us-east-1"
+ ]
+ ]
+ ],
+ "CognitoUserPool": [
+ "Default": [
"PoolId": "xx",
"Region": "us-east-1",
"AppClientId": "xx",
- "AppClientSecret": "xx"]]
+ "AppClientSecret": "xx"
+ ]
]
- ])
- let amplifyConfig = AmplifyConfiguration(auth: categoryConfig)
- try Amplify.configure(amplifyConfig)
- let internalPlugin = try Amplify.Auth.getPlugin(
- for: "awsCognitoAuthPlugin"
- ) as! AWSCognitoAuthPlugin
- let service = internalPlugin.getEscapeHatch()
- switch service {
- case .userPool:
- XCTFail("Should return userPoolAndIdentityPool")
- case .identityPool:
- XCTFail("Should return userPoolAndIdentityPool")
- case .userPoolAndIdentityPool:
- expectation.fulfill()
+ ]
+ let plugin = AWSCognitoAuthPlugin()
+ try plugin.configure(using: configuration)
+ let escapeHatch = plugin.getEscapeHatch()
+ guard case .userPoolAndIdentityPool = escapeHatch else {
+ XCTFail("Expected .userPoolAndIdentityPool, got \(escapeHatch)")
+ return
}
- wait(for: [expectation], timeout: 1)
}
/// Test escape hatch with valid config for only identity pool
///
- /// - Given: Given valid config for only identity pool
+ /// - Given: A AWSCognitoAuthPlugin configured with only Identity Pool
/// - When:
- /// - I configure auth with the given configuration and invoke getEscapeHatch
+ /// - I call getEscapeHatch
/// - Then:
- /// - I should get back only identity pool client
+ /// - I should get back only the Identity Pool client
///
func testEscapeHatchWithOnlyIdentityPool() throws {
- if skipBrokenTests {
- throw XCTSkip("TODO: fix this test")
- }
-
- let plugin = AWSCognitoAuthPlugin()
- try Amplify.add(plugin: plugin)
-
- let categoryConfig = AuthCategoryConfiguration(plugins: [
- "awsCognitoAuthPlugin": [
- "CredentialsProvider": ["CognitoIdentity": ["Default":
- ["PoolId": "cc",
- "Region": "us-east-1"]
- ]]
+ let configuration: JSONValue = [
+ "CredentialsProvider": [
+ "CognitoIdentity": [
+ "Default": [
+ "PoolId": "xx",
+ "Region": "us-east-1"
+ ]
+ ]
]
- ])
- let amplifyConfig = AmplifyConfiguration(auth: categoryConfig)
- try Amplify.configure(amplifyConfig)
- let internalPlugin = try Amplify.Auth.getPlugin(
- for: "awsCognitoAuthPlugin"
- ) as! AWSCognitoAuthPlugin
- let service = internalPlugin.getEscapeHatch()
- switch service {
- case .userPool:
- XCTFail("Should return identityPool")
- case .userPoolAndIdentityPool:
- XCTFail("Should return identityPool")
- case .identityPool:
- print("")
+ ]
+ let plugin = AWSCognitoAuthPlugin()
+ try plugin.configure(using: configuration)
+ let escapeHatch = plugin.getEscapeHatch()
+ guard case .identityPool = escapeHatch else {
+ XCTFail("Expected .identityPool, got \(escapeHatch)")
+ return
}
}
/// Test escape hatch with valid config for only user pool
///
- /// - Given: Given valid config for only user pool
+ /// - Given: A AWSCognitoAuthPlugin configured with only User Pool
/// - When:
- /// - I configure auth with the given configuration and invoke getEscapeHatch
+ /// - I call getEscapeHatch
/// - Then:
- /// - I should get the Cognito User pool client
+ /// - I should get only the User Pool client
///
func testEscapeHatchWithOnlyUserPool() throws {
- if skipBrokenTests {
- throw XCTSkip("TODO: fix this test")
- }
-
- let plugin = AWSCognitoAuthPlugin()
- try Amplify.add(plugin: plugin)
-
- let categoryConfig = AuthCategoryConfiguration(plugins: [
- "awsCognitoAuthPlugin": [
- "CognitoUserPool": ["Default": [
+ let configuration: JSONValue = [
+ "CognitoUserPool": [
+ "Default": [
"PoolId": "xx",
"Region": "us-east-1",
"AppClientId": "xx",
- "AppClientSecret": "xx"]]
+ "AppClientSecret": "xx"
+ ]
]
- ])
- let amplifyConfig = AmplifyConfiguration(auth: categoryConfig)
- try Amplify.configure(amplifyConfig)
- let internalPlugin = try Amplify.Auth.getPlugin(
- for: "awsCognitoAuthPlugin"
- ) as! AWSCognitoAuthPlugin
- let service = internalPlugin.getEscapeHatch()
- switch service {
- case .userPool:
- break
- case .identityPool:
- XCTFail("Should return userPool")
- case .userPoolAndIdentityPool:
- XCTFail("Should return userPool")
+ ]
+ let plugin = AWSCognitoAuthPlugin()
+ try plugin.configure(using: configuration)
+ let escapeHatch = plugin.getEscapeHatch()
+ guard case .userPool = escapeHatch else {
+ XCTFail("Expected .userPool, got \(escapeHatch)")
+ return
+ }
+ }
+
+ /// Test escape hatch without a valid configuration
+ ///
+ /// - Given: A AWSCognitoAuthPlugin plugin without being configured
+ /// - When:
+ /// - I call getEscapeHatch
+ /// - Then:
+ /// - A fatalError is thrown
+ ///
+ func testEscapeHatchWithoutConfiguration() throws {
+ let plugin = AWSCognitoAuthPlugin()
+ try XCTAssertThrowFatalError {
+ _ = plugin.getEscapeHatch()
}
}
-
}
diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/AWSAuthCognitoSessionTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/AWSAuthCognitoSessionTests.swift
index fdb8862284..53075a7adc 100644
--- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/AWSAuthCognitoSessionTests.swift
+++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/AWSAuthCognitoSessionTests.swift
@@ -26,8 +26,7 @@ class AWSAuthCognitoSessionTests: XCTestCase {
let error = AuthError.unknown("", nil)
let tokens = AWSCognitoUserPoolTokens(idToken: CognitoAuthTestHelper.buildToken(for: tokenData),
accessToken: CognitoAuthTestHelper.buildToken(for: tokenData),
- refreshToken: "refreshToken",
- expiresIn: 121)
+ refreshToken: "refreshToken")
let session = AWSAuthCognitoSession(isSignedIn: true,
identityIdResult: .failure(error),
@@ -53,8 +52,7 @@ class AWSAuthCognitoSessionTests: XCTestCase {
let error = AuthError.unknown("", nil)
let tokens = AWSCognitoUserPoolTokens(idToken: CognitoAuthTestHelper.buildToken(for: tokenData),
accessToken: CognitoAuthTestHelper.buildToken(for: tokenData),
- refreshToken: "refreshToken",
- expiresIn: 121)
+ refreshToken: "refreshToken")
let session = AWSAuthCognitoSession(isSignedIn: true,
identityIdResult: .failure(error),
@@ -65,4 +63,161 @@ class AWSAuthCognitoSessionTests: XCTestCase {
XCTAssertFalse(cognitoTokens.doesExpire())
}
+ func testGetUserSub_shouldReturnResult() {
+ let tokenData = [
+ "sub": "1234567890",
+ "name": "John Doe",
+ "iat": "1516239022",
+ "exp": String(Date(timeIntervalSinceNow: 121).timeIntervalSince1970)
+ ]
+
+ let error = AuthError.unknown("", nil)
+ let tokens = AWSCognitoUserPoolTokens(
+ idToken: CognitoAuthTestHelper.buildToken(for: tokenData),
+ accessToken: CognitoAuthTestHelper.buildToken(for: tokenData),
+ refreshToken: "refreshToken"
+ )
+
+ let session = AWSAuthCognitoSession(
+ isSignedIn: true,
+ identityIdResult: .failure(error),
+ awsCredentialsResult: .failure(error),
+ cognitoTokensResult: .success(tokens)
+ )
+
+ guard case .success(let userSub) = session.getUserSub() else {
+ XCTFail("Unable to retrieve userSub")
+ return
+ }
+ XCTAssertEqual(userSub, "1234567890")
+ }
+
+ func testGetUserSub_withoutSub_shouldReturnError() {
+ let tokenData = [
+ "name": "John Doe",
+ "iat": "1516239022",
+ "exp": String(Date(timeIntervalSinceNow: 121).timeIntervalSince1970)
+ ]
+
+ let error = AuthError.unknown("", nil)
+ let tokens = AWSCognitoUserPoolTokens(
+ idToken: CognitoAuthTestHelper.buildToken(for: tokenData),
+ accessToken: CognitoAuthTestHelper.buildToken(for: tokenData),
+ refreshToken: "refreshToken"
+ )
+
+ let session = AWSAuthCognitoSession(
+ isSignedIn: true,
+ identityIdResult: .failure(error),
+ awsCredentialsResult: .failure(error),
+ cognitoTokensResult: .success(tokens)
+ )
+
+ guard case .failure(let error) = session.getUserSub(),
+ case .unknown(let errorDescription, _) = error else {
+ XCTFail("Expected AuthError.unknown")
+ return
+ }
+
+ XCTAssertEqual(errorDescription, "Could not retreive user sub from the fetched Cognito tokens.")
+ }
+
+ func testGetUserSub_signedOut_shouldReturnError() {
+ let error = AuthError.signedOut("", "", nil)
+ let session = AWSAuthCognitoSession(
+ isSignedIn: false,
+ identityIdResult: .failure(error),
+ awsCredentialsResult: .failure(error),
+ cognitoTokensResult: .failure(error)
+ )
+
+ guard case .failure(let error) = session.getUserSub(),
+ case .signedOut(let errorDescription, let recoverySuggestion, _) = error else {
+ XCTFail("Expected AuthError.signedOut")
+ return
+ }
+
+ XCTAssertEqual(errorDescription, AuthPluginErrorConstants.userSubSignOutError.errorDescription)
+ XCTAssertEqual(recoverySuggestion, AuthPluginErrorConstants.userSubSignOutError.recoverySuggestion)
+ }
+
+ func testGetUserSub_serviceError_shouldReturnError() {
+ let serviceError = AuthError.service("Something went wrong", "Try again", nil)
+ let session = AWSAuthCognitoSession(
+ isSignedIn: false,
+ identityIdResult: .failure(serviceError),
+ awsCredentialsResult: .failure(serviceError),
+ cognitoTokensResult: .failure(serviceError)
+ )
+
+ guard case .failure(let error) = session.getUserSub() else {
+ XCTFail("Expected AuthError.signedOut")
+ return
+ }
+
+ XCTAssertEqual(error, serviceError)
+ }
+
+ func testSessionsAreEqual() {
+ let expiration = Date(timeIntervalSinceNow: 121)
+ let tokenData1 = [
+ "sub": "1234567890",
+ "name": "John Doe",
+ "iat": "1516239022",
+ "exp": String(expiration.timeIntervalSince1970)
+ ]
+
+ let credentials1 = AuthAWSCognitoCredentials(
+ accessKeyId: "accessKeyId",
+ secretAccessKey: "secretAccessKey",
+ sessionToken: "sessionToken",
+ expiration: expiration
+ )
+
+ let tokens1 = AWSCognitoUserPoolTokens(
+ idToken: CognitoAuthTestHelper.buildToken(for: tokenData1),
+ accessToken: CognitoAuthTestHelper.buildToken(for: tokenData1),
+ refreshToken: "refreshToken"
+ )
+
+ let session1 = AWSAuthCognitoSession(
+ isSignedIn: true,
+ identityIdResult: .success("identityId"),
+ awsCredentialsResult: .success(credentials1),
+ cognitoTokensResult: .success(tokens1)
+ )
+
+ let tokenData2 = [
+ "sub": "1234567890",
+ "name": "John Doe",
+ "iat": "1516239022",
+ "exp": String(expiration.timeIntervalSince1970)
+ ]
+
+ let credentials2 = AuthAWSCognitoCredentials(
+ accessKeyId: "accessKeyId",
+ secretAccessKey: "secretAccessKey",
+ sessionToken: "sessionToken",
+ expiration: expiration
+ )
+
+ let tokens2 = AWSCognitoUserPoolTokens(
+ idToken: CognitoAuthTestHelper.buildToken(for: tokenData2),
+ accessToken: CognitoAuthTestHelper.buildToken(for: tokenData2),
+ refreshToken: "refreshToken"
+ )
+
+ let session2 = AWSAuthCognitoSession(
+ isSignedIn: true,
+ identityIdResult: .success("identityId"),
+ awsCredentialsResult: .success(credentials2),
+ cognitoTokensResult: .success(tokens2)
+ )
+
+ XCTAssertEqual(session1, session2)
+ XCTAssertEqual(session1.debugDictionary.count, session2.debugDictionary.count)
+ for key in session1.debugDictionary.keys where key != "AWS Credentials" {
+ XCTAssertEqual(session1.debugDictionary[key] as? String, session2.debugDictionary[key] as? String)
+ }
+ }
}
diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIASWebAuthenticationSessionTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIASWebAuthenticationSessionTests.swift
new file mode 100644
index 0000000000..14a8c8c367
--- /dev/null
+++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIASWebAuthenticationSessionTests.swift
@@ -0,0 +1,233 @@
+//
+// Copyright Amazon.com Inc. or its affiliates.
+// All Rights Reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#if os(iOS) || os(macOS)
+import Amplify
+import AuthenticationServices
+@testable import AWSCognitoAuthPlugin
+import XCTest
+
+class HostedUIASWebAuthenticationSessionTests: XCTestCase {
+ private var session: HostedUIASWebAuthenticationSession!
+ private var factory: ASWebAuthenticationSessionFactory!
+
+ override func setUp() {
+ session = HostedUIASWebAuthenticationSession()
+ factory = ASWebAuthenticationSessionFactory()
+ session.authenticationSessionFactory = factory.createSession(url:callbackURLScheme:completionHandler:)
+ }
+
+ override func tearDown() {
+ session = nil
+ factory = nil
+ }
+
+ func testShowHostedUI_withUrlInCallback_withQueryItems_shouldReturnQueryItems() {
+ let expectation = expectation(description: "showHostedUI")
+ factory.mockedURL = createURL(queryItems: [.init(name: "name", value: "value")])
+
+ session.showHostedUI() { result in
+ do {
+ let queryItems = try result.get()
+ XCTAssertEqual(queryItems.count, 1)
+ XCTAssertEqual(queryItems.first?.name, "name")
+ XCTAssertEqual(queryItems.first?.value, "value")
+ } catch {
+ XCTFail("Expected .success(queryItems), got \(result)")
+ }
+ expectation.fulfill()
+ }
+ waitForExpectations(timeout: 1)
+ }
+
+ func testShowHostedUI_withUrlInCallback_withoutQueryItems_shouldReturnEmptyQueryItems() {
+ let expectation = expectation(description: "showHostedUI")
+ factory.mockedURL = createURL()
+
+ session.showHostedUI() { result in
+ do {
+ let queryItems = try result.get()
+ XCTAssertTrue(queryItems.isEmpty)
+ } catch {
+ XCTFail("Expected .success(queryItems), got \(result)")
+ }
+ expectation.fulfill()
+ }
+ waitForExpectations(timeout: 1)
+ }
+
+ func testShowHostedUI_withUrlInCallback_withErrorInQueryItems_shouldReturnServiceMessageError() {
+ let expectation = expectation(description: "showHostedUI")
+ factory.mockedURL = createURL(
+ queryItems: [
+ .init(name: "error", value: "Error."),
+ .init(name: "error_description", value: "Something went wrong")
+ ]
+ )
+
+ session.showHostedUI() { result in
+ do {
+ _ = try result.get()
+ XCTFail("Expected failure(.serviceMessage), got \(result)")
+ } catch let error as HostedUIError {
+ if case .serviceMessage(let message) = error {
+ XCTAssertEqual(message, "Error. Something went wrong")
+ } else {
+ XCTFail("Expected HostedUIError.serviceMessage, got \(error)")
+ }
+ } catch {
+ XCTFail("Expected HostedUIError.serviceMessage, got \(error)")
+ }
+ expectation.fulfill()
+ }
+ waitForExpectations(timeout: 1)
+ }
+
+ func testShowHostedUI_withASWebAuthenticationSessionErrors_shouldReturnRightError() {
+ let errorMap: [ASWebAuthenticationSessionError.Code: HostedUIError] = [
+ .canceledLogin: .cancelled,
+ .presentationContextNotProvided: .invalidContext,
+ .presentationContextInvalid: .invalidContext
+ ]
+
+ let errorCodes: [ASWebAuthenticationSessionError.Code] = [
+ .canceledLogin,
+ .presentationContextNotProvided,
+ .presentationContextInvalid,
+ .init(rawValue: 500)!
+ ]
+
+ for code in errorCodes {
+ factory.mockedError = ASWebAuthenticationSessionError(code)
+ let expectedError = errorMap[code] ?? .unknown
+ let expectation = expectation(description: "showHostedUI for error \(code)")
+ session.showHostedUI() { result in
+ do {
+ _ = try result.get()
+ XCTFail("Expected failure(.\(expectedError)), got \(result)")
+ } catch let error as HostedUIError {
+ XCTAssertEqual(error, expectedError)
+ } catch {
+ XCTFail("Expected HostedUIError.\(expectedError), got \(error)")
+ }
+ expectation.fulfill()
+ }
+ waitForExpectations(timeout: 1)
+ }
+ }
+
+ func testShowHostedUI_withOtherError_shouldReturnUnknownError() {
+ factory.mockedError = CancellationError()
+ let expectation = expectation(description: "showHostedUI")
+ session.showHostedUI() { result in
+ do {
+ _ = try result.get()
+ XCTFail("Expected failure(.unknown), got \(result)")
+ } catch let error as HostedUIError {
+ XCTAssertEqual(error, .unknown)
+ } catch {
+ XCTFail("Expected HostedUIError.unknown, got \(error)")
+ }
+ expectation.fulfill()
+ }
+ waitForExpectations(timeout: 1)
+ }
+
+ private func createURL(queryItems: [URLQueryItem] = []) -> URL {
+ var components = URLComponents(string: "https://test.com")!
+ components.queryItems = queryItems
+ return components.url!
+ }
+}
+
+class ASWebAuthenticationSessionFactory {
+ var mockedURL: URL?
+ var mockedError: Error?
+
+ func createSession(
+ url URL: URL,
+ callbackURLScheme: String?,
+ completionHandler: @escaping ASWebAuthenticationSession.CompletionHandler
+ ) -> ASWebAuthenticationSession {
+ let session = MockASWebAuthenticationSession(
+ url: URL,
+ callbackURLScheme: callbackURLScheme,
+ completionHandler: completionHandler
+ )
+ session.mockedURL = mockedURL
+ session.mockedError = mockedError
+ return session
+ }
+}
+
+class MockASWebAuthenticationSession: ASWebAuthenticationSession {
+ private var callback: ASWebAuthenticationSession.CompletionHandler
+ override init(
+ url URL: URL,
+ callbackURLScheme: String?,
+ completionHandler: @escaping ASWebAuthenticationSession.CompletionHandler
+ ) {
+ self.callback = completionHandler
+ super.init(
+ url: URL,
+ callbackURLScheme: callbackURLScheme,
+ completionHandler: completionHandler
+ )
+ }
+
+ var mockedURL: URL? = nil
+ var mockedError: Error? = nil
+ override func start() -> Bool {
+ callback(mockedURL, mockedError)
+ return presentationContextProvider?.presentationAnchor(for: self) != nil
+ }
+}
+
+extension HostedUIASWebAuthenticationSession {
+ func showHostedUI(callback: @escaping (Result<[URLQueryItem], HostedUIError>) -> Void) {
+ showHostedUI(
+ url: URL(string: "https://test.com")!,
+ callbackScheme: "https",
+ inPrivate: false,
+ presentationAnchor: nil,
+ callback: callback)
+ }
+}
+#else
+
+@testable import AWSCognitoAuthPlugin
+import XCTest
+
+class HostedUIASWebAuthenticationSessionTests: XCTestCase {
+ func testShowHostedUI_shouldThrowServiceError() {
+ let expectation = expectation(description: "showHostedUI")
+ let session = HostedUIASWebAuthenticationSession()
+ session.showHostedUI(
+ url: URL(string: "https://test.com")!,
+ callbackScheme: "https",
+ inPrivate: false,
+ presentationAnchor: nil
+ ) { result in
+ do {
+ _ = try result.get()
+ XCTFail("Expected failure(.serviceMessage), got \(result)")
+ } catch let error as HostedUIError {
+ if case .serviceMessage(let message) = error {
+ XCTAssertEqual(message, "HostedUI is only available in iOS and macOS")
+ } else {
+ XCTFail("Expected HostedUIError.serviceMessage, got \(error)")
+ }
+ } catch {
+ XCTFail("Expected HostedUIError.serviceMessage, got \(error)")
+ }
+ expectation.fulfill()
+ }
+ waitForExpectations(timeout: 1)
+ }
+}
+
+#endif