From bdfa37ad47ccb529ad468eabe9ad258248224d0a Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:28:36 -0400 Subject: [PATCH 1/6] chore(auth): Add logging to non throwing tasks (#3791) * fix(Auth): Add logging to non throwing tasks * update logs * update * added documentation * Update API dumps for new version --------- Co-authored-by: aws-amplify-ops --- .../Task/AWSAuthChangePasswordTask.swift | 1 - ...uthClearFederationToIdentityPoolTask.swift | 1 - .../AWSAuthConfirmResetPasswordTask.swift | 1 - .../Task/AWSAuthConfirmSignInTask.swift | 1 - .../Task/AWSAuthConfirmSignUpTask.swift | 1 - .../Task/AWSAuthDeleteUserTask.swift | 1 - .../AWSAuthFederateToIdentityPoolTask.swift | 1 - .../Task/AWSAuthFetchSessionTask.swift | 1 - .../Task/AWSAuthResendSignUpCodeTask.swift | 1 - .../Task/AWSAuthResetPasswordTask.swift | 1 - .../Task/AWSAuthSignInTask.swift | 1 - .../Task/AWSAuthSignOutTask.swift | 1 - .../Task/AWSAuthSignUpTask.swift | 1 - .../Task/AWSAuthWebUISignInTask.swift | 1 - .../AmplifyAuthTaskNonThrowing.swift | 4 +- .../Auth/AuthAWSCredentialsProvider.swift | 36 +++++- .../Keychain/KeychainStore.swift | 8 +- api-dump/AWSDataStorePlugin.json | 2 +- api-dump/AWSPluginsCore.json | 122 +++++++++--------- api-dump/Amplify.json | 2 +- api-dump/CoreMLPredictionsPlugin.json | 2 +- 21 files changed, 102 insertions(+), 88 deletions(-) diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthChangePasswordTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthChangePasswordTask.swift index 0fb50718c1..84921606c9 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthChangePasswordTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthChangePasswordTask.swift @@ -33,7 +33,6 @@ class AWSAuthChangePasswordTask: AuthChangePasswordTask, DefaultLogger { } func execute() async throws { - log.verbose("Starting execution") do { await taskHelper.didStateMachineConfigured() let accessToken = try await taskHelper.getAccessToken() diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthClearFederationToIdentityPoolTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthClearFederationToIdentityPoolTask.swift index 293ae92b2f..c42bbc3ff1 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthClearFederationToIdentityPoolTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthClearFederationToIdentityPoolTask.swift @@ -35,7 +35,6 @@ public class AWSAuthClearFederationToIdentityPoolTask: AuthClearFederationToIden } public func execute() async throws { - log.verbose("Starting execution") await taskHelper.didStateMachineConfigured() try await clearFederationHelper.clearFederation(authStateMachine) log.verbose("Cleared federation") diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmResetPasswordTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmResetPasswordTask.swift index ceabc14ed2..ffa1750e4a 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmResetPasswordTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmResetPasswordTask.swift @@ -27,7 +27,6 @@ class AWSAuthConfirmResetPasswordTask: AuthConfirmResetPasswordTask, DefaultLogg } func execute() async throws { - log.verbose("Starting execution") if let validationError = request.hasError() { throw validationError } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignInTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignInTask.swift index 3c19a16d61..cc824c6f25 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignInTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignInTask.swift @@ -29,7 +29,6 @@ class AWSAuthConfirmSignInTask: AuthConfirmSignInTask, DefaultLogger { } func execute() async throws -> AuthSignInResult { - log.verbose("Starting execution") await taskHelper.didStateMachineConfigured() // Check if we have a user pool configuration diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignUpTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignUpTask.swift index dbbb086118..a7b7124245 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignUpTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignUpTask.swift @@ -24,7 +24,6 @@ class AWSAuthConfirmSignUpTask: AuthConfirmSignUpTask, DefaultLogger { } func execute() async throws -> AuthSignUpResult { - log.verbose("Starting execution") try request.hasError() let userPoolEnvironment = authEnvironment.userPoolEnvironment do { diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthDeleteUserTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthDeleteUserTask.swift index 4539bcecba..2a0fbf7323 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthDeleteUserTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthDeleteUserTask.swift @@ -26,7 +26,6 @@ class AWSAuthDeleteUserTask: AuthDeleteUserTask, DefaultLogger { } func execute() async throws { - log.verbose("Starting execution") await taskHelper.didStateMachineConfigured() let accessToken = try await taskHelper.getAccessToken() diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFederateToIdentityPoolTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFederateToIdentityPoolTask.swift index 9f3151a63f..1770b532c9 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFederateToIdentityPoolTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFederateToIdentityPoolTask.swift @@ -34,7 +34,6 @@ public class AWSAuthFederateToIdentityPoolTask: AuthFederateToIdentityPoolTask, } public func execute() async throws -> FederateToIdentityPoolResult { - log.verbose("Starting execution") await taskHelper.didStateMachineConfigured() let state = await authStateMachine.currentState guard case .configured(let authNState, let authZState) = state else { diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift index 7a00941ad9..f39263578d 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthFetchSessionTask.swift @@ -26,7 +26,6 @@ class AWSAuthFetchSessionTask: AuthFetchSessionTask, DefaultLogger { } func execute() async throws -> AuthSession { - log.verbose("Starting execution") await taskHelper.didStateMachineConfigured() let doesNeedForceRefresh = request.options.forceRefresh return try await fetchAuthSessionHelper.fetch(authStateMachine, diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResendSignUpCodeTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResendSignUpCodeTask.swift index 527b796585..ff81fc2a56 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResendSignUpCodeTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResendSignUpCodeTask.swift @@ -27,7 +27,6 @@ class AWSAuthResendSignUpCodeTask: AuthResendSignUpCodeTask, DefaultLogger { } func execute() async throws -> AuthCodeDeliveryDetails { - log.verbose("Starting execution") if let validationError = request.hasError() { throw validationError } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResetPasswordTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResetPasswordTask.swift index 823b0178e1..7f86e40f58 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResetPasswordTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthResetPasswordTask.swift @@ -28,7 +28,6 @@ class AWSAuthResetPasswordTask: AuthResetPasswordTask, DefaultLogger { } func execute() async throws -> AuthResetPasswordResult { - log.verbose("Starting execution") if let validationError = request.hasError() { throw validationError } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignInTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignInTask.swift index 3a68e3aeff..abfe5ec41b 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignInTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignInTask.swift @@ -29,7 +29,6 @@ class AWSAuthSignInTask: AuthSignInTask, DefaultLogger { } func execute() async throws -> AuthSignInResult { - log.verbose("Starting execution") await taskHelper.didStateMachineConfigured() // Check if we have a user pool configuration guard let userPoolConfiguration = authConfiguration.getUserPoolConfiguration() else { diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignOutTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignOutTask.swift index f5de608577..5363d3ffdf 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignOutTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignOutTask.swift @@ -25,7 +25,6 @@ class AWSAuthSignOutTask: AuthSignOutTask, DefaultLogger { } func execute() async -> AuthSignOutResult { - log.verbose("Starting execution") await taskHelper.didStateMachineConfigured() guard case .configured(let authNState, _) = await authStateMachine.currentState else { diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignUpTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignUpTask.swift index 9e86b68daf..63194bcea9 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignUpTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthSignUpTask.swift @@ -25,7 +25,6 @@ class AWSAuthSignUpTask: AuthSignUpTask, DefaultLogger { } func execute() async throws -> AuthSignUpResult { - log.verbose("Starting execution") let userPoolEnvironment = authEnvironment.userPoolEnvironment try request.hasError() diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthWebUISignInTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthWebUISignInTask.swift index 564b5c177e..6e35e317ab 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthWebUISignInTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthWebUISignInTask.swift @@ -33,7 +33,6 @@ class AWSAuthWebUISignInTask: AuthWebUISignInTask, DefaultLogger { } func execute() async throws -> AuthSignInResult { - log.verbose("Starting execution") do { await taskHelper.didStateMachineConfigured() let result = try await helper.initiateSignIn() diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/Protocols/AmplifyAuthTaskNonThrowing.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/Protocols/AmplifyAuthTaskNonThrowing.swift index aecfd2cd7e..9377e8707c 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/Protocols/AmplifyAuthTaskNonThrowing.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/Protocols/AmplifyAuthTaskNonThrowing.swift @@ -23,10 +23,12 @@ protocol AmplifyAuthTaskNonThrowing { } -extension AmplifyAuthTaskNonThrowing { +extension AmplifyAuthTaskNonThrowing where Self: DefaultLogger { var value: Success { get async { + log.info("Starting execution for \(eventName)") let valueReturned = await execute() + log.info("Successfully completed execution for \(eventName) with result:\n\(valueReturned)") dispatch(result: valueReturned) return valueReturned } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AuthAWSCredentialsProvider.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AuthAWSCredentialsProvider.swift index c08e8821c8..9739e1066f 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AuthAWSCredentialsProvider.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AuthAWSCredentialsProvider.swift @@ -35,16 +35,42 @@ public protocol AWSCredentialsProvider { func fetchAWSCredentials() async throws -> AWSCredentials } -public protocol AWSTemporaryCredentials: AWSCredentials { - - var sessionToken: String { get } +/** + Represents AWS credentials. - var expiration: Date { get } -} + Typically refers to long-term credentials that do not expire unless manually rotated or deactivated. + These credentials are generally associated with an IAM (Identity and Access Management) user and are used to authenticate API requests to AWS services. + - Properties: + - accessKeyId: A unique identifier. + - secretAccessKey: A secret key used to sign requests cryptographically. + */ public protocol AWSCredentials { + /// A unique identifier. var accessKeyId: String { get } + /// A secret key used to sign requests cryptographically. var secretAccessKey: String { get } } + +/** + Represents temporary AWS credentials. + + Refers to short-term credentials generated by AWS STS (Security Token Service). + These credentials are used for temporary access, often for applications, temporary roles, federated users, or scenarios requiring limited-time access. + + - Inherits: AWSCredentials + + - Properties: + - sessionToken: A token that is required when using temporary security credentials to sign requests. + - expiration: The expiration date and time of the temporary credentials. + */ +public protocol AWSTemporaryCredentials: AWSCredentials { + + /// A token that is required when using temporary security credentials to sign requests. + var sessionToken: String { get } + + /// The expiration date and time of the temporary credentials. + var expiration: Date { get } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStore.swift b/AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStore.swift index 6327676c83..5734a79491 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStore.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Keychain/KeychainStore.swift @@ -92,7 +92,7 @@ public struct KeychainStore: KeychainStoreBehavior { log.error("[KeychainStore] Unable to create String from Data retrieved") throw KeychainStoreError.conversionError("Unable to create String from Data retrieved") } - log.verbose("[KeychainStore] Successfully retrieved string from the store") + log.verbose("[KeychainStore] Successfully retrieved `String` from the store") return string } @@ -175,7 +175,7 @@ public struct KeychainStore: KeychainStoreBehavior { log.error("[KeychainStore] Error updating item to keychain with status=\(updateStatus)") throw KeychainStoreError.securityError(updateStatus) } - log.verbose("[KeychainStore] Successfully updated `String` in keychain for key=\(key)") + log.verbose("[KeychainStore] Successfully updated `Data` in keychain for key=\(key)") #endif case errSecItemNotFound: log.verbose("[KeychainStore] Unable to find an existing item, creating new item") @@ -188,7 +188,7 @@ public struct KeychainStore: KeychainStoreBehavior { log.error("[KeychainStore] Error adding item to keychain with status=\(addStatus)") throw KeychainStoreError.securityError(addStatus) } - log.verbose("[KeychainStore] Successfully added `String` in keychain for key=\(key)") + log.verbose("[KeychainStore] Successfully added `Data` in keychain for key=\(key)") default: log.error("[KeychainStore] Error occurred while retrieving data from keychain when deciding to update or add with status=\(fetchStatus)") throw KeychainStoreError.securityError(fetchStatus) @@ -206,7 +206,7 @@ public struct KeychainStore: KeychainStoreBehavior { let status = SecItemDelete(query as CFDictionary) if status != errSecSuccess && status != errSecItemNotFound { - log.error("[KeychainStore] Error removing itms from keychain with status=\(status)") + log.error("[KeychainStore] Error removing items from keychain with status=\(status)") throw KeychainStoreError.securityError(status) } log.verbose("[KeychainStore] Successfully removed item from keychain") diff --git a/api-dump/AWSDataStorePlugin.json b/api-dump/AWSDataStorePlugin.json index 47c0e792de..d436f34410 100644 --- a/api-dump/AWSDataStorePlugin.json +++ b/api-dump/AWSDataStorePlugin.json @@ -8205,7 +8205,7 @@ "-module", "AWSDataStorePlugin", "-o", - "\/var\/folders\/hn\/5bx1f4_d4ds5vhwhkxc7vdcr0000gn\/T\/tmp.nAGRifhwH6\/AWSDataStorePlugin.json", + "\/var\/folders\/hw\/1f0gcr8d6kn9ms0_wn0_57qc0000gn\/T\/tmp.rjSPtedPzR\/AWSDataStorePlugin.json", "-I", ".build\/debug", "-sdk-version", diff --git a/api-dump/AWSPluginsCore.json b/api-dump/AWSPluginsCore.json index 19b8e2fb57..76cf8a6e6c 100644 --- a/api-dump/AWSPluginsCore.json +++ b/api-dump/AWSPluginsCore.json @@ -3644,13 +3644,13 @@ }, { "kind": "TypeDecl", - "name": "AWSTemporaryCredentials", - "printedName": "AWSTemporaryCredentials", + "name": "AWSCredentials", + "printedName": "AWSCredentials", "children": [ { "kind": "Var", - "name": "sessionToken", - "printedName": "sessionToken", + "name": "accessKeyId", + "printedName": "accessKeyId", "children": [ { "kind": "TypeNominal", @@ -3660,8 +3660,8 @@ } ], "declKind": "Var", - "usr": "s:14AWSPluginsCore23AWSTemporaryCredentialsP12sessionTokenSSvp", - "mangledName": "$s14AWSPluginsCore23AWSTemporaryCredentialsP12sessionTokenSSvp", + "usr": "s:14AWSPluginsCore14AWSCredentialsP11accessKeyIdSSvp", + "mangledName": "$s14AWSPluginsCore14AWSCredentialsP11accessKeyIdSSvp", "moduleName": "AWSPluginsCore", "protocolReq": true, "accessors": [ @@ -3678,10 +3678,10 @@ } ], "declKind": "Accessor", - "usr": "s:14AWSPluginsCore23AWSTemporaryCredentialsP12sessionTokenSSvg", - "mangledName": "$s14AWSPluginsCore23AWSTemporaryCredentialsP12sessionTokenSSvg", + "usr": "s:14AWSPluginsCore14AWSCredentialsP11accessKeyIdSSvg", + "mangledName": "$s14AWSPluginsCore14AWSCredentialsP11accessKeyIdSSvg", "moduleName": "AWSPluginsCore", - "genericSig": "", + "genericSig": "", "protocolReq": true, "reqNewWitnessTableEntry": true, "accessorKind": "get" @@ -3690,19 +3690,19 @@ }, { "kind": "Var", - "name": "expiration", - "printedName": "expiration", + "name": "secretAccessKey", + "printedName": "secretAccessKey", "children": [ { "kind": "TypeNominal", - "name": "Date", - "printedName": "Foundation.Date", - "usr": "s:10Foundation4DateV" + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" } ], "declKind": "Var", - "usr": "s:14AWSPluginsCore23AWSTemporaryCredentialsP10expiration10Foundation4DateVvp", - "mangledName": "$s14AWSPluginsCore23AWSTemporaryCredentialsP10expiration10Foundation4DateVvp", + "usr": "s:14AWSPluginsCore14AWSCredentialsP15secretAccessKeySSvp", + "mangledName": "$s14AWSPluginsCore14AWSCredentialsP15secretAccessKeySSvp", "moduleName": "AWSPluginsCore", "protocolReq": true, "accessors": [ @@ -3713,16 +3713,16 @@ "children": [ { "kind": "TypeNominal", - "name": "Date", - "printedName": "Foundation.Date", - "usr": "s:10Foundation4DateV" + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" } ], "declKind": "Accessor", - "usr": "s:14AWSPluginsCore23AWSTemporaryCredentialsP10expiration10Foundation4DateVvg", - "mangledName": "$s14AWSPluginsCore23AWSTemporaryCredentialsP10expiration10Foundation4DateVvg", + "usr": "s:14AWSPluginsCore14AWSCredentialsP15secretAccessKeySSvg", + "mangledName": "$s14AWSPluginsCore14AWSCredentialsP15secretAccessKeySSvg", "moduleName": "AWSPluginsCore", - "genericSig": "", + "genericSig": "", "protocolReq": true, "reqNewWitnessTableEntry": true, "accessorKind": "get" @@ -3731,29 +3731,19 @@ } ], "declKind": "Protocol", - "usr": "s:14AWSPluginsCore23AWSTemporaryCredentialsP", - "mangledName": "$s14AWSPluginsCore23AWSTemporaryCredentialsP", - "moduleName": "AWSPluginsCore", - "genericSig": "", - "conformances": [ - { - "kind": "Conformance", - "name": "AWSCredentials", - "printedName": "AWSCredentials", - "usr": "s:14AWSPluginsCore14AWSCredentialsP", - "mangledName": "$s14AWSPluginsCore14AWSCredentialsP" - } - ] + "usr": "s:14AWSPluginsCore14AWSCredentialsP", + "mangledName": "$s14AWSPluginsCore14AWSCredentialsP", + "moduleName": "AWSPluginsCore" }, { "kind": "TypeDecl", - "name": "AWSCredentials", - "printedName": "AWSCredentials", + "name": "AWSTemporaryCredentials", + "printedName": "AWSTemporaryCredentials", "children": [ { "kind": "Var", - "name": "accessKeyId", - "printedName": "accessKeyId", + "name": "sessionToken", + "printedName": "sessionToken", "children": [ { "kind": "TypeNominal", @@ -3763,8 +3753,8 @@ } ], "declKind": "Var", - "usr": "s:14AWSPluginsCore14AWSCredentialsP11accessKeyIdSSvp", - "mangledName": "$s14AWSPluginsCore14AWSCredentialsP11accessKeyIdSSvp", + "usr": "s:14AWSPluginsCore23AWSTemporaryCredentialsP12sessionTokenSSvp", + "mangledName": "$s14AWSPluginsCore23AWSTemporaryCredentialsP12sessionTokenSSvp", "moduleName": "AWSPluginsCore", "protocolReq": true, "accessors": [ @@ -3781,10 +3771,10 @@ } ], "declKind": "Accessor", - "usr": "s:14AWSPluginsCore14AWSCredentialsP11accessKeyIdSSvg", - "mangledName": "$s14AWSPluginsCore14AWSCredentialsP11accessKeyIdSSvg", + "usr": "s:14AWSPluginsCore23AWSTemporaryCredentialsP12sessionTokenSSvg", + "mangledName": "$s14AWSPluginsCore23AWSTemporaryCredentialsP12sessionTokenSSvg", "moduleName": "AWSPluginsCore", - "genericSig": "", + "genericSig": "", "protocolReq": true, "reqNewWitnessTableEntry": true, "accessorKind": "get" @@ -3793,19 +3783,19 @@ }, { "kind": "Var", - "name": "secretAccessKey", - "printedName": "secretAccessKey", + "name": "expiration", + "printedName": "expiration", "children": [ { "kind": "TypeNominal", - "name": "String", - "printedName": "Swift.String", - "usr": "s:SS" + "name": "Date", + "printedName": "Foundation.Date", + "usr": "s:10Foundation4DateV" } ], "declKind": "Var", - "usr": "s:14AWSPluginsCore14AWSCredentialsP15secretAccessKeySSvp", - "mangledName": "$s14AWSPluginsCore14AWSCredentialsP15secretAccessKeySSvp", + "usr": "s:14AWSPluginsCore23AWSTemporaryCredentialsP10expiration10Foundation4DateVvp", + "mangledName": "$s14AWSPluginsCore23AWSTemporaryCredentialsP10expiration10Foundation4DateVvp", "moduleName": "AWSPluginsCore", "protocolReq": true, "accessors": [ @@ -3816,16 +3806,16 @@ "children": [ { "kind": "TypeNominal", - "name": "String", - "printedName": "Swift.String", - "usr": "s:SS" + "name": "Date", + "printedName": "Foundation.Date", + "usr": "s:10Foundation4DateV" } ], "declKind": "Accessor", - "usr": "s:14AWSPluginsCore14AWSCredentialsP15secretAccessKeySSvg", - "mangledName": "$s14AWSPluginsCore14AWSCredentialsP15secretAccessKeySSvg", + "usr": "s:14AWSPluginsCore23AWSTemporaryCredentialsP10expiration10Foundation4DateVvg", + "mangledName": "$s14AWSPluginsCore23AWSTemporaryCredentialsP10expiration10Foundation4DateVvg", "moduleName": "AWSPluginsCore", - "genericSig": "", + "genericSig": "", "protocolReq": true, "reqNewWitnessTableEntry": true, "accessorKind": "get" @@ -3834,9 +3824,19 @@ } ], "declKind": "Protocol", - "usr": "s:14AWSPluginsCore14AWSCredentialsP", - "mangledName": "$s14AWSPluginsCore14AWSCredentialsP", - "moduleName": "AWSPluginsCore" + "usr": "s:14AWSPluginsCore23AWSTemporaryCredentialsP", + "mangledName": "$s14AWSPluginsCore23AWSTemporaryCredentialsP", + "moduleName": "AWSPluginsCore", + "genericSig": "", + "conformances": [ + { + "kind": "Conformance", + "name": "AWSCredentials", + "printedName": "AWSCredentials", + "usr": "s:14AWSPluginsCore14AWSCredentialsP", + "mangledName": "$s14AWSPluginsCore14AWSCredentialsP" + } + ] }, { "kind": "TypeDecl", @@ -24273,7 +24273,7 @@ "-module", "AWSPluginsCore", "-o", - "\/var\/folders\/hn\/5bx1f4_d4ds5vhwhkxc7vdcr0000gn\/T\/tmp.nAGRifhwH6\/AWSPluginsCore.json", + "\/var\/folders\/hw\/1f0gcr8d6kn9ms0_wn0_57qc0000gn\/T\/tmp.rjSPtedPzR\/AWSPluginsCore.json", "-I", ".build\/debug", "-sdk-version", diff --git a/api-dump/Amplify.json b/api-dump/Amplify.json index 1bb4d36d2c..c8b9c833c4 100644 --- a/api-dump/Amplify.json +++ b/api-dump/Amplify.json @@ -179735,7 +179735,7 @@ "-module", "Amplify", "-o", - "\/var\/folders\/hn\/5bx1f4_d4ds5vhwhkxc7vdcr0000gn\/T\/tmp.nAGRifhwH6\/Amplify.json", + "\/var\/folders\/hw\/1f0gcr8d6kn9ms0_wn0_57qc0000gn\/T\/tmp.rjSPtedPzR\/Amplify.json", "-I", ".build\/debug", "-sdk-version", diff --git a/api-dump/CoreMLPredictionsPlugin.json b/api-dump/CoreMLPredictionsPlugin.json index 1a9879407f..f7aeb94b9c 100644 --- a/api-dump/CoreMLPredictionsPlugin.json +++ b/api-dump/CoreMLPredictionsPlugin.json @@ -430,7 +430,7 @@ "-module", "CoreMLPredictionsPlugin", "-o", - "\/var\/folders\/hn\/5bx1f4_d4ds5vhwhkxc7vdcr0000gn\/T\/tmp.nAGRifhwH6\/CoreMLPredictionsPlugin.json", + "\/var\/folders\/hw\/1f0gcr8d6kn9ms0_wn0_57qc0000gn\/T\/tmp.rjSPtedPzR\/CoreMLPredictionsPlugin.json", "-I", ".build\/debug", "-sdk-version", From 673a075c78455521406d2e8b1c86990df4adcf49 Mon Sep 17 00:00:00 2001 From: Michael Law <1365977+lawmicha@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:35:35 -0400 Subject: [PATCH 2/6] feat: Add AppSync components (#3825) * add Amplify components for AppSync * Add AWSAppSyncConfigurationTests * Add signing tests * remove unnecessary public apis * add doc comments * Update API dumps for new version --------- Co-authored-by: aws-amplify-ops --- .../Configuration/AmplifyOutputsData.swift | 3 +- .../AWSCognitoAuthPlugin+AppSyncSigner.swift | 140 +++++++++++++ ...SCognitoAuthPluginAppSyncSignerTests.swift | 40 ++++ .../AuthHostApp.xcodeproj/project.pbxproj | 12 ++ .../AppSyncSignerTests.swift | 36 ++++ .../API/AWSAppSyncConfiguration.swift | 46 +++++ .../API/AWSAppSyncConfigurationTests.swift | 31 +++ .../AmplifyAWSCredentialsProvider.swift | 2 +- api-dump/AWSDataStorePlugin.json | 2 +- api-dump/AWSPluginsCore.json | 192 +++++++++++++++++- api-dump/Amplify.json | 76 ++++++- api-dump/CoreMLPredictionsPlugin.json | 2 +- 12 files changed, 576 insertions(+), 6 deletions(-) create mode 100644 AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+AppSyncSigner.swift create mode 100644 AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/AWSCognitoAuthPluginAppSyncSignerTests.swift create mode 100644 AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/AppSyncSignerTests/AppSyncSignerTests.swift create mode 100644 AmplifyPlugins/Core/AWSPluginsCore/API/AWSAppSyncConfiguration.swift create mode 100644 AmplifyPlugins/Core/AWSPluginsCoreTests/API/AWSAppSyncConfigurationTests.swift diff --git a/Amplify/Core/Configuration/AmplifyOutputsData.swift b/Amplify/Core/Configuration/AmplifyOutputsData.swift index 93a2f60e6a..57e388a3ba 100644 --- a/Amplify/Core/Configuration/AmplifyOutputsData.swift +++ b/Amplify/Core/Configuration/AmplifyOutputsData.swift @@ -251,7 +251,8 @@ public struct AmplifyOutputsData: Codable { public struct AmplifyOutputs { /// A closure that resolves the `AmplifyOutputsData` configuration - let resolveConfiguration: () throws -> AmplifyOutputsData + @_spi(InternalAmplifyConfiguration) + public let resolveConfiguration: () throws -> AmplifyOutputsData /// Resolves configuration with `amplify_outputs.json` in the main bundle. public static let amplifyOutputs: AmplifyOutputs = { diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+AppSyncSigner.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+AppSyncSigner.swift new file mode 100644 index 0000000000..b77c3ad92e --- /dev/null +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+AppSyncSigner.swift @@ -0,0 +1,140 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify // Amplify.Auth +import AWSPluginsCore // AuthAWSCredentialsProvider +import AWSClientRuntime // AWSClientRuntime.CredentialsProviding +import ClientRuntime // SdkHttpRequestBuilder +import AwsCommonRuntimeKit // CommonRuntimeKit.initialize() + +extension AWSCognitoAuthPlugin { + + + /// Creates a AWS IAM SigV4 signer capable of signing AWS AppSync requests. + /// + /// **Note**. Although this method is static, **Amplify.Auth** is required to be configured with **AWSCognitoAuthPlugin** as + /// it depends on the credentials provider from Cognito through `Amplify.Auth.fetchAuthSession()`. The static type allows + /// developers to simplify their callsite without having to access the method on the plugin instance. + /// + /// - Parameter region: The region of the AWS AppSync API + /// - Returns: A closure that takes in a requestand returns a signed request. + public static func createAppSyncSigner(region: String) -> ((URLRequest) async throws -> URLRequest) { + return { request in + try await signAppSyncRequest(request, + region: region) + } + } + + static func signAppSyncRequest(_ urlRequest: URLRequest, + region: Swift.String, + signingName: Swift.String = "appsync", + date: ClientRuntime.Date = Date()) async throws -> URLRequest { + CommonRuntimeKit.initialize() + + // Convert URLRequest to SDK's HTTPRequest + guard let requestBuilder = try createAppSyncSdkHttpRequestBuilder( + urlRequest: urlRequest) else { + return urlRequest + } + + // Retrieve the credentials from credentials provider + let credentials: AWSClientRuntime.AWSCredentials + let authSession = try await Amplify.Auth.fetchAuthSession() + if let awsCredentialsProvider = authSession as? AuthAWSCredentialsProvider { + let awsCredentials = try awsCredentialsProvider.getAWSCredentials().get() + credentials = awsCredentials.toAWSSDKCredentials() + } else { + let error = AuthError.unknown("Auth session does not include AWS credentials information") + throw error + } + + // Prepare signing + let flags = SigningFlags(useDoubleURIEncode: true, + shouldNormalizeURIPath: true, + omitSessionToken: false) + let signedBodyHeader: AWSSignedBodyHeader = .none + let signedBodyValue: AWSSignedBodyValue = .empty + let signingConfig = AWSSigningConfig(credentials: credentials, + signedBodyHeader: signedBodyHeader, + signedBodyValue: signedBodyValue, + flags: flags, + date: date, + service: signingName, + region: region, + signatureType: .requestHeaders, + signingAlgorithm: .sigv4) + + // Sign request + guard let httpRequest = await AWSSigV4Signer.sigV4SignedRequest( + requestBuilder: requestBuilder, + + signingConfig: signingConfig + ) else { + return urlRequest + } + + // Update original request with new headers + return setHeaders(from: httpRequest, to: urlRequest) + } + + static func setHeaders(from sdkRequest: SdkHttpRequest, to urlRequest: URLRequest) -> URLRequest { + var urlRequest = urlRequest + for header in sdkRequest.headers.headers { + urlRequest.setValue(header.value.joined(separator: ","), forHTTPHeaderField: header.name) + } + return urlRequest + } + + static func createAppSyncSdkHttpRequestBuilder(urlRequest: URLRequest) throws -> SdkHttpRequestBuilder? { + + guard let url = urlRequest.url, + let host = url.host else { + return nil + } + + var headers = urlRequest.allHTTPHeaderFields ?? [:] + headers.updateValue(host, forKey: "host") + + let httpMethod = (urlRequest.httpMethod?.uppercased()) + .flatMap(HttpMethodType.init(rawValue:)) ?? .get + + let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems? + .map { ClientRuntime.SDKURLQueryItem(name: $0.name, value: $0.value)} ?? [] + + let requestBuilder = SdkHttpRequestBuilder() + .withHost(host) + .withPath(url.path) + .withQueryItems(queryItems) + .withMethod(httpMethod) + .withPort(443) + .withProtocol(.https) + .withHeaders(.init(headers)) + .withBody(.data(urlRequest.httpBody)) + + return requestBuilder + } +} + +extension AWSPluginsCore.AWSCredentials { + + func toAWSSDKCredentials() -> AWSClientRuntime.AWSCredentials { + if let tempCredentials = self as? AWSTemporaryCredentials { + return AWSClientRuntime.AWSCredentials( + accessKey: tempCredentials.accessKeyId, + secret: tempCredentials.secretAccessKey, + expirationTimeout: tempCredentials.expiration, + sessionToken: tempCredentials.sessionToken) + } else { + return AWSClientRuntime.AWSCredentials( + accessKey: accessKeyId, + secret: secretAccessKey, + expirationTimeout: Date()) + } + + } +} diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/AWSCognitoAuthPluginAppSyncSignerTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/AWSCognitoAuthPluginAppSyncSignerTests.swift new file mode 100644 index 0000000000..609fe1c774 --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/AWSCognitoAuthPluginAppSyncSignerTests.swift @@ -0,0 +1,40 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +@testable import Amplify +@testable import AWSCognitoAuthPlugin + +class AWSCognitoAuthPluginAppSyncSignerTests: XCTestCase { + + /// Tests translating the URLRequest to the SDKRequest + /// The translation should account for expected fields, as asserted in the test. + func testCreateAppSyncSdkHttpRequestBuilder() throws { + var urlRequest = URLRequest(url: URL(string: "http://graphql.com")!) + urlRequest.httpMethod = "post" + let dataObject = Data() + urlRequest.httpBody = dataObject + guard let sdkRequestBuilder = try AWSCognitoAuthPlugin.createAppSyncSdkHttpRequestBuilder(urlRequest: urlRequest) else { + XCTFail("Could not create SDK request") + return + } + + let request = sdkRequestBuilder.build() + XCTAssertEqual(request.host, "graphql.com") + XCTAssertEqual(request.path, "") + XCTAssertEqual(request.queryItems, []) + XCTAssertEqual(request.method, .post) + XCTAssertEqual(request.endpoint.port, 443) + XCTAssertEqual(request.endpoint.protocolType, .https) + XCTAssertEqual(request.endpoint.headers?.headers, [.init(name: "host", value: "graphql.com")]) + guard case let .data(data) = request.body else { + XCTFail("Unexpected body") + return + } + XCTAssertEqual(data, dataObject) + } +} diff --git a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.pbxproj b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.pbxproj index efe5f198a7..2444c39e1a 100644 --- a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 21CFD7C62C7524570071C70F /* AppSyncSignerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CFD7C52C7524570071C70F /* AppSyncSignerTests.swift */; }; 21F762A52BD6B1AA0048845A /* AuthSessionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485CB5B727B61F0F006CCEC7 /* AuthSessionHelper.swift */; }; 21F762A62BD6B1AA0048845A /* AsyncTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681DFEA828E747B80000C36A /* AsyncTesting.swift */; }; 21F762A72BD6B1AA0048845A /* AuthSRPSignInTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485CB5BE27B61F1D006CCEC7 /* AuthSRPSignInTests.swift */; }; @@ -169,6 +170,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 21CFD7C52C7524570071C70F /* AppSyncSignerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSyncSignerTests.swift; sourceTree = ""; }; 21F762CB2BD6B1AA0048845A /* AuthGen2IntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AuthGen2IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 21F762CC2BD6B1CD0048845A /* AuthGen2IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = AuthGen2IntegrationTests.xctestplan; sourceTree = ""; }; 4821B2F1286B5F74000EC1D7 /* AuthDeleteUserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthDeleteUserTests.swift; sourceTree = ""; }; @@ -268,6 +270,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 21CFD7C42C75243B0071C70F /* AppSyncSignerTests */ = { + isa = PBXGroup; + children = ( + 21CFD7C52C7524570071C70F /* AppSyncSignerTests.swift */, + ); + path = AppSyncSignerTests; + sourceTree = ""; + }; 4821B2F0286B5F74000EC1D7 /* AuthDeleteUserTests */ = { isa = PBXGroup; children = ( @@ -355,6 +365,7 @@ 485CB5A027B61E04006CCEC7 /* AuthIntegrationTests */ = { isa = PBXGroup; children = ( + 21CFD7C42C75243B0071C70F /* AppSyncSignerTests */, 21F762CC2BD6B1CD0048845A /* AuthGen2IntegrationTests.xctestplan */, 48916F362A412AF800E3E1B1 /* MFATests */, 97B370C32878DA3500F1C088 /* DeviceTests */, @@ -851,6 +862,7 @@ 681DFEAC28E747B80000C36A /* AsyncExpectation.swift in Sources */, 48E3AB3128E52590004EE395 /* GetCurrentUserTests.swift in Sources */, 48916F3A2A412CEE00E3E1B1 /* TOTPHelper.swift in Sources */, + 21CFD7C62C7524570071C70F /* AppSyncSignerTests.swift in Sources */, 485CB5B127B61EAC006CCEC7 /* AWSAuthBaseTest.swift in Sources */, 485CB5C027B61F1E006CCEC7 /* SignedOutAuthSessionTests.swift in Sources */, 485CB5BA27B61F10006CCEC7 /* AuthSignInHelper.swift in Sources */, diff --git a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/AppSyncSignerTests/AppSyncSignerTests.swift b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/AppSyncSignerTests/AppSyncSignerTests.swift new file mode 100644 index 0000000000..02cedefdfb --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/AppSyncSignerTests/AppSyncSignerTests.swift @@ -0,0 +1,36 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +@testable import Amplify +import AWSCognitoAuthPlugin + +class AppSyncSignerTests: AWSAuthBaseTest { + + /// Test signing an AppSync request with a live credentials provider + /// + /// - Given: Base test configures Amplify and adds AWSCognitoAuthPlugin + /// - When: + /// - I invoke AWSCognitoAuthPlugin's AppSync signer + /// - Then: + /// - I should get a signed request. + /// + func testSignAppSyncRequest() async throws { + let request = URLRequest(url: URL(string: "http://graphql.com")!) + let signer = AWSCognitoAuthPlugin.createAppSyncSigner(region: "us-east-1") + let signedRequest = try await signer(request) + guard let headers = signedRequest.allHTTPHeaderFields else { + XCTFail("Missing headers") + return + } + XCTAssertEqual(headers.count, 4) + let containsExpectedHeaders = headers.keys.contains(where: { key in + key == "Authorization" || key == "Host" || key == "X-Amz-Security-Token" || key == "X-Amz-Date" + }) + XCTAssertTrue(containsExpectedHeaders) + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCore/API/AWSAppSyncConfiguration.swift b/AmplifyPlugins/Core/AWSPluginsCore/API/AWSAppSyncConfiguration.swift new file mode 100644 index 0000000000..e7d41af6d7 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCore/API/AWSAppSyncConfiguration.swift @@ -0,0 +1,46 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +@_spi(InternalAmplifyConfiguration) import Amplify + + +/// Hold necessary AWS AppSync configuration values to interact with the AppSync API +public struct AWSAppSyncConfiguration { + + /// The region of the AWS AppSync API + public let region: String + + /// The endpoint of the AWS AppSync API + public let endpoint: URL + + /// API key for API Key authentication. + public let apiKey: String? + + + /// Initializes an `AWSAppSyncConfiguration` instance using the provided AmplifyOutputs file. + /// AmplifyOutputs support multiple ways to read the `amplify_outputs.json` configuration file + /// + /// For example, `try AWSAppSyncConfiguraton(with: .amplifyOutputs)` will read the + /// `amplify_outputs.json` file from the main bundle. + public init(with amplifyOutputs: AmplifyOutputs) throws { + let resolvedConfiguration = try amplifyOutputs.resolveConfiguration() + + guard let dataCategory = resolvedConfiguration.data else { + throw ConfigurationError.invalidAmplifyOutputsFile( + "Missing data category", "", nil) + } + + self.region = dataCategory.awsRegion + guard let endpoint = URL(string: dataCategory.url) else { + throw ConfigurationError.invalidAmplifyOutputsFile( + "Missing region from data category", "", nil) + } + self.endpoint = endpoint + self.apiKey = dataCategory.apiKey + } +} diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/API/AWSAppSyncConfigurationTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/API/AWSAppSyncConfigurationTests.swift new file mode 100644 index 0000000000..42813dc3f7 --- /dev/null +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/API/AWSAppSyncConfigurationTests.swift @@ -0,0 +1,31 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import AWSPluginsCore +@_spi(InternalAmplifyConfiguration) @testable import Amplify + +final class AWSAppSyncConfigurationTests: XCTestCase { + + func testSuccess() throws { + let config = AmplifyOutputsData(data: .init( + awsRegion: "us-east-1", + url: "http://www.example.com", + modelIntrospection: nil, + apiKey: "apiKey123", + defaultAuthorizationType: .amazonCognitoUserPools, + authorizationTypes: [.apiKey, .awsIAM])) + let encoder = JSONEncoder() + let data = try! encoder.encode(config) + + let configuration = try AWSAppSyncConfiguration(with: .data(data)) + + XCTAssertEqual(configuration.region, "us-east-1") + XCTAssertEqual(configuration.endpoint, URL(string: "http://www.example.com")!) + XCTAssertEqual(configuration.apiKey, "apiKey123") + } +} diff --git a/AmplifyPlugins/Core/AmplifyCredentials/AmplifyAWSCredentialsProvider.swift b/AmplifyPlugins/Core/AmplifyCredentials/AmplifyAWSCredentialsProvider.swift index 63d1197a6d..8a45c1d64a 100644 --- a/AmplifyPlugins/Core/AmplifyCredentials/AmplifyAWSCredentialsProvider.swift +++ b/AmplifyPlugins/Core/AmplifyCredentials/AmplifyAWSCredentialsProvider.swift @@ -12,7 +12,7 @@ import AWSPluginsCore import Foundation public class AmplifyAWSCredentialsProvider: AWSClientRuntime.CredentialsProviding { - + public func getCredentials() async throws -> AWSClientRuntime.AWSCredentials { let authSession = try await Amplify.Auth.fetchAuthSession() if let awsCredentialsProvider = authSession as? AuthAWSCredentialsProvider { diff --git a/api-dump/AWSDataStorePlugin.json b/api-dump/AWSDataStorePlugin.json index d436f34410..981969d8c6 100644 --- a/api-dump/AWSDataStorePlugin.json +++ b/api-dump/AWSDataStorePlugin.json @@ -8205,7 +8205,7 @@ "-module", "AWSDataStorePlugin", "-o", - "\/var\/folders\/hw\/1f0gcr8d6kn9ms0_wn0_57qc0000gn\/T\/tmp.rjSPtedPzR\/AWSDataStorePlugin.json", + "\/var\/folders\/4d\/0gnh84wj53j7wyk695q0tc_80000gn\/T\/tmp.BZQxGLOyZz\/AWSDataStorePlugin.json", "-I", ".build\/debug", "-sdk-version", diff --git a/api-dump/AWSPluginsCore.json b/api-dump/AWSPluginsCore.json index 76cf8a6e6c..088d2a9ea1 100644 --- a/api-dump/AWSPluginsCore.json +++ b/api-dump/AWSPluginsCore.json @@ -136,6 +136,196 @@ "mangledName": "$s14AWSPluginsCore21AWSAPIAuthInformationP", "moduleName": "AWSPluginsCore" }, + { + "kind": "TypeDecl", + "name": "AWSAppSyncConfiguration", + "printedName": "AWSAppSyncConfiguration", + "children": [ + { + "kind": "Var", + "name": "region", + "printedName": "region", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "s:14AWSPluginsCore23AWSAppSyncConfigurationV6regionSSvp", + "mangledName": "$s14AWSPluginsCore23AWSAppSyncConfigurationV6regionSSvp", + "moduleName": "AWSPluginsCore", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "s:14AWSPluginsCore23AWSAppSyncConfigurationV6regionSSvg", + "mangledName": "$s14AWSPluginsCore23AWSAppSyncConfigurationV6regionSSvg", + "moduleName": "AWSPluginsCore", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "endpoint", + "printedName": "endpoint", + "children": [ + { + "kind": "TypeNominal", + "name": "URL", + "printedName": "Foundation.URL", + "usr": "s:10Foundation3URLV" + } + ], + "declKind": "Var", + "usr": "s:14AWSPluginsCore23AWSAppSyncConfigurationV8endpoint10Foundation3URLVvp", + "mangledName": "$s14AWSPluginsCore23AWSAppSyncConfigurationV8endpoint10Foundation3URLVvp", + "moduleName": "AWSPluginsCore", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "URL", + "printedName": "Foundation.URL", + "usr": "s:10Foundation3URLV" + } + ], + "declKind": "Accessor", + "usr": "s:14AWSPluginsCore23AWSAppSyncConfigurationV8endpoint10Foundation3URLVvg", + "mangledName": "$s14AWSPluginsCore23AWSAppSyncConfigurationV8endpoint10Foundation3URLVvg", + "moduleName": "AWSPluginsCore", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "apiKey", + "printedName": "apiKey", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Var", + "usr": "s:14AWSPluginsCore23AWSAppSyncConfigurationV6apiKeySSSgvp", + "mangledName": "$s14AWSPluginsCore23AWSAppSyncConfigurationV6apiKeySSSgvp", + "moduleName": "AWSPluginsCore", + "declAttributes": [ + "HasStorage" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Optional", + "printedName": "Swift.String?", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "usr": "s:Sq" + } + ], + "declKind": "Accessor", + "usr": "s:14AWSPluginsCore23AWSAppSyncConfigurationV6apiKeySSSgvg", + "mangledName": "$s14AWSPluginsCore23AWSAppSyncConfigurationV6apiKeySSSgvg", + "moduleName": "AWSPluginsCore", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, + { + "kind": "Constructor", + "name": "init", + "printedName": "init(with:)", + "children": [ + { + "kind": "TypeNominal", + "name": "AWSAppSyncConfiguration", + "printedName": "AWSPluginsCore.AWSAppSyncConfiguration", + "usr": "s:14AWSPluginsCore23AWSAppSyncConfigurationV" + }, + { + "kind": "TypeNominal", + "name": "AmplifyOutputs", + "printedName": "Amplify.AmplifyOutputs", + "usr": "s:7Amplify0A7OutputsV" + } + ], + "declKind": "Constructor", + "usr": "s:14AWSPluginsCore23AWSAppSyncConfigurationV4withAC7Amplify0G7OutputsV_tKcfc", + "mangledName": "$s14AWSPluginsCore23AWSAppSyncConfigurationV4withAC7Amplify0G7OutputsV_tKcfc", + "moduleName": "AWSPluginsCore", + "throwing": true, + "init_kind": "Designated" + } + ], + "declKind": "Struct", + "usr": "s:14AWSPluginsCore23AWSAppSyncConfigurationV", + "mangledName": "$s14AWSPluginsCore23AWSAppSyncConfigurationV", + "moduleName": "AWSPluginsCore" + }, { "kind": "TypeDecl", "name": "AppSyncErrorType", @@ -24273,7 +24463,7 @@ "-module", "AWSPluginsCore", "-o", - "\/var\/folders\/hw\/1f0gcr8d6kn9ms0_wn0_57qc0000gn\/T\/tmp.rjSPtedPzR\/AWSPluginsCore.json", + "\/var\/folders\/4d\/0gnh84wj53j7wyk695q0tc_80000gn\/T\/tmp.BZQxGLOyZz\/AWSPluginsCore.json", "-I", ".build\/debug", "-sdk-version", diff --git a/api-dump/Amplify.json b/api-dump/Amplify.json index c8b9c833c4..70967c7128 100644 --- a/api-dump/Amplify.json +++ b/api-dump/Amplify.json @@ -155992,6 +155992,80 @@ "name": "AmplifyOutputs", "printedName": "AmplifyOutputs", "children": [ + { + "kind": "Var", + "name": "resolveConfiguration", + "printedName": "resolveConfiguration", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "() throws -> Amplify.AmplifyOutputsData", + "children": [ + { + "kind": "TypeNominal", + "name": "AmplifyOutputsData", + "printedName": "Amplify.AmplifyOutputsData", + "usr": "s:7Amplify0A11OutputsDataV" + }, + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + } + ], + "declKind": "Var", + "usr": "s:7Amplify0A7OutputsV20resolveConfigurationAA0aB4DataVyKcvp", + "mangledName": "$s7Amplify0A7OutputsV20resolveConfigurationAA0aB4DataVyKcvp", + "moduleName": "Amplify", + "declAttributes": [ + "SPIAccessControl", + "HasStorage" + ], + "spi_group_names": [ + "InternalAmplifyConfiguration" + ], + "isLet": true, + "hasStorage": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeFunc", + "name": "Function", + "printedName": "() throws -> Amplify.AmplifyOutputsData", + "children": [ + { + "kind": "TypeNominal", + "name": "AmplifyOutputsData", + "printedName": "Amplify.AmplifyOutputsData", + "usr": "s:7Amplify0A11OutputsDataV" + }, + { + "kind": "TypeNominal", + "name": "Void", + "printedName": "()" + } + ] + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify0A7OutputsV20resolveConfigurationAA0aB4DataVyKcvg", + "mangledName": "$s7Amplify0A7OutputsV20resolveConfigurationAA0aB4DataVyKcvg", + "moduleName": "Amplify", + "implicit": true, + "declAttributes": [ + "Transparent" + ], + "accessorKind": "get" + } + ] + }, { "kind": "Var", "name": "amplifyOutputs", @@ -179735,7 +179809,7 @@ "-module", "Amplify", "-o", - "\/var\/folders\/hw\/1f0gcr8d6kn9ms0_wn0_57qc0000gn\/T\/tmp.rjSPtedPzR\/Amplify.json", + "\/var\/folders\/4d\/0gnh84wj53j7wyk695q0tc_80000gn\/T\/tmp.BZQxGLOyZz\/Amplify.json", "-I", ".build\/debug", "-sdk-version", diff --git a/api-dump/CoreMLPredictionsPlugin.json b/api-dump/CoreMLPredictionsPlugin.json index f7aeb94b9c..618df7be0e 100644 --- a/api-dump/CoreMLPredictionsPlugin.json +++ b/api-dump/CoreMLPredictionsPlugin.json @@ -430,7 +430,7 @@ "-module", "CoreMLPredictionsPlugin", "-o", - "\/var\/folders\/hw\/1f0gcr8d6kn9ms0_wn0_57qc0000gn\/T\/tmp.rjSPtedPzR\/CoreMLPredictionsPlugin.json", + "\/var\/folders\/4d\/0gnh84wj53j7wyk695q0tc_80000gn\/T\/tmp.BZQxGLOyZz\/CoreMLPredictionsPlugin.json", "-I", ".build\/debug", "-sdk-version", From 81894600d20aa32f4145eaa157a46c01ceb11652 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Fri, 23 Aug 2024 10:41:11 -0700 Subject: [PATCH 3/6] fix(api): storing cancelablles with actor methods in AppSyncRTC (#3824) * fix(api): storing cancelablles with actor methods in AppSyncRTC * add unit test cases * remove internal modifier --- .../AppSyncRealTimeClient.swift | 66 ++++++++++++------- .../AppSyncRealTimeClientTests.swift | 28 ++++++++ 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeClient.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeClient.swift index 4e374e9fdf..7f3d75abbc 100644 --- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeClient.swift +++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AppSyncRealTimeClient/AppSyncRealTimeClient.swift @@ -165,14 +165,15 @@ actor AppSyncRealTimeClient: AppSyncRealTimeClientProtocol { // Placing the actual subscription work in a deferred task and // promptly returning the filtered publisher for downstream consumption of all error messages. defer { - Task { [weak self] in + let task = Task { [weak self] in guard let self = self else { return } if !(await self.isConnected) { try await connect() try await waitForState(.connected) } - await self.bindCancellableToConnection(try await self.startSubscription(id)) - }.toAnyCancellable.store(in: &cancellablesBindToConnection) + await self.storeInConnectionCancellables(try await self.startSubscription(id)) + } + self.storeInConnectionCancellables(task.toAnyCancellable) } return filterAppSyncSubscriptionEvent(with: id) @@ -236,24 +237,29 @@ actor AppSyncRealTimeClient: AppSyncRealTimeClientProtocol { } private func subscribeToWebSocketEvent() async { - await self.webSocketClient.publisher.sink { [weak self] _ in + let cancellable = await self.webSocketClient.publisher.sink { [weak self] _ in self?.log.debug("[AppSyncRealTimeClient] WebSocketClient terminated") } receiveValue: { webSocketEvent in Task { [weak self] in - await self?.onWebSocketEvent(webSocketEvent) - }.toAnyCancellable.store(in: &self.cancellables) + let task = Task { [weak self] in + await self?.onWebSocketEvent(webSocketEvent) + } + await self?.storeInCancellables(task.toAnyCancellable) + } } - .store(in: &cancellables) + self.storeInCancellables(cancellable) } private func resumeExistingSubscriptions() { log.debug("[AppSyncRealTimeClient] Resuming existing subscriptions") for (id, _) in self.subscriptions { - Task { + Task { [weak self] in do { - try await self.startSubscription(id).store(in: &cancellablesBindToConnection) + if let cancellable = try await self?.startSubscription(id) { + await self?.storeInConnectionCancellables(cancellable) + } } catch { - log.debug("[AppSyncRealTimeClient] Failed to resume existing subscription with id: (\(id))") + Self.log.debug("[AppSyncRealTimeClient] Failed to resume existing subscription with id: (\(id))") } } } @@ -286,7 +292,7 @@ actor AppSyncRealTimeClient: AppSyncRealTimeClientProtocol { subject.filter { switch $0 { case .success(let response): return response.id == id || response.type == .connectionError - case .failure(let error): return true + case .failure: return true } } .map { result -> AppSyncSubscriptionEvent? in @@ -350,10 +356,6 @@ actor AppSyncRealTimeClient: AppSyncRealTimeClientProtocol { return errors.compactMap(AppSyncRealTimeRequest.parseResponseError(error:)) } - private func bindCancellableToConnection(_ cancellable: AnyCancellable) { - cancellable.store(in: &cancellablesBindToConnection) - } - } // MARK: - On WebSocket Events @@ -366,8 +368,11 @@ extension AppSyncRealTimeClient { if self.state.value == .connectionDropped { log.debug("[AppSyncRealTimeClient] reconnecting appSyncClient after connection drop") Task { [weak self] in - try? await self?.connect() - }.toAnyCancellable.store(in: &cancellablesBindToConnection) + let task = Task { [weak self] in + try? await self?.connect() + } + await self?.storeInConnectionCancellables(task.toAnyCancellable) + } } case let .disconnected(closeCode, reason): // @@ -425,24 +430,37 @@ extension AppSyncRealTimeClient { } } - private func monitorHeartBeats(_ connectionAck: JSONValue?) { + func monitorHeartBeats(_ connectionAck: JSONValue?) { let timeoutMs = connectionAck?.connectionTimeoutMs?.intValue ?? 0 log.debug("[AppSyncRealTimeClient] Starting heart beat monitor with interval \(timeoutMs) ms") - heartBeats.eraseToAnyPublisher() + let cancellable = heartBeats.eraseToAnyPublisher() .debounce(for: .milliseconds(timeoutMs), scheduler: DispatchQueue.global()) .first() - .sink(receiveValue: { - self.log.debug("[AppSyncRealTimeClient] KeepAlive timed out, disconnecting") + .sink(receiveValue: { [weak self] in + Self.log.debug("[AppSyncRealTimeClient] KeepAlive timed out, disconnecting") Task { [weak self] in - await self?.reconnect() - }.toAnyCancellable.store(in: &self.cancellables) + let task = Task { [weak self] in + await self?.reconnect() + } + await self?.storeInCancellables(task.toAnyCancellable) + } }) - .store(in: &cancellablesBindToConnection) + self.storeInConnectionCancellables(cancellable) // start counting down heartBeats.send(()) } } +extension AppSyncRealTimeClient { + private func storeInCancellables(_ cancellable: AnyCancellable) { + self.cancellables.insert(cancellable) + } + + private func storeInConnectionCancellables(_ cancellable: AnyCancellable) { + self.cancellablesBindToConnection.insert(cancellable) + } +} + extension Publisher where Output == AppSyncRealTimeSubscription.State, Failure == Never { func toAppSyncSubscriptionEventStream() -> AnyPublisher { self.compactMap { subscriptionState -> AppSyncSubscriptionEvent? in diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AppSyncRealTimeClient/AppSyncRealTimeClientTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AppSyncRealTimeClient/AppSyncRealTimeClientTests.swift index 83c2c58216..99b36943f1 100644 --- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AppSyncRealTimeClient/AppSyncRealTimeClientTests.swift +++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AppSyncRealTimeClient/AppSyncRealTimeClientTests.swift @@ -551,4 +551,32 @@ class AppSyncRealTimeClientTests: XCTestCase { await fulfillment(of: [startTriggered, errorReceived], timeout: 2) } + + func testReconnect_whenHeartBeatSignalIsNotReceived() async throws { + var cancellables = Set() + let timeout = 1.0 + let mockWebSocketClient = MockWebSocketClient() + let mockAppSyncRequestInterceptor = MockAppSyncRequestInterceptor() + let appSyncClient = AppSyncRealTimeClient( + endpoint: URL(string: "https://example.com")!, + requestInterceptor: mockAppSyncRequestInterceptor, + webSocketClient: mockWebSocketClient + ) + + // start monitoring + await appSyncClient.monitorHeartBeats(.object([ + "connectionTimeoutMs": 100 + ])) + + let reconnect = expectation(description: "webSocket triggers event to connection") + await mockWebSocketClient.actionSubject.sink { action in + switch action { + case .connect: + reconnect.fulfill() + default: break + } + }.store(in: &cancellables) + await fulfillment(of: [reconnect], timeout: 2) + } + } From c8286725ce4f9ff0658b6a82d8fcd3be320d6032 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:37:46 -0400 Subject: [PATCH 4/6] chore(deps): bump rexml from 3.3.4 to 3.3.6 in /canaries/example (#3830) Bumps [rexml](https://github.com/ruby/rexml) from 3.3.4 to 3.3.6. - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](https://github.com/ruby/rexml/compare/v3.3.4...v3.3.6) --- updated-dependencies: - dependency-name: rexml dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- canaries/example/Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canaries/example/Gemfile.lock b/canaries/example/Gemfile.lock index e9e2c22c7f..6bf1c1890d 100644 --- a/canaries/example/Gemfile.lock +++ b/canaries/example/Gemfile.lock @@ -168,7 +168,7 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.3.4) + rexml (3.3.6) strscan rouge (2.0.7) ruby2_keywords (0.0.5) From 610dccdd26a8162ee2a3fcd1673f84ba0cb4e059 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:40:06 -0400 Subject: [PATCH 5/6] chore(deps): bump rexml from 3.3.5 to 3.3.6 (#3836) Bumps [rexml](https://github.com/ruby/rexml) from 3.3.5 to 3.3.6. - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](https://github.com/ruby/rexml/compare/v3.3.5...v3.3.6) --- updated-dependencies: - dependency-name: rexml dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d949113527..1ab3463fe4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -262,7 +262,7 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.3.5) + rexml (3.3.6) strscan rouge (2.0.7) ruby-macho (2.5.1) From cb80b91c38d99932af28df6be07633ee0563be08 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:40:29 -0400 Subject: [PATCH 6/6] fix(auth): clear credentials values only if namespacing has changed (#3827) * fix(auth): clear credentials values only if namespacing has changed * fix session errors --- .../AWSCognitoAuthCredentialStore.swift | 4 +- .../FetchAuthSessionOperationHelper.swift | 90 +++++-------------- .../CredentialStoreConfigurationTests.swift | 10 ++- 3 files changed, 34 insertions(+), 70 deletions(-) diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/CredentialStorage/AWSCognitoAuthCredentialStore.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/CredentialStorage/AWSCognitoAuthCredentialStore.swift index 3bb2a2e1bb..28654896ea 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/CredentialStorage/AWSCognitoAuthCredentialStore.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/CredentialStorage/AWSCognitoAuthCredentialStore.swift @@ -65,12 +65,12 @@ struct AWSCognitoAuthCredentialStore { newIdentityConfigData != nil && oldIdentityPoolConfiguration == newIdentityConfigData { - // retrieve data from the old namespace and save with the new namespace if let oldCognitoCredentialsData = try? keychain._getData(oldNameSpace) { try? keychain._set(oldCognitoCredentialsData, key: newNameSpace) } - } else if oldAuthConfigData != currentAuthConfig { + } else if oldAuthConfigData != currentAuthConfig && + oldNameSpace != newNameSpace { // Clear the old credentials try? keychain._remove(oldNameSpace) } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift index b4a7aa9d4a..98504afe89 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Operations/Helpers/FetchAuthSessionOperationHelper.swift @@ -8,7 +8,7 @@ import Foundation import Amplify -class FetchAuthSessionOperationHelper: DefaultLogger { +class FetchAuthSessionOperationHelper { typealias FetchAuthSessionCompletion = (Result) -> Void @@ -108,85 +108,41 @@ class FetchAuthSessionOperationHelper: DefaultLogger { "Auth plugin is in an invalid state") } - func sessionResultWithError(_ error: AuthorizationError, - authenticationState: AuthenticationState) - throws -> AuthSession { - log.verbose("Received error - \(error)") + func sessionResultWithError( + _ error: AuthorizationError, + authenticationState: AuthenticationState + ) throws -> AuthSession { + log.verbose("Received fetch auth session error - \(error)") var isSignedIn = false - if case .signedIn = authenticationState { - isSignedIn = true - } - switch error { - case .sessionError(let fetchError, let credentials): - return try sessionResultWithFetchError(fetchError, - authenticationState: authenticationState, - existingCredentials: credentials) - case .sessionExpired(let error): - let session = AuthCognitoSignedInSessionHelper.makeExpiredSignedInSession( - underlyingError: error) - return session - default: - let message = "Unknown error occurred" - let error = AuthError.unknown(message) - let session = AWSAuthCognitoSession(isSignedIn: isSignedIn, - identityIdResult: .failure(error), - awsCredentialsResult: .failure(error), - cognitoTokensResult: .failure(error)) - return session - } - } - - func sessionResultWithFetchError(_ error: FetchSessionError, - authenticationState: AuthenticationState, - existingCredentials: AmplifyCredentials) - throws -> AuthSession { + var authError: AuthError = error.authError - var isSignedIn = false if case .signedIn = authenticationState { isSignedIn = true } switch error { - - case .notAuthorized, .noCredentialsToRefresh: - if !isSignedIn { + case .sessionError(let fetchError, _): + if (fetchError == .notAuthorized || fetchError == .noCredentialsToRefresh) && !isSignedIn { return AuthCognitoSignedOutSessionHelper.makeSessionWithNoGuestAccess() - } - - case .service(let error): - var authError: AuthError - if let convertedAuthError = (error as? AuthErrorConvertible)?.authError { - authError = convertedAuthError } else { - authError = AuthError.service( - "Unknown service error occurred", - "See the attached error for more details", - error) + authError = fetchError.authError } - let session = AWSAuthCognitoSession( - isSignedIn: isSignedIn, - identityIdResult: .failure(authError), - awsCredentialsResult: .failure(authError), - cognitoTokensResult: .failure(authError)) + case .sessionExpired(let error): + let session = AuthCognitoSignedInSessionHelper.makeExpiredSignedInSession( + underlyingError: error) return session - default: break - + default: + break } - let message = "Unknown error occurred" - let error = AuthError.unknown(message) - let session = AWSAuthCognitoSession(isSignedIn: isSignedIn, - identityIdResult: .failure(error), - awsCredentialsResult: .failure(error), - cognitoTokensResult: .failure(error)) - return session - } - public static var log: Logger { - Amplify.Logging.logger(forCategory: CategoryType.auth.displayName, forNamespace: String(describing: self)) - } - - public var log: Logger { - Self.log + let session = AWSAuthCognitoSession( + isSignedIn: isSignedIn, + identityIdResult: .failure(authError), + awsCredentialsResult: .failure(authError), + cognitoTokensResult: .failure(authError)) + return session } } + +extension FetchAuthSessionOperationHelper: DefaultLogger { } diff --git a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/CredentialStore/CredentialStoreConfigurationTests.swift b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/CredentialStore/CredentialStoreConfigurationTests.swift index 0dd8d34e9f..8efbebdefd 100644 --- a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/CredentialStore/CredentialStoreConfigurationTests.swift +++ b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/CredentialStore/CredentialStoreConfigurationTests.swift @@ -89,8 +89,16 @@ class CredentialStoreConfigurationTests: AWSAuthBaseTest { XCTFail("Unable to save credentials") } + // When configuration changed + let updatedConfig = AuthConfiguration.userPoolsAndIdentityPools( + UserPoolConfigurationData(poolId: Defaults.userPoolId, + clientId: Defaults.appClientId, + region: Defaults.regionString, + clientSecret: Defaults.appClientSecret, + pinpointAppId: "somethingNew"), + Defaults.makeIdentityConfigData()) // When configuration don't change changed - let newCredentialStore = AWSCognitoAuthCredentialStore(authConfiguration: initialAuthConfig) + let newCredentialStore = AWSCognitoAuthCredentialStore(authConfiguration: updatedConfig) // Then guard let credentials = try? newCredentialStore.retrieveCredential(),