From 238850c00c505d39b6f68e5191e71cc5b895b615 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:13:26 -0400 Subject: [PATCH 01/57] fix(auth): throw the identity pool's error from confirmSignIn instead of never returning When the user pool accepted the answer but the identity pool step failed, confirmSignIn's wait loop had no arm for the authorization error state, so the call never returned and the Auth calls queued behind it waited too. Throw the error, as signIn does. --- .../Task/AWSAuthConfirmSignInTask.swift | 5 ++ .../AWSAuthConfirmSignInTaskTests.swift | 46 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignInTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignInTask.swift index 720b4d4f1b..cd6e63fa1f 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignInTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignInTask.swift @@ -70,6 +70,11 @@ final class AWSAuthConfirmSignInTask: AuthConfirmSignInTask, DefaultLogger, @unc case .signedIn: if case .sessionEstablished = authZState { return AuthSignInResult(nextStep: .done) + } else if case .error(let error) = authZState { + // The user pool accepted the answer but the identity pool step failed. The machine stays + // here, so without this arm the call would never return. + log.verbose("Authorization reached an error state \(error)") + throw error.authError } else { log.verbose("Signed In, waiting for authorization to complete") } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift index 90c9556207..47e2242776 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift @@ -8,6 +8,7 @@ import Foundation import Amplify +import AWSCognitoIdentity import AWSCognitoIdentityProvider import XCTest @testable import AWSCognitoAuthPlugin @@ -862,4 +863,49 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest, @unchecked Senda XCTFail("Should not throw when state is already .signedIn; got \(error)") } } + + /// Test that confirmSignIn throws the identity pool's error when the user pool accepted the answer + /// + /// - Given: an auth plugin with mocked service. `RespondToAuthChallenge` accepts the code and answers tokens, + /// and the identity pool's `GetId` fails with `InvalidParameterException` + /// - When: + /// - I invoke confirmSignIn with a valid confirmation code, then getCurrentUser + /// - Then: + /// - confirmSignIn throws a `.service` error with `AWSCognitoAuthError.invalidParameter` as underlying error, + /// instead of never returning + /// - the Auth call queued behind it returns too, each within the bound + /// + func testConfirmSignInThrowsTheIdentityPoolsError() async { + mockIdentity = MockIdentity( + mockGetIdResponse: { _ in + throw AWSCognitoIdentity.InvalidParameterException(message: "Invalid parameter passed") + }, + mockGetCredentialsResponse: getCredentials + ) + mockIdentityProvider = MockIdentityProvider( + mockRespondToAuthChallengeResponse: { _ in + return .testData() + }) + let confirmed = expectation(description: "confirmSignIn returns") + let queued = expectation(description: "the queued call returns") + + Task { + do { + let result = try await self.plugin.confirmSignIn(challengeResponse: "code") + XCTFail("Should not succeed, got \(result)") + } catch AuthError.service(_, _, let underlyingError) { + guard case .invalidParameter = underlyingError as? AWSCognitoAuthError else { + XCTFail("Underlying error should be AWSCognitoAuthError.invalidParameter, got \(String(describing: underlyingError))") + return + } + } catch { + XCTFail("Should produce a service error instead of \(error)") + } + confirmed.fulfill() + _ = try? await self.plugin.getCurrentUser() + queued.fulfill() + } + + await fulfillment(of: [confirmed, queued], timeout: 5, enforceOrder: true) + } } From fc951e1a2c44e48ebe0847c405120c60dbd17d54 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:13:26 -0400 Subject: [PATCH 02/57] fix(auth): redact the challenge session in every printed form of a sign-in challenge The challenge a sign-in answers had no description of its own, so the verbose lines that print sign-in events and states by reflection printed its session and username in full. Give it description, debug description and mirror conformances that redact the session and mask the usernames. --- .../UnitTests/ClientLogCategoryTests.swift | 5 +- .../LiveEngineLogRedactionTests.swift | 2 +- .../CodeGen/Data/RespondToAuthChallenge.swift | 35 +++ .../ChallengeSessionRedactionTests.swift | 202 ++++++++++++++++++ 4 files changed, 241 insertions(+), 3 deletions(-) create mode 100644 AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/ChallengeSessionRedactionTests.swift diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/ClientLogCategoryTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/ClientLogCategoryTests.swift index 05577f9a59..2c17309c22 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/ClientLogCategoryTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/ClientLogCategoryTests.swift @@ -268,7 +268,8 @@ final class ClientLogCategoryTests: XCTestCase { /// - When: /// - each is logged as the engine's actions log the events they send /// - Then: - /// - each line is the engine's, with every username, labelled or not, replaced by `` + /// - each line is the engine's, with every username, labelled or not, replaced by ``, and the challenge + /// session `` func testTheEngineLoggerRedactsTheUsernameOfTheSignInEventsThatCarryItUnlabelled() throws { let scope = EngineLogScope.categoryNamespace("Authentication", "UnlabelledProbe-\(UUID().uuidString)") let logger = ClientEngineLogger().scoped(scope) @@ -306,7 +307,7 @@ final class ClientLogCategoryTests: XCTestCase { prefix + #"respondDeviceSRPChallenge("", "# + device, prefix + #"initiateTOTPSetup("", InternalAWSCognitoAuth.RespondToAuthChallenge(challenge: "# + "AWSCognitoIdentityProvider.CognitoIdentityProviderClientTypes.ChallengeNameType.mfaSetup, " - + #"availableChallenges: [], username: "", session: Optional("setup-session"), "# + + #"availableChallenges: [], username: "", session: Optional(""), "# + #"parameters: nil, inputUsername: Optional(""))), time: nil)"# ]) } diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/LiveEngineLogRedactionTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/LiveEngineLogRedactionTests.swift index f8b7f59343..06b87e71a1 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/LiveEngineLogRedactionTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/LiveEngineLogRedactionTests.swift @@ -25,7 +25,7 @@ final class LiveEngineLogRedactionTests: XCTestCase { /// The rest of a sent `initiateTOTPSetup` line: the challenge, with Cognito's username and the typed one redacted. private static let totpChallenge = "InternalAWSCognitoAuth.RespondToAuthChallenge(challenge: " + "AWSCognitoIdentityProvider.CognitoIdentityProviderClientTypes.ChallengeNameType.mfaSetup, " - + #"availableChallenges: [], username: "", session: Optional("challenge-session"), "# + + #"availableChallenges: [], username: "", session: Optional(""), "# + #"parameters: Optional(["MFAS_CAN_SETUP": "[\"SOFTWARE_TOKEN_MFA\"]"]), inputUsername: Optional(""))), "# + "time: nil)" diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/Data/RespondToAuthChallenge.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/Data/RespondToAuthChallenge.swift index bca2475511..b89b02f6c8 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/Data/RespondToAuthChallenge.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/Data/RespondToAuthChallenge.swift @@ -120,3 +120,38 @@ package extension RespondToAuthChallenge { } extension RespondToAuthChallenge: Codable { } + +// Every printed form redacts the challenge session, as `SignInEventData` does, and masks the usernames. The challenge +// rides in sign-in events whose verbose "Sending event" lines print the event by reflection, and in the sign-in +// states, so default printing would log the session in full. The rest reads as default printing does. +extension RespondToAuthChallenge: CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable { + + package var description: String { + "\(String(reflecting: Self.self))(challenge: \(String(reflecting: challenge)), " + + "availableChallenges: \(String(reflecting: availableChallenges)), " + + "username: \(String(reflecting: username.maskedForLog())), " + + "session: \(String(reflecting: session.map { $0.redactedForLog() })), " + + "parameters: \(String(reflecting: parameters)), " + + "inputUsername: \(String(reflecting: inputUsername.map { $0.maskedForLog() })))" + } + + package var debugDescription: String { + description + } + + /// For `dump` and debuggers: the session redacted, the usernames masked. + package var customMirror: Mirror { + Mirror( + self, + children: [ + "challenge": challenge, + "availableChallenges": availableChallenges, + "username": username.maskedForLog(), + "session": session.map { $0.redactedForLog() } as Any, + "parameters": parameters as Any, + "inputUsername": inputUsername.map { $0.maskedForLog() } as Any + ], + displayStyle: .struct + ) + } +} diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/ChallengeSessionRedactionTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/ChallengeSessionRedactionTests.swift new file mode 100644 index 0000000000..19da025266 --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/ChallengeSessionRedactionTests.swift @@ -0,0 +1,202 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AWSCognitoIdentityProvider +import Foundation +import XCTest +@testable import AWSCognitoAuthPlugin +@testable import InternalAWSCognitoAuth + +/// The challenge a sign-in answers prints with its session redacted in every form: the sign-in actions log the +/// events that carry it at verbose by string interpolation, and states that hold it can be dumped. +final class ChallengeSessionRedactionTests: XCTestCase { + + private static let session = "challenge-session-value" + private static let username = "challenge-probe-user" + private static let inputUsername = "challenge-input-user" + + private let challenge = RespondToAuthChallenge( + challenge: .smsMfa, + availableChallenges: [.smsMfa], + username: username, + session: session, + parameters: ["CODE_DELIVERY_DELIVERY_MEDIUM": "SMS"], + inputUsername: inputUsername + ) + + /// Every printed form of `value`: `print`, `debugPrint`, string interpolation and `dump`. + private func printedForms(of value: Any) -> [String] { + var printed = "" + print(value, to: &printed) + var debugPrinted = "" + debugPrint(value, to: &debugPrinted) + var dumped = "" + dump(value, to: &dumped) + return [printed, debugPrinted, "\(value)", dumped] + } + + /// Fails for every form of `value` that holds one of `clear`: by default the session or a username, in full. + private func assertRedacted( + _ value: Any, + clear: [String] = [session, username, inputUsername], + file: StaticString = #filePath, + line: UInt = #line + ) { + for form in printedForms(of: value) { + for clear in clear where form.contains(clear) { + XCTFail("a printed form holds \(clear): \(form)", file: file, line: line) + } + } + } + + /// Test that the challenge prints without its session + /// + /// - Given: a challenge with a session, a username and an input username + /// - When: + /// - it is printed with `print`, `debugPrint`, interpolation and `dump` + /// - Then: + /// - no form holds the session or a username + /// - `description` and `debugDescription` read as default printing does, with the session `` and the + /// usernames masked, and `dump` names each field with the same values + /// + func testTheChallengePrintsWithoutItsSession() { + assertRedacted(challenge) + XCTAssertEqual(challenge.debugDescription, challenge.description) + let username = Self.username.maskedForLog() + let inputUsername = Self.inputUsername.maskedForLog() + XCTAssertEqual( + challenge.description, + "InternalAWSCognitoAuth.RespondToAuthChallenge(challenge: " + + "AWSCognitoIdentityProvider.CognitoIdentityProviderClientTypes.ChallengeNameType.smsMfa, " + + "availableChallenges: [AWSCognitoIdentityProvider.CognitoIdentityProviderClientTypes.ChallengeNameType.smsMfa], " + + #"username: "\#(username)", session: Optional(""), "# + + #"parameters: Optional(["CODE_DELIVERY_DELIVERY_MEDIUM": "SMS"]), inputUsername: Optional("\#(inputUsername)"))"# + ) + + var dumped = "" + dump(challenge, to: &dumped) + XCTAssertTrue(dumped.contains(#"session: Optional("")"#), dumped) + XCTAssertTrue(dumped.contains(#"- username: "\#(username)""#), dumped) + XCTAssertTrue(dumped.contains(#"inputUsername: Optional("\#(inputUsername)")"#), dumped) + for field in ["challenge", "availableChallenges", "parameters"] { + XCTAssertTrue(dumped.contains(" \(field): "), dumped) + } + } + + /// Test that a challenge without a session prints its session as `nil` + /// + /// - Given: a challenge with no session + /// - When: + /// - it is printed and dumped + /// - Then: + /// - the session is `nil`, as default printing shows it + /// + func testAChallengeWithoutASessionPrintsANilSession() { + let challenge = RespondToAuthChallenge(challenge: .smsMfa, availableChallenges: [], username: "u", session: nil, parameters: nil) + + XCTAssertTrue(challenge.description.contains("session: nil, "), challenge.description) + var dumped = "" + dump(challenge, to: &dumped) + XCTAssertTrue(dumped.contains("- session: nil"), dumped) + } + + /// Test that the events and states that carry the challenge print without its session + /// + /// - Given: each sign-in event that carries the challenge, and the states that hold it + /// - When: + /// - each is interpolated as a "Sending event" line does, and printed in every other form + /// - Then: + /// - no form holds the session; no state, and no event but the TOTP setup's (which carries the username + /// beside the challenge), holds a username + /// + func testTheEventsAndStatesThatCarryTheChallengePrintWithoutItsSession() { + let events: [Any] = [ + SignInEvent(id: "event-1", eventType: .receivedChallenge(challenge)), + SignInEvent(id: "event-2", eventType: .initiateTOTPSetup(Self.username, challenge)), + SignInEvent( + id: "event-3", + eventType: .initiateSignInWithSRP( + SignInEventData(username: nil, password: nil, signInMethod: .apiBased(.userSRP)), + .noData, + challenge + ) + ), + SetUpTOTPEvent(id: "event-4", eventType: .setUpTOTP(challenge)) + ] + for event in events { + let line = "InternalAWSCognitoAuth/VerifyPasswordSRP.swift Sending event \(event)" + XCTAssertFalse(line.contains(Self.session), line) + XCTAssertTrue(line.contains(#"session: Optional("")"#), line) + if case .initiateTOTPSetup? = (event as? SignInEvent)?.eventType { + assertRedacted(event, clear: [Self.session, Self.inputUsername]) + } else { + assertRedacted(event) + } + } + + let states: [Any] = [ + SignInChallengeState.waitingForAnswer(challenge, .apiBased(.userSRP), .confirmSignInWithTOTPCode), + SignInState.resolvingChallenge( + .waitingForAnswer(challenge, .apiBased(.userSRP), .confirmSignInWithTOTPCode), + .smsMfa, + .apiBased(.userSRP) + ) + ] + for state in states { + assertRedacted(state) + } + } + + /// Test that the line `VerifyPasswordSRP` logs for the challenge it sends holds no session + /// + /// - Given: `VerifyPasswordSRP` over a user pool whose `RespondToAuthChallenge` answers an SMS MFA challenge with a + /// session, and a capturing logger as the environment's + /// - When: + /// - the action runs + /// - Then: + /// - it sends the challenge, with its session + /// - its "Sending event" line shows the session as ``, and no line holds it + /// + func testTheVerifyPasswordLineHoldsNoSession() async throws { + let router = CapturingRouter() + let defaults = Defaults.makeDefaultAuthEnvironment(userPoolFactory: { + MockIdentityProvider(mockRespondToAuthChallengeResponse: { _ in + RespondToAuthChallengeOutput( + challengeName: .smsMfa, + challengeParameters: ["CODE_DELIVERY_DELIVERY_MEDIUM": "SMS"], + session: Self.session + ) + }) + }) + let environment = AuthEnvironment( + configuration: defaults.configuration, + userPoolConfigData: defaults.userPoolConfigData, + identityPoolConfigData: defaults.identityPoolConfigData, + authenticationEnvironment: defaults.authenticationEnvironment, + authorizationEnvironment: defaults.authorizationEnvironment, + credentialsClient: defaults.credentialsClient, + logger: router.scopedLogger() + ) + let action = VerifyPasswordSRP(stateData: .testData, authResponse: InitiateAuthOutput.validTestData, clientMetadata: [:]) + let sent = expectation(description: "the challenge is sent") + let dispatcher = MockDispatcher { event in + guard case .receivedChallenge(let challenge) = (event as? SignInEvent)?.eventType else { + return + } + XCTAssertEqual(challenge.session, Self.session) + sent.fulfill() + } + + await action.execute(withDispatcher: dispatcher, environment: environment) + + await fulfillment(of: [sent], timeout: 1) + let messages = router.entries.map(\.message) + XCTAssertFalse(messages.contains { $0.contains(Self.session) }, "\(messages)") + let line = try XCTUnwrap(messages.first { $0.contains("VerifyPasswordSRP.swift Sending event") }, "\(messages)") + XCTAssertTrue(line.contains(#"session: Optional("")"#), line) + } +} From d469846fe245279457831965f90c9b4251618f14 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:13:38 -0400 Subject: [PATCH 03/57] fix(auth): save the signed-out record when the plugin ends a session under an access group With a keychain access group set, sign-out (local and global), clearFederationToIdentityPool and the sign-out after deleteUser now save {"noCredentials":{}} in the shared service instead of deleting the session record: the bytes AmplifyCognitoClient's default session saves on sign-out, which every plugin release reads as signed out. An app extension's first access-group migration then stops at the record, as it does at a login, and never moves a stale login in over a user who signed out. Without an access group the record is still deleted. --- .../AWSCognitoAuthCredentialStore.swift | 14 +- .../AccessGroupSignedOutRecordTests.swift | 395 ++++++++++++++++++ 2 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift index aa71f5031b..196013f060 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift @@ -244,8 +244,20 @@ extension AWSCognitoAuthCredentialStore: AmplifyAuthCredentialStoreBehavior { return amplifyCredential } + /// Ends the saved session. Every plugin path that ends one clears it through here: sign-out, local and global, + /// the local sign-out after a deleted user, and clearing the federation to the identity pool. + /// + /// Without an access group the session record is deleted. With one, it is replaced by the signed-out record, + /// `{"noCredentials":{}}`, the bytes the Cognito client's default session saves on sign-out, which every plugin + /// release reads as signed out. In the shared service a sign-out then counts as migrated: an app extension's + /// first access-group migration stops at the record (`sharedKeychainHasMigratedItems(accessGroup:source:)`) + /// and never moves its own stale login in over it. The next sign-in replaces the record. package func deleteCredential() throws { - try removeSession(for: authConfiguration) + guard accessGroup != nil else { + try removeSession(for: authConfiguration) + return + } + try saveCredential(.noCredentials) } package func saveDevice(_ deviceMetadata: DeviceMetadata, for username: String) throws { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift new file mode 100644 index 0000000000..b10fa6fd22 --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift @@ -0,0 +1,395 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +@_spi(AmplifyExperimental) import AmplifyFoundation +import AmplifyKeychainTestCommon +import AWSCognitoIdentity +import AWSCognitoIdentityProvider +import Foundation +import InternalAmplifyKeychain +import XCTest +@_spi(AmplifyExperimental) @testable import AmplifyCognitoClient +@testable import AWSCognitoAuthPlugin +@testable import InternalAWSCognitoAuth + +/// Under an access group, every plugin path that ends the session saves the signed-out record, +/// `{"noCredentials":{}}`, in the shared service instead of deleting the session record: sign-out, local and +/// global, clearing the federation to the identity pool, and the local sign-out after a deleted user. The record +/// then stops an app extension's first access-group migration, as a login does, so the extension's stale login is +/// never moved in over the user who signed out. Without an access group the record is still deleted. +/// +/// The plugin runs its real credential store, through its test seam, over an in-memory keychain shared with the +/// extension and with the Cognito client's `.default` session. Only the Cognito service calls are mocked. +final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { + + private let service = "com.amplify.awsCognitoAuthPlugin" + private let sharedService = "com.amplify.awsCognitoAuthPluginShared" + private let accessGroup = "group.acme" + + /// Exactly what the Cognito client's `.default` saves on sign-out. + private let signedOutRecord = Data(#"{"noCredentials":{}}"#.utf8) + + /// The app's configuration, as the plugin stores it: `ClientOverKeychain`'s, so the client's `.default` uses the + /// same record. + private let authConfiguration = AuthConfiguration(client: ClientOverKeychain.configuration) + private var sessionAccount: String { + AWSCognitoAuthCredentialStore.sessionAccount(for: authConfiguration) + } + + private var keychain: InMemoryKeychain! + private var userDefaults: UserDefaults! + private var userDefaultsSuite: String! + + override func setUp() { + super.setUp() + keychain = InMemoryKeychain() + userDefaultsSuite = "AccessGroupSignedOutRecordTests.\(UUID().uuidString)" + userDefaults = UserDefaults(suiteName: userDefaultsSuite) + } + + override func tearDown() async throws { + userDefaults.removePersistentDomain(forName: userDefaultsSuite) + keychain = nil + userDefaults = nil + try await super.tearDown() + } + + // MARK: The store + + /// Test that the store saves the signed-out record under an access group, and deletes the record without one + /// + /// - Given: a login saved by the plugin's store with an access group, and one saved without + /// - When: + /// - each store ends its session + /// - Then: + /// - the shared service holds `{"noCredentials":{}}`, byte for byte what the client's `.default` writes, and + /// the store reads it as no credentials + /// - the unshared service holds no session record + /// + func testTheStoreSavesTheSignedOutRecordOnlyUnderAnAccessGroup() throws { + let shared = makeStore(accessGroup: accessGroup) + try shared.saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + let unshared = makeStore(accessGroup: nil) + try unshared.saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + + try shared.deleteCredential() + try unshared.deleteCredential() + + XCTAssertEqual(sharedRecord(), signedOutRecord) + XCTAssertEqual(sharedRecord(), PluginRecordSummary.signedOutPayload) + XCTAssertEqual(sharedRecord(), try JSONEncoder().encode(AmplifyCredentials.noCredentials)) + XCTAssertEqual(try shared.retrieveCredential(), .noCredentials) + XCTAssertNil(keychain.value(service: service, account: sessionAccount)) + } + + // MARK: Each path that ends the session + + /// Test that a plugin sign-out under an access group saves the signed-out record, and the next launch is signed out + /// + /// - Given: alice's login in the shared service, and the plugin configured with the access group + /// - When: + /// - the plugin signs out, locally, then (after alice signs in again) globally + /// - Then: + /// - each sign-out completes, and the shared service holds `{"noCredentials":{}}` + /// - a plugin configured over it, as at the next launch, is signed out + /// - an app extension's first migration then stops at the record, and its own stale login stays in its + /// unshared service + /// + func testASignOutSavesTheSignedOutRecord() async throws { + for globalSignOut in [false, true] { + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + let plugin = makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()) + + let result = await plugin.signOut(options: .init(globalSignOut: globalSignOut)) + + guard let signOut = result as? AWSCognitoSignOutResult, case .complete = signOut else { + return XCTFail("the sign-out (global: \(globalSignOut)) did not complete: \(result)") + } + XCTAssertEqual(sharedRecord(), signedOutRecord, "global: \(globalSignOut)") + await assertTheNextLaunchIsSignedOut() + try assertAnExtensionsFirstMigrationStops() + } + } + + /// Test that clearing the federation to the identity pool under an access group saves the signed-out record + /// + /// - Given: a federated identity in the shared service, and the plugin configured with the access group + /// - When: + /// - the plugin clears the federation + /// - Then: + /// - the shared service holds `{"noCredentials":{}}`, the next launch is signed out, and an app extension's + /// first migration stops at the record + /// + func testClearingTheFederationSavesTheSignedOutRecord() async throws { + try makeStore(accessGroup: accessGroup).saveCredential(.identityPoolWithFederation( + federatedToken: FederatedToken(token: "federated-token", provider: .facebook), + identityID: "federated-identity", + credentials: LongLivedCredentials.awsCredentials() + )) + let plugin = makePlugin(accessGroup: accessGroup, userPool: MockIdentityProvider()) + + try await plugin.clearFederationToIdentityPool() + + XCTAssertEqual(sharedRecord(), signedOutRecord) + await assertTheNextLaunchIsSignedOut() + try assertAnExtensionsFirstMigrationStops() + } + + /// Test that a deleted user's local sign-out under an access group saves the signed-out record + /// + /// - Given: alice's login in the shared service, and the plugin configured with the access group + /// - When: + /// - the plugin deletes the user + /// - Then: + /// - the shared service holds `{"noCredentials":{}}`, the next launch is signed out, and an app extension's + /// first migration stops at the record + /// + func testDeletingTheUserSavesTheSignedOutRecord() async throws { + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + let plugin = makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()) + + try await plugin.deleteUser() + + XCTAssertEqual(sharedRecord(), signedOutRecord) + await assertTheNextLaunchIsSignedOut() + try assertAnExtensionsFirstMigrationStops() + } + + /// Test that without an access group each path that ends the session still deletes the record + /// + /// - Given: alice's login in the unshared service, and the plugin configured without an access group + /// - When: + /// - the plugin signs out; then, signed in again, deletes the user; then, federated, clears the federation + /// - Then: + /// - after each, the unshared service holds no session record, and nothing is saved in the shared service + /// + func testWithoutAnAccessGroupEachPathStillDeletesTheRecord() async throws { + try makeStore(accessGroup: nil).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + _ = await makePlugin(accessGroup: nil, userPool: signOutUserPool()).signOut() + XCTAssertNil(keychain.value(service: service, account: sessionAccount), "after the sign-out") + + try makeStore(accessGroup: nil).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + try await makePlugin(accessGroup: nil, userPool: signOutUserPool()).deleteUser() + XCTAssertNil(keychain.value(service: service, account: sessionAccount), "after the deletion") + + try makeStore(accessGroup: nil).saveCredential(.identityPoolWithFederation( + federatedToken: FederatedToken(token: "federated-token", provider: .facebook), + identityID: "federated-identity", + credentials: LongLivedCredentials.awsCredentials() + )) + try await makePlugin(accessGroup: nil, userPool: MockIdentityProvider()).clearFederationToIdentityPool() + XCTAssertNil(keychain.value(service: service, account: sessionAccount), "after clearing the federation") + + XCTAssertNil(sharedRecord()) + } + + // MARK: After the sign-out + + /// Test that a sign-in after a sign-out replaces the signed-out record + /// + /// - Given: the plugin, configured with the access group, signed out, so the shared service holds the signed-out + /// record + /// - When: + /// - the plugin signs in with SRP + /// - Then: + /// - the sign-in completes, and the shared service holds the new login + /// + func testASignInAfterTheSignOutReplacesTheRecord() async throws { + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + let signOutPlugin = makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()) + _ = await signOutPlugin.signOut() + XCTAssertEqual(sharedRecord(), signedOutRecord) + + let plugin = makePlugin(accessGroup: accessGroup, userPool: KeychainQueryParityTests.signInUserPool()) + let result = try await plugin.signIn(username: "alice", password: "password", options: .init()) + + XCTAssertTrue(result.isSignedIn) + let saved = try XCTUnwrap(sharedRecord()) + guard case .userPoolAndIdentityPool = try JSONDecoder().decode(AmplifyCredentials.self, from: saved) else { + return XCTFail("the shared service does not hold the new login") + } + } + + /// Test that the Cognito client's `.default` reads the plugin's signed-out record as signed out + /// + /// - Given: the plugin, configured with the access group, signed out, so the shared service holds the signed-out + /// record + /// - When: + /// - the client's `.default` in the access group reads its state, and the saved sessions are listed + /// - Then: + /// - `.default` is `.signedOut`, its token provider throws `notSignedIn`, and the record is unchanged + /// - the default listing shows no `.default` row; with signed-out rows, `.default` is a signed-out row with no + /// label or username, as after the client's own sign-out without a sidecar + /// + func testTheClientsDefaultSessionReadsThePluginsSignedOutRecordAsSignedOut() async throws { + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + _ = await makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()).signOut() + XCTAssertEqual(sharedRecord(), signedOutRecord) + + let app = ClientOverKeychain(keychain: keychain) + var client: AmplifyCognitoClient? = try app.client(accessGroup: accessGroup) + let state = try await XCTUnwrap(client).currentSessionState() + XCTAssertEqual(state, .signedOut) + do { + _ = try await XCTUnwrap(client).userPoolTokenProvider.accessToken() + XCTFail("a signed-out `.default` returned an access token") + } catch { + guard case .notSignedIn? = error as? CredentialsError else { + return XCTFail("the token provider threw \(error), not notSignedIn") + } + } + client = nil + await app.waitForBaseline() + + let listed = try await AmplifyCognitoClient.storedSessions( + configuration: ClientOverKeychain.configuration, + accessGroup: accessGroup, + includingSignedOut: false, + dependencies: app.dependencies + ) + XCTAssertFalse(listed.contains { $0.sessionId == .default }) + let withSignedOut = try await AmplifyCognitoClient.storedSessions( + configuration: ClientOverKeychain.configuration, + accessGroup: accessGroup, + includingSignedOut: true, + dependencies: app.dependencies + ) + let row = try XCTUnwrap(withSignedOut.first { $0.sessionId == .default }) + XCTAssertEqual(row.kind, .signedOut) + XCTAssertNil(row.label) + XCTAssertNil(row.username) + XCTAssertEqual(sharedRecord(), signedOutRecord) + } + + // MARK: Helpers + + private func sharedRecord() -> Data? { + keychain.value(service: sharedService, accessGroup: accessGroup, account: sessionAccount) + } + + /// A plugin configured over the shared record, as at the next launch, is signed out, and the record is unchanged. + private func assertTheNextLaunchIsSignedOut(file: StaticString = #filePath, line: UInt = #line) async { + let plugin = makePlugin(accessGroup: accessGroup, userPool: MockIdentityProvider()) + do { + let user = try await plugin.getCurrentUser() + XCTFail("the next launch is signed in as \(user.username)", file: file, line: line) + } catch { + guard case AuthError.signedOut = error else { + return XCTFail("the next launch failed with \(error), not signedOut", file: file, line: line) + } + } + guard case .configured(.signedOut, _, _) = await plugin.authStateMachine.currentState else { + return XCTFail("the next launch is in \(await plugin.authStateMachine.currentState)", file: file, line: line) + } + XCTAssertEqual(sharedRecord(), signedOutRecord, file: file, line: line) + } + + /// An app extension's first configuration with the access group and `migrateKeychainItems: true`, its unshared + /// service holding bob's stale login and its `UserDefaults` no access group, stops at the signed-out record: the + /// record stays, the new store reads no login, and bob's login stays in the extension's own service. + private func assertAnExtensionsFirstMigrationStops(file: StaticString = #filePath, line: UInt = #line) throws { + // Only a sign-in writes a device record; none was saved, so the signed-out record alone stops the migration. + let pluginAccounts = try keychain.store(service: sharedService, accessGroup: accessGroup).allAccounts() + .filter { !SessionRecordAccount.isClientSessionRecord($0) } + XCTAssertEqual(pluginAccounts.sorted(), ["authConfiguration", sessionAccount].sorted(), file: file, line: line) + let extensionKeychain = InMemoryKeychain() + let bob = try JSONEncoder().encode(LongLivedCredentials.userPoolAndIdentityPool(username: "bob")) + try extensionKeychain.store(service: service).set(bob, key: sessionAccount) + let suite = "AccessGroupSignedOutRecordTests.extension.\(UUID().uuidString)" + let extensionDefaults = try XCTUnwrap(UserDefaults(suiteName: suite)) + defer { extensionDefaults.removePersistentDomain(forName: suite) } + let keychain = keychain! + + let store = AWSCognitoAuthCredentialStore( + authConfiguration: authConfiguration, + accessGroup: accessGroup, + migrateKeychainItemsOfUserSession: true, + userDefaults: extensionDefaults, + makeKeychainStore: { service, accessGroup in + accessGroup == nil ? extensionKeychain.store(service: service) : keychain.store(service: service, accessGroup: accessGroup) + }, + logger: AmplifyEngineLogRouter() + ) + + XCTAssertEqual(sharedRecord(), signedOutRecord, file: file, line: line) + XCTAssertEqual(try store.retrieveCredential(), .noCredentials, file: file, line: line) + XCTAssertEqual(extensionKeychain.value(service: service, account: sessionAccount), bob, file: file, line: line) + } + + /// The plugin's credential store over the shared keychain, as `makeCredentialStore` builds it, without migration. + private func makeStore(accessGroup: String?) -> AWSCognitoAuthCredentialStore { + let keychain = keychain! + return AWSCognitoAuthCredentialStore( + authConfiguration: authConfiguration, + accessGroup: accessGroup, + migrateKeychainItemsOfUserSession: false, + userDefaults: userDefaults, + makeKeychainStore: { service, accessGroup in + keychain.store(service: service, accessGroup: accessGroup) + }, + logger: AmplifyEngineLogRouter() + ) + } + + private func signOutUserPool() -> MockIdentityProvider { + MockIdentityProvider( + mockRevokeTokenResponse: { _ in .testData }, + mockGlobalSignOutResponse: { _ in .testData }, + mockDeleteUserOutput: { _ in DeleteUserOutput() } + ) + } + + /// A plugin as `configure(using:)` builds it, with its credential store from `makeStore(accessGroup:)` and only + /// the Cognito service calls mocked. + private func makePlugin(accessGroup: String?, userPool: CognitoUserPoolBehavior) -> AWSCognitoAuthPlugin { + let identity = MockIdentity( + mockGetIdResponse: { _ in .init(identityId: "client-identity-id") }, + mockGetCredentialsResponse: { input in + .init( + credentials: CognitoIdentityClientTypes.Credentials( + accessKeyId: "refreshedAccessKey", + expiration: Date(timeIntervalSinceNow: 3_600), + secretKey: "refreshedSecret", + sessionToken: "refreshedSession" + ), + identityId: input.identityId + ) + } + ) + let factory = AuthEnvironmentFactory( + authConfiguration: authConfiguration, + makeUserPool: { userPool }, + makeIdentityClient: { identity }, + credentialStoreFactory: { [self] in makeStore(accessGroup: accessGroup) }, + legacyKeychainStoreFactory: Defaults.makeLegacyStore(service:), + logger: AmplifyEngineLogRouter(scope: .category("awsCognitoAuthPluginTest")), + userPoolAnalytics: { MockAnalyticsHandler() }, + makeURLSession: { URLSession(configuration: .ephemeral) }, + makeAdvancedSecurity: Defaults.makeDefaultASF + ) + let credentialStoreMachine = CredentialStoreStateMachine( + resolver: CredentialStoreState.Resolver(), + environment: factory.makeCredentialEnvironment(), + initialState: .idle + ) + let authEnvironment = factory.makeAuthEnvironment( + credentialsClient: CredentialStoreOperationClient(credentialStoreStateMachine: credentialStoreMachine) + ) + let plugin = AWSCognitoAuthPlugin() + plugin.configure( + authConfiguration: authConfiguration, + authEnvironment: authEnvironment, + authStateMachine: AuthStateMachine(resolver: AuthState.Resolver(logger: AmplifyEngineLogRouter()), environment: authEnvironment), + credentialStoreStateMachine: credentialStoreMachine, + hubEventHandler: MockAuthHubEventBehavior(), + analyticsHandler: MockAnalyticsHandler() + ) + settleConfigureOperationOnTeardown(of: plugin) + return plugin + } +} From d5afa46e67b21bec033fdcba5fca70e81f3b8786 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:13:38 -0400 Subject: [PATCH 04/57] docs(auth): describe the credential store's log category without the milestone name --- .../CredentialStorage/AWSCognitoAuthCredentialStore.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift index 196013f060..cacf2251a6 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift @@ -336,7 +336,8 @@ private extension AWSCognitoAuthCredentialStore { } package extension AWSCognitoAuthCredentialStore { - /// This store's lines, at their pre-M2 category, through the caller's logger. + /// This store's lines, at the plugin's category for them from before the engine was extracted, through the + /// caller's logger. var log: EngineLogger { logger.scoped(.category("AWSCognitoAuthCredentialStore")) } From deb8ea319f000741ae05166d08a7faaa34808a79 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:36:46 -0400 Subject: [PATCH 05/57] docs(auth): say the plugin's sign-out saves the signed-out record under an access group in the store and migration comments The access-group migration's check, the AWSMobileClient migration's existing-session rule, the plugin's legacy-store clear and the client's default-session notes said the plugin's sign-out deletes the session record, which is now true only without an access group. Describe how the record the plugin saves combines with the AWSMobileClient migration and with a guest the client fetches over it. --- .../SessionRecordStore+DefaultSession.swift | 9 ++++++--- .../CredentialStore/ClearCredentialStore.swift | 4 +++- .../MigrateLegacyCredentialStore.swift | 4 +++- ...ognitoAuthCredentialStore+AccessGroup.swift | 18 +++++++++--------- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift index dcd2492378..a830929aa2 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift @@ -50,9 +50,12 @@ import InternalAWSCognitoAuth // but only until a guest replaces it: the migration reads a guest as no session. So a guest the client fetches over a // signed-out record (`acquireGuestOnce`) clears them again first, best effort; over no record it leaves them, as a // client-only app that never signed in keeps them for a plugin build to migrate. A sidecar alone (a label set before -// anyone signed in) is no record. A guest the plugin, or an app -// extension on it, saves over the signed-out record clears nothing, so after it only an earlier clear protects the -// user. A purge leaves no record. The client never migrates them. +// anyone signed in) is no record. The signed-out record may be the plugin's: under an access group, the plugin's own +// sign-out saves the same bytes (plugin releases after 2.63.0; earlier ones delete the record), so a guest the client +// fetches after one clears them too. Without an access group the plugin's sign-out deletes the record, and the +// client's guest then leaves them, as over no record. A guest the plugin, or an app extension on it, saves over the +// signed-out record clears nothing, so after it only an earlier clear protects the user. A purge leaves no record. +// The client never migrates them. extension SessionRecordStore { /// Whether a store re-reads an absent shared record by default: on macOS only, where the keychain's `set` diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/ClearCredentialStore.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/ClearCredentialStore.swift index e326a0fc54..c719fe1115 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/ClearCredentialStore.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/ClearCredentialStore.swift @@ -64,7 +64,9 @@ package struct ClearCredentialStore: Action { /// /// Best effort: a failure is logged and never fails the clear, because the session itself is /// already gone. If a legacy store survives, the next migration still finds it; that is no worse - /// than before this clear ran. + /// than before this clear ran. Under an access group the session is ended by saving the signed-out + /// record, which stops that migration, so it clears the store without migrating it, until a guest + /// replaces the record (`MigrateLegacyCredentialStore`). private func clearLegacyStores(for credentialEnvironment: CredentialEnvironment, environment: Environment) { let credentialStoreEnvironment = credentialEnvironment.credentialStoreEnvironment let serviceKeys = MigrateLegacyCredentialStore.legacyServiceKeys( diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/MigrateLegacyCredentialStore.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/MigrateLegacyCredentialStore.swift index 2e07a40e10..9f7bf984d2 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/MigrateLegacyCredentialStore.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/MigrateLegacyCredentialStore.swift @@ -552,7 +552,9 @@ extension MigrateLegacyCredentialStore { /// A signed-out record (`{"noCredentials":{}}`) is not absent: a sign-out through the Cognito client's default /// session saves it, and migrating over it would sign back in the user who signed out. It counts as a session /// does, as the access-group migration weighs it (`AWSCognitoAuthCredentialStore.SavedSession.signedOut`). The - /// plugin never saves one (its sign-out deletes the record), so an app on the plugin alone is not affected. + /// plugin saves one only under an access group, where its sign-out already cleared the legacy stores, so this + /// rarely changes what a plugin-only app migrates: only legacy data that survived or reappeared after a sign-out + /// is dropped. private func existingSession( in amplifyCredentialStore: AmplifyAuthCredentialStoreBehavior ) -> ExistingSession { diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore+AccessGroup.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore+AccessGroup.swift index 88e6691e59..ef938bab87 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore+AccessGroup.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore+AccessGroup.swift @@ -174,15 +174,15 @@ extension AWSCognitoAuthCredentialStore { /// record aborted the migration, and left the app's login in the old service. The destination clear /// removes it, and the login moves. A guest's record is an identity pool identity without user pool tokens /// or a federated identity (an identity-pool-only configuration's included). A signed-out record - /// (`{"noCredentials":{}}`, which a sign-out through the Cognito client's default session saves) is not a - /// guest's: it counts, as a login does, so a user who signed out that way is never signed back in by the - /// migration. A plugin sign-out deletes the session record instead, so after one, only a device record stops an - /// extension's first migration from moving its login in: the device metadata a user-pool sign-in saves for a - /// tracked device, or the advanced-security device ID that any user-pool sign-in, hosted-UI sign-in, sign-up or - /// password-reset attempt saves when no device metadata is held. With no login to move, a guest's record still - /// counts, so a launch with nothing to migrate never clears another process's guest identity. Any other item - /// counts as before; a device record without reading it, since only such a user's attempt writes one. A session - /// record that cannot be read or decoded counts as a login. + /// (`{"noCredentials":{}}`, which a sign-out through the Cognito client's default session, or through the plugin, + /// saves) is not a guest's: it counts, as a login does, so a user who signed out that way is never signed back in + /// by the migration. A plugin sign-out saves the same record here (`deleteCredential()`), so it stops the + /// migration too. So does a device record: the device metadata a user-pool sign-in saves for a tracked device, or + /// the advanced-security device ID that any user-pool sign-in, hosted-UI sign-in, sign-up or password-reset + /// attempt saves when no device metadata is held. With no login to move, a guest's record still counts, so a + /// launch with nothing to migrate never clears another process's guest identity. Any other item counts as before; + /// a device record without reading it, since only such a user's attempt writes one. A session record that cannot + /// be read or decoded counts as a login. /// /// - Parameter source: the migration's source store, built only when the shared service holds nothing but /// guests' session records. From 5e2530bc9ae0073822741f5ae2d46266c0ebc58b Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:36:46 -0400 Subject: [PATCH 06/57] test(auth): pin the plugin's signed-out record against the AWSMobileClient migration, the client's guest and a rollback Under an access group, a login left in the AWSMobileClient stores after a plugin sign-out is dropped at the next launch, and a guest the client's default session fetches over the record clears those stores first. A plugin sign-out after a configuration carry survives a rollback to the earlier configuration, and an extension's first transition without migration keeps its own items. Without an access group each case behaves as before. --- .../DefaultSessionGuestLegacyLoginTests.swift | 3 +- .../Tests/UnitTests/StoredSessionsTests.swift | 3 +- .../MigrateLegacyCredentialStoreTests.swift | 3 +- .../AccessGroupSignedOutRecordTests.swift | 183 +++++++++++++++++- .../RollbackMatrixTestSupport+Client.swift | 17 +- 5 files changed, 197 insertions(+), 12 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/DefaultSessionGuestLegacyLoginTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/DefaultSessionGuestLegacyLoginTests.swift index bea95a421a..5d2725f02c 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/DefaultSessionGuestLegacyLoginTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/DefaultSessionGuestLegacyLoginTests.swift @@ -60,7 +60,8 @@ final class DefaultSessionGuestLegacyLoginTests: XCTestCase { } /// - Given: `.default`'s record holding `{"noCredentials":{}}`, as a sign-out stopped between its signed-out write - /// and its clear left it, and AWSMobileClient logins + /// and its clear left it, or a plugin sign-out under an access group whose clear failed, and AWSMobileClient + /// logins /// - When: `.default`'s session is fetched, which fetches a guest /// - Then: /// - the record holds a guest, and the logins are gone diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/StoredSessionsTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/StoredSessionsTests.swift index 3263b8c216..1bbddd8b29 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/StoredSessionsTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/StoredSessionsTests.swift @@ -212,7 +212,8 @@ final class StoredSessionsTests: XCTestCase { ]) } - /// The plugin deletes its record on its own sign-out (and on `deleteUser`); the sidecar stays. + /// Without an access group the plugin deletes its record on its own sign-out (and on `deleteUser`); the sidecar + /// stays. /// /// - Given: no plugin record, and a sidecar naming alice with a label /// - When: saved sessions are listed with `includingSignedOut: true` diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MigrateLegacyCredentialStoreTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MigrateLegacyCredentialStoreTests.swift index 87b3679b68..6143072cdc 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MigrateLegacyCredentialStoreTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MigrateLegacyCredentialStoreTests.swift @@ -474,7 +474,8 @@ extension MigrateLegacyCredentialStoreTests { /// Test that a signed-out record stops the migration, so a user who signed out is not signed back in /// /// - Given: A new credential store holding the signed-out record (`.noCredentials`, which a sign-out through - /// the Cognito client's default session saves; the plugin's own sign-out deletes the record), and in turn + /// the Cognito client's default session saves, and the plugin's own sign-out under an access group; without + /// one the plugin's sign-out deletes the record), and in turn /// a readable legacy login, a readable legacy guest, and a legacy login whose refresh token read fails /// with `errSecInteractionNotAllowed` /// - When: diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift index b10fa6fd22..62dd964c4c 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift @@ -266,6 +266,168 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { XCTAssertEqual(sharedRecord(), signedOutRecord) } + /// Test that an app extension's first access-group transition without migration counts the plugin's signed-out + /// record, as it counts a login, and keeps its own old service's items + /// + /// - Given: the plugin, configured with the access group, signed out, so the shared service holds the signed-out + /// record; and an app extension whose unshared service holds bob's stale login and whose `UserDefaults` hold + /// no access group + /// - When: + /// - the extension's store is configured with the access group and `migrateKeychainItems: false` + /// - Then: + /// - the shared service is not empty, so the extension's unshared service is not cleared: bob's login stays + /// there, and the new store reads no login + /// + func testAnExtensionsFirstTransitionWithoutMigrationKeepsItsOwnItems() async throws { + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + _ = await makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()).signOut() + XCTAssertEqual(sharedRecord(), signedOutRecord) + let extensionKeychain = InMemoryKeychain() + let bob = try JSONEncoder().encode(LongLivedCredentials.userPoolAndIdentityPool(username: "bob")) + try extensionKeychain.store(service: service).set(bob, key: sessionAccount) + let suite = "AccessGroupSignedOutRecordTests.extension.\(UUID().uuidString)" + let extensionDefaults = try XCTUnwrap(UserDefaults(suiteName: suite)) + defer { extensionDefaults.removePersistentDomain(forName: suite) } + let keychain = keychain! + + let store = AWSCognitoAuthCredentialStore( + authConfiguration: authConfiguration, + accessGroup: accessGroup, + migrateKeychainItemsOfUserSession: false, + userDefaults: extensionDefaults, + makeKeychainStore: { service, accessGroup in + accessGroup == nil ? extensionKeychain.store(service: service) : keychain.store(service: service, accessGroup: accessGroup) + }, + logger: AmplifyEngineLogRouter() + ) + + XCTAssertEqual(try store.retrieveCredential(), .noCredentials) + XCTAssertEqual(extensionKeychain.value(service: service, account: sessionAccount), bob) + XCTAssertEqual(sharedRecord(), signedOutRecord) + } + + // MARK: With the AWSMobileClient logins + + /// Test that the next launch's AWSMobileClient migration drops a login left after a plugin sign-out under an access + /// group, and still migrates it without one + /// + /// - Given: alice's login, and the plugin signed out, with the access group and without; then AWSMobileClient + /// stores that still hold a login, as when the sign-out's clear of them failed, or a login was saved there since + /// - When: + /// - the plugin configures over them, as at the next launch, which runs the AWSMobileClient migration + /// - Then: + /// - with the access group, the signed-out record stops the migration: the launch is signed out, the record is + /// unchanged, and the AWSMobileClient stores are cleared + /// - without one, the sign-out deleted the record, so the old login is migrated into the unshared service, as + /// before + /// + func testTheNextLaunchDropsAnAWSMobileClientLoginLeftAfterTheSignOutOnlyUnderAnAccessGroup() async throws { + for accessGroup in [accessGroup, nil] { + let label = accessGroup == nil ? "without an access group" : "with the access group" + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + _ = await makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()).signOut() + let cleared = TestCounter() + let legacy = MockKeychainStoreBehavior(data: "legacy", removeAllHandler: { cleared.increment() }) + + let plugin = makePlugin( + accessGroup: accessGroup, + userPool: MockIdentityProvider(), + legacyStore: { _ in legacy } + ) + _ = try? await plugin.getCurrentUser() + + let saved = keychain.value( + service: accessGroup == nil ? service : sharedService, + accessGroup: accessGroup, + account: sessionAccount + ) + if accessGroup == nil { + let migrated = try JSONDecoder().decode(AmplifyCredentials.self, from: XCTUnwrap(saved, label)) + guard case .userPoolAndIdentityPool = migrated else { + return XCTFail("\(label): the old login was not migrated") + } + } else { + XCTAssertEqual(saved, signedOutRecord, label) + guard case .configured(.signedOut, _, _) = await plugin.authStateMachine.currentState else { + return XCTFail("\(label): the next launch is in \(await plugin.authStateMachine.currentState)") + } + } + XCTAssertGreaterThan(cleared.get(), 0, label) + } + } + + /// Test that a guest the Cognito client's `.default` fetches after a plugin sign-out under an access group clears + /// the AWSMobileClient logins first + /// + /// - Given: the plugin, configured with the access group, signed out, so the shared service holds the signed-out + /// record; and AWSMobileClient logins in the app's keychain, as when the sign-out's clear of them failed + /// - When: + /// - the client's `.default` in the access group fetches its session, which fetches a guest + /// - Then: + /// - the shared service holds the guest, and the AWSMobileClient logins are gone, so a plugin build has none + /// to migrate over the guest + /// + func testAGuestTheClientFetchesAfterThePluginsSignOutClearsTheAWSMobileClientLogins() async throws { + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + _ = await makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()).signOut() + XCTAssertEqual(sharedRecord(), signedOutRecord) + let legacyServices = SessionRecordStore.legacyLoginServices(for: PoolNamespace(authConfiguration)) + XCTAssertFalse(legacyServices.isEmpty) + for legacyService in legacyServices { + try keychain.store(service: legacyService).set(Data("legacy".utf8), key: "tokens.alice") + } + + let app = ClientOverKeychain(keychain: keychain, clearsLegacyLogins: true) + var client: AmplifyCognitoClient? = try app.client(accessGroup: accessGroup) + _ = try await XCTUnwrap(client).fetchAuthSession() + client = nil + await app.waitForBaseline() + + let saved = try JSONDecoder().decode(AmplifyCredentials.self, from: XCTUnwrap(sharedRecord())) + guard case .identityPoolOnly = saved else { + return XCTFail("the shared service does not hold a guest") + } + for legacyService in legacyServices { + XCTAssertNil(keychain.value(service: legacyService, account: "tokens.alice"), legacyService) + } + } + + // MARK: A rollback after a configuration carry + + /// Test that a plugin sign-out after a configuration carry, under an access group, survives a rollback to the + /// build with the earlier configuration, and without one leaves that build the earlier copy + /// + /// - Given: alice saved under a user-pool-only configuration; the plugin's store under the configuration that adds + /// the identity pool, which carries her to its key and keeps the earlier copy; then that store ends the session + /// - When: + /// - the store is built with the earlier configuration, as by a rollback to that build, which runs the + /// plugin's rule back to it + /// - Then: + /// - with the access group, the rule carries the signed-out record over the earlier copy: that build reads + /// `.noCredentials`, as after a sign-out through the client + /// - without one, the sign-out deleted the record, so there is nothing to carry and that build reads alice + /// + func testASignOutAfterACarrySurvivesARollbackOnlyUnderAnAccessGroup() throws { + let earlier = AuthConfiguration(client: ClientOverKeychain.userPoolOnlyConfiguration) + for accessGroup in [accessGroup, nil] { + let label = accessGroup == nil ? "without an access group" : "with the access group" + let earlierStore = makeStore(accessGroup: accessGroup, configuration: earlier) + try earlierStore.saveCredential(.userPoolOnly(signedInData: SignedInData( + signedInDate: Date(), + signInMethod: .apiBased(.userSRP), + cognitoUserPoolTokens: LongLivedCredentials.tokens(username: "alice") + ))) + let alice = try earlierStore.retrieveCredential() + let current = makeStore(accessGroup: accessGroup) + XCTAssertEqual(try current.retrieveCredential(), alice, label) + + try current.deleteCredential() + + let rolledBack = makeStore(accessGroup: accessGroup, configuration: earlier) + XCTAssertEqual(try rolledBack.retrieveCredential(), accessGroup == nil ? alice : .noCredentials, label) + } + } + // MARK: Helpers private func sharedRecord() -> Data? { @@ -321,11 +483,12 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { XCTAssertEqual(extensionKeychain.value(service: service, account: sessionAccount), bob, file: file, line: line) } - /// The plugin's credential store over the shared keychain, as `makeCredentialStore` builds it, without migration. - private func makeStore(accessGroup: String?) -> AWSCognitoAuthCredentialStore { + /// The plugin's credential store over the shared keychain, as `makeCredentialStore` builds it, without migration: + /// the app's configuration's, unless `configuration` is given. + private func makeStore(accessGroup: String?, configuration: AuthConfiguration? = nil) -> AWSCognitoAuthCredentialStore { let keychain = keychain! return AWSCognitoAuthCredentialStore( - authConfiguration: authConfiguration, + authConfiguration: configuration ?? authConfiguration, accessGroup: accessGroup, migrateKeychainItemsOfUserSession: false, userDefaults: userDefaults, @@ -345,8 +508,14 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { } /// A plugin as `configure(using:)` builds it, with its credential store from `makeStore(accessGroup:)` and only - /// the Cognito service calls mocked. - private func makePlugin(accessGroup: String?, userPool: CognitoUserPoolBehavior) -> AWSCognitoAuthPlugin { + /// the Cognito service calls mocked. Its credential store machine starts idle, so its first load skips the + /// AWSMobileClient migration, unless `legacyStore` is given: it then starts as at a launch, and the migration + /// runs over those stores first. + private func makePlugin( + accessGroup: String?, + userPool: CognitoUserPoolBehavior, + legacyStore: (@Sendable (String) -> any KeychainItemStoreBehavior)? = nil + ) -> AWSCognitoAuthPlugin { let identity = MockIdentity( mockGetIdResponse: { _ in .init(identityId: "client-identity-id") }, mockGetCredentialsResponse: { input in @@ -366,7 +535,7 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { makeUserPool: { userPool }, makeIdentityClient: { identity }, credentialStoreFactory: { [self] in makeStore(accessGroup: accessGroup) }, - legacyKeychainStoreFactory: Defaults.makeLegacyStore(service:), + legacyKeychainStoreFactory: legacyStore ?? Defaults.makeLegacyStore(service:), logger: AmplifyEngineLogRouter(scope: .category("awsCognitoAuthPluginTest")), userPoolAnalytics: { MockAnalyticsHandler() }, makeURLSession: { URLSession(configuration: .ephemeral) }, @@ -375,7 +544,7 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { let credentialStoreMachine = CredentialStoreStateMachine( resolver: CredentialStoreState.Resolver(), environment: factory.makeCredentialEnvironment(), - initialState: .idle + initialState: legacyStore == nil ? .idle : .notConfigured ) let authEnvironment = factory.makeAuthEnvironment( credentialsClient: CredentialStoreOperationClient(credentialStoreStateMachine: credentialStoreMachine) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/RollbackMatrixTestSupport+Client.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/RollbackMatrixTestSupport+Client.swift index fb2b6b2c12..1988046c68 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/RollbackMatrixTestSupport+Client.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/RollbackMatrixTestSupport+Client.swift @@ -39,13 +39,18 @@ final class ClientOverKeychain: @unchecked Sendable { let keychain: InMemoryKeychain let registry = SessionCoreDependencies.Registry() let gates = SessionRecordGates() + /// Whether `.default`'s store clears the AWSMobileClient logins in `keychain`'s services for its pools + /// (`SessionRecordStore.legacyLoginServices(for:)`), as the real keychain's store does. Off by default, so a row + /// that doesn't ask for it never reaches them. + let clearsLegacyLogins: Bool private let lock = NSLock() private var userPool = MockIdentityProvider() private var identity = ClientOverKeychain.identityPool() - init(keychain: InMemoryKeychain) { + init(keychain: InMemoryKeychain, clearsLegacyLogins: Bool = false) { self.keychain = keychain + self.clearsLegacyLogins = clearsLegacyLogins } /// The Cognito calls of the clients made from now on. @@ -96,6 +101,7 @@ final class ClientOverKeychain: @unchecked Sendable { var dependencies: SessionCoreDependencies { let keychain = keychain + let clearsLegacyLogins = clearsLegacyLogins return SessionCoreDependencies( registry: registry, gates: gates, @@ -105,7 +111,14 @@ final class ClientOverKeychain: @unchecked Sendable { keychain: keychain.store( service: SessionRecordStore.service(forAccessGroup: namespace.accessGroup), accessGroup: namespace.accessGroup - ) + ), + legacyLoginStores: { + guard clearsLegacyLogins else { + return [] + } + return SessionRecordStore.legacyLoginServices(for: namespace.pools) + .map { keychain.store(service: $0) } + } ) }, makeClients: { try CognitoServiceClients(configuration: $0, configureUserPoolClient: $1) }, From 23847d962120a9166c0121355c1f6d510259bb77 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:03:20 -0400 Subject: [PATCH 07/57] fix(auth): save the signed-out record under an access group only when a login or a signed-out record is there A sign-out with nothing saved, and a guest's sign-out, saved {"noCredentials":{}} in the shared service, so an app extension that signed out that way before the app's first launch with the access group stopped the app's migration and stranded its login in the old service. Under an access group the store now saves the signed-out record only over a login (user pool tokens or a federated identity) or a record that is already signed out. A guest's record, or one it cannot read, is deleted as before, and with nothing saved nothing is written. --- .../AWSCognitoAuthCredentialStore.swift | 30 ++-- .../AccessGroupSignedOutRecordTests.swift | 141 +++++++++++++++++- 2 files changed, 157 insertions(+), 14 deletions(-) diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift index cacf2251a6..179e5f81bf 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift @@ -244,20 +244,32 @@ extension AWSCognitoAuthCredentialStore: AmplifyAuthCredentialStoreBehavior { return amplifyCredential } - /// Ends the saved session. Every plugin path that ends one clears it through here: sign-out, local and global, - /// the local sign-out after a deleted user, and clearing the federation to the identity pool. + /// Ends the saved session. Every plugin path that ends one clears it through here: sign-out, local and global + /// (a guest's and one with nothing saved included), the local sign-out after a deleted user, and clearing the + /// federation to the identity pool. /// - /// Without an access group the session record is deleted. With one, it is replaced by the signed-out record, - /// `{"noCredentials":{}}`, the bytes the Cognito client's default session saves on sign-out, which every plugin - /// release reads as signed out. In the shared service a sign-out then counts as migrated: an app extension's - /// first access-group migration stops at the record (`sharedKeychainHasMigratedItems(accessGroup:source:)`) - /// and never moves its own stale login in over it. The next sign-in replaces the record. + /// Without an access group the session record is deleted. With one, a record that holds a login (user pool + /// tokens or a federated identity), or is already signed out, is replaced by the signed-out record, + /// `{"noCredentials":{}}`, the bytes the Cognito client's default session saves when it signs a session out, + /// which every plugin release reads as signed out. In the shared service ending a login then counts as migrated: + /// an app extension's first access-group migration stops at the record + /// (`sharedKeychainHasMigratedItems(accessGroup:source:)`) and never moves its own stale login in over it. The + /// next sign-in replaces the record. Any other record is deleted, as without an access group: a guest's, so a + /// guest alone still never stops a login's migration, and one this store cannot read. With nothing saved, + /// nothing is written, as the client writes nothing when there is nothing to sign out. package func deleteCredential() throws { - guard accessGroup != nil else { + guard let accessGroup else { try removeSession(for: authConfiguration) return } - try saveCredential(.noCredentials) + // Read as the migration's checks read the shared service: quietly, so the sign-out logs no new line. + let shared = EngineKeychainStore.quiet(makeKeychainStore(sharedService, accessGroup), logger: logger) + switch Self.savedSession(at: generateSessionKey(for: authConfiguration), in: shared) { + case .login, .signedOut: + try saveCredential(.noCredentials) + case .guest, .unknown: + try removeSession(for: authConfiguration) + } } package func saveDevice(_ deviceMetadata: DeviceMetadata, for username: String) throws { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift index 62dd964c4c..21f0579ec6 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift @@ -17,11 +17,13 @@ import XCTest @testable import AWSCognitoAuthPlugin @testable import InternalAWSCognitoAuth -/// Under an access group, every plugin path that ends the session saves the signed-out record, -/// `{"noCredentials":{}}`, in the shared service instead of deleting the session record: sign-out, local and -/// global, clearing the federation to the identity pool, and the local sign-out after a deleted user. The record -/// then stops an app extension's first access-group migration, as a login does, so the extension's stale login is -/// never moved in over the user who signed out. Without an access group the record is still deleted. +/// Under an access group, every plugin path that ends a login saves the signed-out record, `{"noCredentials":{}}`, in +/// the shared service instead of deleting the session record: sign-out, local and global, clearing the federation to +/// the identity pool, and the local sign-out after a deleted user. So does one over a record that is already signed +/// out. The record then stops an app extension's first access-group migration, as a login does, so the extension's +/// stale login is never moved in over the user who signed out. A guest's sign-out still deletes the guest's record, +/// and a sign-out with nothing saved writes nothing, so neither stops a login's migration. Without an access group +/// the record is still deleted. /// /// The plugin runs its real credential store, through its test seam, over an in-memory keychain shared with the /// extension and with the Cognito client's `.default` session. Only the Cognito service calls are mocked. @@ -87,6 +89,45 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { XCTAssertNil(keychain.value(service: service, account: sessionAccount)) } + /// Test that under an access group the store saves the signed-out record only over a login or a signed-out record + /// + /// - Given: the shared service holding, in turn, a user-pool login, a federated identity, the signed-out record, a + /// guest's identity, a record the store cannot decode, and nothing + /// - When: + /// - the store with the access group ends its session + /// - Then: + /// - over a login, a federated identity or the signed-out record, the shared service holds + /// `{"noCredentials":{}}` + /// - over a guest's identity or an unreadable record, the record is deleted, as before + /// - with nothing saved, nothing is written + /// + func testTheStoreSavesTheSignedOutRecordOnlyOverALoginOrASignedOutRecord() throws { + let federated = AmplifyCredentials.identityPoolWithFederation( + federatedToken: FederatedToken(token: "federated-token", provider: .facebook), + identityID: "federated-identity", + credentials: LongLivedCredentials.awsCredentials() + ) + let saved: [(label: String, record: Data?, signsOut: Bool)] = try [ + ("a login", JSONEncoder().encode(LongLivedCredentials.userPoolAndIdentityPool()), true), + ("a federated identity", JSONEncoder().encode(federated), true), + ("the signed-out record", signedOutRecord, true), + ("a guest", JSONEncoder().encode(Self.guest), false), + ("an unreadable record", Data("not a session".utf8), false), + ("nothing", nil, false) + ] + for (label, record, signsOut) in saved { + let shared = keychain.store(service: sharedService, accessGroup: accessGroup) + try shared.remove(sessionAccount) + if let record { + try shared.set(record, key: sessionAccount) + } + + try makeStore(accessGroup: accessGroup).deleteCredential() + + XCTAssertEqual(sharedRecord(), signsOut ? signedOutRecord : nil, label) + } + } + // MARK: Each path that ends the session /// Test that a plugin sign-out under an access group saves the signed-out record, and the next launch is signed out @@ -188,6 +229,57 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { XCTAssertNil(sharedRecord()) } + /// Test that a sign-out with nothing saved writes nothing under an access group, so the app's first migration + /// still moves its login + /// + /// - Given: nothing saved in the shared service, and an app extension's plugin configured with the access group, + /// signed out + /// - When: + /// - the extension signs out, before the app's first launch with the access group + /// - then the app's store is configured with the access group and `migrateKeychainItems: true`, with alice's + /// login in its unshared service + /// - Then: + /// - the sign-out completes and the shared service holds no session record + /// - the app's migration moves alice's login into the shared service + /// + func testASignOutWithNothingSavedWritesNothingAndTheAppsLoginStillMigrates() async throws { + let plugin = makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()) + + let result = await plugin.signOut() + + guard let signOut = result as? AWSCognitoSignOutResult, case .complete = signOut else { + return XCTFail("the sign-out did not complete: \(result)") + } + XCTAssertNil(sharedRecord()) + try assertTheAppsFirstMigrationMovesItsLogin() + } + + /// Test that a guest's sign-out deletes the guest's record under an access group, so the app's first migration + /// still moves its login + /// + /// - Given: a guest's identity in the shared service, and an app extension's plugin configured with the access + /// group + /// - When: + /// - the extension signs the guest out, before the app's first launch with the access group + /// - then the app's store is configured with the access group and `migrateKeychainItems: true`, with alice's + /// login in its unshared service + /// - Then: + /// - the sign-out completes and the shared service holds no session record + /// - the app's migration moves alice's login into the shared service + /// + func testAGuestsSignOutDeletesTheGuestRecordAndTheAppsLoginStillMigrates() async throws { + try makeStore(accessGroup: accessGroup).saveCredential(Self.guest) + let plugin = makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()) + + let result = await plugin.signOut() + + guard let signOut = result as? AWSCognitoSignOutResult, case .complete = signOut else { + return XCTFail("the guest's sign-out did not complete: \(result)") + } + XCTAssertNil(sharedRecord()) + try assertTheAppsFirstMigrationMovesItsLogin() + } + // MARK: After the sign-out /// Test that a sign-in after a sign-out replaces the signed-out record @@ -483,6 +575,45 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { XCTAssertEqual(extensionKeychain.value(service: service, account: sessionAccount), bob, file: file, line: line) } + /// A guest's identity: an identity pool identity with no user pool tokens and no federated identity. + private static let guest = AmplifyCredentials.identityPoolOnly( + identityID: "guest-identity-id", + credentials: LongLivedCredentials.awsCredentials() + ) + + /// The app's first configuration with the access group and `migrateKeychainItems: true`, its unshared service + /// holding alice's login and its `UserDefaults` no access group, moves alice's login into the shared service. The + /// app's unshared service is in the same keychain as the shared service, so the migration can move items between + /// them. + private func assertTheAppsFirstMigrationMovesItsLogin(file: StaticString = #filePath, line: UInt = #line) throws { + let keychain = keychain! + let alice = try JSONEncoder().encode(LongLivedCredentials.userPoolAndIdentityPool(username: "alice")) + try keychain.store(service: service).set(alice, key: sessionAccount) + let suite = "AccessGroupSignedOutRecordTests.app.\(UUID().uuidString)" + let appDefaults = try XCTUnwrap(UserDefaults(suiteName: suite)) + defer { appDefaults.removePersistentDomain(forName: suite) } + + let store = AWSCognitoAuthCredentialStore( + authConfiguration: authConfiguration, + accessGroup: accessGroup, + migrateKeychainItemsOfUserSession: true, + userDefaults: appDefaults, + makeKeychainStore: { service, accessGroup in + keychain.store(service: service, accessGroup: accessGroup) + }, + logger: AmplifyEngineLogRouter() + ) + + XCTAssertEqual(sharedRecord(), alice, "alice's login was not migrated", file: file, line: line) + XCTAssertEqual( + try store.retrieveCredential(), + try JSONDecoder().decode(AmplifyCredentials.self, from: alice), + file: file, + line: line + ) + XCTAssertNil(keychain.value(service: service, account: sessionAccount), file: file, line: line) + } + /// The plugin's credential store over the shared keychain, as `makeCredentialStore` builds it, without migration: /// the app's configuration's, unless `configuration` is given. private func makeStore(accessGroup: String?, configuration: AuthConfiguration? = nil) -> AWSCognitoAuthCredentialStore { From dcfbc565edee9257e748610bbbc5f25f0193ccf8 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:03:20 -0400 Subject: [PATCH 08/57] test(auth): pin the redacted challenge in the error of an MFA setup the plugin cannot continue --- .../ChallengeSessionRedactionTests.swift | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/ChallengeSessionRedactionTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/ChallengeSessionRedactionTests.swift index 19da025266..adeed2ae9a 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/ChallengeSessionRedactionTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/ChallengeSessionRedactionTests.swift @@ -199,4 +199,66 @@ final class ChallengeSessionRedactionTests: XCTestCase { let line = try XCTUnwrap(messages.first { $0.contains("VerifyPasswordSRP.swift Sending event") }, "\(messages)") XCTAssertTrue(line.contains(#"session: Optional("")"#), line) } + + /// Test that the error of an MFA setup the plugin cannot continue carries the challenge without its session + /// + /// - Given: an MFA setup challenge with a session and a username that offers only SMS, which the plugin does not + /// set up + /// - When: + /// - the sign-in resolves the challenge's next step + /// - Then: + /// - it fails with `SignInError.unknown`, whose message, and the `AuthError.unknown` the plugin throws with it, + /// holds the challenge as it prints: the session ``, the usernames masked + /// + func testAnUnresolvableMFASetupErrorHoldsTheChallengeWithoutItsSession() async throws { + let challenge = RespondToAuthChallenge( + challenge: .mfaSetup, + availableChallenges: [], + username: Self.username, + session: Self.session, + parameters: ["MFAS_CAN_SETUP": #"["SMS_MFA"]"#], + inputUsername: Self.inputUsername + ) + let action = InitializeResolveChallenge(challenge: challenge, signInMethod: .apiBased(.userSRP)) + let failed = expectation(description: "the sign-in fails") + let messages = RecordedMessages() + let dispatcher = MockDispatcher { event in + guard case .throwAuthError(let error) = (event as? SignInEvent)?.eventType else { + return + } + guard case .unknown(let message) = error else { + return XCTFail("the sign-in failed with \(error), not unknown") + } + messages.append(message) + messages.append(error.authError.errorDescription) + failed.fulfill() + } + + await action.execute(withDispatcher: dispatcher, environment: Defaults.makeDefaultAuthEnvironment()) + + await fulfillment(of: [failed], timeout: 1) + let recorded = messages.all + XCTAssertEqual(recorded.count, 2) + for message in recorded { + XCTAssertTrue(message.contains(challenge.description), message) + XCTAssertTrue(message.contains(#"session: Optional("")"#), message) + for clear in [Self.session, Self.username, Self.inputUsername] { + XCTAssertFalse(message.contains(clear), "\(clear) in \(message)") + } + } + } +} + +/// The messages a dispatcher's events carried, from its `@Sendable` callback. +private final class RecordedMessages: @unchecked Sendable { + private let lock = NSLock() + private var messages: [String] = [] + + var all: [String] { + lock.withLock { messages } + } + + func append(_ message: String) { + lock.withLock { messages.append(message) } + } } From 5a358da7171a20223c19b72ea9b2543937b1a72b Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:05:34 -0400 Subject: [PATCH 09/57] docs(auth): release notes, design and decisions for the confirmSignIn, challenge-session and access-group sign-out fixes Adds the pending release-note rows since 2.63.0 and records the access-group sign-out as a decision: under an access group the plugin's sign-out of a login, or over a record already signed out, saves the signed-out record; a guest's sign-out and one with nothing saved write none. Corrects the README, the design, the decisions, the rollback notes and the integration-test notes where they said the plugin's sign-out deletes the record, so an app on the plugin alone is not affected: that holds now only without an access group, and for plugin releases up to 2.63.0. --- AmplifyClients/AmplifyCognitoClient/README.md | 43 +++++++++--------- .../SessionRecordStore+DefaultSession.swift | 14 +++--- .../Tests/IntegrationTests/README.md | 4 +- .../ClearCredentialStore.swift | 2 +- ...gnitoAuthCredentialStore+AccessGroup.swift | 25 +++++------ .../AGREED-DESIGN-AmplifyCognitoClient.md | 17 +++---- docs/design/RELEASE-NOTES-PENDING.md | 6 ++- docs/design/auth-client-DECISIONS.md | 44 ++++++++++++++----- docs/design/issues/rollback-behaviours.md | 22 +++++----- 9 files changed, 104 insertions(+), 73 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/README.md b/AmplifyClients/AmplifyCognitoClient/README.md index 55337f3380..4bd84b0354 100644 --- a/AmplifyClients/AmplifyCognitoClient/README.md +++ b/AmplifyClients/AmplifyCognitoClient/README.md @@ -297,10 +297,12 @@ if let picked = rows.first { - Signed-out rows are listed only with `includingSignedOut: true`. - A session whose first sign-in stopped on a challenge is not listed until that sign-in completes. - `.default`'s row comes from the Auth plugin's saved login, with its label while the label belongs to the user the - plugin's record holds. After the plugin signs out (or deletes its record), `.default` is a signed-out row naming - the last user *the client* saved it for: the client keeps that name beside the plugin's record and updates it only - when it writes the session itself. So if the plugin signs bob in over alice's session and then signs out, without - the client running in between, the row still names alice, with alice's label. + plugin's record holds. After the plugin signs out (or deletes its record), `.default` is a signed-out row naming the + last user *the client* saved it for: the client keeps that name beside the plugin's record and updates it only when it + writes the session itself. So if the plugin signs bob in over alice's session and then signs out, without the client + running in between, the row still names alice, with alice's label. With an access group, the plugin's sign-out of a + login saves the signed-out record (plugin releases after 2.63.0; earlier ones delete it), so with no such name beside + it `.default` is still listed with `includingSignedOut: true`, as a signed-out row without a label or username. - A row's kind follows the current configuration: after a change that removes the identity pool, `.default`'s copied login is listed as `.userPoolOnly`, and a copied guest or federated identity as signed out, as a named session is, although its saved bytes are the plugin's as they were. @@ -1553,24 +1555,25 @@ let rows = try await AmplifyCognitoClient.storedSessions( ) ``` -One rollback caveat: a purge, or the plugin's own sign-out, after a configuration change that copied the login, then -a rollback to the build with the earlier configuration: that build finds the earlier copy and signs the user back in. -A sign-out through the client doesn't do this: it keeps the user signed out under the same configuration, and across -rollbacks, and clears the AWSMobileClient logins any plugin release would migrate over it (best effort; see -[Sign-out](#sign-out)). The exception, as with the plugin, is a later configuration change onto an earlier -configuration's copy. An AWSMobileClient login that failed to clear (a warning says so) can still undo it on a -plugin release up to 2.63.0, which migrates it over the signed-out login. Later plugin releases don't: the signed-out -login stops their migration, which then clears the old logins, until a guest replaces the signed-out login. See -[Rolling back and forward](#rolling-back-and-forward). +One rollback caveat: a purge, or the plugin's own sign-out without an access group, after a configuration change that +copied the login, then a rollback to the build with the earlier configuration: that build finds the earlier copy and +signs the user back in. A sign-out through the client doesn't do this, nor does a plugin sign-out under an access group +on plugin releases after 2.63.0, which saves the same signed-out login. A sign-out through the client keeps the user +signed out under the same configuration, and across rollbacks, and clears the AWSMobileClient logins any plugin release +would migrate over it (best effort; see [Sign-out](#sign-out)). The exception, as with the plugin, is a later +configuration change onto an earlier configuration's copy. An AWSMobileClient login that failed to clear (a warning says +so) can still undo it on a plugin release up to 2.63.0, which migrates it over the signed-out login. Later plugin +releases don't: the signed-out login stops their migration, which then clears the old logins, until a guest replaces the +signed-out login. See [Rolling back and forward](#rolling-back-and-forward). ### Labels and the signed-out row after a plugin action -For example: alice signs in through the client, and the app labels the session "Personal". Later the plugin -deletes alice (`deleteUser`). The plugin deletes its own record, but it doesn't know the client's note beside it, -so a picker that lists signed-out rows (`storedSessions(…includingSignedOut: true)`) still shows a row naming alice, -labelled "Personal". The row lasts until the client writes the session again, for example a sign-in through the -client, a client refresh of another user the plugin signed in, a guest fetch or a purge. A sign-in through the -plugin doesn't replace it. +For example: alice signs in through the client, and the app labels the session "Personal". Later the plugin deletes +alice (`deleteUser`). The plugin deletes its own record (with an access group, plugin releases after 2.63.0 save the +signed-out record in its place), but it doesn't know the client's note beside it, so a picker that lists signed-out rows +(`storedSessions(…includingSignedOut: true)`) still shows a row naming alice, labelled "Personal". The row lasts until +the client writes the session again, for example a sign-in through the client, a client refresh of another user the +plugin signed in, a guest fetch or a purge. A sign-in through the plugin doesn't replace it. The rule behind it: what the plugin's format has no room for, the label and the user a signed-out row names, is kept beside the plugin's record and bound to the last user the client wrote the session for. The plugin never @@ -1605,7 +1608,7 @@ never log it. The warning is temporary: it goes when the plugin uses the client |---|---| | Back to a plugin-only release | The plugin reads `.default`'s latest login, signed in or signed out. Named sessions are not seen, and come back on roll-forward, unless a plugin release before 2.63.0 wiped its keychain service on an access-group transition, or a release before 2.53.3 at its first configuration (the next row) | | Back to a plugin release before 2.53.3, on an install where no such release has run (a fresh install of the client build, say) | **That release wipes the plugin's keychain service the first time it configures** (2.51.3 to 2.53.2 only without an access group): `.default`'s login, its label and interrupted sign-in, the named sessions and the device records. The plugin starts signed out, and nothing comes back on roll-forward. Plugin-only apps rolled back across 2.53.3 have the same hazard. Roll back to 2.53.3 or later | -| A purge, or the plugin's own sign-out, after a configuration change that copied the login, then a rollback to the build with the earlier configuration | **That build finds the earlier copy and signs the user back in.** The copy keeps its source, as the plugin's does, and a purge or the plugin's sign-out deletes only the current configuration's login. A sign-out through the client doesn't do this: it saves a signed-out login, which that build reads as signed out | +| A purge, or the plugin's own sign-out without an access group, after a configuration change that copied the login, then a rollback to the build with the earlier configuration | **That build finds the earlier copy and signs the user back in.** The copy keeps its source, as the plugin's does, and a purge or the plugin's sign-out deletes only the current configuration's login. A sign-out through the client doesn't do this: it saves a signed-out login, which that build reads as signed out. Nor does the plugin's sign-out under an access group on releases after 2.63.0, which saves the same signed-out login; on releases up to 2.63.0 it deletes the login, as without one | | A sign-out through the client, then a later configuration change that the rule doesn't copy across, onto a configuration whose key still holds an earlier copy of the user | **That copy is restored signed in**, as with the plugin: the change deletes the signed-out login and lands on the old copy. For example: alice signs in under A; B adds an identity pool and copies her, keeping A's copy; she signs out under B; D (the same user pool with a new app client, and A's identity pool) deletes B's signed-out login, and its key is A's, which still holds her | | Back to a plugin release before 2.51.0, with refresh-token rotation on | That plugin can't refresh on the app client at all, whatever the client did | | Forward from the plugin to the client, with refresh-token rotation on, after the plugin rotated the token | **The user stays signed in**: `.default` reads the plugin's record in place, so the client refreshes with the newest token the plugin saved | diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift index a830929aa2..d7ffa1f313 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift @@ -46,16 +46,16 @@ import InternalAWSCognitoAuth // **Sign-out and purge clear the AWSMobileClient logins**, as the plugin's sign-out does (`clearLegacyLogins()`): the // plugin migrates them into this record at configure whenever it holds no session, or only a guest, so they must not // outlive a sign-out. They are cleared before the signed-out write or the purge's delete, so a failure there leaves -// none. A signed-out record (`{"noCredentials":{}}`) stops that migration too, and the migration then clears them, -// but only until a guest replaces it: the migration reads a guest as no session. So a guest the client fetches over a +// none. A signed-out record (`{"noCredentials":{}}`) stops that migration too, and the migration then clears them, but +// only until a guest replaces it: the migration reads a guest as no session. So a guest the client fetches over a // signed-out record (`acquireGuestOnce`) clears them again first, best effort; over no record it leaves them, as a // client-only app that never signed in keeps them for a plugin build to migrate. A sidecar alone (a label set before // anyone signed in) is no record. The signed-out record may be the plugin's: under an access group, the plugin's own -// sign-out saves the same bytes (plugin releases after 2.63.0; earlier ones delete the record), so a guest the client -// fetches after one clears them too. Without an access group the plugin's sign-out deletes the record, and the -// client's guest then leaves them, as over no record. A guest the plugin, or an app extension on it, saves over the -// signed-out record clears nothing, so after it only an earlier clear protects the user. A purge leaves no record. -// The client never migrates them. +// sign-out of a login saves the same bytes (plugin releases after 2.63.0; earlier ones delete the record), so a guest +// the client fetches after one clears them too. Without an access group the plugin's sign-out deletes the record, and +// the client's guest then leaves them, as over no record. A guest the plugin, or an app extension on it, saves over the +// signed-out record clears nothing, so after it only an earlier clear protects the user. A purge leaves no record. The +// client never migrates them. extension SessionRecordStore { /// Whether a store re-reads an absent shared record by default: on macOS only, where the keychain's `set` diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/README.md b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/README.md index 2d53a0059c..638ad56a38 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/README.md +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/README.md @@ -599,9 +599,9 @@ Simulator builds are signed ad hoc with these entitlements, so no team or provis | `DeviceAliasTests` (`DeviceTestCase`) | DV-10 … DV-19, the plugin's `DeviceAliasTokenRefreshIntegrationTests` (#4207), on `email-alias` (email as the username, device tracking "always remember", 5-minute tokens): the user signs in by email, so the typed name differs from the tokens' username, and every refresh (one, two, after a re-sign-in, after the device persisted), remember, forget (also after a refresh), fetch with details, the same device across a sign-out and sign-in, and the whole lifecycle must find the device record under the typed name. This session's device is identified by its access token's `device_key` | | `FederationTests` | FE-1, the plugin's `FederatedSessionTests.testUnsuccessfulFederation`, against the default backend's identity pool (R-IP), which has no external provider, so only the rejection is reachable: a made-up Facebook token is `notAuthorized`, and the signed-out session stays signed out with no record; plus (not counted) a failed federation from a guest keeps the same guest identity, as the plugin keeps the previous credentials | | `ChallengeResumeTests` (`ClientMFATestCase`) | Design §4.11, its own CR-1 … CR-3 (not the credentials rows): a sign-in interrupted on an MFA challenge survives the app being closed (the client released, the registry holding no live session for it, a new client with the same session ID over the same keychain). CR-1: a TOTP challenge (a fresh `default` user with TOTP enrolled and enabled) is answered in a new client, the challenge record stored while it waits and gone after; CR-2: an emailed code on `mfa-req-email`; CR-3: the new client signs in again instead, superseding the saved challenge | -| `PluginRecordLocationTests` (interop target) | The plugin, in the same app, writes its record under its own key, `amplify..session`, which is also the client's `.default` session record, writes no v1 record, and deletes its record on sign-out (no signed-out marker). Removes `.default`'s three items (the plugin's record, `$default.meta` and `$default.challenge`) before and after, and the user's device records and the plugin's `authConfiguration` at teardown. The record is checked straight after the sign-out: the signed-out fetch then saves the plugin's guest record (`identityPoolOnly`) under the same key when it returns guest credentials, and none when it does not, which the test pins from the fetch's result (the plugin's CI's Gen1 translation states no guest flag) | +| `PluginRecordLocationTests` (interop target) | The plugin, in the same app, writes its record under its own key, `amplify..session`, which is also the client's `.default` session record, writes no v1 record, and deletes its record on sign-out (no signed-out marker; no access group). Removes `.default`'s three items (the plugin's record, `$default.meta` and `$default.challenge`) before and after, and the user's device records and the plugin's `authConfiguration` at teardown. The record is checked straight after the sign-out: the signed-out fetch then saves the plugin's guest record (`identityPoolOnly`) under the same key when it returns guest credentials, and none when it does not, which the test pins from the fetch's result (the plugin's CI's Gen1 translation states no guest flag) | | `CredentialStoreTransitionRealKeychainTests` (interop target) | Parity IO-1 … IO-3: the plugin's access-group transition clear and migration (Q7), with their names; a named row the client wrote is still listed afterwards. `.default`'s `$default.meta` and `$default.challenge` belong to the plugin's session: the clear removes them, the migration moves them, a label the client set on `.default` included, which the client then lists under the shared group. IO-5: the migration's clear of a non-empty destination removes them there and keeps named records, which do not block the migration. IO-6: the plugin, started under another user pool, applies the configuration change to the `authConfiguration` the client recorded, and deletes that configuration's login with `.default`'s sidecar and challenge item, keeping named records. Offline: no request. Wipes the plugin's (and client's) real services | -| `PluginSharedLoginTests` (interop target, formerly `PluginAdoptionTests`) | AD-1 … AD-8 (AD-9 and AD-10 are in `PluginCarriedAndSharedGroupLoginTests`), the shared saved login: `.default` restores in place, with no request, the user the plugin signed in, and reads the plugin's later writes (AD-1); a named session never reads the plugin's record (AD-2); the plugin's `fetchAuthSession`, configured again as at a relaunch, sees the user the client signed in, with the client's tokens (AD-3); a plugin sign-out deletes the record, and the next `.default` is signed out (AD-4); a client sign-out (`.complete`) leaves `{"noCredentials":{}}` and the sidecar, a signed-out `.default` row naming the user, and the relaunched plugin signed out (AD-5); a client refresh is what the relaunched plugin holds (AD-6); the plugin signing out the client's login leaves `.default` signed out with a signed-out row naming the user, from the sidecar (AD-7); the client signing out the plugin's login leaves the relaunched plugin signed out (AD-8). The two never run side by side over `.default`. Signs `alice`, a fresh user of the test's own (signed up and deleted through the client), in through the plugin or the client; removes her device records and the plugin's `authConfiguration` at teardown | +| `PluginSharedLoginTests` (interop target, formerly `PluginAdoptionTests`) | AD-1 … AD-8 (AD-9 and AD-10 are in `PluginCarriedAndSharedGroupLoginTests`), the shared saved login: `.default` restores in place, with no request, the user the plugin signed in, and reads the plugin's later writes (AD-1); a named session never reads the plugin's record (AD-2); the plugin's `fetchAuthSession`, configured again as at a relaunch, sees the user the client signed in, with the client's tokens (AD-3); a plugin sign-out deletes the record (no access group), and the next `.default` is signed out (AD-4); a client sign-out (`.complete`) leaves `{"noCredentials":{}}` and the sidecar, a signed-out `.default` row naming the user, and the relaunched plugin signed out (AD-5); a client refresh is what the relaunched plugin holds (AD-6); the plugin signing out the client's login leaves `.default` signed out with a signed-out row naming the user, from the sidecar (AD-7); the client signing out the plugin's login leaves the relaunched plugin signed out (AD-8). The two never run side by side over `.default`. Signs `alice`, a fresh user of the test's own (signed up and deleted through the client), in through the plugin or the client; removes her device records and the plugin's `authConfiguration` at teardown | | `PluginCarriedAndSharedGroupLoginTests` (interop target) | AD-9: the client signs a fresh user in on `.default` over the default backend's user pool alone; the plugin, configured with the full outputs (the same user pool plus its identity pool), carries that login to the new namespace as its bytes were, keeps the old one, and is signed in from it with the client's access token; a `.default` client then restores it with no request and labels it (writing its sidecar), and a plugin under another user pool deletes the carried login, its sidecar and a `$default.challenge` item, keeping the user-pool-only namespace's record and sidecar. AD-10: AD-3 and AD-5 with the plugin on the host app's shared access group S and a `.default` client with `accessGroup: S`: the client's sign-in writes the plugin's record and sidecar into (the shared service, S), a relaunched plugin on S holds the client's tokens, and a group-less `.default` reads `.signedOut` with no request; the client's sign-out leaves `{"noCredentials":{}}` and the sidecar on S, and a relaunched plugin on S is signed out. Wipes the plugin's (and client's) real services and its access-group record before and after | | `PluginRotationTests` (interop target) | RT-1, RT-2: refresh-token rotation across the plugin and `.default` on live Cognito, the shared login's rollback claim. The client rotates and a relaunched plugin refreshes with the rotated token (no `RefreshTokenReuseException`); the plugin rotates and `.default` restores and refreshes with it (no `sessionExpired`), and a plugin relaunched after that reads the client's newest token. Needs the default pool's `rotation` app client (rotation on, no grace period, `infra/pools/default.json`) and `AmplifyCognitoClientRotationIntegrationTests-amplify_outputs.json`, which `infra/plugin-configs.py` writes once `provision.sh` has made the client; the build phase copies it when present. On CI the overlaid `extended` role brings it (`infra/ci`). Without it, it **skips** on any backend that is not the sandbox (the outputs' sandbox marker), such as the plugin's own files, and **fails** on the sandbox, asking for `provision.sh`. Wipes the plugin's unshared service before and after | | `AccessGroupRemovalRealKeychainTests` (interop target) | IO-4: after the plugin's access group is removed, the moved session keeps its group and a group-less read (plugin, client listing, and a group-less `.default` restoring it as `.signedIn`) still finds it. Signs a fresh user of the test's own in through the plugin | diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/ClearCredentialStore.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/ClearCredentialStore.swift index c719fe1115..f388cfae5c 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/ClearCredentialStore.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/ClearCredentialStore.swift @@ -64,7 +64,7 @@ package struct ClearCredentialStore: Action { /// /// Best effort: a failure is logged and never fails the clear, because the session itself is /// already gone. If a legacy store survives, the next migration still finds it; that is no worse - /// than before this clear ran. Under an access group the session is ended by saving the signed-out + /// than before this clear ran. Under an access group a login is ended by saving the signed-out /// record, which stops that migration, so it clears the store without migrating it, until a guest /// replaces the record (`MigrateLegacyCredentialStore`). private func clearLegacyStores(for credentialEnvironment: CredentialEnvironment, environment: Environment) { diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore+AccessGroup.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore+AccessGroup.swift index ef938bab87..3e0d1f2b4a 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore+AccessGroup.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore+AccessGroup.swift @@ -168,21 +168,20 @@ extension AWSCognitoAuthCredentialStore { /// login another process has already migrated or saved there, so the migration must not clear it. /// /// A guest's session record does not count when `source` holds a login this store goes on to use - /// (`holdsALoginToMigrate(_:)`). An app extension, or the - /// Cognito client's default session, that fetches a session before the app's first launch after the - /// update finds the shared service empty, gets a guest identity and saves it there. Counting that - /// record aborted the migration, and left the app's login in the old service. The destination clear - /// removes it, and the login moves. A guest's record is an identity pool identity without user pool tokens - /// or a federated identity (an identity-pool-only configuration's included). A signed-out record + /// (`holdsALoginToMigrate(_:)`). An app extension, or the Cognito client's default session, that fetches a session + /// before the app's first launch after the update finds the shared service empty, gets a guest identity and saves + /// it there. Counting that record aborted the migration, and left the app's login in the old service. The + /// destination clear removes it, and the login moves. A guest's record is an identity pool identity without user + /// pool tokens or a federated identity (an identity-pool-only configuration's included). A signed-out record /// (`{"noCredentials":{}}`, which a sign-out through the Cognito client's default session, or through the plugin, /// saves) is not a guest's: it counts, as a login does, so a user who signed out that way is never signed back in - /// by the migration. A plugin sign-out saves the same record here (`deleteCredential()`), so it stops the - /// migration too. So does a device record: the device metadata a user-pool sign-in saves for a tracked device, or - /// the advanced-security device ID that any user-pool sign-in, hosted-UI sign-in, sign-up or password-reset - /// attempt saves when no device metadata is held. With no login to move, a guest's record still counts, so a - /// launch with nothing to migrate never clears another process's guest identity. Any other item counts as before; - /// a device record without reading it, since only such a user's attempt writes one. A session record that cannot - /// be read or decoded counts as a login. + /// by the migration. A plugin sign-out of a login saves the same record here (`deleteCredential()`), so it stops + /// the migration too (a guest's sign-out deletes the guest's record instead). So does a device record: the device + /// metadata a user-pool sign-in saves for a tracked device, or the advanced-security device ID that any user-pool + /// sign-in, hosted-UI sign-in, sign-up or password-reset attempt saves when no device metadata is held. With no + /// login to move, a guest's record still counts, so a launch with nothing to migrate never clears another process's + /// guest identity. Any other item counts as before; a device record without reading it, since only such a user's + /// attempt writes one. A session record that cannot be read or decoded counts as a login. /// /// - Parameter source: the migration's source store, built only when the shared service holds nothing but /// guests' session records. diff --git a/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md b/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md index b9c60fb7c5..219a1bf3ef 100644 --- a/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md +++ b/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md @@ -23,8 +23,9 @@ NOTE: Swift is used throughout the design doc showcasing API changes and a per p - **The plugin's own deleting configuration change** now also removes `.default`'s sidecar and challenge record under the old configuration, as the client does on the same change (decided 2026-10-05). → [§6](#6-storage-model) - **A keychain write that loses a race to another writer** updates the item once instead of failing. → [§6](#6-storage-model) -- **The remaining rollback caveats** (a purge or the plugin's own sign-out after a carry, and a later change onto - an earlier configuration's copy) are accepted for the beta, because the plugin behaves the same way. → [§4.8](#48-migrating-an-existing-app) +- **The remaining rollback caveats** (a purge, or the plugin's own sign-out without an access group, after a carry, + and a later change onto an earlier configuration's copy) are accepted for the beta, because the plugin behaves the + same way. → [§4.8](#48-migrating-an-existing-app) - **Limits** add side by side in one process, Gen2 configuration only, and the beta's scope. → [§11](#11-limits) - **API text checked against the code**: `.federated(identityId:)` in the samples and §14.2, `AuthClientSignInStep` where the text said `AuthSignInStep`, `identityIdResult`, `signInWithWebUI`'s signature, @@ -38,7 +39,7 @@ NOTE: Swift is used throughout the design doc showcasing API changes and a per p `AmplifyCredentials`, plus a sidecar for the label and last user (`amplify.1..$default.meta`). No `$default` session record. → [§4.8](#48-migrating-an-existing-app) · [§6](#6-storage-model) - **Read-through, `completeAdoption()` and the "reader ships first" rule are removed**, with the plugin's forward-compatible reader and signed-out marker (all unreleased). → [§4.8](#48-migrating-an-existing-app) · [§14.3](#143-managing-saved-sessions) -- **Migration and rollback** are one section with a rollback matrix, each row pinned by a test; no separate document. A user signed out through the client stays signed out under the same configuration, and across rollbacks. The exception is a later configuration change onto an earlier configuration's copy: a change the rule does not carry deletes the signed-out record, and can land on a key that still holds the user's old copy, which then restores signed in, as with the plugin. A purge, or the plugin's own sign-out, after a configuration carry is a further caveat. → [§4.8](#48-migrating-an-existing-app) +- **Migration and rollback** are one section with a rollback matrix, each row pinned by a test; no separate document. A user signed out through the client stays signed out under the same configuration, and across rollbacks. The exception is a later configuration change onto an earlier configuration's copy: a change the rule does not carry deletes the signed-out record, and can land on a key that still holds the user's old copy, which then restores signed in, as with the plugin. A purge, or the plugin's own sign-out without an access group, after a configuration carry is a further caveat. → [§4.8](#48-migrating-an-existing-app) - **Side by side**: the plugin and the client share one saved login at rest. Running both in one process over the default session is not supported: the bridge, in which the plugin would use the client internally, is not built. → [§3](#3-the-auth-client-contract) · [§4.9](#49-plugin-and-auth-client-side-by-side) - **A configuration change on `.default` follows the plugin's rule**; named sessions keep "keep". → [§6](#6-storage-model) @@ -542,8 +543,8 @@ let migrated = try AmplifyCognitoClient(configuration: config) // sessionId: . - **What the plugin's record cannot hold goes in a sidecar** (§6): the display label, and the last signed-in user's username and user ID for the signed-out picker row. - **Sign-out of `.default`** revokes, writes the plugin's signed-out value, `{"noCredentials":{}}`, through the commit guard, then, once that has committed, writes the sidecar for the signed-out row (the last user and the label). So a sign-out that another writer beat leaves that writer's sidecar, never one naming the user who was signing out. Every plugin release reads that value as signed out. The order, revoke then clear, is the plugin's, and is kept for parity: an app terminated between the two relaunches signed in with the revoked refresh token, hands out its cached tokens while they are valid, and reports `sessionExpired` at the next refresh. **Purge** deletes the record, then the sidecar, then the challenge record. -- **A sign-out by the plugin is seen by the client.** Say the client holds alice, and the plugin signs out (or deletes its record). The client's next guarded write, a refresh say, is discarded, because the stored bytes changed. The re-read finds the plugin's signed-out value, or no record, so the session becomes `.signedOut`, listed as the sidecar's signed-out row naming alice. The client never writes alice's record back. -- **A plugin `deleteUser` leaves the sidecar.** The plugin deletes its own record and does not know the sidecar, so the deleted user can show as a signed-out picker row (its last username and label) until the client writes the session again: a sign-in through the client, a client refresh of another user the plugin signed in, a guest fetch or a purge. A sign-in through the plugin doesn't replace it. This is documented. A plugin release before 2.63.0's configuration change that deletes the login leaves it the same way (the rollback table below). +- **A sign-out by the plugin is seen by the client.** Say the client holds alice, and the plugin signs out (or deletes its record; with an access group, a plugin sign-out from the release after 2.63.0 saves the signed-out value instead). The client's next guarded write, a refresh say, is discarded, because the stored bytes changed. The re-read finds the plugin's signed-out value, or no record, so the session becomes `.signedOut`, listed as the sidecar's signed-out row naming alice. The client never writes alice's record back. +- **A plugin `deleteUser` leaves the sidecar.** The plugin deletes its own record (with an access group, plugin releases after 2.63.0 save the signed-out value in its place) and does not know the sidecar, so the deleted user can show as a signed-out picker row (its last username and label) until the client writes the session again: a sign-in through the client, a client refresh of another user the plugin signed in, a guest fetch or a purge. A sign-in through the plugin doesn't replace it. This is documented. A plugin release before 2.63.0's configuration change that deletes the login leaves it the same way (the rollback table below). - **Named sessions never touch the plugin's item.** They keep the client's own records (§6). **Removed from the design, all unreleased:** @@ -570,7 +571,7 @@ Where a row names a test (in brackets), it is pinned in both rollback matrices ( |---|---| | Back to a plugin-only release, 2.53.3 or later (2.51.0 to 2.53.2 on an install where one of them has already run, or, for 2.51.3 to 2.53.2, with an access group) | The plugin reads its own key, which holds the newest saved login, including a refresh token the client rotated: its refresh sends that token and succeeds (`testMatrix_rotationRollback_isNowSignedIn`). A client sign-in is seen by the plugin (`testMatrix_clientSignIn_isSeenByThePlugin`). The matrices emulate 2.62.0; an earlier release can wipe the service first (the pre-2.53.3 row below) | | A user signed out through the client, then a rollback | **Stays signed out**, because the record holds `{"noCredentials":{}}`, which every plugin release reads as signed out (`testMatrix_clientSignOut_readByEveryPluginAsSignedOut`), and the sign-out cleared the AWSMobileClient logins, so the plugin's configure-time migration has nothing to write back. That clear is best effort, and runs before the signed-out record is written: an AWSMobileClient login that failed to clear (a warning says so) can still undo it on a plugin release up to 2.63.0, which migrates it over the signed-out login (2.63.0's migration reads the signed-out record as no session). A later plugin release counts the signed-out record as a login and migrates nothing over it, until a guest replaces the record. That holds on a build with the earlier configuration too, even when its key still holds a copy an earlier carry left: that build's own rule carries the signed-out record over the copy (`testMatrix_signedOutUserIsNeverSignedBackIn`). It does not hold for a later change the rule does not carry that lands on an earlier configuration's key: the rule deletes the signed-out record, and that key's old copy restores the user signed in, as with the plugin. One caveat for guests: after a rollback, a plugin build with an identity pool that allows guests may start a guest session in the shared login on its first `fetchAuthSession`, as it does after its own sign-out. The guest then replaces the signed-out row. It is never the old user | -| A purge, or the plugin's own sign-out, after a configuration carry, then a rollback to the build with the earlier configuration | **Caveat: that build signs the user back in.** A carry copies and keeps the source (the plugin's rule). A purge deletes only the current configuration's record, and the plugin's own sign-out deletes its record the same way, so the earlier configuration's copy survives and that build reads it (`testMatrix_purgeAfterACarry_leavesTheEarlierConfigurationsCopy_caveat`). So a **sign-out through the client**, which writes `noCredentials` instead of deleting, survives this rollback; only a later change onto an earlier configuration's copy undoes it (the row above) | +| A purge, or the plugin's own sign-out without an access group, after a configuration carry, then a rollback to the build with the earlier configuration | **Caveat: that build signs the user back in.** A carry copies and keeps the source (the plugin's rule). A purge deletes only the current configuration's record, and the plugin's own sign-out without an access group deletes its record the same way, so the earlier configuration's copy survives and that build reads it (`testMatrix_purgeAfterACarry_leavesTheEarlierConfigurationsCopy_caveat`). So a **sign-out through the client**, which writes `noCredentials` instead of deleting, survives this rollback; only a later change onto an earlier configuration's copy undoes it (the row above). From the plugin release after 2.63.0, the plugin's own sign-out under an access group writes the same `noCredentials`, and survives it too (`testASignOutAfterACarrySurvivesARollbackOnlyUnderAnAccessGroup`); releases up to 2.63.0 delete the record there as well | | Back to a plugin older than 2.51.0, with refresh-token rotation on | The plugin cannot refresh, whatever the client did: it refreshes with `REFRESH_TOKEN_AUTH`, which Cognito refuses on a rotation client. Without rotation, as the first row | | Back to a plugin-only release, with named sessions | **Named sessions disappear**, because the plugin never reads `amplify.1.` records. They stay in the keychain and **come back on roll-forward**, unless the rolled-back release wiped its keychain service: on an access-group transition, or at the first configuration of a release before 2.53.3 (the next row). The access-group transition of a plugin release before 2.63.0 wipes the whole service, client records included; the scoped wipe of 2.63.0 and later spares them. The scoped wipe shipped with the client (`testMatrix_namedSessionsDisappearOnRollbackAndReturnOnRollForward`) | | Back to a plugin release before 2.53.3, on an install where no such release has run (a fresh install of the client build, say) | **Caveat: that release wipes the plugin's keychain service the first time it configures** (2.51.3 to 2.53.2 only without an access group): `.default`'s login, its sidecar and challenge record, the named sessions and the device records. The plugin starts signed out, and nothing comes back on roll-forward. Plugin-only apps rolled back across 2.53.3 have the same hazard (the `isKeychainConfigured` check, removed in 2.53.3 by #4129). Roll back to 2.53.3 or later | @@ -583,13 +584,13 @@ Where a row names a test (in brackets), it is pinned in both rollback matrices ( | The plugin and the client in one process, over `.default` | Not supported (§4.9). The plugin keeps its tokens in memory, so with rotation on a refresh by one side can break the other's token until relaunch | | A client-only app upgraded from AWSMobileClient, or changing its access group | No migration. That is the plugin's job. A sign-out or purge of `.default` clears the AWSMobileClient logins, as the plugin's sign-out does | | Back to a plugin release before 2.63.0 while AWSMobileClient logins are still on the device | **Caveat: the old login replaces whatever `.default` holds.** Releases before 2.63.0 migrate the legacy data at configure over any record, with no check, so a user signed in through the client is replaced by the old AWSMobileClient user. After a `.default` sign-out or purge through the client there is nothing left to migrate, except a user pool login after one under an identity-pool-only configuration, which leaves the user pool's service. An app moving from AWSMobileClient ships a plugin release that migrated first (the README says so) | -| To 2.63.0 or a later plugin release, while AWSMobileClient logins are still on the device | A user signed in through the client is kept: the plugin clears the old logins without migrating them. A guest the client saved is replaced: an old user-pool or federated login, or an old guest, migrates over it, as over a guest the plugin saved, and the guest identity is lost. After a sign-out or purge of `.default` through the client there is nothing to migrate, except a user pool login left after one under an identity-pool-only configuration, which a plugin build with a user pool migrates unless it is later than 2.63.0 and finds the signed-out record under its own configuration. From the release after 2.63.0, a client sign-out's signed-out record (`{"noCredentials":{}}`) also stops the migration, as a login does (`MigrateLegacyCredentialStore.existingSession`, `testExecute_whenNewStoreHoldsASignedOutRecord_shouldNotMigrateAndShouldClearLegacyStores`), so a login that failed to clear doesn't sign the user back in while the signed-out record is there. A guest replaces it (the migration reads a guest as no session); the client's own guest fetch over the signed-out record clears the logins again first (`testAGuestOverASignedOutRecordClearsWhatTheSignOutLeft`), never over an absent record (`testAGuestOverAnAbsentRecordKeepsThem`). 2.63.0 reads the signed-out record as no session and migrates over it. The client clears the logins before the signed-out write or the purge's delete (`testASignOutWhoseWriteFailsHasClearedThem`, `testAPurgeWhoseDeleteFailsHasClearedThem`) | +| To 2.63.0 or a later plugin release, while AWSMobileClient logins are still on the device | A user signed in through the client is kept: the plugin clears the old logins without migrating them. A guest the client saved is replaced: an old user-pool or federated login, or an old guest, migrates over it, as over a guest the plugin saved, and the guest identity is lost. After a sign-out or purge of `.default` through the client there is nothing to migrate, except a user pool login left after one under an identity-pool-only configuration, which a plugin build with a user pool migrates unless it is later than 2.63.0 and finds the signed-out record under its own configuration. From the release after 2.63.0, a client sign-out's signed-out record (`{"noCredentials":{}}`) also stops the migration, as a login does (`MigrateLegacyCredentialStore.existingSession`, `testExecute_whenNewStoreHoldsASignedOutRecord_shouldNotMigrateAndShouldClearLegacyStores`), so a login that failed to clear doesn't sign the user back in while the signed-out record is there. From the release after 2.63.0, the plugin's own sign-out under an access group saves the same record, with the same effect (`testTheNextLaunchDropsAnAWSMobileClientLoginLeftAfterTheSignOutOnlyUnderAnAccessGroup`), and the client's guest fetch after it clears the logins first (`testAGuestTheClientFetchesAfterThePluginsSignOutClearsTheAWSMobileClientLogins`); without an access group the plugin's sign-out deletes the record, and the client's guest fetch after it, over an absent record, leaves them. A guest replaces it (the migration reads a guest as no session); the client's own guest fetch over the signed-out record clears the logins again first (`testAGuestOverASignedOutRecordClearsWhatTheSignOutLeft`), never over an absent record (`testAGuestOverAnAbsentRecordKeepsThem`). 2.63.0 reads the signed-out record as no session and migrates over it. The client clears the logins before the signed-out write or the purge's delete (`testASignOutWhoseWriteFailsHasClearedThem`, `testAPurgeWhoseDeleteFailsHasClearedThem`) | The bytes the client writes (each `AmplifyCredentials` shape, `noCredentials` and `authConfiguration`) decode with the plugin's own types, in releases before 2.63.0 too, and the client's items have the plugin's keychain attributes. This solves rollback with rotation, and rollback past the reader, for the default session. The in-process rotation case, the plugin and the client together in one process, is not solved: running both there is not supported (§4.9). -**The caveats in the table are accepted for the beta.** Two behave as the plugin does today: a purge, or the plugin's own sign-out, after a configuration carry; and a later change the rule does not carry onto an earlier configuration's copy. The third is the client's own: a rollback to a plugin release before 2.63.0 while AWSMobileClient logins are still on the device. The plugin's migration deletes those logins at its first configure, so a plugin-only app has none left; a client-only app keeps them until `.default` signs out or is purged. An app moving from AWSMobileClient ships a plugin release that migrated them first. +**The caveats in the table are accepted for the beta.** Two behave as the plugin does today: a purge, or the plugin's own sign-out without an access group, after a configuration carry; and a later change the rule does not carry onto an earlier configuration's copy. The third is the client's own: a rollback to a plugin release before 2.63.0 while AWSMobileClient logins are still on the device. The plugin's migration deletes those logins at its first configure, so a plugin-only app has none left; a client-only app keeps them until `.default` signs out or is purged. An app moving from AWSMobileClient ships a plugin release that migrated them first. ### 4.9 Plugin and Auth Client side by side diff --git a/docs/design/RELEASE-NOTES-PENDING.md b/docs/design/RELEASE-NOTES-PENDING.md index bc8db83539..a2bbc903e4 100644 --- a/docs/design/RELEASE-NOTES-PENDING.md +++ b/docs/design/RELEASE-NOTES-PENDING.md @@ -10,13 +10,17 @@ behaviour. | Change | Who sees it | Details | |---|---|---| -| Upgrading from the AWS Mobile SDK (AWSMobileClient, Amplify v1): a signed-out record (`{"noCredentials":{}}`) in the plugin's saved session now stops the migration, as a login does: nothing migrates over it, the old keychain locations are cleared, and the plugin logs the info line `A signed-out session already exists, discarding legacy credentials`. In 2.63.0 the migration read that record as no session, so an old AWSMobileClient login migrated over it and signed the user back in. Only a sign-out through `AmplifyCognitoClient`'s default session saves that record; the plugin's own sign-out deletes the record, so an app that uses only the plugin is not affected. A guest is still not a login: a guest saved over the signed-out record (by the plugin, an app extension or the client) lets an old login migrate over it again | Apps upgraded from AWSMobileClient or Amplify v1 that use `AmplifyCognitoClient`'s default session, or roll back or forward between a client build and a plugin build | [Design](AGREED-DESIGN-AmplifyCognitoClient.md) §4.8 | +| Security: the sign-in's verbose lines no longer print the challenge session. The challenge a sign-in answers (the engine's `RespondToAuthChallenge`) had no description of its own, so every verbose line that printed it by reflection showed its session in full, and the username: the "Sending event" lines of the sign-in actions when Cognito answers with a challenge (SRP, custom auth, `USER_AUTH`, migrate auth, auto sign-in after sign-up, and each `confirmSignIn` answer), and the lines that print a sign-in state holding one. It now prints with the session as `` and its two usernames masked (`alice` as `al*ce`), as the sign-in data already did, in `print`, `debugPrint`, interpolation and `dump`. The other fields print as they did nested in an event or state in 2.63.0; `parameters`, Cognito's challenge parameters, still print in full, and can hold a username (`USERNAME` on a custom challenge). Printed on its own, the challenge now names its type in full (`InternalAWSCognitoAuth.RespondToAuthChallenge(…)`). The one app-visible text that prints it is the `AuthError.unknown` an MFA setup throws when it offers only SMS, which the plugin does not set up: "Unable to determine next step from challenge:" and the challenge, which held the session and username in full in 2.63.0 | Anyone reading or collecting verbose logs; apps that log or show the text of `AuthError.unknown` from `signIn` or `confirmSignIn` | — | +| Fix: `confirmSignIn` now throws when the user pool accepts the answer but the identity pool step after it fails (`GetId` or `GetCredentialsForIdentity`), with the identity pool's error, as `signIn` does (for example `AuthError.service` with `AWSCognitoAuthError.invalidParameter`), and logs the verbose line `Authorization reached an error state …` that `signIn` logs there. In 2.63.0 it never returned, and every Auth call queued behind it waited too | Apps with an identity pool that sign in with a challenge (MFA, a new password, custom auth, a factor selection) | — | +| Upgrading from the AWS Mobile SDK (AWSMobileClient, Amplify v1): a signed-out record (`{"noCredentials":{}}`) in the plugin's saved session now stops the migration, as a login does: nothing migrates over it, the old keychain locations are cleared, and the plugin logs the info line `A signed-out session already exists, discarding legacy credentials`. In 2.63.0 the migration read that record as no session, so an old AWSMobileClient login migrated over it and signed the user back in. A sign-out through `AmplifyCognitoClient`'s default session saves that record, and so, with a keychain access group set, does the plugin's own sign-out (the access-group row below). Without an access group the plugin's sign-out deletes the record, so an app that uses only the plugin without one is not affected; with one, the plugin's sign-out already clears the old keychain locations, so only an old login that survived that clear, or was saved there since, is now dropped instead of migrated. A guest is still not a login: a guest saved over the signed-out record (by the plugin, an app extension or the client) lets an old login migrate over it again | Apps upgraded from AWSMobileClient or Amplify v1 that use `AmplifyCognitoClient`'s default session or a keychain access group, or roll back or forward between a client build and a plugin build | [Design](AGREED-DESIGN-AmplifyCognitoClient.md) §4.8 | +| Behaviour change: with a keychain access group set, ending a login through the plugin (`signOut`, local or global; `clearFederationToIdentityPool`; the sign-out after `deleteUser`) now saves the signed-out record, `{"noCredentials":{}}`, in the shared keychain service instead of deleting the session record: when the record holds a login (user pool tokens or a federated identity), or is already signed out. A guest's sign-out still deletes the guest's record, as does a sign-out over a record the plugin cannot read, and a sign-out with nothing saved writes nothing, so neither stops a login's migration. Those are the bytes `AmplifyCognitoClient`'s default session saves when it signs a session out, and every plugin release reads them as signed out (releases back to 2.10.0 decode them as `AmplifyCredentials.noCredentials`, the value for no saved session). The next sign-in replaces the record. The record then stops an app extension's first access-group migration (`migrateKeychainItems: true`), as a login does, so a stale login in the extension's own keychain is never moved in over a user who signed out; in 2.63.0 only a device record left by a user-pool sign-in stopped it. An extension's first access-group transition without migration counts the record too, as it counts a login, and keeps the extension's own old keychain items instead of clearing them. The AWSMobileClient (Amplify v1) migration treats the record as a signed-out session (the row above): it migrates nothing over it and clears the old locations, which the sign-out already clears, until a guest replaces the record; a guest `AmplifyCognitoClient`'s default session fetches over it clears those logins first, best effort. A plugin sign-out after a configuration change that copied the login now survives a rollback to the build with the earlier configuration, whose rule copies the signed-out record over the earlier copy, as after a sign-out through the client. Without an access group nothing changes: the record is deleted. `AmplifyCognitoClient.storedSessions(…, includingSignedOut: true)` in that access group now lists `.default` as a signed-out row after a plugin sign-out of a login (with no label or username unless the client saved them beside the record), where it listed no `.default` row; the default listing is unchanged. Logs under an access group: such a sign-out logs the keychain store's verbose `[KeychainStore]` lines for setting an item in place of those for removing one, and at the next launch the plugin no longer logs the info line `No existing session found.`, and its verbose credential-store line sends `completedOperation` with no credentials where it sent `throwError(itemNotFound)` | Apps with a keychain access group, especially with app extensions; apps that list stored sessions with `includingSignedOut: true` | [Decisions](auth-client-DECISIONS.md), decision 40 | | One new log line of the `InternalAWSCognitoAuth/.swift` form, the info line above in `MigrateLegacyCredentialStore`, so the state-machine actions' `\(#fileID)` sites go from 254 to 255, in 66 files | Anyone filtering or parsing plugin logs by file prefix | — | ## AmplifyCognitoClient (`@_spi(AmplifyExperimental)`, beta) | Change | Details | |---|---| +| Security: the engine's verbose lines no longer print a sign-in challenge's session: a sent challenge answer (`initiateTOTPSetup`, say) shows it as ``, beside the usernames the client already redacted. In 2.63.0 it was printed in full | — | | A Cognito internal error (`InternalErrorException`, from either pool) and an error status the SDK does not model are `AuthClientError.service` with no code in every call: `USER_AUTH`, auto, device SRP and passkey sign-ins, `deleteUser`, a sign-out's revoke and global sign-out, and the passkey credential calls included. In 2.63.0 they were `AuthClientError.unknown`, and an internal error in those calls and sign-ins still suggested reporting a bug. A 5xx or an internal error suggests a retry, and any other status says the service refused the request with an error the client does not recognise (the description has the status and message). In a `.partial` sign-out, the revoke's or global sign-out's suggestion says instead that it will not be retried; the global sign-out's says the user's other sessions stay signed in, without the "unless the request reached the service" that only a transport failure's has. Code that matched `.unknown` for these must match `.service(nil, …)`. The credentials providers report both cases the same way, so a provider's `CredentialsError` and its `disposition` don't change. The Auth plugin keeps `AuthError.unknown` | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Errors" | | A refresh, a sign-out's revoke and the passkey credential calls report a transport failure as every other call does: a response that is not JSON, or a bad server response, is `.service` with no code, "The service returned a response that could not be read." and a retry suggestion; a network failure has the network texts, with the network error underneath; any other URL loading failure is "The service request failed.". In 2.63.0 an unreadable body on a refresh kept the engine's text, a passkey call that failed offline said "This should not happen", and an unreadable revoke had an empty description. In a `.partial` sign-out, the revoke's and global sign-out's suggestion says instead that it will not be retried | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Errors" | | A sign-out that can't finish, because the saved record kept changing (`.failed(.storageUnavailable(.interrupted))`) or became unreadable (`.failed(.unknown)`), carries the first server-side failure as its underlying error, the global sign-out's first, mapped as any other call maps it, with the network or retry suggestion. In 2.63.0, after a failed global sign-out, it carried the empty placeholder `revokeTokenError` of a revoke that was never sent, and a revoke that failed on the network said there that the session was signed out on this device and the revoke would not be retried, though the session was still signed in. A sign-in that reaches a challenge as a sign-out, purge or user deletion ends the session throws the sign-in-cancelled `.invalidState`, where it could return the challenge the sign-out had ended | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Errors" | diff --git a/docs/design/auth-client-DECISIONS.md b/docs/design/auth-client-DECISIONS.md index 4110960d59..cc21c17b5d 100644 --- a/docs/design/auth-client-DECISIONS.md +++ b/docs/design/auth-client-DECISIONS.md @@ -8,7 +8,7 @@ says where its decisions are written. Decisions 1–8 were taken in review on 2026-09-22. Decisions 9–19 were taken while the client was built, up to the beta (2026-09-23 to 2026-10-05); they are summarised [below](#later-decisions-2026-09-23-to-2026-10-05). Decisions 20–28 were taken on 2026-10-19, after end-to-end testing of the beta, decisions 29–34 on 2026-10-22, on -the review of its last round, and decisions 35–39 on 2026-10-23, on the review that followed; they are summarised +the review of its last round, and decisions 35–40 on 2026-10-23, on the review that followed; they are summarised [below](#decisions-of-2026-10-19), [below](#decisions-of-2026-10-22) and [below](#decisions-of-2026-10-23). Where a later decision changes an earlier one, the earlier entry says so and is kept as the record. @@ -382,9 +382,11 @@ shipped in 2.63.0, so the releases that wipe the whole service are those before Documented in the design's rollback matrix (§4.8). The first two were accepted for the beta on 2026-10-05, because the plugin behaves the same way; the third is the client's own: -- **A purge, or the plugin's own sign-out, after a configuration carry** leaves the earlier configuration's copy, - because a carry keeps its source. A rollback to the build with that configuration then reads the user signed in. - A sign-out through the client does not do this: it writes the signed-out value, which every build carries. +- **A purge, or the plugin's own sign-out without an access group, after a configuration carry** leaves the earlier + configuration's copy, because a carry keeps its source. A rollback to the build with that configuration then reads + the user signed in. A sign-out through the client does not do this: it writes the signed-out value, which every + build carries. Nor does the plugin's sign-out under an access group from the release after 2.63.0, which writes the + same value (#40); releases up to 2.63.0 delete the record there too. - **A later change that the rule does not carry, onto an earlier configuration's key,** deletes the signed-out login and can find the user's old copy there, which then restores signed in. - **A rollback to a plugin release before 2.63.0 while AWSMobileClient logins are still on the device** replaces @@ -424,7 +426,7 @@ design. | # | Decision | Answer | |---|---|---| | 20 | A guest's session record in the shared service at the access-group migration | **It doesn't stop the migration of a login.** An app extension, or the client's `.default`, that fetched a session before the app's first launch after the update saved it; the app's login replaces it, on a fresh or a resumed migration. A login in the shared service is never replaced, and only a login replaces a guest: one under the app's current configuration, or the one the configuration change at the same launch carries to it, not a stale copy under another configuration's key. | -| 21 | A signed-out record in the shared service at the access-group migration | **It still stops the migration**, as a login does, so a user who signed out through the client's `.default` (which saves that record) is never signed back in by another process's old login. A plugin sign-out deletes the record instead: after one, only a device record stops an extension's first migration. | +| 21 | A signed-out record in the shared service at the access-group migration | **It still stops the migration**, as a login does, so a user who signed out through the client's `.default` (which saves that record) is never signed back in by another process's old login. A plugin sign-out deleted the record instead, so after one only a device record stopped an extension's first migration. *Changed 2026-10-23 (#40):* a plugin sign-out of a login under an access group saves the same record, so it stops the migration too. | | 22 | A known refusal of a carried session's identity step | **The AWS credentials provider fails loudly**: `CredentialsError.notConfigured` (`.failLoudly`), with the refusal underneath. `fetchAuthSession`'s identity fields keep the refusal itself, and the token provider still vends the access token. | | 23 | Which AWSMobileClient credentials a `.default` sign-out clears | **Every identity pool's** credentials service the app's keychain holds, not only the configured one's. The plugin is unchanged. | | 24 | A network failure (the request may not have reached Cognito) | **A retryable `.service` with no code**, in every call, with the client's network texts. The plugin keeps its mapping. | @@ -441,7 +443,8 @@ replaced; with nothing but a guest or nothing to move, the shared guest is kept, nothing to move never discards a guest identity the app already migrated. An extension's first migration whose own old service holds a login does move it over that guest. A signed-out record is not a guest's: it keeps blocking, so a stale login in another process's old service cannot undo a sign-out through the client's `.default`, which saves -that record. A plugin sign-out deletes the record instead, so after one only a device record stops an extension's +that record. A plugin sign-out under an access group saves the same record from the release after 2.63.0 (#40), so it +blocks too. Releases up to 2.63.0 delete the record instead, so after one only a device record stops an extension's first migration: the device metadata a user-pool sign-in saves for a tracked device, or the advanced-security device ID that any user-pool sign-in, hosted-UI sign-in, sign-up or password-reset attempt saves when no device metadata is held. Without one, an extension whose old service holds a login moves it in. @@ -546,8 +549,8 @@ property and the setup URI are unchanged. ## Decisions of 2026-10-23 -Taken on the review that followed the beta's last round. #35, #36 and #37 are written into the design, the client's -README and the release notes. #38 and #39 keep limitations that the client's README documents; #39's is also in +Taken on the review that followed the beta's last round. #35, #36, #37 and #40 are written into the design, the +client's README and the release notes. #38 and #39 keep limitations that the client's README documents; #39's is also in `signOutStoredSession`'s API documentation and the release notes. | # | Decision | Answer | @@ -557,6 +560,7 @@ README and the release notes. #38 and #39 keep limitations that the client's REA | 37 | A Cognito internal error (`InternalErrorException`, from either pool) and an error status the SDK does not model, in the client | **`.service` with no code**, in every call, `USER_AUTH`, auto, device SRP and passkey sign-ins, `deleteUser`, a sign-out's revoke or global sign-out, and the passkey credential calls included, with a recovery suggestion to retry (a 5xx, an internal error) or naming the refusal (a 4xx). In a `.partial` sign-out, the revoke's or global sign-out's suggestion says instead that it will not be retried; a sign-out that could not finish (`.failed`) carries the failure as any other call maps it. The plugin keeps `AuthError.unknown`. | | 38 | A named session's configuration marker left naming an earlier configuration, by a purge stopped before its last step | **Not changed.** The next restore under the purge's configuration carries the session from the earlier one again, and the README says to purge the session again before building a client for it. | | 39 | `signOutStoredSession` with no client for the session live in the process, and `configureUserPoolClient` | **Not given one.** Its revoke uses SDK clients built from the configuration alone, so an app's `configureUserPoolClient` (a custom endpoint, a proxy, certificate pinning) is not applied; hold a client for the session to sign out through it. A purge revokes nothing. | +| 40 | The plugin's own sign-out under a keychain access group | **It saves the signed-out record**, `{"noCredentials":{}}`, in the shared service instead of deleting the session record, on every path that ends a login: `signOut` (local or global), `clearFederationToIdentityPool` and the local sign-out after `deleteUser`, when the record holds a login (user pool tokens or a federated identity) or is already signed out. A guest's sign-out still deletes the guest's record, as does one over a record the plugin cannot read, and a sign-out with nothing saved writes nothing, as the client's `.default` writes nothing when there is nothing to sign out. A plugin sign-out of a login then counts as migrated, as a sign-out through the client's `.default` does. The migration rule is unchanged: a login or a signed-out record stops it; a guest or a configuration record alone doesn't stop a login. Not limited to `migrateKeychainItems`. Without an access group the record is still deleted. Changes #21. | **#35.** The rule exists because a second writer may still save the refresh that used the token, and a forced refresh refused this way is evidence of the same dead token as an unforced one, so it counts too. Keeping the @@ -564,8 +568,11 @@ session retryable while its saved tokens last would only postpone the expiry, an is known to be dead. The text had said the refresh token expired or was revoked, which is not what happened: the token was used, and no one saved the refresh that used it. -**#36.** Only a sign-out through the client's `.default` saves that record; the plugin's own sign-out deletes the -record, so an app that uses only the plugin is not affected. Before, the plugin read the record as no session, so an +**#36.** A sign-out through the client's `.default` saves that record, and so, under an access group, does the +plugin's own sign-out from the release after 2.63.0 (#40). Without an access group the plugin's sign-out deletes the +record, so an app that uses only the plugin without one is not affected; with one, the plugin's sign-out has already +cleared the old logins, so only a login that survived that clear, or was saved there since, is dropped instead of +migrated. Before, the plugin read the record as no session, so an AWSMobileClient login that the client's sign-out failed to clear, or that an app stopped part-way kept, was migrated over it and signed the user back in. The clear comes first so that a failed write or delete, or a process stopped after it, leaves no login behind. A purge leaves no record, so after one only the clear protects the user. The record @@ -588,3 +595,20 @@ stays documented instead. **#39.** The stored-session calls take a configuration, not a client's options. With a client for the session live in the process, the sign-out goes through it, and so through its SDK clients. + +**#40.** After a plugin sign-out under an access group, only a device record stopped an extension's first migration, +so an extension whose own old service still held a login moved it in over the user who signed out. Only ending a login +writes the record: a sign-out with nothing saved, or a guest's, writing it would let an extension that signed out +that way before the app's first launch with the group stop the app's migration and strand its login. The record is +the bytes the client's `.default` saves on sign-out, which every plugin release reads as signed out: releases back to +2.10.0 decode it as `AmplifyCredentials.noCredentials`, the value for no saved session. The next sign-in replaces it. +It ships in the plugin release after 2.63.0; releases up to 2.63.0 delete the record. With #36 it also stops the +AWSMobileClient migration, until a guest replaces the record: the plugin's own guest, or an app extension's, clears +nothing, and a guest the client's `.default` fetches over it clears the old logins first, as over the client's own +signed-out record. The client's `storedSessions(…, includingSignedOut: true)` in that access group lists `.default` +as a signed-out row after such a sign-out, with no label or username unless the client saved them beside the record. +After a configuration carry, such a sign-out survives a rollback to the build with the earlier configuration, since +that build's rule carries the signed-out record over the earlier copy (the caveat in §18 remains without an access +group). Under an access group, the transition without migration also counts the record, as it counts a login, so an +app extension configured with the group for the first time keeps its own old service's items instead of clearing +them. diff --git a/docs/design/issues/rollback-behaviours.md b/docs/design/issues/rollback-behaviours.md index c30990a137..7140f3b7a8 100644 --- a/docs/design/issues/rollback-behaviours.md +++ b/docs/design/issues/rollback-behaviours.md @@ -47,18 +47,18 @@ on, that session can be fully live again. The old binary cannot be fixed. Options: state a minimum rollback target (the first plugin release with the reader), or add a release note. -**Solved by the shared saved login (2026-10-02).** The forward-compatible reader was removed before it shipped, and -the client no longer writes a `$default` session record. No plugin release reads a client record, so none can sign a -user back in from one. A user signed out through the client stays signed out on every plugin release, because the -shared record holds `{"noCredentials":{}}` (`testMatrix_signedOutUserIsNeverSignedBackIn`, +**Solved by the shared saved login (2026-10-02).** The forward-compatible reader was removed before it shipped, and the +client no longer writes a `$default` session record. No plugin release reads a client record, so none can sign a user +back in from one. A user signed out through the client stays signed out on every plugin release, because the shared +record holds `{"noCredentials":{}}` (`testMatrix_signedOutUserIsNeverSignedBackIn`, `testMatrix_clientSignOut_readByEveryPluginAsSignedOut`), unless an AWSMobileClient login left on the device migrates -over it (releases up to 2.63.0 migrate over a signed-out record; design §4.8). One caveat remains, from the plugin's own rule: after a -configuration change that copied the login, a purge (or the plugin's own sign-out, which deletes) leaves the earlier -configuration's copy, which a rollback to the build with that configuration reads -(`testMatrix_purgeAfterACarry_leavesTheEarlierConfigurationsCopy_caveat`). A second caveat, also the plugin's own -rule: a later configuration change that the rule does not carry deletes the signed-out record, and can land on an -earlier configuration's key that still holds the user's old copy, which then restores the user signed in (design -§4.8). Both caveats are accepted for the beta. +over it (releases up to 2.63.0 migrate over a signed-out record; design §4.8). One caveat remains, from the plugin's own +rule: after a configuration change that copied the login, a purge (or the plugin's own sign-out without an access group, +or on plugin releases up to 2.63.0, which deletes) leaves the earlier configuration's copy, which a rollback to the +build with that configuration reads (`testMatrix_purgeAfterACarry_leavesTheEarlierConfigurationsCopy_caveat`). A second +caveat, also the plugin's own rule: a later configuration change that the rule does not carry deletes the signed-out +record, and can land on an earlier configuration's key that still holds the user's old copy, which then restores the +user signed in (design §4.8). Both caveats are accepted for the beta. ## 3. Rolling forward to the client after 1 — resolved within one launch From 54a11d3c091cb218cc3607f05bc60e4c3a13b32a Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:09:58 -0400 Subject: [PATCH 10/57] test(auth): move the access-group record's migration and rollback tests into their own file Keeps the test class within SwiftLint's type-body limit. The tests are unchanged. --- ...GroupSignedOutRecordTests+Migrations.swift | 185 +++++++++++++++++ .../AccessGroupSignedOutRecordTests.swift | 188 ++---------------- 2 files changed, 198 insertions(+), 175 deletions(-) create mode 100644 AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests+Migrations.swift diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests+Migrations.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests+Migrations.swift new file mode 100644 index 0000000000..2d4c041b94 --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests+Migrations.swift @@ -0,0 +1,185 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +@_spi(AmplifyExperimental) import AmplifyFoundation +import AmplifyKeychainTestCommon +import Foundation +import InternalAmplifyKeychain +import XCTest +@_spi(AmplifyExperimental) @testable import AmplifyCognitoClient +@testable import AWSCognitoAuthPlugin +@testable import InternalAWSCognitoAuth + +/// The plugin's signed-out record under an access group against the other migrations: an app extension's first +/// transition without migration, the AWSMobileClient migration at the next launch, a guest the Cognito client fetches +/// over the record, and a rollback after a configuration carry. Beside `AccessGroupSignedOutRecordTests.swift`, whose +/// helpers these use. +extension AccessGroupSignedOutRecordTests { + + /// Test that an app extension's first access-group transition without migration counts the plugin's signed-out + /// record, as it counts a login, and keeps its own old service's items + /// + /// - Given: the plugin, configured with the access group, signed out, so the shared service holds the signed-out + /// record; and an app extension whose unshared service holds bob's stale login and whose `UserDefaults` hold + /// no access group + /// - When: + /// - the extension's store is configured with the access group and `migrateKeychainItems: false` + /// - Then: + /// - the shared service is not empty, so the extension's unshared service is not cleared: bob's login stays + /// there, and the new store reads no login + /// + func testAnExtensionsFirstTransitionWithoutMigrationKeepsItsOwnItems() async throws { + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + _ = await makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()).signOut() + XCTAssertEqual(sharedRecord(), signedOutRecord) + let extensionKeychain = InMemoryKeychain() + let bob = try JSONEncoder().encode(LongLivedCredentials.userPoolAndIdentityPool(username: "bob")) + try extensionKeychain.store(service: service).set(bob, key: sessionAccount) + let suite = "AccessGroupSignedOutRecordTests.extension.\(UUID().uuidString)" + let extensionDefaults = try XCTUnwrap(UserDefaults(suiteName: suite)) + defer { extensionDefaults.removePersistentDomain(forName: suite) } + let keychain = keychain! + + let store = AWSCognitoAuthCredentialStore( + authConfiguration: authConfiguration, + accessGroup: accessGroup, + migrateKeychainItemsOfUserSession: false, + userDefaults: extensionDefaults, + makeKeychainStore: { service, accessGroup in + accessGroup == nil ? extensionKeychain.store(service: service) : keychain.store(service: service, accessGroup: accessGroup) + }, + logger: AmplifyEngineLogRouter() + ) + + XCTAssertEqual(try store.retrieveCredential(), .noCredentials) + XCTAssertEqual(extensionKeychain.value(service: service, account: sessionAccount), bob) + XCTAssertEqual(sharedRecord(), signedOutRecord) + } + + // MARK: With the AWSMobileClient logins + + /// Test that the next launch's AWSMobileClient migration drops a login left after a plugin sign-out under an access + /// group, and still migrates it without one + /// + /// - Given: alice's login, and the plugin signed out, with the access group and without; then AWSMobileClient + /// stores that still hold a login, as when the sign-out's clear of them failed, or a login was saved there since + /// - When: + /// - the plugin configures over them, as at the next launch, which runs the AWSMobileClient migration + /// - Then: + /// - with the access group, the signed-out record stops the migration: the launch is signed out, the record is + /// unchanged, and the AWSMobileClient stores are cleared + /// - without one, the sign-out deleted the record, so the old login is migrated into the unshared service, as + /// before + /// + func testTheNextLaunchDropsAnAWSMobileClientLoginLeftAfterTheSignOutOnlyUnderAnAccessGroup() async throws { + for accessGroup in [accessGroup, nil] { + let label = accessGroup == nil ? "without an access group" : "with the access group" + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + _ = await makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()).signOut() + let cleared = TestCounter() + let legacy = MockKeychainStoreBehavior(data: "legacy", removeAllHandler: { cleared.increment() }) + + let plugin = makePlugin( + accessGroup: accessGroup, + userPool: MockIdentityProvider(), + legacyStore: { _ in legacy } + ) + _ = try? await plugin.getCurrentUser() + + let saved = keychain.value( + service: accessGroup == nil ? service : sharedService, + accessGroup: accessGroup, + account: sessionAccount + ) + if accessGroup == nil { + let migrated = try JSONDecoder().decode(AmplifyCredentials.self, from: XCTUnwrap(saved, label)) + guard case .userPoolAndIdentityPool = migrated else { + return XCTFail("\(label): the old login was not migrated") + } + } else { + XCTAssertEqual(saved, signedOutRecord, label) + guard case .configured(.signedOut, _, _) = await plugin.authStateMachine.currentState else { + return XCTFail("\(label): the next launch is in \(await plugin.authStateMachine.currentState)") + } + } + XCTAssertGreaterThan(cleared.get(), 0, label) + } + } + + /// Test that a guest the Cognito client's `.default` fetches after a plugin sign-out under an access group clears + /// the AWSMobileClient logins first + /// + /// - Given: the plugin, configured with the access group, signed out, so the shared service holds the signed-out + /// record; and AWSMobileClient logins in the app's keychain, as when the sign-out's clear of them failed + /// - When: + /// - the client's `.default` in the access group fetches its session, which fetches a guest + /// - Then: + /// - the shared service holds the guest, and the AWSMobileClient logins are gone, so a plugin build has none + /// to migrate over the guest + /// + func testAGuestTheClientFetchesAfterThePluginsSignOutClearsTheAWSMobileClientLogins() async throws { + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + _ = await makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()).signOut() + XCTAssertEqual(sharedRecord(), signedOutRecord) + let legacyServices = SessionRecordStore.legacyLoginServices(for: PoolNamespace(authConfiguration)) + XCTAssertFalse(legacyServices.isEmpty) + for legacyService in legacyServices { + try keychain.store(service: legacyService).set(Data("legacy".utf8), key: "tokens.alice") + } + + let app = ClientOverKeychain(keychain: keychain, clearsLegacyLogins: true) + var client: AmplifyCognitoClient? = try app.client(accessGroup: accessGroup) + _ = try await XCTUnwrap(client).fetchAuthSession() + client = nil + await app.waitForBaseline() + + let saved = try JSONDecoder().decode(AmplifyCredentials.self, from: XCTUnwrap(sharedRecord())) + guard case .identityPoolOnly = saved else { + return XCTFail("the shared service does not hold a guest") + } + for legacyService in legacyServices { + XCTAssertNil(keychain.value(service: legacyService, account: "tokens.alice"), legacyService) + } + } + + // MARK: A rollback after a configuration carry + + /// Test that a plugin sign-out after a configuration carry, under an access group, survives a rollback to the + /// build with the earlier configuration, and without one leaves that build the earlier copy + /// + /// - Given: alice saved under a user-pool-only configuration; the plugin's store under the configuration that adds + /// the identity pool, which carries her to its key and keeps the earlier copy; then that store ends the session + /// - When: + /// - the store is built with the earlier configuration, as by a rollback to that build, which runs the + /// plugin's rule back to it + /// - Then: + /// - with the access group, the rule carries the signed-out record over the earlier copy: that build reads + /// `.noCredentials`, as after a sign-out through the client + /// - without one, the sign-out deleted the record, so there is nothing to carry and that build reads alice + /// + func testASignOutAfterACarrySurvivesARollbackOnlyUnderAnAccessGroup() throws { + let earlier = AuthConfiguration(client: ClientOverKeychain.userPoolOnlyConfiguration) + for accessGroup in [accessGroup, nil] { + let label = accessGroup == nil ? "without an access group" : "with the access group" + let earlierStore = makeStore(accessGroup: accessGroup, configuration: earlier) + try earlierStore.saveCredential(.userPoolOnly(signedInData: SignedInData( + signedInDate: Date(), + signInMethod: .apiBased(.userSRP), + cognitoUserPoolTokens: LongLivedCredentials.tokens(username: "alice") + ))) + let alice = try earlierStore.retrieveCredential() + let current = makeStore(accessGroup: accessGroup) + XCTAssertEqual(try current.retrieveCredential(), alice, label) + + try current.deleteCredential() + + let rolledBack = makeStore(accessGroup: accessGroup, configuration: earlier) + XCTAssertEqual(try rolledBack.retrieveCredential(), accessGroup == nil ? alice : .noCredentials, label) + } + } +} diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift index 21f0579ec6..699e3361bd 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift @@ -29,21 +29,21 @@ import XCTest /// extension and with the Cognito client's `.default` session. Only the Cognito service calls are mocked. final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { - private let service = "com.amplify.awsCognitoAuthPlugin" - private let sharedService = "com.amplify.awsCognitoAuthPluginShared" - private let accessGroup = "group.acme" + let service = "com.amplify.awsCognitoAuthPlugin" + let sharedService = "com.amplify.awsCognitoAuthPluginShared" + let accessGroup = "group.acme" /// Exactly what the Cognito client's `.default` saves on sign-out. - private let signedOutRecord = Data(#"{"noCredentials":{}}"#.utf8) + let signedOutRecord = Data(#"{"noCredentials":{}}"#.utf8) /// The app's configuration, as the plugin stores it: `ClientOverKeychain`'s, so the client's `.default` uses the /// same record. - private let authConfiguration = AuthConfiguration(client: ClientOverKeychain.configuration) - private var sessionAccount: String { + let authConfiguration = AuthConfiguration(client: ClientOverKeychain.configuration) + var sessionAccount: String { AWSCognitoAuthCredentialStore.sessionAccount(for: authConfiguration) } - private var keychain: InMemoryKeychain! + var keychain: InMemoryKeychain! private var userDefaults: UserDefaults! private var userDefaultsSuite: String! @@ -358,176 +358,14 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { XCTAssertEqual(sharedRecord(), signedOutRecord) } - /// Test that an app extension's first access-group transition without migration counts the plugin's signed-out - /// record, as it counts a login, and keeps its own old service's items - /// - /// - Given: the plugin, configured with the access group, signed out, so the shared service holds the signed-out - /// record; and an app extension whose unshared service holds bob's stale login and whose `UserDefaults` hold - /// no access group - /// - When: - /// - the extension's store is configured with the access group and `migrateKeychainItems: false` - /// - Then: - /// - the shared service is not empty, so the extension's unshared service is not cleared: bob's login stays - /// there, and the new store reads no login - /// - func testAnExtensionsFirstTransitionWithoutMigrationKeepsItsOwnItems() async throws { - try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) - _ = await makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()).signOut() - XCTAssertEqual(sharedRecord(), signedOutRecord) - let extensionKeychain = InMemoryKeychain() - let bob = try JSONEncoder().encode(LongLivedCredentials.userPoolAndIdentityPool(username: "bob")) - try extensionKeychain.store(service: service).set(bob, key: sessionAccount) - let suite = "AccessGroupSignedOutRecordTests.extension.\(UUID().uuidString)" - let extensionDefaults = try XCTUnwrap(UserDefaults(suiteName: suite)) - defer { extensionDefaults.removePersistentDomain(forName: suite) } - let keychain = keychain! - - let store = AWSCognitoAuthCredentialStore( - authConfiguration: authConfiguration, - accessGroup: accessGroup, - migrateKeychainItemsOfUserSession: false, - userDefaults: extensionDefaults, - makeKeychainStore: { service, accessGroup in - accessGroup == nil ? extensionKeychain.store(service: service) : keychain.store(service: service, accessGroup: accessGroup) - }, - logger: AmplifyEngineLogRouter() - ) - - XCTAssertEqual(try store.retrieveCredential(), .noCredentials) - XCTAssertEqual(extensionKeychain.value(service: service, account: sessionAccount), bob) - XCTAssertEqual(sharedRecord(), signedOutRecord) - } - - // MARK: With the AWSMobileClient logins - - /// Test that the next launch's AWSMobileClient migration drops a login left after a plugin sign-out under an access - /// group, and still migrates it without one - /// - /// - Given: alice's login, and the plugin signed out, with the access group and without; then AWSMobileClient - /// stores that still hold a login, as when the sign-out's clear of them failed, or a login was saved there since - /// - When: - /// - the plugin configures over them, as at the next launch, which runs the AWSMobileClient migration - /// - Then: - /// - with the access group, the signed-out record stops the migration: the launch is signed out, the record is - /// unchanged, and the AWSMobileClient stores are cleared - /// - without one, the sign-out deleted the record, so the old login is migrated into the unshared service, as - /// before - /// - func testTheNextLaunchDropsAnAWSMobileClientLoginLeftAfterTheSignOutOnlyUnderAnAccessGroup() async throws { - for accessGroup in [accessGroup, nil] { - let label = accessGroup == nil ? "without an access group" : "with the access group" - try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) - _ = await makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()).signOut() - let cleared = TestCounter() - let legacy = MockKeychainStoreBehavior(data: "legacy", removeAllHandler: { cleared.increment() }) - - let plugin = makePlugin( - accessGroup: accessGroup, - userPool: MockIdentityProvider(), - legacyStore: { _ in legacy } - ) - _ = try? await plugin.getCurrentUser() - - let saved = keychain.value( - service: accessGroup == nil ? service : sharedService, - accessGroup: accessGroup, - account: sessionAccount - ) - if accessGroup == nil { - let migrated = try JSONDecoder().decode(AmplifyCredentials.self, from: XCTUnwrap(saved, label)) - guard case .userPoolAndIdentityPool = migrated else { - return XCTFail("\(label): the old login was not migrated") - } - } else { - XCTAssertEqual(saved, signedOutRecord, label) - guard case .configured(.signedOut, _, _) = await plugin.authStateMachine.currentState else { - return XCTFail("\(label): the next launch is in \(await plugin.authStateMachine.currentState)") - } - } - XCTAssertGreaterThan(cleared.get(), 0, label) - } - } - - /// Test that a guest the Cognito client's `.default` fetches after a plugin sign-out under an access group clears - /// the AWSMobileClient logins first - /// - /// - Given: the plugin, configured with the access group, signed out, so the shared service holds the signed-out - /// record; and AWSMobileClient logins in the app's keychain, as when the sign-out's clear of them failed - /// - When: - /// - the client's `.default` in the access group fetches its session, which fetches a guest - /// - Then: - /// - the shared service holds the guest, and the AWSMobileClient logins are gone, so a plugin build has none - /// to migrate over the guest - /// - func testAGuestTheClientFetchesAfterThePluginsSignOutClearsTheAWSMobileClientLogins() async throws { - try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) - _ = await makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()).signOut() - XCTAssertEqual(sharedRecord(), signedOutRecord) - let legacyServices = SessionRecordStore.legacyLoginServices(for: PoolNamespace(authConfiguration)) - XCTAssertFalse(legacyServices.isEmpty) - for legacyService in legacyServices { - try keychain.store(service: legacyService).set(Data("legacy".utf8), key: "tokens.alice") - } - - let app = ClientOverKeychain(keychain: keychain, clearsLegacyLogins: true) - var client: AmplifyCognitoClient? = try app.client(accessGroup: accessGroup) - _ = try await XCTUnwrap(client).fetchAuthSession() - client = nil - await app.waitForBaseline() - - let saved = try JSONDecoder().decode(AmplifyCredentials.self, from: XCTUnwrap(sharedRecord())) - guard case .identityPoolOnly = saved else { - return XCTFail("the shared service does not hold a guest") - } - for legacyService in legacyServices { - XCTAssertNil(keychain.value(service: legacyService, account: "tokens.alice"), legacyService) - } - } - - // MARK: A rollback after a configuration carry - - /// Test that a plugin sign-out after a configuration carry, under an access group, survives a rollback to the - /// build with the earlier configuration, and without one leaves that build the earlier copy - /// - /// - Given: alice saved under a user-pool-only configuration; the plugin's store under the configuration that adds - /// the identity pool, which carries her to its key and keeps the earlier copy; then that store ends the session - /// - When: - /// - the store is built with the earlier configuration, as by a rollback to that build, which runs the - /// plugin's rule back to it - /// - Then: - /// - with the access group, the rule carries the signed-out record over the earlier copy: that build reads - /// `.noCredentials`, as after a sign-out through the client - /// - without one, the sign-out deleted the record, so there is nothing to carry and that build reads alice - /// - func testASignOutAfterACarrySurvivesARollbackOnlyUnderAnAccessGroup() throws { - let earlier = AuthConfiguration(client: ClientOverKeychain.userPoolOnlyConfiguration) - for accessGroup in [accessGroup, nil] { - let label = accessGroup == nil ? "without an access group" : "with the access group" - let earlierStore = makeStore(accessGroup: accessGroup, configuration: earlier) - try earlierStore.saveCredential(.userPoolOnly(signedInData: SignedInData( - signedInDate: Date(), - signInMethod: .apiBased(.userSRP), - cognitoUserPoolTokens: LongLivedCredentials.tokens(username: "alice") - ))) - let alice = try earlierStore.retrieveCredential() - let current = makeStore(accessGroup: accessGroup) - XCTAssertEqual(try current.retrieveCredential(), alice, label) - - try current.deleteCredential() - - let rolledBack = makeStore(accessGroup: accessGroup, configuration: earlier) - XCTAssertEqual(try rolledBack.retrieveCredential(), accessGroup == nil ? alice : .noCredentials, label) - } - } - // MARK: Helpers - private func sharedRecord() -> Data? { + func sharedRecord() -> Data? { keychain.value(service: sharedService, accessGroup: accessGroup, account: sessionAccount) } /// A plugin configured over the shared record, as at the next launch, is signed out, and the record is unchanged. - private func assertTheNextLaunchIsSignedOut(file: StaticString = #filePath, line: UInt = #line) async { + func assertTheNextLaunchIsSignedOut(file: StaticString = #filePath, line: UInt = #line) async { let plugin = makePlugin(accessGroup: accessGroup, userPool: MockIdentityProvider()) do { let user = try await plugin.getCurrentUser() @@ -546,7 +384,7 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { /// An app extension's first configuration with the access group and `migrateKeychainItems: true`, its unshared /// service holding bob's stale login and its `UserDefaults` no access group, stops at the signed-out record: the /// record stays, the new store reads no login, and bob's login stays in the extension's own service. - private func assertAnExtensionsFirstMigrationStops(file: StaticString = #filePath, line: UInt = #line) throws { + func assertAnExtensionsFirstMigrationStops(file: StaticString = #filePath, line: UInt = #line) throws { // Only a sign-in writes a device record; none was saved, so the signed-out record alone stops the migration. let pluginAccounts = try keychain.store(service: sharedService, accessGroup: accessGroup).allAccounts() .filter { !SessionRecordAccount.isClientSessionRecord($0) } @@ -616,7 +454,7 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { /// The plugin's credential store over the shared keychain, as `makeCredentialStore` builds it, without migration: /// the app's configuration's, unless `configuration` is given. - private func makeStore(accessGroup: String?, configuration: AuthConfiguration? = nil) -> AWSCognitoAuthCredentialStore { + func makeStore(accessGroup: String?, configuration: AuthConfiguration? = nil) -> AWSCognitoAuthCredentialStore { let keychain = keychain! return AWSCognitoAuthCredentialStore( authConfiguration: configuration ?? authConfiguration, @@ -630,7 +468,7 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { ) } - private func signOutUserPool() -> MockIdentityProvider { + func signOutUserPool() -> MockIdentityProvider { MockIdentityProvider( mockRevokeTokenResponse: { _ in .testData }, mockGlobalSignOutResponse: { _ in .testData }, @@ -642,7 +480,7 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { /// the Cognito service calls mocked. Its credential store machine starts idle, so its first load skips the /// AWSMobileClient migration, unless `legacyStore` is given: it then starts as at a launch, and the migration /// runs over those stores first. - private func makePlugin( + func makePlugin( accessGroup: String?, userPool: CognitoUserPoolBehavior, legacyStore: (@Sendable (String) -> any KeychainItemStoreBehavior)? = nil From c4f40b1ba83e6f812cc432d07a67100740db67ce Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:51:02 -0400 Subject: [PATCH 11/57] docs(auth): say which verbose lines the access-group sign-out's read of the record logs The comment said the quiet read logs no new line, but the keychain store still logs its verbose lines for opening the shared service and retrieving the item; only a read failure is kept at verbose. Correct the comment and list the lines in the pending release-note row. --- .../CredentialStorage/AWSCognitoAuthCredentialStore.swift | 3 ++- docs/design/RELEASE-NOTES-PENDING.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift index 179e5f81bf..c7428acaab 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift @@ -262,7 +262,8 @@ extension AWSCognitoAuthCredentialStore: AmplifyAuthCredentialStoreBehavior { try removeSession(for: authConfiguration) return } - // Read as the migration's checks read the shared service: quietly, so the sign-out logs no new line. + // Read as the migration's checks read the shared service, quietly: a read failure is logged at verbose only. + // The read still logs the keychain store's verbose lines for opening the service and retrieving an item. let shared = EngineKeychainStore.quiet(makeKeychainStore(sharedService, accessGroup), logger: logger) switch Self.savedSession(at: generateSessionKey(for: authConfiguration), in: shared) { case .login, .signedOut: diff --git a/docs/design/RELEASE-NOTES-PENDING.md b/docs/design/RELEASE-NOTES-PENDING.md index a2bbc903e4..e36bde757c 100644 --- a/docs/design/RELEASE-NOTES-PENDING.md +++ b/docs/design/RELEASE-NOTES-PENDING.md @@ -13,7 +13,7 @@ behaviour. | Security: the sign-in's verbose lines no longer print the challenge session. The challenge a sign-in answers (the engine's `RespondToAuthChallenge`) had no description of its own, so every verbose line that printed it by reflection showed its session in full, and the username: the "Sending event" lines of the sign-in actions when Cognito answers with a challenge (SRP, custom auth, `USER_AUTH`, migrate auth, auto sign-in after sign-up, and each `confirmSignIn` answer), and the lines that print a sign-in state holding one. It now prints with the session as `` and its two usernames masked (`alice` as `al*ce`), as the sign-in data already did, in `print`, `debugPrint`, interpolation and `dump`. The other fields print as they did nested in an event or state in 2.63.0; `parameters`, Cognito's challenge parameters, still print in full, and can hold a username (`USERNAME` on a custom challenge). Printed on its own, the challenge now names its type in full (`InternalAWSCognitoAuth.RespondToAuthChallenge(…)`). The one app-visible text that prints it is the `AuthError.unknown` an MFA setup throws when it offers only SMS, which the plugin does not set up: "Unable to determine next step from challenge:" and the challenge, which held the session and username in full in 2.63.0 | Anyone reading or collecting verbose logs; apps that log or show the text of `AuthError.unknown` from `signIn` or `confirmSignIn` | — | | Fix: `confirmSignIn` now throws when the user pool accepts the answer but the identity pool step after it fails (`GetId` or `GetCredentialsForIdentity`), with the identity pool's error, as `signIn` does (for example `AuthError.service` with `AWSCognitoAuthError.invalidParameter`), and logs the verbose line `Authorization reached an error state …` that `signIn` logs there. In 2.63.0 it never returned, and every Auth call queued behind it waited too | Apps with an identity pool that sign in with a challenge (MFA, a new password, custom auth, a factor selection) | — | | Upgrading from the AWS Mobile SDK (AWSMobileClient, Amplify v1): a signed-out record (`{"noCredentials":{}}`) in the plugin's saved session now stops the migration, as a login does: nothing migrates over it, the old keychain locations are cleared, and the plugin logs the info line `A signed-out session already exists, discarding legacy credentials`. In 2.63.0 the migration read that record as no session, so an old AWSMobileClient login migrated over it and signed the user back in. A sign-out through `AmplifyCognitoClient`'s default session saves that record, and so, with a keychain access group set, does the plugin's own sign-out (the access-group row below). Without an access group the plugin's sign-out deletes the record, so an app that uses only the plugin without one is not affected; with one, the plugin's sign-out already clears the old keychain locations, so only an old login that survived that clear, or was saved there since, is now dropped instead of migrated. A guest is still not a login: a guest saved over the signed-out record (by the plugin, an app extension or the client) lets an old login migrate over it again | Apps upgraded from AWSMobileClient or Amplify v1 that use `AmplifyCognitoClient`'s default session or a keychain access group, or roll back or forward between a client build and a plugin build | [Design](AGREED-DESIGN-AmplifyCognitoClient.md) §4.8 | -| Behaviour change: with a keychain access group set, ending a login through the plugin (`signOut`, local or global; `clearFederationToIdentityPool`; the sign-out after `deleteUser`) now saves the signed-out record, `{"noCredentials":{}}`, in the shared keychain service instead of deleting the session record: when the record holds a login (user pool tokens or a federated identity), or is already signed out. A guest's sign-out still deletes the guest's record, as does a sign-out over a record the plugin cannot read, and a sign-out with nothing saved writes nothing, so neither stops a login's migration. Those are the bytes `AmplifyCognitoClient`'s default session saves when it signs a session out, and every plugin release reads them as signed out (releases back to 2.10.0 decode them as `AmplifyCredentials.noCredentials`, the value for no saved session). The next sign-in replaces the record. The record then stops an app extension's first access-group migration (`migrateKeychainItems: true`), as a login does, so a stale login in the extension's own keychain is never moved in over a user who signed out; in 2.63.0 only a device record left by a user-pool sign-in stopped it. An extension's first access-group transition without migration counts the record too, as it counts a login, and keeps the extension's own old keychain items instead of clearing them. The AWSMobileClient (Amplify v1) migration treats the record as a signed-out session (the row above): it migrates nothing over it and clears the old locations, which the sign-out already clears, until a guest replaces the record; a guest `AmplifyCognitoClient`'s default session fetches over it clears those logins first, best effort. A plugin sign-out after a configuration change that copied the login now survives a rollback to the build with the earlier configuration, whose rule copies the signed-out record over the earlier copy, as after a sign-out through the client. Without an access group nothing changes: the record is deleted. `AmplifyCognitoClient.storedSessions(…, includingSignedOut: true)` in that access group now lists `.default` as a signed-out row after a plugin sign-out of a login (with no label or username unless the client saved them beside the record), where it listed no `.default` row; the default listing is unchanged. Logs under an access group: such a sign-out logs the keychain store's verbose `[KeychainStore]` lines for setting an item in place of those for removing one, and at the next launch the plugin no longer logs the info line `No existing session found.`, and its verbose credential-store line sends `completedOperation` with no credentials where it sent `throwError(itemNotFound)` | Apps with a keychain access group, especially with app extensions; apps that list stored sessions with `includingSignedOut: true` | [Decisions](auth-client-DECISIONS.md), decision 40 | +| Behaviour change: with a keychain access group set, ending a login through the plugin (`signOut`, local or global; `clearFederationToIdentityPool`; the sign-out after `deleteUser`) now saves the signed-out record, `{"noCredentials":{}}`, in the shared keychain service instead of deleting the session record: when the record holds a login (user pool tokens or a federated identity), or is already signed out. A guest's sign-out still deletes the guest's record, as does a sign-out over a record the plugin cannot read, and a sign-out with nothing saved writes nothing, so neither stops a login's migration. Those are the bytes `AmplifyCognitoClient`'s default session saves when it signs a session out, and every plugin release reads them as signed out (releases back to 2.10.0 decode them as `AmplifyCredentials.noCredentials`, the value for no saved session). The next sign-in replaces the record. The record then stops an app extension's first access-group migration (`migrateKeychainItems: true`), as a login does, so a stale login in the extension's own keychain is never moved in over a user who signed out; in 2.63.0 only a device record left by a user-pool sign-in stopped it. An extension's first access-group transition without migration counts the record too, as it counts a login, and keeps the extension's own old keychain items instead of clearing them. The AWSMobileClient (Amplify v1) migration treats the record as a signed-out session (the row above): it migrates nothing over it and clears the old locations, which the sign-out already clears, until a guest replaces the record; a guest `AmplifyCognitoClient`'s default session fetches over it clears those logins first, best effort. A plugin sign-out after a configuration change that copied the login now survives a rollback to the build with the earlier configuration, whose rule copies the signed-out record over the earlier copy, as after a sign-out through the client. Without an access group nothing changes: the record is deleted. `AmplifyCognitoClient.storedSessions(…, includingSignedOut: true)` in that access group now lists `.default` as a signed-out row after a plugin sign-out of a login (with no label or username unless the client saved them beside the record), where it listed no `.default` row; the default listing is unchanged. Logs under an access group: every plugin sign-out (a guest's and one with nothing saved included) first reads the record, which logs the keychain store's verbose `[KeychainStore]` lines for opening the shared service and retrieving an item (`Initialized keychain…`, `Started retrieving…`, then `Successfully retrieved…` or `No Keychain item found…`); a sign-out of a login then logs the lines for setting an item in place of those for removing one, and at the next launch the plugin no longer logs the info line `No existing session found.`, and its verbose credential-store line sends `completedOperation` with no credentials where it sent `throwError(itemNotFound)` | Apps with a keychain access group, especially with app extensions; apps that list stored sessions with `includingSignedOut: true` | [Decisions](auth-client-DECISIONS.md), decision 40 | | One new log line of the `InternalAWSCognitoAuth/.swift` form, the info line above in `MigrateLegacyCredentialStore`, so the state-machine actions' `\(#fileID)` sites go from 254 to 255, in 66 files | Anyone filtering or parsing plugin logs by file prefix | — | ## AmplifyCognitoClient (`@_spi(AmplifyExperimental)`, beta) From 13798c0871ef62bae4d55c75ffd8dfd70545cd07 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:53:44 -0400 Subject: [PATCH 12/57] docs(auth): say that the guest fetch after a sign-out clears AWSMobileClient and AWS SDK for iOS keychain data again --- AmplifyClients/AmplifyCognitoClient/README.md | 50 +++++++++++-------- .../Sources/Core/SessionCore+Session.swift | 5 +- .../SessionRecordStore+DefaultSession.swift | 8 +-- .../Sources/Storage/SessionRecordStore.swift | 6 ++- .../AGREED-DESIGN-AmplifyCognitoClient.md | 4 +- docs/design/RELEASE-NOTES-PENDING.md | 2 +- 6 files changed, 44 insertions(+), 31 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/README.md b/AmplifyClients/AmplifyCognitoClient/README.md index 4bd84b0354..54ea183c10 100644 --- a/AmplifyClients/AmplifyCognitoClient/README.md +++ b/AmplifyClients/AmplifyCognitoClient/README.md @@ -1350,26 +1350,31 @@ Signing `.default` out or purging it (so also `deleteUser` and `clearFederationT clears the AWSMobileClient (Amplify v1) logins, as the Auth plugin's sign-out does, so a plugin build's migration can't bring an old login back over the signed-out one. Like the plugin's, it runs even when there was nothing to sign out. It runs before the signed-out login is written, or before a purge deletes it, so a write or delete that fails -leaves no login behind. It is best effort: each service that fails to clear logs one warning under -`AmplifyCognitoClient.SessionRecordStore`, and it never fails the sign-out or the purge. The services cleared follow -the client's pools: the user pool's with a user pool, every identity pool's, and AWSMobileClient's own. The identity -pools' services (`.AWSCognitoCredentialsProvider.`) are the AWS SDK for iOS's -`AWSCognitoCredentialsProvider` caches, which AWSMobileClient uses, whatever code wrote them. They are cleared for -every identity pool the app's keychain holds one for, whatever the client is configured with: the client lists the -app's keychain services to find them, and the plugin's sign-out clears only the configured one's. Left behind, such a -login would come back under a plugin build as a guest holding the old user's identity and AWS credentials. If the -listing fails, a warning says so and only the configured identity pool's is cleared. An app that still uses the AWS -SDK for iOS's `AWSCognitoCredentialsProvider` loses its cached identity ID and AWS credentials for every identity -pool on any `.default` sign-out or purge, and a guest there gets a new identity ID. The other two are cleared whole, -as the plugin's sign-out clears them: the user pool service (`.AWSCognitoIdentityUserPool`) holds the logins -of every app client, and the AWSMobileClient service everything AWSMobileClient saved. The user pool service is -cleared only when the client's configuration has a user pool: an identity-pool-only configuration leaves it, and any -user pool login in it, which a plugin build with a user pool then migrates without AWS credentials, unless it is a -plugin release later than 2.63.0 and finds the signed-out login under its own configuration, as when its -rule copies the login on adding that user pool (see -[The default session uses the plugin's saved login](#the-default-session-uses-the-plugins-saved-login)). The -AWSMobileClient service is cleared on every `.default` sign-out or purge, a static one under another configuration -than the app's included. Named sessions never touch those logins. +leaves no login behind. The first `fetchAuthSession()` that saves a guest over `.default`'s signed-out login runs the +same clear again, before it writes the guest: a plugin build reads a guest as no session, so a login the sign-out +failed to clear would otherwise migrate over it. That second clear also removes whatever AWSMobileClient, or the AWS +SDK for iOS used beside the client, saved in those services since the sign-out. It is best effort: each service that +fails to clear logs one warning under `AmplifyCognitoClient.SessionRecordStore`, and it never fails the sign-out, the +purge or the fetch. The services cleared follow the client's pools: the user pool's with a user pool, every identity +pool's, and AWSMobileClient's own. The identity pools' services +(`.AWSCognitoCredentialsProvider.`) are the AWS SDK for iOS's `AWSCognitoCredentialsProvider` +caches, which AWSMobileClient uses, whatever code wrote them. They are cleared for every identity pool the app's +keychain holds one for, whatever the client is configured with: the client lists the app's keychain services to find +them, and the plugin's sign-out clears only the configured one's. Left behind, such a login would come back under a +plugin build as a guest holding the old user's identity and AWS credentials. If the listing fails, a warning says so +and only the configured identity pool's is cleared. An app that still uses the AWS SDK for iOS's +`AWSCognitoCredentialsProvider` loses its cached identity ID and AWS credentials for every identity pool on any +`.default` sign-out or purge, and on the first `fetchAuthSession()` that saves a guest after one, and a guest there +gets a new identity ID. So an identity ID the provider cached between the sign-out and that fetch, with an IoT policy +attached to it, say, is gone after the fetch. The other two are cleared whole, as the plugin's sign-out clears them: +the user pool service (`.AWSCognitoIdentityUserPool`) holds the logins of every app client, and the +AWSMobileClient service everything AWSMobileClient saved. The user pool service is cleared only when the client's +configuration has a user pool: an identity-pool-only configuration leaves it, and any user pool login in it, which a +plugin build with a user pool then migrates without AWS credentials, unless it is a plugin release later than 2.63.0 +and finds the signed-out login under its own configuration, as when its rule copies the login on adding that user pool +(see [The default session uses the plugin's saved login](#the-default-session-uses-the-plugins-saved-login)). The +AWSMobileClient service is cleared on every `.default` sign-out or purge, and at that guest +fetch, a static one under another configuration than the app's included. Named sessions never touch those logins. ### Hosted-UI sign-out @@ -1528,7 +1533,8 @@ Before you switch: take it too. - **Ship the plugin's migrations first.** The client does not migrate AWSMobileClient (Amplify v1) logins or move items between access groups: ship a plugin release first if your users may still need those migrations. It only - clears the AWSMobileClient logins when `.default` signs out or is purged, as the plugin's sign-out does. + clears the AWSMobileClient logins when `.default` signs out or is purged, as the plugin's sign-out does, and again + when `fetchAuthSession()` first saves a guest over `.default`'s signed-out login (see [Sign-out](#sign-out)). - **A migrated AWSMobileClient login without AWS credentials** fails the plugin's AWS credentials for a while. When the Auth plugin migrates an AWSMobileClient login that has user pool tokens but no saved AWS credentials (its user never fetched any, or an identity-pool-only client configuration's `.default` sign-out cleared them and kept the @@ -1846,7 +1852,7 @@ The client keeps the plugin's semantics except where listed here and in the sect | Events for `autoSignIn()` and `clearFederationToIdentityPool()` | No Hub event | `.signedIn` and `.signedOut` | | Storage failure | Reported as signed out | `.unavailable(reason)` / `storageUnavailable`, never signed out | | Another process's sign-in or sign-out in a shared access group | Seen at the next `fetchAuthSession` | Seen at the session's next refresh (`forceRefresh: true` forces one), or, by a signed-out session with an identity pool configured, at its next `fetchAuthSession` | -| AWSMobileClient (Amplify v1) and access-group keychain migrations | Run when the plugin configures | Not run: ship a plugin release that migrates first. `.default`'s sign-out and purge clear the AWSMobileClient logins | +| AWSMobileClient (Amplify v1) and access-group keychain migrations | Run when the plugin configures | Not run: ship a plugin release that migrates first. `.default`'s sign-out and purge clear the AWSMobileClient logins, and so does the first guest `fetchAuthSession()` saves after a sign-out | | Interrupted sign-in | Lost when the app closes | Saved, resumable for up to 15 minutes | | Hosted UI private session | `preferPrivateSession` defaults to `false` | `prefersEphemeralSession` defaults to `true` | | Hosted UI while another is up | Queues silently | `.browserBusy` by default; `.wait(timeout:)` to queue | diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+Session.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+Session.swift index 3a05732df3..cace380beb 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+Session.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+Session.swift @@ -186,7 +186,10 @@ extension SessionCore { /// (`SessionRecordStore.clearLegacyLogins()`), before the guest is written. The signed-out record is what stops a /// plugin build from migrating a login whose clear failed at sign-out (or that a release clearing after the /// signed-out write left when stopped in between); a guest counts as no session there, so once the guest replaces - /// it only the clear protects the user. Never over an absent record: a client-only app that never signed in keeps + /// it only the clear protects the user. It is the sign-out's whole clear, so it also removes whatever AWSMobileClient + /// or the AWS SDK for iOS, used beside the client, saved in those services since the sign-out: an app's + /// `AWSCognitoCredentialsProvider` loses the identity ID and AWS credentials it cached for any identity pool, and + /// gets a new identity ID. Never over an absent record: a client-only app that never signed in keeps /// those logins for a plugin build to migrate. That includes a sidecar with no shared record beside it (a label set /// before anyone signed in), which reads as a signed-out row with no version: the plugin's record is absent there. private nonisolated func acquireGuestOnce() async throws -> GuestAcquisition { diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift index d7ffa1f313..a052ca649e 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift @@ -48,7 +48,8 @@ import InternalAWSCognitoAuth // outlive a sign-out. They are cleared before the signed-out write or the purge's delete, so a failure there leaves // none. A signed-out record (`{"noCredentials":{}}`) stops that migration too, and the migration then clears them, but // only until a guest replaces it: the migration reads a guest as no session. So a guest the client fetches over a -// signed-out record (`acquireGuestOnce`) clears them again first, best effort; over no record it leaves them, as a +// signed-out record (`acquireGuestOnce`) clears them again first, best effort, with whatever AWSMobileClient or the AWS +// SDK for iOS, used beside the client, saved in those services since the sign-out; over no record it leaves them, as a // client-only app that never signed in keeps them for a plugin build to migrate. A sidecar alone (a label set before // anyone signed in) is no record. The signed-out record may be the plugin's: under an access group, the plugin's own // sign-out of a login saves the same bytes (plugin releases after 2.63.0; earlier ones delete the record), so a guest @@ -405,11 +406,12 @@ extension SessionRecordStore { /// configuration names would otherwise be migrated by a plugin build as a guest holding that user's identity and AWS /// credentials. Those services are the AWS SDK for iOS's `AWSCognitoCredentialsProvider` caches, whatever code /// wrote them, so an app still using that provider loses its cached identity ID and AWS credentials, and a guest - /// its identity. The user pool service is cleared whole, as the plugin's `ClearCredentialStore` clears it, only + /// its identity, at each clear: a sign-out, a purge, and the guest the client fetches over the signed-out record + /// after one. The user pool service is cleared whole, as the plugin's `ClearCredentialStore` clears it, only /// with a user pool configured: `.AWSCognitoIdentityUserPool` holds every app client's logins /// (`.*`). /// The AWSMobileClient service is cleared whole on any `.default` sign-out or purge, a static one under another - /// configuration than the app's included. + /// configuration than the app's included, and at that guest fetch. func clearLegacyLogins() { for store in legacyLoginStores() { do { diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore.swift index 6bd781efd1..0e686b1923 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore.swift @@ -81,8 +81,10 @@ struct SessionRecordStore: Sendable { /// keychain service: the plugin's own services for this store's pools (`legacyLoginServices(for:)`), then every /// other identity pool's `AWSCognitoCredentialsProvider` service of this app the keychain lists /// (`legacyLoginStores(for:bundleIdentifier:listServices:makeStore:)`). What `.default`'s sign-out and purge clear, - /// as the plugin's own sign-out does (`clearLegacyLogins()`). None unless given, so a store a test builds never - /// reaches them; `init(namespace:)` gives the real keychain's. + /// as the plugin's own sign-out does, and what the first guest the client fetches over `.default`'s signed-out + /// record clears again, with whatever AWSMobileClient or the AWS SDK for iOS saved there since the sign-out + /// (`clearLegacyLogins()`). None unless given, so a store a test builds never reaches them; `init(namespace:)` gives + /// the real keychain's. let legacyLoginStores: @Sendable () -> [any KeychainItemStoreBehavior] init( diff --git a/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md b/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md index 219a1bf3ef..0da66f90c4 100644 --- a/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md +++ b/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md @@ -557,7 +557,7 @@ let migrated = try AmplifyCognitoClient(configuration: config) // sessionId: . Development builds wrote `amplify.1..$default.session` records. They exist only on development and sandbox devices. Listing ignores them (§6), so they never show as a ghost row. -**What the client does not do: the plugin's migrations.** The plugin runs the AWSMobileClient legacy migration and the access-group migration at configure. The client does neither, so a client-only app gets no AWSMobileClient migration and no access-group migration. **It does clear the AWSMobileClient logins when `.default` signs out or is purged**, as the plugin's sign-out does (`ClearCredentialStore`), best effort: the legacy services `MigrateLegacyCredentialStore.legacyServiceKeys(for:)` names, which follow the client's pools (the user pool's with a user pool, the identity pool's with an identity pool, and AWSMobileClient's own), plus every other identity pool's credentials service the app's keychain holds, with no access group, as the plugin builds them, on every sign-out that ends the session, before the signed-out record is written, and before a purge's deletes. `deleteUser` and `clearFederationToIdentityPool` end in one of the two, so they clear them too. Each service that fails to clear logs one warning naming no one, under `AmplifyCognitoClient.SessionRecordStore`, and none fails the sign-out or the purge. It runs on a sign-out with nothing to sign out too, as the plugin's does (its sign-out from `.signedOut` still clears the credential store, and with it the legacy services). The identity pools' services (`.AWSCognitoCredentialsProvider.`) are the AWS SDK for iOS's `AWSCognitoCredentialsProvider` caches, which AWSMobileClient uses, whatever code wrote them. They are found by listing the app's keychain services (attributes only), so an AWSMobileClient login for an identity pool the client isn't configured with (another one, or any when the client has none) is cleared too, where the plugin's sign-out clears only the configured one's: left behind, a plugin build would migrate it as a guest holding the old user's identity ID and AWS credentials. An app that still uses that SDK provider directly loses its cached identity ID and AWS credentials for every identity pool on any `.default` sign-out or purge, and a guest there gets a new identity ID. A failed listing logs one warning and clears only the configured one's. The other two are cleared whole, as the plugin's sign-out clears them: the user pool service (`.AWSCognitoIdentityUserPool`) holds the logins of every app client, and the AWSMobileClient service everything AWSMobileClient saved. That happens on any `.default` sign-out or purge, a static one under another configuration than the app's included. Named sessions never touch these services. Without it, a plugin build's configure-time migration could write the old login back over a sign-out through the client: plugin releases up to 2.63.0 count a stored `{"noCredentials":{}}` as no session, and later ones count it as a login only until a guest replaces it. The plugin's access-group migration moves `.default`'s sidecar and challenge record (`amplify.1..$default.meta` and `.challenge`) with its own record, because they now belong to the plugin's session. For the same reason, the migration's destination clear and a transition wipe without migration remove them, and the check "does the shared service already hold the plugin's items?" ignores them, so they alone never block a migration. It still leaves named sessions where they are. +**What the client does not do: the plugin's migrations.** The plugin runs the AWSMobileClient legacy migration and the access-group migration at configure. The client does neither, so a client-only app gets no AWSMobileClient migration and no access-group migration. **It does clear the AWSMobileClient logins when `.default` signs out or is purged**, as the plugin's sign-out does (`ClearCredentialStore`), best effort: the legacy services `MigrateLegacyCredentialStore.legacyServiceKeys(for:)` names, which follow the client's pools (the user pool's with a user pool, the identity pool's with an identity pool, and AWSMobileClient's own), plus every other identity pool's credentials service the app's keychain holds, with no access group, as the plugin builds them, on every sign-out that ends the session, before the signed-out record is written, and before a purge's deletes. `deleteUser` and `clearFederationToIdentityPool` end in one of the two, so they clear them too. The first guest the client fetches over `.default`'s signed-out record runs the same clear again, before it writes the guest (`acquireGuestOnce`): the migration reads a guest as no session, so a login the sign-out failed to clear would otherwise migrate over it. That second clear also removes whatever AWSMobileClient, or the AWS SDK for iOS used beside the client, saved in those services since the sign-out. Each service that fails to clear logs one warning naming no one, under `AmplifyCognitoClient.SessionRecordStore`, and none fails the sign-out, the purge or the guest fetch. It runs on a sign-out with nothing to sign out too, as the plugin's does (its sign-out from `.signedOut` still clears the credential store, and with it the legacy services). The identity pools' services (`.AWSCognitoCredentialsProvider.`) are the AWS SDK for iOS's `AWSCognitoCredentialsProvider` caches, which AWSMobileClient uses, whatever code wrote them. They are found by listing the app's keychain services (attributes only), so an AWSMobileClient login for an identity pool the client isn't configured with (another one, or any when the client has none) is cleared too, where the plugin's sign-out clears only the configured one's: left behind, a plugin build would migrate it as a guest holding the old user's identity ID and AWS credentials. An app that still uses that SDK provider directly loses its cached identity ID and AWS credentials for every identity pool on any `.default` sign-out or purge, and on that first guest fetch after a sign-out, and a guest there gets a new identity ID: one the provider cached between the sign-out and that fetch, with an IoT policy attached to it, say, is gone after the fetch. A failed listing logs one warning and clears only the configured one's. The other two are cleared whole, as the plugin's sign-out clears them: the user pool service (`.AWSCognitoIdentityUserPool`) holds the logins of every app client, and the AWSMobileClient service everything AWSMobileClient saved. That happens on any `.default` sign-out or purge, and at that guest fetch, a static one under another configuration than the app's included. Named sessions never touch these services. Without it, a plugin build's configure-time migration could write the old login back over a sign-out through the client: plugin releases up to 2.63.0 count a stored `{"noCredentials":{}}` as no session, and later ones count it as a login only until a guest replaces it. The plugin's access-group migration moves `.default`'s sidecar and challenge record (`amplify.1..$default.meta` and `.challenge`) with its own record, because they now belong to the plugin's session. For the same reason, the migration's destination clear and a transition wipe without migration remove them, and the check "does the shared service already hold the plugin's items?" ignores them, so they alone never block a migration. It still leaves named sessions where they are. **Release order: the scoped wipe shipped with the client.** The plugin's scoped wipe shipped in the same release as the client, 2.63.0, as one unit, so no release has a client writing records beside a plugin that wipes them. Named sessions, the sidecar and the challenge record live in the plugin's keychain service, and the access-group transition of a plugin release before 2.63.0 wipes the whole service. The rule removed above is a different one: that the plugin's forward-compatible *reader* ship first. @@ -582,7 +582,7 @@ Where a row names a test (in brackets), it is pinned in both rollback matrices ( | Forward from the plugin to the client | `.default` reads the plugin's record in place. A token the plugin rotated is already in it, so the client refreshes with the newest token and there is no dead refresh token at rest (`testMatrix_rollForwardAfterAPluginRotation_resumesOnTheNewestToken`). A plugin sign-out is seen by the client (`testMatrix_oldPluginSignOut_isSeenByTheClient`) | | An app extension on the plugin and the app on the client, at rest | Each sees the other's latest login after a relaunch (`testMatrix_mixedBinaries_extensionOnThePluginAppOnTheClient_atRest`) | | The plugin and the client in one process, over `.default` | Not supported (§4.9). The plugin keeps its tokens in memory, so with rotation on a refresh by one side can break the other's token until relaunch | -| A client-only app upgraded from AWSMobileClient, or changing its access group | No migration. That is the plugin's job. A sign-out or purge of `.default` clears the AWSMobileClient logins, as the plugin's sign-out does | +| A client-only app upgraded from AWSMobileClient, or changing its access group | No migration. That is the plugin's job. A sign-out or purge of `.default` clears the AWSMobileClient logins, as the plugin's sign-out does, and the first guest the client fetches after a sign-out clears them again | | Back to a plugin release before 2.63.0 while AWSMobileClient logins are still on the device | **Caveat: the old login replaces whatever `.default` holds.** Releases before 2.63.0 migrate the legacy data at configure over any record, with no check, so a user signed in through the client is replaced by the old AWSMobileClient user. After a `.default` sign-out or purge through the client there is nothing left to migrate, except a user pool login after one under an identity-pool-only configuration, which leaves the user pool's service. An app moving from AWSMobileClient ships a plugin release that migrated first (the README says so) | | To 2.63.0 or a later plugin release, while AWSMobileClient logins are still on the device | A user signed in through the client is kept: the plugin clears the old logins without migrating them. A guest the client saved is replaced: an old user-pool or federated login, or an old guest, migrates over it, as over a guest the plugin saved, and the guest identity is lost. After a sign-out or purge of `.default` through the client there is nothing to migrate, except a user pool login left after one under an identity-pool-only configuration, which a plugin build with a user pool migrates unless it is later than 2.63.0 and finds the signed-out record under its own configuration. From the release after 2.63.0, a client sign-out's signed-out record (`{"noCredentials":{}}`) also stops the migration, as a login does (`MigrateLegacyCredentialStore.existingSession`, `testExecute_whenNewStoreHoldsASignedOutRecord_shouldNotMigrateAndShouldClearLegacyStores`), so a login that failed to clear doesn't sign the user back in while the signed-out record is there. From the release after 2.63.0, the plugin's own sign-out under an access group saves the same record, with the same effect (`testTheNextLaunchDropsAnAWSMobileClientLoginLeftAfterTheSignOutOnlyUnderAnAccessGroup`), and the client's guest fetch after it clears the logins first (`testAGuestTheClientFetchesAfterThePluginsSignOutClearsTheAWSMobileClientLogins`); without an access group the plugin's sign-out deletes the record, and the client's guest fetch after it, over an absent record, leaves them. A guest replaces it (the migration reads a guest as no session); the client's own guest fetch over the signed-out record clears the logins again first (`testAGuestOverASignedOutRecordClearsWhatTheSignOutLeft`), never over an absent record (`testAGuestOverAnAbsentRecordKeepsThem`). 2.63.0 reads the signed-out record as no session and migrates over it. The client clears the logins before the signed-out write or the purge's delete (`testASignOutWhoseWriteFailsHasClearedThem`, `testAPurgeWhoseDeleteFailsHasClearedThem`) | diff --git a/docs/design/RELEASE-NOTES-PENDING.md b/docs/design/RELEASE-NOTES-PENDING.md index e36bde757c..13d423670a 100644 --- a/docs/design/RELEASE-NOTES-PENDING.md +++ b/docs/design/RELEASE-NOTES-PENDING.md @@ -30,7 +30,7 @@ behaviour. | `confirmSignIn` after a TOTP setup whose last request failed with a step (`.resetPassword`, `.confirmSignUp`) throws `.invalidState` ("Cannot use confirmSignIn in the current state. Call signIn to start the sign-in again.") and ends the attempt, as the Auth plugin refuses it, where it never returned and held the session's sign-ins until a sign-out | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Sign-in steps and confirmSignIn" | | A refresh of a carried session that is waiting for its identity, whose save loses to another process's sign-out, answers as signed out: `fetchAuthSession`'s fields and both providers fail with `notSignedIn`, whose disposition is `.discard`. In 2.63.0 every field failed with `sessionExpired`, and the providers with the identity step's error (`CredentialsError.notConfigured` for a known refusal, else `.unknown`) | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Changing the configuration" | | A `.default` login copied from a configuration with an identity pool, after that identity pool is removed, refreshes only when its user pool tokens need it, whatever AWS credentials it kept. In 2.63.0, once those had expired, every token request, signed-in operation and `fetchAuthSession` refreshed it and rewrote the record | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Changing the configuration" | -| AWSMobileClient (Amplify v1) logins: a `.default` sign-out or purge clears them before it writes the signed-out login or deletes the login, so a failed write or delete, or an app stopped right after it, leaves none for a plugin build to migrate back. 2.63.0 cleared them after. `fetchAuthSession()` on `.default` that saves a guest over a signed-out record clears them again first, best effort, so a login a sign-out failed to clear is not migrated back once the guest replaces the signed-out record; not over an absent record, nor over a label set before anyone signed in (an app that never signed in through the client keeps them for the plugin to migrate), and never for a named session. **Rollback:** plugin releases before 2.63.0 migrate those logins over whatever `.default` holds, with no check; 2.63.0 keeps a user signed in through the client, but migrates over a guest and over a signed-out `.default`; later releases also keep a signed-out `.default`, until a guest replaces it (the Auth plugin row above) | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Rolling back and forward" | +| AWSMobileClient (Amplify v1) logins: a `.default` sign-out or purge clears them before it writes the signed-out login or deletes the login, so a failed write or delete, or an app stopped right after it, leaves none for a plugin build to migrate back. 2.63.0 cleared them after. `fetchAuthSession()` on `.default` that saves a guest over a signed-out record clears them again first, best effort, so a login a sign-out failed to clear is not migrated back once the guest replaces the signed-out record. That is the sign-out's whole clear, so it also removes what AWSMobileClient, or the AWS SDK for iOS used beside the client, saved in those services since the sign-out: an app that uses the SDK's `AWSCognitoCredentialsProvider` loses the identity ID and AWS credentials it cached for any identity pool then, and gets a new identity ID. The guest's clear never runs over an absent record, nor over a label set before anyone signed in (an app that never signed in through the client keeps them for the plugin to migrate), nor for a named session. **Rollback:** plugin releases before 2.63.0 migrate those logins over whatever `.default` holds, with no check; 2.63.0 keeps a user signed in through the client, but migrates over a guest and over a signed-out `.default`; later releases also keep a signed-out `.default`, until a guest replaces it (the Auth plugin row above) | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Rolling back and forward" | | A hosted-UI sign-out whose logout page is closed (by the user, `cancelWebUISignIn()` or `resetSystemSheet()`) on a `.default` session the Auth plugin saved as expired returns `.failed(.userCancelled)` and keeps the session, where 2.63.0 signed it out on the plugin's saved flag (`.complete`, or `.partial` with no `hostedUIError`). On a session a refresh found expired while it was in memory (until every handle and provider for it is released, or a relaunch), a closed page still signs out on this device, with `.partial`'s `hostedUIError` `.userCancelled`, but only while the login being signed out still has the refresh token found dead: a new login another app in the access group saved since is kept, `.failed(.userCancelled)`, where 2.63.0 revoked it and signed it out | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Hosted-UI sign-out" | | `cancelWebUISignIn()`: a cancelled passkey call throws `.userCancelled` once it has released the system sheet, as a hosted-UI sign-in does, so `systemSheetHolder` no longer names the session by then, where 2.63.0 answered while the sheet was still closing and an immediate retry got `browserBusy`. If the sheet isn't released within a second, the call throws anyway, and the sheet stays held until the passkey ceremony finishes | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Cancelling and recovering" | | Hosted UI: a second hosted-UI sign-in on a session whose first is still queued behind another session's sheet throws `browserBusy(holder:)` naming the session that holds the sheet ("Session "work" is already waiting for the system sheet, which session "home" holds."), where it named itself. One whose first is in progress but neither holds nor waits for the sheet (before it asks, or committing after) says "Session "work" already has a hosted-UI sign-in in progress.", adding ", and session "home" holds the system sheet" when another session holds it; `holder` is then that session, or the session itself. A sign-out, purge or user deletion that lands just as a hosted-UI attempt begins makes that sign-in throw the sign-in-cancelled `.invalidState` ("The sign-in was cancelled: the session was signed out, purged or deleted while it was in progress."), as one landing at any other point does, where it threw `.userCancelled`. A hosted-UI response whose tokens fail the client's checks (another app client or user pool, another sign-in's nonce, no single user) suggests "Sign in again. If it keeps failing, check that the configuration's user pool, app client and hosted UI domain belong together.", where it suggested `prompt: [.login]`, which cannot change what the tokens say; a user other than the one `identityExpectation` asked for still suggests it | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Hosted UI" | From 9fa519148d232fa75157fd9c6e6fab622666ba39 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:53:55 -0400 Subject: [PATCH 13/57] docs(auth): say that a guest over an identity-pool-only sign-out leaves the user pool login for a plugin build to migrate --- AmplifyClients/AmplifyCognitoClient/README.md | 8 +++++--- docs/design/AGREED-DESIGN-AmplifyCognitoClient.md | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/README.md b/AmplifyClients/AmplifyCognitoClient/README.md index 54ea183c10..93346ee455 100644 --- a/AmplifyClients/AmplifyCognitoClient/README.md +++ b/AmplifyClients/AmplifyCognitoClient/README.md @@ -1372,8 +1372,10 @@ AWSMobileClient service everything AWSMobileClient saved. The user pool service configuration has a user pool: an identity-pool-only configuration leaves it, and any user pool login in it, which a plugin build with a user pool then migrates without AWS credentials, unless it is a plugin release later than 2.63.0 and finds the signed-out login under its own configuration, as when its rule copies the login on adding that user pool -(see [The default session uses the plugin's saved login](#the-default-session-uses-the-plugins-saved-login)). The -AWSMobileClient service is cleared on every `.default` sign-out or purge, and at that guest +(see [The default session uses the plugin's saved login](#the-default-session-uses-the-plugins-saved-login)), and only +until a guest replaces that signed-out login. The guest's clear leaves the user pool service too, so with an identity +pool that allows guests, the next `fetchAuthSession()` on `.default` leaves that login for such a plugin build to +migrate over the guest. The AWSMobileClient service is cleared on every `.default` sign-out or purge, and at that guest fetch, a static one under another configuration than the app's included. Named sessions never touch those logins. ### Hosted-UI sign-out @@ -1619,7 +1621,7 @@ never log it. The warning is temporary: it goes when the plugin uses the client | Back to a plugin release before 2.51.0, with refresh-token rotation on | That plugin can't refresh on the app client at all, whatever the client did | | Forward from the plugin to the client, with refresh-token rotation on, after the plugin rotated the token | **The user stays signed in**: `.default` reads the plugin's record in place, so the client refreshes with the newest token the plugin saved | | Back to a plugin release before 2.63.0 while AWSMobileClient (Amplify v1) logins are still on the device | **The plugin migrates the old login over whatever `.default` holds**: releases before 2.63.0 write it with no check, so a user signed in through the client is replaced by the old AWSMobileClient user. A sign-out or purge of `.default` through the client clears those logins, so after one there is nothing to migrate, except a user pool login after one under an identity-pool-only configuration, which leaves the user pool's service (see [Sign-out](#sign-out)). They are cleared before the signed-out login is written or the purge deletes it, so a failed write or delete leaves none. Ship a plugin release that has migrated them before you move to the client. A migrated login with no AWS credentials saved fails the plugin's unforced AWS credentials until a forced refresh, or until its tokens next need refreshing | -| To 2.63.0 or a later plugin release, while AWSMobileClient (Amplify v1) logins are still on the device | **A user signed in through the client is kept**: the plugin clears the old logins without migrating them. **A guest the client saved is replaced**: an old user-pool or federated login, or an old guest, migrates over it, as over a guest the plugin saved, and the guest identity is lost. After a sign-out or purge of `.default` through the client there is nothing to migrate, except a user pool login left after one under an identity-pool-only configuration, which a plugin build with a user pool migrates unless it is later than 2.63.0 and finds the signed-out login under its own configuration (see [Sign-out](#sign-out)). A sign-out through the client also leaves a signed-out login. On plugin releases later than 2.63.0 it stops the migration as a signed-in user does: the plugin clears the old logins without migrating them, so the user stays signed out even if clearing them at sign-out failed, until a guest replaces the signed-out login. A guest counts as no session there. The client's own `fetchAuthSession()` clears the old logins again, best effort, before it saves a guest over `.default`'s signed-out login, so after that only a second failed clear, or a guest the plugin or an app extension saved, leaves one to migrate. 2.63.0 reads a signed-out login as no session and migrates over it, so there an old login that failed to clear signs the old user back in; so does one 2.63.0's client left, since it cleared them only after writing the signed-out login, and an app stopped in between kept them. A purge, `deleteUser` included, leaves no login, so after one only the clear protects the user. A migrated login with no AWS credentials saved fails the plugin's unforced AWS credentials until a forced refresh (`fetchAuthSession` with `forceRefresh: true`), or until its tokens next need refreshing | +| To 2.63.0 or a later plugin release, while AWSMobileClient (Amplify v1) logins are still on the device | **A user signed in through the client is kept**: the plugin clears the old logins without migrating them. **A guest the client saved is replaced**: an old user-pool or federated login, or an old guest, migrates over it, as over a guest the plugin saved, and the guest identity is lost. After a sign-out or purge of `.default` through the client there is nothing to migrate, except a user pool login left after one under an identity-pool-only configuration, which a plugin build with a user pool migrates unless it is later than 2.63.0 and finds the signed-out login under its own configuration, until a guest replaces it (see [Sign-out](#sign-out)). A sign-out through the client also leaves a signed-out login. On plugin releases later than 2.63.0 it stops the migration as a signed-in user does: the plugin clears the old logins without migrating them, so the user stays signed out even if clearing them at sign-out failed, until a guest replaces the signed-out login. A guest counts as no session there. The client's own `fetchAuthSession()` clears the old logins again, best effort, before it saves a guest over `.default`'s signed-out login, so after that only a second failed clear, a guest the plugin or an app extension saved, or, under an identity-pool-only configuration, the user pool login neither clear touches leaves one to migrate: a plugin build with a user pool carries the guest to its configuration, reads it as no session and migrates that login over it. 2.63.0 reads a signed-out login as no session and migrates over it, so there an old login that failed to clear signs the old user back in; so does one 2.63.0's client left, since it cleared them only after writing the signed-out login, and an app stopped in between kept them. A purge, `deleteUser` included, leaves no login, so after one only the clear protects the user. A migrated login with no AWS credentials saved fails the plugin's unforced AWS credentials until a forced refresh (`fetchAuthSession` with `forceRefresh: true`), or until its tokens next need refreshing | ## Changing the configuration diff --git a/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md b/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md index 0da66f90c4..e2e516564d 100644 --- a/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md +++ b/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md @@ -584,7 +584,7 @@ Where a row names a test (in brackets), it is pinned in both rollback matrices ( | The plugin and the client in one process, over `.default` | Not supported (§4.9). The plugin keeps its tokens in memory, so with rotation on a refresh by one side can break the other's token until relaunch | | A client-only app upgraded from AWSMobileClient, or changing its access group | No migration. That is the plugin's job. A sign-out or purge of `.default` clears the AWSMobileClient logins, as the plugin's sign-out does, and the first guest the client fetches after a sign-out clears them again | | Back to a plugin release before 2.63.0 while AWSMobileClient logins are still on the device | **Caveat: the old login replaces whatever `.default` holds.** Releases before 2.63.0 migrate the legacy data at configure over any record, with no check, so a user signed in through the client is replaced by the old AWSMobileClient user. After a `.default` sign-out or purge through the client there is nothing left to migrate, except a user pool login after one under an identity-pool-only configuration, which leaves the user pool's service. An app moving from AWSMobileClient ships a plugin release that migrated first (the README says so) | -| To 2.63.0 or a later plugin release, while AWSMobileClient logins are still on the device | A user signed in through the client is kept: the plugin clears the old logins without migrating them. A guest the client saved is replaced: an old user-pool or federated login, or an old guest, migrates over it, as over a guest the plugin saved, and the guest identity is lost. After a sign-out or purge of `.default` through the client there is nothing to migrate, except a user pool login left after one under an identity-pool-only configuration, which a plugin build with a user pool migrates unless it is later than 2.63.0 and finds the signed-out record under its own configuration. From the release after 2.63.0, a client sign-out's signed-out record (`{"noCredentials":{}}`) also stops the migration, as a login does (`MigrateLegacyCredentialStore.existingSession`, `testExecute_whenNewStoreHoldsASignedOutRecord_shouldNotMigrateAndShouldClearLegacyStores`), so a login that failed to clear doesn't sign the user back in while the signed-out record is there. From the release after 2.63.0, the plugin's own sign-out under an access group saves the same record, with the same effect (`testTheNextLaunchDropsAnAWSMobileClientLoginLeftAfterTheSignOutOnlyUnderAnAccessGroup`), and the client's guest fetch after it clears the logins first (`testAGuestTheClientFetchesAfterThePluginsSignOutClearsTheAWSMobileClientLogins`); without an access group the plugin's sign-out deletes the record, and the client's guest fetch after it, over an absent record, leaves them. A guest replaces it (the migration reads a guest as no session); the client's own guest fetch over the signed-out record clears the logins again first (`testAGuestOverASignedOutRecordClearsWhatTheSignOutLeft`), never over an absent record (`testAGuestOverAnAbsentRecordKeepsThem`). 2.63.0 reads the signed-out record as no session and migrates over it. The client clears the logins before the signed-out write or the purge's delete (`testASignOutWhoseWriteFailsHasClearedThem`, `testAPurgeWhoseDeleteFailsHasClearedThem`) | +| To 2.63.0 or a later plugin release, while AWSMobileClient logins are still on the device | A user signed in through the client is kept: the plugin clears the old logins without migrating them. A guest the client saved is replaced: an old user-pool or federated login, or an old guest, migrates over it, as over a guest the plugin saved, and the guest identity is lost. After a sign-out or purge of `.default` through the client there is nothing to migrate, except a user pool login left after one under an identity-pool-only configuration, which a plugin build with a user pool migrates unless it is later than 2.63.0 and finds the signed-out record under its own configuration, until a guest replaces that record: the client's guest fetch over it leaves the user pool service too, and the plugin's rule carries the guest, which the migration reads as no session. From the release after 2.63.0, a client sign-out's signed-out record (`{"noCredentials":{}}`) also stops the migration, as a login does (`MigrateLegacyCredentialStore.existingSession`, `testExecute_whenNewStoreHoldsASignedOutRecord_shouldNotMigrateAndShouldClearLegacyStores`), so a login that failed to clear doesn't sign the user back in while the signed-out record is there. From the release after 2.63.0, the plugin's own sign-out under an access group saves the same record, with the same effect (`testTheNextLaunchDropsAnAWSMobileClientLoginLeftAfterTheSignOutOnlyUnderAnAccessGroup`), and the client's guest fetch after it clears the logins first (`testAGuestTheClientFetchesAfterThePluginsSignOutClearsTheAWSMobileClientLogins`); without an access group the plugin's sign-out deletes the record, and the client's guest fetch after it, over an absent record, leaves them. A guest replaces it (the migration reads a guest as no session); the client's own guest fetch over the signed-out record clears the logins again first (`testAGuestOverASignedOutRecordClearsWhatTheSignOutLeft`), never over an absent record (`testAGuestOverAnAbsentRecordKeepsThem`). 2.63.0 reads the signed-out record as no session and migrates over it. The client clears the logins before the signed-out write or the purge's delete (`testASignOutWhoseWriteFailsHasClearedThem`, `testAPurgeWhoseDeleteFailsHasClearedThem`) | The bytes the client writes (each `AmplifyCredentials` shape, `noCredentials` and `authConfiguration`) decode with the plugin's own types, in releases before 2.63.0 too, and the client's items have the plugin's keychain attributes. From 0a9b684b8da45c42326b5cf420e74f6948c146a3 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:54:16 -0400 Subject: [PATCH 14/57] docs(auth): say the signed-out record stops the AWSMobileClient migration only until a guest replaces it --- docs/design/auth-client-DECISIONS.md | 2 +- docs/design/issues/rollback-behaviours.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/design/auth-client-DECISIONS.md b/docs/design/auth-client-DECISIONS.md index cc21c17b5d..8a78d24f11 100644 --- a/docs/design/auth-client-DECISIONS.md +++ b/docs/design/auth-client-DECISIONS.md @@ -392,7 +392,7 @@ because the plugin behaves the same way; the third is the client's own: - **A rollback to a plugin release before 2.63.0 while AWSMobileClient logins are still on the device** replaces whatever `.default` holds with the old AWSMobileClient user: those releases migrate that data at configure with no check. 2.63.0 keeps a user signed in through the client, but migrates over a guest and over a signed-out - `.default`; later releases also keep a signed-out `.default` (#36). The plugin's own migration deletes those logins at its first configure, so a plugin-only app has none + `.default`; later releases also keep a signed-out `.default` (#36), until a guest replaces it. The plugin's own migration deletes those logins at its first configure, so a plugin-only app has none left; a client-only app keeps them until `.default` signs out or is purged. An app moving from AWSMobileClient ships a plugin release that migrated them first. diff --git a/docs/design/issues/rollback-behaviours.md b/docs/design/issues/rollback-behaviours.md index 7140f3b7a8..e858a46f8c 100644 --- a/docs/design/issues/rollback-behaviours.md +++ b/docs/design/issues/rollback-behaviours.md @@ -52,8 +52,9 @@ client no longer writes a `$default` session record. No plugin release reads a c back in from one. A user signed out through the client stays signed out on every plugin release, because the shared record holds `{"noCredentials":{}}` (`testMatrix_signedOutUserIsNeverSignedBackIn`, `testMatrix_clientSignOut_readByEveryPluginAsSignedOut`), unless an AWSMobileClient login left on the device migrates -over it (releases up to 2.63.0 migrate over a signed-out record; design §4.8). One caveat remains, from the plugin's own -rule: after a configuration change that copied the login, a purge (or the plugin's own sign-out without an access group, +over it (releases up to 2.63.0 migrate over a signed-out record; later ones keep the user signed out until a guest +replaces it, the client's own `fetchAuthSession()`'s, the plugin's or an app extension's; design §4.8). One caveat remains, from the +plugin's own rule: after a configuration change that copied the login, a purge (or the plugin's own sign-out without an access group, or on plugin releases up to 2.63.0, which deletes) leaves the earlier configuration's copy, which a rollback to the build with that configuration reads (`testMatrix_purgeAfterACarry_leavesTheEarlierConfigurationsCopy_caveat`). A second caveat, also the plugin's own rule: a later configuration change that the rule does not carry deletes the signed-out From 308d68bd524be8a0cd2cd22950df44e83bd11d64 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:54:31 -0400 Subject: [PATCH 15/57] docs(auth): describe when confirmSignIn refuses an answer to a reset-password or confirm-sign-up step --- AmplifyClients/AmplifyCognitoClient/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/README.md b/AmplifyClients/AmplifyCognitoClient/README.md index 93346ee455..be8fa56e17 100644 --- a/AmplifyClients/AmplifyCognitoClient/README.md +++ b/AmplifyClients/AmplifyCognitoClient/README.md @@ -722,8 +722,8 @@ or `.done`. | `.confirmSignInWithPassword` | The password (after choosing `.password` or `.passwordSRP`). A wrong password ends the sign-in: call `signIn` again | | `.continueSignInWithFirstFactorSelection(factors)` | `AuthClientFactorType..challengeResponse`. Anything else is `.validation`, and so is `"WEB_AUTHN"` when `factors` does not offer it | | `.confirmSignInWithCustomChallenge(info)` | Your custom answer | -| `.resetPassword(info)` | Not answered here: reset the password, then sign in again. After a TOTP setup, `confirmSignIn` refuses an answer with `.invalidState` | -| `.confirmSignUp(info)` | Not answered here: confirm the sign-up, then sign in again. After a TOTP setup, `confirmSignIn` refuses an answer with `.invalidState` | +| `.resetPassword(info)` | Not answered here: reset the password, then sign in again. `confirmSignIn` throws `.invalidState` for this step and ends the attempt, except after Cognito refused a challenge answer or a TOTP code with it, where the answer is sent again | +| `.confirmSignUp(info)` | Not answered here: confirm the sign-up, then sign in again. `confirmSignIn` throws `.invalidState` for this step and ends the attempt, except after Cognito refused a challenge answer or a TOTP code with it, where the answer is sent again | | `.done` | Nothing: signed in | `AuthClientMFAType.rawValue` (`"sms"`, `"totp"`, `"email"`) is not a valid answer. Use `challengeResponse`. From 25ec94129c4b3d6ef8489090d9f3276b4e2a14c4 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:54:42 -0400 Subject: [PATCH 16/57] docs(auth): say browserBusy can name the session itself when no session holds the sheet --- AmplifyClients/AmplifyCognitoClient/README.md | 2 +- docs/design/AGREED-DESIGN-AmplifyCognitoClient.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/README.md b/AmplifyClients/AmplifyCognitoClient/README.md index be8fa56e17..a58ba41e18 100644 --- a/AmplifyClients/AmplifyCognitoClient/README.md +++ b/AmplifyClients/AmplifyCognitoClient/README.md @@ -1438,7 +1438,7 @@ optional `underlyingError`. | `notSignedIn` | No user pool user: signed out, guest, federated, or waiting on a challenge | Sign in; on a federated session, clear the federation or sign out first | | `invalidSessionID` | Empty, too long, or a character outside `A-Z a-z 0-9 _ -` | Use a valid ID | | `challengeExpired` | Cognito no longer accepts the challenge | Call `signIn` again | -| `browserBusy(holder:)` | The one system sheet is held, or a `.wait(timeout:)` expired | Finish or cancel the holder's sheet | +| `browserBusy(holder:)` | The one system sheet is held, or a `.wait(timeout:)` expired, or this session already has a hosted-UI sign-in in progress | Finish or cancel the holder's sheet. When `holder` is this session and no session holds the sheet (`systemSheetHolder` is `nil`), there is no sheet to finish: wait for the earlier call, or cancel its task | | `sessionConfigurationMismatch(SessionID)` | The session is live under a different configuration | Release its handles and providers, or use the same configuration | | `validation(field:)` | An argument was invalid; `field` names it | Fix the argument | | `service(AuthClientServiceErrorCode?)` | Cognito rejected the request, with the code if the client recognises it; with no code, a Cognito internal error (`InternalErrorException`) or an error status the SDK doesn't model (the description has the status and message), in every call, or a network problem stopped the request, which may or may not have reached Cognito (offline, a failed DNS or host lookup, a refused or lost connection, a timeout, or a failed secure connection), or its answer couldn't be read | Depends on the code; with no code, read the suggestion: retry later, or check the request against the service's message (a 4xx status). In a `.partial` sign-out, the revoke's or global sign-out's failure is not retried | diff --git a/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md b/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md index e2e516564d..83c4be01da 100644 --- a/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md +++ b/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md @@ -1127,7 +1127,7 @@ do { } ``` -**New API:** `WebUIOptions`, with `whenBrowserBusy` (and its nested `BrowserBusyPolicy`), `prefersEphemeralSession`, `prompt`, and `identityExpectation` (`.none`, `.matches(_:)` or `.distinctFromOtherSessions`); `cancelWebUISignIn()` and `resetSystemSheet()`; and the `browserBusy(holder:)` and `unexpectedIdentity(expected:returned:)` errors. `browserBusy` carries the session ID holding the lock so an app can name it in the message. Only a hosted-UI sign-in can queue with `.wait(timeout:)`: a passkey sheet or the sign-out page fails at once while another session holds the sheet. `WebUIOptions` is declared just above rather than in 14.6, because the option and the call it modifies read better together; the errors are in 14.8. +**New API:** `WebUIOptions`, with `whenBrowserBusy` (and its nested `BrowserBusyPolicy`), `prefersEphemeralSession`, `prompt`, and `identityExpectation` (`.none`, `.matches(_:)` or `.distinctFromOtherSessions`); `cancelWebUISignIn()` and `resetSystemSheet()`; and the `browserBusy(holder:)` and `unexpectedIdentity(expected:returned:)` errors. `browserBusy` carries the session ID holding the lock so an app can name it in the message, except for a second hosted-UI sign-in of a session whose first is still in progress while no session holds the sheet: `holder` is then the session itself, and there is no sheet to finish, so the app waits for the earlier call or cancels its task. Only a hosted-UI sign-in can queue with `.wait(timeout:)`: a passkey sheet or the sign-out page fails at once while another session holds the sheet. `WebUIOptions` is declared just above rather than in 14.6, because the option and the call it modifies read better together; the errors are in 14.8. **Why `.fail` is the default.** A browser sign-in needs the foreground and the user's attention. Queuing one silently means the user finishes one sign-in and a *second* browser appears unprompted, which reads as a bug. Failing fast lets the app say something useful. `.wait` exists for apps that genuinely want to serialize two sign-ins back to back, and it is bounded, because an unbounded wait turns a stuck operation into a hang with no error. @@ -1594,7 +1594,7 @@ The shipped `@_spi(AmplifyExperimental)` surface differs from 14.1 to 14.7 as fo | `notSignedIn` | The session has no user pool user: signed out, a guest, federated, or (for the providers and the operations on the signed-in user) waiting on a challenge, whatever the session held. Never a silent guest fallback. | | `invalidSessionID` | Malformed session ID: empty, too long, or a character outside `[A-Za-z0-9_-]`. | | `challengeExpired` | Cognito no longer accepts the sign-in's challenge session, so restart sign-in. | -| `browserBusy(holder:)` | The one system sheet across the client's sessions (hosted-UI sign-in or sign-out page, or passkey sheet) is held, or a `.wait(timeout:)` expired first. The Auth plugin's sheets don't take part. | +| `browserBusy(holder:)` | The one system sheet across the client's sessions (hosted-UI sign-in or sign-out page, or passkey sheet) is held, or a `.wait(timeout:)` expired first, or this session already has a hosted-UI sign-in in progress. `holder` is the session holding the sheet; when none does and this session's earlier hosted-UI sign-in is still in progress, it is this session, with no sheet to finish: wait for the earlier call, or cancel its task. The Auth plugin's sheets don't take part. | | `sessionConfigurationMismatch(SessionID)` | A session ID already live under a different user pool, identity pool, keychain access group or settings. Thrown instead of silently attaching - see D.1. | | `validation(field:)` | An argument the caller passed is invalid. Names the offending field. | | `service(AuthClientServiceErrorCode?)` | Cognito rejected the request; the code when the client recognises the exception. `AuthClientServiceErrorCode` is a plain value this error carries: it is not an `Error`, so it is never thrown or caught on its own, and its `errorDescription` is an ordinary property. Match it inside the case: `catch AuthClientError.service(.userNotConfirmed?, _, _, _)`. A Cognito internal error (`InternalErrorException`) and an error status the SDK does not model are `service` with no code too, in every call (the plugin's `unknown`), with a recovery suggestion to retry (a 5xx, an internal error) or naming the refusal (a 4xx); in a `.partial` sign-out, the revoke's or global sign-out's suggestion says it is not retried (the global sign-out's without the network hedge when Cognito failed it itself), and a sign-out that could not finish carries the failure as any other call maps it. The engine keeps nothing under an internal error, so it carries the SDK exception: in the sign-in flows' state-machine errors (`SignInError.service`, `AuthenticationError.service` from `ThrowSignInError`, the passkey sign-in's `WebAuthnError.service`), or beside the engine error (a revoke's and global sign-out's failure `cause`, the deletion's `.error(_, cause:)`, the passkey calls' `WebAuthnCredentialOperations.Failure`). The client maps with it. A network failure (offline, a failed DNS or host lookup, a refused or lost connection, a timeout, or a failed secure connection), whose request may or may not have reached Cognito, is `service` with no code in every call, with the client's network description and suggestion and the network error itself underneath (the `URLError`, or the CFNetwork or POSIX error); any other transport failure is `service` with no code too, never an empty `unknown` (an unreadable response, or another URL loading failure such as an App Transport Security refusal); in a `.partial` sign-out, a revoke or global sign-out that failed that way keeps the sign-out's context in its suggestion: it is not retried. | From 846d43518152e8bd45da7d2394b03178fb93b0d5 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:55:19 -0400 Subject: [PATCH 17/57] docs(auth): say only a partial sign-out's service error says it won't be retried, and that a stored-session sign-out is never global --- .../Sources/AmplifyCognitoClient+StoredSessions.swift | 6 +++--- .../AmplifyCognitoClient/Sources/AuthClientError.swift | 3 ++- docs/design/auth-client-DECISIONS.md | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/AmplifyCognitoClient+StoredSessions.swift b/AmplifyClients/AmplifyCognitoClient/Sources/AmplifyCognitoClient+StoredSessions.swift index 5fd8e85fae..be089fb17b 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/AmplifyCognitoClient+StoredSessions.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/AmplifyCognitoClient+StoredSessions.swift @@ -105,9 +105,9 @@ public extension AmplifyCognitoClient { /// - Returns: `.complete`, or `.partial` with the revoke's failure or the hosted UI's cookie left behind, /// when the session is signed out on this device. `.failed`, and the session is still signed in: /// `storageUnavailable` if storage could not be read or written, or `storageUnavailable(.interrupted)` if - /// the record kept changing under the sign-out (the first server-side failure, if any, the global sign-out's - /// first, is its underlying error, mapped as any other call maps it); `sessionConfigurationMismatch` if a client for this session is live in this process with a - /// different user pool or identity pool, whose records this call would change (sign it out through that + /// the record kept changing under the sign-out (the revoke's failure, if any, is its underlying error, mapped as + /// any other call maps it); `sessionConfigurationMismatch` if a client for this session is live in this process + /// with a different user pool or identity pool, whose records this call would change (sign it out through that /// client); `invalidState` if a different user signed in meanwhile; `unknown` with an underlying /// `CancellationError` if the calling task was cancelled before anything was revoked. static func signOutStoredSession( diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/AuthClientError.swift b/AmplifyClients/AmplifyCognitoClient/Sources/AuthClientError.swift index 0040855ea7..599769bea0 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/AuthClientError.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/AuthClientError.swift @@ -72,7 +72,8 @@ public enum AuthClientError { /// internal error (`InternalErrorException`, from either pool), whose suggestion says to retry, and an error /// status the SDK does not model, whose suggestion says to retry (a 5xx) or that Cognito refused the request with /// an error this client does not recognise. The description has Cognito's message, and the status when there is - /// one. Under a sign-out, the revoke's or global sign-out's suggestion says instead that it will not be retried. + /// one. In a `.partial` sign-out, the revoke's or global sign-out's suggestion says instead that it will not be + /// retried. case service(AuthClientServiceErrorCode?, ErrorDescription, RecoverySuggestion, Error? = nil) /// The caller is not allowed to perform the operation: for example, a wrong password, or a diff --git a/docs/design/auth-client-DECISIONS.md b/docs/design/auth-client-DECISIONS.md index 8a78d24f11..105aace8a0 100644 --- a/docs/design/auth-client-DECISIONS.md +++ b/docs/design/auth-client-DECISIONS.md @@ -585,8 +585,8 @@ reads the signed-out record as no session. Where the client saw the failure's cause, the description and recovery suggestion are the ones it gave before, and only the case changes. The engine kept nothing under an internal error, so it now carries the service's exception beside the engine error for `deleteUser`, a sign-out's revoke and global sign-out, and the passkey credential calls, -which lose the engine's "report a bug" suggestion. Under a sign-out, the revoke and the global sign-out keep the -sign-out's not-retried suggestion, as a transport failure does: the sign-out has already finished on this device, so +which lose the engine's "report a bug" suggestion. In a `.partial` sign-out, the revoke and the global sign-out keep +the sign-out's not-retried suggestion, as a transport failure does: the sign-out has already finished on this device, so "retry" does not fit. The plugin reads only the engine error, so its errors are unchanged. **#38.** Rewriting the marker to the purge's own configuration before deleting it would close the window, but From 0d56e90a4007cbd7d040d19a25169e6af3cab619 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:05:13 -0400 Subject: [PATCH 18/57] test(auth): pin that a guest over the default session's signed-out record clears the AWSMobileClient logins before it is written --- .../DefaultSessionGuestLegacyLoginTests.swift | 68 +++++++++++++++---- .../DefaultSessionLegacyLoginTests.swift | 10 +-- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/DefaultSessionGuestLegacyLoginTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/DefaultSessionGuestLegacyLoginTests.swift index 5d2725f02c..60a7d07191 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/DefaultSessionGuestLegacyLoginTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/DefaultSessionGuestLegacyLoginTests.swift @@ -59,9 +59,9 @@ final class DefaultSessionGuestLegacyLoginTests: XCTestCase { assertLegacyLogins(present: false, "after the guest") } - /// - Given: `.default`'s record holding `{"noCredentials":{}}`, as a sign-out stopped between its signed-out write - /// and its clear left it, or a plugin sign-out under an access group whose clear failed, and AWSMobileClient - /// logins + /// - Given: `.default`'s record holding `{"noCredentials":{}}`, as a sign-out whose clear failed left it (or + /// 2.63.0's sign-out, which cleared after its signed-out write, stopped in between), or a plugin sign-out under an + /// access group whose clear failed, and AWSMobileClient logins /// - When: `.default`'s session is fetched, which fetches a guest /// - Then: /// - the record holds a guest, and the logins are gone @@ -76,6 +76,32 @@ final class DefaultSessionGuestLegacyLoginTests: XCTestCase { assertLegacyLogins(present: false) } + /// The clear comes before the guest's write, so an app stopped between the two never leaves a guest, which a plugin + /// build reads as no session, beside a login the sign-out failed to clear. + /// + /// - Given: `.default`'s record holding `{"noCredentials":{}}`, AWSMobileClient logins, and AWSMobileClient stores + /// that record what `.default`'s saved login holds whenever they are cleared + /// - When: `.default`'s session is fetched, which fetches a guest + /// - Then: + /// - every store was cleared once, each time with the saved login still `{"noCredentials":{}}`, and the record + /// then holds a guest + func testAGuestClearsThemBeforeItIsWritten() async throws { + harness.keychain.put(PluginRecordFixtures.noCredentials, pluginAccount) + seedLegacyLogins() + let order = ClearOrder() + let client = try defaultClient(observing: order) + + _ = try await client.fetchAuthSession() + + XCTAssertEqual(order.savedLoginsSeen.count, services.count) + XCTAssertTrue( + order.savedLoginsSeen.allSatisfy { $0 == PluginRecordFixtures.noCredentials }, + "the guest was written before the AWSMobileClient logins were cleared" + ) + XCTAssertEqual(try harness.storedRecord(.default)?.kind, .guest) + assertLegacyLogins(present: false) + } + /// - Given: nothing saved under `.default`, as in a client-only app that never signed in, and AWSMobileClient logins /// - When: `.default`'s session is fetched, which fetches a guest /// - Then: @@ -150,24 +176,33 @@ final class DefaultSessionGuestLegacyLoginTests: XCTestCase { } } - private func defaultClient(failingWhile failing: FailingSwitch? = nil) throws -> AmplifyCognitoClient { - try client(.default, failingWhile: failing) + private func defaultClient( + failingWhile failing: FailingSwitch? = nil, + observing order: ClearOrder? = nil + ) throws -> AmplifyCognitoClient { + try client(.default, failingWhile: failing, observing: order) } - private func client(_ sessionId: SessionID, failingWhile failing: FailingSwitch? = nil) throws -> AmplifyCognitoClient { + private func client( + _ sessionId: SessionID, + failingWhile failing: FailingSwitch? = nil, + observing order: ClearOrder? = nil + ) throws -> AmplifyCognitoClient { try AmplifyCognitoClient( configuration: ClientFixtures.configuration, options: .init(sessionId: sessionId), - dependencies: dependencies(failingWhile: failing) + dependencies: dependencies(failingWhile: failing, observing: order) ) } /// A store over the harness's keychain, as `TestKeychain.recordStore(for:)` builds it, that clears the harness's - /// AWSMobileClient services, or fails to while `failing` is on. + /// AWSMobileClient services, or fails to while `failing` is on. With `order`, each service's clear first records + /// what `.default`'s saved login holds. private static func store( over keychain: TestKeychain, for namespace: SessionStorageNamespace, - failing: FailingSwitch? + failing: FailingSwitch?, + observing order: ClearOrder? ) -> SessionRecordStore { let services = SessionRecordStore.legacyLoginServices(for: namespace.pools) let clock = TestClock() @@ -189,18 +224,27 @@ final class DefaultSessionGuestLegacyLoginTests: XCTestCase { if failing?.isOn == true { return services.map { _ in UnclearableItemStore() } } - return services.map { keychain.itemStore(service: $0) } + let pluginAccount = SessionRecordKey.pluginSessionAccount(in: namespace.pools) + return services.map { service in + let store = keychain.itemStore(service: service) + guard let order else { + return store + } + return ObservedItemStore(store) { order.record(cleared: keychain.value(pluginAccount)) } + } } ) } /// The harness's dependencies, with stores that clear the harness's AWSMobileClient services. - private func dependencies(failingWhile failing: FailingSwitch?) -> SessionCoreDependencies { + private func dependencies(failingWhile failing: FailingSwitch?, observing order: ClearOrder?) -> SessionCoreDependencies { let base = harness.dependencies var dependencies = SessionCoreDependencies( registry: base.registry, gates: base.gates, - makeStore: { [keychain = harness.keychain] in Self.store(over: keychain, for: $0, failing: failing) }, + makeStore: { [keychain = harness.keychain] in + Self.store(over: keychain, for: $0, failing: failing, observing: order) + }, makeClients: base.makeClients, makeEngine: base.makeEngine, makeRevoker: base.makeRevoker, diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/DefaultSessionLegacyLoginTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/DefaultSessionLegacyLoginTests.swift index 24ac30138a..a5799e5736 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/DefaultSessionLegacyLoginTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/DefaultSessionLegacyLoginTests.swift @@ -605,8 +605,9 @@ final class DefaultSessionLegacyLoginTests: XCTestCase { } } -/// What the saved login held each time an AWSMobileClient service was cleared. -private final class ClearOrder: @unchecked Sendable { +/// What the saved login held each time an AWSMobileClient service was cleared. Shared with +/// `DefaultSessionGuestLegacyLoginTests`. +final class ClearOrder: @unchecked Sendable { // `@unchecked Sendable`: `seen` is only touched while holding `lock`. private let lock = NSLock() private var seen: [Data?] = [] @@ -624,8 +625,9 @@ private final class ClearOrder: @unchecked Sendable { } } -/// An item store that calls `onRemoveAll` before it clears `base`, and otherwise is `base`. -private struct ObservedItemStore: KeychainItemStoreBehavior { +/// An item store that calls `onRemoveAll` before it clears `base`, and otherwise is `base`. Shared with +/// `DefaultSessionGuestLegacyLoginTests`. +struct ObservedItemStore: KeychainItemStoreBehavior { let base: any KeychainItemStoreBehavior let onRemoveAll: @Sendable () -> Void From c1109be63867326ccf358ec52b4f2c8cbbdd7e17 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:05:14 -0400 Subject: [PATCH 19/57] test(auth): pin that a double tap says it is queued only when the session itself is queued --- .../UnitTests/WebUISignInInProgressTests.swift | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignInInProgressTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignInInProgressTests.swift index 03b4693c82..2197557a59 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignInInProgressTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignInInProgressTests.swift @@ -26,6 +26,7 @@ final class WebUISignInInProgressTests: XCTestCase { } private let work = ClientFixtures.id("work") private let home = ClientFixtures.id("home") + private let carol = ClientFixtures.id("carol") /// Holds "work"'s calls just before they ask for the sheet, until opened. private let beforeAsking = Gate() @@ -93,12 +94,12 @@ final class WebUISignInInProgressTests: XCTestCase { XCTAssertEqual(engine.webUISignInCalls.count, 1) } - /// - Given: "home" showing its hosted-UI sign-in, and "work"'s in progress before it asks for the sheet (with - /// `.fail`, so it never queues) + /// - Given: "home" showing its hosted-UI sign-in, "carol"'s queued behind it with `.wait(timeout:)`, and "work"'s in + /// progress before it asks for the sheet (with `.fail`, so it never queues) /// - When: "work" asks again /// - Then: /// - the second call is refused at once with `browserBusy` naming "home", which holds the sheet, and says "work"'s - /// hosted-UI sign-in is in progress, not that "work" is waiting for the sheet + /// hosted-UI sign-in is in progress, not that "work" is waiting for the sheet: only "carol" is queued /// - the first is then refused by the lock, as "home" still holds the sheet; nothing of "work"'s is shown func testADoubleTapWhileAnotherSessionHoldsTheSheetDoesNotSayItIsQueued() async throws { let homeClient = try client(home) @@ -106,12 +107,16 @@ final class WebUISignInInProgressTests: XCTestCase { try XCTUnwrap(harness.engine(for: home)).showWebUISignIns(in: homeBrowser) let homeSignIn = await startSignIn(homeClient) try await homeBrowser.shown.arrivals(1, within: 10) + let carolClient = try client(carol) + let carolSignIn = await startSignIn(carolClient, options: WebUIOptions(whenBrowserBusy: .wait(timeout: 60))) + let lock = harness.sheetLock + await waitUntil("carol is queued for the sheet") { await lock.waiterCount == 1 } let client = try client(work) let engine = try XCTUnwrap(harness.engine(for: work)) let first = await startSignIn(client) try await beforeAsking.arrivals(1, within: 10) - let waiters = await harness.sheetLock.waiterCount - XCTAssertEqual(waiters, 0) + let waiters = await lock.waiterCount + XCTAssertEqual(waiters, 1, "only carol is queued") let error = await authClientError { try await startSignIn(client).value(within: 10) } @@ -124,6 +129,8 @@ final class WebUISignInInProgressTests: XCTestCase { let refused = await authClientError { try await first.value(within: 10) } XCTAssertEqual(refused?.kind, .browserBusy(holder: home)) XCTAssertEqual(engine.webUISignInCalls.count, 0) + await carolClient.cancelWebUISignIn() + _ = await authClientError { try await carolSignIn.value(within: 10) } homeBrowser.finishWithDefault() _ = try await homeSignIn.value(within: 10) } From bacc64a27f6332e6556ad633eec3f6208865f59a Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:05:14 -0400 Subject: [PATCH 20/57] fix(auth): say the sheet is still closing to a double tap queued behind the session's own closing sheet --- .../Sources/Core/SessionCore+WebUI.swift | 8 ++-- .../HostedUI/AuthClientError+HostedUI.swift | 5 ++- .../Sources/HostedUI/SystemSheetLock.swift | 5 ++- .../UnitTests/SystemSheetLockTests.swift | 39 +++++++++++++++++++ .../Tests/UnitTests/WebUISignInTests.swift | 39 +++++++++++++++++++ docs/design/RELEASE-NOTES-PENDING.md | 2 +- 6 files changed, 90 insertions(+), 8 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+WebUI.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+WebUI.swift index 1e874182eb..2fcb89f690 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+WebUI.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+WebUI.swift @@ -723,16 +723,18 @@ extension SessionCore { /// /// | The previous call | Reason | `holder` | /// |---|---|---| - /// | queued for the sheet | `.alreadyQueued` | the session holding it | + /// | queued behind another session's sheet | `.alreadyQueued` | the session holding it | + /// | queued behind this session's own closing sheet (a retry after a cancel) | `.stillClosing` | this session | /// | holding the sheet (this session holds it) | `.alreadyInFlight` | this session | /// | neither: before its lease, or committing after it | `.signInInProgress` | the session holding the sheet, else this session | private nonisolated func webUISignInInProgress() async -> AuthClientError { let (holder, queued) = await sheetLock.holderAndQueue(for: sessionId) - if let holder, queued { + if let holder, queued, holder != sessionId { return .browserBusy(heldBy: holder, requestedBy: sessionId, reason: .alreadyQueued) } if holder == sessionId { - return .browserBusy(heldBy: sessionId, requestedBy: sessionId, reason: .alreadyInFlight) + // Queued while holding: the lock queues a session only behind its own sheet once that sheet is closing. + return .browserBusy(heldBy: sessionId, requestedBy: sessionId, reason: queued ? .stillClosing : .alreadyInFlight) } return .browserBusy(heldBy: holder ?? sessionId, requestedBy: sessionId, reason: .signInInProgress) } diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/AuthClientError+HostedUI.swift b/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/AuthClientError+HostedUI.swift index bceff97056..dbae16d5c2 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/AuthClientError+HostedUI.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/AuthClientError+HostedUI.swift @@ -20,10 +20,11 @@ enum BrowserBusyReason: Equatable { /// The asking session already holds the sheet. case alreadyInFlight - /// The asking session is already queued for the sheet. + /// The asking session is already queued for the sheet, which another session holds. case alreadyQueued - /// The asking session's previous flow was cancelled and its sheet is still closing. + /// The asking session's previous flow was cancelled and its sheet is still closing, whether or not the session + /// has queued a retry behind it. case stillClosing /// The asking session's previous hosted-UI sign-in call is still in progress, and is neither holding the sheet diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/SystemSheetLock.swift b/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/SystemSheetLock.swift index d333ff8075..aff6566ebc 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/SystemSheetLock.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/SystemSheetLock.swift @@ -397,12 +397,13 @@ actor SystemSheetLock { } let holding = current.lease.holder // A second call from a session that is already queued, such as a double tap, would - // otherwise be served straight after the first: a second browser nobody asked for. + // otherwise be served straight after the first: a second browser nobody asked for. Queued behind + // its own sheet, the session is waiting for that sheet to close, which is what it is told. guard !waiters.contains(where: { $0.session == session }) else { continuation.resume(throwing: AuthClientError.browserBusy( heldBy: holding, requestedBy: session, - reason: .alreadyQueued + reason: holding == session ? .stillClosing : .alreadyQueued )) return } diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/SystemSheetLockTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/SystemSheetLockTests.swift index fa0fe5bf21..f175ea1b1f 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/SystemSheetLockTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/SystemSheetLockTests.swift @@ -576,6 +576,45 @@ final class SystemSheetLockTests: XCTestCase { XCTAssertNil(holder) } + /// A repeat call from a session queued behind its own closing sheet is told the sheet is still closing. + /// + /// - Given: alice's flow cancelled with `cancel(for:)` and held at a gate, so it is closing, and her retry queued + /// behind it with `.wait` + /// - When: + /// - alice asks again, once with `.fail` and once with `.wait`, and the old flow then unwinds + /// - Then: + /// - both repeat calls throw `browserBusy(holder: alice)` with the "still closing" message, not the queued one + /// that would say alice waits for a sheet alice holds; only the first retry is ever queued, and it is served + func testARepeatCallQueuedBehindItsOwnClosingSheetIsToldItIsStillClosing() async throws { + let lock = SystemSheetLock() + let closing = Gate() + + let first = Self.start(lock, Self.alice, .fail, Self.heldBody(closing)) + await closing.waitForArrivals(1) + await lock.cancel(for: Self.alice) + await Self.assertCancelled(first) + let retry = Self.start(lock, Self.alice, .wait(timeout: 3_600)) { $0.holder } + await waitUntil("the retry is queued behind the closing flow") { await lock.waiterCount == 1 } + + let closingError = AuthClientError.browserBusy(heldBy: Self.alice, requestedBy: Self.alice, reason: .stillClosing) + for policy in [WebUIOptions.BrowserBusyPolicy.wait(timeout: 3_600), .fail] { + let again = Self.start(lock, Self.alice, policy) { $0.holder } + do { + let value = try await again.value(within: 10) + XCTFail("Expected browserBusy, got \(value)") + } catch let error as AuthClientError { + XCTAssertEqual(error.kind, .browserBusy(holder: Self.alice), "\(policy)") + XCTAssertEqual(error.errorDescription, closingError.errorDescription, "\(policy)") + } + } + let queued = await lock.waiterCount + XCTAssertEqual(queued, 1) + + await closing.open() + let retryResult = try await retry.value + XCTAssertEqual(retryResult, Self.alice) + } + /// A holder whose own caller was cancelled is closing too, so its retry may queue. /// /// - Given: alice holding the lock with a flow that ignores cancellation, held at a gate diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignInTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignInTests.swift index a3b0337f16..c9b322bf76 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignInTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignInTests.swift @@ -278,6 +278,45 @@ final class WebUISignInTests: XCTestCase { await waitForFreeSheet() } + /// A double tap on a retry queued behind the session's own closing sheet says the sheet is still closing + /// + /// - Given: "work"'s hosted-UI sign-in cancelled with `cancelWebUISignIn()`, its browser still closing a second later + /// (so the sheet stays held), and a retry with `.wait(timeout:)` queued behind that closing sheet + /// - When: "work" asks again, with `.fail` and with `.wait(timeout:)` + /// - Then: + /// - each second call is refused at once with `browserBusy` naming "work", saying its previous sheet is still + /// closing, not that "work" is waiting for a sheet "work" holds + /// - once the old browser closes, the retry is shown and signs in; the engine was called twice + func testADoubleTapQueuedBehindItsOwnClosingSheetSaysItIsStillClosing() async throws { + let client = try client(work) + let engine = try XCTUnwrap(harness.engine(for: work)) + let browser = FakeBrowser(afterCancel: .keepRunning) + engine.showWebUISignIns(in: browser) + let first = await startSignIn(client) + await browser.shown.waitForArrivals(1) + await client.cancelWebUISignIn() + _ = await authClientError { try await first.value(within: 10) } + let lock = harness.sheetLock + let holder = await lock.currentHolder + XCTAssertEqual(holder, work, "the cancelled browser should still hold the sheet") + let retry = await startSignIn(client, options: WebUIOptions(whenBrowserBusy: .wait(timeout: 60))) + await waitUntil("the retry is queued behind its own closing sheet") { await lock.waiterCount == 1 } + + for policy in [WebUIOptions.BrowserBusyPolicy.fail, .wait(timeout: 60)] { + let error = await authClientError { try await startSignIn(client, options: WebUIOptions(whenBrowserBusy: policy)).value(within: 10) } + XCTAssertEqual(error?.kind, .browserBusy(holder: work), "\(policy)") + XCTAssertEqual(error?.errorDescription, "Session \"work\"'s previous system sheet is still closing.", "\(policy)") + } + + browser.finish(.failure(CancellationError())) + await browser.shown.waitForArrivals(2) + browser.finishWithDefault() + let result = try await retry.value(within: 10) + XCTAssertEqual(result.nextStep, .done) + XCTAssertEqual(engine.webUISignInCalls.count, 2) + await waitForFreeSheet() + } + /// A double tap /// /// - Given: a hosted-UI sign-in of this session showing its browser diff --git a/docs/design/RELEASE-NOTES-PENDING.md b/docs/design/RELEASE-NOTES-PENDING.md index 13d423670a..47eba22a00 100644 --- a/docs/design/RELEASE-NOTES-PENDING.md +++ b/docs/design/RELEASE-NOTES-PENDING.md @@ -33,6 +33,6 @@ behaviour. | AWSMobileClient (Amplify v1) logins: a `.default` sign-out or purge clears them before it writes the signed-out login or deletes the login, so a failed write or delete, or an app stopped right after it, leaves none for a plugin build to migrate back. 2.63.0 cleared them after. `fetchAuthSession()` on `.default` that saves a guest over a signed-out record clears them again first, best effort, so a login a sign-out failed to clear is not migrated back once the guest replaces the signed-out record. That is the sign-out's whole clear, so it also removes what AWSMobileClient, or the AWS SDK for iOS used beside the client, saved in those services since the sign-out: an app that uses the SDK's `AWSCognitoCredentialsProvider` loses the identity ID and AWS credentials it cached for any identity pool then, and gets a new identity ID. The guest's clear never runs over an absent record, nor over a label set before anyone signed in (an app that never signed in through the client keeps them for the plugin to migrate), nor for a named session. **Rollback:** plugin releases before 2.63.0 migrate those logins over whatever `.default` holds, with no check; 2.63.0 keeps a user signed in through the client, but migrates over a guest and over a signed-out `.default`; later releases also keep a signed-out `.default`, until a guest replaces it (the Auth plugin row above) | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Rolling back and forward" | | A hosted-UI sign-out whose logout page is closed (by the user, `cancelWebUISignIn()` or `resetSystemSheet()`) on a `.default` session the Auth plugin saved as expired returns `.failed(.userCancelled)` and keeps the session, where 2.63.0 signed it out on the plugin's saved flag (`.complete`, or `.partial` with no `hostedUIError`). On a session a refresh found expired while it was in memory (until every handle and provider for it is released, or a relaunch), a closed page still signs out on this device, with `.partial`'s `hostedUIError` `.userCancelled`, but only while the login being signed out still has the refresh token found dead: a new login another app in the access group saved since is kept, `.failed(.userCancelled)`, where 2.63.0 revoked it and signed it out | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Hosted-UI sign-out" | | `cancelWebUISignIn()`: a cancelled passkey call throws `.userCancelled` once it has released the system sheet, as a hosted-UI sign-in does, so `systemSheetHolder` no longer names the session by then, where 2.63.0 answered while the sheet was still closing and an immediate retry got `browserBusy`. If the sheet isn't released within a second, the call throws anyway, and the sheet stays held until the passkey ceremony finishes | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Cancelling and recovering" | -| Hosted UI: a second hosted-UI sign-in on a session whose first is still queued behind another session's sheet throws `browserBusy(holder:)` naming the session that holds the sheet ("Session "work" is already waiting for the system sheet, which session "home" holds."), where it named itself. One whose first is in progress but neither holds nor waits for the sheet (before it asks, or committing after) says "Session "work" already has a hosted-UI sign-in in progress.", adding ", and session "home" holds the system sheet" when another session holds it; `holder` is then that session, or the session itself. A sign-out, purge or user deletion that lands just as a hosted-UI attempt begins makes that sign-in throw the sign-in-cancelled `.invalidState` ("The sign-in was cancelled: the session was signed out, purged or deleted while it was in progress."), as one landing at any other point does, where it threw `.userCancelled`. A hosted-UI response whose tokens fail the client's checks (another app client or user pool, another sign-in's nonce, no single user) suggests "Sign in again. If it keeps failing, check that the configuration's user pool, app client and hosted UI domain belong together.", where it suggested `prompt: [.login]`, which cannot change what the tokens say; a user other than the one `identityExpectation` asked for still suggests it | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Hosted UI" | +| Hosted UI: a second hosted-UI sign-in on a session whose first is still queued behind another session's sheet throws `browserBusy(holder:)` naming the session that holds the sheet ("Session "work" is already waiting for the system sheet, which session "home" holds."), where it named itself. One whose first is in progress but neither holds nor waits for the sheet (before it asks, or committing after) says "Session "work" already has a hosted-UI sign-in in progress.", adding ", and session "home" holds the system sheet" when another session holds it; `holder` is then that session, or the session itself. A second call on a session whose retry is queued behind its own closing sheet (after `cancelWebUISignIn()`, while the cancelled browser is still closing) says "Session "work"'s previous system sheet is still closing.", where it said "Session "work" is already waiting for the system sheet, which session "work" holds.". A sign-out, purge or user deletion that lands just as a hosted-UI attempt begins makes that sign-in throw the sign-in-cancelled `.invalidState` ("The sign-in was cancelled: the session was signed out, purged or deleted while it was in progress."), as one landing at any other point does, where it threw `.userCancelled`. A hosted-UI response whose tokens fail the client's checks (another app client or user pool, another sign-in's nonce, no single user) suggests "Sign in again. If it keeps failing, check that the configuration's user pool, app client and hosted UI domain belong together.", where it suggested `prompt: [.login]`, which cannot change what the tokens say; a user other than the one `identityExpectation` asked for still suggests it | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Hosted UI" | | Texts: the recovery suggestion of either pool's invalid-configuration answer (`InvalidUserPoolConfigurationException`, `InvalidIdentityPoolConfigurationException`, both `.configuration`) names the user pool's settings (device tracking, for the device calls), the identity pool's settings and the client's configuration, where it named only the user pool's. `resendSignUpCode` with an empty username throws `.validation(field: "username")` saying "Username is required to resendSignUpCode", where it named `confirmSignUp`, as the plugin's does. A `resendSignUpCode` answer with no code delivery details is `.unknown` with the plugin's texts ("Unexpected error occurred with message: Unable to get Auth code delivery details"), where it had its own. The warning logged when an interrupted sign-in's record could not be deleted says that a signed-in or federated session's next restore deletes it, and that a signed-out or guest session's next restore may offer the challenge again until it is 15 minutes old, where it said the next restore deletes it | — | | Documented, unchanged since 2.63.0: with no client for the session live in the process, `signOutStoredSession` revokes through SDK clients built from the configuration alone, so `Options.configureUserPoolClient` (a custom endpoint, a proxy, certificate pinning) is not applied to it. Hold a client for the session to sign out through it | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Signing out or purging a saved session without a client" | From 0ab481d53f1497165282b773d59543083c153554 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:46:00 -0400 Subject: [PATCH 21/57] docs(auth): say which refused TOTP code lets confirmSignIn send a reset-password or confirm-sign-up answer again --- AmplifyClients/AmplifyCognitoClient/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/README.md b/AmplifyClients/AmplifyCognitoClient/README.md index a58ba41e18..05c417a88e 100644 --- a/AmplifyClients/AmplifyCognitoClient/README.md +++ b/AmplifyClients/AmplifyCognitoClient/README.md @@ -722,8 +722,8 @@ or `.done`. | `.confirmSignInWithPassword` | The password (after choosing `.password` or `.passwordSRP`). A wrong password ends the sign-in: call `signIn` again | | `.continueSignInWithFirstFactorSelection(factors)` | `AuthClientFactorType..challengeResponse`. Anything else is `.validation`, and so is `"WEB_AUTHN"` when `factors` does not offer it | | `.confirmSignInWithCustomChallenge(info)` | Your custom answer | -| `.resetPassword(info)` | Not answered here: reset the password, then sign in again. `confirmSignIn` throws `.invalidState` for this step and ends the attempt, except after Cognito refused a challenge answer or a TOTP code with it, where the answer is sent again | -| `.confirmSignUp(info)` | Not answered here: confirm the sign-up, then sign in again. `confirmSignIn` throws `.invalidState` for this step and ends the attempt, except after Cognito refused a challenge answer or a TOTP code with it, where the answer is sent again | +| `.resetPassword(info)` | Not answered here: reset the password, then sign in again. `confirmSignIn` throws `.invalidState` for this step and ends the attempt, except after Cognito refused a challenge answer, or a TOTP code at `VerifySoftwareToken`, with it, where the answer is sent again | +| `.confirmSignUp(info)` | Not answered here: confirm the sign-up, then sign in again. `confirmSignIn` throws `.invalidState` for this step and ends the attempt, except after Cognito refused a challenge answer, or a TOTP code at `VerifySoftwareToken`, with it, where the answer is sent again | | `.done` | Nothing: signed in | `AuthClientMFAType.rawValue` (`"sms"`, `"totp"`, `"email"`) is not a valid answer. Use `challengeResponse`. From 3d4239aa6b4e22023260524af8bdb29ed1af4a2b Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:29:08 -0400 Subject: [PATCH 22/57] fix(auth): keep the user's confirmSignIn answer out of the sign-in's verbose lines While Cognito checked an answer, the sign-in's challenge state held it in full, and the plugin's verbose "Checking next step for:" line prints that state by reflection, so every answer confirmSignIn sent through a challenge was logged: codes, a password after a password factor selection, a new password, a custom answer. Nothing reads the answer from that state, so it no longer holds it. The answer's own printed form, which the TOTP setup's verbose lines and the line of an answer sent again after a refused device print, masked it, keeping four characters of a six-digit code. It now prints the answer as in description, debug description and mirror. --- .../CodeGen/Data/ConfirmSignInEventData.swift | 30 +++- .../SignInChallengeState+Debug.swift | 2 +- .../CodeGen/States/SignInChallengeState.swift | 4 +- .../SignInChallengeState+Resolver.swift | 8 +- .../SignInAnswerRedactionTests.swift | 132 ++++++++++++++++++ .../SignIn/SignInSecretLogTests.swift | 112 +++++++++++++++ 6 files changed, 279 insertions(+), 9 deletions(-) create mode 100644 AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/SignInAnswerRedactionTests.swift create mode 100644 AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSecretLogTests.swift diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/Data/ConfirmSignInEventData.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/Data/ConfirmSignInEventData.swift index 635eb0eec7..f0c2657873 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/Data/ConfirmSignInEventData.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/Data/ConfirmSignInEventData.swift @@ -35,14 +35,38 @@ extension ConfirmSignInEventData: Equatable { } extension ConfirmSignInEventData: CustomDebugDictionaryConvertible { package var debugDictionary: [String: Any] { [ - "answer": answer.maskedForLog(), + "answer": answer.redactedForLog(), "attributes": attributes, "metadata": metadata ?? [:] ] } } -extension ConfirmSignInEventData: CustomDebugStringConvertible { - package var debugDescription: String { + +// Every printed form redacts the answer, the user's code, password or custom answer: the answer rides in the +// challenge and TOTP setup events, which a "Sending event" line prints, and in the TOTP setup state the sign-in's +// verbose lines print. A masked answer kept four characters of a six-digit code. +extension ConfirmSignInEventData: CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable { + + package var description: String { debugDictionary.debugDescription } + + package var debugDescription: String { + description + } + + /// For `dump` and debuggers: the answer redacted. + package var customMirror: Mirror { + Mirror( + self, + children: [ + "answer": answer.redactedForLog(), + "attributes": attributes, + "metadata": metadata as Any, + "friendlyDeviceName": friendlyDeviceName as Any, + "presentationAnchor": presentationAnchor as Any + ], + displayStyle: .struct + ) + } } diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/States/DebugInfo/SignInChallengeState+Debug.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/States/DebugInfo/SignInChallengeState+Debug.swift index 6d8a778f6a..0923272826 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/States/DebugInfo/SignInChallengeState+Debug.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/States/DebugInfo/SignInChallengeState+Debug.swift @@ -14,7 +14,7 @@ extension SignInChallengeState: CustomDebugDictionaryConvertible { switch self { case .waitingForAnswer(let respondAuthChallenge, _, _), - .verifying(let respondAuthChallenge, _, _, _): + .verifying(let respondAuthChallenge, _, _): additionalMetadataDictionary = respondAuthChallenge.debugDictionary case .error(let respondAuthChallenge, _, let error, _): additionalMetadataDictionary = respondAuthChallenge.debugDictionary.merging( diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/States/SignInChallengeState.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/States/SignInChallengeState.swift index c716570f62..d84f6fbef7 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/States/SignInChallengeState.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/States/SignInChallengeState.swift @@ -13,7 +13,9 @@ package enum SignInChallengeState: State { case waitingForAnswer(RespondToAuthChallenge, SignInMethod, EngineSignInStep) - case verifying(RespondToAuthChallenge, SignInMethod, String, EngineSignInStep) + /// The answer being verified is not kept: the sign-in's verbose lines print this state by reflection, and the + /// answer is the user's code, password or custom answer. `VerifySignInChallenge` carries it. + case verifying(RespondToAuthChallenge, SignInMethod, EngineSignInStep) case verified diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/Resolvers/SignIn/SignInChallengeState+Resolver.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/Resolvers/SignIn/SignInChallengeState+Resolver.swift index 293d633c66..b591280e4f 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/Resolvers/SignIn/SignInChallengeState+Resolver.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/Resolvers/SignIn/SignInChallengeState+Resolver.swift @@ -38,13 +38,13 @@ package extension SignInChallengeState { currentSignInStep: signInStep ) return .init( - newState: .verifying(challenge, signInMethod, answerEventData.answer, signInStep), + newState: .verifying(challenge, signInMethod, signInStep), actions: [action] ) } return .from(oldState) - case .verifying(let challenge, let signInMethod, _, let signInStep): + case .verifying(let challenge, let signInMethod, let signInStep): if case .retryVerifyChallengeAnswer(let answerEventData, let signInStep) = event.isChallengeEvent { let action = VerifySignInChallenge( @@ -54,7 +54,7 @@ package extension SignInChallengeState { currentSignInStep: signInStep ) return .init( - newState: .verifying(challenge, signInMethod, answerEventData.answer, signInStep), + newState: .verifying(challenge, signInMethod, signInStep), actions: [action] ) } @@ -82,7 +82,7 @@ package extension SignInChallengeState { currentSignInStep: signInStep ) return .init( - newState: .verifying(challenge, signInMethod, answerEventData.answer, signInStep), + newState: .verifying(challenge, signInMethod, signInStep), actions: [action] ) } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/SignInAnswerRedactionTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/SignInAnswerRedactionTests.swift new file mode 100644 index 0000000000..30d398aca3 --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/SignInAnswerRedactionTests.swift @@ -0,0 +1,132 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AWSCognitoIdentityProvider +import Foundation +import XCTest +@testable import AWSCognitoAuthPlugin +@testable import InternalAWSCognitoAuth + +/// The user's answer to a sign-in challenge (a code, a password after a password factor selection, a new password, a +/// custom answer) prints redacted in every form: the sign-in actions log the events that carry it at verbose by +/// string interpolation, the plugin's "Checking next step for:" and "Auth state change" lines print the states that +/// hold it, and any of them can be dumped. +final class SignInAnswerRedactionTests: XCTestCase { + + private static let answer = "S3cret-4821" + + private let answerData = ConfirmSignInEventData( + answer: answer, + attributes: ["custom:city": "Seattle"], + metadata: ["key": "value"], + friendlyDeviceName: "device", + presentationAnchor: nil + ) + + private let challenge = RespondToAuthChallenge( + challenge: .password, + availableChallenges: [], + username: "u", + session: "challenge-session", + parameters: [:] + ) + + private let setup = SignInTOTPSetupData(secretCode: "secret", session: "setup-session", username: "u") + + /// Every printed form of `value`: `print`, `debugPrint`, string interpolation and `dump`. + private func printedForms(of value: Any) -> [String] { + var printed = "" + print(value, to: &printed) + var debugPrinted = "" + debugPrint(value, to: &debugPrinted) + var dumped = "" + dump(value, to: &dumped) + return [printed, debugPrinted, "\(value)", dumped] + } + + /// Fails for every form of `value` that holds one of `clear`. + private func assertRedacted( + _ value: Any, + clear: [String] = [answer], + file: StaticString = #filePath, + line: UInt = #line + ) { + for form in printedForms(of: value) { + for clear in clear where form.contains(clear) { + XCTFail("a printed form holds \(clear): \(form)", file: file, line: line) + } + } + } + + /// Test that a challenge answer prints with the answer redacted + /// + /// - Given: the answer `confirmSignIn` sends, with attributes, metadata and a device name + /// - When: + /// - it is printed with `print`, `debugPrint`, interpolation and `dump`, and through its debug dictionary + /// - Then: + /// - no form holds the answer, not even masked: its debug dictionary reads `"answer": ""` + /// - `description` and `debugDescription` are that dictionary, and `dump` names each field, the answer redacted + /// + func testTheAnswerPrintsRedacted() { + assertRedacted(answerData, clear: [Self.answer, Self.answer.maskedForLog()]) + assertRedacted(answerData.debugDictionary, clear: [Self.answer, Self.answer.maskedForLog()]) + XCTAssertEqual(answerData.description, answerData.debugDictionary.debugDescription) + XCTAssertEqual(answerData.debugDescription, answerData.description) + XCTAssertTrue(answerData.description.contains(#""answer": """#), answerData.description) + + var dumped = "" + dump(answerData, to: &dumped) + XCTAssertTrue(dumped.contains(#"- answer: """#), dumped) + for field in ["attributes", "metadata", "friendlyDeviceName", "presentationAnchor"] { + XCTAssertTrue(dumped.contains(" \(field): "), dumped) + } + } + + /// Test that the events and states that carry a challenge answer print without it + /// + /// - Given: each event that carries the answer (`verifyChallengeAnswer` and its retry, the TOTP setup's answer), + /// the TOTP setup state that holds it, the challenge state while the answer is verified, and the sign-in and auth + /// states that hold those + /// - When: + /// - each is printed in every form, as a "Sending event", "Checking next step for:" or "Auth state change" line + /// prints it + /// - Then: + /// - no form holds the answer, in full or masked + /// + func testTheEventsAndStatesThatCarryTheAnswerPrintWithoutIt() { + let events: [Any] = [ + SignInChallengeEvent(eventType: .verifyChallengeAnswer(answerData)), + SignInChallengeEvent(eventType: .retryVerifyChallengeAnswer(answerData, .confirmSignInWithPassword)), + SetUpTOTPEvent(eventType: .verifyChallengeAnswer(answerData)) + ] + let signIn = SignInEventData(username: nil, password: nil, signInMethod: .apiBased(.userAuth)) + let signInStates: [SignInState] = [ + .resolvingChallenge( + .verifying(challenge, .apiBased(.userAuth), .confirmSignInWithPassword), + .passwordRequired, + .apiBased(.userAuth) + ), + .resolvingTOTPSetup(.verifying(setup, answerData), signIn) + ] + let clear = [Self.answer, Self.answer.maskedForLog()] + for event in events { + assertRedacted(event, clear: clear) + let line = "InternalAWSCognitoAuth/VerifySignInChallenge.swift Sending event \(event)" + XCTAssertFalse(clear.contains { line.contains($0) }, line) + } + for state in signInStates { + assertRedacted(state, clear: clear) + assertRedacted(state.debugDictionary, clear: clear) + let checking = "Checking next step for: \(state)" + XCTAssertFalse(clear.contains { checking.contains($0) }, checking) + let authState = AuthState.configured(.signingIn(state), .configured, .notStarted) + let changed = "Auth state change:\n\n\(authState)\n" + XCTAssertFalse(clear.contains { changed.contains($0) }, changed) + assertRedacted(authState, clear: clear) + } + } +} diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSecretLogTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSecretLogTests.swift new file mode 100644 index 0000000000..f2c2c0c243 --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSecretLogTests.swift @@ -0,0 +1,112 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AWSCognitoIdentityProvider +import Foundation +import XCTest +@testable import Amplify +@testable import AWSCognitoAuthPlugin +@testable import InternalAWSCognitoAuth + +/// The plugin logs none of a sign-in's secrets, at any level: the user's answer to `confirmSignIn` reaches the +/// verbose "Checking next step for:" and "Sending event" lines inside the states and events that carry it. +final class SignInSecretLogTests: BasePluginTest, @unchecked Sendable { + + private static let answer = "S3cret-4821" + + private var capture: CapturingLoggingPlugin! + private var savedPlugins: [PluginKey: LoggingCategoryPlugin] = [:] + private var savedLogLevel: LogLevel = .error + private var savedRouter: (any EngineLogRouter)? + + override func setUp() { + super.setUp() + capture = CapturingLoggingPlugin() + savedPlugins = Amplify.Logging.plugins + savedLogLevel = Amplify.Logging.logLevel + savedRouter = EngineLog.router + Amplify.Logging.plugins = [capture.key: capture] + Amplify.Logging.logLevel = .verbose + // The engine's sign-in helpers log through the global engine router. + EngineLog.install(AmplifyEngineLogRouter()) + } + + override func tearDown() async throws { + Amplify.Logging.plugins = savedPlugins + Amplify.Logging.logLevel = savedLogLevel + if let savedRouter { + EngineLog.install(savedRouter) + } + try await super.tearDown() + } + + /// Fails for every captured line that holds one of `secrets` in full, or one of `answers` in full or masked: a + /// masked session keeps four characters of a long value, a masked code four of six digits. + private func assertNoLineHolds( + _ secrets: [String], + answers: [String] = [], + file: StaticString = #filePath, + line: UInt = #line + ) { + let forbidden = secrets + answers + answers.map { $0.maskedForLog() } + for captured in capture.lines { + for secret in forbidden where captured.message.contains(secret) { + XCTFail("a log line holds \(secret): \(captured.level) \(captured.message)", file: file, line: line) + } + } + } + + /// The captured messages that start with `prefix`. + private func messages(startingWith prefix: String) -> [String] { + capture.lines.map(\.message).filter { $0.hasPrefix(prefix) } + } + + /// Test that `confirmSignIn` logs no line with the user's answer + /// + /// - Given: a plugin waiting for a password after a password factor selection, whose user pool accepts the answer, + /// and a capturing logging plugin at verbose + /// - When: + /// - I confirm sign-in with the password + /// - Then: + /// - the user pool receives the password, and the sign-in completes + /// - the "Checking next step for:" line prints the challenge state while the answer is verified, and no log + /// line holds the password, in full or masked + /// + func testConfirmSignInLogsNoLineWithTheAnswer() async throws { + let challenge = RespondToAuthChallenge( + challenge: .password, + availableChallenges: [], + username: "username", + session: "session", + parameters: [:] + ) + let userPool = MockIdentityProvider(mockRespondToAuthChallengeResponse: { input in + XCTAssertEqual(input.challengeResponses?["PASSWORD"], Self.answer) + return .testData() + }) + let plugin = configureCustomPluginWith( + userPool: { userPool }, + initialState: .configured( + .signingIn(.resolvingChallenge( + .waitingForAnswer(challenge, .apiBased(.userAuth), .confirmSignInWithPassword), + .passwordRequired, + .apiBased(.userAuth) + )), + .configured, + .notStarted + ) + ) + + let result = try await plugin.confirmSignIn(challengeResponse: Self.answer) + + XCTAssertTrue(result.isSignedIn) + let verifying = messages(startingWith: "Checking next step for: resolvingChallenge(") + .filter { $0.contains("SignInChallengeState.verifying(") } + XCTAssertFalse(verifying.isEmpty, "\(capture.lines.map(\.message))") + assertNoLineHolds([], answers: [Self.answer]) + } +} From 8d17aae2916c6534c74e5681bdf86dad92d6bfb7 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:29:22 -0400 Subject: [PATCH 23/57] fix(auth): redact the TOTP setup's verified session in the event VerifyTOTPSetup logs VerifyTOTPSetup logs the event it sends at verbose by string interpolation, and that event carries the session VerifySoftwareToken returns, which answers the MFA setup challenge and finishes the sign-in, so the session was logged in full. The event's type now prints the session as in description, debug description and mirror; its other cases print as default printing does. --- .../CodeGen/Events/SetUpTOTPEvent.swift | 45 +++++++++ .../SignInAnswerRedactionTests.swift | 95 ++++++++++++++++++- .../SignIn/SignInSecretLogTests.swift | 50 +++++++++- 3 files changed, 183 insertions(+), 7 deletions(-) diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/Events/SetUpTOTPEvent.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/Events/SetUpTOTPEvent.swift index 01bd0c8b83..d903931cdf 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/Events/SetUpTOTPEvent.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/CodeGen/Events/SetUpTOTPEvent.swift @@ -71,3 +71,48 @@ extension SetUpTOTPEvent.EventType: Equatable { } } + +// Every printed form redacts the session `VerifySoftwareToken` returns, which `CompleteTOTPSetup` answers the MFA +// setup challenge with: `VerifyTOTPSetup` logs the event that carries it at verbose. The other cases read as default +// printing does; their payloads redact their own secrets. +extension SetUpTOTPEvent.EventType: CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable { + + package var description: String { + switch self { + case .setUpTOTP(let challenge): + return "setUpTOTP(\(String(reflecting: challenge)))" + case .waitForAnswer(let setupData): + return "waitForAnswer(\(String(reflecting: setupData)))" + case .verifyChallengeAnswer(let answer): + return "verifyChallengeAnswer(\(String(reflecting: answer)))" + case .respondToAuthChallenge(let session): + return "respondToAuthChallenge(\(String(reflecting: session.redactedForLog())))" + case .verified: + return "verified" + case .throwError(let error): + return "throwError(\(String(reflecting: error)))" + } + } + + package var debugDescription: String { + "\(String(reflecting: Self.self)).\(description)" + } + + /// For `dump` and debuggers: the session redacted. + package var customMirror: Mirror { + switch self { + case .setUpTOTP(let challenge): + return Mirror(self, children: ["setUpTOTP": challenge], displayStyle: .enum) + case .waitForAnswer(let setupData): + return Mirror(self, children: ["waitForAnswer": setupData], displayStyle: .enum) + case .verifyChallengeAnswer(let answer): + return Mirror(self, children: ["verifyChallengeAnswer": answer], displayStyle: .enum) + case .respondToAuthChallenge(let session): + return Mirror(self, children: ["respondToAuthChallenge": session.redactedForLog()], displayStyle: .enum) + case .verified: + return Mirror(self, children: [:], displayStyle: .enum) + case .throwError(let error): + return Mirror(self, children: ["throwError": error], displayStyle: .enum) + } + } +} diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/SignInAnswerRedactionTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/SignInAnswerRedactionTests.swift index 30d398aca3..1a40511c8b 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/SignInAnswerRedactionTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/SignInAnswerRedactionTests.swift @@ -11,13 +11,15 @@ import XCTest @testable import AWSCognitoAuthPlugin @testable import InternalAWSCognitoAuth -/// The user's answer to a sign-in challenge (a code, a password after a password factor selection, a new password, a -/// custom answer) prints redacted in every form: the sign-in actions log the events that carry it at verbose by -/// string interpolation, the plugin's "Checking next step for:" and "Auth state change" lines print the states that -/// hold it, and any of them can be dumped. +/// A sign-in's secrets print redacted in every form: the user's answer to a challenge (a code, a password after a +/// password factor selection, a new password, a custom answer), and the session `VerifySoftwareToken` returns for a +/// TOTP setup. The sign-in actions log the events that carry them at verbose by string interpolation, the plugin's +/// "Checking next step for:" and "Auth state change" lines print the states that hold them, and any of them can be +/// dumped. final class SignInAnswerRedactionTests: XCTestCase { private static let answer = "S3cret-4821" + private static let totpSession = "totp-verify-session-value" private let answerData = ConfirmSignInEventData( answer: answer, @@ -51,7 +53,7 @@ final class SignInAnswerRedactionTests: XCTestCase { /// Fails for every form of `value` that holds one of `clear`. private func assertRedacted( _ value: Any, - clear: [String] = [answer], + clear: [String] = [answer, totpSession], file: StaticString = #filePath, line: UInt = #line ) { @@ -129,4 +131,87 @@ final class SignInAnswerRedactionTests: XCTestCase { assertRedacted(authState, clear: clear) } } + + /// Test that the TOTP setup's verified session prints redacted + /// + /// - Given: the event `VerifyTOTPSetup` sends with the session `VerifySoftwareToken` returned + /// - When: + /// - the event and its type are printed in every form + /// - Then: + /// - no form holds the session; the event type reads as default printing does, with the session `` + /// - the event's other cases read as default printing does + /// + func testTheTOTPSetupSessionPrintsRedacted() { + let eventType = SetUpTOTPEvent.EventType.respondToAuthChallenge(Self.totpSession) + assertRedacted(SetUpTOTPEvent(id: "event-1", eventType: eventType)) + assertRedacted(eventType) + XCTAssertEqual(eventType.description, #"respondToAuthChallenge("")"#) + XCTAssertEqual( + eventType.debugDescription, + #"InternalAWSCognitoAuth.SetUpTOTPEvent.EventType.respondToAuthChallenge("")"# + ) + var dumped = "" + dump(eventType, to: &dumped) + XCTAssertTrue(dumped.contains(#"- respondToAuthChallenge: """#), dumped) + + let error = SignInError.unknown(message: "failed") + XCTAssertEqual(SetUpTOTPEvent.EventType.verified.description, "verified") + XCTAssertEqual( + SetUpTOTPEvent.EventType.throwError(error).description, + "throwError(\(String(reflecting: error)))" + ) + XCTAssertEqual( + SetUpTOTPEvent.EventType.waitForAnswer(setup).description, + "waitForAnswer(\(String(reflecting: setup)))" + ) + XCTAssertEqual( + SetUpTOTPEvent.EventType.setUpTOTP(challenge).description, + "setUpTOTP(\(String(reflecting: challenge)))" + ) + } + + /// Test that the line `VerifyTOTPSetup` logs for the event it sends holds no session + /// + /// - Given: `VerifyTOTPSetup` over a user pool whose `VerifySoftwareToken` answers with a session, and a capturing + /// logger as the environment's + /// - When: + /// - the action runs + /// - Then: + /// - it sends the session in full + /// - its "Sending event" line shows the session as ``, and no line holds it, nor the TOTP code + /// + func testTheVerifyTOTPSetupLineHoldsNoSession() async throws { + let router = CapturingRouter() + let defaults = Defaults.makeDefaultAuthEnvironment(userPoolFactory: { + MockIdentityProvider(mockVerifySoftwareTokenResponse: { _ in + VerifySoftwareTokenOutput(session: Self.totpSession, status: .success) + }) + }) + let environment = AuthEnvironment( + configuration: defaults.configuration, + userPoolConfigData: defaults.userPoolConfigData, + identityPoolConfigData: defaults.identityPoolConfigData, + authenticationEnvironment: defaults.authenticationEnvironment, + authorizationEnvironment: defaults.authorizationEnvironment, + credentialsClient: defaults.credentialsClient, + logger: router.scopedLogger() + ) + let action = VerifyTOTPSetup(session: "setup-session", totpCode: "482913", friendlyDeviceName: nil) + let sent = expectation(description: "the session is sent") + let dispatcher = MockDispatcher { event in + guard case .respondToAuthChallenge(let session) = (event as? SetUpTOTPEvent)?.eventType else { + return + } + XCTAssertEqual(session, Self.totpSession) + sent.fulfill() + } + + await action.execute(withDispatcher: dispatcher, environment: environment) + + await fulfillment(of: [sent], timeout: 1) + let messages = router.entries.map(\.message) + XCTAssertFalse(messages.contains { $0.contains(Self.totpSession) || $0.contains("482913") }, "\(messages)") + let line = try XCTUnwrap(messages.first { $0.contains("VerifyTOTPSetup.swift Sending event") }, "\(messages)") + XCTAssertTrue(line.contains(#"respondToAuthChallenge("")"#), line) + } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSecretLogTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSecretLogTests.swift index f2c2c0c243..5a08f9def9 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSecretLogTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSecretLogTests.swift @@ -12,11 +12,13 @@ import XCTest @testable import AWSCognitoAuthPlugin @testable import InternalAWSCognitoAuth -/// The plugin logs none of a sign-in's secrets, at any level: the user's answer to `confirmSignIn` reaches the -/// verbose "Checking next step for:" and "Sending event" lines inside the states and events that carry it. +/// The plugin logs none of a sign-in's secrets, at any level: the user's answer to `confirmSignIn`, and the session +/// `VerifySoftwareToken` returns while a TOTP setup finishes, reach the verbose "Checking next step for:" and +/// "Sending event" lines inside the states and events that carry them. final class SignInSecretLogTests: BasePluginTest, @unchecked Sendable { private static let answer = "S3cret-4821" + private static let totpSession = "totp-verify-session-value" private var capture: CapturingLoggingPlugin! private var savedPlugins: [PluginKey: LoggingCategoryPlugin] = [:] @@ -109,4 +111,48 @@ final class SignInSecretLogTests: BasePluginTest, @unchecked Sendable { XCTAssertFalse(verifying.isEmpty, "\(capture.lines.map(\.message))") assertNoLineHolds([], answers: [Self.answer]) } + + /// Test that confirming a TOTP setup logs no line with the TOTP code or the verified session + /// + /// - Given: a plugin waiting for the code of a TOTP setup, whose user pool verifies the code with a new session and + /// accepts that session, and a capturing logging plugin at verbose + /// - When: + /// - I confirm sign-in with the code + /// - Then: + /// - the user pool receives the verified session, and the sign-in completes + /// - `VerifyTOTPSetup`'s "Sending event" line shows the session ``, and no log line holds the session, + /// nor the code in full or masked + /// + func testConfirmingATOTPSetupLogsNoLineWithTheCodeOrTheSession() async throws { + let code = "482913" + let userPool = MockIdentityProvider( + mockRespondToAuthChallengeResponse: { input in + XCTAssertEqual(input.session, Self.totpSession) + return .testData() + }, + mockVerifySoftwareTokenResponse: { input in + XCTAssertEqual(input.userCode, code) + return .init(session: Self.totpSession, status: .success) + } + ) + let plugin = configureCustomPluginWith( + userPool: { userPool }, + initialState: .configured( + .signingIn(.resolvingTOTPSetup( + .waitingForAnswer(.init(secretCode: "sharedSecret", session: "session", username: "username")), + .testData + )), + .configured, + .notStarted + ) + ) + + let result = try await plugin.confirmSignIn(challengeResponse: code) + + XCTAssertTrue(result.isSignedIn) + let sent = capture.lines.map(\.message).filter { $0.contains("VerifyTOTPSetup.swift Sending event") } + XCTAssertEqual(sent.count, 1, "\(capture.lines.map(\.message))") + XCTAssertTrue(sent.first?.contains(#"respondToAuthChallenge("")"#) == true, sent.first ?? "-") + assertNoLineHolds([Self.totpSession], answers: [code]) + } } From 6aa302146ee351434e739dc5cb1cfa834940f4c0 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:29:22 -0400 Subject: [PATCH 24/57] fix(auth): print AuthSignUpStep with the auto sign-in session redacted The plugin's tasks log their result at info ("Successfully completed execution for ... with result:"), and the results of confirmSignUp and of a sign-up Cognito confirms at once hold AuthSignUpStep.completeAutoSignIn(Session), which printed the session in full. With the username, that session signs the user in. AuthSignUpStep now conforms to CustomStringConvertible, CustomDebugStringConvertible and CustomReflectable, so description, debugDescription, interpolation and dump, of the step or of the AuthSignUpResult holding it, show completeAutoSignIn(""); the other steps print as default printing does. The engine's copy of the step prints the same way, so its text still normalises to the public one. The new conformances are an additive API change; api-dump/Amplify.json records them and the three new members. --- .../Auth/Models/AuthSignUpStep.swift | 36 +++++ .../Sources/Models/AuthClientSignUp.swift | 2 +- .../Sources/Models/EngineSignUpResult.swift | 33 ++++ .../SignIn/SignInSecretLogTests.swift | 55 ++++++- .../Auth/AuthSignUpStepDescriptionTests.swift | 111 ++++++++++++++ api-dump/Amplify.json | 142 +++++++++++++++++- 6 files changed, 374 insertions(+), 5 deletions(-) create mode 100644 AmplifyTests/CategoryTests/Auth/AuthSignUpStepDescriptionTests.swift diff --git a/Amplify/Categories/Auth/Models/AuthSignUpStep.swift b/Amplify/Categories/Auth/Models/AuthSignUpStep.swift index c7fe8ef0c3..0414610d61 100644 --- a/Amplify/Categories/Auth/Models/AuthSignUpStep.swift +++ b/Amplify/Categories/Auth/Models/AuthSignUpStep.swift @@ -27,3 +27,39 @@ public enum AuthSignUpStep { } extension AuthSignUpStep: Sendable { } + +extension AuthSignUpStep: CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable { + + /// The default enum printing, with the auto sign-in session redacted: the step is logged as part of a sign-up + /// result, and the session, with the username, signs the user in. Match `.completeAutoSignIn(let session)` for the + /// value. + public var description: String { + switch self { + case .confirmUser(let details, let additionalInfo, let userId): + return "confirmUser(\(String(reflecting: details)), \(String(reflecting: additionalInfo)), " + + "\(String(reflecting: userId)))" + case .completeAutoSignIn: + return "completeAutoSignIn(\(String(reflecting: Self.redactedSession)))" + case .done: + return "done" + } + } + + public var debugDescription: String { + "\(String(reflecting: Self.self)).\(description)" + } + + /// For `dump` and debuggers: the auto sign-in session redacted. + public var customMirror: Mirror { + switch self { + case .confirmUser(let details, let additionalInfo, let userId): + return Mirror(self, children: ["confirmUser": (details, additionalInfo, userId)], displayStyle: .enum) + case .completeAutoSignIn: + return Mirror(self, children: ["completeAutoSignIn": Self.redactedSession], displayStyle: .enum) + case .done: + return Mirror(self, children: [:], displayStyle: .enum) + } + } + + private static let redactedSession = "" +} diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Models/AuthClientSignUp.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Models/AuthClientSignUp.swift index a098cf9cdc..3f9e59349b 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Models/AuthClientSignUp.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Models/AuthClientSignUp.swift @@ -147,7 +147,7 @@ extension AuthClientSignUpStep: Sendable {} extension AuthClientSignUpStep: CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable { /// Redacted: Cognito's auto sign-in session never reaches a log through string interpolation, as the - /// engine masks it in its own log lines. Stricter than core's `AuthSignUpStep`, which is not redacted. + /// engine masks it in its own log lines. Core's `AuthSignUpStep` redacts it too, quoted as `""`. /// `AuthClientSignUpResult` prints its step through this. public var description: String { switch self { diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Models/EngineSignUpResult.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Models/EngineSignUpResult.swift index 38ce462d53..ae1218da5c 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Models/EngineSignUpResult.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Models/EngineSignUpResult.swift @@ -62,6 +62,39 @@ package enum EngineSignUpStep { extension EngineSignUpStep: Sendable { } +// Printed as `Amplify.AuthSignUpStep` prints, with the auto sign-in session redacted, so the engine's text still +// normalises to the public one. +extension EngineSignUpStep: CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable { + + package var description: String { + switch self { + case .confirmUser(let details, let additionalInfo, let userId): + return "confirmUser(\(String(reflecting: details)), \(String(reflecting: additionalInfo)), " + + "\(String(reflecting: userId)))" + case .completeAutoSignIn(let session): + return "completeAutoSignIn(\(String(reflecting: session.redactedForLog())))" + case .done: + return "done" + } + } + + package var debugDescription: String { + "\(String(reflecting: Self.self)).\(description)" + } + + /// For `dump` and debuggers: the auto sign-in session redacted. + package var customMirror: Mirror { + switch self { + case .confirmUser(let details, let additionalInfo, let userId): + return Mirror(self, children: ["confirmUser": (details, additionalInfo, userId)], displayStyle: .enum) + case .completeAutoSignIn(let session): + return Mirror(self, children: ["completeAutoSignIn": session.redactedForLog()], displayStyle: .enum) + case .done: + return Mirror(self, children: [:], displayStyle: .enum) + } + } +} + /// A user attribute on its way to Cognito: the wire name and the value. /// /// The engine's copy of `Amplify.AuthUserAttribute`, with the key as the Cognito name diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSecretLogTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSecretLogTests.swift index 5a08f9def9..d05e73e726 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSecretLogTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/SignInSecretLogTests.swift @@ -12,13 +12,15 @@ import XCTest @testable import AWSCognitoAuthPlugin @testable import InternalAWSCognitoAuth -/// The plugin logs none of a sign-in's secrets, at any level: the user's answer to `confirmSignIn`, and the session -/// `VerifySoftwareToken` returns while a TOTP setup finishes, reach the verbose "Checking next step for:" and -/// "Sending event" lines inside the states and events that carry them. +/// The plugin logs none of a sign-in's or sign-up's secrets, at any level: the user's answer to `confirmSignIn`, +/// the session `VerifySoftwareToken` returns while a TOTP setup finishes, and the auto sign-in session a sign-up +/// result carries. The answer and the TOTP session reach the verbose "Checking next step for:" and "Sending event" +/// lines inside the states and events that carry them; the sign-up session reaches the task's result line at info. final class SignInSecretLogTests: BasePluginTest, @unchecked Sendable { private static let answer = "S3cret-4821" private static let totpSession = "totp-verify-session-value" + private static let signUpSession = "auto-sign-in-session-value" private var capture: CapturingLoggingPlugin! private var savedPlugins: [PluginKey: LoggingCategoryPlugin] = [:] @@ -155,4 +157,51 @@ final class SignInSecretLogTests: BasePluginTest, @unchecked Sendable { XCTAssertTrue(sent.first?.contains(#"respondToAuthChallenge("")"#) == true, sent.first ?? "-") assertNoLineHolds([Self.totpSession], answers: [code]) } + + /// Test that the sign-up results that carry the auto sign-in session log it redacted + /// + /// - Given: a signed-out plugin whose user pool answers a passwordless sign-up as confirmed with a session, and a + /// confirmation with another session, and a capturing logging plugin at verbose + /// - When: + /// - I sign up, then confirm the sign-up + /// - Then: + /// - each result is `.completeAutoSignIn` with its session in full + /// - each task's result line, at info, shows `completeAutoSignIn("")`, and no log line holds either + /// session in full (the sign-up state's line masks it, as before) + /// + func testTheSignUpResultsLogTheAutoSignInSessionRedacted() async throws { + let confirmSession = "confirm-sign-up-session-value" + let userPool = MockIdentityProvider( + mockSignUpResponse: { _ in + .init(codeDeliveryDetails: nil, session: Self.signUpSession, userConfirmed: true, userSub: "sub") + }, + mockConfirmSignUpResponse: { _ in .init(session: confirmSession) } + ) + let plugin = configureCustomPluginWith( + userPool: { userPool }, + initialState: .configured(.signedOut(.init(lastKnownUserName: nil)), .configured, .notStarted) + ) + + let signUp = try await plugin.signUp(username: "jeffb", options: .init()) + let confirmed = try await plugin.confirmSignUp(for: "jeffb", confirmationCode: "123456", options: .init()) + + guard case .completeAutoSignIn(let signUpSession) = signUp.nextStep, + case .completeAutoSignIn(let confirmedSession) = confirmed.nextStep else { + return XCTFail("expected .completeAutoSignIn, got \(signUp.nextStep) and \(confirmed.nextStep)") + } + XCTAssertEqual(signUpSession, Self.signUpSession) + XCTAssertEqual(confirmedSession, confirmSession) + for eventName in [HubPayload.EventName.Auth.signUpAPI, HubPayload.EventName.Auth.confirmSignUpAPI] { + let resultLines = capture.lines.filter { + $0.message.hasPrefix("Successfully completed execution for \(eventName) with result:") + } + XCTAssertEqual(resultLines.count, 1, eventName) + XCTAssertEqual(resultLines.first?.level, "info", eventName) + XCTAssertTrue( + resultLines.first?.message.contains(#"completeAutoSignIn("")"#) == true, + resultLines.first?.message ?? "-" + ) + } + assertNoLineHolds([Self.signUpSession, confirmSession]) + } } diff --git a/AmplifyTests/CategoryTests/Auth/AuthSignUpStepDescriptionTests.swift b/AmplifyTests/CategoryTests/Auth/AuthSignUpStepDescriptionTests.swift new file mode 100644 index 0000000000..37b23f394e --- /dev/null +++ b/AmplifyTests/CategoryTests/Auth/AuthSignUpStepDescriptionTests.swift @@ -0,0 +1,111 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +import XCTest + +/// `AuthSignUpStep` prints the auto sign-in session redacted in every form: the plugin logs sign-up results by string +/// interpolation, and with the username the session signs the user in. +final class AuthSignUpStepDescriptionTests: XCTestCase { + + private static let session = "auto-sign-in-session-value" + + /// Every printed form of `value`: `print`, `debugPrint`, string interpolation and `dump`. + private func printedForms(of value: Any) -> [String] { + var dumped = "" + dump(value, to: &dumped) + var printed = "" + print(value, to: &printed) + var debugPrinted = "" + debugPrint(value, to: &debugPrinted) + return [String(describing: value), String(reflecting: value), "\(value)", dumped, printed, debugPrinted] + } + + /// Fails for every printed form of `value` that holds the session. + private func assertRedacted(_ value: Any, file: StaticString = #filePath, line: UInt = #line) { + for form in printedForms(of: value) where form.contains(Self.session) { + XCTFail("a printed form holds the session: \(form)", file: file, line: line) + } + } + + /// Test that the auto sign-in step prints without its session + /// + /// - Given: the `completeAutoSignIn` sign-up step holding a session + /// - When: + /// - it is printed with `description`, `debugDescription`, `String(reflecting:)`, interpolation, `print`, + /// `debugPrint` and `dump` + /// - Then: + /// - no form holds the session + /// - `description` and `debugDescription` read as default enum printing, with the session `` + /// - `dump` shows the session `` + /// + func testTheAutoSignInStepPrintsWithoutTheSession() { + let step = AuthSignUpStep.completeAutoSignIn(Self.session) + + assertRedacted(step) + XCTAssertEqual(step.description, #"completeAutoSignIn("")"#) + XCTAssertEqual(step.debugDescription, #"Amplify.AuthSignUpStep.completeAutoSignIn("")"#) + XCTAssertEqual(String(reflecting: step), step.debugDescription) + + var dumped = "" + dump(step, to: &dumped) + XCTAssertTrue(dumped.contains(#"- completeAutoSignIn: """#), dumped) + } + + /// Test that the other steps print as default enum printing does + /// + /// - Given: `confirmUser` with delivery details, additional information and a user ID; `confirmUser` with none; + /// and `done` + /// - When: + /// - each is printed with `description` and `debugDescription` + /// - Then: + /// - each reads as default enum printing would + /// + func testTheOtherStepsPrintAsBefore() { + let details = AuthCodeDeliveryDetails(destination: .email("a@example.com")) + let step = AuthSignUpStep.confirmUser(details, ["key": "value"], "sub") + + XCTAssertEqual( + step.description, + "confirmUser(\(String(reflecting: Optional(details))), " + + #"Optional(["key": "value"]), Optional("sub"))"# + ) + XCTAssertEqual(step.debugDescription, "Amplify.AuthSignUpStep.\(step.description)") + XCTAssertEqual(AuthSignUpStep.confirmUser().description, "confirmUser(nil, nil, nil)") + XCTAssertEqual(AuthSignUpStep.done.description, "done") + XCTAssertEqual(AuthSignUpStep.done.debugDescription, "Amplify.AuthSignUpStep.done") + + var dumped = "" + dump(step, to: &dumped) + XCTAssertTrue(dumped.contains(#" .2: Optional("sub")"#), dumped) + } + + /// Test that the sign-up result carrying the auto sign-in step prints without the session + /// + /// - Given: a sign-up result holding the `completeAutoSignIn` step, as `signUp` and `confirmSignUp` return it and + /// the plugin logs it + /// - When: + /// - the result, and the result's line as the plugin logs it, are printed in every form + /// - Then: + /// - no form holds the session, the line shows it ``, and the app still reads it from the step + /// + func testTheResultPrintsWithoutTheSession() { + let result = AuthSignUpResult(.completeAutoSignIn(Self.session), userID: "sub") + + assertRedacted(result) + assertRedacted([result]) + assertRedacted(Optional(result) as Any) + + let line = "Successfully completed execution for Auth.confirmSignUpAPI with result:\n\(result)" + XCTAssertFalse(line.contains(Self.session), line) + XCTAssertTrue(line.contains(#"completeAutoSignIn("")"#), line) + guard case .completeAutoSignIn(let session) = result.nextStep else { + return XCTFail("expected .completeAutoSignIn, got \(result.nextStep)") + } + XCTAssertEqual(session, Self.session) + } +} diff --git a/api-dump/Amplify.json b/api-dump/Amplify.json index 1f2555afb2..d2ffdcaa18 100644 --- a/api-dump/Amplify.json +++ b/api-dump/Amplify.json @@ -20738,13 +20738,153 @@ "usr": "s:7Amplify14AuthSignUpStepO4doneyA2CmF", "mangledName": "$s7Amplify14AuthSignUpStepO4doneyA2CmF", "moduleName": "Amplify" + }, + { + "kind": "Var", + "name": "description", + "printedName": "description", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "s:7Amplify14AuthSignUpStepO11descriptionSSvp", + "mangledName": "$s7Amplify14AuthSignUpStepO11descriptionSSvp", + "moduleName": "Amplify", + "isFromExtension": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify14AuthSignUpStepO11descriptionSSvg", + "mangledName": "$s7Amplify14AuthSignUpStepO11descriptionSSvg", + "moduleName": "Amplify", + "isFromExtension": true, + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "debugDescription", + "printedName": "debugDescription", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Var", + "usr": "s:7Amplify14AuthSignUpStepO16debugDescriptionSSvp", + "mangledName": "$s7Amplify14AuthSignUpStepO16debugDescriptionSSvp", + "moduleName": "Amplify", + "isFromExtension": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "String", + "printedName": "Swift.String", + "usr": "s:SS" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify14AuthSignUpStepO16debugDescriptionSSvg", + "mangledName": "$s7Amplify14AuthSignUpStepO16debugDescriptionSSvg", + "moduleName": "Amplify", + "isFromExtension": true, + "accessorKind": "get" + } + ] + }, + { + "kind": "Var", + "name": "customMirror", + "printedName": "customMirror", + "children": [ + { + "kind": "TypeNominal", + "name": "Mirror", + "printedName": "Swift.Mirror", + "usr": "s:s6MirrorV" + } + ], + "declKind": "Var", + "usr": "s:7Amplify14AuthSignUpStepO12customMirrors0G0Vvp", + "mangledName": "$s7Amplify14AuthSignUpStepO12customMirrors0G0Vvp", + "moduleName": "Amplify", + "isFromExtension": true, + "accessors": [ + { + "kind": "Accessor", + "name": "Get", + "printedName": "Get()", + "children": [ + { + "kind": "TypeNominal", + "name": "Mirror", + "printedName": "Swift.Mirror", + "usr": "s:s6MirrorV" + } + ], + "declKind": "Accessor", + "usr": "s:7Amplify14AuthSignUpStepO12customMirrors0G0Vvg", + "mangledName": "$s7Amplify14AuthSignUpStepO12customMirrors0G0Vvg", + "moduleName": "Amplify", + "isFromExtension": true, + "accessorKind": "get" + } + ] } ], "declKind": "Enum", "usr": "s:7Amplify14AuthSignUpStepO", "mangledName": "$s7Amplify14AuthSignUpStepO", "moduleName": "Amplify", - "isEnumExhaustive": true + "isEnumExhaustive": true, + "conformances": [ + { + "kind": "Conformance", + "name": "CustomDebugStringConvertible", + "printedName": "CustomDebugStringConvertible", + "usr": "s:s28CustomDebugStringConvertibleP", + "mangledName": "$ss28CustomDebugStringConvertibleP" + }, + { + "kind": "Conformance", + "name": "CustomReflectable", + "printedName": "CustomReflectable", + "usr": "s:s17CustomReflectableP", + "mangledName": "$ss17CustomReflectableP" + }, + { + "kind": "Conformance", + "name": "CustomStringConvertible", + "printedName": "CustomStringConvertible", + "usr": "s:s23CustomStringConvertibleP", + "mangledName": "$ss23CustomStringConvertibleP" + } + ] }, { "kind": "TypeDecl", From e70fb2e2689881eff86afb982448edd730d2ca97 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:29:38 -0400 Subject: [PATCH 25/57] fix(auth): sign out when confirmSignIn throws the identity pool's error, as signIn does When the user pool accepted the answer and the identity pool step after it failed, confirmSignIn threw the identity pool's error but left the user signed in in memory with tokens it never saved: fetchAuthSession reported signed in, getCurrentUser returned the user, signIn was refused as already signed in, and a relaunch signed the user out. signIn, signInWithWebUI and autoSignIn cancel the sign-in and wait for the sign-out on the same failure. confirmSignIn now does the same before it throws, when it sent the answer: the sign-in's refresh token is revoked and the user ends signed out. A sign-in it did not make, adopted while it waited, is left as it is. --- .../Task/AWSAuthConfirmSignInTask.swift | 39 +++++++- .../AWSAuthConfirmSignInTaskTests.swift | 89 +++++++++++++++++-- 2 files changed, 115 insertions(+), 13 deletions(-) diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignInTask.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignInTask.swift index cd6e63fa1f..f2a3fa2dc9 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignInTask.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/AWSAuthConfirmSignInTask.swift @@ -58,7 +58,8 @@ final class AWSAuthConfirmSignInTask: AuthConfirmSignInTask, DefaultLogger, @unc throw invalidStateError } - try await analyzeCurrentStateAndCreateEvent(authNState, invalidStateError) + // Whether this call sent the answer, rather than finding a sign-in another app's reconcile adopted. + let answered = try await analyzeCurrentStateAndCreateEvent(authNState, invalidStateError) let stateSequences = await authStateMachine.listen() log.verbose("Waiting for response") @@ -72,8 +73,13 @@ final class AWSAuthConfirmSignInTask: AuthConfirmSignInTask, DefaultLogger, @unc return AuthSignInResult(nextStep: .done) } else if case .error(let error) = authZState { // The user pool accepted the answer but the identity pool step failed. The machine stays - // here, so without this arm the call would never return. + // here, so without this arm the call would never return. The tokens were never saved, so the + // sign-in is cancelled, as `signIn` cancels it: the user ends signed out, never signed in + // in memory only. A sign-in this call did not make is left as it is. log.verbose("Authorization reached an error state \(error)") + if answered { + await waitForSignInCancel() + } throw error.authError } else { log.verbose("Signed In, waiting for authorization to complete") @@ -97,13 +103,18 @@ final class AWSAuthConfirmSignInTask: AuthConfirmSignInTask, DefaultLogger, @unc throw invalidStateError } - fileprivate func analyzeCurrentStateAndCreateEvent(_ authNState: AuthenticationState, _ invalidStateError: AuthError) async throws { + /// Sends the answer for the sign-in step `authNState` waits on, and returns `true`; or returns `false` when the + /// state is already signed in and nothing is sent. + fileprivate func analyzeCurrentStateAndCreateEvent( + _ authNState: AuthenticationState, + _ invalidStateError: AuthError + ) async throws -> Bool { // The shared keychain reconcile may have adopted a sibling app's // sign-in while this flow was waiting for an answer. No event needs // to be dispatched; the listener loop in execute() observes // .signedIn and returns .done. if case .signedIn = authNState { - return + return false } guard case .signingIn(let signInState) = authNState else { @@ -162,6 +173,26 @@ final class AWSAuthConfirmSignInTask: AuthConfirmSignInTask, DefaultLogger, @unc default: throw invalidStateError } + return true + } + + /// Cancels the sign-in and waits until the user is signed out, as `AWSAuthSignInTask` does when a sign-in fails. + /// A sign-out that fails ends the wait too, so the caller still gets its error. + private func waitForSignInCancel() async { + await authStateMachine.send(AuthenticationEvent(eventType: .cancelSignIn)) + let stateSequences = await authStateMachine.listen() + log.verbose("Wait for signIn to cancel") + for await state in stateSequences { + guard case .configured(let authenticationState, _, _) = state else { + continue + } + switch authenticationState { + case .signedOut, .signingOut(.error): + return + default: + continue + } + } } private func answerTOTPSetup(_ setUpTOTPState: SignInTOTPSetupState, _ invalidStateError: AuthError) async throws { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift index 47e2242776..35d80ab116 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift @@ -864,30 +864,39 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest, @unchecked Senda } } - /// Test that confirmSignIn throws the identity pool's error when the user pool accepted the answer + /// Test that confirmSignIn throws the identity pool's error when the user pool accepted the answer, and leaves the + /// user signed out, as signIn does /// /// - Given: an auth plugin with mocked service. `RespondToAuthChallenge` accepts the code and answers tokens, - /// and the identity pool's `GetId` fails with `InvalidParameterException` + /// the identity pool's `GetId` fails with `InvalidParameterException`, and `RevokeToken` succeeds /// - When: - /// - I invoke confirmSignIn with a valid confirmation code, then getCurrentUser + /// - I invoke confirmSignIn with a valid confirmation code /// - Then: /// - confirmSignIn throws a `.service` error with `AWSCognitoAuthError.invalidParameter` as underlying error, /// instead of never returning - /// - the Auth call queued behind it returns too, each within the bound + /// - the sign-in was cancelled as `signIn` cancels it: the tokens it got are revoked, the state machine is + /// signed out, `fetchAuthSession` reports signed out and `getCurrentUser` throws `signedOut` + /// - a second confirmSignIn throws `invalidState` without sending anything /// - func testConfirmSignInThrowsTheIdentityPoolsError() async { + func testConfirmSignInThrowsTheIdentityPoolsError() async throws { mockIdentity = MockIdentity( mockGetIdResponse: { _ in throw AWSCognitoIdentity.InvalidParameterException(message: "Invalid parameter passed") }, mockGetCredentialsResponse: getCredentials ) + let answered = TestCounter() + let revoked = TestCounter() mockIdentityProvider = MockIdentityProvider( + mockRevokeTokenResponse: { _ in + revoked.increment() + return RevokeTokenOutput() + }, mockRespondToAuthChallengeResponse: { _ in + answered.increment() return .testData() }) let confirmed = expectation(description: "confirmSignIn returns") - let queued = expectation(description: "the queued call returns") Task { do { @@ -902,10 +911,72 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest, @unchecked Senda XCTFail("Should produce a service error instead of \(error)") } confirmed.fulfill() - _ = try? await self.plugin.getCurrentUser() - queued.fulfill() + } + await fulfillment(of: [confirmed], timeout: 5) + + guard case .configured(.signedOut, _, _) = await plugin.authStateMachine.currentState else { + return XCTFail("the sign-in was not cancelled: \(await plugin.authStateMachine.currentState)") + } + XCTAssertEqual(revoked.get(), 1) + let session = try await plugin.fetchAuthSession(options: .init()) + XCTAssertFalse(session.isSignedIn) + do { + let user = try await plugin.getCurrentUser() + XCTFail("getCurrentUser returned \(user.username)") + } catch AuthError.signedOut { + } catch { + XCTFail("getCurrentUser threw \(error), not signedOut") + } + do { + let result = try await plugin.confirmSignIn(challengeResponse: "code") + XCTFail("a second confirmSignIn returned \(result)") + } catch AuthError.invalidState { + } catch { + XCTFail("a second confirmSignIn threw \(error), not invalidState") + } + XCTAssertEqual(answered.get(), 1) + } + + /// Test that confirmSignIn leaves a sign-in it did not make as it is when its identity pool step has failed + /// + /// - Given: an auth plugin already signed in (a reconcile adopted a sibling app's sign-in while this flow was + /// waiting for an answer) whose authorization is in an error state + /// - When: + /// - I invoke confirmSignIn + /// - Then: + /// - it throws that error without sending anything, and the user stays signed in: nothing is revoked + /// + func testConfirmSignInLeavesAnAdoptedSignInWhoseAuthorizationFailed() async throws { + let revoked = TestCounter() + let userPool = MockIdentityProvider( + mockRevokeTokenResponse: { _ in + revoked.increment() + return RevokeTokenOutput() + }, + mockRespondToAuthChallengeResponse: { _ in + XCTFail("Cognito service should not be called when already signed in") + return .testData() + }) + let signedInPlugin = configureCustomPluginWith( + userPool: { userPool }, + initialState: .configured( + .signedIn(.testData), + .error(.sessionError(.notAuthorized, .testData)), + .notStarted + ) + ) + + do { + let result = try await signedInPlugin.confirmSignIn(challengeResponse: "code") + XCTFail("Should not succeed, got \(result)") + } catch is AuthError { + } catch { + XCTFail("Should produce an AuthError instead of \(error)") } - await fulfillment(of: [confirmed, queued], timeout: 5, enforceOrder: true) + guard case .configured(.signedIn, _, _) = await signedInPlugin.authStateMachine.currentState else { + return XCTFail("the adopted sign-in was cancelled: \(await signedInPlugin.authStateMachine.currentState)") + } + XCTAssertEqual(revoked.get(), 0) } } From 3528c7b989f00439b373c09edcb3cf266e6ee219 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:29:38 -0400 Subject: [PATCH 26/57] fix(auth): replace an access-group login with the signed-out record in one update Under an access group the sign-out read the record, then wrote the signed-out record with a set, which outside native macOS checks for the item and then updates it. Another process in the group that deleted the record in between (an older plugin's or a guest's sign-out, the client's purge) made the update fail, so the sign-out failed and went back to signed in with tokens it had already revoked, where deleting the record had treated an absent one as success. A login is now replaced with replaceIfPresent, a single update that writes only over a record still there; finding none means the session is already signed out, and the sign-out succeeds. A record already signed out is left as it is, so nothing is written over it. --- .../AWSCognitoAuthCredentialStore.swift | 22 +- .../EngineKeychainStore.swift | 6 + ...cessGroupSignedOutRecordTests+Writes.swift | 213 ++++++++++++++++++ .../AccessGroupSignedOutRecordTests.swift | 20 +- 4 files changed, 247 insertions(+), 14 deletions(-) create mode 100644 AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests+Writes.swift diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift index c7428acaab..1059fc1c8a 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/AWSCognitoAuthCredentialStore.swift @@ -249,14 +249,18 @@ extension AWSCognitoAuthCredentialStore: AmplifyAuthCredentialStoreBehavior { /// federation to the identity pool. /// /// Without an access group the session record is deleted. With one, a record that holds a login (user pool - /// tokens or a federated identity), or is already signed out, is replaced by the signed-out record, - /// `{"noCredentials":{}}`, the bytes the Cognito client's default session saves when it signs a session out, - /// which every plugin release reads as signed out. In the shared service ending a login then counts as migrated: - /// an app extension's first access-group migration stops at the record + /// tokens or a federated identity) is replaced by the signed-out record, `{"noCredentials":{}}`, the bytes the + /// Cognito client's default session saves when it signs a session out, which every plugin release reads as signed + /// out; a record already signed out is left as it is. In the shared service ending a login then counts as + /// migrated: an app extension's first access-group migration stops at the record /// (`sharedKeychainHasMigratedItems(accessGroup:source:)`) and never moves its own stale login in over it. The /// next sign-in replaces the record. Any other record is deleted, as without an access group: a guest's, so a /// guest alone still never stops a login's migration, and one this store cannot read. With nothing saved, /// nothing is written, as the client writes nothing when there is nothing to sign out. + /// + /// The login is replaced in one update, which writes only if the record is still there: another process in the + /// group that deletes it after the read (an older plugin's or a guest's sign-out, the client's purge) leaves the + /// session signed out, and this sign-out succeeds, as a delete of an absent record does. package func deleteCredential() throws { guard let accessGroup else { try removeSession(for: authConfiguration) @@ -264,10 +268,14 @@ extension AWSCognitoAuthCredentialStore: AmplifyAuthCredentialStoreBehavior { } // Read as the migration's checks read the shared service, quietly: a read failure is logged at verbose only. // The read still logs the keychain store's verbose lines for opening the service and retrieving an item. + let sessionKey = generateSessionKey(for: authConfiguration) let shared = EngineKeychainStore.quiet(makeKeychainStore(sharedService, accessGroup), logger: logger) - switch Self.savedSession(at: generateSessionKey(for: authConfiguration), in: shared) { - case .login, .signedOut: - try saveCredential(.noCredentials) + switch Self.savedSession(at: sessionKey, in: shared) { + case .login: + // `false`: the record is gone, so the session is already signed out. + _ = try keychain._replaceIfPresent(encode(object: AmplifyCredentials.noCredentials), key: sessionKey) + case .signedOut: + return case .guest, .unknown: try removeSession(for: authConfiguration) } diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/EngineKeychainStore.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/EngineKeychainStore.swift index 39a5699d5a..4ffb070a54 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/EngineKeychainStore.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/CredentialStorage/EngineKeychainStore.swift @@ -69,6 +69,12 @@ package struct EngineKeychainStore: Sendable { try EngineCredentialStoreError.mapping { try itemStore.set(value, key: key) } } + /// Replaces the value stored under `key` only if an item is stored there, in one update. + /// - Returns: `true` if the item was replaced, `false` if no item existed (nothing is written). + package func _replaceIfPresent(_ value: Data, key: String) throws -> Bool { + try EngineCredentialStoreError.mapping { try itemStore.replaceIfPresent(value, key: key) } + } + /// Remove key-value pair from Keychain based on the provided key. package func _remove(_ key: String) throws { try EngineCredentialStoreError.mapping { try itemStore.remove(key) } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests+Writes.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests+Writes.swift new file mode 100644 index 0000000000..525bf70d7a --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests+Writes.swift @@ -0,0 +1,213 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AmplifyKeychainTestCommon +import AWSCognitoIdentityProvider +import Foundation +import InternalAmplifyKeychain +import XCTest +@testable import Amplify +@testable import AWSCognitoAuthPlugin +@testable import InternalAWSCognitoAuth + +/// How the access-group sign-out writes the signed-out record: in one update that writes only over a record still +/// there, never over one already signed out, and failing the sign-out when the write fails; and the keychain queries it +/// issues. +extension AccessGroupSignedOutRecordTests { + + /// Test that a sign-out completes when another process deletes the record while it is being replaced + /// + /// - Given: alice's login in the shared service, and the plugin configured with the access group over a keychain + /// whose `set` checks for the item, then updates it, as `KeychainItemStore.set` does outside native macOS; another + /// process in the group deletes the record between any such check and its update + /// - When: + /// - the plugin signs out + /// - Then: + /// - the sign-out completes, the user is signed out, and the record is the signed-out record: the replace is one + /// update, with no check before it + /// + func testASignOutCompletesWhenAnotherProcessDeletesTheRecordBeforeTheWrite() async throws { + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + let keychain = keychain! + let sessionAccount = sessionAccount + let plugin = makePlugin( + accessGroup: accessGroup, + userPool: signOutUserPool(), + makeKeychainStore: { service, accessGroup in + CheckThenUpdateItemStore(base: keychain.store(service: service, accessGroup: accessGroup)) { key in + if key == sessionAccount { + try? keychain.store(service: service, accessGroup: accessGroup).remove(key) + } + } + } + ) + + let result = await plugin.signOut() + + guard let signOut = result as? AWSCognitoSignOutResult, case .complete = signOut else { + return XCTFail("the sign-out did not complete: \(result)") + } + guard case .configured(.signedOut, _, _) = await plugin.authStateMachine.currentState else { + return XCTFail("the sign-out ended in \(await plugin.authStateMachine.currentState)") + } + XCTAssertEqual(sharedRecord(), signedOutRecord) + } + + /// Test that the store leaves the session signed out when another process deletes the record after the read + /// + /// - Given: alice's login in the shared service, and another process in the group that deletes the record right + /// after the store reads it + /// - When: + /// - the store with the access group ends its session + /// - Then: + /// - it does not throw, and writes no record: the session is already signed out + /// + func testTheStoreWritesNothingWhenTheRecordIsDeletedAfterTheRead() throws { + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + let store = makeStore(accessGroup: accessGroup) + let keychain = keychain! + let sharedService = sharedService + let accessGroup = accessGroup + keychain.afterRead { service, account in + keychain.afterRead(nil) + try? keychain.store(service: sharedService, accessGroup: accessGroup).remove(account) + XCTAssertEqual(service, sharedService) + } + + try store.deleteCredential() + + XCTAssertNil(sharedRecord()) + } + + /// Test that the store writes nothing over a record that is already signed out + /// + /// - Given: the signed-out record in the shared service + /// - When: + /// - the store with the access group ends its session + /// - Then: + /// - the record is unchanged, and nothing was written or removed + /// + func testTheStoreWritesNothingOverASignedOutRecord() throws { + try keychain.store(service: sharedService, accessGroup: accessGroup).set(signedOutRecord, key: sessionAccount) + let store = makeStore(accessGroup: accessGroup) + keychain.resetMutations() + + try store.deleteCredential() + + XCTAssertEqual(sharedRecord(), signedOutRecord) + XCTAssertEqual(keychain.mutations, []) + } + + /// Test that a sign-out whose write of the signed-out record fails reports the failure and keeps the login + /// + /// - Given: alice's login in the shared service, the plugin configured with the access group, and a keychain that + /// refuses every write + /// - When: + /// - the plugin signs out + /// - Then: + /// - the sign-out fails, rather than completing with the login still saved, and the record still holds alice + /// + func testASignOutWhoseWriteFailsReportsTheFailure() async throws { + let alice = try JSONEncoder().encode(LongLivedCredentials.userPoolAndIdentityPool()) + try keychain.store(service: sharedService, accessGroup: accessGroup).set(alice, key: sessionAccount) + let plugin = makePlugin(accessGroup: accessGroup, userPool: signOutUserPool()) + keychain.failing(.write, with: errSecInteractionNotAllowed) + defer { keychain.clearFailures() } + + let result = await plugin.signOut() + + guard let signOut = result as? AWSCognitoSignOutResult, case .failed = signOut else { + return XCTFail("the sign-out did not fail: \(result)") + } + XCTAssertEqual(sharedRecord(), alice) + } + + /// Test the keychain queries a sign-out and a user deletion issue under an access group + /// + /// - Given: alice's login in the shared service, and the plugin configured with the access group over a keychain + /// that records every query + /// - When: + /// - the plugin signs out; then, signed in again, deletes the user + /// - Then: + /// - each reads the session record, then replaces it with the signed-out record in one update, in the shared + /// service and group, and deletes nothing there + /// + func testTheKeychainQueriesOfASignOutAndAUserDeletionUnderAnAccessGroup() async throws { + let recorder = KeychainQueryRecorder(keychain: keychain) + recorder.substitute(sessionAccount, with: "") + let sessionWrites = { () -> [String] in + recorder.queries.filter { $0.contains("") && $0.contains(" | \(self.sharedService) | ") } + } + let expected = [ + "store | getData | \(sharedService) | \(accessGroup) | -> ok", + "store | replaceIfPresent | \(sharedService) | \(accessGroup) | -> true" + ] + + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + let signingOut = makePlugin( + accessGroup: accessGroup, + userPool: signOutUserPool(), + makeKeychainStore: recorder.makeKeychainStore + ) + _ = try? await signingOut.getCurrentUser() + recorder.reset() + _ = await signingOut.signOut() + XCTAssertEqual(Array(sessionWrites().suffix(2)), expected, "\(recorder.queries)") + XCTAssertFalse(recorder.queries.contains { $0.contains(" | remove | \(sharedService) | ") && $0.contains("") }) + + try makeStore(accessGroup: accessGroup).saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + let deleting = makePlugin( + accessGroup: accessGroup, + userPool: signOutUserPool(), + makeKeychainStore: recorder.makeKeychainStore + ) + _ = try? await deleting.getCurrentUser() + recorder.reset() + try await deleting.deleteUser() + XCTAssertEqual(Array(sessionWrites().suffix(2)), expected, "\(recorder.queries)") + XCTAssertFalse(recorder.queries.contains { $0.contains(" | remove | \(sharedService) | ") && $0.contains("") }) + XCTAssertEqual(sharedRecord(), signedOutRecord) + } +} + +/// A store whose `set` is `KeychainItemStore.set` outside native macOS: a check for the item, then an update if it was +/// there (failing with `errSecItemNotFound` if it is gone by then) or an add. `betweenCheckAndUpdate` runs in between, +/// as another process's write would land. Every other call goes to `base`. +private struct CheckThenUpdateItemStore: KeychainItemStoreBehavior { + let base: any KeychainItemStoreBehavior + let betweenCheckAndUpdate: @Sendable (_ key: String) -> Void + + func set(_ value: Data, key: String) throws { + let exists = try base.dataIfPresent(key) != nil + betweenCheckAndUpdate(key) + if exists { + guard try base.replaceIfPresent(value, key: key) else { + throw KeychainAccessError.securityError(errSecItemNotFound) + } + } else if try !base.addIfAbsent(value, key: key) { + _ = try base.replaceIfPresent(value, key: key) + } + } + + func getData(_ key: String) throws -> Data { try base.getData(key) } + func addIfAbsent(_ value: Data, key: String) throws -> Bool { try base.addIfAbsent(value, key: key) } + func replaceIfPresent(_ value: Data, key: String) throws -> Bool { try base.replaceIfPresent(value, key: key) } + func move(_ key: String, to destination: KeychainItemAttributes) throws -> KeychainMoveOutcome { + try base.move(key, to: destination) + } + + func move(_ entry: KeychainEntry, to destination: KeychainItemAttributes) throws -> KeychainMoveOutcome { + try base.move(entry, to: destination) + } + + func remove(_ key: String) throws { try base.remove(key) } + func remove(_ entry: KeychainEntry) throws { try base.remove(entry) } + func removeAll() throws { try base.removeAll() } + func hasItems() throws -> Bool { try base.hasItems() } + func allAccounts() throws -> [String] { try base.allAccounts() } + func allEntries() throws -> [KeychainEntry] { try base.allEntries() } +} diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift index 699e3361bd..1745689ed0 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift @@ -453,15 +453,20 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { } /// The plugin's credential store over the shared keychain, as `makeCredentialStore` builds it, without migration: - /// the app's configuration's, unless `configuration` is given. - func makeStore(accessGroup: String?, configuration: AuthConfiguration? = nil) -> AWSCognitoAuthCredentialStore { + /// the app's configuration's, unless `configuration` is given; over the item stores `makeKeychainStore` builds, if + /// it is given. + func makeStore( + accessGroup: String?, + configuration: AuthConfiguration? = nil, + makeKeychainStore: AWSCognitoAuthCredentialStore.KeychainStoreFactory? = nil + ) -> AWSCognitoAuthCredentialStore { let keychain = keychain! return AWSCognitoAuthCredentialStore( authConfiguration: configuration ?? authConfiguration, accessGroup: accessGroup, migrateKeychainItemsOfUserSession: false, userDefaults: userDefaults, - makeKeychainStore: { service, accessGroup in + makeKeychainStore: makeKeychainStore ?? { service, accessGroup in keychain.store(service: service, accessGroup: accessGroup) }, logger: AmplifyEngineLogRouter() @@ -476,14 +481,15 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { ) } - /// A plugin as `configure(using:)` builds it, with its credential store from `makeStore(accessGroup:)` and only - /// the Cognito service calls mocked. Its credential store machine starts idle, so its first load skips the + /// A plugin as `configure(using:)` builds it, with its credential store from + /// `makeStore(accessGroup:makeKeychainStore:)` and only the Cognito service calls mocked. Its credential store machine starts idle, so its first load skips the /// AWSMobileClient migration, unless `legacyStore` is given: it then starts as at a launch, and the migration /// runs over those stores first. func makePlugin( accessGroup: String?, userPool: CognitoUserPoolBehavior, - legacyStore: (@Sendable (String) -> any KeychainItemStoreBehavior)? = nil + legacyStore: (@Sendable (String) -> any KeychainItemStoreBehavior)? = nil, + makeKeychainStore: AWSCognitoAuthCredentialStore.KeychainStoreFactory? = nil ) -> AWSCognitoAuthPlugin { let identity = MockIdentity( mockGetIdResponse: { _ in .init(identityId: "client-identity-id") }, @@ -503,7 +509,7 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { authConfiguration: authConfiguration, makeUserPool: { userPool }, makeIdentityClient: { identity }, - credentialStoreFactory: { [self] in makeStore(accessGroup: accessGroup) }, + credentialStoreFactory: { [self] in makeStore(accessGroup: accessGroup, makeKeychainStore: makeKeychainStore) }, legacyKeychainStoreFactory: legacyStore ?? Defaults.makeLegacyStore(service:), logger: AmplifyEngineLogRouter(scope: .category("awsCognitoAuthPluginTest")), userPoolAnalytics: { MockAnalyticsHandler() }, From 072d0cb4d714e6092714cc309fb9462402a76b57 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:29:38 -0400 Subject: [PATCH 27/57] fix(auth): suggest waiting for the queued call when a session's own sheet is still closing A second call on a session whose earlier call is queued behind its own closing sheet was told to retry once the sheet had closed or to pass .wait(timeout:), but the queued call starts by itself, and another call is refused again. It now keeps the "still closing" description and suggests waiting for the earlier call to finish, or cancelling it, as a call queued behind another session's sheet is told. A refusal with no call queued keeps its suggestion. --- .../Sources/Core/SessionCore+WebUI.swift | 4 ++-- .../HostedUI/AuthClientError+HostedUI.swift | 14 ++++++++++++-- .../Sources/HostedUI/SystemSheetLock.swift | 2 +- .../Tests/UnitTests/SystemSheetLockTests.swift | 5 ++++- .../Tests/UnitTests/WebUISignInTests.swift | 9 ++++++++- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+WebUI.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+WebUI.swift index 2fcb89f690..77f59585e5 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+WebUI.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+WebUI.swift @@ -724,7 +724,7 @@ extension SessionCore { /// | The previous call | Reason | `holder` | /// |---|---|---| /// | queued behind another session's sheet | `.alreadyQueued` | the session holding it | - /// | queued behind this session's own closing sheet (a retry after a cancel) | `.stillClosing` | this session | + /// | queued behind this session's own closing sheet (a retry after a cancel) | `.stillClosingWithCallQueued` | this session | /// | holding the sheet (this session holds it) | `.alreadyInFlight` | this session | /// | neither: before its lease, or committing after it | `.signInInProgress` | the session holding the sheet, else this session | private nonisolated func webUISignInInProgress() async -> AuthClientError { @@ -734,7 +734,7 @@ extension SessionCore { } if holder == sessionId { // Queued while holding: the lock queues a session only behind its own sheet once that sheet is closing. - return .browserBusy(heldBy: sessionId, requestedBy: sessionId, reason: queued ? .stillClosing : .alreadyInFlight) + return .browserBusy(heldBy: sessionId, requestedBy: sessionId, reason: queued ? .stillClosingWithCallQueued : .alreadyInFlight) } return .browserBusy(heldBy: holder ?? sessionId, requestedBy: sessionId, reason: .signInInProgress) } diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/AuthClientError+HostedUI.swift b/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/AuthClientError+HostedUI.swift index dbae16d5c2..505e6b1cf3 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/AuthClientError+HostedUI.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/AuthClientError+HostedUI.swift @@ -23,10 +23,13 @@ enum BrowserBusyReason: Equatable { /// The asking session is already queued for the sheet, which another session holds. case alreadyQueued - /// The asking session's previous flow was cancelled and its sheet is still closing, whether or not the session - /// has queued a retry behind it. + /// The asking session's previous flow was cancelled and its sheet is still closing. case stillClosing + /// As `stillClosing`, with the session's earlier call already queued behind that sheet: the earlier call starts + /// by itself once the sheet has closed, so the suggestion is to wait for it, not to retry. + case stillClosingWithCallQueued + /// The asking session's previous hosted-UI sign-in call is still in progress, and is neither holding the sheet /// nor queued for it: before its lease (waiting for the sign-in lock, or listing the other sessions), or after it, /// committing. The holder is another session, named, or, when none holds the sheet, the asking session itself. @@ -71,6 +74,13 @@ extension AuthClientError { "Session \"\(holder)\"'s previous system sheet is still closing.", "Retry once it has closed, or pass `whenBrowserBusy: .wait(timeout:)` to start as soon as it has." ) + case .stillClosingWithCallQueued: + return .browserBusy( + holder: holder, + "Session \"\(holder)\"'s previous system sheet is still closing.", + "The earlier call is queued and starts by itself once the sheet has closed: wait for it to finish, " + + "or cancel it. One session queues for the sheet at most once." + ) case .heldByAnotherSession: return .browserBusy( holder: holder, diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/SystemSheetLock.swift b/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/SystemSheetLock.swift index aff6566ebc..75fe73d0c7 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/SystemSheetLock.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/HostedUI/SystemSheetLock.swift @@ -403,7 +403,7 @@ actor SystemSheetLock { continuation.resume(throwing: AuthClientError.browserBusy( heldBy: holding, requestedBy: session, - reason: holding == session ? .stillClosing : .alreadyQueued + reason: holding == session ? .stillClosingWithCallQueued : .alreadyQueued )) return } diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/SystemSheetLockTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/SystemSheetLockTests.swift index f175ea1b1f..b765e34a71 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/SystemSheetLockTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/SystemSheetLockTests.swift @@ -584,7 +584,8 @@ final class SystemSheetLockTests: XCTestCase { /// - alice asks again, once with `.fail` and once with `.wait`, and the old flow then unwinds /// - Then: /// - both repeat calls throw `browserBusy(holder: alice)` with the "still closing" message, not the queued one - /// that would say alice waits for a sheet alice holds; only the first retry is ever queued, and it is served + /// that would say alice waits for a sheet alice holds, and a suggestion to wait for the queued retry rather + /// than to retry; only the first retry is ever queued, and it is served func testARepeatCallQueuedBehindItsOwnClosingSheetIsToldItIsStillClosing() async throws { let lock = SystemSheetLock() let closing = Gate() @@ -605,6 +606,8 @@ final class SystemSheetLockTests: XCTestCase { } catch let error as AuthClientError { XCTAssertEqual(error.kind, .browserBusy(holder: Self.alice), "\(policy)") XCTAssertEqual(error.errorDescription, closingError.errorDescription, "\(policy)") + XCTAssertNotEqual(error.recoverySuggestion, closingError.recoverySuggestion, "\(policy)") + XCTAssertTrue(error.recoverySuggestion.hasPrefix("The earlier call is queued"), error.recoverySuggestion) } } let queued = await lock.waiterCount diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignInTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignInTests.swift index c9b322bf76..a8f76f124f 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignInTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignInTests.swift @@ -285,7 +285,8 @@ final class WebUISignInTests: XCTestCase { /// - When: "work" asks again, with `.fail` and with `.wait(timeout:)` /// - Then: /// - each second call is refused at once with `browserBusy` naming "work", saying its previous sheet is still - /// closing, not that "work" is waiting for a sheet "work" holds + /// closing, not that "work" is waiting for a sheet "work" holds, and suggesting to wait for the queued retry, + /// which starts by itself, not to retry /// - once the old browser closes, the retry is shown and signs in; the engine was called twice func testADoubleTapQueuedBehindItsOwnClosingSheetSaysItIsStillClosing() async throws { let client = try client(work) @@ -306,6 +307,12 @@ final class WebUISignInTests: XCTestCase { let error = await authClientError { try await startSignIn(client, options: WebUIOptions(whenBrowserBusy: policy)).value(within: 10) } XCTAssertEqual(error?.kind, .browserBusy(holder: work), "\(policy)") XCTAssertEqual(error?.errorDescription, "Session \"work\"'s previous system sheet is still closing.", "\(policy)") + XCTAssertEqual( + error?.recoverySuggestion, + "The earlier call is queued and starts by itself once the sheet has closed: wait for it to finish, " + + "or cancel it. One session queues for the sheet at most once.", + "\(policy)" + ) } browser.finish(.failure(CancellationError())) From 62d3c4b43bcf72cf5684868415b5dcceb4ca4da2 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:29:51 -0400 Subject: [PATCH 28/57] docs(auth): release notes and decisions for the secret redactions, the confirmSignIn sign-out and the access-group record write Add a Security row for each secret the sign-in or sign-up logged in 2.63.0: the confirmSignIn answer, the TOTP setup's verified session and the auto sign-in session of a sign-up result, and the client's row for the engine's lines. The confirmSignIn row says it signs out as signIn does. The challenge-session row no longer describes an app-visible error text no sign-in reaches. The AWSMobileClient row and decision 36 say the sign-out clears only the signing-out app's old keychain locations, name app suites sharing an access group, and give the exception of an app that leaves its access group with migration. The access-group row says a signed-out record is left as it is and a login is replaced in one update, corrects what the record changes for the listing and an extension's migration, and lists what a process reading another's signed-out record now sees. The hosted-UI row quotes 2.63.0's text and the new suggestion. signOutStoredSession's Returns doc names the no-window hosted-UI error a sign-out that could not finish can carry. --- .../AmplifyCognitoClient+StoredSessions.swift | 4 +++- .../MigrateLegacyCredentialStore.swift | 7 ++++--- ...ccessGroupSignedOutRecordTests+Migrations.swift | 3 ++- .../ChallengeSessionRedactionTests.swift | 4 ++++ docs/design/RELEASE-NOTES-PENDING.md | 14 +++++++++----- docs/design/auth-client-DECISIONS.md | 10 ++++++---- 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/AmplifyCognitoClient+StoredSessions.swift b/AmplifyClients/AmplifyCognitoClient/Sources/AmplifyCognitoClient+StoredSessions.swift index be089fb17b..1e4fbc5aa8 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/AmplifyCognitoClient+StoredSessions.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/AmplifyCognitoClient+StoredSessions.swift @@ -106,7 +106,9 @@ public extension AmplifyCognitoClient { /// when the session is signed out on this device. `.failed`, and the session is still signed in: /// `storageUnavailable` if storage could not be read or written, or `storageUnavailable(.interrupted)` if /// the record kept changing under the sign-out (the revoke's failure, if any, is its underlying error, mapped as - /// any other call maps it); `sessionConfigurationMismatch` if a client for this session is live in this process + /// any other call maps it; when the revoke succeeded, a session whose sign-in shared the browser's cookies has + /// the no-window hosted-UI error underneath, whose text says the session was signed out on this device, though + /// it is not); `sessionConfigurationMismatch` if a client for this session is live in this process /// with a different user pool or identity pool, whose records this call would change (sign it out through that /// client); `invalidState` if a different user signed in meanwhile; `unknown` with an underlying /// `CancellationError` if the calling task was cancelled before anything was revoked. diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/MigrateLegacyCredentialStore.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/MigrateLegacyCredentialStore.swift index 9f7bf984d2..907b0983b4 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/MigrateLegacyCredentialStore.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/CredentialStore/MigrateLegacyCredentialStore.swift @@ -552,9 +552,10 @@ extension MigrateLegacyCredentialStore { /// A signed-out record (`{"noCredentials":{}}`) is not absent: a sign-out through the Cognito client's default /// session saves it, and migrating over it would sign back in the user who signed out. It counts as a session /// does, as the access-group migration weighs it (`AWSCognitoAuthCredentialStore.SavedSession.signedOut`). The - /// plugin saves one only under an access group, where its sign-out already cleared the legacy stores, so this - /// rarely changes what a plugin-only app migrates: only legacy data that survived or reappeared after a sign-out - /// is dropped. + /// plugin saves one only under an access group, where its sign-out already cleared the signing-out app's own + /// legacy stores (named after its bundle ID), so this rarely changes what a plugin-only app migrates: only legacy + /// data that survived or reappeared after a sign-out is dropped, or that another app sharing the group still holds + /// in its own legacy stores, as over a login the group holds. private func existingSession( in amplifyCredentialStore: AmplifyAuthCredentialStoreBehavior ) -> ExistingSession { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests+Migrations.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests+Migrations.swift index 2d4c041b94..495fc98ec9 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests+Migrations.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests+Migrations.swift @@ -67,7 +67,8 @@ extension AccessGroupSignedOutRecordTests { /// group, and still migrates it without one /// /// - Given: alice's login, and the plugin signed out, with the access group and without; then AWSMobileClient - /// stores that still hold a login, as when the sign-out's clear of them failed, or a login was saved there since + /// stores that still hold a login, as when the sign-out's clear of them failed, a login was saved there since, or + /// they are another app's in the same group, named after its bundle ID, which this app's sign-out never clears /// - When: /// - the plugin configures over them, as at the next launch, which runs the AWSMobileClient migration /// - Then: diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/ChallengeSessionRedactionTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/ChallengeSessionRedactionTests.swift index adeed2ae9a..66ac90e312 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/ChallengeSessionRedactionTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/ChallengeSessionRedactionTests.swift @@ -202,6 +202,10 @@ final class ChallengeSessionRedactionTests: XCTestCase { /// Test that the error of an MFA setup the plugin cannot continue carries the challenge without its session /// + /// No sign-in reaches this throw today: the response that would carry such a challenge is refused earlier, with + /// "Cannot initiate MFA setup from available Types: …". The action is run on its own, so the text stays redacted + /// if a path to it appears. + /// /// - Given: an MFA setup challenge with a session and a username that offers only SMS, which the plugin does not /// set up /// - When: diff --git a/docs/design/RELEASE-NOTES-PENDING.md b/docs/design/RELEASE-NOTES-PENDING.md index 47eba22a00..fba00d109e 100644 --- a/docs/design/RELEASE-NOTES-PENDING.md +++ b/docs/design/RELEASE-NOTES-PENDING.md @@ -10,10 +10,13 @@ behaviour. | Change | Who sees it | Details | |---|---|---| -| Security: the sign-in's verbose lines no longer print the challenge session. The challenge a sign-in answers (the engine's `RespondToAuthChallenge`) had no description of its own, so every verbose line that printed it by reflection showed its session in full, and the username: the "Sending event" lines of the sign-in actions when Cognito answers with a challenge (SRP, custom auth, `USER_AUTH`, migrate auth, auto sign-in after sign-up, and each `confirmSignIn` answer), and the lines that print a sign-in state holding one. It now prints with the session as `` and its two usernames masked (`alice` as `al*ce`), as the sign-in data already did, in `print`, `debugPrint`, interpolation and `dump`. The other fields print as they did nested in an event or state in 2.63.0; `parameters`, Cognito's challenge parameters, still print in full, and can hold a username (`USERNAME` on a custom challenge). Printed on its own, the challenge now names its type in full (`InternalAWSCognitoAuth.RespondToAuthChallenge(…)`). The one app-visible text that prints it is the `AuthError.unknown` an MFA setup throws when it offers only SMS, which the plugin does not set up: "Unable to determine next step from challenge:" and the challenge, which held the session and username in full in 2.63.0 | Anyone reading or collecting verbose logs; apps that log or show the text of `AuthError.unknown` from `signIn` or `confirmSignIn` | — | -| Fix: `confirmSignIn` now throws when the user pool accepts the answer but the identity pool step after it fails (`GetId` or `GetCredentialsForIdentity`), with the identity pool's error, as `signIn` does (for example `AuthError.service` with `AWSCognitoAuthError.invalidParameter`), and logs the verbose line `Authorization reached an error state …` that `signIn` logs there. In 2.63.0 it never returned, and every Auth call queued behind it waited too | Apps with an identity pool that sign in with a challenge (MFA, a new password, custom auth, a factor selection) | — | -| Upgrading from the AWS Mobile SDK (AWSMobileClient, Amplify v1): a signed-out record (`{"noCredentials":{}}`) in the plugin's saved session now stops the migration, as a login does: nothing migrates over it, the old keychain locations are cleared, and the plugin logs the info line `A signed-out session already exists, discarding legacy credentials`. In 2.63.0 the migration read that record as no session, so an old AWSMobileClient login migrated over it and signed the user back in. A sign-out through `AmplifyCognitoClient`'s default session saves that record, and so, with a keychain access group set, does the plugin's own sign-out (the access-group row below). Without an access group the plugin's sign-out deletes the record, so an app that uses only the plugin without one is not affected; with one, the plugin's sign-out already clears the old keychain locations, so only an old login that survived that clear, or was saved there since, is now dropped instead of migrated. A guest is still not a login: a guest saved over the signed-out record (by the plugin, an app extension or the client) lets an old login migrate over it again | Apps upgraded from AWSMobileClient or Amplify v1 that use `AmplifyCognitoClient`'s default session or a keychain access group, or roll back or forward between a client build and a plugin build | [Design](AGREED-DESIGN-AmplifyCognitoClient.md) §4.8 | -| Behaviour change: with a keychain access group set, ending a login through the plugin (`signOut`, local or global; `clearFederationToIdentityPool`; the sign-out after `deleteUser`) now saves the signed-out record, `{"noCredentials":{}}`, in the shared keychain service instead of deleting the session record: when the record holds a login (user pool tokens or a federated identity), or is already signed out. A guest's sign-out still deletes the guest's record, as does a sign-out over a record the plugin cannot read, and a sign-out with nothing saved writes nothing, so neither stops a login's migration. Those are the bytes `AmplifyCognitoClient`'s default session saves when it signs a session out, and every plugin release reads them as signed out (releases back to 2.10.0 decode them as `AmplifyCredentials.noCredentials`, the value for no saved session). The next sign-in replaces the record. The record then stops an app extension's first access-group migration (`migrateKeychainItems: true`), as a login does, so a stale login in the extension's own keychain is never moved in over a user who signed out; in 2.63.0 only a device record left by a user-pool sign-in stopped it. An extension's first access-group transition without migration counts the record too, as it counts a login, and keeps the extension's own old keychain items instead of clearing them. The AWSMobileClient (Amplify v1) migration treats the record as a signed-out session (the row above): it migrates nothing over it and clears the old locations, which the sign-out already clears, until a guest replaces the record; a guest `AmplifyCognitoClient`'s default session fetches over it clears those logins first, best effort. A plugin sign-out after a configuration change that copied the login now survives a rollback to the build with the earlier configuration, whose rule copies the signed-out record over the earlier copy, as after a sign-out through the client. Without an access group nothing changes: the record is deleted. `AmplifyCognitoClient.storedSessions(…, includingSignedOut: true)` in that access group now lists `.default` as a signed-out row after a plugin sign-out of a login (with no label or username unless the client saved them beside the record), where it listed no `.default` row; the default listing is unchanged. Logs under an access group: every plugin sign-out (a guest's and one with nothing saved included) first reads the record, which logs the keychain store's verbose `[KeychainStore]` lines for opening the shared service and retrieving an item (`Initialized keychain…`, `Started retrieving…`, then `Successfully retrieved…` or `No Keychain item found…`); a sign-out of a login then logs the lines for setting an item in place of those for removing one, and at the next launch the plugin no longer logs the info line `No existing session found.`, and its verbose credential-store line sends `completedOperation` with no credentials where it sent `throwError(itemNotFound)` | Apps with a keychain access group, especially with app extensions; apps that list stored sessions with `includingSignedOut: true` | [Decisions](auth-client-DECISIONS.md), decision 40 | +| Security: `confirmSignIn`'s answer is no longer printed on the sign-in's verbose lines. While Cognito checks an answer, the sign-in's challenge state (`SignInChallengeState.verifying`) held it in full and printed it by reflection, so the plugin's verbose `Checking next step for:` line showed every answer `confirmSignIn` sent through a challenge: an SMS, email or TOTP code, a password after a `PASSWORD` or `PASSWORD_SRP` factor selection, a new password, a custom challenge's answer. In 2.63.0 each one was logged in full there. The state no longer holds the answer. The answer's own printed form, which the TOTP setup's verbose lines (`Checking next step for:`, `Auth state change`) and the `Sending event` line of an answer sent again after Cognito refused the saved device print, showed it masked (`S3*******21`, four characters of a six-digit code); it now shows ``, in `print`, `debugPrint`, interpolation and `dump` | Anyone reading or collecting verbose logs | — | +| Security: the TOTP setup's verified session is no longer printed. When a sign-in sets up TOTP, `VerifyTOTPSetup`'s verbose `Sending event` line printed the session `VerifySoftwareToken` returns, which answers the MFA setup challenge and finishes the sign-in, in full in 2.63.0. It now prints `respondToAuthChallenge("")`, and the event redacts the session in every printed form | Anyone reading or collecting verbose logs | — | +| Security: a sign-up result no longer prints the auto sign-in session. The plugin's tasks log their result at info ("Successfully completed execution for … with result:"), and `AuthSignUpStep.completeAutoSignIn(Session)` had no description of its own, so in 2.63.0 `confirmSignUp`, and a `signUp` Cognito confirms at once (a passwordless sign-up), logged the session in full at info; with the username, the session signs the user in. `AuthSignUpStep` now conforms to `CustomStringConvertible`, `CustomDebugStringConvertible` and `CustomReflectable`, and prints `completeAutoSignIn("")` in `print`, `debugPrint`, interpolation and `dump`, the step and the `AuthSignUpResult` holding it alike; the other steps print as before. An additive API change; `case .completeAutoSignIn(let session)` still reads the session | Anyone reading or collecting info-level logs; apps that print or dump a sign-up result or step | — | +| Security: the sign-in's verbose lines no longer print the challenge session. The challenge a sign-in answers (the engine's `RespondToAuthChallenge`) had no description of its own, so every verbose line that printed it by reflection showed its session in full, and the username: the "Sending event" lines of the sign-in actions when Cognito answers with a challenge (SRP, custom auth, `USER_AUTH`, migrate auth, auto sign-in after sign-up, and each `confirmSignIn` answer), and the lines that print a sign-in state holding one. It now prints with the session as `` and its two usernames masked (`alice` as `al*ce`), as the sign-in data already did, in `print`, `debugPrint`, interpolation and `dump`. The other fields print as they did nested in an event or state in 2.63.0; `parameters`, Cognito's challenge parameters, still print in full, and can hold a username (`USERNAME` on a custom challenge). Printed on its own, the challenge now names its type in full (`InternalAWSCognitoAuth.RespondToAuthChallenge(…)`). The user's answer and the TOTP setup's session are the rows above | Anyone reading or collecting verbose logs | — | +| Fix: `confirmSignIn` now throws when the user pool accepts the answer but the identity pool step after it fails (`GetId` or `GetCredentialsForIdentity`), with the identity pool's error (for example `AuthError.service` with `AWSCognitoAuthError.invalidParameter`), and logs the verbose line `Authorization reached an error state …`, as `signIn` does. Before it throws, it cancels the sign-in as `signIn` does there: the refresh token the sign-in got is revoked, nothing is saved, and the user is signed out, so `fetchAuthSession()` reports signed out, `getCurrentUser()` throws `signedOut`, and the next `signIn` starts a new sign-in; another `confirmSignIn` throws `invalidState`. In 2.63.0 it never returned, and every Auth call queued behind it waited too | Apps with an identity pool that sign in with a challenge (MFA, a new password, custom auth, a factor selection) | — | +| Upgrading from the AWS Mobile SDK (AWSMobileClient, Amplify v1): a signed-out record (`{"noCredentials":{}}`) in the plugin's saved session now stops the migration, as a login does: nothing migrates over it, the old keychain locations are cleared, and the plugin logs the info line `A signed-out session already exists, discarding legacy credentials`. In 2.63.0 the migration read that record as no session, so an old AWSMobileClient login migrated over it and signed the user back in. A sign-out through `AmplifyCognitoClient`'s default session saves that record, and so, with a keychain access group set, does the plugin's own sign-out (the access-group row below). Without an access group the plugin's sign-out deletes the record, so an app that uses only the plugin without one is not affected, unless it later leaves an access group with `.none(migrateKeychainItemsOfUserSession: true)`: that carries a signed-out record from the group into the unshared service, where it stops the migration too. With one, the plugin's sign-out clears the old keychain locations of the app that signs out, which are named after its bundle ID; so an old login that survived that clear, or was saved there since, or that another app sharing the group still holds in its own old locations, is now dropped and cleared at that app's next launch instead of migrated. In 2.63.0 another app's old login was migrated over a sign-out (the record was deleted), and dropped only over a login the group held. A guest is still not a login: a guest saved over the signed-out record (by the plugin, an app extension or the client) lets an old login migrate over it again | Apps upgraded from AWSMobileClient or Amplify v1 that use `AmplifyCognitoClient`'s default session or a keychain access group, including app suites whose apps share an access group, or roll back or forward between a client build and a plugin build | [Design](AGREED-DESIGN-AmplifyCognitoClient.md) §4.8 | +| Behaviour change: with a keychain access group set, ending a login through the plugin (`signOut`, local or global; `clearFederationToIdentityPool`; the sign-out after `deleteUser`) now saves the signed-out record, `{"noCredentials":{}}`, in the shared keychain service instead of deleting the session record, when the record holds a login (user pool tokens or a federated identity); a record already signed out is left as it is. The login is replaced in one keychain update that writes only over a record still there, so a sign-out still completes when another process in the group deletes the record after the plugin read it (an older plugin's or a guest's sign-out, the client's purge), and writes nothing. A guest's sign-out still deletes the guest's record, as does a sign-out over a record the plugin cannot read, and a sign-out with nothing saved writes nothing, so neither stops a login's migration. Those are the bytes `AmplifyCognitoClient`'s default session saves when it signs a session out, and every plugin release reads them as signed out (releases back to 2.10.0 decode them as `AmplifyCredentials.noCredentials`, the value for no saved session). The next sign-in replaces the record. The record then stops an app extension's first access-group migration (`migrateKeychainItems: true`), as a login does, so a stale login in the extension's own keychain is never moved in over a user who signed out. In 2.63.0 only the device records a sign-in leaves stopped it; every user-pool sign-in saves the advanced-security device ID, so that already stopped it after a user-pool user's sign-out, and the record matters mainly after the sign-out of a federated identity (`clearFederationToIdentityPool`), which leaves no device record. An extension's first access-group transition without migration counts the record too, as it counts a login, and keeps the extension's own old keychain items instead of clearing them. The AWSMobileClient (Amplify v1) migration treats the record as a signed-out session (the row above): it migrates nothing over it and clears the old locations, which the sign-out already clears for the app that signs out, until a guest replaces the record; a guest `AmplifyCognitoClient`'s default session fetches over it clears those logins first, best effort. A plugin sign-out after a configuration change that copied the login now survives a rollback to the build with the earlier configuration, whose rule copies the signed-out record over the earlier copy, as after a sign-out through the client. Without an access group nothing changes: the record is deleted. `AmplifyCognitoClient.storedSessions(…, includingSignedOut: true)` in that access group now lists `.default` as a signed-out row after a plugin sign-out of a login, with no label or username, where it listed no `.default` row; when the client saved a label or username beside the record, an absent record was already listed as a signed-out row with them, and still is. The default listing is unchanged. A process that reads a record another process signed out sees the signed-out record instead of none: `clearFederationToIdentityPool()` there, while still federated in memory, throws `AuthError.service("Unable to find credentials to clear for federation.")` where it threw `AuthError.unknown("Unable to clear credential store: …")`, and a sign-out there no longer logs the error line `Unable to update credentials with error: …`. Logs under an access group: every plugin sign-out (a guest's and one with nothing saved included) first reads the record, which logs the keychain store's verbose `[KeychainStore]` lines for opening the shared service and retrieving an item (`Initialized keychain…`, `Started retrieving…`, then `Successfully retrieved…` or `No Keychain item found…`); a sign-out of a login then logs the lines for replacing an item only if present (``Replacing `Data` only if present…``, then `Conditional write succeeded…`, or `Conditional write refused…` when the record is gone) in place of those for removing one, and a sign-out over a record already signed out logs neither, and at the next launch the plugin no longer logs the info line `No existing session found.`, and its verbose credential-store line sends `completedOperation` with no credentials where it sent `throwError(itemNotFound)` | Apps with a keychain access group, especially with app extensions; apps that list stored sessions with `includingSignedOut: true` | [Decisions](auth-client-DECISIONS.md), decision 40 | | One new log line of the `InternalAWSCognitoAuth/.swift` form, the info line above in `MigrateLegacyCredentialStore`, so the state-machine actions' `\(#fileID)` sites go from 254 to 255, in 66 files | Anyone filtering or parsing plugin logs by file prefix | — | ## AmplifyCognitoClient (`@_spi(AmplifyExperimental)`, beta) @@ -21,6 +24,7 @@ behaviour. | Change | Details | |---|---| | Security: the engine's verbose lines no longer print a sign-in challenge's session: a sent challenge answer (`initiateTOTPSetup`, say) shows it as ``, beside the usernames the client already redacted. In 2.63.0 it was printed in full | — | +| Security: the engine's verbose lines no longer print the TOTP setup's verified session: `VerifyTOTPSetup`'s `Sending event` line, which printed the session `VerifySoftwareToken` returns in full in 2.63.0, shows `respondToAuthChallenge("")`. The `Sending event` line of an answer sent again after Cognito refused the saved device shows the answer as ``, where 2.63.0 masked it (`S3*******21`) | — | | A Cognito internal error (`InternalErrorException`, from either pool) and an error status the SDK does not model are `AuthClientError.service` with no code in every call: `USER_AUTH`, auto, device SRP and passkey sign-ins, `deleteUser`, a sign-out's revoke and global sign-out, and the passkey credential calls included. In 2.63.0 they were `AuthClientError.unknown`, and an internal error in those calls and sign-ins still suggested reporting a bug. A 5xx or an internal error suggests a retry, and any other status says the service refused the request with an error the client does not recognise (the description has the status and message). In a `.partial` sign-out, the revoke's or global sign-out's suggestion says instead that it will not be retried; the global sign-out's says the user's other sessions stay signed in, without the "unless the request reached the service" that only a transport failure's has. Code that matched `.unknown` for these must match `.service(nil, …)`. The credentials providers report both cases the same way, so a provider's `CredentialsError` and its `disposition` don't change. The Auth plugin keeps `AuthError.unknown` | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Errors" | | A refresh, a sign-out's revoke and the passkey credential calls report a transport failure as every other call does: a response that is not JSON, or a bad server response, is `.service` with no code, "The service returned a response that could not be read." and a retry suggestion; a network failure has the network texts, with the network error underneath; any other URL loading failure is "The service request failed.". In 2.63.0 an unreadable body on a refresh kept the engine's text, a passkey call that failed offline said "This should not happen", and an unreadable revoke had an empty description. In a `.partial` sign-out, the revoke's and global sign-out's suggestion says instead that it will not be retried | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Errors" | | A sign-out that can't finish, because the saved record kept changing (`.failed(.storageUnavailable(.interrupted))`) or became unreadable (`.failed(.unknown)`), carries the first server-side failure as its underlying error, the global sign-out's first, mapped as any other call maps it, with the network or retry suggestion. In 2.63.0, after a failed global sign-out, it carried the empty placeholder `revokeTokenError` of a revoke that was never sent, and a revoke that failed on the network said there that the session was signed out on this device and the revoke would not be retried, though the session was still signed in. A sign-in that reaches a challenge as a sign-out, purge or user deletion ends the session throws the sign-in-cancelled `.invalidState`, where it could return the challenge the sign-out had ended | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Errors" | @@ -33,6 +37,6 @@ behaviour. | AWSMobileClient (Amplify v1) logins: a `.default` sign-out or purge clears them before it writes the signed-out login or deletes the login, so a failed write or delete, or an app stopped right after it, leaves none for a plugin build to migrate back. 2.63.0 cleared them after. `fetchAuthSession()` on `.default` that saves a guest over a signed-out record clears them again first, best effort, so a login a sign-out failed to clear is not migrated back once the guest replaces the signed-out record. That is the sign-out's whole clear, so it also removes what AWSMobileClient, or the AWS SDK for iOS used beside the client, saved in those services since the sign-out: an app that uses the SDK's `AWSCognitoCredentialsProvider` loses the identity ID and AWS credentials it cached for any identity pool then, and gets a new identity ID. The guest's clear never runs over an absent record, nor over a label set before anyone signed in (an app that never signed in through the client keeps them for the plugin to migrate), nor for a named session. **Rollback:** plugin releases before 2.63.0 migrate those logins over whatever `.default` holds, with no check; 2.63.0 keeps a user signed in through the client, but migrates over a guest and over a signed-out `.default`; later releases also keep a signed-out `.default`, until a guest replaces it (the Auth plugin row above) | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Rolling back and forward" | | A hosted-UI sign-out whose logout page is closed (by the user, `cancelWebUISignIn()` or `resetSystemSheet()`) on a `.default` session the Auth plugin saved as expired returns `.failed(.userCancelled)` and keeps the session, where 2.63.0 signed it out on the plugin's saved flag (`.complete`, or `.partial` with no `hostedUIError`). On a session a refresh found expired while it was in memory (until every handle and provider for it is released, or a relaunch), a closed page still signs out on this device, with `.partial`'s `hostedUIError` `.userCancelled`, but only while the login being signed out still has the refresh token found dead: a new login another app in the access group saved since is kept, `.failed(.userCancelled)`, where 2.63.0 revoked it and signed it out | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Hosted-UI sign-out" | | `cancelWebUISignIn()`: a cancelled passkey call throws `.userCancelled` once it has released the system sheet, as a hosted-UI sign-in does, so `systemSheetHolder` no longer names the session by then, where 2.63.0 answered while the sheet was still closing and an immediate retry got `browserBusy`. If the sheet isn't released within a second, the call throws anyway, and the sheet stays held until the passkey ceremony finishes | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Cancelling and recovering" | -| Hosted UI: a second hosted-UI sign-in on a session whose first is still queued behind another session's sheet throws `browserBusy(holder:)` naming the session that holds the sheet ("Session "work" is already waiting for the system sheet, which session "home" holds."), where it named itself. One whose first is in progress but neither holds nor waits for the sheet (before it asks, or committing after) says "Session "work" already has a hosted-UI sign-in in progress.", adding ", and session "home" holds the system sheet" when another session holds it; `holder` is then that session, or the session itself. A second call on a session whose retry is queued behind its own closing sheet (after `cancelWebUISignIn()`, while the cancelled browser is still closing) says "Session "work"'s previous system sheet is still closing.", where it said "Session "work" is already waiting for the system sheet, which session "work" holds.". A sign-out, purge or user deletion that lands just as a hosted-UI attempt begins makes that sign-in throw the sign-in-cancelled `.invalidState` ("The sign-in was cancelled: the session was signed out, purged or deleted while it was in progress."), as one landing at any other point does, where it threw `.userCancelled`. A hosted-UI response whose tokens fail the client's checks (another app client or user pool, another sign-in's nonce, no single user) suggests "Sign in again. If it keeps failing, check that the configuration's user pool, app client and hosted UI domain belong together.", where it suggested `prompt: [.login]`, which cannot change what the tokens say; a user other than the one `identityExpectation` asked for still suggests it | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Hosted UI" | +| Hosted UI: a second hosted-UI sign-in on a session whose first is still queued behind another session's sheet throws `browserBusy(holder:)` naming the session that holds the sheet ("Session "work" is already waiting for the system sheet, which session "home" holds."), where it named itself. One whose first is in progress but neither holds nor waits for the sheet (before it asks, or committing after) says "Session "work" already has a hosted-UI sign-in in progress.", adding ", and session "home" holds the system sheet" when another session holds it; `holder` is then that session, or the session itself. A second call on a session whose retry is queued behind its own closing sheet (after `cancelWebUISignIn()`, while the cancelled browser is still closing) says "Session "work"'s previous system sheet is still closing." and suggests "The earlier call is queued and starts by itself once the sheet has closed: wait for it to finish, or cancel it. One session queues for the sheet at most once.", where 2.63.0 refused a second hosted-UI sign-in with "Session "work" already has a system sheet in progress.", and the sheet lock refused any other call of the queued session (a hosted-UI sign-out page, say) with "Session "work" is already waiting for the system sheet, which session "work" holds.". A sign-out, purge or user deletion that lands just as a hosted-UI attempt begins makes that sign-in throw the sign-in-cancelled `.invalidState` ("The sign-in was cancelled: the session was signed out, purged or deleted while it was in progress."), as one landing at any other point does, where it threw `.userCancelled`. A hosted-UI response whose tokens fail the client's checks (another app client or user pool, another sign-in's nonce, no single user) suggests "Sign in again. If it keeps failing, check that the configuration's user pool, app client and hosted UI domain belong together.", where it suggested `prompt: [.login]`, which cannot change what the tokens say; a user other than the one `identityExpectation` asked for still suggests it | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Hosted UI" | | Texts: the recovery suggestion of either pool's invalid-configuration answer (`InvalidUserPoolConfigurationException`, `InvalidIdentityPoolConfigurationException`, both `.configuration`) names the user pool's settings (device tracking, for the device calls), the identity pool's settings and the client's configuration, where it named only the user pool's. `resendSignUpCode` with an empty username throws `.validation(field: "username")` saying "Username is required to resendSignUpCode", where it named `confirmSignUp`, as the plugin's does. A `resendSignUpCode` answer with no code delivery details is `.unknown` with the plugin's texts ("Unexpected error occurred with message: Unable to get Auth code delivery details"), where it had its own. The warning logged when an interrupted sign-in's record could not be deleted says that a signed-in or federated session's next restore deletes it, and that a signed-out or guest session's next restore may offer the challenge again until it is 15 minutes old, where it said the next restore deletes it | — | | Documented, unchanged since 2.63.0: with no client for the session live in the process, `signOutStoredSession` revokes through SDK clients built from the configuration alone, so `Options.configureUserPoolClient` (a custom endpoint, a proxy, certificate pinning) is not applied to it. Hold a client for the session to sign out through it | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Signing out or purging a saved session without a client" | diff --git a/docs/design/auth-client-DECISIONS.md b/docs/design/auth-client-DECISIONS.md index 105aace8a0..e83eb8eb60 100644 --- a/docs/design/auth-client-DECISIONS.md +++ b/docs/design/auth-client-DECISIONS.md @@ -560,7 +560,7 @@ client's README and the release notes. #38 and #39 keep limitations that the cli | 37 | A Cognito internal error (`InternalErrorException`, from either pool) and an error status the SDK does not model, in the client | **`.service` with no code**, in every call, `USER_AUTH`, auto, device SRP and passkey sign-ins, `deleteUser`, a sign-out's revoke or global sign-out, and the passkey credential calls included, with a recovery suggestion to retry (a 5xx, an internal error) or naming the refusal (a 4xx). In a `.partial` sign-out, the revoke's or global sign-out's suggestion says instead that it will not be retried; a sign-out that could not finish (`.failed`) carries the failure as any other call maps it. The plugin keeps `AuthError.unknown`. | | 38 | A named session's configuration marker left naming an earlier configuration, by a purge stopped before its last step | **Not changed.** The next restore under the purge's configuration carries the session from the earlier one again, and the README says to purge the session again before building a client for it. | | 39 | `signOutStoredSession` with no client for the session live in the process, and `configureUserPoolClient` | **Not given one.** Its revoke uses SDK clients built from the configuration alone, so an app's `configureUserPoolClient` (a custom endpoint, a proxy, certificate pinning) is not applied; hold a client for the session to sign out through it. A purge revokes nothing. | -| 40 | The plugin's own sign-out under a keychain access group | **It saves the signed-out record**, `{"noCredentials":{}}`, in the shared service instead of deleting the session record, on every path that ends a login: `signOut` (local or global), `clearFederationToIdentityPool` and the local sign-out after `deleteUser`, when the record holds a login (user pool tokens or a federated identity) or is already signed out. A guest's sign-out still deletes the guest's record, as does one over a record the plugin cannot read, and a sign-out with nothing saved writes nothing, as the client's `.default` writes nothing when there is nothing to sign out. A plugin sign-out of a login then counts as migrated, as a sign-out through the client's `.default` does. The migration rule is unchanged: a login or a signed-out record stops it; a guest or a configuration record alone doesn't stop a login. Not limited to `migrateKeychainItems`. Without an access group the record is still deleted. Changes #21. | +| 40 | The plugin's own sign-out under a keychain access group | **It saves the signed-out record**, `{"noCredentials":{}}`, in the shared service instead of deleting the session record, on every path that ends a login: `signOut` (local or global), `clearFederationToIdentityPool` and the local sign-out after `deleteUser`, when the record holds a login (user pool tokens or a federated identity) or is already signed out (one already signed out is left as it is, the same bytes). A guest's sign-out still deletes the guest's record, as does one over a record the plugin cannot read, and a sign-out with nothing saved writes nothing, as the client's `.default` writes nothing when there is nothing to sign out. A plugin sign-out of a login then counts as migrated, as a sign-out through the client's `.default` does. The migration rule is unchanged: a login or a signed-out record stops it; a guest or a configuration record alone doesn't stop a login. Not limited to `migrateKeychainItems`. Without an access group the record is still deleted. Changes #21. | **#35.** The rule exists because a second writer may still save the refresh that used the token, and a forced refresh refused this way is evidence of the same dead token as an unforced one, so it counts too. Keeping the @@ -570,9 +570,11 @@ token was used, and no one saved the refresh that used it. **#36.** A sign-out through the client's `.default` saves that record, and so, under an access group, does the plugin's own sign-out from the release after 2.63.0 (#40). Without an access group the plugin's sign-out deletes the -record, so an app that uses only the plugin without one is not affected; with one, the plugin's sign-out has already -cleared the old logins, so only a login that survived that clear, or was saved there since, is dropped instead of -migrated. Before, the plugin read the record as no session, so an +record, so an app that uses only the plugin without one is not affected, unless it later leaves an access group with +`.none(migrateKeychainItemsOfUserSession: true)`, which carries a signed-out record into the unshared service, where it +stops the migration too. With one, the plugin's sign-out has already cleared the old logins of the app that signs out +(their services are named after its bundle ID), so only a login that survived that clear, or was saved there since, or +that another app sharing the group holds in its own services, is dropped instead of migrated. Before, the plugin read the record as no session, so an AWSMobileClient login that the client's sign-out failed to clear, or that an app stopped part-way kept, was migrated over it and signed the user back in. The clear comes first so that a failed write or delete, or a process stopped after it, leaves no login behind. A purge leaves no record, so after one only the clear protects the user. The record From 5b16e8bc276c35a3d1f97fffa5b81ff07c77c5de Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 13:07:37 -0400 Subject: [PATCH 29/57] test(auth): pin the resumed access-group migration's configuration record and a login saved since A resumed migration killed before the login moves now has a test: the configuration record stays in the unshared service, so the next launch still carries the login after an app extension with the new configuration has saved its record again. A resumed migration in the same update as a configuration change also keeps a login an app extension saved under the new configuration's account, not only a signed-out record. --- ...entialStoreInterruptedMigrationTests.swift | 47 +++++++++++++++++-- .../GuestFirstAccessGroupMigrationTests.swift | 28 +++++++++++ 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AWSCognitoAuthCredentialStoreInterruptedMigrationTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AWSCognitoAuthCredentialStoreInterruptedMigrationTests.swift index cfdba68025..ea9e04790c 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AWSCognitoAuthCredentialStoreInterruptedMigrationTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AWSCognitoAuthCredentialStoreInterruptedMigrationTests.swift @@ -284,7 +284,7 @@ class AWSCognitoAuthCredentialStoreInterruptedMigrationTests: XCTestCase { func testAMigrationKilledBeforeTheLoginMovedStillCarriesItAfterAnExtensionStarts() throws { let alice = try populateUnsharedServiceOnAUserPoolOnlyConfiguration() - try killFirstMigration(atTheMoveOf: userPoolOnlySessionAccount) + try killMigration(atTheMoveOf: userPoolOnlySessionAccount) XCTAssertNotNil(keychain.value(service: service, account: userPoolOnlySessionAccount)) XCTAssertNotNil(keychain.value(service: service, account: "authConfiguration")) @@ -314,7 +314,7 @@ class AWSCognitoAuthCredentialStoreInterruptedMigrationTests: XCTestCase { func testAMigrationKilledAfterTheLoginMovedStillCarriesItAfterAnExtensionStarts() throws { let alice = try populateUnsharedServiceOnAUserPoolOnlyConfiguration() - try killFirstMigration(atTheMoveOf: "authConfiguration") + try killMigration(atTheMoveOf: "authConfiguration") XCTAssertEqual(try keychain.store(service: service).allAccounts(), ["authConfiguration"]) try startAnExtensionThatSavesAGuest() @@ -326,6 +326,42 @@ class AWSCognitoAuthCredentialStoreInterruptedMigrationTests: XCTestCase { XCTAssertNil(userDefaults.object(forKey: migrationInProgressKey)) } + /// A resumed migration killed before the login moved, in the same update as a configuration change, leaves the + /// configuration record in the unshared service, so the next launch still carries the login after an app extension + /// with the new configuration has started again. + /// + /// - Given: alice signed in on a user-pool-only configuration without an access group, over a keychain that + /// lists the configuration record first; the update adds the identity pool and the access group with + /// migration, and its first launch is killed just before alice's session record moves; an app extension with + /// the new configuration starts in the access group and saves its configuration record and a guest's session + /// record there; the resumed launch is killed just before alice's session record moves too; then the extension + /// starts again and saves its configuration record there once more + /// - When: + /// - the app launches again with the new configuration, the access group and migration + /// - Then: + /// - at the resumed launch's kill, alice's session and the configuration record are still in the unshared + /// service, and the mark names the access group + /// - after the relaunch, the new store reads alice's login, carried to the new configuration's account + /// - nothing of the plugin's is left in the unshared service, and the mark is gone + func testAResumedMigrationKilledBeforeTheLoginMovedStillCarriesItAfterAnExtensionStartsAgain() throws { + let alice = try populateUnsharedServiceOnAUserPoolOnlyConfiguration() + try killMigration(atTheMoveOf: userPoolOnlySessionAccount) + try startAnExtensionThatSavesAGuest() + + try killMigration(atTheMoveOf: userPoolOnlySessionAccount) + + XCTAssertNotNil(keychain.value(service: service, account: userPoolOnlySessionAccount)) + XCTAssertNotNil(keychain.value(service: service, account: "authConfiguration")) + XCTAssertEqual(userDefaults.string(forKey: migrationInProgressKey), accessGroup) + try startAnExtensionThatSavesAGuest() + + let relaunched = makeStore(accessGroup: accessGroup, migrate: true, listingTheConfigurationRecordFirst: true) + + XCTAssertEqual(try relaunched.retrieveCredential(), alice) + XCTAssertEqual(try keychain.store(service: service).allAccounts(), []) + XCTAssertNil(userDefaults.object(forKey: migrationInProgressKey)) + } + // MARK: Helpers /// The configuration before the update: `authConfiguration`'s user pool, without its identity pool. Its login @@ -349,9 +385,10 @@ class AWSCognitoAuthCredentialStoreInterruptedMigrationTests: XCTestCase { return try store.retrieveCredential() } - /// Runs the first launch with the access group, migration and `authConfiguration`, over a keychain that lists the - /// configuration record first, killed just before `account` moves. - private func killFirstMigration(atTheMoveOf account: String) throws { + /// Runs a launch with the access group, migration and `authConfiguration`, over a keychain that lists the + /// configuration record first, killed just before `account` moves: the update's first launch, or, once a kill has + /// left the mark, a resumed one. + private func killMigration(atTheMoveOf account: String) throws { let killPoint = KillPoint(keychain: keychain, userDefaults: userDefaults, suite: userDefaultsSuite) keychain.beforeMove { moving in if moving == account { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/GuestFirstAccessGroupMigrationTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/GuestFirstAccessGroupMigrationTests.swift index c6fddaf286..b5aa189683 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/GuestFirstAccessGroupMigrationTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/GuestFirstAccessGroupMigrationTests.swift @@ -466,6 +466,34 @@ class GuestFirstAccessGroupMigrationTests: XCTestCase { XCTAssertEqual(try keychain.store(service: service).allAccounts(), ["authConfiguration"]) } + /// A resumed migration in the same update as a configuration change does not carry the app's login over a login + /// an app extension saved under the new configuration's account since the interruption. + /// + /// - Given: the state a migration killed just before its first move leaves, in the update that adds the identity + /// pool and the access group: alice's login on a user-pool-only configuration still in the unshared service, + /// and the mark naming the access group; then an app extension's plugin with the new configuration starts in the + /// access group and signs bob in, which saves its configuration record and bob's login in the shared service + /// - When: + /// - the app launches again with the new configuration, the access group and migration + /// - Then: + /// - the shared service still holds bob's login, and the new store reads it + /// - the app's configuration record collides with the shared one and stays in the unshared service + func testAResumedMigrationDoesNotCarryTheLoginOverALoginAnExtensionSavedSince() throws { + try makeStore(accessGroup: nil, authConfiguration: userPoolOnlyConfiguration) + .saveCredential(LongLivedCredentials.userPoolAndIdentityPool()) + userDefaults.set(accessGroup, forKey: migrationInProgressKey) + let bob = LongLivedCredentials.userPoolAndIdentityPool(username: "bob") + try makeExtensionStore(migrate: true).saveCredential(bob) + let saved = try XCTUnwrap(keychain.value(service: sharedService, accessGroup: accessGroup, account: sessionAccount)) + XCTAssertNotNil(keychain.value(service: sharedService, accessGroup: accessGroup, account: "authConfiguration")) + + let store = makeStore(accessGroup: accessGroup, migrate: true) + + XCTAssertEqual(keychain.value(service: sharedService, accessGroup: accessGroup, account: sessionAccount), saved) + XCTAssertEqual(try store.retrieveCredential(), bob) + XCTAssertEqual(try keychain.store(service: service).allAccounts(), ["authConfiguration"]) + } + // MARK: Helpers /// alice signs in through the client's `.default` in the access group, then signs out: the shared service holds From 8ae7f6db005768e91aaa1a17e8185cb334a8df2a Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 13:35:24 -0400 Subject: [PATCH 30/57] ci(auth): refuse a first sandbox provision unless the profile holds its own credentials With no account recorded yet, the first-run guard now also reads `aws configure list` and goes on only when the profile AWS_PROFILE names supplies the credentials itself: keys in the credentials or config file, sso, login, a credential_process, or a role_arn it assumes. An EC2 instance role, container credentials, the environment, a legacy credentials file, no credentials, or a profile the CLI cannot resolve is refused before any AWS call, so a profile that holds only a region can no longer build the sandbox in an instance role's account. The exported-keys and web identity refusals are unchanged, and a recorded account is still checked against the caller's identity. --- .../Tests/IntegrationTests/infra/lib.sh | 61 ++++++++++--- .../IntegrationTests/infra/test_provision.sh | 86 +++++++++++++++++-- 2 files changed, 130 insertions(+), 17 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/lib.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/lib.sh index 2c9a27a0b7..ca8cda737a 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/lib.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/lib.sh @@ -76,22 +76,47 @@ require_free_disk() { fi } +# The credential types `aws configure list` reports for credentials the profile itself configures: keys in the shared +# credentials or config file, IAM Identity Center (sso), an `aws login` session, a credential_process, a role_arn it +# assumes (from a source_profile or a credential_source), and a role_arn it assumes with a web_identity_token_file. +# Each was set up in the profile, so it acts in the account the profile was made for. The rest (env, container-role, +# iam-role, ec2-credentials-file, boto-config) are what the CLI falls back to when the profile holds none. +PROFILE_CREDENTIAL_TYPES=(shared-credentials-file config-file sso login custom-process assume-role + assume-role-with-web-identity) + +# The type `aws configure list` reports for the access key of the profile AWS_PROFILE names: "None" when it resolves +# no credentials, "" when the command fails (a profile that does not exist, say). Reads the table of the CLI's current +# releases ("access_key : : : ") and of older ones (the same columns, spaces only). Prints a +# credential type, never a key; the CLI masks the key anyway. +credential_type() { + local list line pattern='^[[:space:]]*access_key[[:space:]:]+[^[:space:]]+[[:space:]:]+([^[:space:]]+)' + list=$(command aws configure list 2>/dev/null) || return 0 + while IFS= read -r line; do + line="${line///}" + if [[ "$line" =~ $pattern ]]; then + printf '%s' "${BASH_REMATCH[1]}" + return 0 + fi + done <<<"$list" +} + # Exits if $STATE_DIR/state.json records an account other than the caller's, so a profile for # another account can never act on this sandbox's recorded resources (or mint a second sandbox under # the same state directory). Sets ACCOUNT, which is never printed. Run before any mutating call. # With no account recorded yet (the first provision.sh), nothing else checks the account, so it also exits, before -# any AWS call, unless AWS_PROFILE is set and none of the variables that give the CLI credentials of their own is: -# AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN (exported keys), AWS_ROLE_ARN and -# AWS_WEB_IDENTITY_TOKEN_FILE (a web identity role, common on CI runners and EKS), and any AWS_CONTAINER_CREDENTIALS_* -# (container credentials). The CLI takes exported keys over every profile, and a web identity role before the -# profile's own credentials. Container credentials are different: the CLI uses them, like an EC2 instance role, -# AWS_CREDENTIAL_FILE and the boto files, only when the profile AWS_PROFILE names resolves no credentials of its own. -# They are refused anyway, which is conservative. That is all it checks. It does not check that the profile is the -# sandbox account's, or that it holds credentials of its own: with a profile that holds none (only a region, say), -# the CLI falls back to an instance role or one of those files, which this check lets through, and a first run then -# builds the sandbox in that account and records it. +# any call to an AWS service, unless AWS_PROFILE is set, none of the variables that give the CLI credentials of their +# own is exported, and the profile holds credentials of its own. The variables are AWS_ACCESS_KEY_ID, +# AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN (exported keys, which the CLI takes over every profile), AWS_ROLE_ARN and +# AWS_WEB_IDENTITY_TOKEN_FILE (a web identity role, common on CI runners and EKS, which it takes before the profile's +# own credentials), and any AWS_CONTAINER_CREDENTIALS_* (container credentials). The profile check reads `aws configure +# list`, which resolves the profile's credentials the way every other call does and makes no call to an AWS service +# (fetching an assumed role's, IAM Identity Center's or a login session's credentials aside), and accepts only +# PROFILE_CREDENTIAL_TYPES. +# A profile that holds none (only a region, say) would leave the CLI to fall back to an EC2 instance role, container +# credentials, AWS_CREDENTIAL_FILE or the boto files, and a first run would then build the sandbox in that account and +# record it. It does not check that the profile is the sandbox account's: the profile named is trusted to be. require_recorded_account() { - local recorded="" exported=() variable + local recorded="" exported=() variable credentials_from reason if [[ -f "$STATE_DIR/state.json" ]]; then recorded=$(python3 -c "import json,sys;print(json.load(open(sys.argv[1])).get('account',''))" "$STATE_DIR/state.json") fi @@ -113,6 +138,20 @@ require_recorded_account() { "first run." >&2 exit 1 fi + credentials_from=$(credential_type) + if [[ " ${PROFILE_CREDENTIAL_TYPES[*]} " != *" ${credentials_from:-unknown} "* ]]; then + case "$credentials_from" in + "") reason="the CLI cannot resolve the profile's credentials (a missing profile, an expired sign-in)" ;; + None) reason="the profile AWS_PROFILE names resolves no credentials" ;; + *) reason="the profile AWS_PROFILE names takes its credentials from $credentials_from" ;; + esac + echo "Refusing: no account is recorded in $STATE_DIR/state.json yet, and $reason" \ + "(\`aws configure list\`). A first run uses only credentials the profile holds itself (keys, sso," \ + "login, credential_process or a role_arn): without them the CLI falls back to an EC2 instance role," \ + "container credentials or a legacy credentials file, and the run would build the sandbox in that" \ + "account." >&2 + exit 1 + fi fi ACCOUNT=$(aws sts get-caller-identity --query Account --output text) if [[ -n "$recorded" && "$recorded" != "$ACCOUNT" ]]; then diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/test_provision.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/test_provision.sh index f05f4d6a47..94776277be 100755 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/test_provision.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/test_provision.sh @@ -16,8 +16,12 @@ # AWS call, and so is one with it while AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY or AWS_SESSION_TOKEN is exported (the # CLI would take the keys over the profile), or AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE or an # AWS_CONTAINER_CREDENTIALS_* variable (its web identity provider, and its container provider, which the CLI uses only -# when the profile holds no credentials, refused anyway), and one with it and none of them goes on; that once state.json -# records the account, exported keys go on to the caller's identity, which the account check compares; that the region +# when the profile holds no credentials, refused anyway), and one with it and none of them goes on only when `aws +# configure list` reports credentials the profile holds itself (keys, sso, login, credential_process, an assumed role), +# in the CLI's current table and its older one, and is refused before any AWS call when it reports an instance role, +# container or environment credentials, a legacy credentials file, none, or fails; that once state.json +# records the account, exported keys and an instance role go on to the caller's identity, which the account check +# compares, without `configure list`; that the region # refusal points at a teardown, and a sandbox in another region (its global role's identity pool) is refused before any # change, with no region recorded and with one recorded (another state directory's sandbox); that find_by_name follows # NextToken to the page that has the name, and stops at the last page without it; and that record_state merges into @@ -32,7 +36,22 @@ cat > "$WORK/bin/aws" <<'EOF' #!/usr/bin/env bash echo "$*" >> "$FAKE_LOG" # FAKE_ACCOUNT and FAKE_ROLE_AUDIENCE answer the caller's identity and the authenticated role's audience (the only -# reads the region guard needs); every other call is refused. +# reads the region guard needs). `configure list` prints the CLI's table for a profile set by AWS_PROFILE whose +# credentials have type FAKE_CREDENTIAL_TYPE (none when it is empty), in the older layout when FAKE_LIST_LAYOUT is +# spaces, and fails when FAKE_LIST_FAILS is not empty. Every other call is refused. +if [[ "$*" == "configure list" ]]; then + [[ -z "${FAKE_LIST_FAILS:-}" ]] || { echo "The config profile (sandbox) could not be found" >&2; exit 255; } + type="${FAKE_CREDENTIAL_TYPE:-None}" key="****************WXYZ" location="" + [[ "$type" != None ]] || { key=""; location=None; } + rows=(profile sandbox env "['AWS_PROFILE', 'AWS_DEFAULT_PROFILE']" access_key "$key" "$type" "$location" + secret_key "$key" "$type" "$location" region us-west-2 config-file "~/.aws/config") + if [[ "${FAKE_LIST_LAYOUT:-}" == spaces ]]; then + printf '%10s %24s %16s %s\n' Name Value Type Location ---- ----- ---- -------- "${rows[@]}" + else + printf '%-10s : %-24s : %-16s : %s\n' NAME VALUE TYPE LOCATION "${rows[@]}" + fi + exit 0 +fi case "$*" in *"sts get-caller-identity"*) [[ -z "${FAKE_ACCOUNT:-}" ]] || { echo "$FAKE_ACCOUNT"; exit 0; } ;; *"iam get-role"*) [[ -z "${FAKE_ROLE_AUDIENCE:-}" ]] || { echo "$FAKE_ROLE_AUDIENCE"; exit 0; } ;; @@ -43,6 +62,8 @@ EOF chmod +x "$WORK/bin/aws" export PATH="$WORK/bin:$PATH" FAKE_LOG="$WORK/calls.log" COGNITO_CLIENT_INTEG_DIR="$WORK/state" export COGNITO_CLIENT_INTEG_MIN_FREE_GIB=0 AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null +export AWS_EC2_METADATA_DISABLED=true FAKE_CREDENTIAL_TYPE=shared-credentials-file +unset FAKE_LIST_LAYOUT FAKE_LIST_FAILS unset AWS_PROFILE AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_ROLE_ARN AWS_WEB_IDENTITY_TOKEN_FILE for variable in $(compgen -e | grep '^AWS_CONTAINER_CREDENTIALS_' || true); do unset "$variable" @@ -125,10 +146,59 @@ a_first_run_with_web_identity_or_container_credentials_is_refused_before_any_aws done } -# Once state.json records the account, the caller's identity is compared with it, so exported keys may be used. +# A profile that holds no credentials of its own leaves the CLI to fall back to an EC2 instance role, container +# credentials, the environment or a legacy credentials file, each in an account the profile does not choose. So with no +# account recorded, a run with AWS_PROFILE goes on to the caller's identity only when `aws configure list` reports +# credentials the profile itself holds, in the CLI's current table and its older one. +a_first_run_with_the_profiles_own_credentials_goes_on_to_the_callers_identity() { + local type layout + for layout in colons spaces; do + for type in shared-credentials-file config-file sso login custom-process assume-role \ + assume-role-with-web-identity; do + : > "$FAKE_LOG" + STATUS=0 + OUT=$(FAKE_CREDENTIAL_TYPE="$type" FAKE_LIST_LAYOUT="$layout" AWS_PROFILE=sandbox \ + COGNITO_CLIENT_INTEG_DIR="$WORK/fresh" "$SCRIPT" us-west-2 2>&1) || STATUS=$? + (( STATUS != 0 )) && [[ "$OUT" != *"no account is recorded"* ]] \ + && grep -q '^configure list$' "$FAKE_LOG" && grep -q -- 'sts get-caller-identity' "$FAKE_LOG" \ + || { echo "$type ($layout): $OUT"; return 1; } + done + done +} + +# ...and any other type, none, or a `configure list` that fails is refused before any AWS call, naming the type. +a_first_run_without_the_profiles_own_credentials_is_refused_before_any_aws_call() { + local type layout expected fails + for layout in colons spaces; do + for type in iam-role container-role env ec2-credentials-file boto-config None failed; do + : > "$FAKE_LOG" + STATUS=0 + fails="" + [[ "$type" != failed ]] || fails=1 + case "$type" in + None) expected="the profile AWS_PROFILE names resolves no credentials" ;; + failed) expected="the CLI cannot resolve the profile's credentials" ;; + *) expected="the profile AWS_PROFILE names takes its credentials from $type (" ;; + esac + OUT=$(FAKE_CREDENTIAL_TYPE="$type" FAKE_LIST_LAYOUT="$layout" FAKE_LIST_FAILS="$fails" \ + AWS_PROFILE=sandbox COGNITO_CLIENT_INTEG_DIR="$WORK/fresh" "$SCRIPT" us-west-2 2>&1) || STATUS=$? + (( STATUS == 1 )) && [[ "$OUT" == *"no account is recorded"*"$expected"* ]] \ + && [[ "$OUT" == *"falls back to an EC2 instance role"* ]] && [[ "$OUT" != *WXYZ* ]] \ + && grep -q '^configure list$' "$FAKE_LOG" && ! grep -v '^configure ' "$FAKE_LOG" | grep -q . \ + || { echo "$type ($layout): $OUT"; return 1; } + done + done +} + +# Once state.json records the account, the caller's identity is compared with it, so exported keys may be used, and so +# may credentials the profile does not hold (an instance role), without asking `configure list`. a_recorded_account_lets_exported_keys_go_on_to_the_callers_identity() { AWS_ACCESS_KEY_ID=placeholder-key-value run - (( STATUS != 0 )) && [[ "$OUT" != *"exports AWS_ACCESS_KEY_ID"* ]] && grep -q -- 'sts get-caller-identity' "$FAKE_LOG" + (( STATUS != 0 )) && [[ "$OUT" != *"exports AWS_ACCESS_KEY_ID"* ]] \ + && grep -q -- 'sts get-caller-identity' "$FAKE_LOG" || return 1 + FAKE_CREDENTIAL_TYPE=iam-role run + (( STATUS != 0 )) && [[ "$OUT" != *"takes its credentials from"* ]] && ! grep -q '^configure list' "$FAKE_LOG" \ + && grep -q -- 'sts get-caller-identity' "$FAKE_LOG" } # The region refusal points at a teardown, not at another state directory, which would build the second sandbox. @@ -235,7 +305,11 @@ check "with no account recorded, a run with AWS_PROFILE and exported access keys a_first_run_with_exported_keys_is_refused_before_any_aws_call check "with no account recorded, a run with AWS_PROFILE and web identity or container credentials is refused" \ a_first_run_with_web_identity_or_container_credentials_is_refused_before_any_aws_call -check "with an account recorded, a run with exported access keys goes on to the caller's identity" \ +check "with no account recorded, a run with AWS_PROFILE and the profile's own credentials goes on" \ + a_first_run_with_the_profiles_own_credentials_goes_on_to_the_callers_identity +check "with no account recorded, a run with AWS_PROFILE and an instance role or other fallback is refused" \ + a_first_run_without_the_profiles_own_credentials_is_refused_before_any_aws_call +check "with an account recorded, a run with exported access keys or an instance role goes on to the caller's identity" \ a_recorded_account_lets_exported_keys_go_on_to_the_callers_identity check "the region refusal points at a teardown, not at another state directory" \ the_region_refusal_does_not_advise_another_state_directory From c81d08d2ebb0a99d78fad4ec045c9b617698478e Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:44:19 -0400 Subject: [PATCH 31/57] ci: fail closed on a rename from an unknown path unless the new path is attributed The selector skipped a rename's old path whenever nothing owned, covered or ignored it, so an unknown file moved into an ignored place such as scripts/ or .github/ selected nothing, while deleting it selected every group. The detect steps now pass each rename's old and new paths (--renames, from git diff --name-status -M --diff-filter=R | cut -f2-), and an unknown old path is skipped only when its new path is owned by a target or covered by a group path. Otherwise it fails closed, as its deletion does. --- .../build_amplify_swift_platforms.yml | 8 +- ...uild_minimum_supported_swift_platforms.yml | 8 +- .github/workflows/build_xcode_preview.yml | 8 +- .github/workflows/integ_test.yml | 8 +- .github/workflows/integ_test_api.yml | 8 +- .github/workflows/integ_test_datastore.yml | 8 +- .github/workflows/unit_test.yml | 8 +- scripts/python/affected_targets.py | 71 +++++++++----- scripts/python/test_affected_targets.py | 98 ++++++++++++++++--- 9 files changed, 156 insertions(+), 69 deletions(-) diff --git a/.github/workflows/build_amplify_swift_platforms.yml b/.github/workflows/build_amplify_swift_platforms.yml index 9da6df2d04..a207f5ae3c 100644 --- a/.github/workflows/build_amplify_swift_platforms.yml +++ b/.github/workflows/build_amplify_swift_platforms.yml @@ -84,14 +84,14 @@ jobs: fi base="$BASE_REF" git fetch --no-tags origin "$base" || true - # With rename detection a rename lists only its new path; its old path goes to --renamed-from, which - # the selector attributes (a bundled file's old path still selects its group) but never fails closed on. + # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; + # the selector attributes the old path too, and fails closed on an unknown one unless the new one is attributed. mb="$(git merge-base HEAD "origin/$base")" git diff --name-only -M "$mb" HEAD > /tmp/changed.txt - git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2 > /tmp/renamed_from.txt + git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2- > /tmp/renames.txt swift package dump-package > /tmp/package.json sel=$(python3 scripts/python/affected_targets.py \ - --package /tmp/package.json --changed /tmp/changed.txt --renamed-from /tmp/renamed_from.txt \ + --package /tmp/package.json --changed /tmp/changed.txt --renames /tmp/renames.txt \ --groups scripts/python/unit_test_groups.json | grep '^selected=' | cut -d= -f2-) [ "$sel" = "[]" ] && echo "build=false" >> "$GITHUB_OUTPUT" || echo "build=true" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/build_minimum_supported_swift_platforms.yml b/.github/workflows/build_minimum_supported_swift_platforms.yml index d4c35e3371..0b56d1eb3b 100644 --- a/.github/workflows/build_minimum_supported_swift_platforms.yml +++ b/.github/workflows/build_minimum_supported_swift_platforms.yml @@ -53,14 +53,14 @@ jobs: fi base="$BASE_REF" git fetch --no-tags origin "$base" || true - # With rename detection a rename lists only its new path; its old path goes to --renamed-from, which - # the selector attributes (a bundled file's old path still selects its group) but never fails closed on. + # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; + # the selector attributes the old path too, and fails closed on an unknown one unless the new one is attributed. mb="$(git merge-base HEAD "origin/$base")" git diff --name-only -M "$mb" HEAD > /tmp/changed.txt - git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2 > /tmp/renamed_from.txt + git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2- > /tmp/renames.txt swift package dump-package > /tmp/package.json sel=$(python3 scripts/python/affected_targets.py \ - --package /tmp/package.json --changed /tmp/changed.txt --renamed-from /tmp/renamed_from.txt \ + --package /tmp/package.json --changed /tmp/changed.txt --renames /tmp/renames.txt \ --groups scripts/python/unit_test_groups.json | grep '^selected=' | cut -d= -f2-) [ "$sel" = "[]" ] && echo "build=false" >> "$GITHUB_OUTPUT" || echo "build=true" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/build_xcode_preview.yml b/.github/workflows/build_xcode_preview.yml index 6f8dd9f46f..6026793be1 100644 --- a/.github/workflows/build_xcode_preview.yml +++ b/.github/workflows/build_xcode_preview.yml @@ -67,14 +67,14 @@ jobs: fi base="$BASE_REF" git fetch --no-tags origin "$base" || true - # With rename detection a rename lists only its new path; its old path goes to --renamed-from, which - # the selector attributes (a bundled file's old path still selects its group) but never fails closed on. + # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; + # the selector attributes the old path too, and fails closed on an unknown one unless the new one is attributed. mb="$(git merge-base HEAD "origin/$base")" git diff --name-only -M "$mb" HEAD > /tmp/changed.txt - git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2 > /tmp/renamed_from.txt + git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2- > /tmp/renames.txt swift package dump-package > /tmp/package.json sel=$(python3 scripts/python/affected_targets.py \ - --package /tmp/package.json --changed /tmp/changed.txt --renamed-from /tmp/renamed_from.txt \ + --package /tmp/package.json --changed /tmp/changed.txt --renames /tmp/renames.txt \ --groups scripts/python/unit_test_groups.json | grep '^selected=' | cut -d= -f2-) [ "$sel" = "[]" ] && echo "build=false" >> "$GITHUB_OUTPUT" || echo "build=true" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/integ_test.yml b/.github/workflows/integ_test.yml index 3054be77d5..66a25e1de8 100644 --- a/.github/workflows/integ_test.yml +++ b/.github/workflows/integ_test.yml @@ -65,14 +65,14 @@ jobs: fi base="$BASE_REF" git fetch --no-tags origin "$base" || true - # With rename detection a rename lists only its new path; its old path goes to --renamed-from, which - # the selector attributes (a bundled file's old path still selects its group) but never fails closed on. + # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; + # the selector attributes the old path too, and fails closed on an unknown one unless the new one is attributed. mb="$(git merge-base HEAD "origin/$base")" git diff --name-only -M "$mb" HEAD > /tmp/changed.txt - git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2 > /tmp/renamed_from.txt + git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2- > /tmp/renames.txt swift package dump-package > /tmp/package.json python3 scripts/python/affected_targets.py \ - --package /tmp/package.json --changed /tmp/changed.txt --renamed-from /tmp/renamed_from.txt \ + --package /tmp/package.json --changed /tmp/changed.txt --renames /tmp/renames.txt \ --groups scripts/python/integ_test_groups.json | tee -a "$GITHUB_OUTPUT" analytics-test: diff --git a/.github/workflows/integ_test_api.yml b/.github/workflows/integ_test_api.yml index 0c00e5acc2..810844bd52 100644 --- a/.github/workflows/integ_test_api.yml +++ b/.github/workflows/integ_test_api.yml @@ -52,14 +52,14 @@ jobs: fi base="$BASE_REF" git fetch --no-tags origin "$base" || true - # With rename detection a rename lists only its new path; its old path goes to --renamed-from, which - # the selector attributes (a bundled file's old path still selects its group) but never fails closed on. + # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; + # the selector attributes the old path too, and fails closed on an unknown one unless the new one is attributed. mb="$(git merge-base HEAD "origin/$base")" git diff --name-only -M "$mb" HEAD > /tmp/changed.txt - git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2 > /tmp/renamed_from.txt + git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2- > /tmp/renames.txt swift package dump-package > /tmp/package.json python3 scripts/python/affected_targets.py \ - --package /tmp/package.json --changed /tmp/changed.txt --renamed-from /tmp/renamed_from.txt \ + --package /tmp/package.json --changed /tmp/changed.txt --renames /tmp/renames.txt \ --groups scripts/python/integ_test_groups.json | tee -a "$GITHUB_OUTPUT" functional-test: diff --git a/.github/workflows/integ_test_datastore.yml b/.github/workflows/integ_test_datastore.yml index ac7a830e8c..0f13d2cd5f 100644 --- a/.github/workflows/integ_test_datastore.yml +++ b/.github/workflows/integ_test_datastore.yml @@ -52,14 +52,14 @@ jobs: fi base="$BASE_REF" git fetch --no-tags origin "$base" || true - # With rename detection a rename lists only its new path; its old path goes to --renamed-from, which - # the selector attributes (a bundled file's old path still selects its group) but never fails closed on. + # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; + # the selector attributes the old path too, and fails closed on an unknown one unless the new one is attributed. mb="$(git merge-base HEAD "origin/$base")" git diff --name-only -M "$mb" HEAD > /tmp/changed.txt - git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2 > /tmp/renamed_from.txt + git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2- > /tmp/renames.txt swift package dump-package > /tmp/package.json python3 scripts/python/affected_targets.py \ - --package /tmp/package.json --changed /tmp/changed.txt --renamed-from /tmp/renamed_from.txt \ + --package /tmp/package.json --changed /tmp/changed.txt --renames /tmp/renames.txt \ --groups scripts/python/integ_test_groups.json | tee -a "$GITHUB_OUTPUT" datastore-base-test: diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index da936c6480..2e7867ec56 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -87,14 +87,14 @@ jobs: else base="$BASE_REF" git fetch --no-tags origin "$base" || true - # With rename detection a rename lists only its new path; its old path goes to --renamed-from, which - # the selector attributes (a bundled file's old path still selects its group) but never fails closed on. + # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; + # the selector attributes the old path too, and fails closed on an unknown one unless the new one is attributed. mb="$(git merge-base HEAD "origin/$base")" git diff --name-only -M "$mb" HEAD > /tmp/changed.txt - git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2 > /tmp/renamed_from.txt + git diff --name-status -M --diff-filter=R "$mb" HEAD | cut -f2- > /tmp/renames.txt swift package dump-package > /tmp/package.json sel=$(python3 scripts/python/affected_targets.py \ - --package /tmp/package.json --changed /tmp/changed.txt --renamed-from /tmp/renamed_from.txt \ + --package /tmp/package.json --changed /tmp/changed.txt --renames /tmp/renames.txt \ --groups scripts/python/unit_test_groups.json | grep '^selected=' | cut -d= -f2-) fi echo "selected: $sel" diff --git a/scripts/python/affected_targets.py b/scripts/python/affected_targets.py index fc7f831030..efdedba083 100644 --- a/scripts/python/affected_targets.py +++ b/scripts/python/affected_targets.py @@ -28,8 +28,8 @@ (push to main / nightly). Fail-closed: a changed file that matches no TARGET at all (and no group path / ignore rule) selects -ALL groups — genuinely-unattributable changes are never silently skipped (a rename's old path is the -one exception, see Renames below). Package.swift and +ALL groups — genuinely-unattributable changes are never silently skipped (a rename's old path can be +the exception, see Renames below). Package.swift and Package.resolved are NOT among these: manifest/lockfile edits are always-ignored (see ALWAYS_IGNORE_NAMES), on the basis that the source changes using a new/changed target trigger their own groups. Integration-test host apps and Xcode project bundles (`HostApp/` and `.xcodeproj/` @@ -38,11 +38,14 @@ SwiftPM/unit inputs. Renames: `--changed` is a rename-detecting list (`git diff --name-only -M`), which names a rename's new -path only, and `--renamed-from` holds the renames' old paths (the source column of `git diff ---name-status -M --diff-filter=R`). An old path is attributed like any changed file, with one -difference: one that nothing owns, covers or ignores is skipped rather than failing closed, since its -new path is already in the changed list. So moving a target's files to a new directory selects what -the new paths select, not every group. A plain deletion is in the changed list and still fails closed. +path only, and `--renames` holds each rename's old and new paths, tab-separated (`git diff --name-status +-M --diff-filter=R | cut -f2-`). An old path is attributed like any changed file, with one difference: +one that nothing owns, covers or ignores is skipped rather than failing closed when its new path is +itself attributed (owned by a target, or under a group's `paths`), since that new path is already in +the changed list and selects its group. So moving a target's files to a new directory selects what the +new paths select, not every group. When the new path is ignored or unknown too (an unknown file moved +into `scripts/` or `.github/`, say), the old path fails closed, as its deletion would. A plain deletion +is in the changed list and still fails closed. Documentation (`.md`) is ignored everywhere, except a file an Xcode project in the checkout copies into a bundle (a host app's README in a test target's Copy Bundle Resources): deleting or renaming that one @@ -67,7 +70,7 @@ Usage: python3 scripts/python/affected_targets.py \ - --package package.json --changed changed.txt [--renamed-from renamed_from.txt] \ + --package package.json --changed changed.txt [--renames renames.txt] \ --groups scripts/python/.json Writes to stdout (append to "$GITHUB_OUTPUT"): @@ -338,13 +341,26 @@ def read_paths(path): return [line.strip() for line in f if line.strip()] +def read_renames(path): + """The (old path, new path) pairs of a rename list: one rename per line, the two paths tab-separated.""" + pairs = [] + for line in read_paths(path): + fields = line.split("\t") + if len(fields) != 2 or not all(fields): + sys.stderr.write("%s: expected an old path, a tab and a new path, found %r\n" % (path, line)) + sys.exit(1) + pairs.append((fields[0], fields[1])) + return pairs + + def main(): ap = argparse.ArgumentParser() ap.add_argument("--package", required=True, help="swift package dump-package JSON") ap.add_argument("--changed", required=True, help="file with one changed path per line, from a rename-detecting diff (a rename's new path)") - ap.add_argument("--renamed-from", - help="file with one rename source (old path) per line; attributed, but never failing closed") + ap.add_argument("--renames", + help="file with one rename per line, the old and new paths tab-separated; an old path is " + "attributed, and an unknown one fails closed unless the new path is attributed") ap.add_argument("--groups", required=True, help="group definition JSON") ap.add_argument("--repo-root", default=SCRIPT_CHECKOUT, help="checkout whose Xcode projects are read for bundled docs (default: the one holding " @@ -354,7 +370,8 @@ def main(): pkg = json.load(open(args.package)) groups = json.load(open(args.groups)) changed = read_paths(args.changed) - renamed_from = read_paths(args.renamed_from) if args.renamed_from else [] + renames = read_renames(args.renames) if args.renames else [] + renamed_from = [old for old, _ in renames] path_of, deps_of, is_test = build_graph(pkg) @@ -391,36 +408,40 @@ def emit(selected): # target, matching no group path, and not ignorable is unknown -> fail closed to all groups. changed_targets = set() - def attribute(f): - """Record f's owning target; False when nothing owns, covers or ignores f.""" + def classify(f): + """"attributed" (recording f's owning target, if any), "ignored", or "unknown".""" if ignored(f): - return True + return "ignored" owner = max((n for n, p in path_of.items() if f.startswith(p)), key=lambda n: len(path_of[n]), default=None) if owner: changed_targets.add(owner) - return True + return "attributed" if f.startswith(XCSCHEME_DIR) and f.endswith(XCSCHEME_SUFFIX): # A per-target scheme -> attribute to that target and let dependency logic narrow it. # An umbrella/aggregate scheme names no target -> unknown (whole-package impact). scheme = f[len(XCSCHEME_DIR):-len(XCSCHEME_SUFFIX)] if scheme in path_of: changed_targets.add(scheme) - return True - return False + return "attributed" + return "unknown" if any(f.startswith(p) for p in group_paths): - return True # attributed to a group by its path rule below - return (f.endswith(IGNORE_SUFFIXES) or f.startswith(IGNORE_PREFIXES) + return "attributed" # to a group, by its path rule below + if (f.endswith(IGNORE_SUFFIXES) or f.startswith(IGNORE_PREFIXES) or f.rsplit("/", 1)[-1] in IGNORE_NAMES - or any(s in f for s in IGNORE_PATH_SUBSTRINGS)) + or any(s in f for s in IGNORE_PATH_SUBSTRINGS)): + return "ignored" + return "unknown" for f in changed: - if not attribute(f): + if classify(f) == "unknown": + emit(groups.keys()) + # A rename's old path: attributed the same way. An unknown one is skipped only when the rename's + # new path (also in `changed`) is itself attributed; otherwise it fails closed, as its deletion + # would (see the module docstring). + for old, new in renames: + if classify(old) == "unknown" and classify(new) != "attributed": emit(groups.keys()) - # A rename's old path: attributed the same way, but an unknown one is skipped rather than failing - # closed, since the rename's new path is in `changed` (see the module docstring). - for f in renamed_from: - attribute(f) # Each group's source target(s) -> group name. source_to_group = {t: name for name, g in groups.items() for t in g.get("targets", [])} diff --git a/scripts/python/test_affected_targets.py b/scripts/python/test_affected_targets.py index d9c5191b11..a3d7334a2a 100644 --- a/scripts/python/test_affected_targets.py +++ b/scripts/python/test_affected_targets.py @@ -66,20 +66,20 @@ def write_list(self, name, paths): f.write("\n".join(paths) + "\n") return path - def run_selector(self, groups, changed, repo_root=REPO, renamed_from=None, cwd=None): - """The selector's completed process for these changed files and rename sources, read against `repo_root`'s - Xcode projects (`None` passes no `--repo-root`), run from `cwd`.""" + def run_selector(self, groups, changed, repo_root=REPO, renames=None, cwd=None): + """The selector's completed process for these changed files and (old, new) rename pairs, read against + `repo_root`'s Xcode projects (`None` passes no `--repo-root`), run from `cwd`.""" command = [sys.executable, SCRIPT, "--package", self.package, "--changed", self.write_list("changed.txt", changed), "--groups", groups] - if renamed_from is not None: - command += ["--renamed-from", self.write_list("renamed_from.txt", renamed_from)] + if renames is not None: + command += ["--renames", self.write_list("renames.txt", ["\t".join(pair) for pair in renames])] if repo_root is not None: command += ["--repo-root", repo_root] return subprocess.run(command, capture_output=True, text=True, check=True, cwd=cwd) - def selected(self, groups, *changed, repo_root=REPO, renamed_from=None, cwd=None): + def selected(self, groups, *changed, repo_root=REPO, renames=None, cwd=None): """The `selected=` array the selector prints for these changed files.""" - result = self.run_selector(groups, changed, repo_root, renamed_from, cwd) + result = self.run_selector(groups, changed, repo_root, renames, cwd) return selection(result) def use_package(self, package): @@ -230,7 +230,7 @@ def test_a_move_that_changes_a_target_directory_selects_only_what_the_new_paths_ old directory, and the real group files - When: - the changed list names the new paths and Package.swift (as `git diff --name-only -M` does), and the - old paths come through `--renamed-from` + renames come through `--renames` - or the old paths are in the changed list itself, as plain deletions - Then: - the unit groups select AmplifyRecordCache alone and the integration groups select nothing @@ -240,8 +240,9 @@ def test_a_move_that_changes_a_target_directory_selects_only_what_the_new_paths_ names = ("AutoFlushSchedulerTests.swift", "RecordCacheTests.swift") new = [RECORD_CACHE + "Tests/UnitTests/" + n for n in names] + ["Package.swift"] old = [RECORD_CACHE + "Tests/" + n for n in names] - self.assertEqual(self.selected(UNIT_GROUPS, *new, renamed_from=old), ["AmplifyRecordCache"]) - self.assertEqual(self.selected(INTEG_GROUPS, *new, renamed_from=old), []) + renames = list(zip(old, new)) + self.assertEqual(self.selected(UNIT_GROUPS, *new, renames=renames), ["AmplifyRecordCache"]) + self.assertEqual(self.selected(INTEG_GROUPS, *new, renames=renames), []) with open(UNIT_GROUPS) as f: every_group = sorted(json.load(f)) self.assertEqual(self.selected(UNIT_GROUPS, *(new + old)), every_group) @@ -253,24 +254,89 @@ def test_a_rename_source_is_attributed_like_a_changed_file(self): and the real group files - When: - that README is renamed: the changed list names only its new path, which no project bundles, and the - old path comes through `--renamed-from` + rename comes through `--renames` - or a file moves out of the Cognito client's sources into AmplifyRecordCache's - Then: - the README's old path selects `auth` with the integration groups and AWSPluginsCore with the unit - groups, as its deletion does; without `--renamed-from` the rename selects nothing + groups, as its deletion does; without `--renames` the rename selects nothing - the moved file selects the groups of the target it left as well as the one it joined """ old = "AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/README.md" new = "AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/Docs/README.md" - self.assertEqual(self.selected(INTEG_GROUPS, new, renamed_from=[old]), ["auth"]) - self.assertEqual(self.selected(UNIT_GROUPS, new, renamed_from=[old]), ["AWSPluginsCore"]) + self.assertEqual(self.selected(INTEG_GROUPS, new, renames=[(old, new)]), ["auth"]) + self.assertEqual(self.selected(UNIT_GROUPS, new, renames=[(old, new)]), ["AWSPluginsCore"]) self.assertEqual(self.selected(INTEG_GROUPS, new), []) self.use_package(stub_package({"AmplifyRecordCache": RECORD_CACHE + "Sources"})) - self.assertEqual(self.selected(UNIT_GROUPS, RECORD_CACHE + "Sources/Moved.swift", - renamed_from=[CLIENT + "Sources/Moved.swift"]), + moved = (CLIENT + "Sources/Moved.swift", RECORD_CACHE + "Sources/Moved.swift") + self.assertEqual(self.selected(UNIT_GROUPS, moved[1], renames=[moved]), ["AWSCognitoAuthPlugin", "AmplifyCognitoClient", "AmplifyRecordCache"]) + def test_an_unknown_file_moved_where_nothing_attributes_it_selects_every_group(self): + """A file nothing owns or covers, moved to an ignored path, fails closed as its deletion does. + + - Given: the real group files, where no target owns and no group path covers the package workspace's data + file or `AmplifyFoundationBridge/AmplifyFoundationBridge.xctestplan` + - When: + - one of them is renamed into `.github/` or `scripts/`, or to a `.md` name, all ignored: the changed list + names only the new path, and `--renames` the pair + - Then: + - every group is selected, with the integration and the unit groups + """ + for old, new in ((".swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata", + ".github/contents.xcworkspacedata"), + ("AmplifyFoundationBridge/AmplifyFoundationBridge.xctestplan", + "scripts/AmplifyFoundationBridge.xctestplan"), + ("AmplifyFoundationBridge/AmplifyFoundationBridge.xctestplan", + "AmplifyFoundationBridge/AmplifyFoundationBridge.md")): + for groups in (INTEG_GROUPS, UNIT_GROUPS): + with self.subTest(new=new, groups=os.path.basename(groups)): + with open(groups) as f: + every_group = sorted(json.load(f)) + self.assertEqual(self.selected(groups, new, renames=[(old, new)]), every_group) + + def test_an_unknown_file_moved_where_something_attributes_it_selects_what_the_new_path_selects(self): + """A file nothing owns or covers, moved to an attributed path, selects only what that new path selects. + + - Given: the real group files, where no target owns and no group path covers + `AmplifyFoundationBridge/AmplifyFoundationBridge.xctestplan` + - When: + - it is renamed under the Geo plugin's directory, a group path in both group files, or into the Cognito + client's sources, which a target owns + - or a file under the ignored `scripts/` is renamed within `scripts/` + - Then: + - the move into the Geo plugin selects `geo` with the integration groups and AWSPluginsCore with the unit + groups, the move into the client's sources selects what a change to them selects, and nothing + fails closed + - the move within `scripts/` selects nothing + """ + old = "AmplifyFoundationBridge/AmplifyFoundationBridge.xctestplan" + for new, integ, unit in (("AmplifyPlugins/Geo/Tests/AmplifyFoundationBridge.xctestplan", ["geo"], + ["AWSPluginsCore"]), + (CLIENT + "Sources/AmplifyFoundationBridge.xctestplan", + ["auth_client", "auth_client_interop"], + ["AWSCognitoAuthPlugin", "AmplifyCognitoClient"])): + with self.subTest(new=new): + self.assertEqual(self.selected(INTEG_GROUPS, new, renames=[(old, new)]), integ) + self.assertEqual(self.selected(UNIT_GROUPS, new, renames=[(old, new)]), unit) + moved = ("scripts/old_name.sh", "scripts/new_name.sh") + for groups in (INTEG_GROUPS, UNIT_GROUPS): + with self.subTest(moved=moved, groups=os.path.basename(groups)): + self.assertEqual(self.selected(groups, moved[1], renames=[moved]), []) + + def test_a_malformed_rename_list_fails(self): + """A `--renames` line without exactly two tab-separated paths stops the selector rather than being guessed at. + + - Given: the real integration group file + - When: + - the rename list holds only an old path, as the source column alone would + - Then: + - the selector exits non-zero and names the line on stderr + """ + with self.assertRaises(subprocess.CalledProcessError) as raised: + self.run_selector(INTEG_GROUPS, ["scripts/new_name.sh"], renames=[("scripts/old_name.sh",)]) + self.assertIn("scripts/old_name.sh", raised.exception.stderr) + def test_the_default_repo_root_is_the_checkout_holding_the_selector(self): """Run from another directory without `--repo-root`, the selector still reads this checkout's projects. From 1841b538a81b2d43469026709482988346579130 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:45:06 -0400 Subject: [PATCH 32/57] ci: pin the selector's default repo root and a bundled README renamed away from .md The default-root test passed with the default reverted to the current directory, because the fallback for a root with no project treats every doc as bundled and selected the same group. It now also asserts that no "found no Xcode project" note is printed. A new test renames a bundled README to a .txt file, so the only .md path is the rename's old side, and checks that the projects are still read for it. --- scripts/python/test_affected_targets.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/scripts/python/test_affected_targets.py b/scripts/python/test_affected_targets.py index a3d7334a2a..d2b317fba2 100644 --- a/scripts/python/test_affected_targets.py +++ b/scripts/python/test_affected_targets.py @@ -345,9 +345,30 @@ def test_the_default_repo_root_is_the_checkout_holding_the_selector(self): - the only changed file is a README the Auth host app bundles - Then: - `auth` is selected, as when run from the checkout's root + - stderr has no "found no Xcode project" note: the projects were read, so the selection does not come from + the fallback that treats an unread root's docs as bundled (which selects `auth` too) """ readme = "AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/README.md" - self.assertEqual(self.selected(INTEG_GROUPS, readme, repo_root=None, cwd=self.work.name), ["auth"]) + result = self.run_selector(INTEG_GROUPS, [readme], repo_root=None, cwd=self.work.name) + self.assertEqual(selection(result), ["auth"]) + self.assertNotIn("found no Xcode project", result.stderr) + + def test_a_bundled_readme_renamed_away_from_md_is_read_through_its_old_path(self): + """A bundled README renamed to a name that isn't `.md` still makes the selector read the projects. + + - Given: this checkout's Xcode projects, where the Auth host app bundles `AuthIntegrationTests/README.md`, + and the real group files + - When: + - that README is renamed to a `.txt` file under the ignored `scripts/`, so the only `.md` path in the change + is the rename's old path + - Then: + - the old path selects `auth` with the integration groups and AWSPluginsCore with the unit groups, as its + deletion does: the projects are read for a `.md` on the old side of a rename too + """ + old = "AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/README.md" + new = "scripts/AuthIntegrationTests-README.txt" + self.assertEqual(self.selected(INTEG_GROUPS, new, renames=[(old, new)]), ["auth"]) + self.assertEqual(self.selected(UNIT_GROUPS, new, renames=[(old, new)]), ["AWSPluginsCore"]) def test_a_root_with_no_project_is_unread(self): """A `--repo-root` holding no Xcode project makes every doc count as bundled, not none of them. From fff2f5f613f1a983868b79cef82a4ebea861ac3d Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:52:06 -0400 Subject: [PATCH 33/57] ci: check for missing bundled files in detect instead of selecting a bundled README's group A host app's README in Copy Bundle Resources was attributed like any host-app file, so an edit selected its group (and AWSPluginsCore with the unit groups, which the three build workflows turn into a full package build), while a deletion of one copied only by a stress-test target still went unseen, since no PR job builds those. The detect steps now run affected_targets.py --check-bundled before selecting. It reads every Xcode project in the checkout and fails with an ::error on the project file for each file a project copies into a bundle that is missing from the checkout, leaving out the ones git ignores (the integration-test configurations CI writes before building). A project it can't read, a root with no project and a failed git query fail it too. Documentation is now ignored by the selection wherever it lives, so a bundled README edit selects nothing. --- .../build_amplify_swift_platforms.yml | 3 + ...uild_minimum_supported_swift_platforms.yml | 3 + .github/workflows/build_xcode_preview.yml | 3 + .github/workflows/integ_test.yml | 3 + .github/workflows/integ_test_api.yml | 3 + .github/workflows/integ_test_datastore.yml | 3 + .github/workflows/unit_test.yml | 3 + scripts/python/affected_targets.py | 169 ++++++---- scripts/python/test_affected_targets.py | 299 ++++++++++-------- 9 files changed, 291 insertions(+), 198 deletions(-) diff --git a/.github/workflows/build_amplify_swift_platforms.yml b/.github/workflows/build_amplify_swift_platforms.yml index a207f5ae3c..957b6c6d47 100644 --- a/.github/workflows/build_amplify_swift_platforms.yml +++ b/.github/workflows/build_amplify_swift_platforms.yml @@ -82,6 +82,9 @@ jobs: echo "build=true" >> "$GITHUB_OUTPUT" exit 0 fi + # A file an Xcode project copies into a bundle (a host app's README, say) that is missing from the + # checkout breaks the copying target's build, which a skipped job can't show: fail here, naming the project. + python3 scripts/python/affected_targets.py --check-bundled base="$BASE_REF" git fetch --no-tags origin "$base" || true # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; diff --git a/.github/workflows/build_minimum_supported_swift_platforms.yml b/.github/workflows/build_minimum_supported_swift_platforms.yml index 0b56d1eb3b..a1414d5832 100644 --- a/.github/workflows/build_minimum_supported_swift_platforms.yml +++ b/.github/workflows/build_minimum_supported_swift_platforms.yml @@ -51,6 +51,9 @@ jobs: echo "build=true" >> "$GITHUB_OUTPUT" exit 0 fi + # A file an Xcode project copies into a bundle (a host app's README, say) that is missing from the + # checkout breaks the copying target's build, which a skipped job can't show: fail here, naming the project. + python3 scripts/python/affected_targets.py --check-bundled base="$BASE_REF" git fetch --no-tags origin "$base" || true # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; diff --git a/.github/workflows/build_xcode_preview.yml b/.github/workflows/build_xcode_preview.yml index 6026793be1..6248aec17c 100644 --- a/.github/workflows/build_xcode_preview.yml +++ b/.github/workflows/build_xcode_preview.yml @@ -65,6 +65,9 @@ jobs: echo "build=true" >> "$GITHUB_OUTPUT" exit 0 fi + # A file an Xcode project copies into a bundle (a host app's README, say) that is missing from the + # checkout breaks the copying target's build, which a skipped job can't show: fail here, naming the project. + python3 scripts/python/affected_targets.py --check-bundled base="$BASE_REF" git fetch --no-tags origin "$base" || true # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; diff --git a/.github/workflows/integ_test.yml b/.github/workflows/integ_test.yml index 66a25e1de8..30b5c4edf9 100644 --- a/.github/workflows/integ_test.yml +++ b/.github/workflows/integ_test.yml @@ -63,6 +63,9 @@ jobs: done exit 0 fi + # A file an Xcode project copies into a bundle (a host app's README, say) that is missing from the + # checkout breaks the copying target's build, which a skipped job can't show: fail here, naming the project. + python3 scripts/python/affected_targets.py --check-bundled base="$BASE_REF" git fetch --no-tags origin "$base" || true # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; diff --git a/.github/workflows/integ_test_api.yml b/.github/workflows/integ_test_api.yml index 810844bd52..53905c60a3 100644 --- a/.github/workflows/integ_test_api.yml +++ b/.github/workflows/integ_test_api.yml @@ -50,6 +50,9 @@ jobs: echo "api=true" >> "$GITHUB_OUTPUT" exit 0 fi + # A file an Xcode project copies into a bundle (a host app's README, say) that is missing from the + # checkout breaks the copying target's build, which a skipped job can't show: fail here, naming the project. + python3 scripts/python/affected_targets.py --check-bundled base="$BASE_REF" git fetch --no-tags origin "$base" || true # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; diff --git a/.github/workflows/integ_test_datastore.yml b/.github/workflows/integ_test_datastore.yml index 0f13d2cd5f..7592631875 100644 --- a/.github/workflows/integ_test_datastore.yml +++ b/.github/workflows/integ_test_datastore.yml @@ -50,6 +50,9 @@ jobs: echo "datastore=true" >> "$GITHUB_OUTPUT" exit 0 fi + # A file an Xcode project copies into a bundle (a host app's README, say) that is missing from the + # checkout breaks the copying target's build, which a skipped job can't show: fail here, naming the project. + python3 scripts/python/affected_targets.py --check-bundled base="$BASE_REF" git fetch --no-tags origin "$base" || true # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 2e7867ec56..7c33a223fd 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -85,6 +85,9 @@ jobs: if [ -n "$IDENTIFIER" ] || [ "$EVENT_NAME" != "push" ] || [ "$GITHUB_REF_NAME" = "main" ]; then sel=$(jq -c 'keys' scripts/python/unit_test_groups.json) else + # A file an Xcode project copies into a bundle (a host app's README, say) that is missing from the + # checkout breaks the copying target's build, which a skipped job can't show: fail here, naming the project. + python3 scripts/python/affected_targets.py --check-bundled base="$BASE_REF" git fetch --no-tags origin "$base" || true # With rename detection a rename lists only its new path. --renames gets each rename's old and new paths; diff --git a/scripts/python/affected_targets.py b/scripts/python/affected_targets.py index efdedba083..147b77a82f 100644 --- a/scripts/python/affected_targets.py +++ b/scripts/python/affected_targets.py @@ -47,22 +47,15 @@ into `scripts/` or `.github/`, say), the old path fails closed, as its deletion would. A plain deletion is in the changed list and still fails closed. -Documentation (`.md`) is ignored everywhere, except a file an Xcode project in the checkout copies into -a bundle (a host app's README in a test target's Copy Bundle Resources): deleting or renaming that one -breaks the build of the target that copies it, so it is attributed like any other host-app file, in -every group file (a rename through its old path, which the project still lists). With the integration -groups that selects the host app's own group. That catches the break only where the group's PR jobs -build the copying target: six of the bundled READMEs are copied only by stress-test targets -(GraphQLAPIStressTests, AnalyticsStressTests, AuthStressTests, DatastoreStressTests, GeoStressTests, -StorageStressTests), which no PR job builds, so for those the selected jobs pass and the break shows -only in stress_test.yml after the merge. With unit_test_groups.json it selects AWSPluginsCore, whose -`paths` hold `AmplifyPlugins/` and so cover every plugin host app, as any other host-app file already -does there: unit_test.yml then runs AWSPluginsCore's unit tests, and the three build workflows, which -only check that something is selected, build the whole package on all their platforms. None of those -builds a host app. -The projects are read from `--repo-root` (default: the checkout that holds this script, wherever it is -run from). A root with no Xcode project at all is treated as unread, so its docs count as bundled -rather than none of them. +Documentation (`.md`) is ignored everywhere, including a file an Xcode project copies into a bundle (a +host app's README in a test target's Copy Bundle Resources): editing one changes nothing a build or test +checks. Deleting or renaming one breaks the build of the target that copies it, which no selection can +promise to catch (six of the bundled READMEs are copied only by stress-test targets, which no PR job +builds), so the detect steps run `--check-bundled` before selecting: it fails, naming the project, when a +file any Xcode project in the checkout lists in Copy Bundle Resources is missing from the checkout, other +than one git ignores (an integration-test configuration CI writes before building). A project it can't +read, or a root with no project at all, fails the check too. The projects are read from `--repo-root` +(default: the checkout that holds this script, wherever it is run from). It also validates the group config: if any group references a target that is absent from the SwiftPM graph (a rename, a typo, or a not-yet-wired target), it exits non-zero rather than @@ -72,29 +65,30 @@ python3 scripts/python/affected_targets.py \ --package package.json --changed changed.txt [--renames renames.txt] \ --groups scripts/python/.json + python3 scripts/python/affected_targets.py --check-bundled [--repo-root ] -Writes to stdout (append to "$GITHUB_OUTPUT"): +The selection writes to stdout (append to "$GITHUB_OUTPUT"): selected= =true|false # one line per group, for use in job-level `if:` +The check writes one `::error` workflow command per problem to stdout and exits 1, or a summary and 0. """ import argparse import json import os import re +import subprocess import sys # Paths that can never affect tests. Kept deliberately small — anything not listed here and not # owned by a target falls through to the fail-closed "run everything" branch. -# Repo/editor metadata and documentation, ignored even inside a target directory (checked before +# Repo/editor metadata and documentation, ignored even inside a target dir (checked before # target ownership). `.md` is here rather than in the unowned-only list below because a README/AGENTS # doc inside a target dir is usually just prose: it is never compiled, so editing it should not -# trigger that target's 5-platform matrix. The exception is a file an Xcode project lists in a -# target's Copy Bundle Resources phase (several integration host apps bundle their READMEs): deleting -# or renaming it breaks that target's build, so such a file is NOT ignored and is attributed like -# any other host-app file (see bundled_files). That runs a job that sees the break only where the -# selected group's PR jobs build the copying target, which the stress-test targets' READMEs miss (see -# the module docstring). (`.yml`/`.yaml` are deliberately NOT promoted — an -# in-target YAML fixture is real test input and must stay owned; see the unowned-only list.) +# trigger that target's 5-platform matrix. A README a host app copies into a bundle is ignored too: +# editing it changes no test, and the deletion or rename that would break the copying target's build +# is caught by `--check-bundled` instead (see the module docstring). (`.yml`/`.yaml` are deliberately +# NOT promoted — an in-target YAML fixture is real test input and must stay owned; see the +# unowned-only list.) ALWAYS_IGNORE_SUFFIXES = (".gitignore", ".gitattributes", ".swiftformat", ".editorconfig", ".md") ALWAYS_IGNORE_NAMES = ("LICENSE", "NOTICE", ".git-blame-ignore-revs", ".gitallowed", # Xcode file-header template macros: an IDE authoring convenience only @@ -157,7 +151,7 @@ def is_always_ignored(path): return path.endswith(ALWAYS_IGNORE_SUFFIXES) or path.rsplit("/", 1)[-1] in ALWAYS_IGNORE_NAMES -# Directories bundled_files never descends into: VCS metadata and local build output. +# Directories read_projects never descends into: VCS metadata and local build output. SCAN_SKIP_DIRS = {".git", ".build", "DerivedData", "node_modules"} # One token of an Xcode project file (an old-style ASCII property list): whitespace or a comment @@ -274,19 +268,16 @@ def files(oid): return copied -def bundled_files(root): - """Return (bundled, unread) for the Xcode projects under the checkout at `root`. +def read_projects(root): + """Return (bundles, unread) for the Xcode projects under the checkout at `root`. - `bundled` holds the repo-relative paths of every file a project copies into a bundle. `unread` - holds the repo-relative directories (with a trailing `/`) of projects that could not be parsed; - every ignorable file under one of them is treated as bundled, so a project this parser cannot read - makes its docs run their group rather than silently skipping a possible build break. A root with no - `project.pbxproj` at all (a wrong `--repo-root`, say) is unread as a whole, for the same reason. + `bundles` maps each readable project's repo-relative `.xcodeproj` path to the repo-relative paths of the files + it copies into a bundle. `unread` holds (project, error) for each project this parser could not read. """ def rel(path): return os.path.relpath(os.path.normpath(path), root).replace(os.sep, "/") - bundled, unread, found = set(), [], False + bundles, unread = {}, [] for dirpath, dirnames, filenames in os.walk(root): dirnames[:] = sorted(d for d in dirnames if d not in SCAN_SKIP_DIRS) if not dirpath.endswith(".xcodeproj"): @@ -294,20 +285,76 @@ def rel(path): dirnames[:] = [] if "project.pbxproj" not in filenames: continue - found = True - project_dir = os.path.dirname(dirpath) try: with open(os.path.join(dirpath, "project.pbxproj"), encoding="utf-8") as f: - bundled.update(rel(p) for p in bundle_resources(project_dir, f.read())) - except Exception as error: # noqa: BLE001 -- any unreadable project fails safe, see above - sys.stderr.write("could not read the bundle resources of %s (%s); treating the files under " - "its directory as bundled\n" % (rel(dirpath), error)) - directory = rel(project_dir) - unread.append("" if directory == "." else directory + "/") - if not found: - sys.stderr.write("found no Xcode project under %s; treating every file under it as bundled\n" % root) - unread.append("") - return bundled, unread + bundles[rel(dirpath)] = {rel(p) for p in bundle_resources(os.path.dirname(dirpath), f.read())} + except Exception as error: # noqa: BLE001 -- any unreadable project fails the check, see check_bundled + unread.append((rel(dirpath), error)) + return bundles, unread + + +def _escape(text, prop=False): + """`text` escaped for a GitHub workflow command's message (or, with `prop`, a property value).""" + text = text.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A") + return text.replace(":", "%3A").replace(",", "%2C") if prop else text + + +def _error(message, project=None): + """An `::error` workflow command (annotating `project`'s project file, if given) on stdout.""" + where = "" if project is None else " file=" + _escape(project + "/project.pbxproj", prop=True) + print("::error%s::%s" % (where, _escape(message))) + + +def git_ignored(root, paths): + """The subset of the repo-relative `paths` that git ignores in the checkout at `root`. Raises OSError or + subprocess.CalledProcessError when git can't answer.""" + if not paths: + return set() + result = subprocess.run(["git", "-C", root, "check-ignore", "-z", "--stdin"], input="\0".join(paths) + "\0", + capture_output=True, text=True) + if result.returncode not in (0, 1): # 1: none of them is ignored + raise subprocess.CalledProcessError(result.returncode, result.args, result.stdout, result.stderr) + return {p for p in result.stdout.split("\0") if p} + + +def check_bundled(root): + """`--check-bundled`: 1 when a file an Xcode project under `root` copies into a bundle is missing from the + checkout, a project can't be read, or there is no project at all; otherwise 0. + + A missing file git ignores is left out: the integration-test configurations (amplifyconfiguration.json, + amplify_outputs.json) are written by CI before a build and are absent from a fresh checkout. So is a reference + that resolves outside the checkout. + """ + bundles, unread = read_projects(root) + if not bundles and not unread: + _error("found no Xcode project under %s, so no bundled file was checked; pass the checkout's root as " + "--repo-root" % root) + return 1 + failed = False + for project, error in unread: + _error("could not read the bundle resources of %s (%s), so a file it copies into a bundle may be missing " + "unnoticed" % (project, error), project) + failed = True + missing = {project: sorted(f for f in files if not f.startswith("../") and + not os.path.exists(os.path.join(root, f))) + for project, files in bundles.items()} + try: + ignored = git_ignored(root, sorted({f for files in missing.values() for f in files})) + except (OSError, subprocess.CalledProcessError) as error: + _error("could not ask git which of the missing bundled files it ignores (%s)" + % (getattr(error, "stderr", None) or error).strip()) + return 1 + for project, files in sorted(missing.items()): + for f in files: + if f not in ignored: + _error("%s copies %s into a bundle (Copy Bundle Resources), but the file is not in the checkout. " + "Restore it, or update the project: remove the reference, or point it at the file's new path." + % (project, f), project) + failed = True + if not failed: + print("checked the %d files %d Xcode projects copy into bundles: none is missing" + % (sum(len(files) for files in bundles.values()), len(bundles))) + return 1 if failed else 0 def build_graph(pkg): @@ -355,17 +402,23 @@ def read_renames(path): def main(): ap = argparse.ArgumentParser() - ap.add_argument("--package", required=True, help="swift package dump-package JSON") - ap.add_argument("--changed", required=True, + ap.add_argument("--check-bundled", action="store_true", + help="instead of selecting, fail when a file an Xcode project copies into a bundle is missing") + ap.add_argument("--package", help="swift package dump-package JSON") + ap.add_argument("--changed", help="file with one changed path per line, from a rename-detecting diff (a rename's new path)") ap.add_argument("--renames", help="file with one rename per line, the old and new paths tab-separated; an old path is " "attributed, and an unknown one fails closed unless the new path is attributed") - ap.add_argument("--groups", required=True, help="group definition JSON") + ap.add_argument("--groups", help="group definition JSON") ap.add_argument("--repo-root", default=SCRIPT_CHECKOUT, - help="checkout whose Xcode projects are read for bundled docs (default: the one holding " - "this script)") + help="checkout whose Xcode projects --check-bundled reads (default: the one holding this " + "script)") args = ap.parse_args() + if args.check_bundled: + sys.exit(check_bundled(args.repo_root)) + if not (args.package and args.changed and args.groups): + ap.error("--package, --changed and --groups are required unless --check-bundled is given") pkg = json.load(open(args.package)) groups = json.load(open(args.groups)) @@ -374,14 +427,7 @@ def main(): renamed_from = [old for old, _ in renames] path_of, deps_of, is_test = build_graph(pkg) - - # An always-ignored file is ignored unless an Xcode project copies it into a bundle (see - # ALWAYS_IGNORE_SUFFIXES). The projects are read only when some changed file needs the answer. - bundled, unread = (bundled_files(args.repo_root) - if any(is_always_ignored(f) for f in changed + renamed_from) else (set(), [])) - - def ignored(f): - return is_always_ignored(f) and f not in bundled and not f.startswith(tuple(unread)) + ignored = is_always_ignored # Guard: every target a group references must exist in the SwiftPM graph. Otherwise a group # naming a renamed, mistyped, or not-yet-wired target would silently never be selected and its @@ -477,9 +523,8 @@ def strip_test_suffix(n): # else: owned by a non-group source target -> selects nothing (see comment above). # Groups selected by a path rule (dirs that are not SwiftPM targets). An ignored file (a README - # under a group's path that no Xcode project bundles, say) selects nothing here either, as in the - # file loop above; a README a host app bundles selects every group whose path covers it (with the unit - # groups, AWSPluginsCore's `AmplifyPlugins/`). A rename's old path counts here too. + # under a group's path, bundled or not) selects nothing here either, as in the file loop above. + # A rename's old path counts here too. relevant = [f for f in changed + renamed_from if not ignored(f)] for name, group in groups.items(): if any(f.startswith(p) for p in group.get("paths", []) for f in relevant): diff --git a/scripts/python/test_affected_targets.py b/scripts/python/test_affected_targets.py index d2b317fba2..554a9ba215 100644 --- a/scripts/python/test_affected_targets.py +++ b/scripts/python/test_affected_targets.py @@ -11,8 +11,8 @@ python3 -m unittest discover -s scripts/python -p 'test_*.py' The stub names every target the group files reference (each under a placeholder path, so the group-target check -passes), plus the Cognito client's source and unit-test targets at their real paths. The host-app README cases read -this checkout's Xcode projects, or a scratch checkout holding one small project or none. +passes), plus the Cognito client's source and unit-test targets at their real paths. The `--check-bundled` cases read +this checkout's Xcode projects, or a scratch git checkout holding one small project or none. """ import json import os @@ -66,32 +66,45 @@ def write_list(self, name, paths): f.write("\n".join(paths) + "\n") return path - def run_selector(self, groups, changed, repo_root=REPO, renames=None, cwd=None): - """The selector's completed process for these changed files and (old, new) rename pairs, read against - `repo_root`'s Xcode projects (`None` passes no `--repo-root`), run from `cwd`.""" + def run_selector(self, groups, changed, renames=None, repo_root=None): + """The selector's completed process for these changed files and (old, new) rename pairs.""" command = [sys.executable, SCRIPT, "--package", self.package, "--changed", self.write_list("changed.txt", changed), "--groups", groups] if renames is not None: command += ["--renames", self.write_list("renames.txt", ["\t".join(pair) for pair in renames])] if repo_root is not None: command += ["--repo-root", repo_root] - return subprocess.run(command, capture_output=True, text=True, check=True, cwd=cwd) + return subprocess.run(command, capture_output=True, text=True, check=True) - def selected(self, groups, *changed, repo_root=REPO, renames=None, cwd=None): + def selected(self, groups, *changed, renames=None): """The `selected=` array the selector prints for these changed files.""" - result = self.run_selector(groups, changed, repo_root, renames, cwd) - return selection(result) + return selection(self.run_selector(groups, changed, renames)) + + def run_check(self, repo_root=REPO, cwd=None): + """The completed process of `--check-bundled` over `repo_root` (`None` passes no `--repo-root`), run from + `cwd`.""" + command = [sys.executable, SCRIPT, "--check-bundled"] + if repo_root is not None: + command += ["--repo-root", repo_root] + return subprocess.run(command, capture_output=True, text=True, cwd=cwd) def use_package(self, package): with open(self.package, "w") as f: json.dump(package, f) - def scratch_checkout(self, pbxproj): - """A scratch checkout holding only `GEO_PROJECT` with this `project.pbxproj` text; returns its root.""" + def scratch_checkout(self, pbxproj, files=(), git=True): + """A scratch checkout holding `GEO_PROJECT` with this `project.pbxproj` text and these other files (paths + relative to the root, each with a line of text), a git repository unless `git` is false; returns its root.""" root = os.path.join(self.work.name, "checkout") os.makedirs(os.path.join(root, GEO_PROJECT)) with open(os.path.join(root, GEO_PROJECT, "project.pbxproj"), "w") as f: f.write(pbxproj) + for path in files: + os.makedirs(os.path.dirname(os.path.join(root, path)), exist_ok=True) + with open(os.path.join(root, path), "w") as f: + f.write("x\n") + if git: + subprocess.run(["git", "init", "-q", root], check=True) return root def test_a_readme_under_a_group_path_selects_no_group(self): @@ -135,93 +148,30 @@ def test_a_readme_beside_a_swift_change_keeps_the_swift_selection(self): self.assertEqual(self.selected(INTEG_GROUPS, CLIENT + "README.md", changed), ["auth_client", "auth_client_interop"]) - def test_a_readme_a_host_app_bundles_selects_its_group(self): - """A README that a host app's test target copies as a bundle resource selects that host app's group. + def test_a_readme_a_host_app_bundles_selects_no_group(self): + """A README that a host app's test target copies as a bundle resource selects nothing when it changes. - Given: this checkout's Xcode projects, where the Auth, Storage and Push host apps list these READMEs in a - test target's Copy Bundle Resources phase, and the real integration group file + test target's Copy Bundle Resources phase, and the real group files (with the unit groups, AWSPluginsCore's + `paths` hold `AmplifyPlugins/`, which covers every plugin host app) - When: - - the only changed file is one of those READMEs (an edit or a deletion: either way the project still - lists it, and a deletion breaks the host app's build) + - the only changed file is one of those READMEs, with `--repo-root` an empty directory - Then: - - the group whose path covers the host app is selected, and nothing else (selection only: the Storage - README is copied by StorageStressTests, which no PR job builds, so its group's jobs can't see the break) - """ - for readme, group in (("AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/README.md", "auth"), - ("AmplifyPlugins/Storage/Tests/StorageHostApp/StorageStressTests/README.md", "storage"), - ("AmplifyPlugins/Notifications/Push/Tests/PushNotificationHostApp/" - "PushNotificationHostAppUITests/README.md", "push_notifications")): - with self.subTest(readme=readme): - self.assertEqual(self.selected(INTEG_GROUPS, readme), [group]) - - def test_a_readme_a_host_app_bundles_selects_the_plugins_core_unit_group(self): - """With the unit groups, a README a host app bundles selects AWSPluginsCore, as any host-app file does. - - - Given: this checkout's Xcode projects, where the Auth, Storage and Push host apps list these READMEs in a - test target's Copy Bundle Resources phase, and the real unit group file, whose AWSPluginsCore group's - `paths` hold `AmplifyPlugins/` - - When: - - the only changed file is one of those READMEs - - Then: - - AWSPluginsCore alone is selected, as any other file under a plugin's host app selects there: unit_test.yml - runs AWSPluginsCore's unit tests, and the three build workflows, which only check that something is - selected, build the whole package (accepted: none of them builds a host app, so none sees the break) + - no group is selected, with the integration or the unit groups: an edit changes nothing a build or test + checks, and a deletion or rename is left to `--check-bundled` + - nothing is printed on stderr: the selection reads no Xcode project """ + empty = os.path.join(self.work.name, "empty") + os.makedirs(empty) for readme in ("AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/README.md", "AmplifyPlugins/Storage/Tests/StorageHostApp/StorageStressTests/README.md", "AmplifyPlugins/Notifications/Push/Tests/PushNotificationHostApp/" "PushNotificationHostAppUITests/README.md"): - with self.subTest(readme=readme): - self.assertEqual(self.selected(UNIT_GROUPS, readme), ["AWSPluginsCore"]) - - def test_a_host_app_readme_no_project_bundles_selects_no_group(self): - """A README inside a host app that no Xcode project copies into a bundle still selects nothing. - - - Given: this checkout's Xcode projects, where these READMEs sit inside host apps but in no Copy Bundle - Resources phase, and the real integration group file - - When: - - the only changed file is one of those READMEs - - Then: - - no group is selected: the docs skip still holds for documentation that is not a bundle resource - """ - for readme in ("AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/PasswordlessTests/README.md", - "AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/README.md", - "AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/README.md"): - with self.subTest(readme=readme): - self.assertEqual(self.selected(INTEG_GROUPS, readme), []) - - def test_a_bundle_resource_resolves_through_the_project_groups(self): - """A bundled README is found through name-only and path groups, and only the listed file is bundled. - - - Given: a scratch checkout whose one project bundles `GeoStressTests/README.md` through a group with a - `path` inside a group with only a `name`, and the README file itself deleted - - When: - - the only changed file is that README, or a README beside it that the project does not list - - Then: - - the bundled README selects `geo`, and the other README selects nothing - """ - root = self.scratch_checkout(GEO_PBXPROJ) - self.assertEqual(self.selected(INTEG_GROUPS, GEO_HOST_APP + "GeoStressTests/README.md", repo_root=root), - ["geo"]) - self.assertEqual(self.selected(INTEG_GROUPS, GEO_HOST_APP + "GeoStressTests/Notes/README.md", - repo_root=root), []) - - def test_a_project_that_cannot_be_read_makes_its_docs_select_their_group(self): - """When a project can't be parsed, the docs under its directory are treated as bundled. - - - Given: a scratch checkout whose one project file is truncated - - When: - - the only changed file is a README under that project's directory, or a README elsewhere - - Then: - - the README under the project selects `geo`, and the selector says on stderr which project it couldn't - read - - the README elsewhere still selects nothing - """ - root = self.scratch_checkout(GEO_PBXPROJ[:len(GEO_PBXPROJ) // 2]) - result = self.run_selector(INTEG_GROUPS, [GEO_HOST_APP + "GeoStressTests/Notes/README.md"], root) - self.assertEqual(selection(result), ["geo"]) - self.assertIn(GEO_PROJECT, result.stderr) - self.assertEqual(self.selected(INTEG_GROUPS, "AmplifyPlugins/Auth/README.md", repo_root=root), []) + for groups in (INTEG_GROUPS, UNIT_GROUPS): + with self.subTest(readme=readme, groups=os.path.basename(groups)): + result = self.run_selector(groups, [readme], repo_root=empty) + self.assertEqual(selection(result), []) + self.assertEqual(result.stderr, "") def test_a_move_that_changes_a_target_directory_selects_only_what_the_new_paths_select(self): """Moving a target's files to a new directory selects the target's group, not every group. @@ -250,28 +200,29 @@ def test_a_move_that_changes_a_target_directory_selects_only_what_the_new_paths_ def test_a_rename_source_is_attributed_like_a_changed_file(self): """A rename's old path still selects what it would select as a changed file. - - Given: this checkout's Xcode projects, where the Auth host app bundles `AuthIntegrationTests/README.md`, - and the real group files + - Given: the real group files, and a package dump where AmplifyRecordCache's sources are at their real path - When: - - that README is renamed: the changed list names only its new path, which no project bundles, and the - rename comes through `--renames` - - or a file moves out of the Cognito client's sources into AmplifyRecordCache's + - a file moves out of the Cognito client's sources into AmplifyRecordCache's: the changed list names only + its new path, and `--renames` the pair + - or a README the Auth host app bundles is renamed within its directory, or to a `.txt` file under + `scripts/` - Then: - - the README's old path selects `auth` with the integration groups and AWSPluginsCore with the unit - groups, as its deletion does; without `--renames` the rename selects nothing - the moved file selects the groups of the target it left as well as the one it joined + - the README's rename selects nothing, with the integration or the unit groups, as its edit does: its old + path is documentation, and `--check-bundled` catches the project still listing it """ - old = "AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/README.md" - new = "AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/Docs/README.md" - self.assertEqual(self.selected(INTEG_GROUPS, new, renames=[(old, new)]), ["auth"]) - self.assertEqual(self.selected(UNIT_GROUPS, new, renames=[(old, new)]), ["AWSPluginsCore"]) - self.assertEqual(self.selected(INTEG_GROUPS, new), []) - self.use_package(stub_package({"AmplifyRecordCache": RECORD_CACHE + "Sources"})) moved = (CLIENT + "Sources/Moved.swift", RECORD_CACHE + "Sources/Moved.swift") self.assertEqual(self.selected(UNIT_GROUPS, moved[1], renames=[moved]), ["AWSCognitoAuthPlugin", "AmplifyCognitoClient", "AmplifyRecordCache"]) + old = "AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/README.md" + for new in ("AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/Docs/README.md", + "scripts/AuthIntegrationTests-README.txt"): + for groups in (INTEG_GROUPS, UNIT_GROUPS): + with self.subTest(new=new, groups=os.path.basename(groups)): + self.assertEqual(self.selected(groups, new, renames=[(old, new)]), []) + def test_an_unknown_file_moved_where_nothing_attributes_it_selects_every_group(self): """A file nothing owns or covers, moved to an ignored path, fails closed as its deletion does. @@ -337,54 +288,128 @@ def test_a_malformed_rename_list_fails(self): self.run_selector(INTEG_GROUPS, ["scripts/new_name.sh"], renames=[("scripts/old_name.sh",)]) self.assertIn("scripts/old_name.sh", raised.exception.stderr) - def test_the_default_repo_root_is_the_checkout_holding_the_selector(self): - """Run from another directory without `--repo-root`, the selector still reads this checkout's projects. + def test_the_check_passes_on_this_checkout(self): + """Every file this checkout's Xcode projects copy into a bundle is present, other than the ones git ignores. - - Given: a working directory outside the checkout, and no `--repo-root` + - Given: this checkout, where the Predictions and Storage host apps and a canary app bundle integration-test + configuration files that git ignores and a fresh checkout lacks - When: - - the only changed file is a README the Auth host app bundles + - `--check-bundled` runs over it - Then: - - `auth` is selected, as when run from the checkout's root - - stderr has no "found no Xcode project" note: the projects were read, so the selection does not come from - the fallback that treats an unread root's docs as bundled (which selects `auth` too) + - it exits 0 and prints no `::error` """ - readme = "AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/README.md" - result = self.run_selector(INTEG_GROUPS, [readme], repo_root=None, cwd=self.work.name) - self.assertEqual(selection(result), ["auth"]) - self.assertNotIn("found no Xcode project", result.stderr) + result = self.run_check() + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertNotIn("::error", result.stdout) + + def test_the_check_fails_on_a_missing_bundled_file(self): + """A bundled file missing from the checkout fails the check, naming the project; an unlisted or ignored one + does not. + + - Given: a scratch checkout whose one project bundles `GeoStressTests/README.md` (through a group with a + `path` inside a group with only a `name`) and `GeoStressTests/amplifyconfiguration.json`, which a + `.gitignore` ignores, and lists `GeoStressTests/Notes/README.md` outside any bundle; none of the three is + in the checkout + - When: + - `--check-bundled` runs over it, then again with the README restored, then again with the `.gitignore` + removed + - Then: + - the first run exits 1 with an `::error` on the project file naming the README, and names neither the + unlisted README nor the ignored configuration + - the second passes + - the third fails, naming the configuration + """ + root = self.scratch_checkout(GEO_PBXPROJ, files=[GEO_HOST_APP + ".gitignore"]) + with open(os.path.join(root, GEO_HOST_APP + ".gitignore"), "w") as f: + f.write("amplifyconfiguration.json\n") + readme = GEO_HOST_APP + "GeoStressTests/README.md" + result = self.run_check(root) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + errors = [line for line in result.stdout.splitlines() if line.startswith("::error")] + self.assertEqual(len(errors), 1, result.stdout) + self.assertTrue(errors[0].startswith("::error file=" + GEO_PROJECT + "/project.pbxproj::"), errors[0]) + self.assertIn(readme, errors[0]) + + os.makedirs(os.path.dirname(os.path.join(root, readme))) + with open(os.path.join(root, readme), "w") as f: + f.write("x\n") + result = self.run_check(root) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + os.remove(os.path.join(root, GEO_HOST_APP + ".gitignore")) + result = self.run_check(root) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + self.assertIn(GEO_HOST_APP + "GeoStressTests/amplifyconfiguration.json", result.stdout) + + def test_the_check_fails_on_a_project_it_cannot_read(self): + """A project the parser can't read fails the check, naming it, rather than passing unchecked. - def test_a_bundled_readme_renamed_away_from_md_is_read_through_its_old_path(self): - """A bundled README renamed to a name that isn't `.md` still makes the selector read the projects. + - Given: a scratch checkout whose one project file is truncated + - When: + - `--check-bundled` runs over it + - Then: + - it exits 1 with an `::error` on that project's file + """ + root = self.scratch_checkout(GEO_PBXPROJ[:len(GEO_PBXPROJ) // 2]) + result = self.run_check(root) + self.assertEqual(result.returncode, 1) + self.assertIn("::error file=" + GEO_PROJECT + "/project.pbxproj::could not read", result.stdout) - - Given: this checkout's Xcode projects, where the Auth host app bundles `AuthIntegrationTests/README.md`, - and the real group files + def test_the_check_fails_when_git_cannot_say_what_it_ignores(self): + """Outside a git checkout, a missing bundled file fails the check rather than being taken as ignored. + + - Given: a scratch directory that is not a git repository, whose one project bundles a README it lacks - When: - - that README is renamed to a `.txt` file under the ignored `scripts/`, so the only `.md` path in the change - is the rename's old path + - `--check-bundled` runs over it - Then: - - the old path selects `auth` with the integration groups and AWSPluginsCore with the unit groups, as its - deletion does: the projects are read for a `.md` on the old side of a rename too + - it exits 1 and says it could not ask git """ - old = "AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/README.md" - new = "scripts/AuthIntegrationTests-README.txt" - self.assertEqual(self.selected(INTEG_GROUPS, new, renames=[(old, new)]), ["auth"]) - self.assertEqual(self.selected(UNIT_GROUPS, new, renames=[(old, new)]), ["AWSPluginsCore"]) + root = self.scratch_checkout(GEO_PBXPROJ, git=False) + result = self.run_check(root) + self.assertEqual(result.returncode, 1) + self.assertIn("could not ask git", result.stdout) + + def test_the_default_repo_root_is_the_checkout_holding_the_selector(self): + """Run from another directory without `--repo-root`, the check still reads this checkout's projects. + + - Given: a working directory outside the checkout, and no `--repo-root` + - When: + - `--check-bundled` runs + - Then: + - it passes, as when run from the checkout's root, with no "found no Xcode project" error + """ + result = self.run_check(repo_root=None, cwd=self.work.name) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertNotIn("found no Xcode project", result.stdout) - def test_a_root_with_no_project_is_unread(self): - """A `--repo-root` holding no Xcode project makes every doc count as bundled, not none of them. + def test_a_root_with_no_project_fails_the_check(self): + """A `--repo-root` holding no Xcode project fails the check rather than passing with nothing checked. - Given: an empty directory as `--repo-root` - When: - - the only changed file is the Auth plugin's README, under the `auth` group's path + - `--check-bundled` runs - Then: - - `auth` is selected, and the selector says on stderr that it found no project + - it exits 1 with an `::error` saying it found no project """ root = os.path.join(self.work.name, "empty") os.makedirs(root) - result = self.run_selector(INTEG_GROUPS, ["AmplifyPlugins/Auth/README.md"], root) - self.assertEqual(selection(result), ["auth"]) - self.assertIn("found no Xcode project", result.stderr) + result = self.run_check(root) + self.assertEqual(result.returncode, 1) + self.assertIn("::error::found no Xcode project", result.stdout) + def test_the_selection_needs_its_inputs_without_the_check(self): + """Without `--check-bundled`, a missing `--package`, `--changed` or `--groups` is a usage error. + + - Given: no package dump argument + - When: + - the selector runs with only `--changed` and `--groups` + - Then: + - it exits 2 and names the required options + """ + result = subprocess.run([sys.executable, SCRIPT, "--changed", self.write_list("changed.txt", ["x"]), + "--groups", INTEG_GROUPS], capture_output=True, text=True) + self.assertEqual(result.returncode, 2) + self.assertIn("--package, --changed and --groups are required", result.stderr) def selection(result): """The `selected=` array in a selector run's output.""" @@ -397,24 +422,26 @@ def selection(result): GEO_PROJECT = GEO_HOST_APP + "GeoHostApp.xcodeproj" # A minimal project: the main group holds a name-only group, which holds the `GeoStressTests` path group, which -# holds README.md and Notes/README.md. Only the first is in the Copy Bundle Resources phase. +# holds README.md, Notes/README.md and amplifyconfiguration.json. The first and the last are in the Copy Bundle +# Resources phase. GEO_PBXPROJ = """// !$*UTF8*$! { archiveVersion = 1; objects = { B1 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = F1 /* README.md */; }; + B3 /* amplifyconfiguration.json in Resources */ = {isa = PBXBuildFile; fileRef = F3 /* amplifyconfiguration.json */; }; F1 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; F2 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Notes/README.md; sourceTree = ""; }; + F3 /* amplifyconfiguration.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = amplifyconfiguration.json; sourceTree = ""; }; G0 = {isa = PBXGroup; children = (G1 /* Tests */, ); sourceTree = ""; }; G1 /* Tests */ = {isa = PBXGroup; children = (G2 /* GeoStressTests */, ); name = Tests; sourceTree = ""; }; - G2 /* GeoStressTests */ = {isa = PBXGroup; children = (F1 /* README.md */, F2 /* README.md */, ); path = GeoStressTests; sourceTree = ""; }; + G2 /* GeoStressTests */ = {isa = PBXGroup; children = (F1 /* README.md */, F2 /* README.md */, F3 /* amplifyconfiguration.json */, ); path = GeoStressTests; sourceTree = ""; }; P1 /* Project object */ = {isa = PBXProject; mainGroup = G0; projectDirPath = ""; targets = (); }; - R1 /* Resources */ = {isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = (B1 /* README.md in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; + R1 /* Resources */ = {isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = (B1 /* README.md in Resources */, B3 /* amplifyconfiguration.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; }; rootObject = P1 /* Project object */; } """ - if __name__ == "__main__": unittest.main() From 76743337e1fb65995515620b37dc6f8dc27f5291 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:52:50 -0400 Subject: [PATCH 34/57] ci: run the test selector's self-test in its own workflow The project.pbxproj trigger the selector's self-test needs applied to the whole script self-tests workflow, so a project change also started the macOS test-configuration job and the fixture-lock job, neither of which reads a project. The selector's job moves to ci_selector_self_tests.yml with the scripts/python and project triggers, and keeps its job name, Test selector. No branch protection rule or ruleset requires a check by that name. --- .github/workflows/ci_script_self_tests.yml | 24 ++---------- .github/workflows/ci_selector_self_tests.yml | 39 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/ci_selector_self_tests.yml diff --git a/.github/workflows/ci_script_self_tests.yml b/.github/workflows/ci_script_self_tests.yml index fb325f6126..f181a83a7e 100644 --- a/.github/workflows/ci_script_self_tests.yml +++ b/.github/workflows/ci_script_self_tests.yml @@ -5,9 +5,9 @@ # integration-test jobs run it, with BSD find. # - scripts/cognito-engine/check_fixture_lock.sh: the plugin behavior snapshot lock's self-file check # (test_check_fixture_lock.sh, in a scratch repository built from this checkout). -# - scripts/python/affected_targets.py: the test selector the unit and integration workflows run, over the real -# group files, this checkout's Xcode projects (the READMEs host apps bundle) and a stub package dump -# (test_affected_targets.py), so it needs no Swift toolchain. A project change runs it too. +# +# The test selector's self-test is in ci_selector_self_tests.yml, so that a project change, which it reads, starts +# only that one. # # No AWS call is made, and the jobs need no credentials and no secrets. @@ -20,9 +20,6 @@ on: - 'scripts/cognito-engine/check_fixture_lock.sh' - 'scripts/cognito-engine/test_check_fixture_lock.sh' - '.github/workflows/auth_golden_fixture_lock.yml' - - 'scripts/python/**' - # The selector's self-test reads the host apps' Copy Bundle Resources from the real Xcode projects. - - '**/*.xcodeproj/project.pbxproj' - '.github/workflows/ci_script_self_tests.yml' push: branches: @@ -32,9 +29,6 @@ on: - 'scripts/cognito-engine/check_fixture_lock.sh' - 'scripts/cognito-engine/test_check_fixture_lock.sh' - '.github/workflows/auth_golden_fixture_lock.yml' - - 'scripts/python/**' - # The selector's self-test reads the host apps' Copy Bundle Resources from the real Xcode projects. - - '**/*.xcodeproj/project.pbxproj' - '.github/workflows/ci_script_self_tests.yml' permissions: @@ -64,15 +58,3 @@ jobs: - name: Run the self-test run: bash scripts/cognito-engine/test_check_fixture_lock.sh - - test-selector: - name: Test selector - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - with: - persist-credentials: false - - - name: Run the self-test - run: python3 -m unittest discover -s scripts/python -p 'test_*.py' diff --git a/.github/workflows/ci_selector_self_tests.yml b/.github/workflows/ci_selector_self_tests.yml new file mode 100644 index 0000000000..6cbde63db5 --- /dev/null +++ b/.github/workflows/ci_selector_self_tests.yml @@ -0,0 +1,39 @@ +# Offline self-test of scripts/python/affected_targets.py, the test selector and bundled-file check the unit, +# integration and build workflows' detect steps run (test_affected_targets.py). It runs over the real group files, a +# stub package dump and this checkout's Xcode projects (the files host apps copy into bundles), so it needs no Swift +# toolchain. A project change runs it too, since the check reads the projects; the other script self-tests are in +# ci_script_self_tests.yml, which a project change doesn't start. +# +# No AWS call is made, and the job needs no credentials and no secrets. + +name: CI | Test Selector Self-Tests + +on: + pull_request: + paths: + - 'scripts/python/**' + - '**/*.xcodeproj/project.pbxproj' + - '.github/workflows/ci_selector_self_tests.yml' + push: + branches: + - main + paths: + - 'scripts/python/**' + - '**/*.xcodeproj/project.pbxproj' + - '.github/workflows/ci_selector_self_tests.yml' + +permissions: + contents: read + +jobs: + test-selector: + name: Test selector + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 + with: + persist-credentials: false + + - name: Run the self-test + run: python3 -m unittest discover -s scripts/python -p 'test_*.py' From 9560239888d29aab897d9d6cb1dfb773b0e3ddf2 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 13:43:04 -0400 Subject: [PATCH 35/57] fix(auth): keep a session's dead-token expiry when its session core is recreated The expiry a refresh found (a refresh token refused as reused twice, 30 seconds apart, or refused as expired or revoked, and a federated session's refused provider token) was kept on the session core only. Once every handle and provider of the session was released, or after a relaunch, the next call refreshed the dead token again: one wasted refresh, the same refusal, and the 30-second rule started over. The proof now also saves a marker with the record, best effort: the cause and a SHA-256 of the dead refresh token (of the credentials, when they hold none), under an additive deadToken key in a named session's envelope and in the default session's sidecar. The Auth plugin's record is never rewritten for it. A core that installs a record whose credentials still hold that token starts expired, with no event, and answers with the saved cause's text; a refresh re-reads first and never refreshes such a record, so another process's core does not spend the token either. The marker names the token, not the record, so any new login or federation ends it, whoever saves it, and signIn stays allowed on an expired session. A marker this build cannot read is ignored. A failed save is logged and leaves the expiry the core's only. The plugin's isRefreshTokenExpired flag is not read: the plugin sets it on whatever record is stored after its own refusal. The in-process reuse memory no longer keeps the dead payload. The reuse-reset test of the refused-token branch now pins that the refusal ends the count of reuses in a row, which shows when the expiry could not be saved. --- .../Sources/AmplifyCognitoClient+WebUI.swift | 3 +- .../Sources/AuthClientError.swift | 6 +- .../Sources/Core/SessionCore+Refresh.swift | 99 ++-- .../Sources/Core/SessionCore+Session.swift | 6 +- .../Sources/Core/SessionCore+WebUI.swift | 9 +- .../Sources/Core/SessionCore.swift | 71 ++- .../Sources/Core/SessionRecordIO.swift | 8 + .../Sources/Storage/DeadToken.swift | 85 ++++ .../Sources/Storage/DefaultSessionMeta.swift | 22 +- .../Storage/SessionRecordEnvelope.swift | 14 +- .../SessionRecordStore+DefaultSession.swift | 55 ++- .../Sources/Storage/SessionRecordStore.swift | 18 + .../Sources/Support/ClientLog.swift | 3 +- .../UnitTests/CredentialProviderTests.swift | 61 +-- .../Tests/UnitTests/SavedExpiryTests.swift | 459 ++++++++++++++++++ .../UnitTests/Support/ClientTestSupport.swift | 8 +- .../Tests/UnitTests/WebUISignOutTests.swift | 41 ++ 17 files changed, 850 insertions(+), 118 deletions(-) create mode 100644 AmplifyClients/AmplifyCognitoClient/Sources/Storage/DeadToken.swift create mode 100644 AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/SavedExpiryTests.swift diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/AmplifyCognitoClient+WebUI.swift b/AmplifyClients/AmplifyCognitoClient/Sources/AmplifyCognitoClient+WebUI.swift index 030777e167..f380405a9d 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/AmplifyCognitoClient+WebUI.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/AmplifyCognitoClient+WebUI.swift @@ -116,8 +116,7 @@ public extension AmplifyCognitoClient { /// /// It also closes this session's hosted-UI sign-out page, if one is showing: that sign-out is then /// `.failed(.userCancelled)` and the session stays signed in, as when the user closes the page (a session a - /// refresh found expired while it was in memory signs out anyway, when the login signed out still has the - /// refresh token found dead). + /// refresh found expired signs out anyway, when the login signed out still has the refresh token found dead). /// /// A call still waiting behind another of this session's sign-ins (its sign-in lock) is not reached yet, and /// goes on; cancel its task to stop it there. diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/AuthClientError.swift b/AmplifyClients/AmplifyCognitoClient/Sources/AuthClientError.swift index 599769bea0..f4f6668223 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/AuthClientError.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/AuthClientError.swift @@ -89,9 +89,9 @@ public enum AuthClientError { /// hosted UI's sign-in browser. Also thrown when `cancelWebUISignIn()` or `resetSystemSheet()` closes the /// sheet. Nothing was changed; retrying is the user's choice. For the hosted UI's sign-out page it is not /// thrown but returned, in `AuthClientSignOutResult.failed`, and the session stays signed in; unless a refresh had - /// found the session's refresh token dead while the session was in memory (until every handle and provider for it - /// is released, or a relaunch), and the login being signed out still has that token: it is signed out on this - /// device anyway, and this is the `.partial` result's `hostedUIError`. + /// found the session's refresh token dead (in this launch or an earlier one: the expiry is saved with the session), + /// and the login being signed out still has that token: it is signed out on this device anyway, and this is the + /// `.partial` result's `hostedUIError`. case userCancelled(ErrorDescription, RecoverySuggestion, Error? = nil) /// A local WebAuthn ceremony failed before its result was sent to Cognito. The underlying error is the diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+Refresh.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+Refresh.swift index 818e1ed3a2..d59f77f989 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+Refresh.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+Refresh.swift @@ -233,8 +233,8 @@ extension SessionCore { if pendingChallenge != nil { throw Self.notSignedIn(sessionId) } - if let dead = expiredCredentials { - throw expiredError(holding: dead) + if let expiry { + throw expiredError(expiry) } guard let snapshot = restoredSnapshotIfAny else { throw Self.notSignedIn(sessionId) @@ -287,20 +287,22 @@ extension SessionCore { /// `RefreshTokenReuse.minimumGap` after the first, whose re-read record's credentials are still exactly /// the ones sent, means nobody else refreshed: the token is dead (a record rolled forward over a token /// the plugin rotated away), so, as for step 4, the session is marked expired, with its own `sessionExpired` - /// (`refreshTokenAlreadyUsed`, `refreshTokenReused` underneath), which it answers with from then on while the - /// session is in memory: until every handle and provider for it is released, or a relaunch, since the expiry - /// is this core's (`isExpired`). Forced or not: a forced refresh refused this way counts too, so a second one - /// expires a session whose saved tokens are still valid, and an unforced call then stops handing them out. + /// (`refreshTokenAlreadyUsed`, `refreshTokenReused` underneath), which it answers with from then on, until its + /// credentials change to ones without that refresh token: the expiry is saved with the record, so a new core of + /// the session (every handle and provider for it released, or a relaunch) answers the same without refreshing. + /// Forced or not: a forced refresh refused this way counts too, so a second one expires a session whose saved + /// tokens are still valid, and an unforced call then stops handing them out. /// 4. `refreshTokenInvalid` means the session needs a fresh sign-in — unless another writer has /// meanwhile stored different credentials (with rotation, a refresh racing another process's can be /// told its token is invalid), so re-read first, as for reuse, and adopt them if so. Otherwise send /// `.sessionExpired` and throw it. The record is kept, and the state stays signed in as its user. /// /// Both expiries install the record they re-read, which holds the credentials proved dead, as they mark the - /// session expired (`markExpired(installing:)`), so the next call does not take that record, saved again in other - /// bytes by another writer, for a new login and refresh the dead token again. A record saved again after the proof - /// with the same refresh token keeps the expiry as well (`apply`); only credentials with another refresh token, or - /// none, end it. + /// session expired (`markExpired(installing:cause:)`), so the next call does not take that record, saved again in + /// other bytes by another writer, for a new login and refresh the dead token again. A record saved again after the + /// proof with the same refresh token keeps the expiry as well (`apply`); only credentials with another refresh token, + /// or none, end it. Both then save the expiry with the record (`saveExpiry`), best effort, and step 1 never refreshes + /// a record that says its refresh token is dead (`savedExpiry(in:)`), whichever core or process saved it. /// /// **A refresh that cannot be saved throws the storage error**; it never hands out tokens storage does /// not hold. Known limit: with rotation, the retry then uses a refresh token the server may already @@ -314,6 +316,12 @@ extension SessionCore { nonisolated func refreshOnce(force: Bool) async throws -> RefreshCommit { try await withRecord { [self] store in let current = try await store.load(sessionId) + if let saved = savedExpiry(in: current) { + // Another core of this session, or another process, proved this refresh token dead since this core + // last read: answered as that proof, never refreshed again. + await apply(current, event: nil) + throw expiredError(saved) + } guard let payload = current.credentials, try force || needsRefresh(payload, of: current) else { return await .adopted(apply(current, event: nil)) } @@ -345,7 +353,8 @@ extension SessionCore { // a fresh sign-in. if let held = reread.credentials, engine.sameCredentials(held, payload), refreshTokenReuse.repeated(payload, at: now(), same: engine.sameCredentials) { - await markExpired(installing: reread) + await markExpired(installing: reread, cause: .reused) + await saveExpiry(of: reread, cause: .reused, store: store) throw Self.refreshTokenAlreadyUsed(sessionId) } throw AuthClientError.unknown( @@ -358,14 +367,13 @@ extension SessionCore { if !reread.holdsCredentials(payload, engine: engine) { return await .adopted(apply(reread, event: nil)) } - // Expired by this refusal, not by an earlier reuse. The reuse memory is the record's, for the process, - // and outlives this core (`RefreshTokenReuse`): a new core of a session an earlier one proved dead after - // a reuse restores it unexpired and refreshes it again. Forgotten here, so this refusal's own text - // ("expired or been revoked", `refreshTokenInvalid` underneath) answers, now and on later calls, not - // the earlier proof's "already used". + // Expired by this refusal, which ends any count of reuses in a row: the reuse memory is the record's, + // for the process, and outlives this core (`RefreshTokenReuse`), so if this expiry could not be saved, a + // new core that refreshes the token again and is told it was reused counts that as a first reuse. refreshTokenReuse.reset() - await markExpired(installing: reread) - throw expiredError(holding: payload) + await markExpired(installing: reread, cause: .invalid) + await saveExpiry(of: reread, cause: .invalid, store: store) + throw expiredError(Expiry(credentials: payload, cause: .invalid)) } catch { throw Self.engineFailure(error) } @@ -423,6 +431,28 @@ extension SessionCore { throw IdentityStepFailure(error: error, commit: result) } + /// Saves the expiry just marked (`markExpired(installing:cause:)`) with `reread`, the record it proved dead, so a new + /// core of the session starts expired (`SessionRecord.deadToken`), and installs the record as saved. Best effort: + /// if the record moved since it was read, or storage fails, the expiry stays this core's only, and a new core + /// refreshes the token once more before it expires again. A failure is logged. + private nonisolated func saveExpiry(of reread: SessionSnapshot, cause: DeadToken.Cause, store: SessionRecordIO) async { + guard let credentials = reread.credentials else { + return + } + let deadToken = DeadToken(token: deadTokenDigest(of: credentials), cause: cause) + do { + if case .committed(let saved) = try await store.saveDeadToken(deadToken, for: sessionId, expecting: reread.version) { + await apply(SessionSnapshot(saved), event: nil) + } + } catch { + ClientLog.logger(ClientLog.sessionRecordStore).warn(Self.expirySaveFailedWarning) + } + } + + /// What a failed save of an expiry logs: no identifiers. + static let expirySaveFailedWarning = + "A session's expiry could not be saved. After a relaunch the session refreshes its refresh token once more before it expires again." + /// Whether two payloads hold the same user pool refresh token, or neither holds one. nonisolated func sameRefreshToken(_ lhs: Data, _ rhs: Data) throws -> Bool { try engine.checkedUserPoolTokens(in: lhs)?.refreshToken == engine.checkedUserPoolTokens(in: rhs)?.refreshToken @@ -574,17 +604,14 @@ extension SessionCore { ) } - /// What a session known to be expired (`isExpired`) while holding `payload` answers with: the dead token after a - /// reuse if that is what expired it, still holding the credentials it was proved on - /// (`RefreshTokenReuse.provedDead`); a federated session's refused provider token; else `sessionExpired(_:)`. - nonisolated func expiredError(holding payload: Data?) -> AuthClientError { - guard let payload else { - return Self.sessionExpired(sessionId) - } - if refreshTokenReuse.provedDead(payload, same: engine.sameCredentials) { + /// What a session known to be expired (`isExpired`) answers with: the dead token after a reuse if that is what + /// expired it; a federated session's refused provider token; else `sessionExpired(_:)`. The cause is the expiry's + /// own, saved with it, so a new core answers as the core that proved it did. + nonisolated func expiredError(_ expiry: Expiry) -> AuthClientError { + if expiry.cause == .reused { return Self.refreshTokenAlreadyUsed(sessionId) } - if (try? engine.describe(payload))?.kind == .federated { + if (try? engine.describe(expiry.credentials))?.kind == .federated { return Self.providerTokenRefused(sessionId) } return Self.sessionExpired(sessionId) @@ -707,15 +734,14 @@ final class RefreshTokenReuse: @unchecked Sendable { /// dead token. static let minimumGap: TimeInterval = 30 - // `@unchecked Sendable`: `first` and `dead` are only touched while holding `lock`. + // `@unchecked Sendable`: `first` is only touched while holding `lock`. private let lock = NSLock() private var first: (payload: Data, at: Date)? - /// The payload whose refresh token a repeat proved dead, until the next reset. - private var dead: Data? /// Records a reuse of `payload` with the record unchanged at `now`; `true` if an earlier one of the same payload - /// was at least `minimumGap` before, which proves its refresh token dead (`provedDead`). A repeat sooner than that - /// is not counted, and keeps the first's time. + /// was at least `minimumGap` before, which proves its refresh token dead: the core then marks the session expired + /// and saves that with the record (`SessionCore.markExpired(installing:cause:)`). A repeat sooner than that is not + /// counted, and keeps the first's time. /// /// - Parameter same: Whether two payloads hold the same credentials (`SessionEngine.sameCredentials`), so a /// record another writer only re-encoded in between still counts as unchanged. @@ -729,22 +755,13 @@ final class RefreshTokenReuse: @unchecked Sendable { return false } self.first = nil - dead = payload return true } } - /// Whether a repeat since the last reset proved `payload`'s refresh token dead. - func provedDead(_ payload: Data, same: (Data, Data) -> Bool = { $0 == $1 }) -> Bool { - lock.withLock { - dead.map { same($0, payload) } ?? false - } - } - func reset() { lock.withLock { first = nil - dead = nil } } } diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+Session.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+Session.swift index cace380beb..1361dd44ab 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+Session.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+Session.swift @@ -61,10 +61,10 @@ extension SessionCore { return try await fetchSession(forceRefresh: forceRefresh && acquisition == .adopted, adopted: true) } case .guest, .federated, .signedIn: - if let dead = expired { - // The dead credentials, not the snapshot's: the same login saved again since (other bytes, or the + if let expired { + // The expiry's cause, not the snapshot's: the same login saved again since (other bytes, or the // plugin's expired flag) answers as the proof did. - return Self.session(failingWith: expiredError(holding: dead)) + return Self.session(failingWith: expiredError(expired)) } guard let payload = snapshot.credentials else { return Self.session(failingWith: Self.notSignedIn(sessionId)) diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+WebUI.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+WebUI.swift index 77f59585e5..65c036dc61 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+WebUI.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+WebUI.swift @@ -32,7 +32,7 @@ extension SessionCore { /// | this session's passkey registration holds the sheet (or its lease is granted, not yet attached) | refused; the registration is stopped, then the page waits up to `passkeySheetClosingTimeout` for its sheet to close | as below | as below; with no registration to stop, asked for once more with `.fail`, else the busy row | /// | the sheet stays busy (this session's passkey sheet did not close in time, or passed to another session as it closed) | none | — | throws `SignOutRefusal(.browserBusy(holder:))`: nothing cleared | /// | lease taken | around this attempt | `.present` | first stops this session's passkey registrations still before their sheet, then the engine's outcome; a page that could not be shown or completed (the window gone, the browser failed) is the engine's `SignOutRefusal`: nothing cleared | - /// | the user closed the page | released | — | rethrows `.userCancelled`: nothing cleared, unless the session is known to be expired while in memory (`afterClosedLogout`) and the login signed out still has the refresh token proved dead, which reruns `.skip` and reports it | + /// | the user closed the page | released | — | rethrows `.userCancelled`: nothing cleared, unless the session is known to be expired (`afterClosedLogout`) and the login signed out still has the refresh token proved dead, which reruns `.skip` and reports it | /// | `cancelWebUISignIn()` or `resetSystemSheet()` interrupted the lease | released | — | waits for the sign-out's own result: the row above if that closed the page, else what it did (signed out, or its failure reported as the revoke's) | /// | the interrupt came before the body began (between the grant and the start; the body is abandoned, never runs) | released | — | the closed-page row, at once: nothing ran | /// | the caller's task was cancelled | released | — | the same decision: `CancellationError` if the body never began or closed the page (nothing cleared), else what it did, and the session is cleared | @@ -230,10 +230,9 @@ extension SessionCore { /// The user closed the logout page: they stay signed in, as with the plugin. Unless the session is known to be /// expired (the plugin's #3956 behaviour): its tokens are dead anyway, so it signs out without the page and /// reports the cookie left behind (`expiredLogoutClosed(_:)`). The plugin keys that on `isRefreshTokenExpired`, - /// which it saves with the session, so it holds across launches; here it is this core's expiry - /// (`expiredCredentials`), in memory, set when a refresh found the refresh token dead. So it holds only while the - /// session is in memory: once every handle and provider for it is released, or after a relaunch, until a refresh - /// finds the token dead again, a closed page keeps the session signed in. + /// which it saves with the session; here it is the core's expiry (`expiredCredentials`), set when a refresh found + /// the refresh token dead, and saved with the record (`SessionRecord.deadToken`), so it holds across launches too, + /// and for a new core once every handle and provider for the session was released. /// /// `payload` is what the sign-out read from the store, which may be a login another writer saved since this core /// marked the session expired (an app sharing the access group signed the session out and in again). Only a diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore.swift index 47b2628d51..bbbd7bf08e 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore.swift @@ -98,15 +98,28 @@ actor SessionCore { /// The last state sent on `states`, to send only changes. private var lastPublished: AuthSessionState? - /// The credentials a refresh proved dead (`markExpired(installing:)`), while the session is known to be expired. - /// Cleared when the credentials change to ones without the dead refresh token (`holdsDeadRefreshToken(_:)`). - private(set) var expiredCredentials: Data? + /// The session is known to be expired: a refresh proved the refresh token of `credentials` dead, for `cause`. + struct Expiry: Sendable { + let credentials: Data + let cause: DeadToken.Cause + } + + /// Set by a refresh that proves the refresh token dead (`markExpired(installing:cause:)`), or by installing a record + /// that says so (`savedExpiry(in:)`); cleared when the credentials change to ones without the dead refresh token + /// (`holdsDeadRefreshToken(_:)`). + private(set) var expiry: Expiry? + + /// The credentials a refresh proved dead, while the session is known to be expired. + var expiredCredentials: Data? { + expiry?.credentials + } /// Whether a refresh proved the refresh token dead. Cleared when the credentials change to ones without that refresh - /// token: a new login, a sign-out. In memory, on this core: once every handle and provider for the session is - /// released, or after a relaunch, a new core starts unexpired. + /// token: a new login, a new federation, a sign-out. Saved with the record (`SessionRecord.deadToken`), so a new + /// core of the session, once every handle and provider for it was released or after a relaunch, starts expired + /// too while the record still holds that token. var isExpired: Bool { - expiredCredentials != nil + expiry != nil } /// Whether a hosted-UI sign-in call of this session is in progress, from before it waits for the @@ -178,10 +191,9 @@ actor SessionCore { snapshot } - /// The snapshot and, while the refresh token is known dead, the credentials proved dead (`expiredCredentials`), read - /// in one hop so they agree. - var snapshotAndExpiry: (SessionSnapshot, Data?) { - (snapshot ?? .absent, expiredCredentials) + /// The snapshot and, while the refresh token is known dead, the expiry (`expiry`), read in one hop so they agree. + var snapshotAndExpiry: (SessionSnapshot, Expiry?) { + (snapshot ?? .absent, expiry) } /// The current state, or `nil` before the first restore. @@ -199,6 +211,8 @@ actor SessionCore { /// /// New credentials end a known expiry, unless they still hold the refresh token proved dead /// (`holdsDeadRefreshToken(_:)`): another writer saved the same login again, in other bytes, after the proof. + /// A record that says its refresh token was proved dead, and still holds it (`savedExpiry(in:)`), starts one, with + /// no event: what a new core restores, or a re-read finds another core or process saved. /// /// Every mutation path ends here, after its storage commit and before the operation returns, so a /// caller that awaited an operation has already caused its event. @@ -213,7 +227,10 @@ actor SessionCore { newLogin: Bool = false ) -> SessionSnapshot { if newLogin || (newSnapshot.credentials != snapshot?.credentials && !holdsDeadRefreshToken(newSnapshot.credentials)) { - expiredCredentials = nil + expiry = nil + } + if !newLogin, expiry == nil, let saved = savedExpiry(in: newSnapshot) { + expiry = saved } snapshot = newSnapshot if case .set(let step) = update { @@ -373,24 +390,42 @@ actor SessionCore { webUISignInInFlight = false } - /// Records that the refresh token of `reread` is dead: installs `reread`, the record the refresh re-read and - /// proved dead, remembers its credentials, and sends `.sessionExpired` unless the session was already expired, in - /// one step on the actor, so no reader sees the record with the flag still clear. Installed so the next call's + /// Records that the refresh token of `reread` is dead, for `cause`: installs `reread`, the record the refresh re-read + /// and proved dead, remembers its credentials, and sends `.sessionExpired` unless the session was already expired, + /// in one step on the actor, so no reader sees the record with the flag still clear. Installed so the next call's /// re-read (`rereadIfExpired`) compares with what the store holds, which may be newer bytes than this core last /// read (the same credentials saved again by another writer). A record saved again after this, holding the same /// refresh token, keeps the expiry too (`apply`): neither is taken for a new login. The state /// does not change: the session stays signed in as its user, so an app knows whom to re-authenticate, and its - /// operations throw `sessionExpired`. - func markExpired(installing reread: SessionSnapshot) { + /// operations throw `sessionExpired`. The caller then saves it with the record (`saveExpiry`). + func markExpired(installing reread: SessionSnapshot, cause: DeadToken.Cause) { let wasExpired = isExpired apply(reread, event: nil) - expiredCredentials = reread.credentials + expiry = reread.credentials.map { Expiry(credentials: $0, cause: cause) } if !wasExpired, isExpired { events.send(.sessionExpired) } } - /// Whether `payload` holds the refresh token a refresh proved dead (`expiredCredentials`), so it is the same login + /// The expiry `snapshot`'s record saved (`SessionRecord.deadToken`), if its credentials still hold the token it + /// names (`deadTokenDigest(of:)`); `nil` otherwise, as for a new login or federation saved over it by a writer that + /// left the marker (the Auth plugin, over `.default`'s sidecar). + nonisolated func savedExpiry(in snapshot: SessionSnapshot) -> Expiry? { + guard let saved = snapshot.ownRecord?.deadToken, let credentials = snapshot.credentials, + saved.token == deadTokenDigest(of: credentials) else { + return nil + } + return Expiry(credentials: credentials, cause: saved.cause) + } + + /// The digest a saved expiry names for `payload` (`DeadToken.digest`): of its user pool refresh token, so the same + /// login saved again in other bytes (the plugin's save with its `isRefreshTokenExpired` flag set) is still the dead + /// one, as `holdsDeadRefreshToken(_:)` compares it; of its bytes when it holds none (a federated session). + nonisolated func deadTokenDigest(of payload: Data) -> String { + DeadToken.digest(refreshToken: (try? engine.checkedUserPoolTokens(in: payload))??.refreshToken, credentials: payload) + } + + /// Whether `payload` holds the refresh token a refresh proved dead (`expiry`), so it is the same login /// whose token died, whatever else changed: the same credentials saved again in other bytes, or the plugin's save of /// the same tokens with its `isRefreshTokenExpired` flag set, which is part of its credentials' equality. Compared /// by refresh token, as a closed logout page is (`afterClosedLogout`). Dead credentials without one (a federated diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionRecordIO.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionRecordIO.swift index 8f797b80b1..2cd25be6a3 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionRecordIO.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionRecordIO.swift @@ -72,6 +72,14 @@ struct SessionRecordIO: Sendable { try await perform { try $0.write(record, for: sessionId, expecting: version) } } + func saveDeadToken( + _ deadToken: DeadToken, + for sessionId: SessionID, + expecting version: RecordVersion? + ) async throws -> SessionRecordStore.CommitOutcome { + try await perform { try $0.saveDeadToken(deadToken, for: sessionId, expecting: version) } + } + func signOut(_ sessionId: SessionID) async throws -> SessionRecordStore.SignOutOutcome { try await perform { try $0.signOut(sessionId) } } diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/DeadToken.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/DeadToken.swift new file mode 100644 index 0000000000..a64170e30b --- /dev/null +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/DeadToken.swift @@ -0,0 +1,85 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import CryptoKit +import Foundation + +/// That a refresh proved a session's refresh token dead, saved with the session's record so that a new session core, +/// after every handle and provider of the session was released or after a relaunch, starts expired rather than +/// refreshing the dead token again (`SessionCore.savedExpiry(in:)`). +/// +/// **It is bound to the dead token**, not to the record: `token` is a digest of the refresh token proved dead (or, for +/// credentials without one, such as a federated session's refused provider token, of the credentials' bytes). It +/// applies only while the record still holds that token, so a new login, a new federation, or a sign-out saved by any +/// writer ends it, whether or not that writer kept or cleared this marker: the Auth plugin, which never reads or writes +/// it, an app extension, or an earlier build, which drops the key it does not know. +/// +/// Stored as `{"cause":"reused","token":"<64 hex digits>"}`: in a named session's envelope (`SessionRecordEnvelope`, +/// key `deadToken`), and in `.default`'s sidecar (`DefaultSessionMeta`, key `deadToken`), beside the plugin's record, +/// which has no room for it. An additive key in both, written only when set, so it needs no schema bump. A value this +/// build cannot read (a newer build's cause, say) is ignored, never taken for a corrupt record: the session then +/// refreshes as if nothing were saved. +struct DeadToken: Equatable, Sendable { + + /// What proved the token dead, which decides the error the session answers with. + enum Cause: String, Sendable { + /// Refused twice as reused, `RefreshTokenReuse.minimumGap` apart, with nobody saving a refresh + /// (`SessionCore.refreshTokenAlreadyUsed(_:)`). + case reused + /// Refused as expired or revoked (`SessionCore.sessionExpired(_:)`), or a federated session's provider token + /// refused by the identity pool (`SessionCore.providerTokenRefused(_:)`). + case invalid + } + + /// The SHA-256 digest, in lowercase hex, of the dead token (`digest(refreshToken:credentials:)`). + let token: String + let cause: Cause + + /// The digest that binds a marker to a token: of `refreshToken` when the credentials hold one, else of the + /// credentials' bytes. Tagged, so the two never collide. + static func digest(refreshToken: String?, credentials: Data) -> String { + let input = refreshToken.map { Data("refreshToken:\($0)".utf8) } ?? Data("credentials:".utf8) + credentials + return SHA256.hash(data: input).map { String(format: "%02x", $0) }.joined() + } +} + +extension DeadToken: Codable { + + /// The on-disk key names. Changing a raw value here changes the stored format. + enum CodingKeys: String, CodingKey { + case token + case cause + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let causeValue = try container.decode(String.self, forKey: .cause) + guard let cause = Cause(rawValue: causeValue) else { + throw DecodingError.dataCorruptedError( + forKey: .cause, + in: container, + debugDescription: "\"\(causeValue)\" is not a cause this build reads." + ) + } + try self.init(token: container.decode(String.self, forKey: .token), cause: cause) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(token, forKey: .token) + try container.encode(cause.rawValue, forKey: .cause) + } +} + +extension KeyedDecodingContainer { + + /// The marker under `key`, or `nil` when there is none or it cannot be read: an unreadable marker is ignored, never + /// taken for a corrupt record. + func decodeDeadTokenIfReadable(forKey key: Key) -> DeadToken? { + (try? decodeIfPresent(DeadToken.self, forKey: key)) ?? nil + } +} diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/DefaultSessionMeta.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/DefaultSessionMeta.swift index 9b966ec36f..e5befa5b6a 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/DefaultSessionMeta.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/DefaultSessionMeta.swift @@ -8,7 +8,8 @@ import Foundation /// What `.default` keeps beside the Auth plugin's record, which has no room for it: the session's display -/// label, and the last signed-in user, for the signed-out row a picker shows. +/// label, the last signed-in user, for the signed-out row a picker shows, and that a refresh proved the record's +/// refresh token dead (`deadToken`). /// /// Stored under its own account, `SessionRecordKey.metaAccount(in:)` /// (`amplify.1..$default.meta`), which no listing reads as a session. @@ -19,12 +20,14 @@ import Foundation /// /// **The stored format is frozen.** Every key is spelled in `CodingKeys` and every value is encoded by hand, with /// sorted keys, so neither renaming a Swift property nor changing an encoder's strategies can change what is on -/// disk. `label`, `username` and `userId` are omitted when `nil`. As for `SessionRecordEnvelope`, any change a +/// disk. `label`, `username`, `userId` and `deadToken` are omitted when `nil`; `deadToken` was added to version 1 as a +/// purely additive key. As for `SessionRecordEnvelope`, any change a /// version-1 reader cannot interpret bumps `schemaVersion`; purely additive keys do not, since readers ignore /// keys they do not know. /// /// **It is bound to a user** (`applies(toRecordUser:recordIsSignedOut:)`), so a label never passes from one -/// user to another. +/// user to another. `deadToken` is bound to the token it names instead (`DeadToken`): the plugin saving a new login +/// leaves the sidecar as it was, and the marker then no longer applies. struct DefaultSessionMeta: Equatable, Sendable { /// The newest schema this build reads and the one it writes. @@ -45,18 +48,24 @@ struct DefaultSessionMeta: Equatable, Sendable { /// user has signed in since the sidecar was written, as for a label set before anyone signed in. var userId: String? + /// That a refresh proved the shared record's refresh token dead, while the record still holds that token + /// (`SessionRecord.deadToken`). + var deadToken: DeadToken? + init( schemaVersion: Int = currentSchemaVersion, lastWriteTimestamp: Date, label: String?, username: String?, - userId: String? + userId: String?, + deadToken: DeadToken? = nil ) { self.schemaVersion = schemaVersion self.lastWriteTimestamp = Self.roundedToMilliseconds(lastWriteTimestamp) self.label = label self.username = username self.userId = userId + self.deadToken = deadToken } // MARK: Binding to a user @@ -109,6 +118,7 @@ extension DefaultSessionMeta: Codable { case label case username case userId + case deadToken } init(from decoder: Decoder) throws { @@ -119,7 +129,8 @@ extension DefaultSessionMeta: Codable { lastWriteTimestamp: Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1_000), label: container.decodeIfPresent(String.self, forKey: .label), username: container.decodeIfPresent(String.self, forKey: .username), - userId: container.decodeIfPresent(String.self, forKey: .userId) + userId: container.decodeIfPresent(String.self, forKey: .userId), + deadToken: container.decodeDeadTokenIfReadable(forKey: .deadToken) ) } @@ -130,6 +141,7 @@ extension DefaultSessionMeta: Codable { try container.encodeIfPresent(label, forKey: .label) try container.encodeIfPresent(username, forKey: .username) try container.encodeIfPresent(userId, forKey: .userId) + try container.encodeIfPresent(deadToken, forKey: .deadToken) } } diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordEnvelope.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordEnvelope.swift index 9d91f3976b..2fb9ada96b 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordEnvelope.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordEnvelope.swift @@ -37,6 +37,11 @@ struct SessionRecord: Equatable, Sendable { /// key, written only when `true`, so it needs no schema bump. var identityPending: Bool = false + /// That a refresh proved the credentials' refresh token dead (`DeadToken`), so a new session core starts expired. + /// Applies only while the credentials still hold that token. An additive key, written only when set, so it needs no + /// schema bump; a record written without it (any new login, a refresh, a sign-out) has none. + var deadToken: DeadToken? + /// A row that is kept after sign-out so a picker can still offer it, holding no credentials. static func signedOut(label: String?, username: String?, userId: String? = nil) -> SessionRecord { SessionRecord(label: label, username: username, userId: userId, kind: .signedOut, credentials: nil) @@ -57,8 +62,8 @@ struct SessionRecord: Equatable, Sendable { /// /// **The stored format is frozen.** Every key is spelled in `CodingKeys` and every value is encoded by /// hand, so neither renaming a Swift property nor changing an encoder's date or data strategy can change -/// what is on disk. `label`, `username`, `userId` and `credentials` are omitted when `nil`. `userId` -/// was added to version 1 as a purely additive key. +/// what is on disk. `label`, `username`, `userId`, `credentials` and `deadToken` are omitted when `nil`. `userId` +/// and `deadToken` were added to version 1 as purely additive keys. /// /// **Any change a version-1 reader cannot interpret bumps `schemaVersion`** — including a new /// `SessionKind` value. A reader classifies a higher version as `unsupportedSchema`, which is neither @@ -112,6 +117,7 @@ extension SessionRecordEnvelope: Codable { case kind case credentials case identityPending + case deadToken } init(from decoder: Decoder) throws { @@ -153,7 +159,8 @@ extension SessionRecordEnvelope: Codable { userId: try container.decodeIfPresent(String.self, forKey: .userId), kind: kind, credentials: credentials, - identityPending: try container.decodeIfPresent(Bool.self, forKey: .identityPending) ?? false + identityPending: try container.decodeIfPresent(Bool.self, forKey: .identityPending) ?? false, + deadToken: container.decodeDeadTokenIfReadable(forKey: .deadToken) ) ) } @@ -171,6 +178,7 @@ extension SessionRecordEnvelope: Codable { if record.identityPending { try container.encode(true, forKey: .identityPending) } + try container.encodeIfPresent(record.deadToken, forKey: .deadToken) } } diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift index a052ca649e..02f0e1406d 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore+DefaultSession.swift @@ -208,7 +208,10 @@ extension SessionRecordStore { kind: kind, credentials: bytes, // Derived: `.default` has no envelope to hold the flag. - identityPending: summary.kind == .userPoolOnly && namespace.pools.hasIdentityPool + identityPending: summary.kind == .userPoolOnly && namespace.pools.hasIdentityPool, + // The sidecar's, whatever user it names: it applies only while these bytes hold the token it names, which + // the core checks (`SessionCore.savedExpiry(in:)`). + deadToken: sidecar?.deadToken ) } @@ -311,10 +314,46 @@ extension SessionRecordStore { } (username, userId) = Self.isUserKind(record.kind) ? (record.username, record.userId) : (nil, nil) } - guard previous != nil || label != nil || username != nil || userId != nil else { + // Only what `record` itself says: a write of other credentials (a refresh, a new login) or a sign-out ends it. + let deadToken = record.credentials == nil ? nil : record.deadToken + guard previous != nil || label != nil || username != nil || userId != nil || deadToken != nil else { return nil } - return DefaultSessionMeta(lastWriteTimestamp: now(), label: label, username: username, userId: userId) + return DefaultSessionMeta(lastWriteTimestamp: now(), label: label, username: username, userId: userId, deadToken: deadToken) + } + + // MARK: Dead token + + /// `saveDeadToken(_:for:expecting:)` for `.default`: the sidecar only, guarded on its bytes, while the shared + /// record still holds the bytes `expected` names. The shared record is never rewritten for it. A lost sidecar race + /// re-reads and tries once more; a newer schema's sidecar is never overwritten (`.discarded`). + /// + /// - Throws: `AuthClientError.storageUnavailable` if storage could not be read or written. + func saveDefaultDeadToken(_ deadToken: DeadToken, expecting expected: RecordVersion?) throws -> CommitOutcome { + guard let bytes = try fetchSharedRecord(operation: "read the default session's saved login"), + expected == .storedBytes(bytes) else { + return .discarded + } + for _ in 1 ... 2 { + let stored = try readSidecar() + if case .unsupportedSchema = stored { + return .discarded + } + guard var record = defaultRecord(holding: bytes, sidecar: stored.meta), record.credentials != nil else { + return .discarded + } + record.deadToken = deadToken + guard let wanted = sidecar(after: record, replacing: stored.meta) else { + return .discarded + } + let committed = try perform("write the default session's sidecar") { + try keychain.setIfUnchanged(wanted.encoded(), key: sidecarAccount, expecting: stored.stored) + } + if committed { + return .committed(VersionedSessionRecord(record: defaultRecord(holding: bytes, sidecar: wanted) ?? record, version: expected)) + } + } + return .discarded } // MARK: Label @@ -356,7 +395,13 @@ extension SessionRecordStore { } else { (username, userId) = (stored.meta?.username, stored.meta?.userId) } - let next = DefaultSessionMeta(lastWriteTimestamp: now(), label: label, username: username, userId: userId) + let next = DefaultSessionMeta( + lastWriteTimestamp: now(), + label: label, + username: username, + userId: userId, + deadToken: stored.meta?.deadToken + ) if stored.meta.map({ Self.describesTheSame(next, $0) }) ?? (label == nil && username == nil && userId == nil) { return try .written(readDefault()) } @@ -546,6 +591,6 @@ extension SessionRecordStore { guard let rhs else { return false } - return lhs.label == rhs.label && lhs.username == rhs.username && lhs.userId == rhs.userId + return lhs.label == rhs.label && lhs.username == rhs.username && lhs.userId == rhs.userId && lhs.deadToken == rhs.deadToken } } diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore.swift index 0e686b1923..66f5e1fce1 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Storage/SessionRecordStore.swift @@ -277,6 +277,24 @@ struct SessionRecordStore: Sendable { return committed ? .committed(VersionedSessionRecord(envelope)) : .discarded } + /// Saves `deadToken` with the session's record (`SessionRecord.deadToken`), while it still is the record the caller + /// read (`expected`) and holds credentials; otherwise `.discarded`, and nothing is written. The credentials are kept + /// as they are. A named session's envelope is written one generation on, through the commit guard; `.default`'s + /// marker goes in its sidecar, and the Auth plugin's record is left alone (`saveDefaultDeadToken`). + /// + /// - Throws: `AuthClientError.storageUnavailable` if storage could not be read or written. + func saveDeadToken(_ deadToken: DeadToken, for sessionId: SessionID, expecting expected: RecordVersion?) throws -> CommitOutcome { + if sessionId == .default { + return try saveDefaultDeadToken(deadToken, expecting: expected) + } + guard case .record(let stored) = try read(sessionId), stored.version == expected, stored.record.credentials != nil else { + return .discarded + } + var record = stored.record + record.deadToken = deadToken + return try write(record, for: sessionId, expecting: expected, recordingMarker: false) + } + // MARK: Sign-out and purge /// What a sign-out did. diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Support/ClientLog.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Support/ClientLog.swift index 4d71f67a3f..351dca891b 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Support/ClientLog.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Support/ClientLog.swift @@ -16,7 +16,8 @@ import AmplifyFoundation /// appears in a category: a session ID can be derived from a user identifier, such as a username or `sub`. enum ClientLog { - /// The session record store: listing, copy-forward and the interrupted sign-in's record. + /// The session record store: listing, copy-forward, the interrupted sign-in's record, and a session's expiry that + /// could not be saved (`SessionCore+Refresh.swift`). static let sessionRecordStore = "SessionRecordStore" /// The sign-out of a session. diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/CredentialProviderTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/CredentialProviderTests.swift index f698e8227a..ee8214a9cc 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/CredentialProviderTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/CredentialProviderTests.swift @@ -769,46 +769,47 @@ final class CredentialProviderTests: XCTestCase { XCTAssertEqual(second?.isSessionExpired, true, "\(String(describing: second))") } - /// The reuse proof outlives the client that made it, but the expiry does not (it is the core's): a new client of the - /// session restores the dead token unexpired and refreshes it again. If Cognito now refuses it as invalid, that - /// refusal answers, not the earlier proof. + /// A refusal of the refresh token as invalid ends a count of reuses in a row. The reuse memory is the record's, for + /// the process, and outlives the client; the expiry is saved with the record, but when that save fails, a new client + /// refreshes the token again, and a reuse it is told of then is a first one. /// - /// - Given: a session needing a refresh, whose refreshes are refused as reused twice, `RefreshTokenReuse.minimumGap` - /// apart, which proves its token dead; the client is then released - /// - When: a new client of the session, whose refresh is refused as `refreshTokenInvalid`, requests credentials, - /// then again + /// - Given: a session needing a refresh, refused once as reused, then as `refreshTokenInvalid` while its record + /// cannot be written, so the expiry is not saved; the client is then released + /// - When: a new client, `RefreshTokenReuse.minimumGap` after the reuse, is refused as reused /// - Then: - /// - the first client's second call says the token was already used - /// - the new client's calls throw `sessionExpired` saying the refresh token has expired or been revoked, with - /// `refreshTokenInvalid` underneath, after one refresh - func testAnInvalidRefreshTokenAfterAReuseProofOfAReleasedClientAnswersWithItsOwnText() async throws { + /// - the refusal as invalid says the refresh token has expired or been revoked, with `refreshTokenInvalid` + /// underneath + /// - the new client's refusal is the retryable `unknown` of a first reuse, not "already used" + func testARefusalAsInvalidEndsTheReuseCount() async throws { let stale = FakePayload.signedIn("alice", stale: true) try harness.signIn(work, stale) var client: AmplifyCognitoClient? = try harness.client(work) - harness.engine(for: work)?.scriptRefresh { _ in throw SessionEngineError.refreshTokenReused } - _ = await credentialsError { try await XCTUnwrap(client).credentialsProvider.resolve() } - harness.advanceClock(by: RefreshTokenReuse.minimumGap) - let proof = await credentialsError { try await XCTUnwrap(client).credentialsProvider.resolve() } - assertRefreshTokenAlreadyUsed(proof?.underlyingError as? AuthClientError, "the proof") + let engine = try XCTUnwrap(harness.engine(for: work)) + engine.scriptRefresh { _ in throw SessionEngineError.refreshTokenReused } + let reuse = await credentialsError { try await XCTUnwrap(client).credentialsProvider.resolve() } + XCTAssertEqual(reuse?.isUnknown, true, "\(String(describing: reuse))") + engine.scriptRefresh { _ in throw SessionEngineError.refreshTokenInvalid } + harness.keychain.failingWrites(of: harness.store().sessionAccount(for: work), with: errSecInteractionNotAllowed) + let invalid = await credentialsError { try await XCTUnwrap(client).credentialsProvider.resolve() } + harness.keychain.clearFailures() client = nil await harness.waitForBaseline() + XCTAssertNil(try harness.storedRecord(work)?.deadToken) + harness.advanceClock(by: RefreshTokenReuse.minimumGap) let next = try harness.client(work) - let engine = try XCTUnwrap(harness.engine(for: work)) - engine.scriptRefresh { _ in throw SessionEngineError.refreshTokenInvalid } - let first = await credentialsError { try await next.credentialsProvider.resolve() } - let second = await credentialsError { try await next.credentialsProvider.resolve() } - - for (name, error) in [("the first", first), ("the second", second)] { - XCTAssertEqual(error?.isSessionExpired, true, "\(name): \(String(describing: error))") - let underlying = error?.underlyingError as? AuthClientError - XCTAssertEqual(underlying?.errorDescription, "Session \"work\"'s refresh token has expired or been revoked.", name) - guard case SessionEngineError.refreshTokenInvalid? = underlying?.underlyingError as? SessionEngineError else { - XCTFail("\(name): expected refreshTokenInvalid underneath, got \(String(describing: underlying?.underlyingError))") - continue - } + let nextEngine = try XCTUnwrap(harness.engine(for: work)) + nextEngine.scriptRefresh { _ in throw SessionEngineError.refreshTokenReused } + let afterwards = await credentialsError { try await next.credentialsProvider.resolve() } + + XCTAssertEqual(invalid?.isSessionExpired, true, "\(String(describing: invalid))") + let underlying = invalid?.underlyingError as? AuthClientError + XCTAssertEqual(underlying?.errorDescription, "Session \"work\"'s refresh token has expired or been revoked.") + guard case SessionEngineError.refreshTokenInvalid? = underlying?.underlyingError as? SessionEngineError else { + return XCTFail("expected refreshTokenInvalid underneath, got \(String(describing: underlying?.underlyingError))") } - XCTAssertEqual(engine.refreshCalls.count, 1) + XCTAssertEqual(afterwards?.isUnknown, true, "\(String(describing: afterwards))") + XCTAssertEqual(nextEngine.refreshCalls.count, 1) } /// The dead-token proof installs the record it proved dead: another writer that saved the record after this client diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/SavedExpiryTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/SavedExpiryTests.swift new file mode 100644 index 0000000000..fccc227770 --- /dev/null +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/SavedExpiryTests.swift @@ -0,0 +1,459 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@_spi(AmplifyExperimental) import AmplifyFoundation +import Foundation +import Security +import XCTest +@_spi(AmplifyExperimental) @testable import AmplifyCognitoClient + +/// A dead refresh token's expiry is saved with the session's record (`SessionRecord.deadToken`): in a named session's +/// envelope, and in `.default`'s sidecar beside the Auth plugin's record. A new session core, once every handle and +/// provider of the session was released or after a relaunch, starts expired while the record still holds that token, +/// so it never refreshes the dead token again. Any new login or federation ends it, whoever saved it. +final class SavedExpiryTests: XCTestCase { + + private var harness: ClientHarness! + /// Other harnesses over `harness`'s keychain (a relaunch, or a second process), waited on at tear-down. + private var others: [ClientHarness] = [] + private let work = ClientFixtures.id("work") + private let alice = AuthClientUser(username: "alice", userId: "sub-alice") + private var pluginAccount: String { SessionRecordKey.pluginSessionAccount(in: StorageFixtures.pools) } + private var sidecarAccount: String { SessionRecordKey.metaAccount(in: StorageFixtures.pools) } + + override func setUp() { + harness = ClientHarness() + others = [] + } + + override func tearDown() async throws { + harness.keychain.clearFailures() + await harness.waitForBaseline() + for other in others { + await other.waitForBaseline() + } + others = [] + harness = nil + } + + /// A new harness over the same keychain: a relaunch of the app, or another process sharing its access group. + private func relaunch() -> ClientHarness { + let relaunched = ClientHarness(keychain: harness.keychain) + others.append(relaunched) + return relaunched + } + + // MARK: Released, and relaunched + + /// - Given: a session whose refresh token two reuse refusals, `RefreshTokenReuse.minimumGap` apart, proved dead; + /// its client then released + /// - When: a new client of the session asks both providers, and fetches the session, unforced and forced + /// - Then: + /// - every answer is the proof's `sessionExpired` ("already used", `refreshTokenReused` underneath) + /// - the new client makes no refresh, and sends no `.sessionExpired` of its own + /// - the record keeps its credentials, and the session stays signed in as the user + func testAReuseExpirySurvivesReleasingTheClient() async throws { + let stale = FakePayload.signedIn("alice", stale: true) + try harness.signIn(work, stale) + try await expireByReuse(work, in: harness) + + let next = try harness.client(work) + let engine = try XCTUnwrap(harness.engine(for: work)) + let events = StreamRecorder(next.listenToAuthEvents()) + let aws = await credentialsError { try await next.credentialsProvider.resolve() } + let token = await credentialsError { try await next.userPoolTokenProvider.accessToken() } + let unforced = try await next.fetchAuthSession() + let forced = try await next.fetchAuthSession(options: .init(forceRefresh: true)) + + for (name, error) in [("the AWS credentials provider", aws), ("the token provider", token)] { + XCTAssertEqual(error?.isSessionExpired, true, "\(name): \(String(describing: error))") + assertAlreadyUsed(error?.underlyingError as? AuthClientError, work, name) + } + assertEveryField(of: unforced, isAlreadyUsed: work, "the unforced fetch") + assertEveryField(of: forced, isAlreadyUsed: work, "the forced fetch") + XCTAssertEqual(engine.refreshCalls, []) + XCTAssertEqual(try harness.storedRecord(work)?.credentials, stale.data) + let state = await next.currentSessionState() + XCTAssertEqual(state, .signedIn(alice)) + // Ends with a known event, so a `.sessionExpired` still in the recorder's buffer is counted too. + _ = await next.signOut() + await events.waitFor(1) + XCTAssertEqual(events.received, [.signedOut]) + } + + /// - Given: a named session's refresh token proved dead by two reuse refusals + /// - When: the app relaunches (a new harness over the same keychain) and asks for credentials + /// - Then: + /// - the answer is the proof's `sessionExpired` ("already used"), with no refresh + /// - the envelope holds the marker: the dead refresh token's digest and its cause + func testAReuseExpirySurvivesARelaunch() async throws { + let stale = FakePayload.signedIn("alice", stale: true) + try harness.signIn(work, stale) + try await expireByReuse(work, in: harness) + + let relaunched = relaunch() + let client = try relaunched.client(work) + let engine = try XCTUnwrap(relaunched.engine(for: work)) + let error = await credentialsError { try await client.credentialsProvider.resolve() } + + XCTAssertEqual(error?.isSessionExpired, true, "\(String(describing: error))") + assertAlreadyUsed(error?.underlyingError as? AuthClientError, work, "after the relaunch") + XCTAssertEqual(engine.refreshCalls, []) + let saved = try XCTUnwrap(try relaunched.storedRecord(work)?.deadToken) + XCTAssertEqual(saved.cause, .reused) + XCTAssertEqual(saved.token, DeadToken.digest(refreshToken: stale.userPoolTokens?.refreshToken, credentials: stale.data)) + } + + /// The same for `.default`, whose marker is in the sidecar: the Auth plugin's record is never rewritten for it. + /// + /// - Given: `.default` holding the plugin's record for alice, whose refresh token two reuse refusals proved dead + /// - When: the app relaunches and asks for credentials + /// - Then: + /// - the answer is the proof's `sessionExpired` ("already used"), with no refresh + /// - the plugin's record holds the same bytes, and the sidecar holds the marker + func testADefaultSessionsExpirySurvivesARelaunchInItsSidecar() async throws { + let stale = FakePayload.signedIn("alice", stale: true) + harness.keychain.put(stale.data, pluginAccount) + try await expireByReuse(.default, in: harness) + + let relaunched = relaunch() + let client = try relaunched.client(.default) + let engine = try XCTUnwrap(relaunched.engine(for: .default)) + let error = await credentialsError { try await client.credentialsProvider.resolve() } + + XCTAssertEqual(error?.isSessionExpired, true, "\(String(describing: error))") + assertAlreadyUsed(error?.underlyingError as? AuthClientError, .default, "after the relaunch") + XCTAssertEqual(engine.refreshCalls, []) + XCTAssertEqual(harness.keychain.value(pluginAccount), stale.data) + let sidecar = try XCTUnwrap(harness.keychain.value(sidecarAccount).map(DefaultSessionMeta.decode)) + guard case .meta(let meta) = sidecar else { + return XCTFail("the sidecar is not readable: \(sidecar)") + } + XCTAssertEqual(meta.deadToken?.cause, .reused) + XCTAssertEqual(meta.userId, "sub-alice") + } + + /// A refused refresh token's expiry is saved too, with its own text. + /// + /// - Given: a session whose refresh is refused as `refreshTokenInvalid` + /// - When: the app relaunches and asks for credentials + /// - Then: the answer is `sessionExpired` saying the refresh token has expired or been revoked, with + /// `refreshTokenInvalid` underneath, and no refresh + func testAnInvalidRefreshTokensExpirySurvivesARelaunch() async throws { + try harness.signIn(work, .signedIn("alice", stale: true)) + var client: AmplifyCognitoClient? = try harness.client(work) + harness.engine(for: work)?.scriptRefresh { _ in throw SessionEngineError.refreshTokenInvalid } + _ = await credentialsError { try await XCTUnwrap(client).credentialsProvider.resolve() } + client = nil + await harness.waitForBaseline() + + let relaunched = relaunch() + let next = try relaunched.client(work) + let engine = try XCTUnwrap(relaunched.engine(for: work)) + let error = await credentialsError { try await next.credentialsProvider.resolve() } + + XCTAssertEqual(error?.isSessionExpired, true, "\(String(describing: error))") + let underlying = error?.underlyingError as? AuthClientError + XCTAssertEqual(underlying?.errorDescription, "Session \"work\"'s refresh token has expired or been revoked.") + guard case SessionEngineError.refreshTokenInvalid? = underlying?.underlyingError as? SessionEngineError else { + return XCTFail("expected refreshTokenInvalid underneath, got \(String(describing: underlying?.underlyingError))") + } + XCTAssertEqual(engine.refreshCalls, []) + } + + /// Another process's proof reaches a core already in memory: its next refresh re-reads first, and does not spend + /// the dead token again. + /// + /// - Given: two processes over one keychain, each with a client of the session restored; the first proves the + /// refresh token dead with two reuse refusals + /// - When: the second asks for credentials, which need a refresh + /// - Then: it answers the proof's `sessionExpired` ("already used") with no refresh of its own, and the session + /// stays signed in as the user + func testAnotherProcesssSavedExpiryStopsTheNextRefresh() async throws { + try harness.signIn(work, .signedIn("alice", stale: true)) + let other = relaunch() + let otherClient = try other.client(work) + _ = await otherClient.currentSessionState() + let otherEngine = try XCTUnwrap(other.engine(for: work)) + try await expireByReuse(work, in: harness) + + let error = await credentialsError { try await otherClient.credentialsProvider.resolve() } + + XCTAssertEqual(error?.isSessionExpired, true, "\(String(describing: error))") + assertAlreadyUsed(error?.underlyingError as? AuthClientError, work, "the other process") + XCTAssertEqual(otherEngine.refreshCalls, []) + let state = await otherClient.currentSessionState() + XCTAssertEqual(state, .signedIn(alice)) + } + + // MARK: What ends it + + /// - Given: a session restored expired from its saved marker, after its client was released + /// - When: the user signs in again, and the app then relaunches + /// - Then: + /// - the sign-in is not refused: an expired session signs in again + /// - after the relaunch the session vends the new login's credentials, with no refresh: the sign-in's record + /// carries no marker + func testASignInEndsTheSavedExpiry() async throws { + try harness.signIn(work, .signedIn("alice", stale: true)) + try await expireByReuse(work, in: harness) + var client: AmplifyCognitoClient? = try harness.client(work) + _ = await credentialsError { try await XCTUnwrap(client).credentialsProvider.resolve() } + + _ = try await XCTUnwrap(client).signInForTest() + client = nil + await harness.waitForBaseline() + let relaunched = relaunch() + let next = try relaunched.client(work) + let credentials = try await next.credentialsProvider.resolve() + + XCTAssertEqual(credentials as? CognitoAWSCredentials, FakePayload.signedIn("alice").awsCredentials) + XCTAssertEqual(relaunched.engine(for: work)?.refreshCalls, []) + XCTAssertNil(try relaunched.storedRecord(work)?.deadToken) + } + + /// - Given: a named session's saved expiry, then another process saving a new login of the same user, with another + /// refresh token, that needs no refresh + /// - When: a new client of the session asks for credentials + /// - Then: they are the new login's, with no refresh, and the session is signed in as the user + func testANewLoginAnotherProcessSavedEndsTheSavedExpiry() async throws { + try harness.signIn(work, .signedIn("alice", stale: true)) + try await expireByReuse(work, in: harness) + let newLogin = FakePayload.signedIn("alice", version: 2) + let otherStore = relaunch().store() + guard case .record(let stored) = try otherStore.read(work) else { + return XCTFail("the expired record was not kept") + } + try otherStore.write(newLogin.record(), for: work, expecting: stored.version) + + let next = try harness.client(work) + let credentials = try await next.credentialsProvider.resolve() + + XCTAssertEqual(credentials as? CognitoAWSCredentials, newLogin.awsCredentials) + XCTAssertEqual(harness.engine(for: work)?.refreshCalls, []) + let state = await next.currentSessionState() + XCTAssertEqual(state, .signedIn(alice)) + } + + /// The marker names the dead token, not the record: the Auth plugin saving a new login over `.default` leaves the + /// sidecar as it was, and its marker no longer applies. + /// + /// - Given: `.default`'s saved expiry, then the plugin saving a new login of the same user, with another refresh + /// token, over its record alone + /// - When: the app relaunches and asks for credentials + /// - Then: + /// - the sidecar still holds the marker + /// - the credentials are the new login's, with no refresh, and the session is signed in as the user + func testANewLoginThePluginSavedEndsTheSavedExpiryLeftInTheSidecar() async throws { + harness.keychain.put(FakePayload.signedIn("alice", stale: true).data, pluginAccount) + try await expireByReuse(.default, in: harness) + let newLogin = FakePayload.signedIn("alice", version: 2) + harness.keychain.put(newLogin.data, pluginAccount) + + let relaunched = relaunch() + let client = try relaunched.client(.default) + let credentials = try await client.credentialsProvider.resolve() + + guard case .meta(let meta)? = harness.keychain.value(sidecarAccount).map(DefaultSessionMeta.decode) else { + return XCTFail("the sidecar is not readable") + } + XCTAssertEqual(meta.deadToken?.cause, .reused, "the plugin never writes the sidecar") + XCTAssertEqual(credentials as? CognitoAWSCredentials, newLogin.awsCredentials) + XCTAssertEqual(relaunched.engine(for: .default)?.refreshCalls, []) + let state = await client.currentSessionState() + XCTAssertEqual(state, .signedIn(alice)) + } + + /// A federated session's refused provider token is saved too (by its credentials, which hold no refresh token), + /// and a new federation ends it. + /// + /// - Given: a federated session whose refresh is refused as `refreshTokenInvalid` (the identity pool no longer + /// accepts its provider token), its client then released + /// - When: a new client asks for credentials, federates again, and asks again; then the app relaunches and asks + /// - Then: + /// - the first answer is `sessionExpired` saying the provider token is no longer accepted, with no refresh + /// - the federation commits, and both later answers are the new federation's credentials, with no refresh + func testANewFederationEndsAFederatedSessionsSavedExpiry() async throws { + try harness.signIn(work, .federated(stale: true)) + var client: AmplifyCognitoClient? = try harness.client(work) + harness.engine(for: work)?.scriptRefresh { _ in throw SessionEngineError.refreshTokenInvalid } + _ = await credentialsError { try await XCTUnwrap(client).credentialsProvider.resolve() } + client = nil + await harness.waitForBaseline() + + let next = try harness.client(work) + let engine = try XCTUnwrap(harness.engine(for: work)) + let expired = await credentialsError { try await next.credentialsProvider.resolve() } + let federated = FakePayload.federated(version: 2) + engine.scriptAccountOperation(.federateToIdentityPool) { _ in federated.data } + _ = try await next.federateToIdentityPool(withProviderToken: "new-token", for: .google) + let after = try await next.credentialsProvider.resolve() + let relaunched = relaunch() + let relaunchedClient = try relaunched.client(work) + let afterRelaunch = try await relaunchedClient.credentialsProvider.resolve() + + XCTAssertEqual(expired?.isSessionExpired, true, "\(String(describing: expired))") + XCTAssertEqual( + (expired?.underlyingError as? AuthClientError)?.errorDescription, + "Session \"work\"'s provider token is no longer accepted by the identity pool." + ) + XCTAssertEqual(after as? CognitoAWSCredentials, federated.awsCredentials) + XCTAssertEqual(afterRelaunch as? CognitoAWSCredentials, federated.awsCredentials) + XCTAssertEqual(engine.refreshCalls, []) + XCTAssertEqual(relaunched.engine(for: work)?.refreshCalls, []) + } + + /// The save is best effort: when it fails, the expiry stays the core's, and a later core refreshes once more. + /// + /// - Given: a session whose refresh token two reuse refusals prove dead while its record cannot be written + /// - When: the client is released, and a new client asks for credentials after `RefreshTokenReuse.minimumGap` + /// - Then: + /// - the proof still answers "already used", and the record holds no marker + /// - the new client refreshes again (the retryable `unknown` of a first reuse) + func testAnExpiryThatCouldNotBeSavedStaysTheCoresOnly() async throws { + try harness.signIn(work, .signedIn("alice", stale: true)) + harness.keychain.failingWrites(of: harness.store().sessionAccount(for: work), with: errSecInteractionNotAllowed) + try await expireByReuse(work, in: harness) + harness.keychain.clearFailures() + XCTAssertNil(try harness.storedRecord(work)?.deadToken) + + harness.advanceClock(by: RefreshTokenReuse.minimumGap) + let next = try harness.client(work) + harness.engine(for: work)?.scriptRefresh { _ in throw SessionEngineError.refreshTokenReused } + let error = await credentialsError { try await next.credentialsProvider.resolve() } + + XCTAssertEqual(error?.isUnknown, true, "\(String(describing: error))") + XCTAssertEqual(harness.engine(for: work)?.refreshCalls.count, 1) + } + + // MARK: Stored format + + /// - Given: a named session's record with a marker, and one without + /// - When: each is encoded, and decoded back + /// - Then: + /// - the marker is the envelope's `deadToken` key, `{"cause":"reused","token":""}`, and round-trips + /// - a record without one has no `deadToken` key + func testTheEnvelopeStoresTheMarkerOnlyWhenSet() throws { + let deadToken = DeadToken(token: DeadToken.digest(refreshToken: "refresh", credentials: Data()), cause: .reused) + var record = FakePayload.signedIn("alice").record() + let without = try SessionRecordEnvelope(generation: 1, lastWriteTimestamp: TestClock.start, record: record).encoded() + record.deadToken = deadToken + let with = try SessionRecordEnvelope(generation: 1, lastWriteTimestamp: TestClock.start, record: record).encoded() + + let json = try XCTUnwrap(JSONSerialization.jsonObject(with: with) as? [String: Any]) + XCTAssertEqual(json["deadToken"] as? [String: String], ["cause": "reused", "token": deadToken.token]) + XCTAssertFalse(String(decoding: without, as: UTF8.self).contains("deadToken")) + guard case .envelope(let decoded) = SessionRecordEnvelope.decode(with) else { + return XCTFail("the envelope did not decode") + } + XCTAssertEqual(decoded.record.deadToken, deadToken) + XCTAssertEqual(deadToken.token.count, 64) + } + + /// A marker this build cannot read (a newer build's cause) is ignored: the record is still read, never taken for a + /// corrupt one, and the session refreshes as if nothing were saved. + /// + /// - Given: an envelope and a sidecar whose `deadToken` has a cause this build does not know + /// - When: each is decoded + /// - Then: both decode, with no marker, and the rest of each as stored + func testAnUnreadableMarkerIsIgnored() throws { + let record = FakePayload.signedIn("alice").record() + let envelope = try SessionRecordEnvelope(generation: 3, lastWriteTimestamp: TestClock.start, record: record).encoded() + var envelopeJSON = try XCTUnwrap(JSONSerialization.jsonObject(with: envelope) as? [String: Any]) + envelopeJSON["deadToken"] = ["cause": "future", "token": "abc"] + let sidecar = try DefaultSessionMeta(lastWriteTimestamp: TestClock.start, label: "Work", username: "alice", userId: "sub-alice").encoded() + var sidecarJSON = try XCTUnwrap(JSONSerialization.jsonObject(with: sidecar) as? [String: Any]) + sidecarJSON["deadToken"] = ["cause": "future", "token": "abc"] + + guard case .envelope(let decoded) = try SessionRecordEnvelope.decode(JSONSerialization.data(withJSONObject: envelopeJSON)) else { + return XCTFail("an unreadable marker made the envelope unreadable") + } + XCTAssertNil(decoded.record.deadToken) + XCTAssertEqual(decoded.record.credentials, record.credentials) + XCTAssertEqual(decoded.generation, 3) + guard case .meta(let meta) = try DefaultSessionMeta.decode(JSONSerialization.data(withJSONObject: sidecarJSON)) else { + return XCTFail("an unreadable marker made the sidecar unreadable") + } + XCTAssertNil(meta.deadToken) + XCTAssertEqual(meta.label, "Work") + } + + // MARK: Helpers + + /// Proves `sessionId`'s refresh token dead in `harness` with two reuse refusals, `RefreshTokenReuse.minimumGap` + /// apart, then releases the client and waits for its core to go. + private func expireByReuse(_ sessionId: SessionID, in harness: ClientHarness, file: StaticString = #filePath, line: UInt = #line) async throws { + var client: AmplifyCognitoClient? = try harness.client(sessionId) + harness.engine(for: sessionId)?.scriptRefresh { _ in throw SessionEngineError.refreshTokenReused } + let first = await credentialsError { try await XCTUnwrap(client).credentialsProvider.resolve() } + XCTAssertEqual(first?.isUnknown, true, "the first reuse: \(String(describing: first))", file: file, line: line) + harness.advanceClock(by: RefreshTokenReuse.minimumGap) + let proof = await credentialsError { try await XCTUnwrap(client).credentialsProvider.resolve() } + assertAlreadyUsed(proof?.underlyingError as? AuthClientError, sessionId, "the proof", file: file, line: line) + client = nil + await harness.waitForBaseline(file: file, line: line) + } + + private func credentialsError(_ body: () async throws -> some Any, file: StaticString = #filePath, line: UInt = #line) async -> CredentialsError? { + do { + _ = try await body() + XCTFail("expected an error", file: file, line: line) + return nil + } catch let error as CredentialsError { + return error + } catch { + XCTFail("expected a CredentialsError, got \(error)", file: file, line: line) + return nil + } + } + + /// Asserts `error` is the dead token after a reuse for `sessionId`. + private func assertAlreadyUsed( + _ error: AuthClientError?, + _ sessionId: SessionID, + _ name: String, + file: StaticString = #filePath, + line: UInt = #line + ) { + guard let error else { + return XCTFail("\(name): no error", file: file, line: line) + } + XCTAssertEqual(error.kind, .sessionExpired, "\(name): \(error)", file: file, line: line) + XCTAssertEqual( + error.errorDescription, + "Session \"\(sessionId)\"'s refresh token was already used, and no refreshed tokens were saved, so the session cannot be renewed.", + name, + file: file, + line: line + ) + guard case SessionEngineError.refreshTokenReused? = error.underlyingError as? SessionEngineError else { + return XCTFail("\(name): expected refreshTokenReused underneath, got \(String(describing: error.underlyingError))", file: file, line: line) + } + } + + private func assertEveryField( + of session: AuthClientSession, + isAlreadyUsed sessionId: SessionID, + _ name: String, + file: StaticString = #filePath, + line: UInt = #line + ) { + let fields: [(String, Result)] = [ + ("userPoolTokens", session.userPoolTokensResult.map { _ in () }), + ("userSub", session.userSubResult.map { _ in () }), + ("identityId", session.identityIdResult.map { _ in () }), + ("awsCredentials", session.awsCredentialsResult.map { _ in () }) + ] + for (field, result) in fields { + guard case .failure(let error) = result else { + XCTFail("\(name): \(field) should fail", file: file, line: line) + continue + } + assertAlreadyUsed(error, sessionId, "\(name), \(field)", file: file, line: line) + } + } +} diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/Support/ClientTestSupport.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/Support/ClientTestSupport.swift index 045187bc5b..a870f14015 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/Support/ClientTestSupport.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/Support/ClientTestSupport.swift @@ -14,7 +14,7 @@ import XCTest /// session, so no test touches process-wide state or the real keychain. final class ClientHarness: @unchecked Sendable { - let keychain = TestKeychain() + let keychain: TestKeychain let registry: SessionCoreDependencies.Registry let gates = SessionRecordGates() let revoker = FakeRevoker() @@ -45,11 +45,15 @@ final class ClientHarness: @unchecked Sendable { /// - restoreBound: Generous by default, so a slow machine never trips it by accident. /// - restoresOnConstruction: Whether construction schedules a warm restore, as `live` does. Off by /// default, so a test controls exactly when storage is read. + /// - keychain: Another harness's, to model a relaunch, or a second process sharing the access group: a new + /// registry, gate table and record memory over the same saved records. init( registry: SessionCoreDependencies.Registry = .init(), restoreBound: UInt64 = 60_000_000_000, - restoresOnConstruction: Bool = false + restoresOnConstruction: Bool = false, + keychain: TestKeychain = TestKeychain() ) { + self.keychain = keychain self.registry = registry self.restoreBound = restoreBound self.restoresOnConstruction = restoresOnConstruction diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignOutTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignOutTests.swift index c77d5cbe56..71bc7e1a6f 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignOutTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/WebUISignOutTests.swift @@ -988,6 +988,47 @@ final class WebUISignOutTests: XCTestCase { XCTAssertEqual(again, .complete) } + /// The #3956 exception holds after a relaunch: the expiry is saved with the record. + /// + /// - Given: a shared-cookie session whose refresh token a refresh proved dead, then a relaunch (a new harness over + /// the same keychain) + /// - When: the user closes the relaunched session's logout page + /// - Then: + /// - the sign-out reruns with the hosted UI skipped and signs out: `.partial`, with the expired session's + /// `.userCancelled` as `hostedUIError` + /// - nothing was refreshed after the relaunch + func testClosingTheLogoutPageOfASessionExpiredBeforeARelaunchStillSignsOut() async throws { + var payload = HostedUIFixtures.hostedUIPayload() + payload.stale = true + try harness.signIn(work, payload) + var expiring: AmplifyCognitoClient? = try client(work) + harness.engine(for: work)?.scriptRefresh { _ in throw SessionEngineError.refreshTokenInvalid } + _ = try await XCTUnwrap(expiring).fetchAuthSession() + expiring = nil + await harness.waitForBaseline() + + let relaunched = ClientHarness(keychain: harness.keychain) + var client: AmplifyCognitoClient? = try relaunched.client(work, configuration: HostedUIFixtures.configuration) + let engine = try XCTUnwrap(relaunched.engine(for: work)) + engine.scriptHostedUIRevoke { _, _, plan in + if case .present = plan { + throw AuthClientError.userCancelled("closed", "Sign out again.") + } + return .complete + } + let result = try await signOut(XCTUnwrap(client)) + client = nil + + XCTAssertEqual(plans(engine), ["present", "skip"]) + XCTAssertEqual(hostedUIError(result)?.kind, .userCancelled) + XCTAssertEqual(hostedUIError(result)?.errorDescription, SessionCore.expiredLogoutClosedDescription) + XCTAssertEqual(try relaunched.storedRecord(work)?.isSignedOut, true) + XCTAssertEqual(engine.refreshCalls, []) + let holder = await relaunched.sheetLock.currentHolder + XCTAssertNil(holder, "the relaunched session left the sheet held") + await relaunched.waitForBaseline() + } + /// The #3956 exception covers only the login proved dead /// /// - Given: a shared-cookie session whose refresh token a refresh proved dead, then a new login with another refresh From 5745c4d1340009844a9c83c310b4855b2f6abd7e Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 13:14:17 -0400 Subject: [PATCH 36/57] ci(auth): extend the code sink's API key from a weekly ccit-ci- Lambda Phase 2 now makes ccit-ci-extend-api-key, its role ccit-ci-key-exec (its logs, and appsync:ListApiKeys and appsync:UpdateApiKey on the code sink API's ARN only) and the weekly rule ccit-ci-extend-api-key. The function extends the tests' key in place to 365 days from now, rounded down to the hour after a 600 s allowance, once 120 or fewer days are left, so the key no longer needs a manual extension. Teardown, lambda-drift and the frozen-rerun plan cover it. --- .../infra/ci/lambda/extend-api-key/index.mjs | 129 ++++++++++++++++++ .../extend-api-key/test_extend_api_key.mjs | 124 +++++++++++++++++ .../IntegrationTests/infra/ci/lambda_code.py | 1 + .../IntegrationTests/infra/ci/provision-ci.sh | 112 +++++++++------ .../infra/ci/test_lambda_code.sh | 29 ++-- .../infra/ci/test_provision_ci.sh | 80 ++++++++--- 6 files changed, 407 insertions(+), 68 deletions(-) create mode 100644 AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/index.mjs create mode 100644 AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/test_extend_api_key.mjs diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/index.mjs b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/index.mjs new file mode 100644 index 0000000000..69489046af --- /dev/null +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/index.mjs @@ -0,0 +1,129 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// Keeps the code sink's read-only API key (infra/ci/provision-ci.sh, the "ccit-ci tests" key of the ccit-ci-codes +// AppSync API) from expiring. Run once a week by an EventBridge rule. It picks the key a rerun of provision-ci.sh +// would pick, the one that expires last, and, once it has EXTEND_WITHIN_DAYS or fewer left, extends it in place to +// KEY_DAYS days from now (AppSync's maximum is 365), rounded down to the hour after CLOCK_ALLOWANCE_SECONDS, as +// provision-ci.sh does. The key keeps its id, so the uploaded files that name it stay valid. +// +// An expired key that AppSync has not deleted yet (it keeps one for 60 days) is extended the same way. When AppSync +// refuses that, or no key is found, the invocation fails, with what to run in the log: provision-ci.sh makes a new +// key, which this function cannot (its role may only list and update keys). +// +// Logs dates only, never the key, whose id is its secret. The AWS SDK is the one the Node.js runtime ships, loaded +// only by the handler, so the logic below can be tested with no SDK installed (test_extend_api_key.mjs). + +export const DESCRIPTION = "ccit-ci tests"; +const DAY_SECONDS = 24 * 60 * 60; +// How long AppSync keeps a key after it expires, when ListApiKeys gives no `deletes`. +const KEPT_AFTER_EXPIRY_SECONDS = 60 * DAY_SECONDS; + +/// The expiry, in seconds since the epoch, that an extension at `now` asks for. +export function extendedExpiry(now, { days, allowanceSeconds }) { + return Math.floor((now + days * DAY_SECONDS - allowanceSeconds) / 3600) * 3600; +} + +/// What to do with the tests' key, from ListApiKeys's keys and `now` (seconds since the epoch): `none` (no key to +/// extend), `keep`, `extend` or `extend-expired`, with the key's id and its expiry before and after. +export function plan(keys, now, limits) { + const candidates = keys.filter((key) => key.description === DESCRIPTION + && (key.deletes ?? key.expires + KEPT_AFTER_EXPIRY_SECONDS) > now); + if (candidates.length === 0) { + return { action: "none" }; + } + const key = candidates.reduce((last, next) => (next.expires > last.expires ? next : last)); + if (key.expires > now + limits.extendWithinDays * DAY_SECONDS) { + return { action: "keep", id: key.id, expires: key.expires }; + } + return { + action: key.expires > now ? "extend" : "extend-expired", + id: key.id, + from: key.expires, + expires: extendedExpiry(now, limits), + }; +} + +/// A date, as the log names it. +export function day(seconds) { + return new Date(seconds * 1000).toISOString().slice(0, 10); +} + +const RERUN = "Run infra/ci/provision-ci.sh --apply --scope all, which makes a new key, and replace the uploaded " + + "files that name the old one, as it says."; + +/// Lists the keys and extends the tests' key when it is due, on `appsync`, an object with listKeys() and +/// update(id, expires). Returns what was done; throws when nothing could keep the key alive. +export async function run(appsync, now, limits) { + const step = plan(await appsync.listKeys(), now, limits); + switch (step.action) { + case "none": + throw new Error(`No "${DESCRIPTION}" API key is left to extend. ${RERUN}`); + case "keep": + console.log(`The tests' API key expires on ${day(step.expires)}, more than ${limits.extendWithinDays} days ` + + "from now: kept."); + return { action: "kept", expires: day(step.expires) }; + default: + try { + await appsync.update(step.id, step.expires); + } catch (error) { + if (step.action === "extend-expired") { + throw new Error(`AppSync refused to extend the tests' API key, which expired on ${day(step.from)} ` + + `(${(error && error.name) || "error"}). ${RERUN}`); + } + throw error; + } + console.log(`Extended the tests' API key${step.action === "extend-expired" ? ", which had expired," : ""} ` + + `from ${day(step.from)} to ${day(step.expires)}.`); + return { action: step.action === "extend" ? "extended" : "extended-expired", expires: day(step.expires) }; + } +} + +/// A whole number from the environment, within [min, max]. +function whole(environment, name, fallback, min, max) { + const text = environment[name] ?? String(fallback); + const value = Number(text); + if (!/^[0-9]+$/.test(text) || value < min || value > max) { + throw new Error(`${name} must be a whole number from ${min} to ${max}`); + } + return value; +} + +/// The settings, from the function's environment. +export function settings(environment) { + if (!environment.API_ID) { + throw new Error("API_ID must be set"); + } + return { + apiId: environment.API_ID, + limits: { + days: whole(environment, "KEY_DAYS", 365, 2, 365), + allowanceSeconds: whole(environment, "CLOCK_ALLOWANCE_SECONDS", 600, 0, 3600), + extendWithinDays: whole(environment, "EXTEND_WITHIN_DAYS", 120, 1, 364), + }, + }; +} + +export const handler = async () => { + const { apiId, limits } = settings(process.env); + const { AppSyncClient, ListApiKeysCommand, UpdateApiKeyCommand } = await import("@aws-sdk/client-appsync"); + const client = new AppSyncClient({}); + const appsync = { + async listKeys() { + const keys = []; + let nextToken; + do { + const page = await client.send(new ListApiKeysCommand({ apiId, nextToken })); + keys.push(...(page.apiKeys || [])); + nextToken = page.nextToken; + } while (nextToken); + return keys; + }, + update: (id, expires) => client.send(new UpdateApiKeyCommand({ apiId, id, expires })), + }; + return run(appsync, Math.floor(Date.now() / 1000), limits); +}; diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/test_extend_api_key.mjs b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/test_extend_api_key.mjs new file mode 100644 index 0000000000..11942f9df4 --- /dev/null +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/test_extend_api_key.mjs @@ -0,0 +1,124 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// Tests the code sink key's extension over a fake AppSync, with no AWS SDK and no AWS call: +// +// node --test infra/ci/lambda/extend-api-key/test_extend_api_key.mjs + +import assert from "node:assert/strict"; +import test from "node:test"; +import { DESCRIPTION, day, extendedExpiry, plan, run, settings } from "./index.mjs"; + +const DAY = 24 * 60 * 60; +// One minute past an hour, so the rounding down shows. +const NOW = Date.parse("2026-10-05T12:01:00Z") / 1000; +const LIMITS = { days: 365, allowanceSeconds: 600, extendWithinDays: 120 }; +const EXTENDED = extendedExpiry(NOW, LIMITS); + +const key = (id, expires, extra = {}) => ({ id, description: DESCRIPTION, expires, ...extra }); + +// A fake AppSync over `keys`: update() sets a key's expiry, or throws `refusal` (an error name) when it is given. +function fakeAppSync(keys, refusal) { + const updates = []; + return { + updates, + listKeys: async () => structuredClone(keys), + update: async (id, expires) => { + updates.push({ id, expires }); + if (refusal) { + throw Object.assign(new Error("refused"), { name: refusal }); + } + }, + }; +} + +// Runs `body` with console.log captured; returns its result and the lines logged. +async function logged(body) { + const lines = []; + const original = console.log; + console.log = (line) => lines.push(line); + try { + return { result: await body(), lines }; + } finally { + console.log = original; + } +} + +test("the extension asks for 365 days from now, less the allowance, rounded down to the hour", () => { + assert.equal(EXTENDED, Date.parse("2027-10-05T11:00:00Z") / 1000); + assert.equal(EXTENDED % 3600, 0); + assert.ok(EXTENDED <= NOW + 365 * DAY - 600 && EXTENDED > NOW + 365 * DAY - 600 - 3600); +}); + +test("a key with more than 120 days left is kept; one with 120 or fewer is extended, at the edge too", () => { + assert.equal(plan([key("a", NOW + 120 * DAY + 1)], NOW, LIMITS).action, "keep"); + assert.deepEqual(plan([key("a", NOW + 120 * DAY)], NOW, LIMITS), + { action: "extend", id: "a", from: NOW + 120 * DAY, expires: EXTENDED }); + assert.equal(plan([key("a", NOW + 1)], NOW, LIMITS).action, "extend"); +}); + +test("an expired key AppSync still keeps is extended; one past its deletion, or another key, is not a candidate", () => { + assert.equal(plan([key("a", NOW)], NOW, LIMITS).action, "extend-expired"); + assert.equal(plan([key("a", NOW - 59 * DAY)], NOW, LIMITS).action, "extend-expired"); + assert.equal(plan([key("a", NOW - 61 * DAY)], NOW, LIMITS).action, "none"); + assert.equal(plan([key("a", NOW - DAY, { deletes: NOW - 1 })], NOW, LIMITS).action, "none"); + assert.equal(plan([key("a", NOW - DAY, { deletes: NOW + DAY })], NOW, LIMITS).action, "extend-expired"); + assert.equal(plan([{ id: "b", description: "another team's", expires: NOW + DAY }], NOW, LIMITS).action, "none"); + assert.equal(plan([], NOW, LIMITS).action, "none"); +}); + +test("of several tests' keys, the one that expires last is the one decided on, as provision-ci.sh picks it", () => { + const keys = [key("old", NOW - DAY), key("new", NOW + 30 * DAY), key("older", NOW - 2 * DAY)]; + assert.deepEqual(plan(keys, NOW, LIMITS), { action: "extend", id: "new", from: NOW + 30 * DAY, expires: EXTENDED }); + assert.equal(plan([key("old", NOW - DAY), key("new", NOW + 200 * DAY)], NOW, LIMITS).action, "keep"); +}); + +test("run extends a key that is due, in place, and logs the dates only", async () => { + const appsync = fakeAppSync([key("da2-secret", NOW + 10 * DAY)]); + const { result, lines } = await logged(() => run(appsync, NOW, LIMITS)); + assert.deepEqual(appsync.updates, [{ id: "da2-secret", expires: EXTENDED }]); + assert.deepEqual(result, { action: "extended", expires: "2027-10-05" }); + assert.deepEqual(lines, [`Extended the tests' API key from ${day(NOW + 10 * DAY)} to 2027-10-05.`]); + assert.ok(!lines.join("\n").includes("da2-secret")); +}); + +test("run keeps a key that is not due, with no update", async () => { + const appsync = fakeAppSync([key("da2-secret", NOW + 200 * DAY)]); + const { result, lines } = await logged(() => run(appsync, NOW, LIMITS)); + assert.deepEqual(appsync.updates, []); + assert.equal(result.action, "kept"); + assert.ok(lines[0].endsWith("more than 120 days from now: kept.") && !lines[0].includes("da2-secret")); +}); + +test("run extends an expired key when AppSync takes it, and fails with the rerun when AppSync refuses", async () => { + const taken = fakeAppSync([key("da2-secret", NOW - DAY)]); + const { result, lines } = await logged(() => run(taken, NOW, LIMITS)); + assert.deepEqual(taken.updates, [{ id: "da2-secret", expires: EXTENDED }]); + assert.equal(result.action, "extended-expired"); + assert.ok(lines[0].includes(", which had expired, from")); + const refused = fakeAppSync([key("da2-secret", NOW - DAY)], "BadRequestException"); + await assert.rejects(run(refused, NOW, LIMITS), (error) => { + assert.match(error.message, /refused to extend .* \(BadRequestException\)\. Run infra\/ci\/provision-ci\.sh --apply --scope all/); + assert.ok(!error.message.includes("da2-secret")); + return true; + }); +}); + +test("run fails when no key is left, and passes on any refusal of a key that has not expired", async () => { + await assert.rejects(run(fakeAppSync([]), NOW, LIMITS), /No "ccit-ci tests" API key is left to extend/); + await assert.rejects(run(fakeAppSync([key("a", NOW + DAY)], "AccessDeniedException"), NOW, LIMITS), + (error) => error.name === "AccessDeniedException"); +}); + +test("settings need the API, default the limits as provision-ci.sh sets them, and refuse others", () => { + assert.throws(() => settings({}), /API_ID must be set/); + assert.deepEqual(settings({ API_ID: "api" }), { apiId: "api", limits: LIMITS }); + assert.deepEqual(settings({ API_ID: "api", KEY_DAYS: "365", CLOCK_ALLOWANCE_SECONDS: "600", + EXTEND_WITHIN_DAYS: "120" }).limits, LIMITS); + assert.throws(() => settings({ API_ID: "api", KEY_DAYS: "366" }), /KEY_DAYS must be a whole number/); + assert.throws(() => settings({ API_ID: "api", EXTEND_WITHIN_DAYS: "1.5" }), /EXTEND_WITHIN_DAYS/); +}); diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda_code.py b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda_code.py index 3acd31d259..799a410327 100755 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda_code.py +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda_code.py @@ -87,6 +87,7 @@ "verify-auth-challenge": TRIGGERS, "discard-sender": ("ci/lambda/discard-sender", ("index.mjs",)), "new-password-reset": ("ci/lambda/new-password-reset", ("index.mjs",)), + "extend-api-key": ("ci/lambda/extend-api-key", ("index.mjs",)), "custom-sender": ("lambda/custom-sender", ("index.mjs", "package.json", "package-lock.json")), } # Lambda takes a zip of up to 50 MB directly (provision-ci.sh's --zip-file) and 250 MB unzipped; anything past the diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh index 5f48cf01c1..17d0814d06 100755 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh @@ -33,6 +33,11 @@ # place of the plugin's MFA-required backend (ci-overlay.sh's `mfa-req-totp-sms` role): # MF-13, MF-15…17 and the two USER_AUTH MFA-setup checks. Its pre-sign-up trigger is the # device-alias pool's, and its custom senders the discard sender, so nothing is sent +# ccit-ci-extend-api-key +# a Lambda, run weekly by the rule ccit-ci-extend-api-key, that extends the code sink's +# read-only API key in place to 365 days from now once 120 or fewer are left (its role +# ccit-ci-key-exec may only list and update the code sink API's keys), so the key never +# expires between reruns # # A rerun over resources an earlier run made adds what is missing and updates, after their tag checks, only # these of its own: the KMS key's policy and the sender's and triggers' role policies (each to name the new pool @@ -41,9 +46,10 @@ # stops in between leaves it open, still held to the external ID and the account, until a rerun narrows it), the # invoke permissions of the Lambdas the new pool names (one that names another pool's ARN, as after a pool is # deleted and made again, is replaced), the reset Lambda's role policy (to decrypt its parameter with the key, or -# to name a default pool made again), the identity pool roles' trust (to name an identity pool made again), and -# an SSM parameter stored under another key than the script's own, which it stores again, with the same value, -# under that key. +# to name a default pool made again), the identity pool roles' trust (to name an identity pool made again), an +# SSM parameter stored under another key than the script's own, which it stores again, with the same value, +# under that key, and the code sink's API key, extended in place once 120 or fewer days are left, as the weekly +# ccit-ci-extend-api-key does. # # It never updates a pool or a Lambda's code, so either is replaced: deleted by hand, after its tag check, then # made again by infra/ci/provision-ci.sh --apply --scope all. Never a plain --apply: that is phase 1, which makes @@ -51,7 +57,8 @@ # lambda-drift and the baseline check still pass. Do it while no CI run is going: until the rerun, the pools # cannot use what was deleted. # - A Lambda, after a change to lambda/triggers/triggers.mjs, lambda/custom-sender or ci/lambda/* (lambda-drift -# tells a function whose deployed files are not this checkout's): check that it carries +# tells a function whose deployed files are not this checkout's), or after the code sink's API is made again +# (ccit-ci-extend-api-key names the API it was made for): check that it carries # purpose=amplify-cognito-client-integ, delete it, and run --apply --scope all, which makes it again with every # pool's invoke permissions. # - A pool, after a change to its template: --upload never overwrites a file, so the uploaded files that name the @@ -185,7 +192,6 @@ POOL_KEYS=(email-alias) ALL_POOL_KEYS=(email-alias default email-alias-codes mfa-req-totp-sms) # The pools whose SmsConfiguration names the SNS caller role (the templates with SMS MFA), which its trust names. SMS_POOL_KEYS=(default mfa-req-totp-sms) -TRIGGERS_ALL=(pre-sign-up pre-sign-up-confirmable define-auth-challenge create-auth-challenge verify-auth-challenge) # The pools whose codes the custom sender decrypts: the ones it is the sender of. SENDER_POOL_KEYS=(default email-alias-codes) @@ -205,14 +211,18 @@ SMS_ROLE_CODES="InvalidSmsRoleTrustRelationshipException InvalidSmsRoleAccessPol TABLE="$(fn_name codes)" API_NAME="$(fn_name codes)" RULE="$(fn_name new-password-reset)" +KEY_RULE="$(fn_name extend-api-key)" S3_FOLDER_KEY="$CI_PREFIX_PATH/auth/$CI_S3_FOLDER" -# The functions in scope, with each one's handler, role and code. +# The functions of each phase (lambda_code.py's SOURCES zips every one), with each one's handler, role and code. +PHASE_1_FUNCTIONS=(pre-sign-up discard-sender) +PHASE_2_FUNCTIONS=(pre-sign-up-confirmable custom-sender define-auth-challenge create-auth-challenge + verify-auth-challenge new-password-reset extend-api-key) +ALL_FUNCTIONS=("${PHASE_1_FUNCTIONS[@]}" "${PHASE_2_FUNCTIONS[@]}") functions_in_scope() { - printf '%s\n' pre-sign-up discard-sender + printf '%s\n' "${PHASE_1_FUNCTIONS[@]}" if [[ "$SCOPE" == "all" ]]; then - printf '%s\n' pre-sign-up-confirmable custom-sender define-auth-challenge create-auth-challenge \ - verify-auth-challenge new-password-reset + printf '%s\n' "${PHASE_2_FUNCTIONS[@]}" fi } fn_handler() { @@ -221,13 +231,14 @@ fn_handler() { define-auth-challenge) echo triggers.defineAuthChallenge ;; create-auth-challenge) echo triggers.createAuthChallenge ;; verify-auth-challenge) echo triggers.verifyAuthChallenge ;; - custom-sender|discard-sender|new-password-reset) echo index.handler ;; + custom-sender|discard-sender|new-password-reset|extend-api-key) echo index.handler ;; esac } fn_role() { case "$1" in custom-sender) echo sender-exec ;; new-password-reset) echo reset-exec ;; + extend-api-key) echo key-exec ;; *) echo trigger-exec ;; esac } @@ -293,7 +304,7 @@ preflight() { } # Dies unless the account has room for the $1 (role, api or rule) this run is about to create, as preflight read it. -# Each keeps a margin: 50 roles left (the script makes 7 at most), 2 APIs left after this one, 250 rules at most. +# Each keeps a margin: 50 roles left (the script makes 8 at most), 2 APIs left after this one, 250 rules at most. require_headroom() { case "$1" in role) @@ -457,6 +468,14 @@ reset_policy() { "kms:EncryptionContext:PARAMETER_ARN": $p}}}]}' } +# The weekly key extension's (ccit-ci-extend-api-key): its logs, and listing and updating the code sink API's keys +# only. AppSync's ListApiKeys and UpdateApiKey take the API's ARN as their resource. +key_policy() { + jq -n -c --arg api "$API_ARN" --argjson logs "$(logs_statement extend-api-key)" '{Version: "2012-10-17", + Statement: [$logs, {Sid: "ExtendTestsKey", Effect: "Allow", Action: ["appsync:ListApiKeys", "appsync:UpdateApiKey"], + Resource: $api}]}' +} + identity_trust() { jq -n -c --arg id "$IDENTITY_POOL_ID" --arg amr "$1" '{Version: "2012-10-17", Statement: [{Effect: "Allow", Principal: {Federated: "cognito-identity.amazonaws.com"}, Action: "sts:AssumeRoleWithWebIdentity", @@ -824,7 +843,7 @@ generate_temporary() { # --- Lambdas ---------------------------------------------------------------------------------------------- # Sets ZIP to the zip for function $1 in $CI_WORK, built once per code: the sandbox's triggers.mjs, the sandbox's -# custom sender (npm ci), or this directory's discard sender or new-password reset. The zip is deterministic +# custom sender (npm ci), or this directory's discard sender, new-password reset or key extension. The zip is deterministic # (lambda_code.py), and lambda-drift compares a deployed function's files with it. Built in --apply, and for # lambda-drift (BUILD_ZIPS=1). It sets a variable rather than printing the path, so # that it runs in this shell, never in a $(…), where a refusal would end only the subshell and the run would go on. @@ -836,6 +855,7 @@ zip_for() { custom-sender) zip="$CI_WORK/custom-sender.zip" ;; discard-sender) zip="$CI_WORK/discard-sender.zip" ;; new-password-reset) zip="$CI_WORK/new-password-reset.zip" ;; + extend-api-key) zip="$CI_WORK/extend-api-key.zip" ;; *) zip="$CI_WORK/triggers.zip" ;; esac if [[ "$suffix" == custom-sender ]]; then @@ -871,6 +891,10 @@ fn_environment() { jq -n -c --arg p "$(kv_get POOL_ID "default")" --arg u "$(new_password_users | paste -sd, -)" \ --arg t "$CI_SSM_TEMPORARY" '{POOL_ID: $p, USERNAMES: $u, TEMP_PASSWORD_PARAMETER: $t, USED_AFTER_MINUTES: "60", REFRESH_AFTER_DAYS: "10"}' ;; + # The same lifetime, allowance and threshold as ensure_api_key's. + extend-api-key) + jq -n -c --arg a "$API_ID" --arg d "$KEY_DAYS" --arg c "$KEY_CLOCK_ALLOWANCE" --arg w "$KEY_REUSE_DAYS" \ + '{API_ID: $a, KEY_DAYS: $d, CLOCK_ALLOWANCE_SECONDS: $c, EXTEND_WITHIN_DAYS: $w}' ;; *) printf '{}' ;; esac } @@ -890,6 +914,7 @@ ensure_function() { fi [[ "$suffix" == "custom-sender" ]] && memory=256 [[ "$suffix" == "new-password-reset" ]] && timeout=60 + [[ "$suffix" == "extend-api-key" ]] && timeout=30 input=$(jq -n -c --arg n "$name" --arg role "$(role_arn "$(fn_role "$suffix")")" --arg h "$(fn_handler "$suffix")" \ --argjson t "$timeout" --argjson m "$memory" --argjson e "$environment" --argjson tags "$(tags_json)" \ '{FunctionName: $n, Role: $role, Handler: $h, Runtime: "nodejs22.x", Timeout: $t, MemorySize: $m, @@ -1100,27 +1125,29 @@ ensure_identity_pool() { # --- New-password users (default) ------------------------------------------------------------------------- -ensure_reset_schedule() { - local out rule_arn target fn - fn=$(fn_name new-password-reset) - rule_arn="arn:aws:events:$CI_REGION:$CI_ACCOUNT:rule/$RULE" - if read_aws_or_missing out ResourceNotFoundException events describe-rule --name "$RULE"; then - require_ours rule "$RULE" "$RULE" - say " = schedule $RULE" +# Schedules function $2 by rule $1 at rate $3 (as "10 minutes", "7 days"), described by $4: the rule, the function's +# invoke permission for it, and the rule's target. +ensure_schedule() { + local rule="$1" suffix="$2" rate="$3" description="$4" out rule_arn target fn + fn=$(fn_name "$suffix") + rule_arn="arn:aws:events:$CI_REGION:$CI_ACCOUNT:rule/$rule" + if read_aws_or_missing out ResourceNotFoundException events describe-rule --name "$rule"; then + require_ours rule "$rule" "$rule" + say " = schedule $rule" else require_headroom rule - mutate "$RULE" "schedule $fn every 10 minutes" -- events put-rule --name "$RULE" \ - --schedule-expression "rate(10 minutes)" --state ENABLED \ - --description "ccit-ci: keeps the new-password users fresh" --tags "Key=$CI_TAG_KEY,Value=$CI_TAG_VALUE" + mutate "$rule" "schedule $fn every $rate" -- events put-rule --name "$rule" \ + --schedule-expression "rate($rate)" --state ENABLED \ + --description "$description" --tags "Key=$CI_TAG_KEY,Value=$CI_TAG_VALUE" fi - ensure_invoke_permission new-password-reset events-schedule events.amazonaws.com "$rule_arn" + ensure_invoke_permission "$suffix" events-schedule events.amazonaws.com "$rule_arn" target="" - if read_aws_or_missing out ResourceNotFoundException events list-targets-by-rule --rule "$RULE"; then - target=$(jq -r --arg a "$(fn_arn new-password-reset)" '[.Targets[] | select(.Arn == $a)][0].Id // empty' <<<"$out") + if read_aws_or_missing out ResourceNotFoundException events list-targets-by-rule --rule "$rule"; then + target=$(jq -r --arg a "$(fn_arn "$suffix")" '[.Targets[] | select(.Arn == $a)][0].Id // empty' <<<"$out") fi if [[ -z "$target" ]]; then - mutate "$RULE" "point the schedule at Lambda $fn" -- events put-targets --rule "$RULE" \ - --targets "Id=new-password-reset,Arn=$(fn_arn new-password-reset)" + mutate "$rule" "point the schedule at Lambda $fn" -- events put-targets --rule "$rule" \ + --targets "Id=$suffix,Arn=$(fn_arn "$suffix")" fi } @@ -1305,7 +1332,7 @@ provision() { say "Lambdas:" while read -r suffix; do - [[ "$suffix" == "new-password-reset" ]] || ensure_function "$suffix" + [[ "$suffix" == "new-password-reset" || "$suffix" == "extend-api-key" ]] || ensure_function "$suffix" done < <(functions_in_scope) say "User pools:" @@ -1327,8 +1354,12 @@ provision() { say "New-password users:" ensure_role reset-exec "$(lambda_trust)" "$(reset_policy)" ensure_function new-password-reset - ensure_reset_schedule + ensure_schedule "$RULE" new-password-reset "10 minutes" "ccit-ci: keeps the new-password users fresh" run_reset_once + say "The code sink key's weekly extension:" + ensure_role key-exec "$(lambda_trust)" "$(key_policy)" + ensure_function extend-api-key + ensure_schedule "$KEY_RULE" extend-api-key "7 days" "ccit-ci: keeps the code sink's API key from expiring" fi if [[ "$CI_MODE" == "apply" ]]; then @@ -1414,7 +1445,7 @@ lambda_drift() { mkdir -p "$zips" deployed="{}" BUILD_ZIPS=1 - for suffix in "${TRIGGERS_ALL[@]}" discard-sender custom-sender new-password-reset; do + for suffix in "${ALL_FUNCTIONS[@]}"; do name=$(fn_name "$suffix") entry="null" if read_aws_or_missing out ResourceNotFoundException lambda get-function --function-name "$name"; then @@ -1583,7 +1614,7 @@ snapshot() { # --- teardown --------------------------------------------------------------------------------------------- teardown() { - local out key file id suffix role fn + local out key file id suffix role fn spec rule if [[ "$CI_MODE" == "apply" ]]; then say "Teardown: deleting the ccit-ci- resources, each after its tag check." else @@ -1596,11 +1627,14 @@ teardown() { mutate "$key" "delete auth/$CI_S3_FOLDER/$file" -- s3api delete-object --bucket "$CI_BUCKET" --key "$key" fi done - if read_aws_or_missing out ResourceNotFoundException events describe-rule --name "$RULE"; then - require_ours rule "$RULE" "$RULE" - mutate "$RULE" "remove $RULE's target" -- events remove-targets --rule "$RULE" --ids new-password-reset - mutate "$RULE" "delete schedule $RULE" -- events delete-rule --name "$RULE" - fi + for spec in "$RULE new-password-reset" "$KEY_RULE extend-api-key"; do + read -r rule suffix <<<"$spec" + if read_aws_or_missing out ResourceNotFoundException events describe-rule --name "$rule"; then + require_ours rule "$rule" "$rule" + mutate "$rule" "remove $rule's target" -- events remove-targets --rule "$rule" --ids "$suffix" + mutate "$rule" "delete schedule $rule" -- events delete-rule --name "$rule" + fi + done read_all_pages out IdentityPools cognito-identity list-identity-pools for id in $(jq -r --arg n "$CI_IDENTITY_POOL" '.IdentityPools[] | select(.IdentityPoolName == $n) | .IdentityPoolId' <<<"$out"); do require_ours identity-pool "$id" "$CI_IDENTITY_POOL" @@ -1615,7 +1649,7 @@ teardown() { cognito-idp delete-user-pool --user-pool-id "$id" done done - for suffix in "${TRIGGERS_ALL[@]}" discard-sender custom-sender new-password-reset; do + for suffix in "${ALL_FUNCTIONS[@]}"; do fn=$(fn_name "$suffix") if read_aws_or_missing out ResourceNotFoundException lambda get-function-configuration --function-name "$fn"; then require_ours lambda "$fn" "$fn" @@ -1631,7 +1665,7 @@ teardown() { require_ours table "$TABLE" "$TABLE" mutate "$TABLE" "delete table $TABLE" -- dynamodb delete-table --table-name "$TABLE" fi - for suffix in trigger-exec sender-exec appsync-codes cognito-sms reset-exec identity-authenticated \ + for suffix in trigger-exec sender-exec appsync-codes cognito-sms reset-exec key-exec identity-authenticated \ identity-unauthenticated; do role=$(role_name "$suffix") if read_aws_or_missing out NoSuchEntity iam get-role --role-name "$role"; then @@ -1666,7 +1700,7 @@ teardown() { fi mutate "$CI_KMS_ALIAS" "delete alias $CI_KMS_ALIAS" -- kms delete-alias --alias-name "$CI_KMS_ALIAS" fi - for suffix in "${TRIGGERS_ALL[@]}" discard-sender custom-sender new-password-reset; do + for suffix in "${ALL_FUNCTIONS[@]}"; do file=$(log_group "$suffix") read_aws out logs describe-log-groups --log-group-name-prefix "$file" if jq -e --arg n "$file" '.logGroups[] | select(.logGroupName == $n)' <<<"$out" >/dev/null; then diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_lambda_code.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_lambda_code.sh index f4f49dcf32..97bdf123e2 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_lambda_code.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_lambda_code.sh @@ -49,14 +49,15 @@ umask 077 COPY="$WORK/infra" mkdir -p "$COPY/lambda" "$COPY/ci/lambda" cp -R "$INFRA/lambda/triggers" "$INFRA/lambda/custom-sender" "$COPY/lambda/" -cp -R "$INFRA/ci/lambda/discard-sender" "$INFRA/ci/lambda/new-password-reset" "$COPY/ci/lambda/" +cp -R "$INFRA/ci/lambda/discard-sender" "$INFRA/ci/lambda/new-password-reset" "$INFRA/ci/lambda/extend-api-key" \ + "$COPY/ci/lambda/" rm -rf "$COPY/lambda/custom-sender/node_modules" export CCIT_CI_INFRA_DIR="$COPY" CCIT_CI_TEST_FILE_CODE_URLS=1 # The local server is never reached through a proxy. export no_proxy="127.0.0.1${no_proxy:+,$no_proxy}" BASELINE="$WORK/baseline.json" -SUFFIXES=(create-auth-challenge custom-sender define-auth-challenge discard-sender new-password-reset pre-sign-up - pre-sign-up-confirmable verify-auth-challenge) +SUFFIXES=(create-auth-challenge custom-sender define-auth-challenge discard-sender extend-api-key new-password-reset + pre-sign-up pre-sign-up-confirmable verify-auth-challenge) failures=0 check() { @@ -153,6 +154,8 @@ with zipfile.ZipFile(os.path.join(root, "pre-sign-up.zip")) as z: assert info.date_time == (1980, 1, 1, 0, 0, 0) and info.external_attr >> 16 == 0o644, info with zipfile.ZipFile(os.path.join(root, "discard-sender.zip")) as z: assert z.namelist() == ["index.mjs"], z.namelist() +with zipfile.ZipFile(os.path.join(root, "extend-api-key.zip")) as z: + assert z.namelist() == ["index.mjs"], z.namelist() with zipfile.ZipFile(os.path.join(root, "custom-sender.zip")) as z: assert z.namelist() == ["index.mjs", "node_modules/dependency/index.js", "package.json"], z.namelist() PY @@ -162,13 +165,13 @@ compare_reads_the_deployed_files() { build_all "$WORK/z" deploy_all "$WORK/z" run compare "$WORK/deployed.json" "$WORK/z" --baseline "$BASELINE" - (( STATUS == 0 )) && [[ "$(grep -c "the deployed files are this checkout's" <<<"$OUT")" == 8 ]] \ - && [[ "$(grep -c "CodeSha256, a note only: the same as this checkout's zip's" <<<"$OUT")" == 8 ]] \ + (( STATUS == 0 )) && [[ "$(grep -c "the deployed files are this checkout's" <<<"$OUT")" == 9 ]] \ + && [[ "$(grep -c "CodeSha256, a note only: the same as this checkout's zip's" <<<"$OUT")" == 9 ]] \ || { echo "$OUT"; return 1; } # The same files in another zip: in sync; only the note tells the CodeSha256 apart. redeploy discard-sender rezipped run compare "$WORK/deployed.json" "$WORK/z" --baseline "$BASELINE" - (( STATUS == 0 )) && [[ "$(grep -c "the deployed files are this checkout's" <<<"$OUT")" == 8 ]] \ + (( STATUS == 0 )) && [[ "$(grep -c "the deployed files are this checkout's" <<<"$OUT")" == 9 ]] \ && [[ "$(grep -A1 '= ccit-ci-discard-sender' <<<"$OUT")" == *"not this checkout's zip's (the files decide)"* ]] \ || { echo "$OUT"; return 1; } # Other files: DRIFT, by name, with what differs. @@ -273,7 +276,7 @@ only_an_aws_https_url_is_downloaded() { STATUS=0 OUT=$(env -u CCIT_CI_TEST_FILE_CODE_URLS python3 "$SCRIPT" compare "$WORK/deployed.json" "$WORK/u" --baseline "$BASELINE" 2>&1) \ || STATUS=$? - (( STATUS == 1 )) && [[ "$(grep -c 'could not be read' <<<"$OUT")" == 8 ]] || { echo "$OUT"; return 1; } + (( STATUS == 1 )) && [[ "$(grep -c 'could not be read' <<<"$OUT")" == 9 ]] || { echo "$OUT"; return 1; } # A download that is no zip, or none at all. echo 'not a zip' > "$WORK/deployed/pre-sign-up.zip" relocate discard-sender "file://$WORK/deployed/missing.zip" @@ -398,12 +401,12 @@ an_accepted_deployment_is_in_sync_until_its_sources_change() { (( STATUS == 1 )) && [[ "$OUT" == *"DRIFT ccit-ci-pre-sign-up"*"changed (1): triggers.mjs"*"Nothing recorded."* ]] \ && [[ ! -e "$BASELINE" ]] || { echo "record without --accept-deployed: $OUT"; return 1; } run record "$WORK/deployed.json" "$WORK/r" --baseline "$BASELINE" --accept-deployed - (( STATUS == 0 )) && jq -e '.functions | (keys | length == 8) and (.["pre-sign-up"].files | keys == ["notes.txt", "triggers.mjs"]) + (( STATUS == 0 )) && jq -e '.functions | (keys | length == 9) and (.["pre-sign-up"].files | keys == ["notes.txt", "triggers.mjs"]) and all(.[]; (.sources | test("^[0-9a-f]{64}$")) and (.files | all(.[]; test("^[0-9a-f]{64}$"))))' "$BASELINE" >/dev/null \ || { echo "record: $OUT"; return 1; } run compare "$WORK/deployed.json" "$WORK/r" --baseline "$BASELINE" (( STATUS == 0 )) && [[ "$OUT" == *"= ccit-ci-pre-sign-up: the deployed files are the ones the baseline accepted"* ]] \ - && [[ "$(grep -c "the deployed files are this checkout's" <<<"$OUT")" == 7 ]] || { echo "after the record: $OUT"; return 1; } + && [[ "$(grep -c "the deployed files are this checkout's" <<<"$OUT")" == 8 ]] || { echo "after the record: $OUT"; return 1; } run check-baseline --baseline "$BASELINE" (( STATUS == 0 )) || { echo "check-baseline: $OUT"; return 1; } # triggers.mjs changes: the accepted deployment of pre-sign-up is no longer this checkout's. @@ -488,7 +491,7 @@ a_baselined_function_that_is_not_deployed_is_drift() { || { echo "the advice: $OUT"; return 1; } deploy_all "$WORK/m" run record "$WORK/deployed.json" "$WORK/m" --baseline "$baseline" --accept-deployed - (( STATUS == 0 )) && [[ "$(jq '.functions | length' "$baseline")" == 8 ]] || { echo "record: $OUT"; return 1; } + (( STATUS == 0 )) && [[ "$(jq '.functions | length' "$baseline")" == 9 ]] || { echo "record: $OUT"; return 1; } cp "$baseline" "$WORK/m-recorded.json" # The five triggers deleted, then a plain --apply, which makes pre-sign-up again but not the other four. for suffix in create-auth-challenge define-auth-challenge pre-sign-up-confirmable verify-auth-challenge; do @@ -496,7 +499,7 @@ a_baselined_function_that_is_not_deployed_is_drift() { done run compare "$WORK/deployed.json" "$WORK/m" --baseline "$baseline" --github (( STATUS == 1 )) && [[ "$(grep -c '^::error::DRIFT ccit-ci-.*: not deployed, though the baseline records its deployment. Make it again: rerun provision-ci.sh --apply --scope all (not a plain --apply' <<<"$OUT")" == 4 ]] \ - && [[ "$OUT" == *"::error::DRIFT ccit-ci-verify-auth-challenge: not deployed"* && "$(grep -c "are this checkout's" <<<"$OUT")" == 4 ]] \ + && [[ "$OUT" == *"::error::DRIFT ccit-ci-verify-auth-challenge: not deployed"* && "$(grep -c "are this checkout's" <<<"$OUT")" == 5 ]] \ || { echo "compare: $OUT"; return 1; } run record "$WORK/deployed.json" "$WORK/m" --baseline "$baseline" --accept-deployed (( STATUS == 1 )) && [[ "$OUT" == *"Nothing recorded: not deployed: ccit-ci-create-auth-challenge, ccit-ci-define-auth-challenge, ccit-ci-pre-sign-up-confirmable, ccit-ci-verify-auth-challenge. A baseline holds every CI Lambda"*"first check that the deployed ccit-ci- functions carry purpose=amplify-cognito-client-integ (aws lambda list-tags), then rerun provision-ci.sh --apply --scope all"* ]] \ @@ -576,7 +579,7 @@ PY run compare "$WORK/deployed.json" "$WORK/k" --baseline "$baseline" (( STATUS == 1 )) && [[ "$OUT" == *"! ccit-ci-discard-sender: the deployed code could not be read: it is not a zip this script can read (error)."* ]] \ && [[ "$OUT" == *"! ccit-ci-pre-sign-up: the deployed code could not be read: it is not a zip this script can read (RuntimeError)."* ]] \ - && [[ "$(grep -c "the deployed files are this checkout's" <<<"$OUT")" == 6 && "$OUT" != *Traceback* ]] \ + && [[ "$(grep -c "the deployed files are this checkout's" <<<"$OUT")" == 7 && "$OUT" != *Traceback* ]] \ || { echo "compare: $STATUS $OUT"; return 1; } run record "$WORK/deployed.json" "$WORK/k" --baseline "$baseline" --accept-deployed (( STATUS == 1 )) && [[ "$OUT" == *"Nothing recorded: a deployed function's code could not be read."* && ! -e "$baseline" ]] \ @@ -596,7 +599,7 @@ a_record_holds_every_function() { && [[ ! -e "$baseline" ]] || { echo "one missing: $OUT"; return 1; } deploy_all "$WORK/a8" run record "$WORK/deployed.json" "$WORK/a8" --baseline "$baseline" --accept-deployed - (( STATUS == 0 )) && [[ "$OUT" == *"Recorded the deployed files of 8 functions"* ]] \ + (( STATUS == 0 )) && [[ "$OUT" == *"Recorded the deployed files of 9 functions"* ]] \ && python3 - "$SCRIPT" "$baseline" <<'PY' || { echo "the complete set: $OUT"; return 1; } import importlib.util, json, sys spec = importlib.util.spec_from_file_location("lambda_code", sys.argv[1]) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh index 81db5c95a8..9cf0f1c1f3 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh @@ -18,10 +18,13 @@ # needs; that a ccit-ci- name without the purpose tag is refused before any change, by a dry run and by --apply; # that --apply creates everything, tagged, leaves the plugin's resources exactly as they were, narrows the key to the # four pools, the sender's decrypt to the two it sends for and the SMS role to the default and MFA-required pools, -# and writes the seven files (mode 600, in a mode-700 directory) with what the client reads; that a second --apply makes no call; that +# makes the code sink key's weekly extension (its function, a role allowed only its logs and the two AppSync key +# calls on the API's ARN, and a weekly rule), and writes the seven files (mode 600, in a mode-700 directory) with +# what the client reads; that a second --apply makes no call; that # --upload puts only new keys under auth/cognito-client-ci/ and refuses, with no upload, once one exists; that a # rerun over phase 2 as it was first applied (the script at 80712afe4 when git has it, else skipped) plans and makes -# only the alias-codes and MFA-required pools and what they need, updates only the script's own resources, and +# only the alias-codes and MFA-required pools and what they need, and the key's weekly extension, updates only the +# script's own resources, and # uploads only the three new files, finding the four uploaded ones byte for byte the same; that a phase-1 rerun after # phase 2 makes no call; that the code sink key is made and extended for 365 days, to the hour, within AppSync's # bounds (which the fake checks) though AppSync's clock is behind; that a rerun reuses a key that lives more than @@ -356,6 +359,34 @@ secrets_use_the_ccit_ci_key() { | endswith(":parameter/ccit-ci/new-password-temporary"))] == [true])' "$FAKE_STATE" >/dev/null } +# The code sink key's weekly extension as made: the function, from ci/lambda/extend-api-key, told the code sink's API +# and the script's own lifetime, allowance and threshold; its role, allowed only its logs and the two AppSync calls +# on that API's ARN; and the weekly rule, the function its one target and allowed to invoke it from that rule only; +# each tagged. +key_extension_is_right() { + jq -e '(.apis | to_entries[] | select(.value.api.name == "ccit-ci-codes") | .value.api) as $api + | .lambdas["ccit-ci-extend-api-key"] as $f | .roles["ccit-ci-key-exec"] as $r | .rules["ccit-ci-extend-api-key"] as $e + | ($f.config | .Handler == "index.handler" and (.Role | endswith(":role/ccit-ci-key-exec")) and .Timeout == 30 + and .Environment.Variables == {API_ID: $api.apiId, KEY_DAYS: "365", CLOCK_ALLOWANCE_SECONDS: "600", + EXTEND_WITHIN_DAYS: "120"}) + and ($f.tags.purpose == "amplify-cognito-client-integ") + and ($r.Role.AssumeRolePolicyDocument.Statement == [{Effect: "Allow", Principal: {Service: "lambda.amazonaws.com"}, + Action: "sts:AssumeRole", Condition: {StringEquals: {"aws:SourceAccount": .account}}}]) + and ($r.tags == [{Key: "purpose", Value: "amplify-cognito-client-integ"}]) and ($r.attached == []) + and ($r.inline | keys == ["ccit-ci-key-exec-policy"]) + and ($r.inline["ccit-ci-key-exec-policy"].Statement == [ + {Sid: "WriteOwnLogs", Effect: "Allow", Action: ["logs:CreateLogStream", "logs:PutLogEvents"], + Resource: ["arn:aws:logs:us-east-1:\(.account):log-group:/aws/lambda/ccit-ci-extend-api-key:*"]}, + {Sid: "ExtendTestsKey", Effect: "Allow", Action: ["appsync:ListApiKeys", "appsync:UpdateApiKey"], + Resource: $api.arn}]) + and ($e.rule.ScheduleExpression == "rate(7 days)") and ($e.tags == [{Key: "purpose", Value: "amplify-cognito-client-integ"}]) + and ($e.targets == [{Id: "extend-api-key", Arn: $f.config.FunctionArn}]) + and ($f.policy == [{Sid: "events-schedule", Principal: {Service: "events.amazonaws.com"}, + Condition: {ArnLike: {"AWS:SourceArn": $e.rule.Arn}}}]) + and (.log_groups["/aws/lambda/ccit-ci-extend-api-key"] == {tags: {purpose: "amplify-cognito-client-integ"}, retention: 7})' \ + "$FAKE_STATE" >/dev/null +} + phase_2_adds_the_default_pool_and_keeps_phase_1() { local before modes default rotation before=$(plugin_part) @@ -382,9 +413,9 @@ phase_2_adds_the_default_pool_and_keeps_phase_1() { jq -e '([.pools[] | select(.UserPool.Name | startswith("ccit-ci-")) | .UserPool.UserPoolTags.purpose] | length == 4 and (unique == ["amplify-cognito-client-integ"])) and ([.pools[] | .UserPool.Name] | sort == ["amplify-plugin-default", "ccit-ci-default", "ccit-ci-email-alias", "ccit-ci-email-alias-codes", "ccit-ci-mfa-req-totp-sms"]) and ([.pools[] | select(.UserPool.Name == "ccit-ci-default") | .UserPool.UserPoolTier] == ["ESSENTIALS"]) - and ([.roles | keys[] | select(startswith("ccit-ci-"))] | length == 7) + and ([.roles | keys[] | select(startswith("ccit-ci-"))] | length == 8) and ([.roles[] | select(.Role.RoleName | startswith("ccit-ci-")) | .tags[0].Value] | unique == ["amplify-cognito-client-integ"]) - and ([.lambdas[] | select(.config.FunctionName | startswith("ccit-ci-")) | .tags.purpose] | length == 8 and (unique == ["amplify-cognito-client-integ"])) + and ([.lambdas[] | select(.config.FunctionName | startswith("ccit-ci-")) | .tags.purpose] | length == 9 and (unique == ["amplify-cognito-client-integ"])) and ([.lambdas[] | .config.FunctionName] | all(startswith("ccit-ci-") or . == "plugin-pre-sign-up")) and ([.ssm | keys[]] == ["/ccit-ci/custom-challenge-answer", "/ccit-ci/new-password-temporary"]) and ([.identity_pools[] | .IdentityPoolName] | sort == ["ccit_ci_default", "plugin_identity"])' "$FAKE_STATE" >/dev/null || return 1 @@ -394,7 +425,7 @@ phase_2_adds_the_default_pool_and_keeps_phase_1() { | .UserPool.Arn] | sort) as $sms | .roles["ccit-ci-cognito-sms"].Role.AssumeRolePolicyDocument.Statement[0].Condition.ArnEquals["aws:SourceArn"] == $sms' \ "$FAKE_STATE" >/dev/null || return 1 - secrets_use_the_ccit_ci_key || return 1 + secrets_use_the_ccit_ci_key && key_extension_is_right || return 1 [[ "$OUT" == *"new-password users: created 12"* ]] } @@ -556,10 +587,13 @@ a_key_with_other_contents_refuses_the_upload() { # What a rerun over phase 2 as first applied (80712afe4) plans: the alias-codes and MFA-required pools and what they # need, the updates to the script's own resources that name them (the SMS role's trust opened while the MFA-required -# pool is made, then narrowed), and the three new files. The code sink key that script made (for 364 days) is reused, +# pool is made, then narrowed), the code sink key's weekly extension (its log group, role, function and rule), and +# the three new files. The code sink key that script made (for 364 days) is reused, # as the live one is: it lives more than KEY_REUSE_DAYS. RERUN_PLAN="create log group /aws/lambda/ccit-ci-pre-sign-up-confirmable keep /aws/lambda/ccit-ci-pre-sign-up-confirmable for 7 days +create log group /aws/lambda/ccit-ci-extend-api-key +keep /aws/lambda/ccit-ci-extend-api-key for 7 days open the key policy of alias/ccit-ci-senders to the account's pools while a ccit-ci- pool is made set the one inline policy of ccit-ci-trigger-exec open the trust of ccit-ci-cognito-sms to the account's pools while a ccit-ci- SMS pool is made @@ -579,6 +613,12 @@ set the key policy of alias/ccit-ci-senders to the ccit-ci- pools set the one inline policy of ccit-ci-sender-exec set the trust of ccit-ci-cognito-sms to the ccit-ci- SMS pools set the one inline policy of ccit-ci-reset-exec +create role ccit-ci-key-exec +set the one inline policy of ccit-ci-key-exec +create Lambda ccit-ci-extend-api-key (Node.js 22, no reserved concurrency) +schedule ccit-ci-extend-api-key every 7 days +let events.amazonaws.com invoke ccit-ci-extend-api-key (events-schedule) +point the schedule at Lambda ccit-ci-extend-api-key upload auth/cognito-client-ci/ccit-ci-email-alias-codes-amplify_outputs.json (new key only) upload auth/cognito-client-ci/ccit-ci-capabilities.json (new key only) upload auth/cognito-client-ci/ccit-ci-mfa-req-totp-sms-amplify_outputs.json (new key only)" @@ -617,7 +657,7 @@ a_rerun_over_phase_2_as_applied_adds_only_the_alias_codes_pool() { (( STATUS == 0 )) && only_reads && no_identifiers && plus_lines_are_ours \ && [[ "$(jq -S . "$FAKE_STATE")" == "$before" ]] \ && [[ "$(sed -nE 's/^ \+ //p' <<<"$OUT")" == "$RERUN_PLAN" ]] \ - && [[ "$OUT" == *"Planned: 24 calls. Nothing was changed."* ]] \ + && [[ "$OUT" == *"Planned: 32 calls. Nothing was changed."* ]] \ && [[ "$OUT" == *"= the code sink API key (lives more than 120 more days)"* ]] \ || { echo "$OUT" | sed -nE 's/^ \+ //p'; return 1; } before=$(plugin_part) @@ -644,6 +684,8 @@ a_rerun_over_phase_2_as_applied_adds_only_the_alias_codes_pool() { && [[ "$out" == *"changed ssm-parameter /ccit-ci/new-password-temporary: KeyId (ccit-ci-, this script's own)"* ]] \ && [[ "$out" == *"added user-pool ccit-ci-email-alias-codes (ccit-ci-, expected)"* ]] \ && [[ "$out" == *"added user-pool ccit-ci-mfa-req-totp-sms (ccit-ci-, expected)"* ]] \ + && [[ "$out" == *"added lambda ccit-ci-extend-api-key (ccit-ci-, expected)"* ]] \ + && [[ "$out" == *"added events-rule ccit-ci-extend-api-key (ccit-ci-, expected)"* ]] \ && [[ "$out" == *"changed iam-role ccit-ci-cognito-sms: AssumeRolePolicyDocument.Statement (ccit-ci-, this script's own)"* ]] \ && [[ "$out" == *"changed lambda ccit-ci-discard-sender: RevisionId (ccit-ci-, this script's own)"* ]] \ && [[ "$out" == *" 0 changed, 19 ccit-ci- changed, 0 removed"* ]] || { echo "$out"; return 1; } @@ -686,6 +728,10 @@ snapshot_and_verify() { first=$(find "$WORK/disc" -name 'snapshot-*.json' ! -name '*.docs.json' | sort | tail -1) [[ -f "$first" && "$(stat -f '%Lp' "$first")" == 600 && "$(stat -f '%Lp' "${first%.json}.docs.json")" == 600 ]] || return 1 snapshot_documents_hold_no_secret "${first%.json}.docs.json" || { echo "a secret is in the documents"; return 1; } + # The key extension's function, rule, role and log group are read like every other. + jq -e '[.entries[] | select(.label | test("ccit-ci-(extend-api-key|key-exec)$")) | .kind] | sort + == ["events-rule", "events-rule-targets", "iam-role", "iam-role-policies", "lambda", "lambda-policy", "log-group"]' \ + "$first" >/dev/null || { jq -c '[.entries[] | select(.label | contains("extend") or contains("key-exec"))]' "$first"; return 1; } mv "$first" "$WORK/disc/before.json" mv "${first%.json}.docs.json" "$WORK/disc/before.docs.json" # A user count moving is not a change. @@ -837,6 +883,8 @@ teardown_is_a_dry_run_by_default() { run teardown (( STATUS == 0 )) && only_reads && no_identifiers && [[ "$(jq -S . "$FAKE_STATE")" == "$before" ]] \ && [[ "$OUT" == *"+ delete user pool ccit-ci-default"* && "$OUT" == *"+ schedule the key's deletion in 7 days"* ]] \ + && [[ "$OUT" == *"+ delete schedule ccit-ci-extend-api-key"* && "$OUT" == *"+ delete Lambda ccit-ci-extend-api-key"* ]] \ + && [[ "$OUT" == *"+ delete role ccit-ci-key-exec"* && "$OUT" == *"+ delete log group /aws/lambda/ccit-ci-extend-api-key"* ]] \ && [[ "$OUT" != *"amplify-plugin-default"* && "$OUT" != *"plugin-role"* && "$OUT" != *"someone-else"* ]] } @@ -853,7 +901,7 @@ teardown_deletes_only_ours() { # the roles left after it, as a sorted JSON list and ([.kms.keys[] | .state] | unique == ["PendingDeletion"]) and ([.identity_pools[] | .IdentityPoolName] == ["plugin_identity"]) and (.apis == {}) and (.tables == {}) and ([.s3 | keys[] | select(contains("cognito-client-ci"))] == []) - and ([.log_groups | keys[]] == [])' --argjson roles "$roles" "$FAKE_STATE" >/dev/null + and ((.rules // {}) == {}) and ([.log_groups | keys[]] == [])' --argjson roles "$roles" "$FAKE_STATE" >/dev/null } teardown_refuses_an_untagged_ccit_ci_resource() { @@ -905,7 +953,7 @@ another_region_is_refused() { CCIT_CI_REGION=us-west-2 run teardown --apply (( STATUS == 1 )) && only_reads && [[ "$OUT" == *"Set CCIT_CI_REGION to theirs."* ]] || { echo "$OUT" | tail -5; return 1; } run lambda-drift --record --accept-deployed - (( STATUS == 0 )) && [[ "$OUT" == *"Recorded the deployed files of 8 functions"* ]] || { echo "$OUT" | tail -5; return 1; } + (( STATUS == 0 )) && [[ "$OUT" == *"Recorded the deployed files of 9 functions"* ]] || { echo "$OUT" | tail -5; return 1; } baseline=$(cat "$CCIT_CI_LAMBDA_BASELINE") for args in "" "--record --accept-deployed"; do # shellcheck disable=SC2086 @@ -1052,13 +1100,13 @@ lambda_drift_compares_the_deployed_files() { "$FAKE_STATE" > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" run lambda-drift (( STATUS == 0 )) && only_reads && no_identifiers \ - && [[ "$(grep -c "the deployed files are this checkout's" <<<"$OUT")" == 8 ]] \ - && [[ "$(grep -c "CodeSha256, a note only: the same as this checkout's zip's" <<<"$OUT")" == 8 ]] \ - && [[ "$(calls | grep -c '^lambda get-function$')" == 8 && "$(calls | grep -c '^lambda list-tags$')" == 8 ]] \ + && [[ "$(grep -c "the deployed files are this checkout's" <<<"$OUT")" == 9 ]] \ + && [[ "$(grep -c "CodeSha256, a note only: the same as this checkout's zip's" <<<"$OUT")" == 9 ]] \ + && [[ "$(calls | grep -c '^lambda get-function$')" == 9 && "$(calls | grep -c '^lambda list-tags$')" == 9 ]] \ && [[ -z "$(find "$TMPDIR" -name 'ccit-ci-lambda-code.*')" ]] || { echo "$OUT"; return 1; } hand_deploy ccit-ci-discard-sender rezipped run lambda-drift - (( STATUS == 0 )) && [[ "$(grep -c "the deployed files are this checkout's" <<<"$OUT")" == 8 ]] \ + (( STATUS == 0 )) && [[ "$(grep -c "the deployed files are this checkout's" <<<"$OUT")" == 9 ]] \ && [[ "$(grep -A1 '= ccit-ci-discard-sender' <<<"$OUT")" == *"not this checkout's zip's (the files decide)"* ]] \ || { echo "$OUT"; return 1; } hand_deploy ccit-ci-pre-sign-up edited @@ -1076,8 +1124,8 @@ lambda_drift_compares_the_deployed_files() { run lambda-drift --accept-deployed (( STATUS == 2 )) || return 1 run lambda-drift --record --accept-deployed - (( STATUS == 0 )) && only_reads && [[ "$OUT" == *"$summary"*"Recorded the deployed files of 8 functions"* ]] \ - && jq -e '.functions | length == 8 and (.["pre-sign-up"].files | keys == ["notes.txt", "triggers.mjs"]) + (( STATUS == 0 )) && only_reads && [[ "$OUT" == *"$summary"*"Recorded the deployed files of 9 functions"* ]] \ + && jq -e '.functions | length == 9 and (.["pre-sign-up"].files | keys == ["notes.txt", "triggers.mjs"]) and all(.[]; (.sources | test("^[0-9a-f]{64}$")) and (.files | all(.[]; test("^[0-9a-f]{64}$"))))' \ "$CCIT_CI_LAMBDA_BASELINE" >/dev/null || { echo "$OUT"; return 1; } run lambda-drift @@ -1185,7 +1233,7 @@ check "the Cognito lists are read to their last page: a ccit-ci- pool on page 2 check "teardown is a dry run by default and names only ccit-ci- resources" teardown_is_a_dry_run_by_default check "teardown --apply deletes only the ccit-ci- resources it made" teardown_deletes_only_ours if frozen_script; then - check "a rerun over phase 2 as applied ($FROZEN_COMMIT) adds only the alias-codes and MFA-required pools and three new files" \ + check "a rerun over phase 2 as applied ($FROZEN_COMMIT) adds only the alias-codes and MFA-required pools, the key's extension and three new files" \ a_rerun_over_phase_2_as_applied_adds_only_the_alias_codes_pool check "teardown --apply then deletes every ccit-ci- resource, the alias-codes pool's too" \ teardown_deletes_only_ours '["plugin-role"]' From abddf61493aae147246e0da4bdeeed90b7941914 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 13:35:40 -0400 Subject: [PATCH 37/57] ci(auth): extend an expired code sink key in place before making a new one AppSync's API reference keeps an expired key for 60 days and lets it be updated until then. A rerun now extends the tests' key that expires last even after it has expired, unless AppSync has deleted it. If AppSync refuses the update (BadRequestException, NotFoundException or ApiKeyValidityOutOfBoundsException), the run makes a new key as before, and the stale-upload refusal still applies. The fake AWS keeps expired keys, and refuses their update under FAKE_APPSYNC_EXPIRED_KEYS, so both answers are tested. --- .../IntegrationTests/infra/ci/fake_aws.py | 17 +++-- .../IntegrationTests/infra/ci/provision-ci.sh | 64 +++++++++++------ .../infra/ci/test_provision_ci.sh | 71 +++++++++++++------ 3 files changed, 106 insertions(+), 46 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/fake_aws.py b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/fake_aws.py index fed049a299..a0f7314f09 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/fake_aws.py +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/fake_aws.py @@ -289,10 +289,11 @@ def handle(state, service, verb, o, data): api["resolvers"][f"{data['typeName']}.{data['fieldName']}"] = data return {"resolver": data} if verb == "list-api-keys": + # As AppSync, which may still list a key for a while after it deleted it (60 days after it expired). return {"apiKeys": api["keys"]} if verb in ("create-api-key", "update-api-key"): - # As AppSync: 1 to 365 days from the call, stored rounded down to the hour. FAKE_APPSYNC_CLOCK_BEHIND - # (seconds) sets AppSync's clock behind the caller's. + # As AppSync: 1 to 365 days from the call, stored rounded down to the hour, and deleted 60 days after it + # expires. FAKE_APPSYNC_CLOCK_BEHIND (seconds) sets AppSync's clock behind the caller's. expires = int(one(o, "expires") if verb == "create-api-key" else data["expires"]) now = time.time() - int(os.environ.get("FAKE_APPSYNC_CLOCK_BEHIND", "0")) if not now + 86400 <= expires <= now + 365 * 86400: @@ -300,14 +301,20 @@ def handle(state, service, verb, o, data): "maximum of 365 days.") expires -= expires % 3600 if verb == "create-api-key": - key = {"id": "da2-" + new_id(s, "apikey"), "description": one(o, "description"), "expires": expires} + key = {"id": "da2-" + new_id(s, "apikey"), "description": one(o, "description"), "expires": expires, + "deletes": expires + 60 * 86400} api["keys"].append(key) return {"apiKey": key} if verb == "update-api-key": key = next((k for k in api["keys"] if k["id"] == data["id"]), None) - if key is None: + if key is None or key.get("deletes", key["expires"] + 60 * 86400) <= now: raise Fail("NotFoundException", "API key not found") - key["expires"] = expires + # AppSync's API reference has an expired key updated until it is deleted; FAKE_APPSYNC_EXPIRED_KEYS, an + # error code, has the fake refuse that instead, with that code. + refusal = os.environ.get("FAKE_APPSYNC_EXPIRED_KEYS") + if refusal and key["expires"] <= now: + raise Fail(refusal, "API key has expired") + key.update({"expires": expires, "deletes": expires + 60 * 86400}) return {"apiKey": key} if verb == "delete-graphql-api": del apis[api_id] diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh index 17d0814d06..2fd8fbf626 100755 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh @@ -661,46 +661,70 @@ ensure_api_resolvers() { done } -# The tests' read-only API key: one that lives more than KEY_REUSE_DAYS more days is reused; one that lives fewer but -# has not expired is extended to KEY_DAYS days from now, keeping its id, so the two uploaded files that name it (the -# default and alias-codes pools' outputs, which --upload never rewrites) stay valid; else a new one, for KEY_DAYS days. -# Its expiry is said right there (note_key_expiry). +# The tests' read-only API key, the "ccit-ci tests" key that expires last: one that lives more than KEY_REUSE_DAYS +# more days is reused; one that lives fewer is extended to KEY_DAYS days from now, keeping its id, so the two +# uploaded files that name it (the default and alias-codes pools' outputs, which --upload never rewrites) stay valid; +# else a new one, for KEY_DAYS days. Its expiry is said right there (note_key_expiry). +# An expired key is extended in place too: AppSync keeps one for 60 days after it expires (its `deletes`; one past +# that, which ListApiKeys may still name until it is gone, is not counted) and documents that it can still be +# updated until then. When AppSync refuses that (EXPIRED_KEY_REFUSALS), a new key is made instead, and the uploaded +# files that name the old one must be replaced (note_key_expiry says how, and --apply refuses at the end until they +# are). A dry run cannot tell which, and says both. # KEY_DAYS is AppSync's maximum: CreateApiKey and UpdateApiKey take 1 to 365 days from the call, and store the expiry # rounded down to the hour. The expiry asked for is that hour, taken after KEY_CLOCK_ALLOWANCE seconds off, so a # local clock up to 10 minutes ahead of AppSync's never asks for more than 365 days. # KEY_REUSE_DAYS is one quarter (90 days, the CI account's routine check) and 30 days of slack: any rerun leaves the # key with more than that, so it outlives the next quarterly rerun even when that is a month late, and a key is -# extended about every 245 days, so most reruns plan no key call. +# extended about every 245 days, so most reruns plan no key call. The weekly ccit-ci-extend-api-key extends it on the +# same terms between reruns. KEY_DAYS=365 KEY_CLOCK_ALLOWANCE=600 KEY_REUSE_DAYS=120 +KEY_KEPT_AFTER_EXPIRY=$((60 * 86400)) +EXPIRED_KEY_REFUSALS="BadRequestException NotFoundException ApiKeyValidityOutOfBoundsException" ensure_api_key() { - local out now expires input reuse_days="$KEY_REUSE_DAYS" + local out now expires input key_expires="" reuse_days="$KEY_REUSE_DAYS" description now=$(date +%s) expires=$(( (now + KEY_DAYS * 86400 - KEY_CLOCK_ALLOWANCE) / 3600 * 3600 )) API_KEY_EXPIRES="$expires" + API_KEY="" if ! is_new "$API_ID"; then read_aws out appsync list-api-keys --api-id "$API_ID" - API_KEY=$(jq -r --argjson soon "$((now + reuse_days * 86400))" \ - '[.apiKeys[] | select(.description == "ccit-ci tests" and .expires > $soon)] | sort_by(.expires) | last | .id // empty' <<<"$out") - if [[ -n "$API_KEY" ]]; then - API_KEY_EXPIRES=$(CCIT_SECRET_K="$API_KEY" jq -r '[.apiKeys[] | select(.id == $ENV.CCIT_SECRET_K)][0].expires' <<<"$out") - say " = the code sink API key (lives more than $reuse_days more days)" + API_KEY=$(jq -r --argjson now "$now" --argjson kept "$KEY_KEPT_AFTER_EXPIRY" '[.apiKeys[] + | select(.description == "ccit-ci tests" and ((.deletes // (.expires + $kept)) > $now))] + | sort_by(.expires) | last | .id // empty' <<<"$out") + [[ -z "$API_KEY" ]] \ + || key_expires=$(CCIT_SECRET_K="$API_KEY" jq -r '[.apiKeys[] | select(.id == $ENV.CCIT_SECRET_K)][0].expires' <<<"$out") + fi + if [[ -n "$API_KEY" ]] && (( key_expires > now + reuse_days * 86400 )); then + API_KEY_EXPIRES="$key_expires" + say " = the code sink API key (lives more than $reuse_days more days)" + note_key_expiry + return + fi + if [[ -n "$API_KEY" ]]; then + require_ours appsync "$API_ARN" "$API_NAME" + description="extend the tests' read-only API key to $KEY_DAYS days from now (the same key)" + (( key_expires > now )) \ + || description="extend the tests' read-only API key, which has expired, to $KEY_DAYS days from now (the same key, if AppSync takes it)" + # The key's id is its secret: in the input file, never on the command line. + input=$(CCIT_SECRET_K="$API_KEY" jq -n -c --arg id "$API_ID" --argjson e "$expires" \ + '{apiId: $id, id: $ENV.CCIT_SECRET_K, expires: $e}') + if (( key_expires > now )); then + mutate "$API_NAME" "$description" -- appsync update-api-key --cli-input-json "file://$(input_file "$input")" note_key_expiry return fi - API_KEY=$(jq -r --argjson now "$now" \ - '[.apiKeys[] | select(.description == "ccit-ci tests" and .expires > $now)] | sort_by(.expires) | last | .id // empty' <<<"$out") - if [[ -n "$API_KEY" ]]; then - require_ours appsync "$API_ARN" "$API_NAME" - # The key's id is its secret: in the input file, never on the command line. - input=$(CCIT_SECRET_K="$API_KEY" jq -n -c --arg id "$API_ID" --argjson e "$expires" \ - '{apiId: $id, id: $ENV.CCIT_SECRET_K, expires: $e}') - mutate "$API_NAME" "extend the tests' read-only API key to $KEY_DAYS days from now (the same key)" -- \ - appsync update-api-key --cli-input-json "file://$(input_file "$input")" + if MUTATE_TOLERATE="$EXPIRED_KEY_REFUSALS" mutate "$API_NAME" "$description" -- \ + appsync update-api-key --cli-input-json "file://$(input_file "$input")"; then + if [[ "$CI_MODE" != "apply" ]]; then + say " If AppSync refuses to extend the expired key, --apply makes a new one instead, and then the" \ + "uploaded files that name the old key must be replaced: the run says how." + fi note_key_expiry return fi + say " AppSync refused to extend the expired key (${AWS_ERR_CODE}): a new key instead." fi mutate "$API_NAME" "create the tests' read-only API key, for $KEY_DAYS days" -- appsync create-api-key \ --api-id "$API_ID" --description "ccit-ci tests" --expires "$expires" diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh index 9cf0f1c1f3..74ea210e92 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh @@ -28,8 +28,9 @@ # uploads only the three new files, finding the four uploaded ones byte for byte the same; that a phase-1 rerun after # phase 2 makes no call; that the code sink key is made and extended for 365 days, to the hour, within AppSync's # bounds (which the fake checks) though AppSync's clock is behind; that a rerun reuses a key that lives more than -# KEY_REUSE_DAYS (120), the one the frozen script made too, extends one that lives fewer in place, and makes a new -# one once it has expired, and that then the uploaded files that name the old key are named, and --apply refuses at +# KEY_REUSE_DAYS (120), the one the frozen script made too, extends one that lives fewer in place, and one that has +# expired too, while AppSync keeps it, and makes a new one once AppSync has deleted it or refuses the extension +# (the fake does either), and that then the uploaded files that name the old key are named, and --apply refuses at # the end, until they are replaced; that the AppSync headroom is checked only by a run that creates the API, against # CCIT_CI_APPSYNC_API_QUOTA; that a dry run refuses a custom sender to build without npm, and a failed npm ci stops # --apply before the sender; that lambda-drift downloads each deployed zip and tells a @@ -469,29 +470,37 @@ an_api_key_near_its_expiry_is_extended_in_place() { || { echo "$OUT" | grep 'expires on'; return 1; } } -# A rerun at the edges of the reuse threshold (dry runs only; the key's expiry is put back after): a key that lives +# A rerun at the edges of the reuse threshold (dry runs only; the key is put back after): a key that lives # KEY_REUSE_DAYS (120) more days and 10 minutes is reused, one 10 minutes short of them is extended, so is one an hour -# from its expiry, and an expired one is replaced by a new key. +# from its expiry, and so is one that expired a second or 59 days ago, which AppSync still keeps (the dry run says +# what follows if AppSync refuses it); one that AppSync has deleted (60 days after it expired), though still listed, +# is replaced by a new key. a_rerun_reuses_or_extends_the_key_at_the_threshold() { local before now case expires plan - before=$(jq -c '[.apis[] | .keys[] | .expires]' "$FAKE_STATE") + before=$(jq -c '[.apis[] | .keys[]]' "$FAKE_STATE") [[ "$(jq length <<<"$before")" == 1 ]] || { echo "not one key: $before"; return 1; } now=$(date +%s) # : - for case in $((120 * 86400 + 600)):reuse $((120 * 86400 - 600)):extend 3600:extend -1:create; do + for case in $((120 * 86400 + 600)):reuse $((120 * 86400 - 600)):extend 3600:extend -1:expired \ + $((-59 * 86400)):expired $((-61 * 86400)):create; do expires=$(( now + ${case%%:*} )) - jq --argjson e "$expires" '(.apis[] | .keys[]).expires = $e' "$FAKE_STATE" > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" + jq --argjson e "$expires" '(.apis[] | .keys[]) |= (.expires = $e | .deletes = $e + 60 * 86400)' "$FAKE_STATE" \ + > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" run --scope all plan=$(sed -nE 's/^ \+ //p' <<<"$OUT") (( STATUS == 0 )) && only_reads || { echo "$case: $OUT" | tail -5; return 1; } case "${case##*:}" in extend) [[ "$plan" == "extend the tests' read-only API key to 365 days from now (the same key)" ]] ;; reuse) [[ -z "$plan" && "$OUT" == *"= the code sink API key (lives more than 120 more days)"* ]] ;; + expired) + [[ "$plan" == "extend the tests' read-only API key, which has expired, to 365 days from now (the same key, if AppSync takes it)" ]] \ + && [[ "$OUT" == *"If AppSync refuses to extend the expired key, --apply makes a new one instead"* ]] \ + && [[ "$OUT" != *"but the uploaded"* ]] ;; create) [[ "$plan" == "create the tests' read-only API key, for 365 days" ]] ;; esac || { echo "$case: $plan"; return 1; } done - jq --argjson e "$before" '(.apis[] | .keys[]).expires = $e[0]' "$FAKE_STATE" > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" - [[ "$(jq -c '[.apis[] | .keys[] | .expires]' "$FAKE_STATE")" == "$before" ]] + jq --argjson k "$before" '(.apis[] | .keys) = $k' "$FAKE_STATE" > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" + [[ "$(jq -c '[.apis[] | .keys[]]' "$FAKE_STATE")" == "$before" ]] } days_from_now() { python3 -c 'import datetime,sys;print(datetime.datetime.now(datetime.timezone.utc).date() + datetime.timedelta(days=int(sys.argv[1])))' "$1"; } @@ -538,21 +547,39 @@ the_key_lives_365_days_within_appsyncs_bounds() { (( STATUS == 1 )) && [[ "$OUT" == *ApiKeyValidityOutOfBoundsException* ]] || { echo "$OUT" | tail -5; return 1; } } -# An expired key cannot be extended: --apply makes a new one, but the two uploaded files that name the old key are -# never overwritten: the dry run names them, --apply says how to replace them and refuses at the end, its upload -# refused, all with read calls only to tell. Once both are deleted (by hand, after their tag check), a rerun with -# --upload uploads them with the new key. +# An expired key that AppSync still keeps is extended in place when AppSync takes it, as its API reference says it +# does: the same key, one call, and the uploaded files that name it stay as they are. +an_expired_key_is_extended_in_place_when_appsync_takes_it() { + local key saved="$WORK/expired-in-place-state.json" + cp "$FAKE_STATE" "$saved" + key=$(jq -r '[.apis[] | .keys[]] | .[0].id' "$FAKE_STATE") + jq --argjson e "$(( $(date +%s) - 86400 ))" '(.apis[] | .keys[]).expires = $e' "$FAKE_STATE" > "$WORK/s" \ + && mv "$WORK/s" "$FAKE_STATE" + run --apply --scope all --upload + cp "$FAKE_STATE" "$WORK/expired-in-place-after.json" + cp "$saved" "$FAKE_STATE" + (( STATUS == 0 )) && [[ "$OUT" == *"Done: 1 calls made."* ]] && no_identifiers \ + && [[ "$(sed -nE 's/^ \+ //p' <<<"$OUT")" == "extend the tests' read-only API key, which has expired, to 365 days from now (the same key, if AppSync takes it)" ]] \ + && ! calls | grep -qE 'put-object|create-api-key' && [[ "$(grep -c 'uploaded already, the same bytes' <<<"$OUT")" == 7 ]] \ + && [[ "$OUT" == *"The code sink's API key expires on $(new_key_date) after this run."* && "$OUT" != *"refused"* ]] \ + || { echo "$OUT" | tail -8; return 1; } + jq -e --arg k "$key" --argjson e "$(( $(date +%s) + 300 * 86400 ))" \ + '[.apis[] | .keys[]] | length == 1 and .[0].id == $k and .[0].expires > $e' "$WORK/expired-in-place-after.json" >/dev/null +} + +# An expired key AppSync refuses to extend (FAKE_APPSYNC_EXPIRED_KEYS): --apply tries, then makes a new one, but the +# two uploaded files that name the old key are never overwritten: --apply names them, says how to replace them and +# refuses at the end, its upload refused, all with read calls only to tell. Once both are deleted (by hand, after +# their tag check), a rerun with --upload uploads them with the new key. an_expired_key_needs_the_uploads_that_name_it_replaced() { local prefix="v2/testconfiguration/auth/cognito-client-ci" file newest local stale="ccit-ci-default-amplify_outputs.json ccit-ci-email-alias-codes-amplify_outputs.json name another code sink key" jq --argjson e "$(( $(date +%s) - 86400 ))" '(.apis[] | .keys[]).expires = $e' "$FAKE_STATE" > "$WORK/s" \ && mv "$WORK/s" "$FAKE_STATE" - run --scope all - (( STATUS == 0 )) && only_reads && no_identifiers \ - && [[ "$OUT" == *"The code sink's API key expires on $(new_key_date) after this run, but the uploaded auth/cognito-client-ci/ files $stale"* ]] \ - || { echo "$OUT" | tail -8; return 1; } - run --apply --scope all --upload - (( STATUS == 1 )) && [[ "$OUT" == *"+ create the tests' read-only API key, for 365 days"* ]] \ + FAKE_APPSYNC_EXPIRED_KEYS=BadRequestException run --apply --scope all --upload + (( STATUS == 1 )) && [[ "$OUT" == *"+ extend the tests' read-only API key, which has expired, to 365 days from now"* ]] \ + && [[ "$OUT" == *"AppSync refused to extend the expired key (BadRequestException): a new key instead."* ]] \ + && [[ "$OUT" == *"+ create the tests' read-only API key, for 365 days"* ]] \ && [[ "$OUT" == *"after this run, but the uploaded auth/cognito-client-ci/ files $stale"* ]] \ && [[ "$OUT" == *"purpose=amplify-cognito-client-integ"*"--apply --scope all --upload"* ]] \ && [[ "$OUT" == *"than the one that expires on"*"go on using the other key"* ]] \ @@ -1215,11 +1242,13 @@ check "a phase-1 --apply --upload after phase 2 makes no call: the triggers' rol a_phase_1_rerun_after_phase_2_makes_no_call check "the code sink's API key near its expiry is extended in place, and the uploaded files stay valid" \ an_api_key_near_its_expiry_is_extended_in_place -check "a rerun reuses the key beyond the reuse threshold, extends it within it, and replaces an expired one" \ +check "a rerun reuses the key beyond the reuse threshold, extends it within it and once expired, and replaces a deleted one" \ a_rerun_reuses_or_extends_the_key_at_the_threshold check "the key is made and extended for 365 days, to the hour, within AppSync's bounds with its clock behind" \ the_key_lives_365_days_within_appsyncs_bounds -check "an expired key's replacement refuses --apply until the uploaded files that name the old key are replaced" \ +check "an expired key is extended in place when AppSync takes it, and the uploaded files stay valid" \ + an_expired_key_is_extended_in_place_when_appsync_takes_it +check "an expired key AppSync refuses is replaced, and --apply refuses until the uploaded files that name it are replaced" \ an_expired_key_needs_the_uploads_that_name_it_replaced check "a key with other contents refuses the whole upload" a_key_with_other_contents_refuses_the_upload check "snapshot and verify-unchanged: a user count is no change; a change, a removal, a foreign addition fail" \ From cd83dd95ed8c22150fc426bac934d280b2c72ea2 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 13:41:01 -0400 Subject: [PATCH 38/57] ci(auth): refuse a phase-1 --apply while phase-2 ccit-ci- resources exist A plain --apply (or --scope email-alias) is phase 1, which makes only two Lambdas again and gives only the device-alias pool its invoke permissions. Once a phase-2 pool, Lambda, role or the code sink's API exists, it now refuses before its dry run, naming what it found and --scope all; a dry run says it would refuse. --- .../IntegrationTests/infra/ci/provision-ci.sh | 62 ++++++++++++++++++- .../infra/ci/test_provision_ci.sh | 32 ++++++---- 2 files changed, 80 insertions(+), 14 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh index 2fd8fbf626..9f49eec005 100755 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh @@ -54,8 +54,8 @@ # It never updates a pool or a Lambda's code, so either is replaced: deleted by hand, after its tag check, then # made again by infra/ci/provision-ci.sh --apply --scope all. Never a plain --apply: that is phase 1, which makes # only pre-sign-up and the discard sender again, and adds invoke permissions for the device-alias pool only, while -# lambda-drift and the baseline check still pass. Do it while no CI run is going: until the rerun, the pools -# cannot use what was deleted. +# lambda-drift and the baseline check still pass; it refuses once any phase-2 ccit-ci- resource exists. Do it while +# no CI run is going: until the rerun, the pools cannot use what was deleted. # - A Lambda, after a change to lambda/triggers/triggers.mjs, lambda/custom-sender or ci/lambda/* (lambda-drift # tells a function whose deployed files are not this checkout's), or after the code sink's API is made again # (ccit-ci-extend-api-key names the API it was made for): check that it carries @@ -80,7 +80,9 @@ # --dry-run (the default) reads the account and prints every call it would make, identifiers masked; # --apply makes them, then writes the client's configuration files into a new private directory (mode 600 # files); --upload also uploads them to /auth/cognito-client-ci/: a key that exists is left alone when -# it holds the same bytes, and any other existing key refuses the whole upload. --scope picks the phase. +# it holds the same bytes, and any other existing key refuses the whole upload. --scope picks the phase; +# --apply refuses phase 1 (--scope email-alias, the default) once a phase-2 pool, Lambda, role or the code sink's +# API exists. # infra/ci/provision-ci.sh snapshot # read-only: hashes the metadata it reads of every existing resource of the kinds the script writes (snapshot.py # lists them; AppSync schemas, resolvers and keys, and S3 objects' contents, are not read) into @@ -275,6 +277,59 @@ require_recorded_region() { || die "the ccit-ci- resources are in $regions, not CCIT_CI_REGION's $CI_REGION. Set CCIT_CI_REGION to theirs." } +# The phase-2 ccit-ci- resources that exist (PHASE_2_FOUND, their names, space-separated; "" when none): the pools, +# functions and roles only --scope all makes, and the code sink's API. Read-only. It sets a variable rather than +# printing, so that a refusal in a read ends the script (as zip_for). +PHASE_2_POOL_KEYS=(default email-alias-codes mfa-req-totp-sms) +PHASE_2_ROLES=(sender-exec appsync-codes cognito-sms reset-exec key-exec identity-authenticated identity-unauthenticated) +PHASE_2_FOUND="" +find_phase_2() { + local out key suffix + PHASE_2_FOUND="" + read_all_pages out UserPools cognito-idp list-user-pools + for key in "${PHASE_2_POOL_KEYS[@]}"; do + if jq -e --arg n "$(pool_name "$key")" 'any(.UserPools[]; .Name == $n)' <<<"$out" >/dev/null; then + PHASE_2_FOUND+=" $(pool_name "$key")" + fi + done + for suffix in "${PHASE_2_FUNCTIONS[@]}"; do + if read_aws_or_missing out ResourceNotFoundException lambda get-function-configuration \ + --function-name "$(fn_name "$suffix")"; then + PHASE_2_FOUND+=" $(fn_name "$suffix")" + fi + done + for suffix in "${PHASE_2_ROLES[@]}"; do + if read_aws_or_missing out NoSuchEntity iam get-role --role-name "$(role_name "$suffix")"; then + PHASE_2_FOUND+=" $(role_name "$suffix")" + fi + done + read_aws out appsync list-graphql-apis + if jq -e --arg n "$API_NAME" 'any(.graphqlApis[]; .name == $n)' <<<"$out" >/dev/null; then + PHASE_2_FOUND+=" $API_NAME" + fi + PHASE_2_FOUND="${PHASE_2_FOUND# }" +} + +# Phase 1 (--scope email-alias, the default) once phase 2 exists: --apply refuses, and a dry run says it would. Phase 1 +# makes only pre-sign-up and the discard sender again, and gives only the device-alias pool its invoke permissions, +# so after a phase-2 resource was deleted by hand it would leave the other pools without what they name. +refuse_phase_1_over_phase_2() { + local names count found why + [[ "$SCOPE" == "email-alias" ]] || return 0 + find_phase_2 + [[ -n "$PHASE_2_FOUND" ]] || return 0 + read -r -a names <<<"$PHASE_2_FOUND" + count=${#names[@]} + names=("${names[@]:0:3}") + found="${names[*]}" + (( count <= 3 )) || found+=" and $((count - 3)) more" + why="phase 2 exists here ($found), and --scope email-alias, the default, is phase 1: it would make only \ +$(fn_name pre-sign-up) and $(fn_name discard-sender) again, and give only $(pool_name email-alias) its invoke \ +permissions. Run infra/ci/provision-ci.sh --apply --scope all instead." + [[ "$CI_MODE" != "apply" ]] || die "$why" + say "Note: --apply would refuse: $why" +} + # The account's headroom, read once; each create that needs it checks it (require_headroom), so a rerun that creates # nothing (extending the code sink's key, say) is never refused for a quota it does not use. The AppSync quota is not # readable with the CI role's permissions: CCIT_CI_APPSYNC_API_QUOTA gives a raised one (default 25, AWS's default). @@ -1742,6 +1797,7 @@ teardown() { case "$COMMAND" in provision|teardown) require_recorded_region + [[ "$COMMAND" != "provision" ]] || refuse_phase_1_over_phase_2 # --apply runs the whole dry run first, quietly, so a refusal anywhere (a ccit-ci- name without the tag, too # little quota, another region) stops it before its first change. An uploaded key with other contents is # not among them: the dry run has no file to compare it with, so --apply finds it, and refuses the whole diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh index 74ea210e92..83bced4a40 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh @@ -25,8 +25,8 @@ # rerun over phase 2 as it was first applied (the script at 80712afe4 when git has it, else skipped) plans and makes # only the alias-codes and MFA-required pools and what they need, and the key's weekly extension, updates only the # script's own resources, and -# uploads only the three new files, finding the four uploaded ones byte for byte the same; that a phase-1 rerun after -# phase 2 makes no call; that the code sink key is made and extended for 365 days, to the hour, within AppSync's +# uploads only the three new files, finding the four uploaded ones byte for byte the same; that a phase-1 --apply +# after phase 2 is refused before any change, naming --scope all, and its dry run plans nothing; that the code sink key is made and extended for 365 days, to the hour, within AppSync's # bounds (which the fake checks) though AppSync's clock is behind; that a rerun reuses a key that lives more than # KEY_REUSE_DAYS (120), the one the frozen script made too, extends one that lives fewer in place, and one that has # expired too, while AppSync keeps it, and makes a new one once AppSync has deleted it or refuses the extension @@ -435,13 +435,23 @@ a_second_apply_makes_no_call_and_uploads_nothing() { (( STATUS == 0 )) && [[ "$OUT" == *"Done: 0 calls made."* ]] && ! calls | grep -q 'put-object' && no_identifiers } -# The default scope after phase 2 changes nothing: the triggers' role keeps the phase-2 triggers' log groups. -a_phase_1_rerun_after_phase_2_makes_no_call() { - run --apply --upload - if ! { (( STATUS == 0 )) && [[ "$OUT" == *"Done: 0 calls made."* ]] && ! calls | grep -q 'put-object'; }; then - echo "$OUT" | sed -nE 's/^ \+ //p' - return 1 - fi +# Phase 1 after phase 2: --apply refuses, the default scope and --scope email-alias alike, before its dry run, with +# read calls only and nothing changed, naming the phase-2 resources it found and --scope all. A dry run says --apply +# would refuse, and plans nothing: the triggers' role keeps the phase-2 triggers' log groups. +PHASE_1_REFUSAL="Refusing: phase 2 exists here (ccit-ci-default ccit-ci-email-alias-codes ccit-ci-mfa-req-totp-sms and 15 more), and --scope email-alias, the default, is phase 1: it would make only ccit-ci-pre-sign-up and ccit-ci-discard-sender again, and give only ccit-ci-email-alias its invoke permissions. Run infra/ci/provision-ci.sh --apply --scope all instead." +a_phase_1_apply_after_phase_2_is_refused() { + local before args + before=$(jq -S . "$FAKE_STATE") + for args in "--apply --upload" "--apply --scope email-alias"; do + # shellcheck disable=SC2086 # split on purpose: each case is a list of arguments + run $args + (( STATUS == 1 )) && only_reads && no_identifiers && [[ "$(jq -S . "$FAKE_STATE")" == "$before" ]] \ + && [[ "$OUT" == *"$PHASE_1_REFUSAL"* && "$OUT" != *"Checking with a dry run first"* ]] \ + || { echo "$args: $OUT" | tail -5; return 1; } + done + run + (( STATUS == 0 )) && only_reads && [[ "$OUT" == *"Note: --apply would refuse: phase 2 exists here"* ]] \ + && [[ "$OUT" == *"Planned: 0 calls."* ]] || { echo "$OUT" | tail -5; return 1; } no_identifiers && jq -e '.roles["ccit-ci-trigger-exec"].inline["ccit-ci-trigger-exec-policy"].Statement[0].Resource | any(contains(":log-group:/aws/lambda/ccit-ci-verify-auth-challenge:"))' "$FAKE_STATE" >/dev/null } @@ -1238,8 +1248,8 @@ check "phase 1 --apply --upload makes only the Lite device-alias pool, its trigg check "phase 2 adds the default, alias-codes and MFA-required pools, narrowed, keeps phase 1, and uploads only the six new files" \ phase_2_adds_the_default_pool_and_keeps_phase_1 check "a second --apply --upload makes no call and uploads nothing" a_second_apply_makes_no_call_and_uploads_nothing -check "a phase-1 --apply --upload after phase 2 makes no call: the triggers' role keeps every trigger's logs" \ - a_phase_1_rerun_after_phase_2_makes_no_call +check "a phase-1 --apply after phase 2 is refused, naming --scope all; its dry run plans nothing" \ + a_phase_1_apply_after_phase_2_is_refused check "the code sink's API key near its expiry is extended in place, and the uploaded files stay valid" \ an_api_key_near_its_expiry_is_extended_in_place check "a rerun reuses the key beyond the reuse threshold, extends it within it and once expired, and replaces a deleted one" \ From b60429dce4a45f1b7e5fe4e1a83f0c4151cf2cf1 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:06:24 -0400 Subject: [PATCH 39/57] ci(auth): add a guarded provision-ci.sh replace command replace automates the pool replacement recipe: after the tag check of each, through mutate, it deletes the pool's uploaded file, for ccit-ci-default also the rotation file, the identity pool and the reset Lambda, then the pool, and reruns --apply --scope all --upload (phase 1 when only phase 1 exists). It is a dry run by default, which also checks the rerun's dry run, so --apply deletes nothing when either would refuse. It takes only the script's pools, and refuses while gh shows a queued or running client CI run, warning when gh cannot tell. --- .../IntegrationTests/infra/ci/provision-ci.sh | 172 ++++++++++++++++- .../infra/ci/test_provision_ci.sh | 176 +++++++++++++++++- 2 files changed, 337 insertions(+), 11 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh index 9f49eec005..a0e694ba1d 100755 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh @@ -61,8 +61,9 @@ # (ccit-ci-extend-api-key names the API it was made for): check that it carries # purpose=amplify-cognito-client-integ, delete it, and run --apply --scope all, which makes it again with every # pool's invoke permissions. -# - A pool, after a change to its template: --upload never overwrites a file, so the uploaded files that name the -# pool go too, or CI goes on reading the deleted pool. Check that the pool and each of those files carries +# - A pool, after a change to its template (infra/ci/provision-ci.sh replace does all of this, below): +# --upload never overwrites a file, so the uploaded files that name the pool go too, or CI goes on reading the +# deleted pool. Check that the pool and each of those files carries # purpose=amplify-cognito-client-integ (the files: aws s3api get-object-tagging), then delete the pool and its # auth/cognito-client-ci/ccit-ci--amplify_outputs.json. For ccit-ci-default, also delete # ccit-ci-rotation-amplify_outputs.json, the identity pool ccit_ci_default (it federates the old pool's client, @@ -95,6 +96,12 @@ # infra/ci/provision-ci.sh teardown [--dry-run | --apply] # deletes only the resources above, each after its tag check (the KMS key is scheduled for deletion in # 7 days); a dry run by default +# infra/ci/provision-ci.sh replace [--dry-run | --apply] +# replaces one of the pools above (by its name, or the part after ccit-ci-) as the recipe above does: after the +# tag check of each, deletes the pool and its uploaded file, for ccit-ci-default also the rotation file, the +# identity pool and ccit-ci-new-password-reset, then reruns --apply --scope all --upload. A dry run by default, +# which checks the rerun's dry run too. It refuses any other name, and refuses while gh shows a CI run that +# reads the pools (it warns when gh cannot tell) # infra/ci/provision-ci.sh lambda-drift [--record [--accept-deployed]] # read-only: downloads each ccit-ci- Lambda's deployed zip (GetFunction's presigned URL) and compares its # files, by path and content hash, with the zip built from this checkout, or with the files @@ -126,6 +133,10 @@ POSITIONAL=() RECORD=0 ACCEPT_DEPLOYED="" APPLY_GIVEN=0 +SCOPE_GIVEN=0 +REPLACE_KEY="" +# Every pool the script makes, by its key (the pool is ccit-ci-). +ALL_POOL_KEYS=(email-alias default email-alias-codes mfa-req-totp-sms) usage() { sed -n '/^# infra\/ci\/provision-ci.sh/,/^# Environment/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' >&2 @@ -137,11 +148,11 @@ while (( $# > 0 )); do --dry-run) CI_MODE="plan" ;; --apply) CI_MODE="apply"; APPLY_GIVEN=1 ;; --upload) UPLOAD=1 ;; - --scope) [[ $# -ge 2 ]] || usage; SCOPE="$2"; shift ;; + --scope) [[ $# -ge 2 ]] || usage; SCOPE="$2"; SCOPE_GIVEN=1; shift ;; --allow-foreign-additions) ALLOW_FOREIGN=1 ;; --record) RECORD=1 ;; --accept-deployed) ACCEPT_DEPLOYED=1 ;; - snapshot|verify-unchanged|teardown|lambda-drift) COMMAND="$1" ;; + snapshot|verify-unchanged|teardown|lambda-drift|replace) COMMAND="$1" ;; -h|--help) usage ;; -*) usage ;; *) POSITIONAL+=("$1") ;; @@ -157,6 +168,19 @@ if [[ "$COMMAND" == "verify-unchanged" ]]; then (( ALLOW_FOREIGN )) && args+=(--allow-foreign-additions) exec python3 "$CI_DIR/snapshot.py" "${args[@]}" fi +# replace takes one pool, by its key or its name, and only one of the script's: any other name is refused before any +# AWS call. It always reruns with --upload, and picks the rerun's scope itself. +if [[ "$COMMAND" == "replace" ]]; then + (( ${#POSITIONAL[@]} == 1 && SCOPE_GIVEN == 0 )) || usage + for key in "${ALL_POOL_KEYS[@]}"; do + if [[ "${POSITIONAL[0]}" == "$key" || "${POSITIONAL[0]}" == "ccit-ci-$key" ]]; then + REPLACE_KEY="$key" + fi + done + [[ -n "$REPLACE_KEY" ]] \ + || die "replace takes one of the script's pools: $(printf 'ccit-ci-%s\n' "${ALL_POOL_KEYS[@]}" | paste -sd' ' -)" + POSITIONAL=() +fi (( ${#POSITIONAL[@]} == 0 )) || usage if [[ "$COMMAND" != "provision" && "$UPLOAD" == 1 ]]; then usage @@ -189,9 +213,15 @@ require_caller # --- Names ------------------------------------------------------------------------------------------------ -POOL_KEYS=(email-alias) -[[ "$SCOPE" == "all" ]] && POOL_KEYS=(email-alias default email-alias-codes mfa-req-totp-sms) -ALL_POOL_KEYS=(email-alias default email-alias-codes mfa-req-totp-sms) +# Sets SCOPE, and POOL_KEYS, the pools in it. +set_scope() { + SCOPE="$1" + POOL_KEYS=(email-alias) + if [[ "$SCOPE" == "all" ]]; then + POOL_KEYS=("${ALL_POOL_KEYS[@]}") + fi +} +set_scope "$SCOPE" # The pools whose SmsConfiguration names the SNS caller role (the templates with SMS MFA), which its trust names. SMS_POOL_KEYS=(default mfa-req-totp-sms) # The pools whose codes the custom sender decrypts: the ones it is the sender of. @@ -1508,6 +1538,132 @@ note_key_expiry() { fi } +# --- replace ----------------------------------------------------------------------------------------- + +# Refuses while a CI run that reads the ccit-ci- pools is going, as far as gh can tell cheaply (GitHub's API, reads +# only): a queued or running run of integ_test_auth_client.yml, or a job of integ_test.yml's, queued or running, that +# runs the Cognito client's tests (its name holds "Cognito Client" or "CognitoClient"). Without gh, or when gh cannot +# list the runs (not logged in, say), it warns and goes on. CCIT_CI_GITHUB_REPO names another repository. +CI_GITHUB_REPO="${CCIT_CI_GITHUB_REPO:-aws-amplify/amplify-swift}" +require_no_ci_run() { + local status out id running="" unknown="Warning: could not tell whether a CI run is going, which the deletions would break: check that none is before --apply." + if ! command -v gh >/dev/null; then + say "$unknown (gh is not on PATH.)" + return 0 + fi + for status in in_progress queued; do + if ! out=$(gh run list -R "$CI_GITHUB_REPO" -w integ_test_auth_client.yml -s "$status" -L 20 --json databaseId \ + 2>/dev/null) || ! jq -e 'type == "array"' <<<"$out" >/dev/null 2>&1; then + say "$unknown" + return 0 + fi + [[ "$(jq length <<<"$out")" == 0 ]] || running="integ_test_auth_client.yml" + if ! out=$(gh run list -R "$CI_GITHUB_REPO" -w integ_test.yml -s "$status" -L 50 --json databaseId 2>/dev/null) \ + || ! jq -e 'type == "array"' <<<"$out" >/dev/null 2>&1; then + say "$unknown" + return 0 + fi + for id in $(jq -r '.[].databaseId' <<<"$out"); do + if ! out=$(gh run view "$id" -R "$CI_GITHUB_REPO" --json jobs 2>/dev/null) \ + || ! jq -e '.jobs | type == "array"' <<<"$out" >/dev/null 2>&1; then + say "$unknown" + return 0 + fi + if jq -e 'any(.jobs[]; (.name | test("Cognito ?Client")) and .status != "completed")' <<<"$out" >/dev/null; then + running="integ_test.yml's Cognito client jobs" + fi + done + done + [[ -z "$running" ]] || die "a CI run of $running is going, and it reads the ccit-ci- pools: replace one once it is done." +} + +# replace [--apply]: replaces pool ccit-ci-, as after a change to its template (a rerun never +# updates a pool). After the tag check of each, it deletes, through `mutate`: the pool's uploaded +# auth/cognito-client-ci/ccit-ci--amplify_outputs.json (--upload never overwrites a file); for the default +# pool also ccit-ci-rotation-amplify_outputs.json, the identity pool (it federates the old pool's client, so a rerun +# would refuse) and the Lambda ccit-ci-new-password-reset (it names the old pool, and makes the users only when it is +# made); and the pool itself, with its clients and users. Then it reruns --apply --scope all --upload, which makes +# them again and uploads the new files; the other uploaded files stay as they are. Whatever is already gone is +# skipped, so a replace that stopped part-way can be run again. The rerun's scope is phase 1 instead when the pool is +# the device-alias pool and no phase-2 resource exists, so that a replace never adds phase 2. +# A dry run (the default) plans the deletions, and checks the rerun with its dry run, which must refuse nothing: +# --apply runs both first, so nothing is deleted when either would refuse. +replace() { + local key="$REPLACE_KEY" name file object out id ids rerun_scope=all files=() + name=$(pool_name "$key") + if [[ "$CI_MODE" == "apply" ]]; then + say "Replacing $name: deleting it and what names it, each after its tag check, then rerunning." + else + say "Replace dry run ($name): nothing is deleted. '+' is a call replace --apply would make, '=' one it skips." + fi + require_no_ci_run + if [[ "$key" == "email-alias" ]]; then + find_phase_2 + [[ -n "$PHASE_2_FOUND" ]] || rerun_scope=email-alias + fi + if [[ "$CI_MODE" != "apply" ]]; then + say "The rerun (--apply --scope $rerun_scope --upload), checked with its dry run:" + rerun_provision "$rerun_scope" plan >/dev/null \ + || die "the rerun's dry run refused (above); nothing was deleted." + say " it refuses nothing." + fi + say "Deleting:" + files=("$name-amplify_outputs.json") + [[ "$key" != "default" ]] || files+=("$(pool_name rotation)-amplify_outputs.json") + for file in "${files[@]}"; do + object="$S3_FOLDER_KEY/$file" + if read_aws_or_missing out "404 NoSuchKey NotFound" s3api head-object --bucket "$CI_BUCKET" --key "$object"; then + require_ours s3-object "$object" "$object" + mutate "$object" "delete auth/$CI_S3_FOLDER/$file" -- s3api delete-object --bucket "$CI_BUCKET" --key "$object" + else + say " = auth/$CI_S3_FOLDER/$file is not uploaded" + fi + done + if [[ "$key" == "default" ]]; then + read_all_pages out IdentityPools cognito-identity list-identity-pools + ids=$(jq -r --arg n "$CI_IDENTITY_POOL" '.IdentityPools[] | select(.IdentityPoolName == $n) | .IdentityPoolId' <<<"$out") + [[ -n "$ids" ]] || say " = identity pool $CI_IDENTITY_POOL does not exist" + for id in $ids; do + require_ours identity-pool "$id" "$CI_IDENTITY_POOL" + mutate "$CI_IDENTITY_POOL" "delete identity pool $CI_IDENTITY_POOL" -- cognito-identity delete-identity-pool \ + --identity-pool-id "$id" + done + if read_aws_or_missing out ResourceNotFoundException lambda get-function-configuration \ + --function-name "$(fn_name new-password-reset)"; then + require_ours lambda "$(fn_name new-password-reset)" "$(fn_name new-password-reset)" + mutate "$(fn_name new-password-reset)" "delete Lambda $(fn_name new-password-reset)" -- lambda delete-function \ + --function-name "$(fn_name new-password-reset)" + else + say " = Lambda $(fn_name new-password-reset) does not exist" + fi + fi + read_all_pages out UserPools cognito-idp list-user-pools + ids=$(jq -r --arg n "$name" '.UserPools[] | select(.Name == $n) | .Id' <<<"$out") + (( $(printf '%s' "$ids" | grep -c .) <= 1 )) || die "more than one user pool is named $name." + if [[ -n "$ids" ]]; then + require_ours user-pool "$ids" "$name" + mutate "$name" "delete user pool $name (its clients and users with it)" -- cognito-idp delete-user-pool \ + --user-pool-id "$ids" + else + say " = user pool $name does not exist" + fi + if [[ "$CI_MODE" != "apply" ]]; then + say "Planned: $CI_MUTATIONS deletions, then the rerun. Nothing was changed." + return 0 + fi + say "Deleted: $CI_MUTATIONS calls made. The rerun, --apply --scope $rerun_scope --upload:" + rerun_provision "$rerun_scope" plan >/dev/null \ + || die "the rerun's dry run refused (above), after the deletions: fix that, then run infra/ci/provision-ci.sh --apply --scope $rerun_scope --upload." + rerun_provision "$rerun_scope" apply +} + +# replace's rerun: provision, with --upload, in scope $1 and mode $2 (plan or apply), its calls counted from 0, in a +# subshell, so that nothing it reads or sets reaches replace's own state. Its status is the subshell's. +rerun_provision() { + local CI_MODE="$2" CI_MUTATIONS=0 + ( set_scope "$1"; UPLOAD=1; provision ) +} + # --- Lambda drift (lambda_code.py) ------------------------------------------------------------------------ # lambda-drift [--record [--accept-deployed]]: read-only. Reads each ccit-ci- Lambda (its name and purpose tag checked @@ -1795,7 +1951,7 @@ teardown() { } case "$COMMAND" in - provision|teardown) + provision|teardown|replace) require_recorded_region [[ "$COMMAND" != "provision" ]] || refuse_phase_1_over_phase_2 # --apply runs the whole dry run first, quietly, so a refusal anywhere (a ccit-ci- name without the tag, too diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh index 83bced4a40..ce973fef9a 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh @@ -41,7 +41,9 @@ # --apply; that teardown is a dry run by default, deletes only the ccit-ci- resources with # --apply, refuses an untagged one, and schedules the key's deletion before removing its alias; that a run in another # region is refused (lambda-drift too, which there would find nothing to compare); that a pool made again gets its -# Lambdas' invoke permissions replaced; and that the library's +# Lambdas' invoke permissions replaced; that replace takes only the script's pools, is a dry run by default, refuses +# an untagged pool and, through a fake gh, a CI run that reads the pools, and with --apply deletes the pool and what +# names it, then reruns --scope all (phase 1 where phase 2 does not exist); and that the library's # guards refuse a name that is not ccit-ci- and a mutating call in a dry run. set -euo pipefail CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -70,6 +72,29 @@ else fi EOF chmod +x "$BIN/date" +# A fake gh, for replace's check that no CI run is going: FAKE_GH unset, no run is going; "dispatched", a run of +# integ_test_auth_client.yml is; "called", a queued run of integ_test.yml has a Cognito client job queued; "other", +# a running run of integ_test.yml has only other jobs going; "broken", gh cannot list the runs. +cat > "$BIN/gh" <<'EOF' +#!/usr/bin/env bash +[[ "${FAKE_GH:-}" != broken ]] || { echo "To get started with GitHub CLI, please run: gh auth login" >&2; exit 4; } +if [[ "$1 $2" == "run list" ]]; then + case "${FAKE_GH:-}:$*" in + dispatched:*"-w integ_test_auth_client.yml -s in_progress"*) echo '[{"databaseId": 11}]' ;; + called:*"-w integ_test.yml -s queued"*) echo '[{"databaseId": 12}]' ;; + other:*"-w integ_test.yml -s in_progress"*) echo '[{"databaseId": 13}]' ;; + *) echo '[]' ;; + esac +elif [[ "$1 $2 $3" == "run view 12" ]]; then + echo '{"jobs": [{"name": "Auth Cognito Client Integration Tests / iOS Tests | CognitoClientIntegrationTests", "status": "queued"}]}' +elif [[ "$1 $2 $3" == "run view 13" ]]; then + echo '{"jobs": [{"name": "Auth Integration Tests / iOS Tests | AuthIntegrationTests", "status": "in_progress"}, + {"name": "Auth Cognito Client Integration Tests / iOS Tests | CognitoClientIntegrationTests", "status": "completed"}]}' +else + exit 1 +fi +EOF +chmod +x "$BIN/gh" export PATH="$BIN:$PATH" FAKE_STATE="$WORK/state.json" FAKE_LOG="$WORK/calls.log" TMPDIR="$WORK/tmp" export AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null CCIT_CI_DISCOVERY_DIR="$WORK/disc" export COGNITO_CLIENT_INTEG_CI_CONFIG_URL="s3://fake-ci-bucket.example/v2/testconfiguration" @@ -614,10 +639,14 @@ an_expired_key_needs_the_uploads_that_name_it_replaced() { done } +# An uploaded key with other contents refuses the whole upload. The file is put back after, so later uploads find +# every file the same. a_key_with_other_contents_refuses_the_upload() { - jq '.s3["v2/testconfiguration/auth/cognito-client-ci/ccit-ci-default-credentials.json"].body = "{}"' "$FAKE_STATE" \ - > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" + local key="v2/testconfiguration/auth/cognito-client-ci/ccit-ci-default-credentials.json" body + body=$(jq -c --arg k "$key" '.s3[$k].body' "$FAKE_STATE") + jq --arg k "$key" '.s3[$k].body = "{}"' "$FAKE_STATE" > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" run --apply --scope all --upload + jq --arg k "$key" --argjson b "$body" '.s3[$k].body = $b' "$FAKE_STATE" > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" (( STATUS == 1 )) && [[ "$OUT" == *"nothing is ever overwritten"* ]] && ! calls | grep -q 'put-object' \ && only_reads && no_identifiers } @@ -914,6 +943,138 @@ snapshot_ignores_the_runtime_version() { && [[ "$out" != *RuntimeVersionConfig* ]] || { echo "$out"; return 1; } } +# replace takes one of the script's pools, by key or name, and nothing else: any other name is refused before any AWS +# call, and so are no pool, two, --upload and --scope. +replace_takes_only_the_scripts_pools() { + local args + for args in "replace amplify-plugin-default" "replace ccit-ci-rotation" "replace ccit-ci-default-x"; do + # shellcheck disable=SC2086 # split on purpose: each case is a list of arguments + run $args + (( STATUS == 1 )) && [[ -z "$(calls)" ]] \ + && [[ "$OUT" == *"replace takes one of the script's pools: ccit-ci-email-alias ccit-ci-default ccit-ci-email-alias-codes ccit-ci-mfa-req-totp-sms"* ]] \ + || { echo "$args: $OUT"; return 1; } + done + for args in "replace" "replace default email-alias" "replace default --upload" "replace default --scope all"; do + # shellcheck disable=SC2086 + run $args + (( STATUS == 2 )) && [[ -z "$(calls)" ]] || { echo "$args: $OUT"; return 1; } + done +} + +# What replace plans for the default pool: its two uploaded files, the identity pool and the reset Lambda, then the +# pool. +REPLACE_DEFAULT_PLAN="delete auth/cognito-client-ci/ccit-ci-default-amplify_outputs.json +delete auth/cognito-client-ci/ccit-ci-rotation-amplify_outputs.json +delete identity pool ccit_ci_default +delete Lambda ccit-ci-new-password-reset +delete user pool ccit-ci-default (its clients and users with it)" + +# replace is a dry run by default: read calls only, nothing changed, the deletions planned and the rerun's dry run +# checked. It refuses, before any change, while a CI run that reads the pools is going (a dispatched run, or a +# Cognito client job of integ_test.yml's), but not for other jobs; when gh cannot tell, it warns and goes on. +replace_is_a_dry_run_and_refuses_while_ci_runs() { + local before + before=$(jq -S . "$FAKE_STATE") + run replace default + (( STATUS == 0 )) && only_reads && no_identifiers && plus_lines_are_ours && [[ "$(jq -S . "$FAKE_STATE")" == "$before" ]] \ + && [[ "$(sed -nE 's/^ \+ //p' <<<"$OUT")" == "$REPLACE_DEFAULT_PLAN" ]] \ + && [[ "$OUT" == *"The rerun (--apply --scope all --upload), checked with its dry run:"$'\n'" it refuses nothing."* ]] \ + && [[ "$OUT" == *"Planned: 5 deletions, then the rerun. Nothing was changed."* && "$OUT" != *Warning* ]] \ + || { echo "$OUT"; return 1; } + FAKE_GH=dispatched run replace default --apply + (( STATUS == 1 )) && only_reads && [[ "$(jq -S . "$FAKE_STATE")" == "$before" ]] \ + && [[ "$OUT" == *"Refusing: a CI run of integ_test_auth_client.yml is going, and it reads the ccit-ci- pools"* ]] \ + || { echo "$OUT" | tail -5; return 1; } + FAKE_GH=called run replace email-alias --apply + (( STATUS == 1 )) && only_reads && [[ "$(jq -S . "$FAKE_STATE")" == "$before" ]] \ + && [[ "$OUT" == *"Refusing: a CI run of integ_test.yml's Cognito client jobs is going"* ]] || { echo "$OUT" | tail -5; return 1; } + FAKE_GH=other run replace default + (( STATUS == 0 )) && [[ "$OUT" != *Warning* && "$OUT" != *Refusing* ]] || { echo "$OUT" | tail -5; return 1; } + FAKE_GH=broken run replace default + (( STATUS == 0 )) && only_reads \ + && [[ "$OUT" == *"Warning: could not tell whether a CI run is going, which the deletions would break"* ]] \ + || { echo "$OUT" | tail -5; return 1; } +} + +# replace --apply refuses a pool without the purpose tag before any change. +replace_refuses_an_untagged_pool() { + local before + jq '(.pools[] | select(.UserPool.Name == "ccit-ci-email-alias-codes") | .UserPool.UserPoolTags) = {}' "$FAKE_STATE" \ + > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" + before=$(jq -S . "$FAKE_STATE") + run replace ccit-ci-email-alias-codes --apply + local status=$STATUS out=$OUT after + after=$(jq -S . "$FAKE_STATE") + jq '(.pools[] | select(.UserPool.Name == "ccit-ci-email-alias-codes") | .UserPool.UserPoolTags) = {purpose: "amplify-cognito-client-integ"}' \ + "$FAKE_STATE" > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" + OUT=$out + (( status == 1 )) && only_reads && [[ "$after" == "$before" ]] \ + && [[ "$out" == *"user-pool ccit-ci-email-alias-codes exists without purpose=amplify-cognito-client-integ"* ]] \ + && [[ "$out" == *"the dry run refused"* ]] && no_identifiers || { echo "$out" | tail -5; return 1; } +} + +# The uploaded file $1 (under auth/cognito-client-ci/), as the fake holds it. +uploaded() { + jq -r --arg k "v2/testconfiguration/auth/cognito-client-ci/$1" '.s3[$k].body' "$FAKE_STATE" +} + +# replace default --apply: deletes the pool, its two files, the identity pool and the reset Lambda, then the rerun +# makes them again, with the new pool in the reset Lambda, the identity pool, the invoke permissions and the SMS +# role's trust, uploads the two new files, and leaves the other uploaded files and the plugin's resources as they +# were. A second rerun makes no call. +replace_default_makes_it_again() { + local old_pool old_identity other before new_pool new_identity + old_pool=$(jq -r '.pools[] | select(.UserPool.Name == "ccit-ci-default") | .UserPool.Id' "$FAKE_STATE") + old_identity=$(jq -r '.identity_pools[] | select(.IdentityPoolName == "ccit_ci_default") | .IdentityPoolId' "$FAKE_STATE") + other=$(jq -c '.s3 | with_entries(select(.key | test("ccit-ci-(default|rotation)-amplify") | not))' "$FAKE_STATE") + before=$(plugin_part) + run replace default --apply + (( STATUS == 0 )) && no_identifiers && [[ "$(plugin_part)" == "$before" ]] \ + && [[ "$(sed -nE 's/^ \+ //p' <<<"$OUT" | head -5)" == "$REPLACE_DEFAULT_PLAN" ]] \ + && [[ "$OUT" == *"Deleted: 5 calls made. The rerun, --apply --scope all --upload:"* ]] \ + && [[ "$OUT" == *"+ create user pool ccit-ci-default"* && "$OUT" == *"new-password users: created 12"* ]] \ + && [[ "$(calls | grep -c 's3api put-object')" == 2 ]] || { echo "$OUT" | tail -12; return 1; } + new_pool=$(jq -r '.pools[] | select(.UserPool.Name == "ccit-ci-default") | .UserPool.Id' "$FAKE_STATE") + new_identity=$(jq -r '.identity_pools[] | select(.IdentityPoolName == "ccit_ci_default") | .IdentityPoolId' "$FAKE_STATE") + [[ -n "$new_pool" && "$new_pool" != "$old_pool" && -n "$new_identity" && "$new_identity" != "$old_identity" ]] \ + || { echo "not made again"; return 1; } + [[ "$(uploaded ccit-ci-default-amplify_outputs.json | jq -r '.auth.user_pool_id + " " + .auth.identity_pool_id')" == "$new_pool $new_identity" ]] \ + && [[ "$(uploaded ccit-ci-rotation-amplify_outputs.json | jq -r .auth.user_pool_id)" == "$new_pool" ]] \ + && [[ "$(jq -c '.s3 | with_entries(select(.key | test("ccit-ci-(default|rotation)-amplify") | not))' "$FAKE_STATE")" == "$other" ]] \ + || { echo "the uploaded files"; return 1; } + jq -e --arg p "$new_pool" '(.pools[$p].UserPool.Arn) as $a + | (.lambdas["ccit-ci-new-password-reset"].config.Environment.Variables.POOL_ID == $p) + and ([.lambdas[] | .policy[] | select(.Sid == "cognito-default") | .Condition.ArnLike["AWS:SourceArn"]] + | length > 0 and all(. == $a)) + and ([.identity_pools[] | select(.IdentityPoolName == "ccit_ci_default") | .CognitoIdentityProviders[0].ProviderName + | endswith($p)] == [true]) + and (.roles["ccit-ci-cognito-sms"].Role.AssumeRolePolicyDocument.Statement[0].Condition.ArnEquals["aws:SourceArn"] + | index($a) != null)' "$FAKE_STATE" >/dev/null || { echo "what names the pool"; return 1; } + run --apply --scope all --upload + (( STATUS == 0 )) && [[ "$OUT" == *"Done: 0 calls made."* ]] || { echo "$OUT" | sed -nE 's/^ \+ //p'; return 1; } +} + +# replace of the device-alias pool where only phase 1 exists reruns phase 1, so it never adds phase 2. The state is +# put back after. +replace_over_phase_1_reruns_phase_1() { + local saved="$WORK/replace-phase-1-state.json" old status out made=0 + cp "$FAKE_STATE" "$saved" + fresh_state + run --apply --upload + (( STATUS == 0 )) || { echo "$OUT" | tail -5; cp "$saved" "$FAKE_STATE"; return 1; } + old=$(jq -r '.pools[] | select(.UserPool.Name == "ccit-ci-email-alias") | .UserPool.Id' "$FAKE_STATE") + run replace email-alias --apply + status=$STATUS out=$OUT + jq -e --arg o "$old" '([.pools[] | .UserPool.Name] | sort == ["amplify-plugin-default", "ccit-ci-email-alias"]) + and ([.pools[] | select(.UserPool.Name == "ccit-ci-email-alias") | .UserPool.Id] != [$o]) + and ((.apis // {}) == {}) and ([.roles | keys[]] | sort == ["ccit-ci-trigger-exec", "plugin-role"])' \ + "$FAKE_STATE" >/dev/null && [[ "$(uploaded ccit-ci-email-alias-amplify_outputs.json | jq -r .auth.user_pool_id)" != "$old" ]] \ + || made=1 + cp "$saved" "$FAKE_STATE" + (( status == 0 && made == 0 )) && [[ "$out" == *"The rerun, --apply --scope email-alias --upload:"* ]] \ + || { echo "$out" | tail -8; return 1; } +} + teardown_is_a_dry_run_by_default() { local before before=$(jq -S . "$FAKE_STATE") @@ -1269,6 +1430,15 @@ check "snapshot: a Lambda's RuntimeVersionConfig, which Lambda moves by itself, snapshot_ignores_the_runtime_version check "the Cognito lists are read to their last page: a ccit-ci- pool on page 2 is found, snapshotted, torn down" \ lists_are_read_to_their_last_page +check "replace takes only the script's pools, refusing any other name before any AWS call" \ + replace_takes_only_the_scripts_pools +check "replace is a dry run by default, and refuses while a CI run that reads the pools is going" \ + replace_is_a_dry_run_and_refuses_while_ci_runs +check "replace --apply refuses a pool without the tag, before any change" replace_refuses_an_untagged_pool +check "replace default --apply deletes the pool and what names it, then the rerun makes them again" \ + replace_default_makes_it_again +check "replace of the device-alias pool over phase 1 alone reruns phase 1, adding nothing of phase 2" \ + replace_over_phase_1_reruns_phase_1 check "teardown is a dry run by default and names only ccit-ci- resources" teardown_is_a_dry_run_by_default check "teardown --apply deletes only the ccit-ci- resources it made" teardown_deletes_only_ours if frozen_script; then From cdfbb31c765b96d7de0de013632ff7b7bc6e9737 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:19:07 -0400 Subject: [PATCH 40/57] test(auth): check the SNS caller role's trust in the fake AWS, without git history The fake now refuses create-user-pool and set-user-pool-mfa-config for a pool whose SnsCallerArn role's trust does not admit it (Cognito's InvalidSmsRoleTrustRelationshipException), and a new test deletes the MFA-required pool after phase 2 and reruns, so the trust's opening while an SMS pool is made is checked on every clone, not only by the frozen-commit rerun. The MFA-required pool's check reads each Lambda's invoke statement on its own. --- .../IntegrationTests/infra/ci/fake_aws.py | 43 ++++++++++ .../infra/ci/test_provision_ci.sh | 78 ++++++++++++++++++- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/fake_aws.py b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/fake_aws.py index a0f7314f09..c155629320 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/fake_aws.py +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/fake_aws.py @@ -12,6 +12,7 @@ import base64 import fcntl +import fnmatch import hashlib import json import os @@ -102,6 +103,41 @@ def page(o, field, items): return result +def as_list(value): + return value if isinstance(value, list) else [] if value is None else [value] + + +def require_sms_trust(state, sms, pool_arn): + """As Cognito, which assumes a pool's SMS role (SmsConfiguration.SnsCallerArn) when the pool is made or its SMS + MFA set: refuses unless one of the role's trust statements lets cognito-idp.amazonaws.com assume it for this pool, + with the pool's external id. A Condition the fake does not know refuses too.""" + role = state.get("roles", {}).get(sms["SnsCallerArn"].rsplit("/", 1)[-1]) + statements = as_list(role["Role"]["AssumeRolePolicyDocument"].get("Statement")) if role else [] + for statement in statements: + if statement.get("Effect") != "Allow" or "sts:AssumeRole" not in as_list(statement.get("Action")): + continue + if "cognito-idp.amazonaws.com" not in as_list((statement.get("Principal") or {}).get("Service")): + continue + condition = statement.get("Condition", {}) + if set(condition) - {"StringEquals", "ArnEquals", "ArnLike"}: + continue + equals = condition.get("StringEquals", {}) + if set(equals) - {"sts:ExternalId", "aws:SourceAccount"}: + continue + if "sts:ExternalId" in equals and sms.get("ExternalId") not in as_list(equals["sts:ExternalId"]): + continue + if "aws:SourceAccount" in equals and state["account"] not in as_list(equals["aws:SourceAccount"]): + continue + if "ArnEquals" in condition and pool_arn not in as_list(condition["ArnEquals"].get("aws:SourceArn")): + continue + if "ArnLike" in condition and not any(fnmatch.fnmatchcase(pool_arn, pattern) + for pattern in as_list(condition["ArnLike"].get("aws:SourceArn"))): + continue + return + raise Fail("InvalidSmsRoleTrustRelationshipException", + "Role does not have a trust relationship allowing Cognito to assume the role.") + + def handle(state, service, verb, o, data): acct, region = account(state), "us-east-1" s = state @@ -485,6 +521,9 @@ def handle(state, service, verb, o, data): for p in pools.values()]) if verb == "create-user-pool": pool_id = f"{region}_Fake{new_id(s, 'pool', 6)}" + if data.get("SmsConfiguration"): + arn = f"arn:aws:cognito-idp:{region}:{acct}:userpool/{pool_id}" + require_sms_trust(s, data["SmsConfiguration"], arn) pool = {k: v for k, v in data.items() if k != "PoolName"} pool.update({"Id": pool_id, "Name": data["PoolName"], "EstimatedNumberOfUsers": 0, "LastModifiedDate": "2026-10-05T00:00:00Z", "CreationDate": "2026-10-05T00:00:00Z", @@ -500,6 +539,10 @@ def handle(state, service, verb, o, data): if verb == "get-user-pool-mfa-config": return pool["mfa"] if verb == "set-user-pool-mfa-config": + sms = (data.get("SmsMfaConfiguration") or {}).get("SmsConfiguration") + if sms: + arn = pool["UserPool"].get("Arn", f"arn:aws:cognito-idp:{region}:{acct}:userpool/{pool_id}") + require_sms_trust(s, sms, arn) pool["mfa"] = {k: v for k, v in data.items() if k != "UserPoolId"} return pool["mfa"] if verb == "list-user-pool-clients": diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh index ce973fef9a..7e95141a7c 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh @@ -43,7 +43,9 @@ # region is refused (lambda-drift too, which there would find nothing to compare); that a pool made again gets its # Lambdas' invoke permissions replaced; that replace takes only the script's pools, is a dry run by default, refuses # an untagged pool and, through a fake gh, a CI run that reads the pools, and with --apply deletes the pool and what -# names it, then reruns --scope all (phase 1 where phase 2 does not exist); and that the library's +# names it, then reruns --scope all (phase 1 where phase 2 does not exist); that the fake, as Cognito, makes an SMS +# pool only when the SNS caller role's trust admits it, so that an SMS pool made again (no git history needed) shows +# the trust opened while it is made and narrowed after; and that the library's # guards refuse a name that is not ccit-ci- and a mutating call in a dry run. set -euo pipefail CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -72,6 +74,13 @@ else fi EOF chmod +x "$BIN/date" +# A `sleep` that returns at once when FAKE_NO_SLEEP is set, so that a run retrying a refusal it waits out (as a role +# Cognito cannot assume yet) fails in seconds. +cat > "$BIN/sleep" <<'EOF' +#!/usr/bin/env bash +[[ -n "${FAKE_NO_SLEEP:-}" ]] || exec /bin/sleep "$@" +EOF +chmod +x "$BIN/sleep" # A fake gh, for replace's check that no CI run is going: FAKE_GH unset, no run is going; "dispatched", a run of # integ_test_auth_client.yml is; "called", a queued run of integ_test.yml has a Cognito client job queued; "other", # a running run of integ_test.yml has only other jobs going; "broken", gh cannot list the runs. @@ -341,7 +350,9 @@ mfa_required_pool_is_right() { and ($p[0].mfa.MfaConfiguration == "ON") and ($p[0].mfa.SoftwareTokenMfaConfiguration.Enabled == true) and ([$p[0].clients[] | .ClientName] == ["ccit-ci-mfa-req-totp-sms-ci"]) and ([$p[0].clients[] | .ExplicitAuthFlows] == [$t[0].appClients.ci.ExplicitAuthFlows]) - and ([.lambdas["ccit-ci-discard-sender", "ccit-ci-pre-sign-up"].policy[].Sid] | index("cognito-mfa-req-totp-sms") != null)' \ + and ([.lambdas["ccit-ci-discard-sender", "ccit-ci-pre-sign-up"] + | [.policy[] | select(.Sid == "cognito-mfa-req-totp-sms") | .Condition.ArnLike["AWS:SourceArn"]]] + == [[$p[0].UserPool.Arn], [$p[0].UserPool.Arn]])' \ "$FAKE_STATE" >/dev/null || return 1 jq -e '(.auth.mfa_configuration == "REQUIRED") and (.auth.mfa_methods == ["SMS", "TOTP"]) and (.data == null) and (.custom == null) and (.auth.identity_pool_id == null)' "$file" >/dev/null || return 1 @@ -1075,6 +1086,65 @@ replace_over_phase_1_reruns_phase_1() { || { echo "$out" | tail -8; return 1; } } +# The fake, as Cognito, makes a pool that names the SNS caller role, or sets its SMS MFA, only when the role's trust +# admits that pool, with its external id: a trust narrowed to other pools, or another external id, is refused. +the_fake_checks_the_sms_roles_trust() { + local pool role="ccit-ci-cognito-sms" external status out id opened="$WORK/opened.json" + external=$(jq -r --arg r "$role" '.roles[$r].Role.AssumeRolePolicyDocument.Statement[0].Condition.StringEquals["sts:ExternalId"]' "$FAKE_STATE") + for id in "$external" not-the-external-id; do + jq -n --arg a "arn:aws:iam::$ACCOUNT:role/$role" --arg x "$id" '{PoolName: "ccit-ci-trust-probe", + SmsConfiguration: {SnsCallerArn: $a, ExternalId: $x, SnsRegion: "us-east-1"}}' > "$WORK/sms-pool-$id.json" + done + # The trust names the default and MFA-required pools only: no new pool is admitted, whatever its external id. + for id in "$external" not-the-external-id; do + status=0 + out=$(aws --region us-east-1 --output json cognito-idp create-user-pool --cli-input-json "file://$WORK/sms-pool-$id.json" 2>&1) \ + || status=$? + (( status == 254 )) && [[ "$out" == *"(InvalidSmsRoleTrustRelationshipException)"* ]] || { echo "create: $out"; return 1; } + done + # Its own pools are: their SMS MFA can be set again. + pool=$(jq -r '.pools[] | select(.UserPool.Name == "ccit-ci-mfa-req-totp-sms") | .UserPool.Id' "$FAKE_STATE") + jq --arg id "$pool" '.pools[$id].mfa + {UserPoolId: $id}' "$FAKE_STATE" > "$WORK/sms-mfa.json" + aws --region us-east-1 --output json cognito-idp set-user-pool-mfa-config --cli-input-json "file://$WORK/sms-mfa.json" \ + >/dev/null || { echo "set-user-pool-mfa-config was refused"; return 1; } + # Opened to the account's pools, as the script opens it while it makes one: a new pool is admitted, with the + # role's external id only. + jq --arg r "$role" --arg p "arn:aws:cognito-idp:us-east-1:$ACCOUNT:userpool/*" \ + '.roles[$r].Role.AssumeRolePolicyDocument.Statement[0].Condition |= (del(.ArnEquals) + {ArnLike: {"aws:SourceArn": $p}})' \ + "$FAKE_STATE" > "$opened" + if FAKE_STATE="$opened" aws --region us-east-1 --output json cognito-idp create-user-pool \ + --cli-input-json "file://$WORK/sms-pool-not-the-external-id.json" >/dev/null 2>&1; then + echo "another external id was admitted" + return 1 + fi + FAKE_STATE="$opened" aws --region us-east-1 --output json cognito-idp create-user-pool \ + --cli-input-json "file://$WORK/sms-pool-$external.json" >/dev/null +} + +# The MFA-required pool deleted by hand (as a replace does) while the default pool stays: the rerun opens the SNS +# caller role's trust to the account's pools while it makes the pool again, which the fake (as Cognito) requires, +# then narrows it to the two SMS pools. A run that left the trust narrowed to the default pool would have its create +# refused (retried, then failed). This needs no git history. +a_remade_sms_pool_gets_the_sms_roles_trust() { + local before arn + before=$(plugin_part) + jq 'del(.pools[] | select(.UserPool.Name == "ccit-ci-mfa-req-totp-sms")) + | del(.s3["v2/testconfiguration/auth/cognito-client-ci/ccit-ci-mfa-req-totp-sms-amplify_outputs.json"])' \ + "$FAKE_STATE" > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" + : > "$FAKE_LOG" + STATUS=0 + OUT=$(FAKE_NO_SLEEP=1 "$SCRIPT" --apply --scope all --upload 2>&1) || STATUS=$? + (( STATUS == 0 )) && [[ "$(plugin_part)" == "$before" ]] && no_identifiers \ + && [[ "$OUT" == *"+ open the trust of ccit-ci-cognito-sms to the account's pools while a ccit-ci- SMS pool is made"*"+ create user pool ccit-ci-mfa-req-totp-sms"*"+ set the trust of ccit-ci-cognito-sms to the ccit-ci- SMS pools"* ]] \ + && [[ "$(calls | grep -c 'cognito-idp create-user-pool$')" == 1 ]] || { echo "$OUT" | tail -8; return 1; } + CONFIG=$(config_dir_of "$OUT") + mfa_required_pool_is_right || return 1 + arn=$(jq -c '[.pools[] | select(.UserPool.Name == "ccit-ci-default" or .UserPool.Name == "ccit-ci-mfa-req-totp-sms") + | .UserPool.Arn] | sort' "$FAKE_STATE") + jq -e --argjson a "$arn" '.roles["ccit-ci-cognito-sms"].Role.AssumeRolePolicyDocument.Statement[0].Condition + | (.ArnEquals["aws:SourceArn"] == $a) and (has("ArnLike") | not)' "$FAKE_STATE" >/dev/null +} + teardown_is_a_dry_run_by_default() { local before before=$(jq -S . "$FAKE_STATE") @@ -1430,6 +1500,10 @@ check "snapshot: a Lambda's RuntimeVersionConfig, which Lambda moves by itself, snapshot_ignores_the_runtime_version check "the Cognito lists are read to their last page: a ccit-ci- pool on page 2 is found, snapshotted, torn down" \ lists_are_read_to_their_last_page +check "the fake makes an SMS pool, or sets its SMS MFA, only when the SNS caller role's trust admits it" \ + the_fake_checks_the_sms_roles_trust +check "an SMS pool made again opens the SNS caller role's trust while it is made, then narrows it (no git history)" \ + a_remade_sms_pool_gets_the_sms_roles_trust check "replace takes only the script's pools, refusing any other name before any AWS call" \ replace_takes_only_the_scripts_pools check "replace is a dry run by default, and refuses while a CI run that reads the pools is going" \ From 5d303dda485b414db1d2ca5b59cac032092ca3ea Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:48:54 -0400 Subject: [PATCH 41/57] test(auth): check that replace refuses an untagged uploaded file of the pool The rerun's dry run already refuses an untagged pool, but never reads an uploaded file's tags, so only replace's own tag check stops it from deleting an untagged file. The test now covers both. --- .../infra/ci/test_provision_ci.sh | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh index 7e95141a7c..7034a102a5 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/test_provision_ci.sh @@ -1007,21 +1007,25 @@ replace_is_a_dry_run_and_refuses_while_ci_runs() { || { echo "$OUT" | tail -5; return 1; } } -# replace --apply refuses a pool without the purpose tag before any change. +# replace --apply refuses, before any change, a pool without the purpose tag, and an uploaded file that names the pool +# without it (which only replace reads the tag of: the rerun never deletes a file). replace_refuses_an_untagged_pool() { - local before - jq '(.pools[] | select(.UserPool.Name == "ccit-ci-email-alias-codes") | .UserPool.UserPoolTags) = {}' "$FAKE_STATE" \ - > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" - before=$(jq -S . "$FAKE_STATE") - run replace ccit-ci-email-alias-codes --apply - local status=$STATUS out=$OUT after - after=$(jq -S . "$FAKE_STATE") - jq '(.pools[] | select(.UserPool.Name == "ccit-ci-email-alias-codes") | .UserPool.UserPoolTags) = {purpose: "amplify-cognito-client-integ"}' \ - "$FAKE_STATE" > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" - OUT=$out - (( status == 1 )) && only_reads && [[ "$after" == "$before" ]] \ - && [[ "$out" == *"user-pool ccit-ci-email-alias-codes exists without purpose=amplify-cognito-client-integ"* ]] \ - && [[ "$out" == *"the dry run refused"* ]] && no_identifiers || { echo "$out" | tail -5; return 1; } + local before status out after case filter + local file='.s3["v2/testconfiguration/auth/cognito-client-ci/ccit-ci-email-alias-codes-amplify_outputs.json"].tags' + local pool='(.pools[] | select(.UserPool.Name == "ccit-ci-email-alias-codes") | .UserPool.UserPoolTags)' + for case in "$pool:user-pool ccit-ci-email-alias-codes" \ + "$file:s3-object /auth/cognito-client-ci/ccit-ci-email-alias-codes-amplify_outputs.json"; do + filter="${case%%:*}" + jq "$filter = {}" "$FAKE_STATE" > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" + before=$(jq -S . "$FAKE_STATE") + run replace ccit-ci-email-alias-codes --apply + status=$STATUS out=$OUT + after=$(jq -S . "$FAKE_STATE") + jq "$filter = {purpose: \"amplify-cognito-client-integ\"}" "$FAKE_STATE" > "$WORK/s" && mv "$WORK/s" "$FAKE_STATE" + (( status == 1 )) && only_reads && [[ "$after" == "$before" ]] \ + && [[ "$out" == *"${case#*:} exists without purpose=amplify-cognito-client-integ"* ]] \ + && [[ "$out" == *"the dry run refused"* ]] && no_identifiers || { echo "$out" | tail -5; return 1; } + done } # The uploaded file $1 (under auth/cognito-client-ci/), as the fake holds it. @@ -1508,7 +1512,8 @@ check "replace takes only the script's pools, refusing any other name before any replace_takes_only_the_scripts_pools check "replace is a dry run by default, and refuses while a CI run that reads the pools is going" \ replace_is_a_dry_run_and_refuses_while_ci_runs -check "replace --apply refuses a pool without the tag, before any change" replace_refuses_an_untagged_pool +check "replace --apply refuses a pool or an uploaded file without the tag, before any change" \ + replace_refuses_an_untagged_pool check "replace default --apply deletes the pool and what names it, then the rerun makes them again" \ replace_default_makes_it_again check "replace of the device-alias pool over phase 1 alone reruns phase 1, adding nothing of phase 2" \ From 902222b71eacee7b0c8243c2d22acfabdfada187 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:33:39 -0400 Subject: [PATCH 42/57] test(auth): pin that confirmSignIn returns when the sign-out cancelling it cannot clear the store When the user pool accepts the answer and the identity pool step fails, confirmSignIn cancels the sign-in and waits for the sign-out. If the credential store refuses the local clear, the sign-out ends in .signingOut(.error) and goes back to .signedIn, never reaching .signedOut, so the wait must also end on the error. Nothing covered that exit: removing it left every confirmSignIn test passing. The new test uses a credential store that refuses the delete and checks that confirmSignIn throws the identity pool's error within the timeout, after one revoke and one refused delete, and that the state machine goes back to .signedIn. --- .../AWSAuthConfirmSignInTaskTests.swift | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift index 35d80ab116..92b2e2714a 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift @@ -937,6 +937,99 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest, @unchecked Senda XCTAssertEqual(answered.get(), 1) } + /// Test that confirmSignIn still returns the identity pool's error when the sign-out that cancels the sign-in + /// cannot clear the credential store + /// + /// - Given: an auth plugin with mocked service whose credential store refuses to delete the saved credentials. + /// `RespondToAuthChallenge` accepts the code and answers tokens, the identity pool's `GetId` fails with + /// `InvalidParameterException`, and `RevokeToken` succeeds + /// - When: + /// - I invoke confirmSignIn with a valid confirmation code + /// - Then: + /// - confirmSignIn throws a `.service` error with `AWSCognitoAuthError.invalidParameter` as underlying error + /// within the timeout, instead of waiting for a sign-out that never ends: the failed local clear ends the + /// wait at `.signingOut(.error)` + /// - the tokens were revoked once, the store refused one delete, and the state machine goes back to + /// `.signedIn`, as `signIn`'s cancel would leave it + /// + func testConfirmSignInThrowsTheIdentityPoolsErrorWhenTheSignOutCannotClearTheStore() async throws { + let identityPool = MockIdentity( + mockGetIdResponse: { _ in + throw AWSCognitoIdentity.InvalidParameterException(message: "Invalid parameter passed") + }, + mockGetCredentialsResponse: getCredentials + ) + let answered = TestCounter() + let revoked = TestCounter() + let refusedDeletes = TestCounter() + let userPool = MockIdentityProvider( + mockRevokeTokenResponse: { _ in + revoked.increment() + return RevokeTokenOutput() + }, + mockRespondToAuthChallengeResponse: { _ in + answered.increment() + return .testData() + }) + let base = Defaults.makeDefaultAuthEnvironment( + identityPoolFactory: { identityPool }, + userPoolFactory: { userPool } + ) + let environment = AuthEnvironment( + configuration: base.configuration, + userPoolConfigData: base.userPoolConfigData, + identityPoolConfigData: base.identityPoolConfigData, + authenticationEnvironment: base.authenticationEnvironment, + authorizationEnvironment: base.authorizationEnvironment, + credentialsClient: CredentialStoreRefusingDelete(base: base.credentialsClient, refusals: refusedDeletes), + logger: base.logger + ) + let stateMachine = AuthStateMachine( + resolver: AuthState.Resolver(logger: AmplifyEngineLogRouter()), + environment: environment, + initialState: initialState + ) + let refusingPlugin = AWSCognitoAuthPlugin() + refusingPlugin.configure( + authConfiguration: Defaults.makeDefaultAuthConfigData(), + authEnvironment: environment, + authStateMachine: stateMachine, + credentialStoreStateMachine: Defaults.makeDefaultCredentialStateMachine(), + hubEventHandler: MockAuthHubEventBehavior(), + analyticsHandler: MockAnalyticsHandler() + ) + settleConfigureOperationOnTeardown(of: refusingPlugin) + let confirmed = expectation(description: "confirmSignIn returns") + + Task { + do { + let result = try await refusingPlugin.confirmSignIn(challengeResponse: "code") + XCTFail("Should not succeed, got \(result)") + } catch AuthError.service(_, _, let underlyingError) { + guard case .invalidParameter = underlyingError as? AWSCognitoAuthError else { + XCTFail("Underlying error should be AWSCognitoAuthError.invalidParameter, got \(String(describing: underlyingError))") + return + } + } catch { + XCTFail("Should produce a service error instead of \(error)") + } + confirmed.fulfill() + } + await fulfillment(of: [confirmed], timeout: 5) + + XCTAssertEqual(answered.get(), 1) + XCTAssertEqual(revoked.get(), 1) + XCTAssertEqual(refusedDeletes.get(), 1) + let deadline = Date().addingTimeInterval(5) + while Date() < deadline { + if case .configured(.signedIn, _, _) = await refusingPlugin.authStateMachine.currentState { + return + } + try await Task.sleep(nanoseconds: 10_000_000) + } + XCTFail("the failed sign-out did not go back to signedIn: \(await refusingPlugin.authStateMachine.currentState)") + } + /// Test that confirmSignIn leaves a sign-in it did not make as it is when its identity pool step has failed /// /// - Given: an auth plugin already signed in (a reconcile adopted a sibling app's sign-in while this flow was @@ -980,3 +1073,28 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest, @unchecked Senda XCTAssertEqual(revoked.get(), 0) } } + +/// A credential store that forwards to `base` but refuses to delete the saved credentials, as a keychain that refuses +/// the clear would. +private struct CredentialStoreRefusingDelete: CredentialStoreStateBehavior { + struct RefusedDelete: Error {} + + let base: CredentialStoreStateBehavior + let refusals: TestCounter + + func fetchData(type: CredentialStoreDataType) async throws -> CredentialStoreData { + try await base.fetchData(type: type) + } + + func storeData(data: CredentialStoreData) async throws { + try await base.storeData(data: data) + } + + func deleteData(type: CredentialStoreDataType) async throws { + if case .amplifyCredentials = type { + refusals.increment() + throw RefusedDelete() + } + try await base.deleteData(type: type) + } +} From 084551cd3019a5ceb0de2e4b4cac9e947fa13985 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:33:39 -0400 Subject: [PATCH 43/57] docs(auth): say in the client README that a session's dead-token expiry is saved across launches A session that reported sessionExpired now restores expired after a relaunch or in a new client, and may sign in again without signing out. The 30-second count before the expiry stays per launch, so the known limitation about it is narrowed rather than removed. --- AmplifyClients/AmplifyCognitoClient/README.md | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/README.md b/AmplifyClients/AmplifyCognitoClient/README.md index 05c417a88e..62e27f04a5 100644 --- a/AmplifyClients/AmplifyCognitoClient/README.md +++ b/AmplifyClients/AmplifyCognitoClient/README.md @@ -523,9 +523,10 @@ sessions never block it. A guest session signs in on the same session, but its i the Auth plugin, the signed-in identity ID is the one the identity pool maps the login to. A session waiting on a challenge starts over. -A session that reported `sessionExpired` in this process may sign in again without signing out. That is held in -memory only: after a relaunch, or once every handle and provider for the session is released, `signIn` is refused -with `.invalidState` again until a token request finds the dead token again, so sign out first, as with the plugin. +A session that reported `sessionExpired` may sign in again without signing out, in that launch or a later one: the +expiry is saved with the session while its saved login keeps the refresh token found dead. A session whose refresh +token was revoked elsewhere, and that no refresh has found dead yet, is still signed in as its user, so `signIn` is +refused with `.invalidState` until a token request reports `sessionExpired`; sign out first, as with the plugin. The result's `nextStep` is `.done` when the user is signed in. Otherwise it is the step to present; see [Sign-in steps and confirmSignIn](#sign-in-steps-and-confirmsignin). In an SRP, `.userPassword` or custom-auth @@ -1003,12 +1004,12 @@ let accessToken = try await client.userPoolTokenProvider.accessToken() retryable `unknown` an unforced refresh gets there (unless another writer saved a refresh meanwhile, with a new refresh token, which it adopts). It counts toward the dead-token rule (see [Known limitations](#known-limitations)): a second refusal at least 30 seconds later, with nothing saved meanwhile, - reports `sessionExpired` and expires the session even though its saved tokens are still valid. From then on, while - the session is in memory (until every handle and provider for it is released, or a relaunch), an unforced fetch and - both providers fail with `sessionExpired` too, until the session signs in again or signs out. Another writer saving - the same login again doesn't end that, before or after the expiry: the same credentials in other bytes, or the Auth - plugin's save of them marked expired, still hold the refresh token found dead. A login with another refresh token - does. + reports `sessionExpired` and expires the session even though its saved tokens are still valid. From then on, an + unforced fetch and both providers fail with `sessionExpired` too, until the session signs in again or signs out. The + expiry is saved with the session, so a new client of it, and the app after a relaunch, answer the same without + refreshing the dead token again. Another writer saving the same login again doesn't end that, before or after the + expiry: the same credentials in other bytes, or the Auth plugin's save of them marked expired, still hold the + refresh token found dead. A login with another refresh token does. - There is no `isSignedIn` field. Use `currentSessionState()`. - Tokens and AWS credentials print redacted. @@ -1317,7 +1318,7 @@ await client.signOut(options: .init(purgeStoredSession: true)) | Result | Meaning | |---|---| | `.complete` | Tokens revoked and cleared from this device, or there were none | -| `.partial(revokeTokenError:globalSignOutError:hostedUIError:storageError:)` | Signed out on this device, but part of the work failed. `revokeTokenError`: the refresh token stays valid until it expires, unless a global sign-out succeeded (`globalSignOutError` is `nil`), which already invalidated it. After a failed global sign-out the token isn't revoked and this holds a placeholder `.service` error with empty texts, as in the plugin. `globalSignOutError`: other devices stay signed in. Neither is retried, and signing out again doesn't retry it: the session is signed out on this device, so a second sign-out is `.complete` and sends nothing. `hostedUIError`: the browser keeps its hosted-UI cookie; only for a sign-out without a window (`signOut(options:)` or `signOutStoredSession`, as `.validation`), or a closed page on a session a refresh found expired while the session was in memory (until every handle and provider for it is released, or a relaunch), when the login signed out still has that refresh token. `storageError`: with `purgeStoredSession`, the row couldn't be removed; purge it again | +| `.partial(revokeTokenError:globalSignOutError:hostedUIError:storageError:)` | Signed out on this device, but part of the work failed. `revokeTokenError`: the refresh token stays valid until it expires, unless a global sign-out succeeded (`globalSignOutError` is `nil`), which already invalidated it. After a failed global sign-out the token isn't revoked and this holds a placeholder `.service` error with empty texts, as in the plugin. `globalSignOutError`: other devices stay signed in. Neither is retried, and signing out again doesn't retry it: the session is signed out on this device, so a second sign-out is `.complete` and sends nothing. `hostedUIError`: the browser keeps its hosted-UI cookie; only for a sign-out without a window (`signOut(options:)` or `signOutStoredSession`, as `.validation`), or a closed page on a session a refresh found expired, in this launch or an earlier one, when the login signed out still has that refresh token. `storageError`: with `purgeStoredSession`, the row couldn't be removed; purge it again | | `.failed(AuthClientError)` | Not signed out: the session is still signed in on this device. See below | `.failed` carries: @@ -1397,10 +1398,10 @@ func hostedUISignOut(client: AmplifyCognitoClient, window: AuthClientPresentatio signs out in the browser. Until then, sign in to the hosted UI with `prompt: [.login]` or `prefersEphemeralSession: true`. - If the user closes the sign-out page, the result is `.failed(.userCancelled)` and the session stays signed in, as - with the plugin, unless a refresh has already found the session expired while it was in memory (the client keeps - that on the session until every handle and provider for it is released, or a relaunch; the plugin saves it with the - session), and the login being signed out still has the refresh token found dead. A new login another app in the - access group saved since then stays signed in: `.failed(.userCancelled)`. Otherwise it signs out anyway and returns `.partial` with `hostedUIError` + with the plugin, unless a refresh has already found the session expired, in this launch or an earlier one (the + client saves that beside the session; it doesn't read the plugin's own saved flag), and the login being signed out + still has the refresh token found dead. A new login another app in the access group saved since then stays signed + in: `.failed(.userCancelled)`. Otherwise it signs out anyway and returns `.partial` with `hostedUIError` `.userCancelled`, which says the session was signed out on this device but not from the hosted UI: as without a window, signing out again won't clear the cookie. The same goes when `cancelWebUISignIn()` closes this session's sign-out page, or `resetSystemSheet()` closes it before it finished. @@ -1926,28 +1927,28 @@ The client keeps the plugin's semantics except where listed here and in the sect - **Rollback to an older plugin release**: plugin releases before 2.51.0 can't refresh at all on an app client with rotation on. Releases before 2.53.3 can wipe the plugin's keychain service, every saved session included, the first time they configure on an install that never ran one of them. Roll back to 2.53.3 or later. -- **After a relaunch, a session whose refresh token was revoked elsewhere refuses `signIn`.** Its expiry is known in - memory only, so it restores signed in as its user, and `signIn` throws `.invalidState` ("There is already a user - in signedIn state") until the session signs out, or a refresh in that launch reports `sessionExpired`. What to do: - on `sessionExpired`, or on that refusal, sign the session out before showing sign-in. -- **A second refresh-token reuse counts as a dead token after 30 seconds, within one launch.** Until then the - failure, forced or not, is retryable. The rule is kept in memory: after an app terminated in the middle of a - refresh with refresh-token rotation on (Cognito rotated the token, and the new one was never saved), the saved, - retired refresh token gives the retryable `unknown` at the first refusal of each launch, and `sessionExpired` at a - second one at least 30 seconds later in the same launch. Forced refreshes count too, even while the saved tokens - are still valid. This `sessionExpired` says the refresh token was already used and no refreshed tokens were saved, - with `refreshTokenReused` underneath, not the "expired or been revoked" of a refused refresh token. That repeats in - every launch until the session is signed out, or signs in again after a `sessionExpired`. Before that - `sessionExpired`, the session is signed in as its user, so `signIn` refuses it with `.invalidState`. What to do: - after repeated refresh failures, sign the session out, then show sign-in. +- **A session whose refresh token was revoked elsewhere refuses `signIn` until a refresh finds it.** It is signed in + as its user, and `signIn` throws `.invalidState` ("There is already a user in signedIn state") until the session + signs out, or a refresh reports `sessionExpired`. From then on the expiry is saved with the session, so a later + launch lets it sign in again too. What to do: on `sessionExpired`, or on that refusal, sign the session out before + showing sign-in. +- **A second refresh-token reuse counts as a dead token after 30 seconds, in one launch.** Until then the failure, + forced or not, is retryable. The count is kept in memory: after an app terminated in the middle of a refresh with + refresh-token rotation on (Cognito rotated the token, and the new one was never saved), the saved, retired refresh + token gives the retryable `unknown` at its first refusal, and `sessionExpired` at a second one at least 30 seconds + later in the same launch; a relaunch in between starts the count again. Forced refreshes count too, even while the + saved tokens are still valid. This `sessionExpired` says the refresh token was already used and no refreshed tokens + were saved, with `refreshTokenReused` underneath, not the "expired or been revoked" of a refused refresh token. Once + it is reported, the expiry is saved with the session: later launches, and new clients of the session, answer it at + once without refreshing, until the session signs in again or signs out. Before that `sessionExpired`, the session is + signed in as its user, so `signIn` refuses it with `.invalidState`. What to do: after repeated refresh failures, + sign the session out, then show sign-in. - **A sign-in after an expired session can't be resumed after a relaunch.** This affects a session whose refresh token expired (it reported `sessionExpired`) when its user signs in again, that sign-in stops on a challenge - (an MFA code, say), and the app is closed before the user answers. The saved login still holds the expired user, - so on relaunch the saved challenge is discarded. The session's expiry is known in memory only: after the relaunch - it is signed in as that user again, its cached tokens and AWS credentials are handed out while they are still - valid (when the expiry was found early, by a forced refresh or after a revoke elsewhere), and only its next - refresh reports `sessionExpired` again. What to do: on `sessionExpired`, sign the session out, then show sign-in - and start it over. A first sign-in, or one after a sign-out, resumes as usual. + (an MFA code, say), and the app is closed before the user answers. The saved login still holds the expired user, so + on relaunch the saved challenge is discarded. The session restores expired (its expiry is saved with it): it hands + out no tokens, answers `sessionExpired`, and lets its user sign in again from the start. What to do: on + `sessionExpired`, show sign-in and start it over. A first sign-in, or one after a sign-out, resumes as usual. - **The side-by-side warning is temporary.** This affects apps that run the Auth plugin and the client over `.default` in one process, which is not supported. When `.default` finds a different user or guest in the saved login, it logs once under `AmplifyCognitoClient.DefaultSession` and changes nothing else. What to do: use one or From 9d7abf3a08e907a2c82fb9e3e4f65a9988679563 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:33:39 -0400 Subject: [PATCH 44/57] docs(auth): describe the weekly key extension and the first-run credential check in the integration README Adds the extend-api-key Lambda, its rule and role to CI's additive resources, and lists what the sandbox's first-run check accepts: credentials the profile holds itself, including login and assume-role-with-web-identity, and none it would fall back to. --- .../Tests/IntegrationTests/README.md | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/README.md b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/README.md index 638ad56a38..a9d6ab8d69 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/README.md +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/README.md @@ -733,6 +733,7 @@ but in `SandboxPool.optionalRoles`, and the build phases copy its files as optio | The define, create and verify custom-auth triggers, and the answer in SSM `/ccit-ci/custom-challenge-answer` (a SecureString under the key `alias/ccit-ci-senders`, as the temporary password is; the Lambda holds only its SHA-256) | CA-1…3 and the stored-answer check | | `ccit-ci-new-password-1` … `-12`, kept in `FORCE_CHANGE_PASSWORD` every 10 minutes by `ccit-ci-new-password-reset` (`infra/ci/lambda/new-password-reset`) and an EventBridge rule; the temporary password in SSM | CH-1, P-3, and the fixture check with the credentials file | | The custom sender (`lambda/custom-sender`), the code sink (AppSync API and table `ccit-ci-codes`, `codesink/`), their roles, and the SNS caller role for SMS MFA | AT-2, its second half, AT-4, RP-3; Cognito sends nothing | +| Lambda `ccit-ci-extend-api-key` (`infra/ci/lambda/extend-api-key`), run weekly by the EventBridge rule `ccit-ci-extend-api-key`, with the role `ccit-ci-key-exec` (its logs, and listing and updating the code sink API's keys only) | The code sink's API key keeps working: it is extended in place, keeping its id, once 120 or fewer days are left | | Pool `ccit-ci-email-alias-codes` (`pools/email-alias.json`, on **Lite**) and its `client` app client, with Lambda `ccit-ci-pre-sign-up-confirmable` (`triggers.mjs` without `REFUSE_CONFIRMATION_USERS`, so it leaves `ccit-confirm-` users to confirm) and the custom sender into the code sink | The client's own role `SandboxPool.emailAliasCodes` (optional, not in `allCases`): the email-alias code check; Cognito sends nothing | | Pool `ccit-ci-mfa-req-totp-sms` (`pools/mfa-req-totp-sms.json`, on Essentials) and its `ci` app client (SRP and `USER_AUTH`, no `USER_PASSWORD_AUTH`), MFA required with TOTP and SMS, with `ccit-ci-pre-sign-up` and the discard sender as both custom senders; the SNS caller role's trust names it beside `ccit-ci-default`. A run that makes either of the two opens that trust to the account's pools while the pool is made (still held to the external ID and `aws:SourceAccount`), as it opens the key policy, and narrows it again after (the plan says both). A run that stops in between leaves it open until a rerun narrows it | The client's overlay role `mfa-req-totp-sms`, in place of the plugin's MFA-required backend: MF-13, MF-15…17 and the two `USER_AUTH` MFA-setup checks, and that role in the every-pool checks; Cognito sends nothing | | `ccit-ci-capabilities.json`: `refuses_non_test_users` and `reset_password_codes` on `extended`, `email_alias_codes` on `email-alias-codes` | The three sandbox checks, on those roles ("Sandbox checks", above) | @@ -867,16 +868,22 @@ R-IP (and the identity-only pool below R-IP2). The scripts use whatever credentials the AWS CLI resolves, and the first `provision.sh` records that account in `state.json`. From then on `provision.sh`, `prepare-run.sh`, `self-sign-up.sh`, `teardown.sh` and `parity.py` refuse any other account (`require_recorded_account` in `lib.sh` and `parity.py`). While no account is recorded -yet, `lib.sh`'s check exits before any AWS call unless `AWS_PROFILE` is set and none of `AWS_ACCESS_KEY_ID`, -`AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_ROLE_ARN`, `AWS_WEB_IDENTITY_TOKEN_FILE` and the -`AWS_CONTAINER_CREDENTIALS_*` variables is exported. The CLI takes exported keys over any profile, and a web identity -role before the profile's own credentials (CI runners and EKS often export the web identity variables). Container -credentials come later: the CLI uses them only when the profile holds no credentials of its own, as it does an EC2 -instance role, `AWS_CREDENTIAL_FILE` and the boto files. The check refuses them anyway, to be safe. That is all it -checks. It doesn't know which account the profile belongs to, or whether the profile holds credentials at all: with -a profile that holds only a region, on an EC2 host the CLI uses the instance role, and the first run then builds the -sandbox in that role's account and records it. Set `AWS_PROFILE` to the sandbox account's profile, make sure that -profile has credentials of its own, and check `aws sts get-caller-identity` before that first run. +yet, `lib.sh`'s check exits before any AWS call unless all three of these hold: +- `AWS_PROFILE` is set. +- None of `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_ROLE_ARN`, + `AWS_WEB_IDENTITY_TOKEN_FILE` and the `AWS_CONTAINER_CREDENTIALS_*` variables is exported. The CLI takes exported + keys over any profile, and a web identity role before the profile's own credentials (CI runners and EKS often + export the web identity variables). Container credentials come later, but the check refuses them too, to be safe. +- `aws configure list` reports credentials the profile holds itself: keys in the credentials or config file + (`shared-credentials-file`, `config-file`), `sso`, `login`, a `credential_process` (`custom-process`), or a + `role_arn` it assumes (`assume-role`, `assume-role-with-web-identity`). A profile that holds none, only a region + say, leaves the CLI to fall back to an EC2 instance role (`iam-role`), container credentials (`container-role`), + `AWS_CREDENTIAL_FILE` or the boto files, and the first run would build the sandbox in that account, so those are + refused. So is a profile the CLI cannot resolve: a missing one, or one whose sign-in has expired. + +`aws configure list` reads the profile locally; it calls no AWS service, except to fetch an assumed role's or a +sign-in's credentials, as the run's first call would. The check still doesn't know which account the profile belongs +to: set `AWS_PROFILE` to the sandbox account's profile and check `aws sts get-caller-identity` before that first run. The sandbox lives in one region, because its IAM roles are global. `provision.sh` refuses a region other than the one `state.json` records, and, on every run, a region recorded or not, one other than the region of the identity pool From 8b3c0ca9b2c85c3cbf1daf6267be642ad207fdc9 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:33:40 -0400 Subject: [PATCH 45/57] docs(auth): document the saved dead-token expiry in the client design The storage table and model gain the deadToken marker, its binding to the dead token and its best-effort save; the sign-out result and the offline-expiry note follow. --- docs/design/AGREED-DESIGN-AmplifyCognitoClient.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md b/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md index 83c4be01da..8c7faf75d8 100644 --- a/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md +++ b/docs/design/AGREED-DESIGN-AmplifyCognitoClient.md @@ -772,9 +772,9 @@ Both families live in the plugin's keychain service, `com.amplify.awsCognitoAuth | Item | Account | Payload | |---|---|---| | `.default`'s session record | the plugin's key, `amplify..session` | the plugin's `AmplifyCredentials` JSON, exactly as the plugin writes it | -| `.default`'s sidecar | `amplify.1..$default.meta` | the label, and the last signed-in user's username and user ID | +| `.default`'s sidecar | `amplify.1..$default.meta` | the label, the last signed-in user's username and user ID, and a dead refresh token's saved expiry (`deadToken`) | | `.default`'s challenge record | `amplify.1..$default.challenge`, unchanged | as for a named session (4.11) | -| a named session | `amplify.1...`, kind `session` or `challenge` | `SessionRecordEnvelope`: a schema version, a generation, the kind, the label, the username and user ID, and the same `AmplifyCredentials` JSON in its `credentials` field | +| a named session | `amplify.1...`, kind `session` or `challenge` | `SessionRecordEnvelope`: a schema version, a generation, the kind, the label, the username and user ID, a dead refresh token's saved expiry (`deadToken`, only when set), and the same `AmplifyCredentials` JSON in its `credentials` field | - **No `$default` session record.** Only the **session** record of `.default` moves to the plugin's key. Its challenge record and its sidecar stay in the client's own family, under `$default`. `SessionID.default` stays in the API, and is still distinct from `.named("default")`. - **Development leftovers.** Listing ignores `amplify.1..$default.session`, the session record development builds wrote. @@ -783,6 +783,7 @@ Both families live in the plugin's keychain service, `com.amplify.awsCognitoAuth - **while the shared record is signed out** (`noCredentials`) **or absent**, the picker shows a signed-out row with the sidecar's label and last username; - **when the shared record holds a different user**, the sidecar's label is hidden. The sidecar is rewritten for the new user, without the label, only at the client's next write of the record: a plugin sign-in leaves it as it is, so after a later plugin sign-out the row names the sidecar's user and label again; - **a sign-in over a signed-out row keeps its label only for the same user**, or when the sidecar has no user yet (a label set before anyone signed in). A different user drops it. +- **A dead refresh token's expiry is saved beside the record** (`DeadToken`): `{"cause":"reused"|"invalid","token":""}`, under `deadToken`, in a named session's envelope and in `.default`'s sidecar, both additive keys written only when set. `token` names the refresh token proved dead (SHA-256 of `refreshToken:`), or, for credentials without one, the credentials' bytes. A core that installs a record whose credentials still hold that token starts expired, with no event, and a refresh re-reads first and never refreshes such a record. It is bound to the token, not to the record: the plugin saving a new login over `.default` leaves the sidecar, and the marker no longer applies. The client's own writes of other credentials and its sign-outs drop it; a label write keeps it. Best effort: a failed save leaves the expiry the core's only. A marker with a cause this build can't read is ignored. The plugin's `isRefreshTokenExpired` is not read: the plugin sets it on whatever record is stored after its own refusal, which may be a newer writer's valid login. - **The commit guard on `.default` compares the stored bytes.** `setIfUnchanged` already does this, so the default session has no generation number. The plugin itself writes unguarded. That is one reason running both in one process is not supported (§4.9). Whether another writer changed the *credentials* is decided on the decoded value, not on the bytes, because the plugin may save the same credentials again with other bytes. - **macOS: absent once is not signed out.** There the keychain's `set` deletes the item and adds it again, so a reader in another process can find it absent in between. A `.default` read that finds the shared record absent reads it once more before concluding "signed out". - **A write that loses a race updates once.** When another writer (the plugin beside a client, an app beside its extension) creates the same keychain item at the same moment, the write updates the item once instead of failing. The plugin and the client both behave this way. @@ -1552,10 +1553,10 @@ The shipped `@_spi(AmplifyExperimental)` surface differs from 14.1 to 14.7 as fo - **`.failed`, with the session still signed in:** another sign-in replaced the session during the sign-out (`.invalidState`); a storage failure before the local sign-out; `sessionConfigurationMismatch` and any other error before anything was revoked; a closed hosted-UI sign-out page (`.userCancelled`), unless a refresh had - found the session expired while it was in memory and the login signed out still has the refresh token found - dead; any hosted-UI sign-out page that could not be shown or completed, - including no hosted-UI configuration or sign-out redirect URI; task cancellation before anything was revoked, - as `.failed(.unknown("The sign-out was cancelled before anything was revoked; the session is still signed + found the session expired (in this launch or an earlier one: the expiry is saved with the record) and the login + signed out still has the refresh token found dead; any hosted-UI sign-out page that could not be shown or + completed, including no hosted-UI configuration or sign-out redirect URI; task cancellation before anything was + revoked, as `.failed(.unknown("The sign-out was cancelled before anything was revoked; the session is still signed in.", "Retry the sign-out.", CancellationError()))`. - **A purge that fails after the local sign-out succeeded** returns `.partial` with `storageError` set, and `signedOutLocally == true`. @@ -1841,7 +1842,7 @@ Verified against `amplify-swift`, `amplify-android`, and `amplify-flutter` at `o **C.2 The state machine is already per-instance.** No static session state on any platform. Flutter constructs one per plugin instance and its tests build them standalone. This is the finding that makes the change tractable. -**C.3 Refresh-token expiry is not derivable offline.** The stored "refresh expired" marker is set only inside a session-expired error path, after a refresh has already failed over the network, and the refresh token is never parsed. Hence the rule in 4.2 that a picker row cannot promise liveness, and why the field is not on `StoredSession`. +**C.3 Refresh-token expiry is not derivable offline.** The stored "refresh expired" marker is set only inside a session-expired error path, after a refresh has already failed over the network, and the refresh token is never parsed. Hence the rule in 4.2 that a picker row cannot promise liveness, and why the field is not on `StoredSession`. The client's own saved expiry (`deadToken`, §6) is the same kind of marker, set only after a refresh failed over the network, so a picker row still promises nothing. **C.4 The plugin's readiness check has no timeout.** Every task file waits on an internal "did configuration finish?" check with no bound and no failure state. It is safe today only because there is exactly one credential store per process, serialized by its own **inner** queue, not by the visible facade queue (see A.8). Stating this as "a process-wide queue" is the sloppiness that invites the reply "then just make more instances"; the point is that the load-bearing lock is the inner one. Two things follow for the implementer: do not assume the visible facade queue is what serializes access, and do not inherit the unbounded wait. The Android analogue, where a dropped store event could turn a readiness wait into an invisible hang rather than an error, is **Appendix E.1** - stated there as something to validate rather than something established, and an Android prerequisite either way. From 3ec28ac319d302af0c6574376fe84254064ac5f0 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:33:40 -0400 Subject: [PATCH 46/57] docs(auth): record that a dead-token expiry is saved with the session's record A new decision for a session core made after the expiry, and the reuse-count and second-refusal decisions refined to match: the count stays per launch, the expiry it leads to is saved. --- docs/design/auth-client-DECISIONS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/design/auth-client-DECISIONS.md b/docs/design/auth-client-DECISIONS.md index e83eb8eb60..bedf53d6e4 100644 --- a/docs/design/auth-client-DECISIONS.md +++ b/docs/design/auth-client-DECISIONS.md @@ -433,7 +433,7 @@ design. | 25 | When a hosted-UI sign-in cancelled with `cancelWebUISignIn()` answers | **Once it has released the system sheet**: `systemSheetHolder` no longer names the session. If the sheet isn't released within a second, the call throws anyway, and the sheet stays held until the sign-in's remaining calls, or the passkey ceremony, finish. A passkey call too. | | 26 | A forgotten device key on a sign-in that isn't SRP | **Dropped and retried once**, in the shared engine: the password (`USER_PASSWORD_AUTH`), `USER_AUTH`, custom and auto sign-in flows remove the device record and resend without a device key. Plugin and client alike. | | 27 | The user ID of a `signUp` that completes with auto sign-in | **Carried**: Cognito's sub, as `.done` and `.confirmUser` already carried it. Plugin and client alike. `confirmSignUp`'s `.completeAutoSignIn` and `.done` still carry none, since Cognito's answer there has no sub. | -| 28 | A refresh refused as a reused refresh token | **The 30-second dead-token rule holds within one launch**, and is not persisted; a refused forced refresh fails with the retryable error instead of returning the tokens it sent, and counts toward the rule: a second one at least 30 seconds later expires the session, with its own `sessionExpired` text (`refreshTokenReused` underneath; #35). | +| 28 | A refresh refused as a reused refresh token | **The 30-second dead-token rule holds within one launch**, and is not persisted; a refused forced refresh fails with the retryable error instead of returning the tokens it sent, and counts toward the rule: a second one at least 30 seconds later expires the session, with its own `sessionExpired` text (`refreshTokenReused` underneath; #35). Refined: the count stays in memory, per launch; the expiry it leads to is saved with the record (#41). | **#20 and #21.** An extension that fetches a session first finds the shared service empty and saves a guest identity there. Counting that record as a migrated login aborted the app's migration and stranded its login in the old @@ -555,12 +555,13 @@ client's README and the release notes. #38 and #39 keep limitations that the cli | # | Decision | Answer | |---|---|---| -| 35 | A second refusal of a reused refresh token, at least 30 seconds after the first, with nothing saved meanwhile | **The session still expires, forced or not**, even while its saved tokens are valid, and from then on, while the session is in memory (until every handle and provider for it is released, or a relaunch), its fields and providers answer `sessionExpired` until its credentials change to ones with another refresh token, or none; the same login saved again, before or after the expiry, in other bytes or with the plugin's expired flag set, is not a change. Its text says the refresh token was already used and no refreshed tokens were saved, so the session cannot be renewed, and to sign in again, with `refreshTokenReused` underneath, not the "expired or been revoked" text of a refused refresh token. Refines #28. | +| 35 | A second refusal of a reused refresh token, at least 30 seconds after the first, with nothing saved meanwhile | **The session still expires, forced or not**, even while its saved tokens are valid, and from then on, in this launch and later ones (the expiry is saved with the record, #41), its fields and providers answer `sessionExpired` until its credentials change to ones with another refresh token, or none; the same login saved again, before or after the expiry, in other bytes or with the plugin's expired flag set, is not a change. Its text says the refresh token was already used and no refreshed tokens were saved, so the session cannot be renewed, and to sign in again, with `refreshTokenReused` underneath, not the "expired or been revoked" text of a refused refresh token. Refines #28. | | 36 | AWSMobileClient (Amplify v1) logins against a signed-out `.default` | **The signed-out record stops the plugin's migration**, as a login does: nothing migrates over it, and the old logins are cleared. A `.default` sign-out or purge through the client clears those logins before it writes the signed-out record or deletes the record. | | 37 | A Cognito internal error (`InternalErrorException`, from either pool) and an error status the SDK does not model, in the client | **`.service` with no code**, in every call, `USER_AUTH`, auto, device SRP and passkey sign-ins, `deleteUser`, a sign-out's revoke or global sign-out, and the passkey credential calls included, with a recovery suggestion to retry (a 5xx, an internal error) or naming the refusal (a 4xx). In a `.partial` sign-out, the revoke's or global sign-out's suggestion says instead that it will not be retried; a sign-out that could not finish (`.failed`) carries the failure as any other call maps it. The plugin keeps `AuthError.unknown`. | | 38 | A named session's configuration marker left naming an earlier configuration, by a purge stopped before its last step | **Not changed.** The next restore under the purge's configuration carries the session from the earlier one again, and the README says to purge the session again before building a client for it. | | 39 | `signOutStoredSession` with no client for the session live in the process, and `configureUserPoolClient` | **Not given one.** Its revoke uses SDK clients built from the configuration alone, so an app's `configureUserPoolClient` (a custom endpoint, a proxy, certificate pinning) is not applied; hold a client for the session to sign out through it. A purge revokes nothing. | | 40 | The plugin's own sign-out under a keychain access group | **It saves the signed-out record**, `{"noCredentials":{}}`, in the shared service instead of deleting the session record, on every path that ends a login: `signOut` (local or global), `clearFederationToIdentityPool` and the local sign-out after `deleteUser`, when the record holds a login (user pool tokens or a federated identity) or is already signed out (one already signed out is left as it is, the same bytes). A guest's sign-out still deletes the guest's record, as does one over a record the plugin cannot read, and a sign-out with nothing saved writes nothing, as the client's `.default` writes nothing when there is nothing to sign out. A plugin sign-out of a login then counts as migrated, as a sign-out through the client's `.default` does. The migration rule is unchanged: a login or a signed-out record stops it; a guest or a configuration record alone doesn't stop a login. Not limited to `migrateKeychainItems`. Without an access group the record is still deleted. Changes #21. | +| 41 | A session core made after the dead-token expiry (every handle and provider released, or a relaunch) | **It starts expired, with no event, while the saved login keeps the refresh token found dead.** The proof saves a marker beside the record (`deadToken`: the cause and a SHA-256 of the dead refresh token, or of the credentials when they hold none), in a named session's envelope or `.default`'s sidecar, never in the plugin's record. Bound to the token, so any new login or federation, by any writer, ends it, and it can never lock out a user: `signIn` is allowed on an expired session. The plugin's `isRefreshTokenExpired` is not used: the plugin sets it on whatever record is stored after its own refusal. Best effort; the 30-second count stays per launch (#28). Refines #35. | **#35.** The rule exists because a second writer may still save the refresh that used the token, and a forced refresh refused this way is evidence of the same dead token as an unforced one, so it counts too. Keeping the From 640d5f024900ad70798b3809b80884d588dc5717 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:33:40 -0400 Subject: [PATCH 47/57] docs(auth): release notes for the saved dead-token expiry The reused-token and hosted-UI sign-out rows drop the in-memory qualifier, and a new row says a session that reported sessionExpired restores expired. --- docs/design/RELEASE-NOTES-PENDING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/design/RELEASE-NOTES-PENDING.md b/docs/design/RELEASE-NOTES-PENDING.md index fba00d109e..6ba943c24a 100644 --- a/docs/design/RELEASE-NOTES-PENDING.md +++ b/docs/design/RELEASE-NOTES-PENDING.md @@ -28,14 +28,15 @@ behaviour. | A Cognito internal error (`InternalErrorException`, from either pool) and an error status the SDK does not model are `AuthClientError.service` with no code in every call: `USER_AUTH`, auto, device SRP and passkey sign-ins, `deleteUser`, a sign-out's revoke and global sign-out, and the passkey credential calls included. In 2.63.0 they were `AuthClientError.unknown`, and an internal error in those calls and sign-ins still suggested reporting a bug. A 5xx or an internal error suggests a retry, and any other status says the service refused the request with an error the client does not recognise (the description has the status and message). In a `.partial` sign-out, the revoke's or global sign-out's suggestion says instead that it will not be retried; the global sign-out's says the user's other sessions stay signed in, without the "unless the request reached the service" that only a transport failure's has. Code that matched `.unknown` for these must match `.service(nil, …)`. The credentials providers report both cases the same way, so a provider's `CredentialsError` and its `disposition` don't change. The Auth plugin keeps `AuthError.unknown` | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Errors" | | A refresh, a sign-out's revoke and the passkey credential calls report a transport failure as every other call does: a response that is not JSON, or a bad server response, is `.service` with no code, "The service returned a response that could not be read." and a retry suggestion; a network failure has the network texts, with the network error underneath; any other URL loading failure is "The service request failed.". In 2.63.0 an unreadable body on a refresh kept the engine's text, a passkey call that failed offline said "This should not happen", and an unreadable revoke had an empty description. In a `.partial` sign-out, the revoke's and global sign-out's suggestion says instead that it will not be retried | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Errors" | | A sign-out that can't finish, because the saved record kept changing (`.failed(.storageUnavailable(.interrupted))`) or became unreadable (`.failed(.unknown)`), carries the first server-side failure as its underlying error, the global sign-out's first, mapped as any other call maps it, with the network or retry suggestion. In 2.63.0, after a failed global sign-out, it carried the empty placeholder `revokeTokenError` of a revoke that was never sent, and a revoke that failed on the network said there that the session was signed out on this device and the revoke would not be retried, though the session was still signed in. A sign-in that reaches a challenge as a sign-out, purge or user deletion ends the session throws the sign-in-cancelled `.invalidState`, where it could return the challenge the sign-out had ended | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Errors" | -| A refresh token refused as reused a second time, at least 30 seconds after the first, with nothing saved meanwhile, still expires the session, forced or not. Its `sessionExpired` now says the refresh token was already used and no refreshed tokens were saved, so the session can't be renewed, with `refreshTokenReused` underneath, where 2.63.0 gave the "has expired or been revoked" text of a refused token with `refreshTokenInvalid`. The expiry now holds while the saved login keeps the refresh token proved dead, so another writer saving the same login again, before or after the expiry, no longer clears it: the same credentials in other bytes (the Auth plugin over `.default`, an app extension over a named session), or the plugin's save of them with its expired flag set. In 2.63.0 the next call refreshed the dead token again, and a later refusal sent a second `.sessionExpired`. A login with another refresh token ends the expiry. The expiry lasts while the session is in memory: until every handle and provider for it is released, or a relaunch | [`issues/rollback-behaviours.md`](issues/rollback-behaviours.md) §3 | +| A refresh token refused as reused a second time, at least 30 seconds after the first, with nothing saved meanwhile, still expires the session, forced or not. Its `sessionExpired` now says the refresh token was already used and no refreshed tokens were saved, so the session can't be renewed, with `refreshTokenReused` underneath, where 2.63.0 gave the "has expired or been revoked" text of a refused token with `refreshTokenInvalid`. The expiry now holds while the saved login keeps the refresh token proved dead, so another writer saving the same login again, before or after the expiry, no longer clears it: the same credentials in other bytes (the Auth plugin over `.default`, an app extension over a named session), or the plugin's save of them with its expired flag set. In 2.63.0 the next call refreshed the dead token again, and a later refusal sent a second `.sessionExpired`. A login with another refresh token ends the expiry. The expiry is now saved with the session, beside its record (a named session's envelope, `.default`'s sidecar; the Auth plugin's record is not rewritten), so a new client of the session, and the app after a relaunch, answer the same `sessionExpired` without refreshing the dead token again, where 2.63.0 refreshed it once more, got the same refusal and started the 30-second rule over. A refused refresh token's expiry (and a federated session's refused provider token) is saved the same way. It ends with any new login or federation, whoever saves it. The 30-second count before the expiry is still per launch | [`issues/rollback-behaviours.md`](issues/rollback-behaviours.md) §3 | +| A session that reported `sessionExpired` restores expired after a relaunch, or in a new client once every handle and provider for it was released: it hands out no tokens, makes no refresh, answers `sessionExpired` with the same text, and `signIn` is allowed on it without signing out. In 2.63.0 it restored signed in, refused `signIn` with `.invalidState`, and refreshed again on the next token request. The expiry is saved beside the record, as an additive `deadToken` key that earlier builds ignore, and applies only while the saved login keeps the refresh token found dead | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Sign-in", "Known limitations" | | A federated session's errors name what works on it. When the identity pool no longer accepts its provider token, its `sessionExpired` says so ("Session ""'s provider token is no longer accepted by the identity pool.") and suggests calling `federateToIdentityPool` with a new provider token, where it said the refresh token had expired or been revoked and suggested signing in, which the client refuses on a federated session; the underlying error is unchanged. Its signed-in operations' `notSignedIn`, and `fetchAuthSession`'s `userSubResult` and `userPoolTokensResult`, say the session has no user pool user ("Session "" has no user pool user: it is federated to the identity pool."); they, `getCurrentUser`'s `notSignedIn` and the token provider's `notConfigured` suggest calling `clearFederationToIdentityPool()` or signing the session out before signing in through the user pool. Of the `notSignedIn` texts, only a signed-out session's AWS credentials still say that a signed-out session never falls back to guest credentials | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Federation to the identity pool" | | WebAuthn: a `"WEB_AUTHN"` answer to a first-factor selection that does not offer it is refused with `.validation(field: "challengeResponse")` before anything is sent, on every platform, and the selection stays pending. 2.63.0 accepted it from a fixed list on every OS, and Cognito's WebAuthn challenge then ended the sign-in. The Auth plugin refuses it only below iOS 17.4 and macOS 13.5 and on tvOS and watchOS | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Sign-in steps and confirmSignIn" | | `confirmSignIn` after a TOTP setup whose last request failed with a step (`.resetPassword`, `.confirmSignUp`) throws `.invalidState` ("Cannot use confirmSignIn in the current state. Call signIn to start the sign-in again.") and ends the attempt, as the Auth plugin refuses it, where it never returned and held the session's sign-ins until a sign-out | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Sign-in steps and confirmSignIn" | | A refresh of a carried session that is waiting for its identity, whose save loses to another process's sign-out, answers as signed out: `fetchAuthSession`'s fields and both providers fail with `notSignedIn`, whose disposition is `.discard`. In 2.63.0 every field failed with `sessionExpired`, and the providers with the identity step's error (`CredentialsError.notConfigured` for a known refusal, else `.unknown`) | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Changing the configuration" | | A `.default` login copied from a configuration with an identity pool, after that identity pool is removed, refreshes only when its user pool tokens need it, whatever AWS credentials it kept. In 2.63.0, once those had expired, every token request, signed-in operation and `fetchAuthSession` refreshed it and rewrote the record | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Changing the configuration" | | AWSMobileClient (Amplify v1) logins: a `.default` sign-out or purge clears them before it writes the signed-out login or deletes the login, so a failed write or delete, or an app stopped right after it, leaves none for a plugin build to migrate back. 2.63.0 cleared them after. `fetchAuthSession()` on `.default` that saves a guest over a signed-out record clears them again first, best effort, so a login a sign-out failed to clear is not migrated back once the guest replaces the signed-out record. That is the sign-out's whole clear, so it also removes what AWSMobileClient, or the AWS SDK for iOS used beside the client, saved in those services since the sign-out: an app that uses the SDK's `AWSCognitoCredentialsProvider` loses the identity ID and AWS credentials it cached for any identity pool then, and gets a new identity ID. The guest's clear never runs over an absent record, nor over a label set before anyone signed in (an app that never signed in through the client keeps them for the plugin to migrate), nor for a named session. **Rollback:** plugin releases before 2.63.0 migrate those logins over whatever `.default` holds, with no check; 2.63.0 keeps a user signed in through the client, but migrates over a guest and over a signed-out `.default`; later releases also keep a signed-out `.default`, until a guest replaces it (the Auth plugin row above) | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Rolling back and forward" | -| A hosted-UI sign-out whose logout page is closed (by the user, `cancelWebUISignIn()` or `resetSystemSheet()`) on a `.default` session the Auth plugin saved as expired returns `.failed(.userCancelled)` and keeps the session, where 2.63.0 signed it out on the plugin's saved flag (`.complete`, or `.partial` with no `hostedUIError`). On a session a refresh found expired while it was in memory (until every handle and provider for it is released, or a relaunch), a closed page still signs out on this device, with `.partial`'s `hostedUIError` `.userCancelled`, but only while the login being signed out still has the refresh token found dead: a new login another app in the access group saved since is kept, `.failed(.userCancelled)`, where 2.63.0 revoked it and signed it out | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Hosted-UI sign-out" | +| A hosted-UI sign-out whose logout page is closed (by the user, `cancelWebUISignIn()` or `resetSystemSheet()`) on a `.default` session the Auth plugin saved as expired returns `.failed(.userCancelled)` and keeps the session, where 2.63.0 signed it out on the plugin's saved flag (`.complete`, or `.partial` with no `hostedUIError`). On a session a refresh found expired, in this launch or an earlier one, a closed page still signs out on this device, with `.partial`'s `hostedUIError` `.userCancelled`, but only while the login being signed out still has the refresh token found dead: a new login another app in the access group saved since is kept, `.failed(.userCancelled)`, where 2.63.0 revoked it and signed it out | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Hosted-UI sign-out" | | `cancelWebUISignIn()`: a cancelled passkey call throws `.userCancelled` once it has released the system sheet, as a hosted-UI sign-in does, so `systemSheetHolder` no longer names the session by then, where 2.63.0 answered while the sheet was still closing and an immediate retry got `browserBusy`. If the sheet isn't released within a second, the call throws anyway, and the sheet stays held until the passkey ceremony finishes | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Cancelling and recovering" | | Hosted UI: a second hosted-UI sign-in on a session whose first is still queued behind another session's sheet throws `browserBusy(holder:)` naming the session that holds the sheet ("Session "work" is already waiting for the system sheet, which session "home" holds."), where it named itself. One whose first is in progress but neither holds nor waits for the sheet (before it asks, or committing after) says "Session "work" already has a hosted-UI sign-in in progress.", adding ", and session "home" holds the system sheet" when another session holds it; `holder` is then that session, or the session itself. A second call on a session whose retry is queued behind its own closing sheet (after `cancelWebUISignIn()`, while the cancelled browser is still closing) says "Session "work"'s previous system sheet is still closing." and suggests "The earlier call is queued and starts by itself once the sheet has closed: wait for it to finish, or cancel it. One session queues for the sheet at most once.", where 2.63.0 refused a second hosted-UI sign-in with "Session "work" already has a system sheet in progress.", and the sheet lock refused any other call of the queued session (a hosted-UI sign-out page, say) with "Session "work" is already waiting for the system sheet, which session "work" holds.". A sign-out, purge or user deletion that lands just as a hosted-UI attempt begins makes that sign-in throw the sign-in-cancelled `.invalidState` ("The sign-in was cancelled: the session was signed out, purged or deleted while it was in progress."), as one landing at any other point does, where it threw `.userCancelled`. A hosted-UI response whose tokens fail the client's checks (another app client or user pool, another sign-in's nonce, no single user) suggests "Sign in again. If it keeps failing, check that the configuration's user pool, app client and hosted UI domain belong together.", where it suggested `prompt: [.login]`, which cannot change what the tokens say; a user other than the one `identityExpectation` asked for still suggests it | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Hosted UI" | | Texts: the recovery suggestion of either pool's invalid-configuration answer (`InvalidUserPoolConfigurationException`, `InvalidIdentityPoolConfigurationException`, both `.configuration`) names the user pool's settings (device tracking, for the device calls), the identity pool's settings and the client's configuration, where it named only the user pool's. `resendSignUpCode` with an empty username throws `.validation(field: "username")` saying "Username is required to resendSignUpCode", where it named `confirmSignUp`, as the plugin's does. A `resendSignUpCode` answer with no code delivery details is `.unknown` with the plugin's texts ("Unexpected error occurred with message: Unable to get Auth code delivery details"), where it had its own. The warning logged when an interrupted sign-in's record could not be deleted says that a signed-in or federated session's next restore deletes it, and that a signed-out or guest session's next restore may offer the challenge again until it is 15 minutes old, where it said the next restore deletes it | — | From 70f57e460bc7c4a0c1986fba1396b2abf7ede9a2 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:31:19 -0400 Subject: [PATCH 48/57] ci(auth): log a fixed text when AppSync refuses to extend a live code sink key The key extension Lambda passed on the SDK's error as it came when AppSync refused to extend a key that has not expired, so Lambda logged the SDK's message and stack. It now throws a fixed text carrying only the error's name, as the expired-key branch already does, and says a rerun of provision-ci.sh extends the key in place. --- .../infra/ci/lambda/extend-api-key/index.mjs | 5 ++++- .../extend-api-key/test_extend_api_key.mjs | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/index.mjs b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/index.mjs index 69489046af..02096e26e6 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/index.mjs +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/index.mjs @@ -75,7 +75,10 @@ export async function run(appsync, now, limits) { throw new Error(`AppSync refused to extend the tests' API key, which expired on ${day(step.from)} ` + `(${(error && error.name) || "error"}). ${RERUN}`); } - throw error; + // A fixed text with the error's name only, never the SDK's message, which Lambda would log with its stack. + throw new Error(`AppSync refused to extend the tests' API key, which expires on ${day(step.from)} ` + + `(${(error && error.name) || "error"}). The key is unchanged: a rerun of infra/ci/provision-ci.sh ` + + "--apply --scope all extends it in place."); } console.log(`Extended the tests' API key${step.action === "extend-expired" ? ", which had expired," : ""} ` + `from ${day(step.from)} to ${day(step.expires)}.`); diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/test_extend_api_key.mjs b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/test_extend_api_key.mjs index 11942f9df4..4b26e57fcd 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/test_extend_api_key.mjs +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda/extend-api-key/test_extend_api_key.mjs @@ -21,7 +21,8 @@ const EXTENDED = extendedExpiry(NOW, LIMITS); const key = (id, expires, extra = {}) => ({ id, description: DESCRIPTION, expires, ...extra }); -// A fake AppSync over `keys`: update() sets a key's expiry, or throws `refusal` (an error name) when it is given. +// A fake AppSync over `keys`: update() sets a key's expiry, or throws `refusal` (an error name) when it is given, +// with a message that names the key, as an SDK error's message might. function fakeAppSync(keys, refusal) { const updates = []; return { @@ -30,7 +31,7 @@ function fakeAppSync(keys, refusal) { update: async (id, expires) => { updates.push({ id, expires }); if (refusal) { - throw Object.assign(new Error("refused"), { name: refusal }); + throw Object.assign(new Error(`refused to update ${id}`), { name: refusal }); } }, }; @@ -108,10 +109,17 @@ test("run extends an expired key when AppSync takes it, and fails with the rerun }); }); -test("run fails when no key is left, and passes on any refusal of a key that has not expired", async () => { +test("run fails when no key is left, and with a fixed text on any refusal of a key that has not expired", async () => { await assert.rejects(run(fakeAppSync([]), NOW, LIMITS), /No "ccit-ci tests" API key is left to extend/); - await assert.rejects(run(fakeAppSync([key("a", NOW + DAY)], "AccessDeniedException"), NOW, LIMITS), - (error) => error.name === "AccessDeniedException"); + const refused = fakeAppSync([key("da2-secret", NOW + DAY)], "AccessDeniedException"); + await assert.rejects(run(refused, NOW, LIMITS), (error) => { + assert.match(error.message, /refused to extend the tests' API key, which expires on .* \(AccessDeniedException\)\. The key is unchanged/); + assert.equal(error.name, "Error"); + assert.equal(error.cause, undefined); + assert.ok(!error.message.includes("da2-secret")); + assert.ok(!String(error.stack).includes("da2-secret")); + return true; + }); }); test("settings need the API, default the limits as provision-ci.sh sets them, and refuse others", () => { From ca4e9d9890fbb05ec301e2ee4699316e1c56dab3 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:31:19 -0400 Subject: [PATCH 49/57] docs(auth): say that replace only warns when gh cannot tell whether a CI run is going The provision-ci.sh header said replace refuses while a CI run that reads the pools is going. It refuses only while gh shows one; when gh is missing or cannot list the runs, it warns and goes on. --- .../Tests/IntegrationTests/infra/ci/provision-ci.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh index a0e694ba1d..f5b2eb7863 100755 --- a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/provision-ci.sh @@ -100,8 +100,9 @@ # replaces one of the pools above (by its name, or the part after ccit-ci-) as the recipe above does: after the # tag check of each, deletes the pool and its uploaded file, for ccit-ci-default also the rotation file, the # identity pool and ccit-ci-new-password-reset, then reruns --apply --scope all --upload. A dry run by default, -# which checks the rerun's dry run too. It refuses any other name, and refuses while gh shows a CI run that -# reads the pools (it warns when gh cannot tell) +# which checks the rerun's dry run too. It refuses any other name, and refuses while gh shows a queued or +# running CI run that reads the pools. When gh is missing, or cannot list the runs (not logged in, say), it +# only warns and goes on: check that no CI run is going first # infra/ci/provision-ci.sh lambda-drift [--record [--accept-deployed]] # read-only: downloads each ccit-ci- Lambda's deployed zip (GetFunction's presigned URL) and compares its # files, by path and content hash, with the zip built from this checkout, or with the files From 636a44cdbdaa3ce7997ff105926f582832c1de14 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 18:32:34 -0400 Subject: [PATCH 50/57] ci: pin that the bundled-file check escapes a missing file's name in its error line Removing the workflow-command escaping in --check-bundled's ::error line left every selector test passing, so a project file could put a newline into a bundled file's name and add lines of its own to the detect log. A new test bundles a missing file named with % and a newline and checks it is named once, as 100%25%0Asure.md, on one ::error line. --- scripts/python/test_affected_targets.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/scripts/python/test_affected_targets.py b/scripts/python/test_affected_targets.py index 554a9ba215..6432443d29 100644 --- a/scripts/python/test_affected_targets.py +++ b/scripts/python/test_affected_targets.py @@ -341,6 +341,31 @@ def test_the_check_fails_on_a_missing_bundled_file(self): self.assertEqual(result.returncode, 1, result.stdout + result.stderr) self.assertIn(GEO_HOST_APP + "GeoStressTests/amplifyconfiguration.json", result.stdout) + def test_the_check_escapes_a_missing_file_name_in_its_error(self): + """A missing bundled file whose name holds `%` and a newline is named in one escaped `::error` line, so the + name can't add lines, or workflow commands, of its own to the log. + + - Given: a scratch checkout whose one project bundles `GeoStressTests/100%sure.md` (a quoted path + with a `\\n` escape) and no such file + - When: + - `--check-bundled` runs over it + - Then: + - it exits 1 with one `::error` line on the project file, naming the file as `100%25%0Asure.md` + - no other line of its output holds the name's second half + """ + pbxproj = GEO_PBXPROJ.replace("path = README.md;", 'path = "100%\\nsure.md";', 1) + root = self.scratch_checkout(pbxproj, files=[GEO_HOST_APP + ".gitignore"]) + with open(os.path.join(root, GEO_HOST_APP + ".gitignore"), "w") as f: + f.write("amplifyconfiguration.json\n") + result = self.run_check(root) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) + lines = result.stdout.splitlines() + errors = [line for line in lines if line.startswith("::error")] + self.assertEqual(len(errors), 1, result.stdout) + self.assertTrue(errors[0].startswith("::error file=" + GEO_PROJECT + "/project.pbxproj::"), errors[0]) + self.assertIn(GEO_HOST_APP + "GeoStressTests/100%25%0Asure.md", errors[0]) + self.assertEqual([line for line in lines if "sure.md" in line and line is not errors[0]], [], result.stdout) + def test_the_check_fails_on_a_project_it_cannot_read(self): """A project the parser can't read fails the check, naming it, rather than passing unchecked. From 94844b0c7b3b9862f37193d9cd49354b62e3cff7 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 19:13:58 -0400 Subject: [PATCH 51/57] fix(auth): revoke a cancelled sign-in's own tokens, not the login the credential store holds --- .../Actions/SignOut/InitiateSignOut.swift | 10 +- .../AuthenticationState+Resolver.swift | 3 +- .../AWSAuthConfirmSignInTaskTests.swift | 108 ++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/SignOut/InitiateSignOut.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/SignOut/InitiateSignOut.swift index cc757ae147..0569575d1e 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/SignOut/InitiateSignOut.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Actions/SignOut/InitiateSignOut.swift @@ -14,9 +14,17 @@ package struct InitiateSignOut: Action { package let signedInData: SignedInData package let signOutEventData: SignOutEventData + /// Whether the sign-out ends the user pool login the credential store holds, when it holds one, rather than + /// `signedInData`. A cancelled sign-in passes `false`: it cancels from `.signedIn` only after the identity pool + /// step failed, before its tokens were saved, so the store can hold only another login (under a keychain access + /// group, another app's), and the tokens to revoke are the sign-in's own. + var revokesSavedLogin = true + package func execute(withDispatcher dispatcher: EventDispatcher, environment: Environment) async { logVerbose("\(#fileID) Starting execution", environment: environment) - let updatedSignedInData = await getUpdatedSignedInData(environment: environment) + let updatedSignedInData = revokesSavedLogin + ? await getUpdatedSignedInData(environment: environment) + : signedInData let event: SignOutEvent // `signOutPresentsBrowser` is this branch's test (a hosted-UI sign-in that shared the browser's // cookies), shared with `AmplifyCognitoClient`, which decides from it whether a sign-out takes the diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/Resolvers/Authentication/AuthenticationState+Resolver.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/Resolvers/Authentication/AuthenticationState+Resolver.swift index f258616a87..e601417fa4 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/Resolvers/Authentication/AuthenticationState+Resolver.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/StateMachine/Resolvers/Authentication/AuthenticationState+Resolver.swift @@ -231,7 +231,8 @@ package extension AuthenticationState { case .cancelSignIn: let action = InitiateSignOut( signedInData: currentSignedInData, - signOutEventData: .init(globalSignOut: false) + signOutEventData: .init(globalSignOut: false), + revokesSavedLogin: false ) let signOutState = SignOutState.notStarted let resolution = StateResolution( diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift index 92b2e2714a..09324fd2c9 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthConfirmSignInTaskTests.swift @@ -937,6 +937,92 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest, @unchecked Senda XCTAssertEqual(answered.get(), 1) } + /// Test that the sign-out cancelling a confirmSignIn whose identity pool step failed revokes the sign-in's own + /// refresh token, not a login the credential store holds + /// + /// - Given: an auth plugin with mocked service whose credential store holds another user pool login, as another + /// app sharing a keychain access group saves one when it signs in during the challenge. + /// `RespondToAuthChallenge` accepts the code and answers tokens with the refresh token `refreshTokenXXX`, the + /// identity pool's `GetId` fails with `InvalidParameterException`, and `RevokeToken` succeeds + /// - When: + /// - I invoke confirmSignIn with a valid confirmation code + /// - Then: + /// - confirmSignIn throws a `.service` error with `AWSCognitoAuthError.invalidParameter` as underlying error + /// - the one revoke sent is for `refreshTokenXXX`, and the saved login's refresh token is not revoked + /// - the state machine is signed out + /// + func testConfirmSignInCancelRevokesTheSignInsOwnTokensNotTheSavedLogin() async throws { + let identityPool = MockIdentity( + mockGetIdResponse: { _ in + throw AWSCognitoIdentity.InvalidParameterException(message: "Invalid parameter passed") + }, + mockGetCredentialsResponse: getCredentials + ) + let revokedTokens = TestBox<[String?]>([]) + let userPool = MockIdentityProvider( + mockRevokeTokenResponse: { input in + revokedTokens.with { $0.append(input.token) } + return RevokeTokenOutput() + }, + mockRespondToAuthChallengeResponse: { _ in + return .testData() + }) + let savedLogin = SignedInData( + signedInDate: Date(), + signInMethod: .apiBased(.userSRP), + cognitoUserPoolTokens: EngineUserPoolTokens( + idToken: "anotherIdToken", + accessToken: "anotherAccessToken", + refreshToken: "anotherAppsRefreshToken", + expiresIn: 3_600 + ) + ) + let base = Defaults.makeDefaultAuthEnvironment( + identityPoolFactory: { identityPool }, + userPoolFactory: { userPool } + ) + let environment = AuthEnvironment( + configuration: base.configuration, + userPoolConfigData: base.userPoolConfigData, + identityPoolConfigData: base.identityPoolConfigData, + authenticationEnvironment: base.authenticationEnvironment, + authorizationEnvironment: base.authorizationEnvironment, + credentialsClient: CredentialStoreHoldingLogin(base: base.credentialsClient, login: savedLogin), + logger: base.logger + ) + let stateMachine = AuthStateMachine( + resolver: AuthState.Resolver(logger: AmplifyEngineLogRouter()), + environment: environment, + initialState: initialState + ) + let holdingPlugin = AWSCognitoAuthPlugin() + holdingPlugin.configure( + authConfiguration: Defaults.makeDefaultAuthConfigData(), + authEnvironment: environment, + authStateMachine: stateMachine, + credentialStoreStateMachine: Defaults.makeDefaultCredentialStateMachine(), + hubEventHandler: MockAuthHubEventBehavior(), + analyticsHandler: MockAnalyticsHandler() + ) + settleConfigureOperationOnTeardown(of: holdingPlugin) + + do { + let result = try await holdingPlugin.confirmSignIn(challengeResponse: "code") + XCTFail("Should not succeed, got \(result)") + } catch AuthError.service(_, _, let underlyingError) { + guard case .invalidParameter = underlyingError as? AWSCognitoAuthError else { + return XCTFail("Underlying error should be AWSCognitoAuthError.invalidParameter, got \(String(describing: underlyingError))") + } + } catch { + XCTFail("Should produce a service error instead of \(error)") + } + + XCTAssertEqual(revokedTokens.get(), ["refreshTokenXXX"]) + guard case .configured(.signedOut, _, _) = await holdingPlugin.authStateMachine.currentState else { + return XCTFail("the sign-in was not cancelled: \(await holdingPlugin.authStateMachine.currentState)") + } + } + /// Test that confirmSignIn still returns the identity pool's error when the sign-out that cancels the sign-in /// cannot clear the credential store /// @@ -1074,6 +1160,28 @@ class AuthenticationProviderConfirmSigninTests: BasePluginTest, @unchecked Senda } } +/// A credential store that forwards to `base` but reads `login` as the saved credentials, as a keychain access group +/// does after another app in the group signs in. +private struct CredentialStoreHoldingLogin: CredentialStoreStateBehavior { + let base: CredentialStoreStateBehavior + let login: SignedInData + + func fetchData(type: CredentialStoreDataType) async throws -> CredentialStoreData { + if case .amplifyCredentials = type { + return .amplifyCredentials(.userPoolOnly(signedInData: login)) + } + return try await base.fetchData(type: type) + } + + func storeData(data: CredentialStoreData) async throws { + try await base.storeData(data: data) + } + + func deleteData(type: CredentialStoreDataType) async throws { + try await base.deleteData(type: type) + } +} + /// A credential store that forwards to `base` but refuses to delete the saved credentials, as a keychain that refuses /// the clear would. private struct CredentialStoreRefusingDelete: CredentialStoreStateBehavior { From 5421190159e7225ec333f14c0c7638b437b37fe2 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 19:14:11 -0400 Subject: [PATCH 52/57] docs(auth): release notes for the revoke of a cancelled sign-in's own tokens --- docs/design/RELEASE-NOTES-PENDING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/design/RELEASE-NOTES-PENDING.md b/docs/design/RELEASE-NOTES-PENDING.md index 6ba943c24a..cfc464b1c6 100644 --- a/docs/design/RELEASE-NOTES-PENDING.md +++ b/docs/design/RELEASE-NOTES-PENDING.md @@ -15,6 +15,7 @@ behaviour. | Security: a sign-up result no longer prints the auto sign-in session. The plugin's tasks log their result at info ("Successfully completed execution for … with result:"), and `AuthSignUpStep.completeAutoSignIn(Session)` had no description of its own, so in 2.63.0 `confirmSignUp`, and a `signUp` Cognito confirms at once (a passwordless sign-up), logged the session in full at info; with the username, the session signs the user in. `AuthSignUpStep` now conforms to `CustomStringConvertible`, `CustomDebugStringConvertible` and `CustomReflectable`, and prints `completeAutoSignIn("")` in `print`, `debugPrint`, interpolation and `dump`, the step and the `AuthSignUpResult` holding it alike; the other steps print as before. An additive API change; `case .completeAutoSignIn(let session)` still reads the session | Anyone reading or collecting info-level logs; apps that print or dump a sign-up result or step | — | | Security: the sign-in's verbose lines no longer print the challenge session. The challenge a sign-in answers (the engine's `RespondToAuthChallenge`) had no description of its own, so every verbose line that printed it by reflection showed its session in full, and the username: the "Sending event" lines of the sign-in actions when Cognito answers with a challenge (SRP, custom auth, `USER_AUTH`, migrate auth, auto sign-in after sign-up, and each `confirmSignIn` answer), and the lines that print a sign-in state holding one. It now prints with the session as `` and its two usernames masked (`alice` as `al*ce`), as the sign-in data already did, in `print`, `debugPrint`, interpolation and `dump`. The other fields print as they did nested in an event or state in 2.63.0; `parameters`, Cognito's challenge parameters, still print in full, and can hold a username (`USERNAME` on a custom challenge). Printed on its own, the challenge now names its type in full (`InternalAWSCognitoAuth.RespondToAuthChallenge(…)`). The user's answer and the TOTP setup's session are the rows above | Anyone reading or collecting verbose logs | — | | Fix: `confirmSignIn` now throws when the user pool accepts the answer but the identity pool step after it fails (`GetId` or `GetCredentialsForIdentity`), with the identity pool's error (for example `AuthError.service` with `AWSCognitoAuthError.invalidParameter`), and logs the verbose line `Authorization reached an error state …`, as `signIn` does. Before it throws, it cancels the sign-in as `signIn` does there: the refresh token the sign-in got is revoked, nothing is saved, and the user is signed out, so `fetchAuthSession()` reports signed out, `getCurrentUser()` throws `signedOut`, and the next `signIn` starts a new sign-in; another `confirmSignIn` throws `invalidState`. In 2.63.0 it never returned, and every Auth call queued behind it waited too | Apps with an identity pool that sign in with a challenge (MFA, a new password, custom auth, a factor selection) | — | +| Fix: a sign-in cancelled because the identity pool step failed after the user pool signed the user in (`signIn`, `confirmSignIn`, `autoSignIn`, `signInWithWebUI`) now revokes the refresh token that sign-in got. In 2.63.0 the cancel's sign-out revoked the user pool login saved in the keychain instead, when there was one, and left the sign-in's own refresh token valid; with a keychain access group set, that saved login can be one another app in the group saved during the sign-in. The cancel no longer reads the saved session first, so with nothing saved it no longer logs the error line `Unable to update credentials with error: …`. Under an access group its sign-out still writes the signed-out record over a login another app saved (the access-group row below), so that app is signed out at its next `fetchAuthSession`, though its refresh token is no longer revoked | Apps with an identity pool; apps that share a keychain access group | — | | Upgrading from the AWS Mobile SDK (AWSMobileClient, Amplify v1): a signed-out record (`{"noCredentials":{}}`) in the plugin's saved session now stops the migration, as a login does: nothing migrates over it, the old keychain locations are cleared, and the plugin logs the info line `A signed-out session already exists, discarding legacy credentials`. In 2.63.0 the migration read that record as no session, so an old AWSMobileClient login migrated over it and signed the user back in. A sign-out through `AmplifyCognitoClient`'s default session saves that record, and so, with a keychain access group set, does the plugin's own sign-out (the access-group row below). Without an access group the plugin's sign-out deletes the record, so an app that uses only the plugin without one is not affected, unless it later leaves an access group with `.none(migrateKeychainItemsOfUserSession: true)`: that carries a signed-out record from the group into the unshared service, where it stops the migration too. With one, the plugin's sign-out clears the old keychain locations of the app that signs out, which are named after its bundle ID; so an old login that survived that clear, or was saved there since, or that another app sharing the group still holds in its own old locations, is now dropped and cleared at that app's next launch instead of migrated. In 2.63.0 another app's old login was migrated over a sign-out (the record was deleted), and dropped only over a login the group held. A guest is still not a login: a guest saved over the signed-out record (by the plugin, an app extension or the client) lets an old login migrate over it again | Apps upgraded from AWSMobileClient or Amplify v1 that use `AmplifyCognitoClient`'s default session or a keychain access group, including app suites whose apps share an access group, or roll back or forward between a client build and a plugin build | [Design](AGREED-DESIGN-AmplifyCognitoClient.md) §4.8 | | Behaviour change: with a keychain access group set, ending a login through the plugin (`signOut`, local or global; `clearFederationToIdentityPool`; the sign-out after `deleteUser`) now saves the signed-out record, `{"noCredentials":{}}`, in the shared keychain service instead of deleting the session record, when the record holds a login (user pool tokens or a federated identity); a record already signed out is left as it is. The login is replaced in one keychain update that writes only over a record still there, so a sign-out still completes when another process in the group deletes the record after the plugin read it (an older plugin's or a guest's sign-out, the client's purge), and writes nothing. A guest's sign-out still deletes the guest's record, as does a sign-out over a record the plugin cannot read, and a sign-out with nothing saved writes nothing, so neither stops a login's migration. Those are the bytes `AmplifyCognitoClient`'s default session saves when it signs a session out, and every plugin release reads them as signed out (releases back to 2.10.0 decode them as `AmplifyCredentials.noCredentials`, the value for no saved session). The next sign-in replaces the record. The record then stops an app extension's first access-group migration (`migrateKeychainItems: true`), as a login does, so a stale login in the extension's own keychain is never moved in over a user who signed out. In 2.63.0 only the device records a sign-in leaves stopped it; every user-pool sign-in saves the advanced-security device ID, so that already stopped it after a user-pool user's sign-out, and the record matters mainly after the sign-out of a federated identity (`clearFederationToIdentityPool`), which leaves no device record. An extension's first access-group transition without migration counts the record too, as it counts a login, and keeps the extension's own old keychain items instead of clearing them. The AWSMobileClient (Amplify v1) migration treats the record as a signed-out session (the row above): it migrates nothing over it and clears the old locations, which the sign-out already clears for the app that signs out, until a guest replaces the record; a guest `AmplifyCognitoClient`'s default session fetches over it clears those logins first, best effort. A plugin sign-out after a configuration change that copied the login now survives a rollback to the build with the earlier configuration, whose rule copies the signed-out record over the earlier copy, as after a sign-out through the client. Without an access group nothing changes: the record is deleted. `AmplifyCognitoClient.storedSessions(…, includingSignedOut: true)` in that access group now lists `.default` as a signed-out row after a plugin sign-out of a login, with no label or username, where it listed no `.default` row; when the client saved a label or username beside the record, an absent record was already listed as a signed-out row with them, and still is. The default listing is unchanged. A process that reads a record another process signed out sees the signed-out record instead of none: `clearFederationToIdentityPool()` there, while still federated in memory, throws `AuthError.service("Unable to find credentials to clear for federation.")` where it threw `AuthError.unknown("Unable to clear credential store: …")`, and a sign-out there no longer logs the error line `Unable to update credentials with error: …`. Logs under an access group: every plugin sign-out (a guest's and one with nothing saved included) first reads the record, which logs the keychain store's verbose `[KeychainStore]` lines for opening the shared service and retrieving an item (`Initialized keychain…`, `Started retrieving…`, then `Successfully retrieved…` or `No Keychain item found…`); a sign-out of a login then logs the lines for replacing an item only if present (``Replacing `Data` only if present…``, then `Conditional write succeeded…`, or `Conditional write refused…` when the record is gone) in place of those for removing one, and a sign-out over a record already signed out logs neither, and at the next launch the plugin no longer logs the info line `No existing session found.`, and its verbose credential-store line sends `completedOperation` with no credentials where it sent `throwError(itemNotFound)` | Apps with a keychain access group, especially with app extensions; apps that list stored sessions with `includingSignedOut: true` | [Decisions](auth-client-DECISIONS.md), decision 40 | | One new log line of the `InternalAWSCognitoAuth/.swift` form, the info line above in `MigrateLegacyCredentialStore`, so the state-machine actions' `\(#fileID)` sites go from 254 to 255, in 66 files | Anyone filtering or parsing plugin logs by file prefix | — | From 5ae12171fc3dc3ff2bbdaa9441bee8054e51a60f Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 19:14:40 -0400 Subject: [PATCH 53/57] docs(auth): say the plugin's confirmSignIn hung on a failed identity pool step only in 2.63.0 and earlier --- AmplifyClients/AmplifyCognitoClient/README.md | 2 +- .../AWSAuthFetchSessionTokenRotationTests.swift | 2 +- docs/design/auth-client-DECISIONS.md | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/README.md b/AmplifyClients/AmplifyCognitoClient/README.md index 62e27f04a5..88363ba17d 100644 --- a/AmplifyClients/AmplifyCognitoClient/README.md +++ b/AmplifyClients/AmplifyCognitoClient/README.md @@ -1850,7 +1850,7 @@ The client keeps the plugin's semantics except where listed here and in the sect | Saved login | One record | `.default` uses the plugin's own record; named sessions keep their own, which the plugin never reads | | Session label and signed-out row | None | Kept beside the plugin's record for `.default`, bound to its user | | Second sign-in | Refused while signed in | Refused only on the same session, and allowed there once the session reported `sessionExpired` in this process | -| Identity pool step fails after the user pool signed the user in | `signIn` throws and signs the user out. `confirmSignIn` doesn't return, and the Auth calls queued behind it (`fetchAuthSession` and `signOut` included) wait | `signIn` and `confirmSignIn` both throw the identity pool's error, revoke the tokens just issued and leave the session signed out | +| Identity pool step fails after the user pool signed the user in | `signIn` and `confirmSignIn` both throw the identity pool's error, revoke the tokens just issued and sign the user out; under a keychain access group that sign-out writes the signed-out record over a login another app saved meanwhile. In 2.63.0 and earlier, `confirmSignIn` didn't return, and the Auth calls queued behind it (`fetchAuthSession` and `signOut` included) waited | `signIn` and `confirmSignIn` both throw the identity pool's error, revoke the tokens just issued and leave the session signed out, and the saved record as it is | | Events | Hub | Per-session `AsyncStream`s | | Events for `autoSignIn()` and `clearFederationToIdentityPool()` | No Hub event | `.signedIn` and `.signedOut` | | Storage failure | Reported as signed out | `.unavailable(reason)` / `storageUnavailable`, never signed out | diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFetchSessionTokenRotationTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFetchSessionTokenRotationTests.swift index 82c4cf94a8..c8e1f10554 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFetchSessionTokenRotationTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/AuthorizationTests/AWSAuthFetchSessionTokenRotationTests.swift @@ -147,7 +147,7 @@ class AWSAuthFetchSessionTokenRotationTests: XCTestCase, @unchecked Sendable { /// Valid user-pool-only tokens left by a failed identity step are returned without a request /// /// - Given: A session error holding valid user-pool-only tokens, as a `confirmSignIn` whose identity-pool - /// step failed leaves it, with an identity pool configured + /// step failed left it in 2.63.0 and earlier, with an identity pool configured /// - When: /// - fetchAuthSession is called without force refresh /// - Then: diff --git a/docs/design/auth-client-DECISIONS.md b/docs/design/auth-client-DECISIONS.md index bedf53d6e4..1e2984a45e 100644 --- a/docs/design/auth-client-DECISIONS.md +++ b/docs/design/auth-client-DECISIONS.md @@ -536,7 +536,8 @@ one, and a copy an extension rewrote are never carried over it. **#32.** The user pool issued tokens, but the sign-in can't finish what its configuration asks for, so the client revokes them and leaves the session signed out, for `signIn` and `confirmSignIn` alike. The plugin's `confirmSignIn` -doesn't return in that case. The README's differences table and the release notes record it. +didn't return in that case in 2.63.0 and earlier; later releases throw and sign the user out, as its `signIn` does. +The README's differences table and the release notes record it. **#33.** A session signed out in this process adopts the login an app extension, the Auth plugin or another call has saved since. An unforced fetch handed it out as it was, so it could return tokens and AWS credentials that had From 9e3a60b4e4d7d3293815d3fa7668ffbca6e87d30 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 19:15:08 -0400 Subject: [PATCH 54/57] docs(auth): say a plugin sign-out under an access group leaves a signed-out record as it is --- .../AccessGroupSignedOutRecordTests.swift | 19 ++++++++++--------- docs/design/auth-client-DECISIONS.md | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift index 1745689ed0..6bbf08f67c 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/CredentialStore/AccessGroupSignedOutRecordTests.swift @@ -19,11 +19,11 @@ import XCTest /// Under an access group, every plugin path that ends a login saves the signed-out record, `{"noCredentials":{}}`, in /// the shared service instead of deleting the session record: sign-out, local and global, clearing the federation to -/// the identity pool, and the local sign-out after a deleted user. So does one over a record that is already signed -/// out. The record then stops an app extension's first access-group migration, as a login does, so the extension's -/// stale login is never moved in over the user who signed out. A guest's sign-out still deletes the guest's record, -/// and a sign-out with nothing saved writes nothing, so neither stops a login's migration. Without an access group -/// the record is still deleted. +/// the identity pool, and the local sign-out after a deleted user. One over a record that is already signed out +/// leaves it as it is. The record then stops an app extension's first access-group migration, as a login does, so the +/// extension's stale login is never moved in over the user who signed out. A guest's sign-out still deletes the +/// guest's record, and a sign-out with nothing saved writes nothing, so neither stops a login's migration. Without an +/// access group the record is still deleted. /// /// The plugin runs its real credential store, through its test seam, over an in-memory keychain shared with the /// extension and with the Cognito client's `.default` session. Only the Cognito service calls are mocked. @@ -89,19 +89,20 @@ final class AccessGroupSignedOutRecordTests: XCTestCase, @unchecked Sendable { XCTAssertNil(keychain.value(service: service, account: sessionAccount)) } - /// Test that under an access group the store saves the signed-out record only over a login or a signed-out record + /// Test that under an access group the store saves the signed-out record only over a login, and leaves a + /// signed-out record as it is /// /// - Given: the shared service holding, in turn, a user-pool login, a federated identity, the signed-out record, a /// guest's identity, a record the store cannot decode, and nothing /// - When: /// - the store with the access group ends its session /// - Then: - /// - over a login, a federated identity or the signed-out record, the shared service holds - /// `{"noCredentials":{}}` + /// - over a login or a federated identity, the shared service holds `{"noCredentials":{}}` + /// - the signed-out record is left as it is, so the shared service still holds `{"noCredentials":{}}` /// - over a guest's identity or an unreadable record, the record is deleted, as before /// - with nothing saved, nothing is written /// - func testTheStoreSavesTheSignedOutRecordOnlyOverALoginOrASignedOutRecord() throws { + func testTheStoreSavesTheSignedOutRecordOnlyOverALoginAndLeavesASignedOutOne() throws { let federated = AmplifyCredentials.identityPoolWithFederation( federatedToken: FederatedToken(token: "federated-token", provider: .facebook), identityID: "federated-identity", diff --git a/docs/design/auth-client-DECISIONS.md b/docs/design/auth-client-DECISIONS.md index 1e2984a45e..351c91193e 100644 --- a/docs/design/auth-client-DECISIONS.md +++ b/docs/design/auth-client-DECISIONS.md @@ -561,7 +561,7 @@ client's README and the release notes. #38 and #39 keep limitations that the cli | 37 | A Cognito internal error (`InternalErrorException`, from either pool) and an error status the SDK does not model, in the client | **`.service` with no code**, in every call, `USER_AUTH`, auto, device SRP and passkey sign-ins, `deleteUser`, a sign-out's revoke or global sign-out, and the passkey credential calls included, with a recovery suggestion to retry (a 5xx, an internal error) or naming the refusal (a 4xx). In a `.partial` sign-out, the revoke's or global sign-out's suggestion says instead that it will not be retried; a sign-out that could not finish (`.failed`) carries the failure as any other call maps it. The plugin keeps `AuthError.unknown`. | | 38 | A named session's configuration marker left naming an earlier configuration, by a purge stopped before its last step | **Not changed.** The next restore under the purge's configuration carries the session from the earlier one again, and the README says to purge the session again before building a client for it. | | 39 | `signOutStoredSession` with no client for the session live in the process, and `configureUserPoolClient` | **Not given one.** Its revoke uses SDK clients built from the configuration alone, so an app's `configureUserPoolClient` (a custom endpoint, a proxy, certificate pinning) is not applied; hold a client for the session to sign out through it. A purge revokes nothing. | -| 40 | The plugin's own sign-out under a keychain access group | **It saves the signed-out record**, `{"noCredentials":{}}`, in the shared service instead of deleting the session record, on every path that ends a login: `signOut` (local or global), `clearFederationToIdentityPool` and the local sign-out after `deleteUser`, when the record holds a login (user pool tokens or a federated identity) or is already signed out (one already signed out is left as it is, the same bytes). A guest's sign-out still deletes the guest's record, as does one over a record the plugin cannot read, and a sign-out with nothing saved writes nothing, as the client's `.default` writes nothing when there is nothing to sign out. A plugin sign-out of a login then counts as migrated, as a sign-out through the client's `.default` does. The migration rule is unchanged: a login or a signed-out record stops it; a guest or a configuration record alone doesn't stop a login. Not limited to `migrateKeychainItems`. Without an access group the record is still deleted. Changes #21. | +| 40 | The plugin's own sign-out under a keychain access group | **It saves the signed-out record**, `{"noCredentials":{}}`, in the shared service instead of deleting the session record, on every path that ends a login: `signOut` (local or global), `clearFederationToIdentityPool` and the local sign-out after `deleteUser`, when the record holds a login (user pool tokens or a federated identity); a record already signed out is left as it is. A guest's sign-out still deletes the guest's record, as does one over a record the plugin cannot read, and a sign-out with nothing saved writes nothing, as the client's `.default` writes nothing when there is nothing to sign out. A plugin sign-out of a login then counts as migrated, as a sign-out through the client's `.default` does. The migration rule is unchanged: a login or a signed-out record stops it; a guest or a configuration record alone doesn't stop a login. Not limited to `migrateKeychainItems`. Without an access group the record is still deleted. Changes #21. | | 41 | A session core made after the dead-token expiry (every handle and provider released, or a relaunch) | **It starts expired, with no event, while the saved login keeps the refresh token found dead.** The proof saves a marker beside the record (`deadToken`: the cause and a SHA-256 of the dead refresh token, or of the credentials when they hold none), in a named session's envelope or `.default`'s sidecar, never in the plugin's record. Bound to the token, so any new login or federation, by any writer, ends it, and it can never lock out a user: `signIn` is allowed on an expired session. The plugin's `isRefreshTokenExpired` is not used: the plugin sets it on whatever record is stored after its own refusal. Best effort; the 30-second count stays per launch (#28). Refines #35. | **#35.** The rule exists because a second writer may still save the refresh that used the token, and a forced From 71454933c8752b073c76d506ce9ad46f7f7c8534 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 19:15:30 -0400 Subject: [PATCH 55/57] docs(auth): tell apps with their own AuthSignUpStep description conformance to remove it --- docs/design/RELEASE-NOTES-PENDING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/RELEASE-NOTES-PENDING.md b/docs/design/RELEASE-NOTES-PENDING.md index cfc464b1c6..9c949e5c7e 100644 --- a/docs/design/RELEASE-NOTES-PENDING.md +++ b/docs/design/RELEASE-NOTES-PENDING.md @@ -12,7 +12,7 @@ behaviour. |---|---|---| | Security: `confirmSignIn`'s answer is no longer printed on the sign-in's verbose lines. While Cognito checks an answer, the sign-in's challenge state (`SignInChallengeState.verifying`) held it in full and printed it by reflection, so the plugin's verbose `Checking next step for:` line showed every answer `confirmSignIn` sent through a challenge: an SMS, email or TOTP code, a password after a `PASSWORD` or `PASSWORD_SRP` factor selection, a new password, a custom challenge's answer. In 2.63.0 each one was logged in full there. The state no longer holds the answer. The answer's own printed form, which the TOTP setup's verbose lines (`Checking next step for:`, `Auth state change`) and the `Sending event` line of an answer sent again after Cognito refused the saved device print, showed it masked (`S3*******21`, four characters of a six-digit code); it now shows ``, in `print`, `debugPrint`, interpolation and `dump` | Anyone reading or collecting verbose logs | — | | Security: the TOTP setup's verified session is no longer printed. When a sign-in sets up TOTP, `VerifyTOTPSetup`'s verbose `Sending event` line printed the session `VerifySoftwareToken` returns, which answers the MFA setup challenge and finishes the sign-in, in full in 2.63.0. It now prints `respondToAuthChallenge("")`, and the event redacts the session in every printed form | Anyone reading or collecting verbose logs | — | -| Security: a sign-up result no longer prints the auto sign-in session. The plugin's tasks log their result at info ("Successfully completed execution for … with result:"), and `AuthSignUpStep.completeAutoSignIn(Session)` had no description of its own, so in 2.63.0 `confirmSignUp`, and a `signUp` Cognito confirms at once (a passwordless sign-up), logged the session in full at info; with the username, the session signs the user in. `AuthSignUpStep` now conforms to `CustomStringConvertible`, `CustomDebugStringConvertible` and `CustomReflectable`, and prints `completeAutoSignIn("")` in `print`, `debugPrint`, interpolation and `dump`, the step and the `AuthSignUpResult` holding it alike; the other steps print as before. An additive API change; `case .completeAutoSignIn(let session)` still reads the session | Anyone reading or collecting info-level logs; apps that print or dump a sign-up result or step | — | +| Security: a sign-up result no longer prints the auto sign-in session. The plugin's tasks log their result at info ("Successfully completed execution for … with result:"), and `AuthSignUpStep.completeAutoSignIn(Session)` had no description of its own, so in 2.63.0 `confirmSignUp`, and a `signUp` Cognito confirms at once (a passwordless sign-up), logged the session in full at info; with the username, the session signs the user in. `AuthSignUpStep` now conforms to `CustomStringConvertible`, `CustomDebugStringConvertible` and `CustomReflectable`, and prints `completeAutoSignIn("")` in `print`, `debugPrint`, interpolation and `dump`, the step and the `AuthSignUpResult` holding it alike; the other steps print as before. An additive API change; `case .completeAutoSignIn(let session)` still reads the session. An app's own `CustomStringConvertible`, `CustomDebugStringConvertible` or `CustomReflectable` conformance for `AuthSignUpStep` now gets a compiler warning, and Amplify's conformance is used instead: remove the app's own | Anyone reading or collecting info-level logs; apps that print or dump a sign-up result or step, or that declare their own description conformance for `AuthSignUpStep` | — | | Security: the sign-in's verbose lines no longer print the challenge session. The challenge a sign-in answers (the engine's `RespondToAuthChallenge`) had no description of its own, so every verbose line that printed it by reflection showed its session in full, and the username: the "Sending event" lines of the sign-in actions when Cognito answers with a challenge (SRP, custom auth, `USER_AUTH`, migrate auth, auto sign-in after sign-up, and each `confirmSignIn` answer), and the lines that print a sign-in state holding one. It now prints with the session as `` and its two usernames masked (`alice` as `al*ce`), as the sign-in data already did, in `print`, `debugPrint`, interpolation and `dump`. The other fields print as they did nested in an event or state in 2.63.0; `parameters`, Cognito's challenge parameters, still print in full, and can hold a username (`USERNAME` on a custom challenge). Printed on its own, the challenge now names its type in full (`InternalAWSCognitoAuth.RespondToAuthChallenge(…)`). The user's answer and the TOTP setup's session are the rows above | Anyone reading or collecting verbose logs | — | | Fix: `confirmSignIn` now throws when the user pool accepts the answer but the identity pool step after it fails (`GetId` or `GetCredentialsForIdentity`), with the identity pool's error (for example `AuthError.service` with `AWSCognitoAuthError.invalidParameter`), and logs the verbose line `Authorization reached an error state …`, as `signIn` does. Before it throws, it cancels the sign-in as `signIn` does there: the refresh token the sign-in got is revoked, nothing is saved, and the user is signed out, so `fetchAuthSession()` reports signed out, `getCurrentUser()` throws `signedOut`, and the next `signIn` starts a new sign-in; another `confirmSignIn` throws `invalidState`. In 2.63.0 it never returned, and every Auth call queued behind it waited too | Apps with an identity pool that sign in with a challenge (MFA, a new password, custom auth, a factor selection) | — | | Fix: a sign-in cancelled because the identity pool step failed after the user pool signed the user in (`signIn`, `confirmSignIn`, `autoSignIn`, `signInWithWebUI`) now revokes the refresh token that sign-in got. In 2.63.0 the cancel's sign-out revoked the user pool login saved in the keychain instead, when there was one, and left the sign-in's own refresh token valid; with a keychain access group set, that saved login can be one another app in the group saved during the sign-in. The cancel no longer reads the saved session first, so with nothing saved it no longer logs the error line `Unable to update credentials with error: …`. Under an access group its sign-out still writes the signed-out record over a login another app saved (the access-group row below), so that app is signed out at its next `fetchAuthSession`, though its refresh token is no longer revoked | Apps with an identity pool; apps that share a keychain access group | — | From 9e4cc8dcf00fc004363c4f665a9bcde9818716c2 Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Thu, 29 Oct 2026 19:35:46 -0400 Subject: [PATCH 56/57] ci(auth): record the CI Lambdas' deployed files as the drift baseline --- .../infra/ci/lambda-baseline.json | 4623 +++++++++++++++++ 1 file changed, 4623 insertions(+) create mode 100644 AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda-baseline.json diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda-baseline.json b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda-baseline.json new file mode 100644 index 0000000000..cec621bc7f --- /dev/null +++ b/AmplifyClients/AmplifyCognitoClient/Tests/IntegrationTests/infra/ci/lambda-baseline.json @@ -0,0 +1,4623 @@ +{ + "functions": { + "create-auth-challenge": { + "files": { + "triggers.mjs": "a6804658073b5ab9c7b05a61a046c22abaa0818eb47f74ceb0d731480a7ef180" + }, + "sources": "ae794739623770c99a8650a5ab95e2dbcd6ce6a56065d2055534791571f465cf" + }, + "custom-sender": { + "files": { + "index.mjs": "724ca958bf1205b3c7f61c91982d17cd18590d96dcb80f2e85642febb9b13187", + "node_modules/@aws-crypto/branch-keystore-node/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/branch-keystore-node/README.md": "de801015485eaadbd2ac4d6f47f771485d1e82c9965ece538a22de2eb7a985a7", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/branch_keystore.d.ts": "1e57ac03bbb8a99d411d2a814236f30627ba9737767c2f8319a55b4e2db55f56", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/branch_keystore.d.ts.map": "e70eaddb7d1f372604e5e137bff46aa0b8ed5bbb71d2e3b62dfc61b3a69e2e6e", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/branch_keystore.js": "d930d0c3a6b3c1531e76e20fcfa999587a2e3b5e333870ad3adcae17fec8d15c", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/branch_keystore_helpers.d.ts": "0c06eac33b62e9dc2e82aa9195f4d0946b3ea56ff5c541a5f1fcd83a32cfaeb3", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/branch_keystore_helpers.d.ts.map": "0b3a47337dc85a57e8c1885aa254de9470551bfcdf0fa276b7af390f336376f1", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/branch_keystore_helpers.js": "c85b0a68d2ad5b971821b88f7438c7821561c7baadc693a5ff666c163dbd6f18", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/branch_keystore_structures.d.ts": "d2c578a1510c60aae913ea659bf7049b464310e477178066e01845b180a273c6", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/branch_keystore_structures.d.ts.map": "68114136c8fff6acafff38e76383386030bb5677e7e40163f16351696d73d8eb", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/branch_keystore_structures.js": "c0ed01ca4e2f5509d8b2a4c5571bd9697873c70ed0ec96c452cf679603874271", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/constants.d.ts": "c5d67e613df216ae3010e49b3f34fc9ad5ecf31dc09ebd00a41d6ce2febe8df5", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/constants.d.ts.map": "000f22968d543eb298f7077460d6ed015249a31f052c887506239d10c9a8177b", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/constants.js": "7ada84aa2897b4c3cab181033813f15f63f81e94a980062739cc2f34c5f5dc62", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/dynamodb_key_storage.d.ts": "01e646877f4fa8e69f962c295cc2533fa16e5cd1415d2f52a0bc46237f730aea", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/dynamodb_key_storage.d.ts.map": "b90cdc650ffb50041cad7f5f742f28a33c6cd604f08c9c1a734a0b4bde5cbd0f", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/dynamodb_key_storage.js": "c0319483e8bfe909f878db9b5b159b023811ce184ac523e754625fd4bfdec33a", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/index.d.ts": "024971a9e3841f78522cdfe5df16b57fcb81bf95f0a4b1f8d8e565c294bfb108", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/index.d.ts.map": "48c3ce6993eef627f82b0612468b0dfb4fea2fb560c38833c2368dc1ec0e1442", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/index.js": "7d583e6b28daa1817b76f1c3b68b7b080da2bc53a56e4b6cb56060bd7f4fbd71", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/key_helpers.d.ts": "d2c9cab22da7d0d32e6791bd286abb7335c91f9c68894bab7b44a5ff3c2b515e", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/key_helpers.d.ts.map": "8f8fa3b61b4f95b715b50c8aae8a7351cf41f0b03da23ac95925fe2997b2a87d", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/key_helpers.js": "0e6dd100262787e4c0a31ffd7bb193df361a2052d24ab4e4d4d022dcb0d16c55", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/kms_config.d.ts": "429b66a1be0ea9ff2ec8e785b1377ab67e8187e815f00e11ca953e2385bdecdc", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/kms_config.d.ts.map": "4022072e930cba6c4d46f87f093a9253df37d7b44e227c3b102935789ee206c8", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/kms_config.js": "425597d8ec84f4f1431e9590fad97a47c0d6777b76d722fc5a0d2971f0091b38", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/types.d.ts": "aca384b0f70f1121adc7cd095ad8d1ae88bbd249a1ce06209f3dc8c44b568553", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/types.d.ts.map": "939b5a16835e61e9f074285449578c06ba4a736d63f2ae8af1995a3ca6ad0a1d", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/types.js": "af57fcf06b1a2bc66ac26f6440a21b7ff2913aef51793e794fee03671d26029c", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/version.d.ts": "a1811365ce81305883060dcbc980fd4e39812e56a82d5bcf9596d70efe90ab86", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/version.d.ts.map": "12c7e8136d70bcc364b01ab4709d2002dcdbe4c589296b50b312a8b1380f01c2", + "node_modules/@aws-crypto/branch-keystore-node/build/main/src/version.js": "b18e805e2a56360c346b31e73d0dfe831557b6daf44541c8cb01642f9c8b11df", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/branch_keystore.d.ts": "1e57ac03bbb8a99d411d2a814236f30627ba9737767c2f8319a55b4e2db55f56", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/branch_keystore.d.ts.map": "e70eaddb7d1f372604e5e137bff46aa0b8ed5bbb71d2e3b62dfc61b3a69e2e6e", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/branch_keystore.js": "5d344a0a0619ab1c63e7b508cfe994584d0a29b778c10525e8524bb1f7275dd9", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/branch_keystore_helpers.d.ts": "0c06eac33b62e9dc2e82aa9195f4d0946b3ea56ff5c541a5f1fcd83a32cfaeb3", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/branch_keystore_helpers.d.ts.map": "0b3a47337dc85a57e8c1885aa254de9470551bfcdf0fa276b7af390f336376f1", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/branch_keystore_helpers.js": "a26d91f3be6de0639ebee04aadbc18d7babcb6423217524ec9c81a2b0df6feaa", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/branch_keystore_structures.d.ts": "d2c578a1510c60aae913ea659bf7049b464310e477178066e01845b180a273c6", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/branch_keystore_structures.d.ts.map": "68114136c8fff6acafff38e76383386030bb5677e7e40163f16351696d73d8eb", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/branch_keystore_structures.js": "4cbae0b036420bd7c70b925ae16ea2d72ee446c4e3a2c459d9875ed60fb7b8e9", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/constants.d.ts": "c5d67e613df216ae3010e49b3f34fc9ad5ecf31dc09ebd00a41d6ce2febe8df5", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/constants.d.ts.map": "000f22968d543eb298f7077460d6ed015249a31f052c887506239d10c9a8177b", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/constants.js": "5db6a8f4bc74453975ae07b559b13f66767c1f95d64fb1363a489487f5b2cca4", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/dynamodb_key_storage.d.ts": "01e646877f4fa8e69f962c295cc2533fa16e5cd1415d2f52a0bc46237f730aea", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/dynamodb_key_storage.d.ts.map": "b90cdc650ffb50041cad7f5f742f28a33c6cd604f08c9c1a734a0b4bde5cbd0f", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/dynamodb_key_storage.js": "0d58f6f591cadc034705570b621eb313ae679af1a3941b4a38273c98121b800f", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/index.d.ts": "024971a9e3841f78522cdfe5df16b57fcb81bf95f0a4b1f8d8e565c294bfb108", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/index.d.ts.map": "48c3ce6993eef627f82b0612468b0dfb4fea2fb560c38833c2368dc1ec0e1442", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/index.js": "8efc0da1455f74df2ed4759005598b3bfc15c416449534e59e34163d6c1cbf98", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/key_helpers.d.ts": "d2c9cab22da7d0d32e6791bd286abb7335c91f9c68894bab7b44a5ff3c2b515e", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/key_helpers.d.ts.map": "8f8fa3b61b4f95b715b50c8aae8a7351cf41f0b03da23ac95925fe2997b2a87d", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/key_helpers.js": "8f10e8a5f3ac7fc5588278527d309b566e953f16b85dfeddef05cfa4ad5f0498", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/kms_config.d.ts": "429b66a1be0ea9ff2ec8e785b1377ab67e8187e815f00e11ca953e2385bdecdc", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/kms_config.d.ts.map": "4022072e930cba6c4d46f87f093a9253df37d7b44e227c3b102935789ee206c8", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/kms_config.js": "6952256adbde2b5c6cc04a3b34658e1011fe11bdd3b5a91a2e46c7a06502aa67", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/types.d.ts": "aca384b0f70f1121adc7cd095ad8d1ae88bbd249a1ce06209f3dc8c44b568553", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/types.d.ts.map": "939b5a16835e61e9f074285449578c06ba4a736d63f2ae8af1995a3ca6ad0a1d", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/types.js": "abac7eb89c9169d003d3dddb4c2929d05901dbf7c6fe79e98913d72a28a673c6", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/version.d.ts": "a1811365ce81305883060dcbc980fd4e39812e56a82d5bcf9596d70efe90ab86", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/version.d.ts.map": "12c7e8136d70bcc364b01ab4709d2002dcdbe4c589296b50b312a8b1380f01c2", + "node_modules/@aws-crypto/branch-keystore-node/build/module/src/version.js": "50438f43dec51197bb44fba70be2639af7b3e486a2aeb4ad4e99c02fd9f7f8d4", + "node_modules/@aws-crypto/branch-keystore-node/package.json": "67161ccd0a9b135b80eb57c618581b2ac31f6c40b2f45abfdf24fbc1a85aa56b", + "node_modules/@aws-crypto/cache-material/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/cache-material/README.md": "91c8ea5274f697bffc6993630e34eeacd033d3aff5e32b030af45b51fb7661e3", + "node_modules/@aws-crypto/cache-material/build/main/src/build_cryptographic_materials_cache_key_helpers.d.ts": "c101103877adfb356ba91c6d70672c520118177ddc76514dd84589c0bf08a0d9", + "node_modules/@aws-crypto/cache-material/build/main/src/build_cryptographic_materials_cache_key_helpers.d.ts.map": "e8575677d6499de9edf4be81d6fad7f98e4487d08555d177e91fd755db607648", + "node_modules/@aws-crypto/cache-material/build/main/src/build_cryptographic_materials_cache_key_helpers.js": "31cdd827f9c9eb80ca781f7281691a74b315e467ea3e7b474aa60c783e966dd4", + "node_modules/@aws-crypto/cache-material/build/main/src/caching_cryptographic_materials_decorators.d.ts": "af7042fa4b5fc706416dd585337c5e2c5369887b0b8409699e8322b6349b101a", + "node_modules/@aws-crypto/cache-material/build/main/src/caching_cryptographic_materials_decorators.d.ts.map": "30ea78c94c5f7df8f126cdbf0ee6a7683f0065fe0dc1039cefbc7103d738656a", + "node_modules/@aws-crypto/cache-material/build/main/src/caching_cryptographic_materials_decorators.js": "a93f1b9656e7763c8a20a4e2935e5d5b6f79b63e450266c8acaa1456c28ee3e4", + "node_modules/@aws-crypto/cache-material/build/main/src/cryptographic_materials_cache.d.ts": "3d812db9fbf6a081077bd035fd31202fbbf95eefc54f7e90b10dd716f1b5f30b", + "node_modules/@aws-crypto/cache-material/build/main/src/cryptographic_materials_cache.d.ts.map": "1f68feb0604e6ef38edace5b8d5aec4a7a1780cd3a9579dc20fd3de9bffd8314", + "node_modules/@aws-crypto/cache-material/build/main/src/cryptographic_materials_cache.js": "4a09c14e34dbd91fde29a3c9568874e438b8f15edac41362bd147e4b32248904", + "node_modules/@aws-crypto/cache-material/build/main/src/get_local_cryptographic_materials_cache.d.ts": "cec1442f07d43d10b1945098aa9d26ada597a53fdf6aa06e6d898e8c12f9aa88", + "node_modules/@aws-crypto/cache-material/build/main/src/get_local_cryptographic_materials_cache.d.ts.map": "632084f3688ae4814e966db5eebaf80f2a5d758f13ab0863f5e8812b88f5d89a", + "node_modules/@aws-crypto/cache-material/build/main/src/get_local_cryptographic_materials_cache.js": "0c65743f0f15496e7d86302d96c69356ba33007df181233ae77a4cd3c81278b9", + "node_modules/@aws-crypto/cache-material/build/main/src/index.d.ts": "932d9048eacbd59250b3fc02c13c09686ab5ced82e593183431e8fa2bca79bf5", + "node_modules/@aws-crypto/cache-material/build/main/src/index.d.ts.map": "cffb3c2d423a5fbda99c6d89364dda455a88fad6dcb77441c8afa7d605f179a1", + "node_modules/@aws-crypto/cache-material/build/main/src/index.js": "a1f63b6805590547b8e7fcb4374723e697b91b317cfeb6dc9328c973ddfc2340", + "node_modules/@aws-crypto/cache-material/build/main/src/portable_compare.d.ts": "65053cd8d85473e4ff19d390212b22b54e03bbc011bf5dddfe6bf763a0da930c", + "node_modules/@aws-crypto/cache-material/build/main/src/portable_compare.d.ts.map": "d73e5f4066a2aeedc0408409853ac7f2bd5a3288962221d3ca534be627c15118", + "node_modules/@aws-crypto/cache-material/build/main/src/portable_compare.js": "b63533f39999708f7d9d22217327a22e8514ec8cea2fd42455720b9168781d26", + "node_modules/@aws-crypto/cache-material/build/module/src/build_cryptographic_materials_cache_key_helpers.d.ts": "c101103877adfb356ba91c6d70672c520118177ddc76514dd84589c0bf08a0d9", + "node_modules/@aws-crypto/cache-material/build/module/src/build_cryptographic_materials_cache_key_helpers.d.ts.map": "e8575677d6499de9edf4be81d6fad7f98e4487d08555d177e91fd755db607648", + "node_modules/@aws-crypto/cache-material/build/module/src/build_cryptographic_materials_cache_key_helpers.js": "b4d391ac02403987722d85ede4034efe426ff3ae25c998f2c2b5a424bc972a56", + "node_modules/@aws-crypto/cache-material/build/module/src/caching_cryptographic_materials_decorators.d.ts": "af7042fa4b5fc706416dd585337c5e2c5369887b0b8409699e8322b6349b101a", + "node_modules/@aws-crypto/cache-material/build/module/src/caching_cryptographic_materials_decorators.d.ts.map": "30ea78c94c5f7df8f126cdbf0ee6a7683f0065fe0dc1039cefbc7103d738656a", + "node_modules/@aws-crypto/cache-material/build/module/src/caching_cryptographic_materials_decorators.js": "4ff8b749ee0174b487d2a6e6142bea39ebc5fb55e56a2cc4325343eaa096a607", + "node_modules/@aws-crypto/cache-material/build/module/src/cryptographic_materials_cache.d.ts": "3d812db9fbf6a081077bd035fd31202fbbf95eefc54f7e90b10dd716f1b5f30b", + "node_modules/@aws-crypto/cache-material/build/module/src/cryptographic_materials_cache.d.ts.map": "1f68feb0604e6ef38edace5b8d5aec4a7a1780cd3a9579dc20fd3de9bffd8314", + "node_modules/@aws-crypto/cache-material/build/module/src/cryptographic_materials_cache.js": "5ac18e3a90a38b1f91d59d26a63958b7bc8d0e75f3c042926542e0a7ee3dfb37", + "node_modules/@aws-crypto/cache-material/build/module/src/get_local_cryptographic_materials_cache.d.ts": "cec1442f07d43d10b1945098aa9d26ada597a53fdf6aa06e6d898e8c12f9aa88", + "node_modules/@aws-crypto/cache-material/build/module/src/get_local_cryptographic_materials_cache.d.ts.map": "632084f3688ae4814e966db5eebaf80f2a5d758f13ab0863f5e8812b88f5d89a", + "node_modules/@aws-crypto/cache-material/build/module/src/get_local_cryptographic_materials_cache.js": "3ab36e580c50c004bdea3fe737585d3746b4a99ce5cf371fb6f637597df6b745", + "node_modules/@aws-crypto/cache-material/build/module/src/index.d.ts": "932d9048eacbd59250b3fc02c13c09686ab5ced82e593183431e8fa2bca79bf5", + "node_modules/@aws-crypto/cache-material/build/module/src/index.d.ts.map": "cffb3c2d423a5fbda99c6d89364dda455a88fad6dcb77441c8afa7d605f179a1", + "node_modules/@aws-crypto/cache-material/build/module/src/index.js": "3c3aabc6a5f7fe9fa56bad565d845525638a45ae21f6fa722e5c822aceb81a4c", + "node_modules/@aws-crypto/cache-material/build/module/src/portable_compare.d.ts": "65053cd8d85473e4ff19d390212b22b54e03bbc011bf5dddfe6bf763a0da930c", + "node_modules/@aws-crypto/cache-material/build/module/src/portable_compare.d.ts.map": "d73e5f4066a2aeedc0408409853ac7f2bd5a3288962221d3ca534be627c15118", + "node_modules/@aws-crypto/cache-material/build/module/src/portable_compare.js": "62d45764201e2f094c299777994807b07918b92d788dcb7cadf75770a6a40dfe", + "node_modules/@aws-crypto/cache-material/package.json": "5f3ba46cce7ea7074f660dcd6caf30dd9a96f3643227505c1bf6da81db7e066c", + "node_modules/@aws-crypto/caching-materials-manager-node/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/caching-materials-manager-node/README.md": "d50bc4d41c4f66416014c6f0e410522dabd1d22da4e4cba2bf566a20411e005b", + "node_modules/@aws-crypto/caching-materials-manager-node/build/main/src/caching_materials_manager_node.d.ts": "7bbfdf1b9bfbf109a16e95852554e297894870347ee1f04fc9bfcc57a5638196", + "node_modules/@aws-crypto/caching-materials-manager-node/build/main/src/caching_materials_manager_node.d.ts.map": "178365346624c693853a0d0fe437d6816f1d0b6fd4872c4e218b7eccf1b54b64", + "node_modules/@aws-crypto/caching-materials-manager-node/build/main/src/caching_materials_manager_node.js": "dc9eea35e19cd4374344fe12f115bb6139b773230d98077efda01d11f58f62e4", + "node_modules/@aws-crypto/caching-materials-manager-node/build/main/src/index.d.ts": "a37de5b4e00e325f68e7835edf572576833c7cf53ac388b67d20b68640746eea", + "node_modules/@aws-crypto/caching-materials-manager-node/build/main/src/index.d.ts.map": "0cdb5eed210651bf12866346f0d038bcf3f2f934f17eaee1a97a60cdeca355db", + "node_modules/@aws-crypto/caching-materials-manager-node/build/main/src/index.js": "6785fd5d5f81a3a8914b7b8f1aee81aa1d2ce67193db7bad9e930fed471b6ff6", + "node_modules/@aws-crypto/caching-materials-manager-node/build/main/src/sha512.d.ts": "c1ce4741fabc5409dbc3179eb9862312e32021b81b37620f89ac5ba4335158bb", + "node_modules/@aws-crypto/caching-materials-manager-node/build/main/src/sha512.d.ts.map": "5a8ade03b90723dd35fe0e2132f37546a6a1a76ba7703522bbb46bbd7afe5124", + "node_modules/@aws-crypto/caching-materials-manager-node/build/main/src/sha512.js": "822e01cbfd1f90a32e54b5b7dbfdf2c9ad9943851a7d493fdd38a90d79679549", + "node_modules/@aws-crypto/caching-materials-manager-node/build/module/src/caching_materials_manager_node.d.ts": "7bbfdf1b9bfbf109a16e95852554e297894870347ee1f04fc9bfcc57a5638196", + "node_modules/@aws-crypto/caching-materials-manager-node/build/module/src/caching_materials_manager_node.d.ts.map": "178365346624c693853a0d0fe437d6816f1d0b6fd4872c4e218b7eccf1b54b64", + "node_modules/@aws-crypto/caching-materials-manager-node/build/module/src/caching_materials_manager_node.js": "0108a07c1ed9041d1eca14769ca32631a63b36b95a2241166d3a9322b126ec32", + "node_modules/@aws-crypto/caching-materials-manager-node/build/module/src/index.d.ts": "a37de5b4e00e325f68e7835edf572576833c7cf53ac388b67d20b68640746eea", + "node_modules/@aws-crypto/caching-materials-manager-node/build/module/src/index.d.ts.map": "0cdb5eed210651bf12866346f0d038bcf3f2f934f17eaee1a97a60cdeca355db", + "node_modules/@aws-crypto/caching-materials-manager-node/build/module/src/index.js": "759e4659569d6bf470f0962eb4fb76388c8a39bf413a954c6001ed6268eadd76", + "node_modules/@aws-crypto/caching-materials-manager-node/build/module/src/sha512.d.ts": "c1ce4741fabc5409dbc3179eb9862312e32021b81b37620f89ac5ba4335158bb", + "node_modules/@aws-crypto/caching-materials-manager-node/build/module/src/sha512.d.ts.map": "5a8ade03b90723dd35fe0e2132f37546a6a1a76ba7703522bbb46bbd7afe5124", + "node_modules/@aws-crypto/caching-materials-manager-node/build/module/src/sha512.js": "973ce03273d6f578be097ff815d7212a7bcdc63fd093eeb7e2d428611a560e00", + "node_modules/@aws-crypto/caching-materials-manager-node/package.json": "b4877aabb8e3f2a2ed13c1227ea88977ffb4e5828acb8dc11a2f3452dbc4fd6d", + "node_modules/@aws-crypto/client-node/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/client-node/Readme.md": "7a7620bf68adfd8e4c959cf91e362c72b198d9a319ce34ea4c56dab3f6aa4839", + "node_modules/@aws-crypto/client-node/build/main/src/index.d.ts": "34c59d91653fa9186d0a3095271a44570057a42959a50242a4be3a542d65b507", + "node_modules/@aws-crypto/client-node/build/main/src/index.d.ts.map": "ba23d7d8a8c3b3e7a22cb2d5bd6bac44a5d4f8936574f3951b9514d360b7cc9c", + "node_modules/@aws-crypto/client-node/build/main/src/index.js": "ca74a904602b63b649d76c2d343d0c308218fb295ccaae67fa87d17bbf653098", + "node_modules/@aws-crypto/client-node/build/module/src/index.d.ts": "34c59d91653fa9186d0a3095271a44570057a42959a50242a4be3a542d65b507", + "node_modules/@aws-crypto/client-node/build/module/src/index.d.ts.map": "ba23d7d8a8c3b3e7a22cb2d5bd6bac44a5d4f8936574f3951b9514d360b7cc9c", + "node_modules/@aws-crypto/client-node/build/module/src/index.js": "7c96666a7df5d71736c0e81b5b9cd47cb8b04fded4b20aecaa5059a3ce78cde3", + "node_modules/@aws-crypto/client-node/package.json": "30e1db65275c685258ee77bcec288d866730a430e191b4b92ee8ca1e566f9638", + "node_modules/@aws-crypto/decrypt-node/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/decrypt-node/README.md": "d6add68c00d2af1cfdb3ee625979ed488311d93d7494454acf4d485729cb5e12", + "node_modules/@aws-crypto/decrypt-node/build/main/src/decipher_stream.d.ts": "c8bf70e8bdc5b257e966e000fa07579d8e7400b683315c3c7fd94072f828b473", + "node_modules/@aws-crypto/decrypt-node/build/main/src/decipher_stream.d.ts.map": "4630f565798a380445b1b14f9ffc0e3dd0699fd1c3529f963dbc106d5cede5a1", + "node_modules/@aws-crypto/decrypt-node/build/main/src/decipher_stream.js": "3299148cf8c3326f8d7ab870580e0e712836e74d7ec4e6f570d0f0fa9425cf4d", + "node_modules/@aws-crypto/decrypt-node/build/main/src/decrypt.d.ts": "6f9ffc616c97295723a161cfa4be3ac0f191ddd22498cdb1c55432664e5c1deb", + "node_modules/@aws-crypto/decrypt-node/build/main/src/decrypt.d.ts.map": "3804e38cc17a95a1d846c032cb66f481f13c6864bc3b34d8e46b0e46dd5810b2", + "node_modules/@aws-crypto/decrypt-node/build/main/src/decrypt.js": "d9695b743e15818984c4ff93413db86ecad9b1f0dc3b41cbb144fd43d0f8ac9a", + "node_modules/@aws-crypto/decrypt-node/build/main/src/decrypt_client.d.ts": "9a59668bf57802dbd6d2e0b3ce49cae9ec557023a4708661a26d005d7ea4409f", + "node_modules/@aws-crypto/decrypt-node/build/main/src/decrypt_client.d.ts.map": "ebd3294093d2002f02b5ca2809da2eb7f11fde14404f917f16d0bd980d0fa70b", + "node_modules/@aws-crypto/decrypt-node/build/main/src/decrypt_client.js": "8c6c91980e43f1266dbd5616b5d1c008d0c244cd8a38c0334321cc6f0b297332", + "node_modules/@aws-crypto/decrypt-node/build/main/src/decrypt_stream.d.ts": "673bbbaf162b2db5cab9cf2d5b8223af7fa6e01c0bae9b62abda5e24d746b522", + "node_modules/@aws-crypto/decrypt-node/build/main/src/decrypt_stream.d.ts.map": "f997915647eab6b50ecbacf08a4ccc382886ed15224b06625854a304609467d6", + "node_modules/@aws-crypto/decrypt-node/build/main/src/decrypt_stream.js": "1d91e60e10dd62e45b4e9e55fdf1605b495431fe0b05eb09feab4afb2f84a748", + "node_modules/@aws-crypto/decrypt-node/build/main/src/index.d.ts": "a4a0721ea82cb1c28f8d0d0006c2e54df97554453ba621f95a3a250f3d22e5da", + "node_modules/@aws-crypto/decrypt-node/build/main/src/index.d.ts.map": "6c407a1887d46311d304a011c058cd18b6caaba1668ea6a114367c5c44f8f7a2", + "node_modules/@aws-crypto/decrypt-node/build/main/src/index.js": "61b39a0ff32a19352207151784aa5745ef1ff8ae48e30ed313ef617a71c51501", + "node_modules/@aws-crypto/decrypt-node/build/main/src/parse_header_stream.d.ts": "1665b1ff4198591761a820a0b9009fcbee6a2d2fd98dcb9c02b92df63f1e75fb", + "node_modules/@aws-crypto/decrypt-node/build/main/src/parse_header_stream.d.ts.map": "489a83f0121b5aa8aef1d60e1683608efc6b929c1868f9189d0c47be2b84297c", + "node_modules/@aws-crypto/decrypt-node/build/main/src/parse_header_stream.js": "a7ede3e9ae7fa122341bccbee3f433e1c9b480d6e7bd1c5b58f6b6a23b1219bb", + "node_modules/@aws-crypto/decrypt-node/build/main/src/types.d.ts": "a5083f26868d6d2dbc7dd7393302a4bda48703adda348e448fadca85e6c39bca", + "node_modules/@aws-crypto/decrypt-node/build/main/src/types.d.ts.map": "a965e3394486dcac603509a7c2afe127c68d311b1904b729978559ec7426b4c0", + "node_modules/@aws-crypto/decrypt-node/build/main/src/types.js": "ffac8d9b74b561b5cda5614f1e4b688cdb69f496e1d42af24060569a80bb04c4", + "node_modules/@aws-crypto/decrypt-node/build/main/src/verify_stream.d.ts": "f7ca256c4a62aeec4cf26a57c209e3126b9dcc0676ec897373c40944d7a483d4", + "node_modules/@aws-crypto/decrypt-node/build/main/src/verify_stream.d.ts.map": "066e908c45d5f70e67d5797aa28547e4e8194953a912ba7c0d695eefc9200ca5", + "node_modules/@aws-crypto/decrypt-node/build/main/src/verify_stream.js": "f005e6b3238f18d39e0e9df53648f74ed244c6677546a54e9db9bf13b0f90e4c", + "node_modules/@aws-crypto/decrypt-node/build/module/src/decipher_stream.d.ts": "c8bf70e8bdc5b257e966e000fa07579d8e7400b683315c3c7fd94072f828b473", + "node_modules/@aws-crypto/decrypt-node/build/module/src/decipher_stream.d.ts.map": "4630f565798a380445b1b14f9ffc0e3dd0699fd1c3529f963dbc106d5cede5a1", + "node_modules/@aws-crypto/decrypt-node/build/module/src/decipher_stream.js": "69561fbc29ddd56c8273c4acd11a2a53588352e37f6751daa32ce63c2a195950", + "node_modules/@aws-crypto/decrypt-node/build/module/src/decrypt.d.ts": "6f9ffc616c97295723a161cfa4be3ac0f191ddd22498cdb1c55432664e5c1deb", + "node_modules/@aws-crypto/decrypt-node/build/module/src/decrypt.d.ts.map": "3804e38cc17a95a1d846c032cb66f481f13c6864bc3b34d8e46b0e46dd5810b2", + "node_modules/@aws-crypto/decrypt-node/build/module/src/decrypt.js": "90acd8f6950a6083274aa8c45cd38c27621eb30ab7f0a8272ca0181336d52827", + "node_modules/@aws-crypto/decrypt-node/build/module/src/decrypt_client.d.ts": "9a59668bf57802dbd6d2e0b3ce49cae9ec557023a4708661a26d005d7ea4409f", + "node_modules/@aws-crypto/decrypt-node/build/module/src/decrypt_client.d.ts.map": "ebd3294093d2002f02b5ca2809da2eb7f11fde14404f917f16d0bd980d0fa70b", + "node_modules/@aws-crypto/decrypt-node/build/module/src/decrypt_client.js": "e02c509e81c5bc641f9ca8060af1bfbf1ae5ddacaa718761fbbe70347285e3f9", + "node_modules/@aws-crypto/decrypt-node/build/module/src/decrypt_stream.d.ts": "673bbbaf162b2db5cab9cf2d5b8223af7fa6e01c0bae9b62abda5e24d746b522", + "node_modules/@aws-crypto/decrypt-node/build/module/src/decrypt_stream.d.ts.map": "f997915647eab6b50ecbacf08a4ccc382886ed15224b06625854a304609467d6", + "node_modules/@aws-crypto/decrypt-node/build/module/src/decrypt_stream.js": "d9d45a8fd6b38058b95e01a726e4c6d3aaf27e4473b857b8cd41385429bec915", + "node_modules/@aws-crypto/decrypt-node/build/module/src/index.d.ts": "a4a0721ea82cb1c28f8d0d0006c2e54df97554453ba621f95a3a250f3d22e5da", + "node_modules/@aws-crypto/decrypt-node/build/module/src/index.d.ts.map": "6c407a1887d46311d304a011c058cd18b6caaba1668ea6a114367c5c44f8f7a2", + "node_modules/@aws-crypto/decrypt-node/build/module/src/index.js": "9ed6b12e3e2b9de8f59e8396c25bcc970d59c4355a0305ffe26f2f9b40d78a9a", + "node_modules/@aws-crypto/decrypt-node/build/module/src/parse_header_stream.d.ts": "1665b1ff4198591761a820a0b9009fcbee6a2d2fd98dcb9c02b92df63f1e75fb", + "node_modules/@aws-crypto/decrypt-node/build/module/src/parse_header_stream.d.ts.map": "489a83f0121b5aa8aef1d60e1683608efc6b929c1868f9189d0c47be2b84297c", + "node_modules/@aws-crypto/decrypt-node/build/module/src/parse_header_stream.js": "1d5f8421dd1bebea6fc6351d7fc5ada571f465b668c036643b42b4f3b049bff8", + "node_modules/@aws-crypto/decrypt-node/build/module/src/types.d.ts": "a5083f26868d6d2dbc7dd7393302a4bda48703adda348e448fadca85e6c39bca", + "node_modules/@aws-crypto/decrypt-node/build/module/src/types.d.ts.map": "a965e3394486dcac603509a7c2afe127c68d311b1904b729978559ec7426b4c0", + "node_modules/@aws-crypto/decrypt-node/build/module/src/types.js": "0168a2d65fd821d8d322a8f9008350c7cb4a6b71463480e639e85e279974ec7a", + "node_modules/@aws-crypto/decrypt-node/build/module/src/verify_stream.d.ts": "f7ca256c4a62aeec4cf26a57c209e3126b9dcc0676ec897373c40944d7a483d4", + "node_modules/@aws-crypto/decrypt-node/build/module/src/verify_stream.d.ts.map": "066e908c45d5f70e67d5797aa28547e4e8194953a912ba7c0d695eefc9200ca5", + "node_modules/@aws-crypto/decrypt-node/build/module/src/verify_stream.js": "3ee3e264faa7d15f3433247d898bc8e6d950684079e779216d1531cffd02899f", + "node_modules/@aws-crypto/decrypt-node/package.json": "b3dc768e1d600e3756fb25d2216d04f33ecc55b0bf15fe870bd533d2b63efe60", + "node_modules/@aws-crypto/encrypt-node/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/encrypt-node/README.md": "501c20c45f78b2df792c4e8e94187ffc0b7b4391b93e591a62c2d262d005bea6", + "node_modules/@aws-crypto/encrypt-node/build/main/src/encrypt.d.ts": "657c6e363c6914bc192a8db95325e677821a309ba4eb416d57ac2d79e406001d", + "node_modules/@aws-crypto/encrypt-node/build/main/src/encrypt.d.ts.map": "fdb66da85daff81b08fcbbe323aa49409e795e388d4082a08f7e1bb8cfa8ce65", + "node_modules/@aws-crypto/encrypt-node/build/main/src/encrypt.js": "650e5a684b67eaa54ce4366b37a364b96a4a67aa4faebdd53e3f4f1fc5205769", + "node_modules/@aws-crypto/encrypt-node/build/main/src/encrypt_client.d.ts": "0cce7b00472608dff651d8f2b122356d2bcd00e0080095f2b953edd89020172b", + "node_modules/@aws-crypto/encrypt-node/build/main/src/encrypt_client.d.ts.map": "42ecebfaa0bac5f16916a7e9f286204d35a6adbc3424ca6f1d058afca10ec761", + "node_modules/@aws-crypto/encrypt-node/build/main/src/encrypt_client.js": "02f74ca6d9daef09cd63b8a6dc8678660799615aadc352423ae4911fc619e2e4", + "node_modules/@aws-crypto/encrypt-node/build/main/src/encrypt_stream.d.ts": "b81b909b393dd8ffebcd4369ed6edd18004721c33e81daf88d22fca6cc437acc", + "node_modules/@aws-crypto/encrypt-node/build/main/src/encrypt_stream.d.ts.map": "b4dbae2c9b01cc649a00a48885789ad12607a9d10d450a1b5618ad286e97bc41", + "node_modules/@aws-crypto/encrypt-node/build/main/src/encrypt_stream.js": "12d11ed421d8702e091b1a13fb18ca21541093f2baea2aebe9a6988f788c4325", + "node_modules/@aws-crypto/encrypt-node/build/main/src/framed_encrypt_stream.d.ts": "b587d75773f92c35fe36216e7d97de893bb6ae94fc170be896251776e3d762ba", + "node_modules/@aws-crypto/encrypt-node/build/main/src/framed_encrypt_stream.d.ts.map": "beca0e5ea00a55f6280428d58599cb9fadd29d1e46507e5fc8c59906f9d36456", + "node_modules/@aws-crypto/encrypt-node/build/main/src/framed_encrypt_stream.js": "27e2e953b4f639ce192771c8971a19a4a268540988064a60a4b5357fb83dc452", + "node_modules/@aws-crypto/encrypt-node/build/main/src/index.d.ts": "20c9d890e0ff09c3ba4a998923d2b4ac590b98a60af6936ff1be6814ae108e11", + "node_modules/@aws-crypto/encrypt-node/build/main/src/index.d.ts.map": "7b83b6be77380857332b20f892576dce8d9f8a45c4777295e6d2114821091b64", + "node_modules/@aws-crypto/encrypt-node/build/main/src/index.js": "92643fb127a25605194986ded18c17c24a18653440af821427ec1261f40290fa", + "node_modules/@aws-crypto/encrypt-node/build/main/src/signature_stream.d.ts": "f490d333adb8dc81f69fd0d4d66d10e1e8a53b3baee709d45cd2a66f6567ca7d", + "node_modules/@aws-crypto/encrypt-node/build/main/src/signature_stream.d.ts.map": "57a2dbe3e08758c42514fdb6cd87dcda64a315fe62a43e607246d6727fae7764", + "node_modules/@aws-crypto/encrypt-node/build/main/src/signature_stream.js": "28cfafa3de6ce26fe1677c652dfc101b4eddde6a542e5e3ea01a3f1ff9f4c233", + "node_modules/@aws-crypto/encrypt-node/build/module/src/encrypt.d.ts": "657c6e363c6914bc192a8db95325e677821a309ba4eb416d57ac2d79e406001d", + "node_modules/@aws-crypto/encrypt-node/build/module/src/encrypt.d.ts.map": "fdb66da85daff81b08fcbbe323aa49409e795e388d4082a08f7e1bb8cfa8ce65", + "node_modules/@aws-crypto/encrypt-node/build/module/src/encrypt.js": "46621dffd0946c53fa5e1ba04a22a209771e9ddf81efc7c28a9b8cb67857cf3e", + "node_modules/@aws-crypto/encrypt-node/build/module/src/encrypt_client.d.ts": "0cce7b00472608dff651d8f2b122356d2bcd00e0080095f2b953edd89020172b", + "node_modules/@aws-crypto/encrypt-node/build/module/src/encrypt_client.d.ts.map": "42ecebfaa0bac5f16916a7e9f286204d35a6adbc3424ca6f1d058afca10ec761", + "node_modules/@aws-crypto/encrypt-node/build/module/src/encrypt_client.js": "c8f87a626a00691cb72cd2c2c40d7e0389e4a050f682e87b1220655c6d2d00b2", + "node_modules/@aws-crypto/encrypt-node/build/module/src/encrypt_stream.d.ts": "b81b909b393dd8ffebcd4369ed6edd18004721c33e81daf88d22fca6cc437acc", + "node_modules/@aws-crypto/encrypt-node/build/module/src/encrypt_stream.d.ts.map": "b4dbae2c9b01cc649a00a48885789ad12607a9d10d450a1b5618ad286e97bc41", + "node_modules/@aws-crypto/encrypt-node/build/module/src/encrypt_stream.js": "64cc764c20ac58a4619e46538dbe0f097e6f426060f0fd59b4e31b56b141943e", + "node_modules/@aws-crypto/encrypt-node/build/module/src/framed_encrypt_stream.d.ts": "b587d75773f92c35fe36216e7d97de893bb6ae94fc170be896251776e3d762ba", + "node_modules/@aws-crypto/encrypt-node/build/module/src/framed_encrypt_stream.d.ts.map": "beca0e5ea00a55f6280428d58599cb9fadd29d1e46507e5fc8c59906f9d36456", + "node_modules/@aws-crypto/encrypt-node/build/module/src/framed_encrypt_stream.js": "a11ac8bc71a0096b7bd3d03f1c6cda28087c772396eeaf5c5b8cf7ff165b577f", + "node_modules/@aws-crypto/encrypt-node/build/module/src/index.d.ts": "20c9d890e0ff09c3ba4a998923d2b4ac590b98a60af6936ff1be6814ae108e11", + "node_modules/@aws-crypto/encrypt-node/build/module/src/index.d.ts.map": "7b83b6be77380857332b20f892576dce8d9f8a45c4777295e6d2114821091b64", + "node_modules/@aws-crypto/encrypt-node/build/module/src/index.js": "adff356cdc1efb2d6378868b99e5e1fb52d9677daaec654e9112ec3457bdadd9", + "node_modules/@aws-crypto/encrypt-node/build/module/src/signature_stream.d.ts": "f490d333adb8dc81f69fd0d4d66d10e1e8a53b3baee709d45cd2a66f6567ca7d", + "node_modules/@aws-crypto/encrypt-node/build/module/src/signature_stream.d.ts.map": "57a2dbe3e08758c42514fdb6cd87dcda64a315fe62a43e607246d6727fae7764", + "node_modules/@aws-crypto/encrypt-node/build/module/src/signature_stream.js": "8938f66b7e98fa1b600b44b36106979dccf23a7afb5688552e89243db50ffbb2", + "node_modules/@aws-crypto/encrypt-node/package.json": "75be40680a615e71f9b978bec0d6d6dc59138a07deca1756373742d7ea93b29f", + "node_modules/@aws-crypto/hkdf-node/LICENSE": "ab6cb34f90af84d9dd0d6ed5a3e5e274502c24aa4de078bc93b597a7430996f8", + "node_modules/@aws-crypto/hkdf-node/README.md": "ff25bdd6e2dc13ceba8d24c55d90da2e0577ea8331fcf71cc4056759691d36a2", + "node_modules/@aws-crypto/hkdf-node/build/main/src/errors.d.ts": "6d34624393431c578a8e64a01a185627cc1ac73160b28f4c95e4f5bb72b4a14a", + "node_modules/@aws-crypto/hkdf-node/build/main/src/errors.d.ts.map": "f344451e20249412c845d38d0fd0e88ddb507d0a5a140fa1b0d8854ba4c23734", + "node_modules/@aws-crypto/hkdf-node/build/main/src/errors.js": "96163d0b733a6295878c5d51ff35d480419e771efae72bb2819780c2ddaa2470", + "node_modules/@aws-crypto/hkdf-node/build/main/src/hkdf.d.ts": "911ad96eb6777e8e0be3fc29fdaa33caaca45412db318b41d9d17581f44863bc", + "node_modules/@aws-crypto/hkdf-node/build/main/src/hkdf.d.ts.map": "71fc272e53189daf3c5e014e38e887478dc5bf92d8f8d1ca691131d7751fe6eb", + "node_modules/@aws-crypto/hkdf-node/build/main/src/hkdf.js": "b146d472d2f4e052a2ddb08a341b68c406b4dbb35d4e13c5a0a049c4b5dae28a", + "node_modules/@aws-crypto/hkdf-node/build/main/src/index.d.ts": "bf35183e140262c6af1554ce4ff5f0e405e8009396c42f5463348994e8a4af99", + "node_modules/@aws-crypto/hkdf-node/build/main/src/index.d.ts.map": "18729ae67e112eec96fba7202821701309acb2636e7d8b920a2ed2a11655ce00", + "node_modules/@aws-crypto/hkdf-node/build/main/src/index.js": "efe5340fd8ca9585f1f7f2110c3d27fb4e445d3f31a6002d197ab20075af7b4b", + "node_modules/@aws-crypto/hkdf-node/build/module/src/errors.d.ts": "6d34624393431c578a8e64a01a185627cc1ac73160b28f4c95e4f5bb72b4a14a", + "node_modules/@aws-crypto/hkdf-node/build/module/src/errors.d.ts.map": "f344451e20249412c845d38d0fd0e88ddb507d0a5a140fa1b0d8854ba4c23734", + "node_modules/@aws-crypto/hkdf-node/build/module/src/errors.js": "575c09529d53ad1940dff54db242825abf27da52fff574dcf22fb5fb7a190204", + "node_modules/@aws-crypto/hkdf-node/build/module/src/hkdf.d.ts": "911ad96eb6777e8e0be3fc29fdaa33caaca45412db318b41d9d17581f44863bc", + "node_modules/@aws-crypto/hkdf-node/build/module/src/hkdf.d.ts.map": "71fc272e53189daf3c5e014e38e887478dc5bf92d8f8d1ca691131d7751fe6eb", + "node_modules/@aws-crypto/hkdf-node/build/module/src/hkdf.js": "39edd18eddcc4ecc9f76fbad29435fd0d85f307dbd696159cf8399cf031990d0", + "node_modules/@aws-crypto/hkdf-node/build/module/src/index.d.ts": "bf35183e140262c6af1554ce4ff5f0e405e8009396c42f5463348994e8a4af99", + "node_modules/@aws-crypto/hkdf-node/build/module/src/index.d.ts.map": "18729ae67e112eec96fba7202821701309acb2636e7d8b920a2ed2a11655ce00", + "node_modules/@aws-crypto/hkdf-node/build/module/src/index.js": "4122048a2201bc69fbd8eb6c023122125f95c7c979bfd624ce2023d2db4219d5", + "node_modules/@aws-crypto/hkdf-node/package.json": "59f5c6fd82ae82f40b540217b694d307a7264e0b8b23388a2bad69f512d7ee7e", + "node_modules/@aws-crypto/kdf-ctr-mode-node/LICENSE": "ab6cb34f90af84d9dd0d6ed5a3e5e274502c24aa4de078bc93b597a7430996f8", + "node_modules/@aws-crypto/kdf-ctr-mode-node/README.md": "6fe3300b8aae70896e23de1cb61056ca48f3042745739dbda4a40591439f385c", + "node_modules/@aws-crypto/kdf-ctr-mode-node/build/main/src/index.d.ts": "9edaaf8ded57c7d765734ff1cbb35416819a03cc7eafce1331d6de2f3782761a", + "node_modules/@aws-crypto/kdf-ctr-mode-node/build/main/src/index.d.ts.map": "4c116340a28e108e772a9eb57b5304f24639c8310ee8fec0c20d3f22278bdf93", + "node_modules/@aws-crypto/kdf-ctr-mode-node/build/main/src/index.js": "f2a56ecb692702954454d4f51e67375fda53294c87b4c7df07e1b30662529e42", + "node_modules/@aws-crypto/kdf-ctr-mode-node/build/main/src/kdfctr.d.ts": "b3b9da738eee56ca095bd705141b8db85b5a509a9b75193b44acf44c5c0c3b16", + "node_modules/@aws-crypto/kdf-ctr-mode-node/build/main/src/kdfctr.d.ts.map": "f6d3259fcafb0936834480878dbb363abd0655e590b485e92e413a3624f3e606", + "node_modules/@aws-crypto/kdf-ctr-mode-node/build/main/src/kdfctr.js": "3a5bb62d84cc0eb4f2d25cf8644b0bbce7881a911adad955b005d21ca6917327", + "node_modules/@aws-crypto/kdf-ctr-mode-node/build/module/src/index.d.ts": "9edaaf8ded57c7d765734ff1cbb35416819a03cc7eafce1331d6de2f3782761a", + "node_modules/@aws-crypto/kdf-ctr-mode-node/build/module/src/index.d.ts.map": "4c116340a28e108e772a9eb57b5304f24639c8310ee8fec0c20d3f22278bdf93", + "node_modules/@aws-crypto/kdf-ctr-mode-node/build/module/src/index.js": "70867692c24842eeadcd3b857572e71a2b1edb7933deed29e363b023a29343a7", + "node_modules/@aws-crypto/kdf-ctr-mode-node/build/module/src/kdfctr.d.ts": "b3b9da738eee56ca095bd705141b8db85b5a509a9b75193b44acf44c5c0c3b16", + "node_modules/@aws-crypto/kdf-ctr-mode-node/build/module/src/kdfctr.d.ts.map": "f6d3259fcafb0936834480878dbb363abd0655e590b485e92e413a3624f3e606", + "node_modules/@aws-crypto/kdf-ctr-mode-node/build/module/src/kdfctr.js": "6fe6a68db07a65ad5499eda62ed67e96accb6f0de573364331b639abee6877b8", + "node_modules/@aws-crypto/kdf-ctr-mode-node/package.json": "64941a7bf18042ff4f71ffc2f43430358f44e862cb0f9f8488c97f4a4048d5a5", + "node_modules/@aws-crypto/kms-keyring-node/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/kms-keyring-node/README.md": "a6e29d9746c28e25ab4d45136b37eb545d5e9d1e610f457fe9971dccde294f4d", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/constants.d.ts": "257fa6d17c6f653ae461a2526a1981447be9f955aeaf171419ed1d6d517d0acb", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/constants.d.ts.map": "aeb7e3d72d38f6f6b97dece5fcbc173900282296dd9dafbe02c524680cc5dcc1", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/constants.js": "19b8ba4bb87f0a49f42215399b3cd3bb9c45c13b328c95ee96449d1484d2334f", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/index.d.ts": "1e0f06f434426b07b6cb5b7e9fffd0b222319f05c2f3b2addc0b4deb61be4c63", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/index.d.ts.map": "6c29f041fc0d587ed6e6b3eb52a7328e718350a64f8f347ed7dd69e1d5a35a3d", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/index.js": "e97d5cc9257c54ebca307669e4757fd114e771716e001b092fbc71df31590006", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_hkeyring_node.d.ts": "cc3c4681f62993ea6c453aa045b4bf9d31bc5ccea75d90b5cd52463aa8e91492", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_hkeyring_node.d.ts.map": "818aac3b7c4c0a4c54dd1bb84277ddd55db763591477c22e5e3f1c1abc240948", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_hkeyring_node.js": "2a9c36fea8b6f48292c21911021bf6ae84e5982a15012e0ebbb89cb9f8cec2f0", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_hkeyring_node_helpers.d.ts": "d913a9b571392fb293c880b82d7a762ce7160fad04a771c5b4a1dd30a863700d", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_hkeyring_node_helpers.d.ts.map": "2d71f233898f1bbb2a041bb3720db95281f657587537fe56cf0f75ebd7fb6472", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_hkeyring_node_helpers.js": "444309822650bd49616a6357f8abae390ba6d607ce3718f9fe96b7c9f1a049a7", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_keyring_node.d.ts": "78539a045a2b09096b6d0bddd1533b545c74eb54ddb64c5ba1984309ceb55357", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_keyring_node.d.ts.map": "edd0c79d043f2db97f67597cafd519f5015bacd6c3996bfe9e354c482e4ea390", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_keyring_node.js": "8f44710a570c60d494d2f527c3cb0802fd9b662d72f3ec359b938b0867e8a1b5", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_mrk_discovery_keyring_node.d.ts": "9ce8ccf9df0d63475e28dc1559cce9c6fbb9d257e85f7930619dba1338bc2718", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_mrk_discovery_keyring_node.d.ts.map": "124f1da6cac0524d70c84135f6e0728c68ec26a4fd30d2e51df379960ac34fb6", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_mrk_discovery_keyring_node.js": "5b77688a96b1b4ea1808876e64863ccded60371a08a471080c6640dec7a2b21d", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_mrk_discovery_multi_keyring_node.d.ts": "3d9be1e37917ead659a86390f8e2c987627dca5754820cbc44afba353037c722", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_mrk_discovery_multi_keyring_node.d.ts.map": "d9dd0de9dfbcd75df6cd39746bedef668b47ddbb6cf02d8212a32093ab138422", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_mrk_discovery_multi_keyring_node.js": "9b3cb2daac0e792fe64070e8148e58449c5542e5191c1414a8c62810364a73bf", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_mrk_keyring_node.d.ts": "cdbc9d91737beffbdd7d2e50a6b8ad2d5425842f4464dff7a2c6f990f63b3feb", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_mrk_keyring_node.d.ts.map": "36d429bcf70ea9d92fe891387ac4889b8411a3b63664dcdfae4e8c713d21ab2a", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_mrk_keyring_node.js": "e7906cd219c4fa4eb65854452747370e4d9d34f11925148e3bf337e489084491", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_mrk_strict_multi_keyring_node.d.ts": "5156d9a8ab23c76dab4d6b9840eac0a64e9ebbff998323552f2e2ea1f09f18a0", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_mrk_strict_multi_keyring_node.d.ts.map": "79bc2063b45470da3a48b6080de92a03de1cd73a337c3db11f7cdc0c579beb9c", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/kms_mrk_strict_multi_keyring_node.js": "447c21fb94ffc2cbe92294f2dfd719dcdd0aa05e75a6ab6c7ae192552f89eae5", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/version.d.ts": "a1811365ce81305883060dcbc980fd4e39812e56a82d5bcf9596d70efe90ab86", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/version.d.ts.map": "12c7e8136d70bcc364b01ab4709d2002dcdbe4c589296b50b312a8b1380f01c2", + "node_modules/@aws-crypto/kms-keyring-node/build/main/src/version.js": "b18e805e2a56360c346b31e73d0dfe831557b6daf44541c8cb01642f9c8b11df", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/constants.d.ts": "257fa6d17c6f653ae461a2526a1981447be9f955aeaf171419ed1d6d517d0acb", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/constants.d.ts.map": "aeb7e3d72d38f6f6b97dece5fcbc173900282296dd9dafbe02c524680cc5dcc1", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/constants.js": "3ffd2ec8255f075e5f26df98d313bc2881ce50fd13c75f8879902659565b01c2", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/index.d.ts": "1e0f06f434426b07b6cb5b7e9fffd0b222319f05c2f3b2addc0b4deb61be4c63", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/index.d.ts.map": "6c29f041fc0d587ed6e6b3eb52a7328e718350a64f8f347ed7dd69e1d5a35a3d", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/index.js": "ea7f38ba37b8f8cec48fbe22035273c684dbb7efabde1c94e04404cf36ad5078", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_hkeyring_node.d.ts": "cc3c4681f62993ea6c453aa045b4bf9d31bc5ccea75d90b5cd52463aa8e91492", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_hkeyring_node.d.ts.map": "818aac3b7c4c0a4c54dd1bb84277ddd55db763591477c22e5e3f1c1abc240948", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_hkeyring_node.js": "2c1f77dc4b2f51b61da0a3249f45711ef4e5fe89a3479eb3a4c60daa6fcb90bf", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_hkeyring_node_helpers.d.ts": "d913a9b571392fb293c880b82d7a762ce7160fad04a771c5b4a1dd30a863700d", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_hkeyring_node_helpers.d.ts.map": "2d71f233898f1bbb2a041bb3720db95281f657587537fe56cf0f75ebd7fb6472", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_hkeyring_node_helpers.js": "e0c1024016227d6bd3ed7dd8c18f6806f24946da51f313f187d02569746dc0d5", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_keyring_node.d.ts": "78539a045a2b09096b6d0bddd1533b545c74eb54ddb64c5ba1984309ceb55357", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_keyring_node.d.ts.map": "edd0c79d043f2db97f67597cafd519f5015bacd6c3996bfe9e354c482e4ea390", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_keyring_node.js": "f33e123e8d0530b3f6fd3b47a0c61be2f2caa3e5476e18e16399987227c18d71", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_mrk_discovery_keyring_node.d.ts": "9ce8ccf9df0d63475e28dc1559cce9c6fbb9d257e85f7930619dba1338bc2718", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_mrk_discovery_keyring_node.d.ts.map": "124f1da6cac0524d70c84135f6e0728c68ec26a4fd30d2e51df379960ac34fb6", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_mrk_discovery_keyring_node.js": "f3c0bbaee9dc2cbc5b508c21a81efdf306441adf7c893fc2e1d0e5d02d872f70", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_mrk_discovery_multi_keyring_node.d.ts": "3d9be1e37917ead659a86390f8e2c987627dca5754820cbc44afba353037c722", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_mrk_discovery_multi_keyring_node.d.ts.map": "d9dd0de9dfbcd75df6cd39746bedef668b47ddbb6cf02d8212a32093ab138422", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_mrk_discovery_multi_keyring_node.js": "d046bc3cdd1176d24ab625a1316cb609ca31d109029708ca5549eb5d09aed458", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_mrk_keyring_node.d.ts": "cdbc9d91737beffbdd7d2e50a6b8ad2d5425842f4464dff7a2c6f990f63b3feb", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_mrk_keyring_node.d.ts.map": "36d429bcf70ea9d92fe891387ac4889b8411a3b63664dcdfae4e8c713d21ab2a", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_mrk_keyring_node.js": "135da36284c5ea8206f269f0d7363dc4dc0fc2fd8938bd350e9d20a03ef6241d", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_mrk_strict_multi_keyring_node.d.ts": "5156d9a8ab23c76dab4d6b9840eac0a64e9ebbff998323552f2e2ea1f09f18a0", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_mrk_strict_multi_keyring_node.d.ts.map": "79bc2063b45470da3a48b6080de92a03de1cd73a337c3db11f7cdc0c579beb9c", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/kms_mrk_strict_multi_keyring_node.js": "b6a8630710222886744450b9801cc50e363a55bfe32c6f691b00123e7ffecfaa", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/version.d.ts": "a1811365ce81305883060dcbc980fd4e39812e56a82d5bcf9596d70efe90ab86", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/version.d.ts.map": "12c7e8136d70bcc364b01ab4709d2002dcdbe4c589296b50b312a8b1380f01c2", + "node_modules/@aws-crypto/kms-keyring-node/build/module/src/version.js": "50438f43dec51197bb44fba70be2639af7b3e486a2aeb4ad4e99c02fd9f7f8d4", + "node_modules/@aws-crypto/kms-keyring-node/package.json": "435ddd6b5208ca67ae6522ee8e8a74a59c2d98f94b0dffa77e67499355820397", + "node_modules/@aws-crypto/kms-keyring/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/kms-keyring/README.md": "ce79939c81460d2517c04e36174bccdc44ad5267ff12b98f74c8eda7bf85e1e2", + "node_modules/@aws-crypto/kms-keyring/build/main/src/arn_parsing.d.ts": "fe43aaade635506b636da0f3f703f44f811ce8f8f7f09f2fce9f04c9acc609aa", + "node_modules/@aws-crypto/kms-keyring/build/main/src/arn_parsing.d.ts.map": "201c1742393ea204615aabb9e5b31494bc60691afd1e7988443cd0d80dda9d8a", + "node_modules/@aws-crypto/kms-keyring/build/main/src/arn_parsing.js": "bbbf931c221c33451ba50f7c5d955bd9f0c9f790f88c8d401e498f4972f1d2db", + "node_modules/@aws-crypto/kms-keyring/build/main/src/aws_kms_mrk_are_unique.d.ts": "0a0b70ae096cd6cd848904bc31b95a252b4d33cd50d225dea729ee4da7bdbe72", + "node_modules/@aws-crypto/kms-keyring/build/main/src/aws_kms_mrk_are_unique.d.ts.map": "1360cdd43b5913b597594d3ea09c506b001903b2aad2382c366761433c15d415", + "node_modules/@aws-crypto/kms-keyring/build/main/src/aws_kms_mrk_are_unique.js": "8ef784331f658092c6c37fe6bfdd00856f66cec63a4df4147b8693f2aa093b65", + "node_modules/@aws-crypto/kms-keyring/build/main/src/branch_key_id_supplier.d.ts": "c6e876af2e95572b5ea96bd009c25b1308435e5e8155b40e8892a47e14e54897", + "node_modules/@aws-crypto/kms-keyring/build/main/src/branch_key_id_supplier.d.ts.map": "744dfd459eb58b756a4d39fdbd8c6cff1078df9e27b9df29e491d8afb0ecf63d", + "node_modules/@aws-crypto/kms-keyring/build/main/src/branch_key_id_supplier.js": "2d59469f80d17dc6231cfd72356ae80f73a36446ad2f7cadea66952240d7c7f9", + "node_modules/@aws-crypto/kms-keyring/build/main/src/helpers.d.ts": "74cb8f04d2e8ed2dafbee95070b2a3f7ca0f60332016bbf7302f94519ed12bed", + "node_modules/@aws-crypto/kms-keyring/build/main/src/helpers.d.ts.map": "ffc8ce4a0dc09dc483966769e492666977a73537b05b04bbcf2373b5da4f066b", + "node_modules/@aws-crypto/kms-keyring/build/main/src/helpers.js": "32f0bbfbf02bcc6c46619ee7e7092d9d1e7b6bb2f0cda66e6652051ee10d8253", + "node_modules/@aws-crypto/kms-keyring/build/main/src/index.d.ts": "5910df01d1613807af664fccace5fc0d3223bba2caed256fda2eba00ea65252f", + "node_modules/@aws-crypto/kms-keyring/build/main/src/index.d.ts.map": "bfb0ed06c216043ac948f13daab7274901d54f8553b61a243c779cfd32820fc6", + "node_modules/@aws-crypto/kms-keyring/build/main/src/index.js": "47ed9e0ee0d26f8de1be197dce07a39137f302cc03cd8800b9e8c7573810308d", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_client_supplier.d.ts": "a32c277b447299acebeb9cefd65efd86fa74d8cc0accb8b79d3f57edd0882674", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_client_supplier.d.ts.map": "fe54baf08d6174d251aaa2524c38852b7d2a461132b9a4d1de07ca1a53293ced", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_client_supplier.js": "debe5d6904f97b3ba3ab6735572ce895b1409a91da40cc10413eb854aa1e5694", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_keyring.d.ts": "e0dc0a8ee8c37c2187e8d9472a025473dfcce603cc4ccc39114dc2efc7c07690", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_keyring.d.ts.map": "bacc96664638a5f34e4965103a1cce394b6ca6a154bce4b84898d0de0137b79c", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_keyring.js": "8483b20a8ba2b48d248c0660cecd1a5e68ecffa46f294d51b3ae2f8a305f9d4b", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_mrk_discovery_keyring.d.ts": "4b97353fe0b70242190945e261da0e9ddde5f9b5f8e38a8ac9cd2d6fe3525a51", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_mrk_discovery_keyring.d.ts.map": "76b6b30955eed406318f78b2f50e03254882d16ae532a58cfce7ec25b4118770", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_mrk_discovery_keyring.js": "015cc7478c4a9f54753eeaeca587c53e7d466b3c5c0d54a39407213f73fed19e", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_mrk_discovery_multi_keyring.d.ts": "24204745acb218aace2f294e309b998b2174576dd718076a64921455f45c20af", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_mrk_discovery_multi_keyring.d.ts.map": "929da00e60e3a8fb260eb0af940cd7613fa6e9d38e5a5ade378ed877ce413212", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_mrk_discovery_multi_keyring.js": "ac058c33ebea66299fcdb9d0a2ea21c786a88034801698456015e6b365d52f23", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_mrk_keyring.d.ts": "2e431350cd922c92f6b13a22717efbf4a74cc73a57b75d56bc3fe578370fefb4", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_mrk_keyring.d.ts.map": "23bac382af8420e3e639dbdbefd7d987730656199bbeaa984cfb28d2b8f2d244", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_mrk_keyring.js": "c078a96099f102796b2b0a1fa0afd6906c81281b0e42c5dfd9ae583ebc033bbc", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_mrk_strict_multi_keyring.d.ts": "b4141df39b10c7176ae88b72e1dc77bbe71f2d20c2cf4f94a28fd980b9cc267a", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_mrk_strict_multi_keyring.d.ts.map": "9ab2e63fd8a2adff3e9bd6a740960406c55b415fef2d32e83960836d69aacf72", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_mrk_strict_multi_keyring.js": "b09cf017d536e80db2ee9ffc5db5e48c246ab3fdbe671766e9c2a79257ce8366", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_types.d.ts": "f19e48c8dd7860016a373a85c11c82d4605d54e4007e8737af30c2d444efdd41", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_types.d.ts.map": "b73503389c66cca7deda6af318adc7bb0d466e2177424cd8228e385072e5bd19", + "node_modules/@aws-crypto/kms-keyring/build/main/src/kms_types.js": "e69774ac2e40bd6c257175976986569e6e7bff81ffcc78b81ecdc0ec7089acbd", + "node_modules/@aws-crypto/kms-keyring/build/main/src/region_from_kms_key_arn.d.ts": "0606c90c9445d65d02ea667342b4ae6c55f30371bd358c8edbe7dc3e2a15c631", + "node_modules/@aws-crypto/kms-keyring/build/main/src/region_from_kms_key_arn.d.ts.map": "dfef6ecdb866b6488638c9383a10db92d8ea2ff584aa1009ce007c678c2230e7", + "node_modules/@aws-crypto/kms-keyring/build/main/src/region_from_kms_key_arn.js": "65dc41cbffe999fb011341074facd6bc598fb8405d6a0517737306b85c43b60a", + "node_modules/@aws-crypto/kms-keyring/build/module/src/arn_parsing.d.ts": "fe43aaade635506b636da0f3f703f44f811ce8f8f7f09f2fce9f04c9acc609aa", + "node_modules/@aws-crypto/kms-keyring/build/module/src/arn_parsing.d.ts.map": "201c1742393ea204615aabb9e5b31494bc60691afd1e7988443cd0d80dda9d8a", + "node_modules/@aws-crypto/kms-keyring/build/module/src/arn_parsing.js": "743b7127f658b1354a2480b96c6e9eb6a6693fd387b5a2fa13694825735a5bec", + "node_modules/@aws-crypto/kms-keyring/build/module/src/aws_kms_mrk_are_unique.d.ts": "0a0b70ae096cd6cd848904bc31b95a252b4d33cd50d225dea729ee4da7bdbe72", + "node_modules/@aws-crypto/kms-keyring/build/module/src/aws_kms_mrk_are_unique.d.ts.map": "1360cdd43b5913b597594d3ea09c506b001903b2aad2382c366761433c15d415", + "node_modules/@aws-crypto/kms-keyring/build/module/src/aws_kms_mrk_are_unique.js": "109f1ced902b8c25af4270df0f4a95c7dcd038eb1d772ceb2ce6c1cfbab5fc82", + "node_modules/@aws-crypto/kms-keyring/build/module/src/branch_key_id_supplier.d.ts": "c6e876af2e95572b5ea96bd009c25b1308435e5e8155b40e8892a47e14e54897", + "node_modules/@aws-crypto/kms-keyring/build/module/src/branch_key_id_supplier.d.ts.map": "744dfd459eb58b756a4d39fdbd8c6cff1078df9e27b9df29e491d8afb0ecf63d", + "node_modules/@aws-crypto/kms-keyring/build/module/src/branch_key_id_supplier.js": "c0bc2acdddc660fc1fff1cda723a1c4aab5efa865b5fab501ae99c545feeb890", + "node_modules/@aws-crypto/kms-keyring/build/module/src/helpers.d.ts": "74cb8f04d2e8ed2dafbee95070b2a3f7ca0f60332016bbf7302f94519ed12bed", + "node_modules/@aws-crypto/kms-keyring/build/module/src/helpers.d.ts.map": "ffc8ce4a0dc09dc483966769e492666977a73537b05b04bbcf2373b5da4f066b", + "node_modules/@aws-crypto/kms-keyring/build/module/src/helpers.js": "9c65e1cb083f8180bbc512260cc429bd1e1e1dfd36206a979c137fc262278c23", + "node_modules/@aws-crypto/kms-keyring/build/module/src/index.d.ts": "5910df01d1613807af664fccace5fc0d3223bba2caed256fda2eba00ea65252f", + "node_modules/@aws-crypto/kms-keyring/build/module/src/index.d.ts.map": "bfb0ed06c216043ac948f13daab7274901d54f8553b61a243c779cfd32820fc6", + "node_modules/@aws-crypto/kms-keyring/build/module/src/index.js": "15c15aef249e6fb5537ac7f430b57b0f71136896b3d4c29a8b44e4a9036f5aa6", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_client_supplier.d.ts": "a32c277b447299acebeb9cefd65efd86fa74d8cc0accb8b79d3f57edd0882674", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_client_supplier.d.ts.map": "fe54baf08d6174d251aaa2524c38852b7d2a461132b9a4d1de07ca1a53293ced", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_client_supplier.js": "863a39ecaf96175cc033f24e421a8f5d8290e635d0e55d8622dae8b193a703d3", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_keyring.d.ts": "e0dc0a8ee8c37c2187e8d9472a025473dfcce603cc4ccc39114dc2efc7c07690", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_keyring.d.ts.map": "bacc96664638a5f34e4965103a1cce394b6ca6a154bce4b84898d0de0137b79c", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_keyring.js": "566e94c0d07c109a35ca3f63c31e74e9b9eedcfcf198f5af7ee61c7452744447", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_mrk_discovery_keyring.d.ts": "4b97353fe0b70242190945e261da0e9ddde5f9b5f8e38a8ac9cd2d6fe3525a51", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_mrk_discovery_keyring.d.ts.map": "76b6b30955eed406318f78b2f50e03254882d16ae532a58cfce7ec25b4118770", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_mrk_discovery_keyring.js": "32ed0e0e6ce3f4a827085c20374c8e56ed161eb2724cec63a4d5c4574bb91125", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_mrk_discovery_multi_keyring.d.ts": "24204745acb218aace2f294e309b998b2174576dd718076a64921455f45c20af", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_mrk_discovery_multi_keyring.d.ts.map": "929da00e60e3a8fb260eb0af940cd7613fa6e9d38e5a5ade378ed877ce413212", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_mrk_discovery_multi_keyring.js": "0e3343f74716ecd0d061040b471091eeec3b45ccd80242ed8d07cb007a5ff088", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_mrk_keyring.d.ts": "2e431350cd922c92f6b13a22717efbf4a74cc73a57b75d56bc3fe578370fefb4", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_mrk_keyring.d.ts.map": "23bac382af8420e3e639dbdbefd7d987730656199bbeaa984cfb28d2b8f2d244", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_mrk_keyring.js": "89879699196f2a93cd1d95401aa337db465321f2855116891468b9d6c5da60ab", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_mrk_strict_multi_keyring.d.ts": "b4141df39b10c7176ae88b72e1dc77bbe71f2d20c2cf4f94a28fd980b9cc267a", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_mrk_strict_multi_keyring.d.ts.map": "9ab2e63fd8a2adff3e9bd6a740960406c55b415fef2d32e83960836d69aacf72", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_mrk_strict_multi_keyring.js": "c4cc9696e0c9d73a6813cb903e4f897586828ac10816a169e9eeb34af5536f9d", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_types.d.ts": "f19e48c8dd7860016a373a85c11c82d4605d54e4007e8737af30c2d444efdd41", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_types.d.ts.map": "b73503389c66cca7deda6af318adc7bb0d466e2177424cd8228e385072e5bd19", + "node_modules/@aws-crypto/kms-keyring/build/module/src/kms_types.js": "736469e7429b6771a914635399c78717487467e6157072cd61bb9e1863db13bd", + "node_modules/@aws-crypto/kms-keyring/build/module/src/region_from_kms_key_arn.d.ts": "0606c90c9445d65d02ea667342b4ae6c55f30371bd358c8edbe7dc3e2a15c631", + "node_modules/@aws-crypto/kms-keyring/build/module/src/region_from_kms_key_arn.d.ts.map": "dfef6ecdb866b6488638c9383a10db92d8ea2ff584aa1009ce007c678c2230e7", + "node_modules/@aws-crypto/kms-keyring/build/module/src/region_from_kms_key_arn.js": "07ea4f92d36521490343623aea3b51b921d3083c38c451ab73cdbc7eff9f7b70", + "node_modules/@aws-crypto/kms-keyring/package.json": "13ebe0136f03d439c23f7842ba2e4227294fc3dd1eb7e37fed5a1df2e52e99ac", + "node_modules/@aws-crypto/material-management-node/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/material-management-node/README.md": "360b019a3ce0687ff6f763e88f661d355dde1a8bea242cfc6e663ee4b3da6c7a", + "node_modules/@aws-crypto/material-management-node/build/main/src/index.d.ts": "9e0313bfdd28ed842b5dfb6e7759758d3810ea4f817a6ed60fd47672837ae525", + "node_modules/@aws-crypto/material-management-node/build/main/src/index.d.ts.map": "84b73f80669cfa502c750f859d3cd3ff4be16df7503717be156b28e3da4c4c41", + "node_modules/@aws-crypto/material-management-node/build/main/src/index.js": "0e56de8159c9342c3b66ac27886c56a025078a945060be1c8e501a96048f7847", + "node_modules/@aws-crypto/material-management-node/build/main/src/material_helpers.d.ts": "1089db0e163441942ef7b1f204ab1c34afe1372165ba28842580a2c014754471", + "node_modules/@aws-crypto/material-management-node/build/main/src/material_helpers.d.ts.map": "18ea61050736abc6de4334b7b7eac931ebf7c2fdfdd5959468565d7547f88321", + "node_modules/@aws-crypto/material-management-node/build/main/src/material_helpers.js": "bd002da16305b09601bfbe4704ed0890e0a061f94b469f949a45991e920dffb2", + "node_modules/@aws-crypto/material-management-node/build/main/src/node_cryptographic_materials_manager.d.ts": "63edf962eb9d83407b85cc523685b3f749550654a2f01e333f7074e09c22353d", + "node_modules/@aws-crypto/material-management-node/build/main/src/node_cryptographic_materials_manager.d.ts.map": "b3544b493b05d47e844d2a9669241ec198b25daeb79f651520e5845829a51c8a", + "node_modules/@aws-crypto/material-management-node/build/main/src/node_cryptographic_materials_manager.js": "26e5a16e6df8f1279c97cca3934c3b1d7e0a3c321768557dd08a370701cb6b84", + "node_modules/@aws-crypto/material-management-node/build/main/src/types.d.ts": "e8043ae437d46c0a404e8d95ef115b7239ce2a5d696ac8399cef6babb76141f0", + "node_modules/@aws-crypto/material-management-node/build/main/src/types.d.ts.map": "12553ad495d5493bc10817b5e841a08f704feeede7cecb301c76478d66ab981f", + "node_modules/@aws-crypto/material-management-node/build/main/src/types.js": "ffac8d9b74b561b5cda5614f1e4b688cdb69f496e1d42af24060569a80bb04c4", + "node_modules/@aws-crypto/material-management-node/build/module/src/index.d.ts": "9e0313bfdd28ed842b5dfb6e7759758d3810ea4f817a6ed60fd47672837ae525", + "node_modules/@aws-crypto/material-management-node/build/module/src/index.d.ts.map": "84b73f80669cfa502c750f859d3cd3ff4be16df7503717be156b28e3da4c4c41", + "node_modules/@aws-crypto/material-management-node/build/module/src/index.js": "5a60a4b174170ec4c7163af4e204dc0dd93c2728ae359cc979a9b6c9cad69df4", + "node_modules/@aws-crypto/material-management-node/build/module/src/material_helpers.d.ts": "1089db0e163441942ef7b1f204ab1c34afe1372165ba28842580a2c014754471", + "node_modules/@aws-crypto/material-management-node/build/module/src/material_helpers.d.ts.map": "18ea61050736abc6de4334b7b7eac931ebf7c2fdfdd5959468565d7547f88321", + "node_modules/@aws-crypto/material-management-node/build/module/src/material_helpers.js": "2dea09cf3dc2fc45b7f1c1b4203cddd9de62f2f981f261704c3c0a44a7d39877", + "node_modules/@aws-crypto/material-management-node/build/module/src/node_cryptographic_materials_manager.d.ts": "63edf962eb9d83407b85cc523685b3f749550654a2f01e333f7074e09c22353d", + "node_modules/@aws-crypto/material-management-node/build/module/src/node_cryptographic_materials_manager.d.ts.map": "b3544b493b05d47e844d2a9669241ec198b25daeb79f651520e5845829a51c8a", + "node_modules/@aws-crypto/material-management-node/build/module/src/node_cryptographic_materials_manager.js": "a08435e3fc8cc5d7611b485d32847b724c351d3ed92808f3deae6fb0ece4d097", + "node_modules/@aws-crypto/material-management-node/build/module/src/types.d.ts": "e8043ae437d46c0a404e8d95ef115b7239ce2a5d696ac8399cef6babb76141f0", + "node_modules/@aws-crypto/material-management-node/build/module/src/types.d.ts.map": "12553ad495d5493bc10817b5e841a08f704feeede7cecb301c76478d66ab981f", + "node_modules/@aws-crypto/material-management-node/build/module/src/types.js": "0168a2d65fd821d8d322a8f9008350c7cb4a6b71463480e639e85e279974ec7a", + "node_modules/@aws-crypto/material-management-node/package.json": "571146c173be538b611bb9c0a3dfc5a22a4691b7707941e95f244f4ff3836a08", + "node_modules/@aws-crypto/material-management/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/material-management/README.md": "de43324c3cbc3a165880b75f465fe4aa0e45ffaf3d3bf83462deb064e48b1bd6", + "node_modules/@aws-crypto/material-management/build/main/src/algorithm_suites.d.ts": "f71594ea0936ffbb00b8bc90388f7d3d20d72fa70a823f2cd3ee32e3700cb0d0", + "node_modules/@aws-crypto/material-management/build/main/src/algorithm_suites.d.ts.map": "f42f4c1c5ab95fe1640aed75cc176c36e7ccaa7933d47b9ff397d9e573773c61", + "node_modules/@aws-crypto/material-management/build/main/src/algorithm_suites.js": "3f904f150e07dec8cda1f65e50a1abec9ac0bf105462e89fd042685d0703310a", + "node_modules/@aws-crypto/material-management/build/main/src/clone_cryptographic_material.d.ts": "261d9bba778dd39e7676148eabb5a83a983e2170143880a07030289100aa4a67", + "node_modules/@aws-crypto/material-management/build/main/src/clone_cryptographic_material.d.ts.map": "e7f73ceb978d05ebe8fdce75ef80d509e268374f55fe154023c7b7709c921bb1", + "node_modules/@aws-crypto/material-management/build/main/src/clone_cryptographic_material.js": "3ccaccec7db7db943c6473490df9637557e8bc9d44318ab33a7aa5fda90b6197", + "node_modules/@aws-crypto/material-management/build/main/src/cryptographic_material.d.ts": "cd8fd6a6ebf68662b45ad4a372b3af5c15f9f4a26e30abcc120f2fbcdf9d1f73", + "node_modules/@aws-crypto/material-management/build/main/src/cryptographic_material.d.ts.map": "be6dce56b65d4a70ebbc71a8324571bce80c308c13fb7f521a34bfc2ceac355d", + "node_modules/@aws-crypto/material-management/build/main/src/cryptographic_material.js": "c388abac43e8642b144be53c0ddc2d433f2edee9a09284fbccfcdf8414421c4b", + "node_modules/@aws-crypto/material-management/build/main/src/ecc_decode.d.ts": "c074e4b272ff914eafe6abc3fe89c4e6c6314a82fce7d3f5b3ec392f76735ceb", + "node_modules/@aws-crypto/material-management/build/main/src/ecc_decode.d.ts.map": "1ecf3a67388a025da1f5567fa762281a584c71a18879a81ab432629011ba20fc", + "node_modules/@aws-crypto/material-management/build/main/src/ecc_decode.js": "da0099608fcd226c1518f58fb49e283afbe6452b974b917d008233e0a3a10b4f", + "node_modules/@aws-crypto/material-management/build/main/src/ecc_encode.d.ts": "d2b843b9e6cd279e05b8d3a3033dc1d740e6089ff49d6c5f86ef1eb75f07ec3f", + "node_modules/@aws-crypto/material-management/build/main/src/ecc_encode.d.ts.map": "6c7808b45329cc141197b8cf72a265c155b3e87bc6d59ab73e08da67661c679e", + "node_modules/@aws-crypto/material-management/build/main/src/ecc_encode.js": "293fc758cf9feeb64e6e0ddf1ccb1a1346165a173e7e7f0e58c4681284808d51", + "node_modules/@aws-crypto/material-management/build/main/src/encrypted_data_key.d.ts": "430d698a95a084726ab538ed6e8f57a66fb66ee9ece73ba1437258c45aa74794", + "node_modules/@aws-crypto/material-management/build/main/src/encrypted_data_key.d.ts.map": "33b89054a15ec6a1351c3bbbba54aa05ec46227c2e851b889e920f7f3ef57335", + "node_modules/@aws-crypto/material-management/build/main/src/encrypted_data_key.js": "d0a38e00c64c630b3cf1c810bde762b57f2457528886740ac2ae0fca04516c47", + "node_modules/@aws-crypto/material-management/build/main/src/error.d.ts": "fc08ae9069814ba16568daebc3d71c212189d6af4518c94d29b0868b2aaa9449", + "node_modules/@aws-crypto/material-management/build/main/src/error.d.ts.map": "999026b19b37d8193eaf936f94e3eb4fa733a8bfe296ff0ffdc6892e3e8221c1", + "node_modules/@aws-crypto/material-management/build/main/src/error.js": "1ea2126f28898fe56299c2432e9359faa7ff993ead9056bb8ac9d6c1fa77e853", + "node_modules/@aws-crypto/material-management/build/main/src/immutable_class.d.ts": "4e94a4f6f79eef551c24eb9f6ea259da5fdb1126219d1372f897285393300860", + "node_modules/@aws-crypto/material-management/build/main/src/immutable_class.d.ts.map": "2de5f0d027c6c2f682afbf2557d541fb7614e167e80a01a1e6105f0165bb25bd", + "node_modules/@aws-crypto/material-management/build/main/src/immutable_class.js": "f0a379989952c35a3f744ad7529b86d776006819c5427cc2340098ea63aa1663", + "node_modules/@aws-crypto/material-management/build/main/src/index.d.ts": "b6973239276df98a0846453202899256478f87e333db00d0ccc13866ce9576c6", + "node_modules/@aws-crypto/material-management/build/main/src/index.d.ts.map": "efd4dba074c0f6da29de0c58cf2875616ec91925049c30bde72ce39810ee3d26", + "node_modules/@aws-crypto/material-management/build/main/src/index.js": "dec48419b3fc8f93ab9201f5f63a2e83addd48c346851293e635b3b1b1cec308", + "node_modules/@aws-crypto/material-management/build/main/src/keyring.d.ts": "5bde23e441c9099447ff99cde9f17162c2c94c14b2ba7c25f405246356baa006", + "node_modules/@aws-crypto/material-management/build/main/src/keyring.d.ts.map": "351ae56ae1a27df1f3fabe98e3936613465de522d0b0bf714089fbcc90af2b1e", + "node_modules/@aws-crypto/material-management/build/main/src/keyring.js": "09b6ce01ea49a42586c55b0b2311de42f6128c4a6cb18dcd0a702c6d3e88fc8e", + "node_modules/@aws-crypto/material-management/build/main/src/keyring_trace.d.ts": "ae02f8abc994b47dea7be4fdcea59d6987e7997a312a2a292b13b87e46a52b42", + "node_modules/@aws-crypto/material-management/build/main/src/keyring_trace.d.ts.map": "47bfc6be07c15adb15485d6da13fba17836c2f7b75394096ef2eb17824412a99", + "node_modules/@aws-crypto/material-management/build/main/src/keyring_trace.js": "4aeaea0cf62bc3ee24b5bbc2fbb4cfc929a2e927f1799f59ee6186568c0b3120", + "node_modules/@aws-crypto/material-management/build/main/src/materials_manager.d.ts": "ed1ad9ba64464d743bde931b5093086cac363ac808893d5739389bf6f6c39f67", + "node_modules/@aws-crypto/material-management/build/main/src/materials_manager.d.ts.map": "a9d3053d60bcef556479ae372f23368ea47c4ddbfc177fea26214e9cfed30b51", + "node_modules/@aws-crypto/material-management/build/main/src/materials_manager.js": "c042818353ce680dd3cb52107073d38117da49ae44d89f0b76b27ed1dfe6fa46", + "node_modules/@aws-crypto/material-management/build/main/src/multi_keyring.d.ts": "120f89e07e5d5156b9d3f28c1d0c01259cd86a3f91acfbbddd2c0679e0e2bf74", + "node_modules/@aws-crypto/material-management/build/main/src/multi_keyring.d.ts.map": "9582bf92e7a5a6965782f9eb79b2a8dc170bb906155d051ca8e994a0ff3a8a23", + "node_modules/@aws-crypto/material-management/build/main/src/multi_keyring.js": "2681050bbe83c357dc3b482e7c72c2516352d916feda0d007b748c023aa27b28", + "node_modules/@aws-crypto/material-management/build/main/src/needs.d.ts": "8dd6f6fa90a6a3af4802927c5dd42bbb0b0a359068e4ce889e97df502b01d362", + "node_modules/@aws-crypto/material-management/build/main/src/needs.d.ts.map": "b2dadc132f566acf270b6c45cd4010aa718ce14565f4c4aa5119f9737a905710", + "node_modules/@aws-crypto/material-management/build/main/src/needs.js": "1ba9192386ee2c87019d581fac76e3ba138a222d18834d2daf4d4a61dfcbc5a4", + "node_modules/@aws-crypto/material-management/build/main/src/node_algorithms.d.ts": "2332030092955da1199e9f03d0720ec0cfae5c508d3030bb9d4e02a3d999573e", + "node_modules/@aws-crypto/material-management/build/main/src/node_algorithms.d.ts.map": "652125c1d9fca4b518657a67412811ca597cd93bae28e44b75d55602672a269b", + "node_modules/@aws-crypto/material-management/build/main/src/node_algorithms.js": "f855e8bc1c7afe023d091df43b20dbcb6d1bd03c78efae4797b112550586bebc", + "node_modules/@aws-crypto/material-management/build/main/src/pem_helpers.d.ts": "7a8eb6db3b85ce232f1e4aae8bbc019d01270f57d856c6abed914d67d07e700c", + "node_modules/@aws-crypto/material-management/build/main/src/pem_helpers.d.ts.map": "c5cca0ed359d4601a43ac176c23482c8705e4a355fe53f522bc948f80b273f2a", + "node_modules/@aws-crypto/material-management/build/main/src/pem_helpers.js": "87dd6b58d56ddd027c12e74743fe98f96497f35fb72d39340012c1f5bec62bd0", + "node_modules/@aws-crypto/material-management/build/main/src/signature_key.d.ts": "221a6bfd865015f4d6d65f9884c0694310cb711c07723ca430ce28571919d2b5", + "node_modules/@aws-crypto/material-management/build/main/src/signature_key.d.ts.map": "049c3c5e7476ec8a8c34d68b29660db9872b93c08bdb56b30440a5e97c2f3f99", + "node_modules/@aws-crypto/material-management/build/main/src/signature_key.js": "88d271d542d0a66d1a876f06993c04e95b531966f69b9f3659578158fd0f186e", + "node_modules/@aws-crypto/material-management/build/main/src/types.d.ts": "80c7bb0b3306457226dacd8352b3699d08084f94e4e05d5992f2902ac9df7180", + "node_modules/@aws-crypto/material-management/build/main/src/types.d.ts.map": "77b8e72eb07242c6fc404635b6dd1265e4d173ab9b7f59d4fdafe2ff47f7d963", + "node_modules/@aws-crypto/material-management/build/main/src/types.js": "ffac8d9b74b561b5cda5614f1e4b688cdb69f496e1d42af24060569a80bb04c4", + "node_modules/@aws-crypto/material-management/build/main/src/web_crypto_algorithms.d.ts": "fb0f85d84d8869defcc889ce0dccb9233039c6697d588e907c46239d81e17ec3", + "node_modules/@aws-crypto/material-management/build/main/src/web_crypto_algorithms.d.ts.map": "2536b28c445f0d8e5c44f059d64064bc149c504e57912e1d5b278940c65b1e22", + "node_modules/@aws-crypto/material-management/build/main/src/web_crypto_algorithms.js": "bcc7594fcd5adf087f89e25f1690a2bfd198f0f6510c3897a8a2ad9062efc3ec", + "node_modules/@aws-crypto/material-management/build/module/src/algorithm_suites.d.ts": "f71594ea0936ffbb00b8bc90388f7d3d20d72fa70a823f2cd3ee32e3700cb0d0", + "node_modules/@aws-crypto/material-management/build/module/src/algorithm_suites.d.ts.map": "f42f4c1c5ab95fe1640aed75cc176c36e7ccaa7933d47b9ff397d9e573773c61", + "node_modules/@aws-crypto/material-management/build/module/src/algorithm_suites.js": "c5b6513d81af71c1de4251d7e94bc1d38cf828d57d01189b70021261ce8636f8", + "node_modules/@aws-crypto/material-management/build/module/src/clone_cryptographic_material.d.ts": "261d9bba778dd39e7676148eabb5a83a983e2170143880a07030289100aa4a67", + "node_modules/@aws-crypto/material-management/build/module/src/clone_cryptographic_material.d.ts.map": "e7f73ceb978d05ebe8fdce75ef80d509e268374f55fe154023c7b7709c921bb1", + "node_modules/@aws-crypto/material-management/build/module/src/clone_cryptographic_material.js": "19f9b925b36cd39df4c0c72f8d5031e7e8e1f0512c3766dd9e67aff9c1cd6037", + "node_modules/@aws-crypto/material-management/build/module/src/cryptographic_material.d.ts": "cd8fd6a6ebf68662b45ad4a372b3af5c15f9f4a26e30abcc120f2fbcdf9d1f73", + "node_modules/@aws-crypto/material-management/build/module/src/cryptographic_material.d.ts.map": "be6dce56b65d4a70ebbc71a8324571bce80c308c13fb7f521a34bfc2ceac355d", + "node_modules/@aws-crypto/material-management/build/module/src/cryptographic_material.js": "fb7bca82e0e2b93f457a08e3585f1bae6b5288571a185f271eac53e8228b97f0", + "node_modules/@aws-crypto/material-management/build/module/src/ecc_decode.d.ts": "c074e4b272ff914eafe6abc3fe89c4e6c6314a82fce7d3f5b3ec392f76735ceb", + "node_modules/@aws-crypto/material-management/build/module/src/ecc_decode.d.ts.map": "1ecf3a67388a025da1f5567fa762281a584c71a18879a81ab432629011ba20fc", + "node_modules/@aws-crypto/material-management/build/module/src/ecc_decode.js": "421a743605eaf57c7328c10f83d441c98d2c6c4402a07b42dcb5b881d092ef87", + "node_modules/@aws-crypto/material-management/build/module/src/ecc_encode.d.ts": "d2b843b9e6cd279e05b8d3a3033dc1d740e6089ff49d6c5f86ef1eb75f07ec3f", + "node_modules/@aws-crypto/material-management/build/module/src/ecc_encode.d.ts.map": "6c7808b45329cc141197b8cf72a265c155b3e87bc6d59ab73e08da67661c679e", + "node_modules/@aws-crypto/material-management/build/module/src/ecc_encode.js": "22b1386662686311ce6d1962647cc7b18da5ef3f608a4b5412f90f3f50bae5f6", + "node_modules/@aws-crypto/material-management/build/module/src/encrypted_data_key.d.ts": "430d698a95a084726ab538ed6e8f57a66fb66ee9ece73ba1437258c45aa74794", + "node_modules/@aws-crypto/material-management/build/module/src/encrypted_data_key.d.ts.map": "33b89054a15ec6a1351c3bbbba54aa05ec46227c2e851b889e920f7f3ef57335", + "node_modules/@aws-crypto/material-management/build/module/src/encrypted_data_key.js": "6bf3a20e67f22c4b1f4a38207a2f949fb8a3d583456ba10f4d8e311701e5db7b", + "node_modules/@aws-crypto/material-management/build/module/src/error.d.ts": "fc08ae9069814ba16568daebc3d71c212189d6af4518c94d29b0868b2aaa9449", + "node_modules/@aws-crypto/material-management/build/module/src/error.d.ts.map": "999026b19b37d8193eaf936f94e3eb4fa733a8bfe296ff0ffdc6892e3e8221c1", + "node_modules/@aws-crypto/material-management/build/module/src/error.js": "0b7d46ddc4f5853315ef8d2d3c2a9db06c7321dcdf67b57619110ff2b98fe60a", + "node_modules/@aws-crypto/material-management/build/module/src/immutable_class.d.ts": "4e94a4f6f79eef551c24eb9f6ea259da5fdb1126219d1372f897285393300860", + "node_modules/@aws-crypto/material-management/build/module/src/immutable_class.d.ts.map": "2de5f0d027c6c2f682afbf2557d541fb7614e167e80a01a1e6105f0165bb25bd", + "node_modules/@aws-crypto/material-management/build/module/src/immutable_class.js": "b324e9e826e989f772de1109664d38cc4aea45e61bfa87ee75666f1cd79adf56", + "node_modules/@aws-crypto/material-management/build/module/src/index.d.ts": "b6973239276df98a0846453202899256478f87e333db00d0ccc13866ce9576c6", + "node_modules/@aws-crypto/material-management/build/module/src/index.d.ts.map": "efd4dba074c0f6da29de0c58cf2875616ec91925049c30bde72ce39810ee3d26", + "node_modules/@aws-crypto/material-management/build/module/src/index.js": "6328d51378b6e3f6266459f54a41ca2e8dbf3d786f2ef96781667257f7f026d7", + "node_modules/@aws-crypto/material-management/build/module/src/keyring.d.ts": "5bde23e441c9099447ff99cde9f17162c2c94c14b2ba7c25f405246356baa006", + "node_modules/@aws-crypto/material-management/build/module/src/keyring.d.ts.map": "351ae56ae1a27df1f3fabe98e3936613465de522d0b0bf714089fbcc90af2b1e", + "node_modules/@aws-crypto/material-management/build/module/src/keyring.js": "5d88286cfc457b4f5b4a007d017dacb13a131606824e31a9c2cbf77854efed5d", + "node_modules/@aws-crypto/material-management/build/module/src/keyring_trace.d.ts": "ae02f8abc994b47dea7be4fdcea59d6987e7997a312a2a292b13b87e46a52b42", + "node_modules/@aws-crypto/material-management/build/module/src/keyring_trace.d.ts.map": "47bfc6be07c15adb15485d6da13fba17836c2f7b75394096ef2eb17824412a99", + "node_modules/@aws-crypto/material-management/build/module/src/keyring_trace.js": "6f4004b843786609d78f14957a461a59a0b920d6f6434aacd2face0d3a860222", + "node_modules/@aws-crypto/material-management/build/module/src/materials_manager.d.ts": "ed1ad9ba64464d743bde931b5093086cac363ac808893d5739389bf6f6c39f67", + "node_modules/@aws-crypto/material-management/build/module/src/materials_manager.d.ts.map": "a9d3053d60bcef556479ae372f23368ea47c4ddbfc177fea26214e9cfed30b51", + "node_modules/@aws-crypto/material-management/build/module/src/materials_manager.js": "1607f3315e297b4dd68041cbe288e90994fb2285a258ae3ef4079d781ef137de", + "node_modules/@aws-crypto/material-management/build/module/src/multi_keyring.d.ts": "120f89e07e5d5156b9d3f28c1d0c01259cd86a3f91acfbbddd2c0679e0e2bf74", + "node_modules/@aws-crypto/material-management/build/module/src/multi_keyring.d.ts.map": "9582bf92e7a5a6965782f9eb79b2a8dc170bb906155d051ca8e994a0ff3a8a23", + "node_modules/@aws-crypto/material-management/build/module/src/multi_keyring.js": "9d515d53a847b9339cd256fb29be74e5ddf60cdb9590291922314e820e30e190", + "node_modules/@aws-crypto/material-management/build/module/src/needs.d.ts": "8dd6f6fa90a6a3af4802927c5dd42bbb0b0a359068e4ce889e97df502b01d362", + "node_modules/@aws-crypto/material-management/build/module/src/needs.d.ts.map": "b2dadc132f566acf270b6c45cd4010aa718ce14565f4c4aa5119f9737a905710", + "node_modules/@aws-crypto/material-management/build/module/src/needs.js": "6d1fcc4d56285162bea0ef5f9382f23ce9fe5da32e3242d3b9ad1f64dbc69429", + "node_modules/@aws-crypto/material-management/build/module/src/node_algorithms.d.ts": "2332030092955da1199e9f03d0720ec0cfae5c508d3030bb9d4e02a3d999573e", + "node_modules/@aws-crypto/material-management/build/module/src/node_algorithms.d.ts.map": "652125c1d9fca4b518657a67412811ca597cd93bae28e44b75d55602672a269b", + "node_modules/@aws-crypto/material-management/build/module/src/node_algorithms.js": "c98b3d180760de917e6400d8f96f1678ddb2cce5e81732269f72049e6b2e154f", + "node_modules/@aws-crypto/material-management/build/module/src/pem_helpers.d.ts": "7a8eb6db3b85ce232f1e4aae8bbc019d01270f57d856c6abed914d67d07e700c", + "node_modules/@aws-crypto/material-management/build/module/src/pem_helpers.d.ts.map": "c5cca0ed359d4601a43ac176c23482c8705e4a355fe53f522bc948f80b273f2a", + "node_modules/@aws-crypto/material-management/build/module/src/pem_helpers.js": "f7657fcbeba73bae34cfccbd1bac262b18e94226ec2b80eb7f8588d234530bda", + "node_modules/@aws-crypto/material-management/build/module/src/signature_key.d.ts": "221a6bfd865015f4d6d65f9884c0694310cb711c07723ca430ce28571919d2b5", + "node_modules/@aws-crypto/material-management/build/module/src/signature_key.d.ts.map": "049c3c5e7476ec8a8c34d68b29660db9872b93c08bdb56b30440a5e97c2f3f99", + "node_modules/@aws-crypto/material-management/build/module/src/signature_key.js": "cf621deb97384c45c439a6e35ef806b1194d7facc7396bb5f862f69e1a8122ee", + "node_modules/@aws-crypto/material-management/build/module/src/types.d.ts": "80c7bb0b3306457226dacd8352b3699d08084f94e4e05d5992f2902ac9df7180", + "node_modules/@aws-crypto/material-management/build/module/src/types.d.ts.map": "77b8e72eb07242c6fc404635b6dd1265e4d173ab9b7f59d4fdafe2ff47f7d963", + "node_modules/@aws-crypto/material-management/build/module/src/types.js": "0168a2d65fd821d8d322a8f9008350c7cb4a6b71463480e639e85e279974ec7a", + "node_modules/@aws-crypto/material-management/build/module/src/web_crypto_algorithms.d.ts": "fb0f85d84d8869defcc889ce0dccb9233039c6697d588e907c46239d81e17ec3", + "node_modules/@aws-crypto/material-management/build/module/src/web_crypto_algorithms.d.ts.map": "2536b28c445f0d8e5c44f059d64064bc149c504e57912e1d5b278940c65b1e22", + "node_modules/@aws-crypto/material-management/build/module/src/web_crypto_algorithms.js": "4aaf6ca8da7c11c00dd8399680332021d10c04f46cdb6737389506f06b8f3869", + "node_modules/@aws-crypto/material-management/package.json": "fd1180e9dca5d23c527325379fd6d51897c326593c6039ab24de89dba42f94bc", + "node_modules/@aws-crypto/raw-aes-keyring-node/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/raw-aes-keyring-node/README.md": "0b86f4244f4344af73a777dc467df6ff1882a9d6e7b2ebeb02a93692913b2693", + "node_modules/@aws-crypto/raw-aes-keyring-node/build/main/src/index.d.ts": "e8d5089e9c9ca0a099900ede2a69c895109934c7fdce4515bfb06806578450ef", + "node_modules/@aws-crypto/raw-aes-keyring-node/build/main/src/index.d.ts.map": "84b3f5d42d06ed1933e8ea90b3d248ab94d6e5a8024bae7fa60ec7549ba04737", + "node_modules/@aws-crypto/raw-aes-keyring-node/build/main/src/index.js": "4be11154d04ecc8fa41e0de7262b8c6ec92b31e4ff6ae4d9f0d1c88835db90f0", + "node_modules/@aws-crypto/raw-aes-keyring-node/build/main/src/raw_aes_keyring_node.d.ts": "1ef5e7634f6f879866306e7cc83e534e1db374f7b69c244239b8b238a5c5d049", + "node_modules/@aws-crypto/raw-aes-keyring-node/build/main/src/raw_aes_keyring_node.d.ts.map": "0d0b3f078fd30dc86aacf47dc4aca679ed760c03ae7b7954de5a2ebef9c3d2cb", + "node_modules/@aws-crypto/raw-aes-keyring-node/build/main/src/raw_aes_keyring_node.js": "8e540df0758c2afe202f35203986735aacd5850a7ba15b19f3af26a74a0214ed", + "node_modules/@aws-crypto/raw-aes-keyring-node/build/module/src/index.d.ts": "e8d5089e9c9ca0a099900ede2a69c895109934c7fdce4515bfb06806578450ef", + "node_modules/@aws-crypto/raw-aes-keyring-node/build/module/src/index.d.ts.map": "84b3f5d42d06ed1933e8ea90b3d248ab94d6e5a8024bae7fa60ec7549ba04737", + "node_modules/@aws-crypto/raw-aes-keyring-node/build/module/src/index.js": "72c6a8b93c66648a6ac39d20b3c40912afd3841246ce8c9bff7083104b93d399", + "node_modules/@aws-crypto/raw-aes-keyring-node/build/module/src/raw_aes_keyring_node.d.ts": "1ef5e7634f6f879866306e7cc83e534e1db374f7b69c244239b8b238a5c5d049", + "node_modules/@aws-crypto/raw-aes-keyring-node/build/module/src/raw_aes_keyring_node.d.ts.map": "0d0b3f078fd30dc86aacf47dc4aca679ed760c03ae7b7954de5a2ebef9c3d2cb", + "node_modules/@aws-crypto/raw-aes-keyring-node/build/module/src/raw_aes_keyring_node.js": "51374770e2c392131f439d74c96d1eeac4b99ca27a9b785758386dda2fcb9a91", + "node_modules/@aws-crypto/raw-aes-keyring-node/package.json": "b8f5b6019e2e1a4edcb2a55b4d2efe3ca4a55e7f547660327468f2bea0fe3405", + "node_modules/@aws-crypto/raw-keyring/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/raw-keyring/README.md": "ccad527affdefe902788076ff7e5ed44d7e8ebf3977c59e491f47c55494073ca", + "node_modules/@aws-crypto/raw-keyring/build/main/src/index.d.ts": "3842ff035632bb688bbb21ef94c8540239b2cfa787cef335ea5061459d6c8898", + "node_modules/@aws-crypto/raw-keyring/build/main/src/index.d.ts.map": "f9d57b4f37dcfbf25ef75d1abc37655871743a90ba32f730e5ee8e7315adc20c", + "node_modules/@aws-crypto/raw-keyring/build/main/src/index.js": "0f71666f43b0894f43b37cd4acacde9e757b01ffa831a56d82ebe29f3ac003eb", + "node_modules/@aws-crypto/raw-keyring/build/main/src/raw_aes_algorithm_suite.d.ts": "51065e9da14b64a1416e20782b46a1146a2b75bcfc8b259cba3cec1eaed70e9d", + "node_modules/@aws-crypto/raw-keyring/build/main/src/raw_aes_algorithm_suite.d.ts.map": "0efbc996668bc21da3de7916575fb5b6015fa0102dd212357604e63666de6d09", + "node_modules/@aws-crypto/raw-keyring/build/main/src/raw_aes_algorithm_suite.js": "3ef9bf175abce7c10b9ee9b421c8ed49fc5414f1937bb8e1f32406fdb963618c", + "node_modules/@aws-crypto/raw-keyring/build/main/src/raw_aes_encrypted_data_keys.d.ts": "d83efab88f17d907759f7fea4a602cf71dea8335fbaeecf9c84d11fb59586b6c", + "node_modules/@aws-crypto/raw-keyring/build/main/src/raw_aes_encrypted_data_keys.d.ts.map": "80362c2d3cbcfc6f17b7ad50af5d9a00fc32b31ce09edb9916387819b38f9633", + "node_modules/@aws-crypto/raw-keyring/build/main/src/raw_aes_encrypted_data_keys.js": "d50c288db47b75ce3a930b811a844ca3dbfd8c8918f41c88174a50410d63b843", + "node_modules/@aws-crypto/raw-keyring/build/main/src/raw_aes_material.d.ts": "57b3a66dc113c6a5a5d764f6d7e135384a5058a8793a65c075c384b188439ca3", + "node_modules/@aws-crypto/raw-keyring/build/main/src/raw_aes_material.d.ts.map": "bab3eeacf564f1136ddaf1b852ab70ab498064aee7256f0a733f09bc5b01f7b4", + "node_modules/@aws-crypto/raw-keyring/build/main/src/raw_aes_material.js": "4ebeec50047dfc42db6a409babc77e4f580f9a3adf1d0cc9623a64a778da591b", + "node_modules/@aws-crypto/raw-keyring/build/main/src/raw_keyring_decorators.d.ts": "118036e18c789112d791b6a237c232effe6a0cef08562365b5e360d68ea35a9b", + "node_modules/@aws-crypto/raw-keyring/build/main/src/raw_keyring_decorators.d.ts.map": "c40155f218ac5c25961aa8c423584ec12375b52be7ff50bb51c3bd9c4d1cdafa", + "node_modules/@aws-crypto/raw-keyring/build/main/src/raw_keyring_decorators.js": "d67a1d030851456abb57c586e22dcde162469692c60a9e414cf4689a42ed7c15", + "node_modules/@aws-crypto/raw-keyring/build/module/src/index.d.ts": "3842ff035632bb688bbb21ef94c8540239b2cfa787cef335ea5061459d6c8898", + "node_modules/@aws-crypto/raw-keyring/build/module/src/index.d.ts.map": "f9d57b4f37dcfbf25ef75d1abc37655871743a90ba32f730e5ee8e7315adc20c", + "node_modules/@aws-crypto/raw-keyring/build/module/src/index.js": "683b6d3f7366804a208dcfe24e1f889ebde42af8004a7d6361f9bc2d5e79ccf3", + "node_modules/@aws-crypto/raw-keyring/build/module/src/raw_aes_algorithm_suite.d.ts": "51065e9da14b64a1416e20782b46a1146a2b75bcfc8b259cba3cec1eaed70e9d", + "node_modules/@aws-crypto/raw-keyring/build/module/src/raw_aes_algorithm_suite.d.ts.map": "0efbc996668bc21da3de7916575fb5b6015fa0102dd212357604e63666de6d09", + "node_modules/@aws-crypto/raw-keyring/build/module/src/raw_aes_algorithm_suite.js": "d64d59b73a1449aa66c4fbb62281ffa5671b74d1c638117b4e2e9534987075d6", + "node_modules/@aws-crypto/raw-keyring/build/module/src/raw_aes_encrypted_data_keys.d.ts": "d83efab88f17d907759f7fea4a602cf71dea8335fbaeecf9c84d11fb59586b6c", + "node_modules/@aws-crypto/raw-keyring/build/module/src/raw_aes_encrypted_data_keys.d.ts.map": "80362c2d3cbcfc6f17b7ad50af5d9a00fc32b31ce09edb9916387819b38f9633", + "node_modules/@aws-crypto/raw-keyring/build/module/src/raw_aes_encrypted_data_keys.js": "c3f5fe2bcb27d30d2766c04c3203f43355b6d6045fba61c322ea10a0210da35a", + "node_modules/@aws-crypto/raw-keyring/build/module/src/raw_aes_material.d.ts": "57b3a66dc113c6a5a5d764f6d7e135384a5058a8793a65c075c384b188439ca3", + "node_modules/@aws-crypto/raw-keyring/build/module/src/raw_aes_material.d.ts.map": "bab3eeacf564f1136ddaf1b852ab70ab498064aee7256f0a733f09bc5b01f7b4", + "node_modules/@aws-crypto/raw-keyring/build/module/src/raw_aes_material.js": "8b0ffdc09ddc23152476a649f538db550a1119e791e663e2f1c185aba2602de6", + "node_modules/@aws-crypto/raw-keyring/build/module/src/raw_keyring_decorators.d.ts": "118036e18c789112d791b6a237c232effe6a0cef08562365b5e360d68ea35a9b", + "node_modules/@aws-crypto/raw-keyring/build/module/src/raw_keyring_decorators.d.ts.map": "c40155f218ac5c25961aa8c423584ec12375b52be7ff50bb51c3bd9c4d1cdafa", + "node_modules/@aws-crypto/raw-keyring/build/module/src/raw_keyring_decorators.js": "c8790778453ca68982c7c6e570fec0c6a136db0d11f6a012e5c8ea4437655f94", + "node_modules/@aws-crypto/raw-keyring/package.json": "ec666722d87291715ab46850682271eb3837df9bb7ef06bf5b21bcaa76e68a2d", + "node_modules/@aws-crypto/raw-rsa-keyring-node/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/raw-rsa-keyring-node/README.md": "1697a322300aae036c1e970f94375a9b48b684bb2d9c3ff4acacede4b9ac0362", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/main/src/index.d.ts": "aa1bba1f79c9f6a572c2154e203e0ee1568a60de3d3fdf09d8464802dd2e6be5", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/main/src/index.d.ts.map": "7f71e158b06121c22c183e1abb68b04bdb6f548f524c521f01050873a1663418", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/main/src/index.js": "7823c581237a30f85c8d6dbe6f33f0bb67f984e2dafd9dcae22a0c9064e04427", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/main/src/oaep_hash_supported.d.ts": "1babc050bf5aafd653fbc34b3914dfd1c3b06363433e14d2eec5b2bdc89bec31", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/main/src/oaep_hash_supported.d.ts.map": "4bc120ac86beca07441d2608f5ecbefbf38f5e77351f0b3988b1755d72defae6", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/main/src/oaep_hash_supported.js": "824ce9c30881d79610c6c6cc014e5cbaaa9464e8e75443a4b609f6adca0cbe8b", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/main/src/raw_rsa_keyring_node.d.ts": "809b7e238f54874aff4093b963e3fe6b3c0c69d41297bed88f3bb313cdd43035", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/main/src/raw_rsa_keyring_node.d.ts.map": "57a7fd7f555f80a7409e905de9ada5a539b62e24c05f92155e7fbbb9e426488c", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/main/src/raw_rsa_keyring_node.js": "f025f3e349fa213efbd94930cc87c50447929abd43ec0b48904d9777e2606882", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/module/src/index.d.ts": "aa1bba1f79c9f6a572c2154e203e0ee1568a60de3d3fdf09d8464802dd2e6be5", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/module/src/index.d.ts.map": "7f71e158b06121c22c183e1abb68b04bdb6f548f524c521f01050873a1663418", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/module/src/index.js": "a556e593949959fc92f5ca07adec3081357829457c0287f2f75c3aa34fcb77a6", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/module/src/oaep_hash_supported.d.ts": "1babc050bf5aafd653fbc34b3914dfd1c3b06363433e14d2eec5b2bdc89bec31", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/module/src/oaep_hash_supported.d.ts.map": "4bc120ac86beca07441d2608f5ecbefbf38f5e77351f0b3988b1755d72defae6", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/module/src/oaep_hash_supported.js": "eef0286f8cdd3d172f138649829baf8ee075bc81fd8d519a1f8ae4ac8a8533b3", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/module/src/raw_rsa_keyring_node.d.ts": "809b7e238f54874aff4093b963e3fe6b3c0c69d41297bed88f3bb313cdd43035", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/module/src/raw_rsa_keyring_node.d.ts.map": "57a7fd7f555f80a7409e905de9ada5a539b62e24c05f92155e7fbbb9e426488c", + "node_modules/@aws-crypto/raw-rsa-keyring-node/build/module/src/raw_rsa_keyring_node.js": "a5014ed3b00718dbf2fcdd2a443607d45d4d03958ec5d7144efc79bb6076e92d", + "node_modules/@aws-crypto/raw-rsa-keyring-node/package.json": "babb55436bc14b9ffe131a3cf2967b8a32fda532b9d740d08077086fbced63ea", + "node_modules/@aws-crypto/serialize/LICENSE": "ace0d7e53b96d00ba57ffb9d3b4b4ccbee3ac54633eff82e89a607cc1b93d618", + "node_modules/@aws-crypto/serialize/README.md": "5c14d700faa557b865b7516fff40c9f12557d2663f4a9c0535d16b6202d478ff", + "node_modules/@aws-crypto/serialize/build/main/src/aad_factory.d.ts": "63e137913d66133ee095a564082d78f75690be4c5e457165474b02dc9aa294ae", + "node_modules/@aws-crypto/serialize/build/main/src/aad_factory.d.ts.map": "60824776160d82efa753ab9715f6a33dd93f5f7e889854e835c03d5b7075d4fa", + "node_modules/@aws-crypto/serialize/build/main/src/aad_factory.js": "565bfc4949130ed731faac73a8c1e5cd0e394fd16f4dddda29f56bc467a9ad8c", + "node_modules/@aws-crypto/serialize/build/main/src/concat_buffers.d.ts": "fd6a2f699dff39b332be67424fc17a81368cae7f9b407337411832face9ea612", + "node_modules/@aws-crypto/serialize/build/main/src/concat_buffers.d.ts.map": "4eaf4e47f6f58ffc11a168c5724dd1b1e24add46e0c006d163764ada82594602", + "node_modules/@aws-crypto/serialize/build/main/src/concat_buffers.js": "f6b9ffb33532135d7ef1331f753d98d2a394dab5da69f758c247ad0c9c752555", + "node_modules/@aws-crypto/serialize/build/main/src/decode_body_header.d.ts": "edd9d10c3746d44e6d5ec134182fb7ac9c97ee50237e02482d340763c520746e", + "node_modules/@aws-crypto/serialize/build/main/src/decode_body_header.d.ts.map": "993d0dbbaa9cf1f919c7bd7800f208d9ab0dc77ee56eec1df36568de2e48ea38", + "node_modules/@aws-crypto/serialize/build/main/src/decode_body_header.js": "e327f5c7dec7e663f548e949e87b18b0fade6d34ecd11064cdd07ccd818fcaad", + "node_modules/@aws-crypto/serialize/build/main/src/decode_encryption_context.d.ts": "6005e73605fb20d8067db90d4c13553c0d4cd582eda8152693ffedf19ce043e2", + "node_modules/@aws-crypto/serialize/build/main/src/decode_encryption_context.d.ts.map": "5dabe5f47837f03f6b46780e91d07d751e7e6910f0c78d9902c28c99f3220b30", + "node_modules/@aws-crypto/serialize/build/main/src/decode_encryption_context.js": "5368dd049f94d6be860d7620225db6c4df2950dd1b5d4dffa3f476ba74004744", + "node_modules/@aws-crypto/serialize/build/main/src/deserialize_encrypted_data_keys.d.ts": "5a81b748cc5581ba041677a48ec558f5e25833076452e0246ddeb0f45006acbd", + "node_modules/@aws-crypto/serialize/build/main/src/deserialize_encrypted_data_keys.d.ts.map": "6f104aa23f7a6f576837fcb14140c8d6db083952a54a84d7a3459f28005e514c", + "node_modules/@aws-crypto/serialize/build/main/src/deserialize_encrypted_data_keys.js": "3826dcfb38be09bde1c6e5390f973f9bd9e71a2fe1441d4755455aea17d1a728", + "node_modules/@aws-crypto/serialize/build/main/src/deserialize_factory.d.ts": "96ab7afa545f5278deba0361cbf452ee0a44d338e574f95d171118736ee68b8e", + "node_modules/@aws-crypto/serialize/build/main/src/deserialize_factory.d.ts.map": "b37ce7c9ed26eec74d3ce67989ea855920d75170aeaf0decdc69973797392ff7", + "node_modules/@aws-crypto/serialize/build/main/src/deserialize_factory.js": "6c7774ef09ae704d32f76b4de0cb9dc425eb4f4adf448e1b732b76fe1518a877", + "node_modules/@aws-crypto/serialize/build/main/src/deserialize_header_v1.d.ts": "24250472fc8a7289890e37cd8e4bc0193402ce568b04d29b6c997d130e9e99b7", + "node_modules/@aws-crypto/serialize/build/main/src/deserialize_header_v1.d.ts.map": "1bcf81dce9fb922f9d7dd2df137503907c062e1eb4d1aeab6f3038e968cb4738", + "node_modules/@aws-crypto/serialize/build/main/src/deserialize_header_v1.js": "e2c133431d745ea093cc04065d2e86080317717ffbde42c33453dbf9ab094832", + "node_modules/@aws-crypto/serialize/build/main/src/deserialize_header_v2.d.ts": "0e62b5d0515f3aa905c587d2e3acc6d8e8d2f5d4f7a231aa0596e36a84994a88", + "node_modules/@aws-crypto/serialize/build/main/src/deserialize_header_v2.d.ts.map": "063cbde77804801f3bf0cbef8e3c7a24defae166bbea6c4faae3492b079542b1", + "node_modules/@aws-crypto/serialize/build/main/src/deserialize_header_v2.js": "2f787a1621e14ebb804e0f1a9abc4e49043098dd2bf0e88c0ca4378138406e3b", + "node_modules/@aws-crypto/serialize/build/main/src/ecdsa_signature.d.ts": "017f5f5c026e50aeaaa9450ae45ac335b6321d753d703bc4b5b158eddd13013a", + "node_modules/@aws-crypto/serialize/build/main/src/ecdsa_signature.d.ts.map": "5825f57b212487869e58cbac82c506aeb5ad21fc9fdd0d07a36ac1277e1b913e", + "node_modules/@aws-crypto/serialize/build/main/src/ecdsa_signature.js": "62c3adf9e21e9dff9c008d503207dd5fb4db691d42703e506132ee725554ee37", + "node_modules/@aws-crypto/serialize/build/main/src/identifiers.d.ts": "839cd27f4deaad329c732d81d31a06c992ef6436e2039b12b87a4b2741a50d51", + "node_modules/@aws-crypto/serialize/build/main/src/identifiers.d.ts.map": "93c21e7721fd7b9205ea455160d4d392eb859aebc5100fa6b0f6b3b3227bf622", + "node_modules/@aws-crypto/serialize/build/main/src/identifiers.js": "1c4b6992a64b94e0cdaf7875ec31ec7945e68f8aa6e7286e3d9bc21f65d2366b", + "node_modules/@aws-crypto/serialize/build/main/src/index.d.ts": "406376276b0e96592ce5ff4214ea8cc8cb61abddb9a54bcecb5fdf80f6868012", + "node_modules/@aws-crypto/serialize/build/main/src/index.d.ts.map": "d9f8b3965de1f269421e2ec97755216f644e6ed906a4de54d66badbb73cd0427", + "node_modules/@aws-crypto/serialize/build/main/src/index.js": "cd993286a3e6d3a76caa0a0c9fbf1c89b57b3b90948019c33dbac84446c82981", + "node_modules/@aws-crypto/serialize/build/main/src/kdf_info.d.ts": "53e6e847838e0077c9e2a58c48306c9b48d8fab569deaae859d096b4e6f99df4", + "node_modules/@aws-crypto/serialize/build/main/src/kdf_info.d.ts.map": "1844dd99702b4d824a51d0743045d970a1b81dd9cb19f85c30e88af116c4c05f", + "node_modules/@aws-crypto/serialize/build/main/src/kdf_info.js": "3c0f0bb36083cff323c33e2363326eb2e053549f41d1a6e88914a7a3624c47ba", + "node_modules/@aws-crypto/serialize/build/main/src/read_element.d.ts": "4dc5e334031a599bb8c573048788545678ba32a80536fd742f0ad904e925ea64", + "node_modules/@aws-crypto/serialize/build/main/src/read_element.d.ts.map": "6d2f5ac5cfc364a879d2daa4d8352cf23b3aa6ef8bb62388a3839346c6d2e84a", + "node_modules/@aws-crypto/serialize/build/main/src/read_element.js": "d51a382441a6f8c0f1409014d9ae8284a3415bb9d2d3e4f3b3f91f5f27dab054", + "node_modules/@aws-crypto/serialize/build/main/src/serialize_factory.d.ts": "89c7b4626aba66b9df4a72da2d5ecf23cb46010165c45d4d52b9e5c5a16aae8f", + "node_modules/@aws-crypto/serialize/build/main/src/serialize_factory.d.ts.map": "cf15a185fac2d28ef6199e679f8891dbc464c07a131429c96ff03c4ef7fb5135", + "node_modules/@aws-crypto/serialize/build/main/src/serialize_factory.js": "c92dfa9e43e71471f3a96db01e1ae215092ac9b11b132d9c69f93d06facaf2e3", + "node_modules/@aws-crypto/serialize/build/main/src/signature_info.d.ts": "a59440c76e10ffa0dd14da9f123d73cdc0fd5ed2fba254cacf407e5a8825ee20", + "node_modules/@aws-crypto/serialize/build/main/src/signature_info.d.ts.map": "879742f3a4acec910ebba010fb074de5f1463e0306a06278d098adeb133cd95a", + "node_modules/@aws-crypto/serialize/build/main/src/signature_info.js": "c6bbd345cf33f95a8661300dd04a0dc0fbf7622eb4ab17300e1f6f77e54ad795", + "node_modules/@aws-crypto/serialize/build/main/src/types.d.ts": "012cb677052bb3cc700dec5c98f9a490c36dd3afefc954600d1a0144ee28ab08", + "node_modules/@aws-crypto/serialize/build/main/src/types.d.ts.map": "e685b7a042faf9a38d8a6f6170cb7d2603c0699ff05db771c77fe4c4832613eb", + "node_modules/@aws-crypto/serialize/build/main/src/types.js": "ffac8d9b74b561b5cda5614f1e4b688cdb69f496e1d42af24060569a80bb04c4", + "node_modules/@aws-crypto/serialize/build/main/src/uint_util.d.ts": "96e04ccb07bdbe309e2e62b33eacb44942507ad58aab085f076a9df3681c410a", + "node_modules/@aws-crypto/serialize/build/main/src/uint_util.d.ts.map": "8a0eeb37ace05f8b39cd5898616915d7ac6c01a2a425378715facc9ee05afd99", + "node_modules/@aws-crypto/serialize/build/main/src/uint_util.js": "8defabbe648d526a505ff7c97c2af6dca4d956cece6fdf8a3a66c983b92bf95e", + "node_modules/@aws-crypto/serialize/build/main/src/uuidv4_factory.d.ts": "31d04f6a3663b4604a273cf1ce89e6bc846548c8d16f9731a34fc2bd06cfb0af", + "node_modules/@aws-crypto/serialize/build/main/src/uuidv4_factory.d.ts.map": "4bfe021a986dd4d259683356277ae3013ee5f47cd82bc821a5b932f44eafc7f5", + "node_modules/@aws-crypto/serialize/build/main/src/uuidv4_factory.js": "8da77014b567281a0a18f9307dba140b224716b8c1686ba0891d1c9e2e6bcc77", + "node_modules/@aws-crypto/serialize/build/module/src/aad_factory.d.ts": "63e137913d66133ee095a564082d78f75690be4c5e457165474b02dc9aa294ae", + "node_modules/@aws-crypto/serialize/build/module/src/aad_factory.d.ts.map": "60824776160d82efa753ab9715f6a33dd93f5f7e889854e835c03d5b7075d4fa", + "node_modules/@aws-crypto/serialize/build/module/src/aad_factory.js": "6b12ac77e357bddcf524b2abde6879dfaf7944506da6a0a2224359a4cb1d640e", + "node_modules/@aws-crypto/serialize/build/module/src/concat_buffers.d.ts": "fd6a2f699dff39b332be67424fc17a81368cae7f9b407337411832face9ea612", + "node_modules/@aws-crypto/serialize/build/module/src/concat_buffers.d.ts.map": "4eaf4e47f6f58ffc11a168c5724dd1b1e24add46e0c006d163764ada82594602", + "node_modules/@aws-crypto/serialize/build/module/src/concat_buffers.js": "8098131244194f3bf0769994172d17db152689cf1edc9d272531db8e74977b45", + "node_modules/@aws-crypto/serialize/build/module/src/decode_body_header.d.ts": "edd9d10c3746d44e6d5ec134182fb7ac9c97ee50237e02482d340763c520746e", + "node_modules/@aws-crypto/serialize/build/module/src/decode_body_header.d.ts.map": "993d0dbbaa9cf1f919c7bd7800f208d9ab0dc77ee56eec1df36568de2e48ea38", + "node_modules/@aws-crypto/serialize/build/module/src/decode_body_header.js": "ddd200a7741463eaf3a27d62fca6d014f9d8636fcf5c31a38d48a79781019e27", + "node_modules/@aws-crypto/serialize/build/module/src/decode_encryption_context.d.ts": "6005e73605fb20d8067db90d4c13553c0d4cd582eda8152693ffedf19ce043e2", + "node_modules/@aws-crypto/serialize/build/module/src/decode_encryption_context.d.ts.map": "5dabe5f47837f03f6b46780e91d07d751e7e6910f0c78d9902c28c99f3220b30", + "node_modules/@aws-crypto/serialize/build/module/src/decode_encryption_context.js": "a0442d83eb9f883af5b9a1b70b72a3a1c4446d4cf813e41bd477332461367c9b", + "node_modules/@aws-crypto/serialize/build/module/src/deserialize_encrypted_data_keys.d.ts": "5a81b748cc5581ba041677a48ec558f5e25833076452e0246ddeb0f45006acbd", + "node_modules/@aws-crypto/serialize/build/module/src/deserialize_encrypted_data_keys.d.ts.map": "6f104aa23f7a6f576837fcb14140c8d6db083952a54a84d7a3459f28005e514c", + "node_modules/@aws-crypto/serialize/build/module/src/deserialize_encrypted_data_keys.js": "801cbe6b5a4ad92aaf3e16a052273e03c440c6bc67fc08cedf801c95c32bad6b", + "node_modules/@aws-crypto/serialize/build/module/src/deserialize_factory.d.ts": "96ab7afa545f5278deba0361cbf452ee0a44d338e574f95d171118736ee68b8e", + "node_modules/@aws-crypto/serialize/build/module/src/deserialize_factory.d.ts.map": "b37ce7c9ed26eec74d3ce67989ea855920d75170aeaf0decdc69973797392ff7", + "node_modules/@aws-crypto/serialize/build/module/src/deserialize_factory.js": "3ec747f9941e852ab6329aa21df5ab98b0fec4ab75a3f2802b1f41b71d8d9510", + "node_modules/@aws-crypto/serialize/build/module/src/deserialize_header_v1.d.ts": "24250472fc8a7289890e37cd8e4bc0193402ce568b04d29b6c997d130e9e99b7", + "node_modules/@aws-crypto/serialize/build/module/src/deserialize_header_v1.d.ts.map": "1bcf81dce9fb922f9d7dd2df137503907c062e1eb4d1aeab6f3038e968cb4738", + "node_modules/@aws-crypto/serialize/build/module/src/deserialize_header_v1.js": "f53af6b95acdf38eb4ea078733f56d06becc68647dd51b7c03b307eac890f33c", + "node_modules/@aws-crypto/serialize/build/module/src/deserialize_header_v2.d.ts": "0e62b5d0515f3aa905c587d2e3acc6d8e8d2f5d4f7a231aa0596e36a84994a88", + "node_modules/@aws-crypto/serialize/build/module/src/deserialize_header_v2.d.ts.map": "063cbde77804801f3bf0cbef8e3c7a24defae166bbea6c4faae3492b079542b1", + "node_modules/@aws-crypto/serialize/build/module/src/deserialize_header_v2.js": "4c605f546efd4d2e3ff83de56b5a6459c3a8380c4c96c03e6852fa38d844e3da", + "node_modules/@aws-crypto/serialize/build/module/src/ecdsa_signature.d.ts": "017f5f5c026e50aeaaa9450ae45ac335b6321d753d703bc4b5b158eddd13013a", + "node_modules/@aws-crypto/serialize/build/module/src/ecdsa_signature.d.ts.map": "5825f57b212487869e58cbac82c506aeb5ad21fc9fdd0d07a36ac1277e1b913e", + "node_modules/@aws-crypto/serialize/build/module/src/ecdsa_signature.js": "7acdf61d55a51e99bfa8b784da9f572355a47dda3c630110abdd924d6f0ccefd", + "node_modules/@aws-crypto/serialize/build/module/src/identifiers.d.ts": "839cd27f4deaad329c732d81d31a06c992ef6436e2039b12b87a4b2741a50d51", + "node_modules/@aws-crypto/serialize/build/module/src/identifiers.d.ts.map": "93c21e7721fd7b9205ea455160d4d392eb859aebc5100fa6b0f6b3b3227bf622", + "node_modules/@aws-crypto/serialize/build/module/src/identifiers.js": "800271d7a8a2d15971ce57150241dc0b5e34458849bedc2e1cc72a4808b8504b", + "node_modules/@aws-crypto/serialize/build/module/src/index.d.ts": "406376276b0e96592ce5ff4214ea8cc8cb61abddb9a54bcecb5fdf80f6868012", + "node_modules/@aws-crypto/serialize/build/module/src/index.d.ts.map": "d9f8b3965de1f269421e2ec97755216f644e6ed906a4de54d66badbb73cd0427", + "node_modules/@aws-crypto/serialize/build/module/src/index.js": "ffc6f9ce07f5442024c4ad31314950741115030779f10c3eb07200082ff1c763", + "node_modules/@aws-crypto/serialize/build/module/src/kdf_info.d.ts": "53e6e847838e0077c9e2a58c48306c9b48d8fab569deaae859d096b4e6f99df4", + "node_modules/@aws-crypto/serialize/build/module/src/kdf_info.d.ts.map": "1844dd99702b4d824a51d0743045d970a1b81dd9cb19f85c30e88af116c4c05f", + "node_modules/@aws-crypto/serialize/build/module/src/kdf_info.js": "8f067b33f7eec9507e1f7bffd66887132c6a39a9e4a283eea230c84640fa394e", + "node_modules/@aws-crypto/serialize/build/module/src/read_element.d.ts": "4dc5e334031a599bb8c573048788545678ba32a80536fd742f0ad904e925ea64", + "node_modules/@aws-crypto/serialize/build/module/src/read_element.d.ts.map": "6d2f5ac5cfc364a879d2daa4d8352cf23b3aa6ef8bb62388a3839346c6d2e84a", + "node_modules/@aws-crypto/serialize/build/module/src/read_element.js": "569497e4d23aec44480cdecaa65b206c8b44ee411e6cf359175887fe757f8122", + "node_modules/@aws-crypto/serialize/build/module/src/serialize_factory.d.ts": "89c7b4626aba66b9df4a72da2d5ecf23cb46010165c45d4d52b9e5c5a16aae8f", + "node_modules/@aws-crypto/serialize/build/module/src/serialize_factory.d.ts.map": "cf15a185fac2d28ef6199e679f8891dbc464c07a131429c96ff03c4ef7fb5135", + "node_modules/@aws-crypto/serialize/build/module/src/serialize_factory.js": "9a236ab419219a0fe8e6f3e32b49d0cfdbc1e0767b53af1c2dfa8390211643dc", + "node_modules/@aws-crypto/serialize/build/module/src/signature_info.d.ts": "a59440c76e10ffa0dd14da9f123d73cdc0fd5ed2fba254cacf407e5a8825ee20", + "node_modules/@aws-crypto/serialize/build/module/src/signature_info.d.ts.map": "879742f3a4acec910ebba010fb074de5f1463e0306a06278d098adeb133cd95a", + "node_modules/@aws-crypto/serialize/build/module/src/signature_info.js": "30785b53c8d68d0b6aee82be5fc6a1ffffd83acedc573621a8dea91627aaba17", + "node_modules/@aws-crypto/serialize/build/module/src/types.d.ts": "012cb677052bb3cc700dec5c98f9a490c36dd3afefc954600d1a0144ee28ab08", + "node_modules/@aws-crypto/serialize/build/module/src/types.d.ts.map": "e685b7a042faf9a38d8a6f6170cb7d2603c0699ff05db771c77fe4c4832613eb", + "node_modules/@aws-crypto/serialize/build/module/src/types.js": "0168a2d65fd821d8d322a8f9008350c7cb4a6b71463480e639e85e279974ec7a", + "node_modules/@aws-crypto/serialize/build/module/src/uint_util.d.ts": "96e04ccb07bdbe309e2e62b33eacb44942507ad58aab085f076a9df3681c410a", + "node_modules/@aws-crypto/serialize/build/module/src/uint_util.d.ts.map": "8a0eeb37ace05f8b39cd5898616915d7ac6c01a2a425378715facc9ee05afd99", + "node_modules/@aws-crypto/serialize/build/module/src/uint_util.js": "3abb2cf186d3aa57212cc743309c0deeb405304a13ff9551cec5a81c82566f52", + "node_modules/@aws-crypto/serialize/build/module/src/uuidv4_factory.d.ts": "31d04f6a3663b4604a273cf1ce89e6bc846548c8d16f9731a34fc2bd06cfb0af", + "node_modules/@aws-crypto/serialize/build/module/src/uuidv4_factory.d.ts.map": "4bfe021a986dd4d259683356277ae3013ee5f47cd82bc821a5b932f44eafc7f5", + "node_modules/@aws-crypto/serialize/build/module/src/uuidv4_factory.js": "e3f8e2c9d1603b29618c4d74b2a8a6c759f4c384d5343f3585294c817a8099a7", + "node_modules/@aws-crypto/serialize/package.json": "9f945fb9eebb7ec577a8cf06d38bd9d1637d0b028cc2d2d73c79da9692661653", + "node_modules/@aws-sdk/client-dynamodb/LICENSE": "e345c2ee7df4446e739de7b91535f0c10ddcd909b9753889e943ee637b05db8c", + "node_modules/@aws-sdk/client-dynamodb/README.md": "be7bb1506d67bbde5e2f38c955c2cc0bab4b8ddd5ccdedcb42afdb8058178b89", + "node_modules/@aws-sdk/client-dynamodb/dist-cjs/index.js": "3cd9dce86e67079829cd79ad1d4ecf8a76fb662a2b8e4ed56b557c04bd13c87c", + "node_modules/@aws-sdk/client-dynamodb/dist-es/DynamoDB.js": "6c2b2731a33feadf25fa704a42c0533080d637e7d1eaca254a51357d2ebad5ac", + "node_modules/@aws-sdk/client-dynamodb/dist-es/DynamoDBClient.js": "a334c1efaa553fa118f6ad97557c58fbc207c9bddcdb60656ccc4400edfe7931", + "node_modules/@aws-sdk/client-dynamodb/dist-es/auth/httpAuthExtensionConfiguration.js": "331902cc5df5060ac9644261de8711ff641d6b91e60b5fcf77d2facc441c8cf6", + "node_modules/@aws-sdk/client-dynamodb/dist-es/auth/httpAuthSchemeProvider.js": "2db7a2039677034e1d80065f70bd95535c5ea5e0c992502e7a6073a5e5110ff2", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commandBuilder.js": "16b0e3ee1f14ebdfca8afea7b1ebb45aa85d10ddec31e47a4bda72158414eb4a", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/BatchExecuteStatementCommand.js": "5d9316ef83e690dfc2aa42884214b4220df284cb8d1aab07caf3edb8ec16012a", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/BatchGetItemCommand.js": "bd6a356caddf0c2b3596226f36901f4ea38b85dfd29369f6ea4a755bfe0a13ce", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/BatchWriteItemCommand.js": "0d11fb3252046f8774cfb6a61b2f9b9e1acf1e20f3a8d9e7c50c848a15aa31be", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/CreateBackupCommand.js": "80ba643755e0266d52e3c5ae3f7f1dd02aca31977d3eefb6e146b7ef1644539d", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/CreateGlobalTableCommand.js": "c1d39cc4d461b10c30d794d84656bdc0b639a0c5588800c37dfbac93c3ce3253", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/CreateTableCommand.js": "b7dd6f89400ffe1c960ded44d48a6f21b50fdfb650c9188b0d84d66bf6dc3291", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DeleteBackupCommand.js": "4d59bad698730d89427e783fce3f703eb69b9e6bfa01dca28ccae01a9c152aad", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DeleteItemCommand.js": "26493be5535895573d57893a3bc87e11715873d870b479131172fdae099ea0a6", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DeleteResourcePolicyCommand.js": "ba3e4699b69fa4cba2a57cd2cf7721a73c3f127f41c383cbce44999d42747c5b", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DeleteTableCommand.js": "0b06aeea5de2e5c0b1dbec072a033a09c5557669e3a04f91a9d845aa70e4f954", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeBackupCommand.js": "6986fd92c706a61c7bb010fd6c4f3319a48d2706ba043342e1c73736b7d08136", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeContinuousBackupsCommand.js": "1f61780967abaa9a5cb5328f5d5f4cd9b24c3233a3765a4c0512b2a932658ec4", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeContributorInsightsCommand.js": "1a13134fa3c5e742f68847fd019ce43f6aeb304168fb126810d2f4fcdfada0e3", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeEndpointsCommand.js": "ba6287263ba6ee50641d4be5b8d175cdf997de5eff3bd33cc4959c6415b9c3cb", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeExportCommand.js": "fb0d98e938c7c5c4f470c5c8f15f0b016cbce2a8c4a78c147b0f9b2e9a0fbf4a", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeGlobalTableCommand.js": "41f5803ece70a15ec61e16535135c179a7b1fab99e8b1285709eb6f734bed117", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeGlobalTableSettingsCommand.js": "e1a713c360566ee4d5a4dad82df84f4263ec311ab6f6a4c759a5a9f56345cea9", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeImportCommand.js": "122c218ef1b52ea630494c45cd23e7edf86790f1c473c870b426cfd04d470a80", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeKinesisStreamingDestinationCommand.js": "ae7d1dd09e751675f48b682cf33350b8b127553c77ca5556310b333fa23f82f0", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeLimitsCommand.js": "6d26ef94f0baf27b25a4a032b04dc2c32bc02362602c5438c3629bbdff544d37", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeTableCommand.js": "88ce879dc4482caa1c1ea1c6453a43a824f0b2737bb19b51468c68f0df78e6af", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeTableReplicaAutoScalingCommand.js": "ebb076d7799e5b67560ffca2f50a4a63ca400b1eb1bdf27c293eeb438fa4c7df", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DescribeTimeToLiveCommand.js": "e02ef60fbdddd0264a9c358f35f2fad8ddc654df584ef9e3da737b81088fac67", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/DisableKinesisStreamingDestinationCommand.js": "42a094d7dce98f7dc9bb90f00856e1293a6c574510323477931dfd8bc3c48b23", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/EnableKinesisStreamingDestinationCommand.js": "979b1866c40e0d9b2ce95eaeb1e1c504d45c7bdf50cbd24ae9e1fea67ffb8a39", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/ExecuteStatementCommand.js": "9ced0c9b1cade4d7ed0b7419ada85951d09b31400b7a0942fbae50e04ee7508f", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/ExecuteTransactionCommand.js": "2eb80a107842b74b32f41037e537923fbd460071b14c81400119496c409e3b68", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/ExportTableToPointInTimeCommand.js": "3707e5634e5195618d9e329f4905ed3a6c0140ac4bc0051d0c3e57340a573095", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/GetItemCommand.js": "784a257fe9dd7689dc590973610840173030d031921becb281a21bac202772f2", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/GetResourcePolicyCommand.js": "982af0807e074a81d12ff019281d1a232ecb8a070573b65a6d6a4c9b71b60cc3", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/ImportTableCommand.js": "868b9a5399633a7aae22ada8eab0466d829f54644ab54d93303c45b3ccfcaf60", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/ListBackupsCommand.js": "8eb0c235318cd872e51a6c5fca6fb0acb8746991934cd7bcd40685e198fa49a9", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/ListContributorInsightsCommand.js": "67e8aa212f2ff8823e59817720e02a036cc92f9f70f4f2719ae291dd9c131f9c", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/ListExportsCommand.js": "d1513fdc09cc6b2135986ff34d4f176b488f3a6416e48a993f5db4c35e63a11c", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/ListGlobalTablesCommand.js": "1bc3f5cb66c0d6410a4360946e5c43d056fbdfbf999341468adf31f0bebb5f0d", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/ListImportsCommand.js": "34e421415dba13687e0c98ab95bb045d40cdad42b2066d7645fd582b9d43fea7", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/ListTablesCommand.js": "351aa799210a127696d8a04e6f6fefb68fa0174586978699c78f3aa59fce3a23", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/ListTagsOfResourceCommand.js": "34078c4c1578a86e1a1e489649b68ba13f1780569655fb80fff8cf64172c0812", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/PutItemCommand.js": "012300dde00f404dbb5334574e5e6d02a9267d734696b315262e5ce27e04827c", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/PutResourcePolicyCommand.js": "5359509c766ab95f23ffd8479e80e00741c9c184ac398a6c8b9ef3c5b91f34f7", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/QueryCommand.js": "3c51fbc13b9ba667b31e02331b554cf37c97304736239ee401f26447ec2a0cf5", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/RestoreTableFromBackupCommand.js": "738aee032752f59c34e48da45503b763f769c40953601d193c40bccacf46c587", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/RestoreTableToPointInTimeCommand.js": "48e1438eaea32ff6e748230724c567ba992a4bfa6c86edd5fe8bd299fb585474", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/ScanCommand.js": "bd295042da0c76954cfd8dd503a93bc85a765ae4bac708195c01993ded73763c", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/SearchVectorsCommand.js": "1c4d0b5c46ed0949eb67bf2f2661e11c8f7557988382701491b131211da53577", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/TagResourceCommand.js": "27b9f8d8872f03577d1ab32d40a086c2aaefaf466755bcdbc86441ffe504eb3e", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/TransactGetItemsCommand.js": "51e663f8aa03b2c5ce8b0004bb0619d0b530187bd7e77f13ea4a4a664e0a409a", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/TransactWriteItemsCommand.js": "df0796948498ff3eaec149ee6f7ea49765841b38fe7ac3ce7f0e20c3dd3ef8ae", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/UntagResourceCommand.js": "f45ea9eb36350b30637467ae25af46fd351699ca122792e9a3a325c2fe6de522", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/UpdateContinuousBackupsCommand.js": "ab1c30429f54d0b84d5b03e486258d6d7f42aa33e3d05363bc993dbcda818312", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/UpdateContributorInsightsCommand.js": "e07c9f1a9397b3f54b64942d94d2b9a9066dc31681e378353df4382fac07a57f", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/UpdateGlobalTableCommand.js": "8f7a7a3b8f8b17d947f73735644bed5931f42ecd168fc92c8a1ec3ffab9a28f0", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/UpdateGlobalTableSettingsCommand.js": "711d0a94c0024d7793cd751cb4e7fdcb8dd49036205ba7905b85921c840dc033", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/UpdateItemCommand.js": "23a34f7f7f4c2eb49130c579e478a3315a7c18cbc825b0b393a94e56a9a7545c", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/UpdateKinesisStreamingDestinationCommand.js": "89175e0cea013e9954c1dea029e40f03a955e2aeffbb46e1cc305b6a06fd377d", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/UpdateTableCommand.js": "c17e9e867905aadfa6c8742acdccc916e41f7505a4f21e8ff0119378616d1aac", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/UpdateTableReplicaAutoScalingCommand.js": "9e5785d752ee4cca254aec67cd7c0334bae2c2f0fb23a01f47529e19c45e4acd", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/UpdateTimeToLiveCommand.js": "7c49e90e83c0d843d4b36176f3ccd564650fb0987489638196842eeac7691550", + "node_modules/@aws-sdk/client-dynamodb/dist-es/commands/index.js": "de39b36c3025fd94953885dc52bcddc2c3380c7221a4d1fef7e8341357abae79", + "node_modules/@aws-sdk/client-dynamodb/dist-es/endpoint/EndpointParameters.js": "deb726c0032f4e2a8c04f7a07fd2ff25073e4e8241fa480698e4be3a4036505d", + "node_modules/@aws-sdk/client-dynamodb/dist-es/endpoint/bdd.js": "f71181281e2dca3bdab27e1f011adb8746b4d4dcead5310ae78543365aa9ec4a", + "node_modules/@aws-sdk/client-dynamodb/dist-es/endpoint/endpointResolver.js": "e701630d92948e77704224bdd6bd9e6bc5e15752597fef399d93b9c8c206007d", + "node_modules/@aws-sdk/client-dynamodb/dist-es/extensionConfiguration.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/client-dynamodb/dist-es/index.js": "feb5bd841d00e0a3c622b351700f4ecf2853eff25e6e7cee2d17ced8f4424341", + "node_modules/@aws-sdk/client-dynamodb/dist-es/models/DynamoDBServiceException.js": "a0878ee0995328f1d63b86167def3b7c7a03d08035166d53e927288fe58f34ba", + "node_modules/@aws-sdk/client-dynamodb/dist-es/models/enums.js": "be2e3266dc9b19173419aaac3511d221479f5b87952f69af2befa0bd0aa11b41", + "node_modules/@aws-sdk/client-dynamodb/dist-es/models/errors.js": "4ec23449a319ff23060b90635cb8f382127d3f624ddc7e743c0f284d575f58dc", + "node_modules/@aws-sdk/client-dynamodb/dist-es/models/models_0.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/client-dynamodb/dist-es/pagination/Interfaces.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/client-dynamodb/dist-es/pagination/ListContributorInsightsPaginator.js": "8aa1b7a8b498f2d135d12958981f0cd3a2dff273181f1fcbdc39b17d50f73a74", + "node_modules/@aws-sdk/client-dynamodb/dist-es/pagination/ListExportsPaginator.js": "683aef6ae97e120fd5fef2c2a082e0aabde899616fa4081d23bc8b3c5b417638", + "node_modules/@aws-sdk/client-dynamodb/dist-es/pagination/ListImportsPaginator.js": "7eb00b9a7c626db9c17778d445b53f2b4db3bd954673aeb261d423a5a67e734e", + "node_modules/@aws-sdk/client-dynamodb/dist-es/pagination/ListTablesPaginator.js": "7c372a3532cca34fa28da559a98ee61ffde5d7712e52f5dc94197999954ba1e4", + "node_modules/@aws-sdk/client-dynamodb/dist-es/pagination/QueryPaginator.js": "b93976981c8b9945b137f8e627107bd8d7ab547d4b759b1d48e0349bacf6bc1d", + "node_modules/@aws-sdk/client-dynamodb/dist-es/pagination/ScanPaginator.js": "47a81ef1db8ac5b4dd7b8e16e4b970b0da603166207237c843a1d34c376abbbb", + "node_modules/@aws-sdk/client-dynamodb/dist-es/pagination/index.js": "756bde5eb8bc63e94fc9f1e552b3e5972fd4b146cf4ed677e56196a3d0174b6d", + "node_modules/@aws-sdk/client-dynamodb/dist-es/runtimeConfig.browser.js": "cb6c484fffb0454dbcc5fb7fd5930a9050a38682e7b952b6174a5971687cc8b7", + "node_modules/@aws-sdk/client-dynamodb/dist-es/runtimeConfig.js": "2892a319a5ae6fa2078e6c14a4ebd85058c9547222e702e4a740dcda17bf621f", + "node_modules/@aws-sdk/client-dynamodb/dist-es/runtimeConfig.native.js": "c0c73f0322ae95adce92f60f138a0e54cc9d686a4ef04c27a0fae1902f47a969", + "node_modules/@aws-sdk/client-dynamodb/dist-es/runtimeConfig.shared.js": "903c6eec4b4bf171fe7855e6fc81c4382289bcd3764e0500d968657cee9263e5", + "node_modules/@aws-sdk/client-dynamodb/dist-es/runtimeExtensions.js": "91acbc1e819f62b25b16ae8d717eab8eb748fedc63a656ffcf6ae353cd0b5dbc", + "node_modules/@aws-sdk/client-dynamodb/dist-es/schemas/schemas_0.js": "7f5e573102c3d0b1fc0aa21f3f32d5a8071edd0947296e7a0ec1cb6d6b1989ca", + "node_modules/@aws-sdk/client-dynamodb/dist-es/waiters/index.js": "fa139a82123d7caebebd4192601ff85ec0678b7b84dd1f0aa3970a6ec594237d", + "node_modules/@aws-sdk/client-dynamodb/dist-es/waiters/waitForContributorInsightsEnabled.js": "5fdc95afe7a0270eec3da95176a301d9fafbafb1e73e8e2a84535f5dac3db323", + "node_modules/@aws-sdk/client-dynamodb/dist-es/waiters/waitForExportCompleted.js": "5cdf73ddb51d9b195de66496d3218a66fc505f4bee931a16a99faaaec5d82043", + "node_modules/@aws-sdk/client-dynamodb/dist-es/waiters/waitForImportCompleted.js": "7a57ccce2c8b99ff4b4cf33055926c4eea40c7d541809f943066553284db621f", + "node_modules/@aws-sdk/client-dynamodb/dist-es/waiters/waitForKinesisStreamingDestinationActive.js": "d877fe8f7af84fb1a99f879d81c73a4beb305a6ae1f6b4eac0042ffcefde6744", + "node_modules/@aws-sdk/client-dynamodb/dist-es/waiters/waitForTableExists.js": "4151ee95098ae64b0f527783c6423ed16ad02a08e5a2131bdb27a36328e02cdf", + "node_modules/@aws-sdk/client-dynamodb/dist-es/waiters/waitForTableNotExists.js": "cd9e542e040da84cd2962115bf04419cb9d4ea025aa72b43abcc0533bbc42b0c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/DynamoDB.d.ts": "28e25b17ab2af015e5681b4c37dc2ab89b30e20f259e3d283a234a633d1c8b4f", + "node_modules/@aws-sdk/client-dynamodb/dist-types/DynamoDBClient.d.ts": "ddb960baef7ce17b36dc7449b245fd500aa7e3df0bb06d5e7528d421b2ea085c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/auth/httpAuthExtensionConfiguration.d.ts": "65558b00891630645a3386fcd17eacd06c53a5649e6f53f5a5cfff490d4390d2", + "node_modules/@aws-sdk/client-dynamodb/dist-types/auth/httpAuthSchemeProvider.d.ts": "c8705d16c77562353c8865195842d9b9e74bdaa524f1d9374d9f8d2be167763d", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commandBuilder.d.ts": "e560fff4c94141829a59a32ddee6597e558c0f4d92b7660b5c67da50f18da5db", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/BatchExecuteStatementCommand.d.ts": "f53372507da7913f804a752e505081310de5e8007eea98824295db6cf9651bfa", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/BatchGetItemCommand.d.ts": "d51ab84f3f03210449cf51a93df1036b7f20ac261c0b7632e93a26dd859b8512", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/BatchWriteItemCommand.d.ts": "f1081b0bab0ceab62a90c7a895c3241d4de0ae55584bcf689c4dd0cac946a0bc", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/CreateBackupCommand.d.ts": "5ff589cea11b6da0a4b4a19f68e3046597e5831dcb067b8822eba8d2e09e06d4", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/CreateGlobalTableCommand.d.ts": "3d2dc5ead3b9d8fd1edd52a56a26f393ee5a19b7903576727766bb20f8763953", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/CreateTableCommand.d.ts": "f1464280756a79c68ac4fdad37d26d0dd3a877eb058c1ce4b0dacdad9e29bc0a", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DeleteBackupCommand.d.ts": "f2b0ef9e545e45d75f9b99a71c7b3e51066bf39d1028d3879c1b8a1855f45f5e", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DeleteItemCommand.d.ts": "8a9fdb0c3cb07b44ab3a5748ce3a59ae6452d06e5631d8cbe126434c0aade703", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DeleteResourcePolicyCommand.d.ts": "81688626f397c77b4e4eadc83a29b8a237ad6751d57111be2f83fc968a1f7a30", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DeleteTableCommand.d.ts": "2334eb69474f929300ab98033b870f161baf2d9f49f51577c4b78c4ccba6b599", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeBackupCommand.d.ts": "810038252416b29f0ba947d3ff21397cf25a2ded62a56b5dd67286bd8a00efd8", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeContinuousBackupsCommand.d.ts": "fee6bffaf87d800e7d947da23821e2ca2c94f6188fe6e112a7d37c4ee3b8f49f", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeContributorInsightsCommand.d.ts": "5ffa31b7ffa9d9381aaa6f98047515f67117c085e067ab2e3554af7aac266112", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeEndpointsCommand.d.ts": "6d8517d780beb3f55ef066e0cec98373a39b68840e4ce8f76a8c73652b7bba03", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeExportCommand.d.ts": "d39995f5ee726f7edc01637b55f1c5fb9e2faa23ed3e464e28b749e102a9c367", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeGlobalTableCommand.d.ts": "4f4d02ae78ede9d8bac8d35f4c92bcceeda99c088e6994d6b65de7aed0fa9292", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeGlobalTableSettingsCommand.d.ts": "b41c13fe348a716df2a531aab8326f8e657dd2a0fe85622f31aa22548eae8b33", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeImportCommand.d.ts": "99953026a2ed0aec715f32532935d80fae4f429b9fab99eeaaf1c6cd70b52cc6", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeKinesisStreamingDestinationCommand.d.ts": "e7bb36360dfbfca5984484e38efa79f1ea1ceb352e188b2183a3592185d87851", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeLimitsCommand.d.ts": "c7ec6a8318130b8a3c88cb21ac630744105a2086e24c4cf8e4e80aa314d911f0", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeTableCommand.d.ts": "c823871dca1059f030d505ef34ad96bf7a34155acc0bb33939417d62a1c99a8a", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeTableReplicaAutoScalingCommand.d.ts": "c5ab24213736db23d41721fad5e4794ed1e818871336b35d6cdd14beeeba7125", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DescribeTimeToLiveCommand.d.ts": "52c90067ac4c5be5fa6182c0199ec0de9a40ced67ae0c5817a5870405bdae79e", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/DisableKinesisStreamingDestinationCommand.d.ts": "a24954e2365167700c570c2fc4ffb06c01325b8a38c32d91a810896157212d62", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/EnableKinesisStreamingDestinationCommand.d.ts": "5a619ce7a35ed56bd59b29320fa830cadf1a49399ff9a36cb21cdc2b2a972921", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/ExecuteStatementCommand.d.ts": "13795a5f62dbcafd5def1457ececc42337857fc3cd88011385aeba2080947b9d", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/ExecuteTransactionCommand.d.ts": "b2e6bd3cc95b25acc2110a26f0e6a4368c5d92052deab27a41d2cef059401aa2", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/ExportTableToPointInTimeCommand.d.ts": "27a8d90efc56abdb0dce74e92c73068101c93e3f496a44cd540bdb49e499a598", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/GetItemCommand.d.ts": "460f7697f246160abdceea30b619a007874dc72184e94b723f6af2ef272e113e", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/GetResourcePolicyCommand.d.ts": "266a9008971b60beb92558325efb48f94116d8e78bcaf16cb8214716e840ce82", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/ImportTableCommand.d.ts": "a98787caca258fb3d33cb3dc94231a2b4c1ba3577aa64e007451ca28a470141c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/ListBackupsCommand.d.ts": "fd6fe3fa9c12945b1f120f8a91e1767bb8eb94998447ba310b53a6d2f54dba5b", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/ListContributorInsightsCommand.d.ts": "c6a54511b6c25015d96eedf0127b06e6cb5b5ef53b2aab516a9aeff77f126aad", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/ListExportsCommand.d.ts": "5ab8abe74aaef25a5f6ed12469e65ad77f714d95d75e5173ef4a33aa8164e17f", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/ListGlobalTablesCommand.d.ts": "a1a0b7024eac2fe27c2983f3e7632202f46c8154601013e920b927ff7d796fb5", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/ListImportsCommand.d.ts": "090380c7d2e93b92a98509955c95b135d824a8bd4c64c73bd3c7da0a8b324621", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/ListTablesCommand.d.ts": "71ff877daca4d51c54f7267950725ac474d2a0097c2793ffd4ffcfa7793c378c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/ListTagsOfResourceCommand.d.ts": "cc077f2dcff2d91b7733196db41538fbcbc9967ad3d92fb1d625ea9cd0ac0fd2", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/PutItemCommand.d.ts": "d43dcf8495e0d826f307c3efad93c3bfd294a4e25e0ab1c663fbf72c73536f62", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/PutResourcePolicyCommand.d.ts": "529f7bf978d720dd488a900dd2b2a3eb5508cd529c01ec58f917a7e587c4b433", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/QueryCommand.d.ts": "7db9d9e23389c436d058775b7fc7dae1a47e2daec38ada84dd25c60333c8333c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/RestoreTableFromBackupCommand.d.ts": "8f5ecc32df120b1a37972c0f9914a7f477e7365fb07bd54392db8ab58224621f", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/RestoreTableToPointInTimeCommand.d.ts": "79b46d4020f16bdb20343b93307ff4e226794efd7047e8aa3ff6648f35b78789", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/ScanCommand.d.ts": "58877274fa84f3e5475a2712b0c28eb0a71d3e987309b8124c5abfec099873fe", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/SearchVectorsCommand.d.ts": "a2f3b17dd8a7e04ec5c0ef3891302df099ff431a783bd5d1221e540927f8b9d1", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/TagResourceCommand.d.ts": "34165f15de28d7a44ad9788576b5a79717304407b89d4898a80ad230d2b48487", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/TransactGetItemsCommand.d.ts": "9f3bf0f9345bafb0dd04fd146bbba6c67433827821fd4c783bf8451048719045", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/TransactWriteItemsCommand.d.ts": "15b4b7d6d96e9df8027c711678a92344bf59c54af2dfa5143d97444205b28148", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/UntagResourceCommand.d.ts": "bf04bbc0501efec83884da5817fd996ec404967d06f4ab7b3a0ff4b8058052fd", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/UpdateContinuousBackupsCommand.d.ts": "22e8054791d5bc57929eaf3ce4630c8f0d15db85be4ac0576021503a241f18aa", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/UpdateContributorInsightsCommand.d.ts": "498f1b76c7cdf65f182894cc2d4f4c0e630e716095565e83819098785e1d07bd", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/UpdateGlobalTableCommand.d.ts": "d7f655a7f95e522fe98b06bd7c8ad7211904cd040ffcf3b63a5759c3b9d86453", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/UpdateGlobalTableSettingsCommand.d.ts": "73263eaf1bedfaf38ed2d12ec9e3ad2d4cd79df8dccc6b29498599624797df81", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/UpdateItemCommand.d.ts": "ffb64d585c00abf53c5977c3c12a6be59d98d1b5320a00ea5e27a1b753656208", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/UpdateKinesisStreamingDestinationCommand.d.ts": "c8ebf8b389f3853b3df19a18f6edd898edcefabe308e7a32143a2f02d95e81e3", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/UpdateTableCommand.d.ts": "7683f5d1585b6be426ed231fb58cba9882f9cd2d6ce2769a2a685b8916d57956", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/UpdateTableReplicaAutoScalingCommand.d.ts": "1c49815f42f3d0ce9fd615e0e0a331457d083b519dbfa96f9630173626834f41", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/UpdateTimeToLiveCommand.d.ts": "b8f01e85906bd1f371f8f5c5a16bb4e59b5206ff79b1338af2f9aee5a3ab2928", + "node_modules/@aws-sdk/client-dynamodb/dist-types/commands/index.d.ts": "de39b36c3025fd94953885dc52bcddc2c3380c7221a4d1fef7e8341357abae79", + "node_modules/@aws-sdk/client-dynamodb/dist-types/endpoint/EndpointParameters.d.ts": "848280326451307fa2d2a398e07aea7d0760ff3b38d0bb5fcebeba292fa21c32", + "node_modules/@aws-sdk/client-dynamodb/dist-types/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/client-dynamodb/dist-types/endpoint/endpointResolver.d.ts": "b82b0ed889b5688a674912c9a0efc4667863d3c9ccf9240673c5a30d9fa8116c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/extensionConfiguration.d.ts": "45512c77904424be840a8b85db5ae7a5513a2f7e004c24818bfdec4bd0450289", + "node_modules/@aws-sdk/client-dynamodb/dist-types/index.d.ts": "9a2d85fde23d33ef453eb62f9717790d932539ad6a329e4b2bafac6f887eec3d", + "node_modules/@aws-sdk/client-dynamodb/dist-types/models/DynamoDBServiceException.d.ts": "63879d90e11a68643f0f3ea1ca2b9f45ae00ee3ac2f3f9f62f22a0aab5035e96", + "node_modules/@aws-sdk/client-dynamodb/dist-types/models/enums.d.ts": "ca197755a63443b8a58e442e4d9d62367bc9e6fd3e9194abb258b231585cbe75", + "node_modules/@aws-sdk/client-dynamodb/dist-types/models/errors.d.ts": "e2d2d6fb5a65ba4a6750a4c75b52250532f7019d7982541536870b0f1b51fca5", + "node_modules/@aws-sdk/client-dynamodb/dist-types/models/models_0.d.ts": "67f044304a3f6d7382265d31259f41d1d702e5c78225944bc2964fa9792f6fa0", + "node_modules/@aws-sdk/client-dynamodb/dist-types/pagination/Interfaces.d.ts": "2e6ad3ae50fcff6708ebc52379e2fdeaafacd807988e5966cda12cc80109a6d7", + "node_modules/@aws-sdk/client-dynamodb/dist-types/pagination/ListContributorInsightsPaginator.d.ts": "f35c3263b36c19b040fb3a6a844bee4fc377cb47258378f161cd2b4cce433bf5", + "node_modules/@aws-sdk/client-dynamodb/dist-types/pagination/ListExportsPaginator.d.ts": "cd354a3a5a12100c215ded853734bf14c34020afec7516c509ec4383c11e420e", + "node_modules/@aws-sdk/client-dynamodb/dist-types/pagination/ListImportsPaginator.d.ts": "4a1aa92963f8773116065f35af75ad906817d780fdbaab79f74c477cd065ccb6", + "node_modules/@aws-sdk/client-dynamodb/dist-types/pagination/ListTablesPaginator.d.ts": "abd2b1d3bd1cff2743102d43448f96794e6c0f52385718fecbdf226dad7174df", + "node_modules/@aws-sdk/client-dynamodb/dist-types/pagination/QueryPaginator.d.ts": "3f3996b0ff3f6c1f1bf625cc28eeea557a2d4135d169d95cfd1ec57f732ae995", + "node_modules/@aws-sdk/client-dynamodb/dist-types/pagination/ScanPaginator.d.ts": "24022228c302f73936ad1c732f8f0aa8413e0852d498b49f2127b5ef8a4f3ba0", + "node_modules/@aws-sdk/client-dynamodb/dist-types/pagination/index.d.ts": "756bde5eb8bc63e94fc9f1e552b3e5972fd4b146cf4ed677e56196a3d0174b6d", + "node_modules/@aws-sdk/client-dynamodb/dist-types/runtimeConfig.browser.d.ts": "63dd2f0bd65417c49f41ff21089cbc024a0ac68b076bc4398d51f10d4c996026", + "node_modules/@aws-sdk/client-dynamodb/dist-types/runtimeConfig.d.ts": "6e9049832df7ffc6dbecb0aa4d6e6f6b937b2e704f9faada7a7e8cedc289e30c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/runtimeConfig.native.d.ts": "0a59ef899f72470aa693dd7289abfce6eb94f44b85cda3fdad3ff96a339db757", + "node_modules/@aws-sdk/client-dynamodb/dist-types/runtimeConfig.shared.d.ts": "c6a028592e6e7859f99d34908dc0482e7485128f20806d51a3e1020acbbe6175", + "node_modules/@aws-sdk/client-dynamodb/dist-types/runtimeExtensions.d.ts": "15ed4556cff311627dc38714252224523ba9eed7c56650ff5a34985c5212122f", + "node_modules/@aws-sdk/client-dynamodb/dist-types/schemas/schemas_0.d.ts": "b683f7e29ec84775e4073ed807b0a2fc6d3506ec1b5043afe34b6c2eb0e179f8", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/DynamoDB.d.ts": "1a8e2293ba8ba675a2e888a38043a7007e96e313e92124ce8f5f42e74e7f5c7c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/DynamoDBClient.d.ts": "7c933830d5116e1064bbd4a6b54b8a331a0573bbe3b344488d400f54def8ab75", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/auth/httpAuthExtensionConfiguration.d.ts": "f682a21f86e85b9fd42b7eafdc93eb62fc84ce7954be92ec701981616b9d264d", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/auth/httpAuthSchemeProvider.d.ts": "13bf0fcea171fc2f23b32706b2ed71b5a6cdb44cccff3310426ff71caf0907c7", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commandBuilder.d.ts": "83d02c41ae4a3ff2ae3b18eaf037bcfbeb1e23083da652a665046502b6d0b83d", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/BatchExecuteStatementCommand.d.ts": "3a8301d1e5289dd649d2aef22bda44b9b15187d4644cd1b0c0f3756c143bae52", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/BatchGetItemCommand.d.ts": "d1513b2cc56e77cf3d462626e1bb68604413408876298cf4acc71cc6487149a5", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/BatchWriteItemCommand.d.ts": "0987f57b966fb9ba4c91ca48e5e6d0492bfe48a4d2dc59308603caf5d966006d", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/CreateBackupCommand.d.ts": "cc15ffd405beb30aa95ff1ce64ae4969c897f788544e98f07be7a8ffcbb6ba3a", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/CreateGlobalTableCommand.d.ts": "4c03bf74920a58fef5bd394033a27f9392398b69fddb4a7602fa006b8d51d992", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/CreateTableCommand.d.ts": "96f546f1d6a7ad678c54b64116ef55655c32ee2825c0e135b5ea0c8bd060444b", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DeleteBackupCommand.d.ts": "2f3290c0bc757ceefc2e6947c88864dc90920c1c9b9981c13d0c46922d6658c3", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DeleteItemCommand.d.ts": "1de1a5543cba2390589512fdc6155156433f1e80c329226e8f11e0b5dcd6b001", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DeleteResourcePolicyCommand.d.ts": "a2fc3621383b9704820db05b2b0d40f1fe7d93cd1da7b622d222fc759b95daf8", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DeleteTableCommand.d.ts": "32065bbe595e35429141611ea493f1eb3037fa72076d91018a0379d3282f99b0", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeBackupCommand.d.ts": "679327c76aed789f450808da76bee1c8f94a126929b9bc64973f7529b7b39454", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeContinuousBackupsCommand.d.ts": "ed2a101467926569356fb486a9a34eb42faaf73755a4c196cb916344c0ddb987", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeContributorInsightsCommand.d.ts": "d2da3f8f300ff419dc9b06ea3491867f5326ee4db9dbd1e2e2d426cae627d5c6", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeEndpointsCommand.d.ts": "c5b9334bc28dec246feb7236634967ff4e6b2caf56bf36e0408c5951e68542ab", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeExportCommand.d.ts": "ae392b9d4ed751702d1b1d40094ddc698e1ee37791d4ad49b123d012a92e91ff", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeGlobalTableCommand.d.ts": "f20dceedabf4a28439e309b9fb6dba7589b60f286d89f9dd5b7596c62f25397c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeGlobalTableSettingsCommand.d.ts": "f4987ab0d5b66451b8b3badfd4ceb0d0ad161d60328cd6fe8bcec3e5c1b4e445", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeImportCommand.d.ts": "84a2b58302e09adb6bd0abcf5ae9e9685c46dcddc98f9543e0a74f6646d8276b", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeKinesisStreamingDestinationCommand.d.ts": "0e676bc74870f107e89971794da2d616070c215c1d48cf531e71a406b216ce20", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeLimitsCommand.d.ts": "3a9718d048f020b9d98f094b04fa6154d1aa1726bca641a59b779c14d202b69e", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeTableCommand.d.ts": "f02deb031f28736c2ae292a7750ac9ecd72db8a6f4a479de547c0123a702d33f", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeTableReplicaAutoScalingCommand.d.ts": "8071ee4f8db870d490783b79d170ad4b4ad72776c53e8752d290df74651c98f6", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DescribeTimeToLiveCommand.d.ts": "b7d14a276d6ae0ec7735031b692d98e15f73ebe20e9d113f856e7bfa48caf79d", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/DisableKinesisStreamingDestinationCommand.d.ts": "67502f79d89fd5ef6494344f85877385bfd5c181b64c3b068fc2d4bf4d95744c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/EnableKinesisStreamingDestinationCommand.d.ts": "37800f6bd6b38b026968f55c2322e6b739de72d5b097843c83bf4964df43b3d0", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/ExecuteStatementCommand.d.ts": "ddd5445a930795664b1528fd3d33f4352379fa59a9a4cdf31290c4a85f8d1c4c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/ExecuteTransactionCommand.d.ts": "ad2fe32a4a238fa5f6f7908029b6a8ed22d33498713a16a1cac70f693018edd8", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/ExportTableToPointInTimeCommand.d.ts": "2755ab995dec32902a747d4a3b57bf9ab1db7866eb3cd917fe643eb0b6a0ee72", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/GetItemCommand.d.ts": "a1f5429c6ddf0a5273ce8c3471b2e6d6a3e5541affffe56be3a9fc3f83b2141b", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/GetResourcePolicyCommand.d.ts": "dfcc36bd4ed15452178cf9d34355d3665d7b6f8bdf439c154a211299733d72da", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/ImportTableCommand.d.ts": "5c8cd47c6e7dddcb50964e844d3697fcb8427cd6481948913ce977b77b97cc1f", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/ListBackupsCommand.d.ts": "ed7a7502c60319d4a1a3e4e55121b43203586efdc53ca02a24397bef497e0827", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/ListContributorInsightsCommand.d.ts": "e9434219bb8c16eb4e1dac271bb4c823486cdf090dc5728f87b8640946448e84", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/ListExportsCommand.d.ts": "bd7beae0dd7343f807788b24c4dbf3f78ae2626b6329a2fa0ca36a3b666ef543", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/ListGlobalTablesCommand.d.ts": "30d76f0c1e0765285513c63f83de10a1f740d8943328f60030d1641bec7afdd1", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/ListImportsCommand.d.ts": "25004b9ec5b61accdf5ad414ba5ca4ed25c78e1fa4ddee0ae13a108b9d1e56c2", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/ListTablesCommand.d.ts": "b4265a4a3a2e29bbe66803440cf549cb3274cb81512e12d69b1f3492c39974d5", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/ListTagsOfResourceCommand.d.ts": "fd584d3a53c93105747b14fa2ea3a94e827be8e7bd3920bd866b0dca5dfe5723", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/PutItemCommand.d.ts": "ae25f8d919528180a90585384dbfb69aae207cc7a5173d23576fe41ec2345db4", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/PutResourcePolicyCommand.d.ts": "83a180c623fbba797ef7bec3a5dc8904d4f53bcccd5afdb1645de98708252587", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/QueryCommand.d.ts": "d3a996a3754e212dd36965965c23380d9ed033afe518f45c6db874fbb571cc69", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/RestoreTableFromBackupCommand.d.ts": "3604a0c470aece71df99a8c156659e3a0a103c5ad91c20ebeec6aba9af43b5c8", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/RestoreTableToPointInTimeCommand.d.ts": "16c327787d4c38e076ee5cd774e9dcfe13bf575f504ee2d636f9f483eaa41678", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/ScanCommand.d.ts": "09effa9982591d2d05e7e199751c2bed750d2e011051abdb9278f37ac58c5331", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/SearchVectorsCommand.d.ts": "b3de881032eb3c58e40a1972ee5d457fc2fc018f319ad7c5fac3e091918f3760", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/TagResourceCommand.d.ts": "0c2dd89169714c36256f54a753ef82c4d77a0e5c1fad3009f9298490e9da7e87", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/TransactGetItemsCommand.d.ts": "22c77eb6d9a778e3d23872de317beb39b5cc50d0f8dfe27c410d0f6fbf920b06", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/TransactWriteItemsCommand.d.ts": "abac49ae156432f05ed8d46341d2aa6da40bec2f69387b3579356d57be23005b", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/UntagResourceCommand.d.ts": "960e65b79d4802f61dd58017776842b482e4ab5d31046179a72ff68a3104fca4", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/UpdateContinuousBackupsCommand.d.ts": "c8fc4ced8d044b3a9922ab4ea9454799aab6488d1045d21406855a30b7c128c7", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/UpdateContributorInsightsCommand.d.ts": "064b232bc1ee1c42a4fa0fd028d92cef4aca1674d0f2c782a6ce03554e014b0d", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/UpdateGlobalTableCommand.d.ts": "15700037337027ab8994253810b2128542edac3ac5e087a8e3c50169f849fd56", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/UpdateGlobalTableSettingsCommand.d.ts": "4fc5e02002e3264de669337586e24707dc28fc99c289157f35c6c6b68c2b28f9", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/UpdateItemCommand.d.ts": "c56304d6d7b2881bdcf5debba60bf30b903a4935db0dfc19c87c93bd36625c80", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/UpdateKinesisStreamingDestinationCommand.d.ts": "e47ff4cca8ad7f0074c7ba84ec102c6b03955e70463237faf645bf51b726ba46", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/UpdateTableCommand.d.ts": "fdddc7168413ae748070a9033438ee8c59d770959484f2ff4c6a02ca67cfaa6c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/UpdateTableReplicaAutoScalingCommand.d.ts": "2545e7500803e4b43c950a0f1a8e9569095c935b72f269f1dd97b4014661ee91", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/UpdateTimeToLiveCommand.d.ts": "bc3eb9724c527acdc2a1cdc7eef7175514ac96a7e3863226b35ce0484839ad29", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/commands/index.d.ts": "de39b36c3025fd94953885dc52bcddc2c3380c7221a4d1fef7e8341357abae79", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/endpoint/EndpointParameters.d.ts": "bd995e552287231b37b131e0244a5eea4a1e160db81a3539c87d5590cd265f47", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/endpoint/endpointResolver.d.ts": "8db6ac77345e04ed548fd662661a0fd1ebd10197510160beb75c3116fde4d2c5", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/extensionConfiguration.d.ts": "67673090e732f3011c3947feaa474dd7c7de3587a01c23a8cc5a12efcd1a5663", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/index.d.ts": "0805d7f7c1902c8192ca9db0562a7d6c7c838f4be99c5d7e73907b18ae44e012", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/models/DynamoDBServiceException.d.ts": "25a3b8f9ddbb5ded628f023c564d795c9b539b75c9ed3276412f7f976ba13d6a", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/models/enums.d.ts": "1ed8157dae17a1e7d3b41f9ec22ac427582da449445ba7c91922f7398b568f0f", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/models/errors.d.ts": "136814545c9e55663b6ba2238d56e3b87ed3dea2c6b52c3283c40083cdd7a172", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/models/models_0.d.ts": "efa143149d4ceefb9c0182ce469efd28bbe3e8e7d15b392080cde6992411c21f", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/pagination/Interfaces.d.ts": "55b0ee0f9f6335cac1528e956a30da04d567011560993e8805750f7934513705", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/pagination/ListContributorInsightsPaginator.d.ts": "e6cff2adfdc067ccffaf113ca35b574efa64214ae52d2295056c27404b85185e", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/pagination/ListExportsPaginator.d.ts": "d4bdb7325bbb9327c19a3e639a0f0a10aafd5ede1e9d0b2f46d4321ba20f664f", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/pagination/ListImportsPaginator.d.ts": "b6484b8b3fa3e40b77b1efe452ae27165f1555b995407cf86f7cd8fb4fc131a2", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/pagination/ListTablesPaginator.d.ts": "2074024e53da03e97aa244880de49bd5e2bfc936a8ad182720deb8dfab922e56", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/pagination/QueryPaginator.d.ts": "6406c39ee006b71d760602e67a2f05eedf212f021ad6ffa18673deb975ed3733", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/pagination/ScanPaginator.d.ts": "b461c92a4fa2d981b1d24093a5caf1991ed455b4a4ddb2519217c1d4930b4dc8", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/pagination/index.d.ts": "756bde5eb8bc63e94fc9f1e552b3e5972fd4b146cf4ed677e56196a3d0174b6d", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/runtimeConfig.browser.d.ts": "a3c7b9f96a61059da9ce7b401bd112239c8dc03626f2ee0dacb74ef06654d60e", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/runtimeConfig.d.ts": "c84935efce3d688213277e46ed11bd1091be05e358d830e4628e7673a747e6d8", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/runtimeConfig.native.d.ts": "5f503786c70530c5208b365e0d5b5ad5b942b3c219ef53fe9d0a6876fb3116d6", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/runtimeConfig.shared.d.ts": "35522aeb08a98fc8021f46ee0927aec3146d362db641ee7a6908ff2d7e55aff5", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/runtimeExtensions.d.ts": "ed15234c7d9c7d55e7a60e1b3a4fb1df4a3db725660d0e9c38d93da96215180e", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/schemas/schemas_0.d.ts": "9d4f4668abd734019eb19b82caaa7b988fef35a508915ee915bf55e858774880", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/waiters/index.d.ts": "fa139a82123d7caebebd4192601ff85ec0678b7b84dd1f0aa3970a6ec594237d", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/waiters/waitForContributorInsightsEnabled.d.ts": "23b8cfbbf43eff96d613831958e73b48e8912cb872f74ce30096c0c120f47a82", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/waiters/waitForExportCompleted.d.ts": "a1a57b14dd885c8387785a8fbf3f0b36c93fd69aa42a96b9539ab7b0b52adafc", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/waiters/waitForImportCompleted.d.ts": "4f50e3ff1620215d6950d86ebf7434f5e3f1869d0cee9006c630e4e8feee960c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/waiters/waitForKinesisStreamingDestinationActive.d.ts": "adb3998f7d5254b8c64f652ab54edac27e598ea22bafc12107f3902b20f4fa54", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/waiters/waitForTableExists.d.ts": "93385dce5d5e7ded90352b42da174136e339e5ec0c55e336baf4e1281d77bdb4", + "node_modules/@aws-sdk/client-dynamodb/dist-types/ts3.4/waiters/waitForTableNotExists.d.ts": "e0491fa33deabbd98ad74f739bd406861e1f54e77a89941df922538341f51d3a", + "node_modules/@aws-sdk/client-dynamodb/dist-types/waiters/index.d.ts": "fa139a82123d7caebebd4192601ff85ec0678b7b84dd1f0aa3970a6ec594237d", + "node_modules/@aws-sdk/client-dynamodb/dist-types/waiters/waitForContributorInsightsEnabled.d.ts": "3a23bc7ae84077fa523c73d1db6196fbe76c4ed0fb4150a538785aa456e5d68c", + "node_modules/@aws-sdk/client-dynamodb/dist-types/waiters/waitForExportCompleted.d.ts": "5711467accb2c80e388243cdba3b99712748ab339cb0e0c14ef4856fb828c57e", + "node_modules/@aws-sdk/client-dynamodb/dist-types/waiters/waitForImportCompleted.d.ts": "262bb4a041c9076a3c66538cfa1165d41d880e75782c374bfd93d5d5b681130e", + "node_modules/@aws-sdk/client-dynamodb/dist-types/waiters/waitForKinesisStreamingDestinationActive.d.ts": "83bc42f2b6f24a43d238cc664784d83c0153c71555f5d738940f72a6a19ec29e", + "node_modules/@aws-sdk/client-dynamodb/dist-types/waiters/waitForTableExists.d.ts": "511e707384680b0f558b922d676aeb4129d252bb383db805820355b771e133e3", + "node_modules/@aws-sdk/client-dynamodb/dist-types/waiters/waitForTableNotExists.d.ts": "8f492c73d8e691c81183c9d1d10820e9bce9b368fd7a6bde13bba0fe723a7a95", + "node_modules/@aws-sdk/client-dynamodb/package.json": "54d14e17bce6b00e688e17ba36583eb9140d01c0757ce6c80779e180be6b1d5e", + "node_modules/@aws-sdk/client-kms/LICENSE": "e345c2ee7df4446e739de7b91535f0c10ddcd909b9753889e943ee637b05db8c", + "node_modules/@aws-sdk/client-kms/README.md": "0bfbf5aa8d1a780895496a5e56b7dd77169131f9c675979aa8cde13a693855f0", + "node_modules/@aws-sdk/client-kms/dist-cjs/index.js": "cfbe42e651a2edbbd7965dbcfa68d795938247b1809702f002224dda87385f7e", + "node_modules/@aws-sdk/client-kms/dist-es/KMS.js": "64a7bc4e385018fd146459116e27423376c695ad176c8cc0021e642b7c2f860e", + "node_modules/@aws-sdk/client-kms/dist-es/KMSClient.js": "935c7667811ffa275d14b106f2f701ee0a77796c5a95d5c771de7a1e6f4e398b", + "node_modules/@aws-sdk/client-kms/dist-es/auth/httpAuthExtensionConfiguration.js": "331902cc5df5060ac9644261de8711ff641d6b91e60b5fcf77d2facc441c8cf6", + "node_modules/@aws-sdk/client-kms/dist-es/auth/httpAuthSchemeProvider.js": "ecffdfe55177c7094684e7de8f440cc122bc780fd809d71fe9882e30ed3da6d0", + "node_modules/@aws-sdk/client-kms/dist-es/commandBuilder.js": "8dbe6f102126467863532d794d1233d54910cfa478311925a0ad13d22127e2e8", + "node_modules/@aws-sdk/client-kms/dist-es/commands/CancelKeyDeletionCommand.js": "38805d343cdd2d26ae4e6481622b3a95422db23a03d8babdadc357a86bcd24b1", + "node_modules/@aws-sdk/client-kms/dist-es/commands/ConnectCustomKeyStoreCommand.js": "ff440de3d7d45de6a4aba7785076c16adaadbd4e24adf1de476f2077825b7d60", + "node_modules/@aws-sdk/client-kms/dist-es/commands/CreateAliasCommand.js": "f9290d9f997bed1a18ae9640f2ec831ff1b0a470c363336e3afef84107dcb03b", + "node_modules/@aws-sdk/client-kms/dist-es/commands/CreateCustomKeyStoreCommand.js": "2c3206c01b08150203b95e93f6427113c9058682dc990b0fd587d7aa928efd5e", + "node_modules/@aws-sdk/client-kms/dist-es/commands/CreateGrantCommand.js": "b31467b64e467ead3b12a41063104636dc0fd9cf141db499df2f049f07e8a07a", + "node_modules/@aws-sdk/client-kms/dist-es/commands/CreateKeyCommand.js": "2ed36ec893e02363b2d6fdaeb8d3b57cff54d3789f27181c5e0668b17a4be6db", + "node_modules/@aws-sdk/client-kms/dist-es/commands/DecryptCommand.js": "8fb5b68e84a76112532f74f4e853c015ee8c87bd2c6129ef2812ebbb576783af", + "node_modules/@aws-sdk/client-kms/dist-es/commands/DeleteAliasCommand.js": "960ed9117a8b458ff0a1a5124c70c9a6a8e8cabc6c4dec92f5698704983dccff", + "node_modules/@aws-sdk/client-kms/dist-es/commands/DeleteCustomKeyStoreCommand.js": "6950ff12e1241a07062791783ef2833f0839c29f5d3e8a5104e8b9acef8ba4e0", + "node_modules/@aws-sdk/client-kms/dist-es/commands/DeleteImportedKeyMaterialCommand.js": "d65c101625902b83a66634df5eea9c1f6dc867e0b03cba48a585a920d704ab14", + "node_modules/@aws-sdk/client-kms/dist-es/commands/DeriveSharedSecretCommand.js": "4ebad180abdde3d4512148d60ff8e9b8b7821693e82e9e8a3c3058cc38dfe995", + "node_modules/@aws-sdk/client-kms/dist-es/commands/DescribeCustomKeyStoresCommand.js": "abd27db94f873f2b6448c82dd23a932cd6a039284c3dec5452cb1b1618758673", + "node_modules/@aws-sdk/client-kms/dist-es/commands/DescribeKeyCommand.js": "613ce7e31d4402bd94552716b7b1d770536b1a8c2203d66fa08b4a4d1c2190ac", + "node_modules/@aws-sdk/client-kms/dist-es/commands/DisableKeyCommand.js": "06b4692176d1d84370e5a7bbfd2370a9ad110936d20d972b2e0760ab29c227ec", + "node_modules/@aws-sdk/client-kms/dist-es/commands/DisableKeyRotationCommand.js": "d9f52bf7aed5555a0934c152f7fcb8ff06df7dd43a62feecdeac131a32d8bd79", + "node_modules/@aws-sdk/client-kms/dist-es/commands/DisconnectCustomKeyStoreCommand.js": "a0bc4bbb337e8e96af5ef2b8ce226a072af67f395640e288f6ea71dc66c2b83a", + "node_modules/@aws-sdk/client-kms/dist-es/commands/EnableKeyCommand.js": "9667dc328edca6be85a9f5960357d182b7488119507aa761c5b58fa94d1cfaee", + "node_modules/@aws-sdk/client-kms/dist-es/commands/EnableKeyRotationCommand.js": "f6ba3284e0c31d49c349d884da18fc35b0bb96e72652ca02bf861abf1bdfb991", + "node_modules/@aws-sdk/client-kms/dist-es/commands/EncryptCommand.js": "d95452a9fcf0e3578014c2cc75b5839c0c8b93432ec9307abe181a11faf896a5", + "node_modules/@aws-sdk/client-kms/dist-es/commands/GenerateDataKeyCommand.js": "2d3ed46620d416a3fa0f35b1f5a9f0bba3eccf2a543f65de7ed25f03dfb64d67", + "node_modules/@aws-sdk/client-kms/dist-es/commands/GenerateDataKeyPairCommand.js": "2859a1a0ff7f9a52bf5d72caf6ec5373cbda47aa44672e56ce8e70d2f7ae4c3c", + "node_modules/@aws-sdk/client-kms/dist-es/commands/GenerateDataKeyPairWithoutPlaintextCommand.js": "2f9443278841abe43c17fa38cf2af320a593193bf5fb31da315b630128b0e99a", + "node_modules/@aws-sdk/client-kms/dist-es/commands/GenerateDataKeyWithoutPlaintextCommand.js": "c1db2337f2307460424423a57c19645867603924c923cbe78264b68970466253", + "node_modules/@aws-sdk/client-kms/dist-es/commands/GenerateMacCommand.js": "17445811d42ac006d9d93a91417d615debbda6663103039a191a8b66bc42cb0a", + "node_modules/@aws-sdk/client-kms/dist-es/commands/GenerateRandomCommand.js": "69aa9a2155064b51588d01f5134d3f0a4d4f9a78b682b1a3b92e116e36f1db8d", + "node_modules/@aws-sdk/client-kms/dist-es/commands/GetKeyLastUsageCommand.js": "ef0edda2c99105829d709be86d1c6254b64676505939a2fb1bea74f6196175d5", + "node_modules/@aws-sdk/client-kms/dist-es/commands/GetKeyPolicyCommand.js": "5e6e56546d3a7173b9ffe34cb71e9d0c7adba007af5c4041610769dab5e1a4ff", + "node_modules/@aws-sdk/client-kms/dist-es/commands/GetKeyRotationStatusCommand.js": "b65b01fe45c206a7139f67ec32068a24a79fb23f044b6722c4ddbdcb4471543a", + "node_modules/@aws-sdk/client-kms/dist-es/commands/GetParametersForImportCommand.js": "6bec261a912ebb8e451a12485d946c71fbf16ae36b5cb83d3607d9d9c83d4c43", + "node_modules/@aws-sdk/client-kms/dist-es/commands/GetPublicKeyCommand.js": "b7b8832d4947391b52b311766f0afd7a0f4c4b7f80e0705e1627819bb92b4934", + "node_modules/@aws-sdk/client-kms/dist-es/commands/ImportKeyMaterialCommand.js": "ed0c6a8e93dbc839a738004034540ac99287848eb88cc55b79a0ff35e933aa3b", + "node_modules/@aws-sdk/client-kms/dist-es/commands/ListAliasesCommand.js": "489e3de567765cc884250d45c8a4c48b908288f322ee6e2a446cdc61bbba24f5", + "node_modules/@aws-sdk/client-kms/dist-es/commands/ListGrantsCommand.js": "c2f97c9e9a2d869c167843614489cc99e9659e4407bc8ba15e1c5e616eec96ff", + "node_modules/@aws-sdk/client-kms/dist-es/commands/ListKeyPoliciesCommand.js": "e76ad2712759d0709772e339fb7c7cf86775405444f0e1611c9cb40d471424e1", + "node_modules/@aws-sdk/client-kms/dist-es/commands/ListKeyRotationsCommand.js": "2f28ca80bd0c95ce823697adaaeef1da900a831aa07d97ee84d6989e92b65490", + "node_modules/@aws-sdk/client-kms/dist-es/commands/ListKeysCommand.js": "8bbca1b5bac4d4b439af4d16921903783a7b9183548dfb4df1357e035638593f", + "node_modules/@aws-sdk/client-kms/dist-es/commands/ListResourceTagsCommand.js": "4d7e122e122d2c6ab236c01747009469bc0c840e16ed6fcbfad762fdc337b9fc", + "node_modules/@aws-sdk/client-kms/dist-es/commands/ListRetirableGrantsCommand.js": "33bf7e09b4492e42702770daba4293627df06cf0e2ac4287b2fc40dee3967ae7", + "node_modules/@aws-sdk/client-kms/dist-es/commands/PutKeyPolicyCommand.js": "4d3b119d976ba1aa192274c0ed3cb0661d1e3cb4c43a1ff535bf21f98af03e55", + "node_modules/@aws-sdk/client-kms/dist-es/commands/ReEncryptCommand.js": "9c9497c3f929d995460313efad8a1095072425f35a082d1a14ecefe254092bc8", + "node_modules/@aws-sdk/client-kms/dist-es/commands/ReplicateKeyCommand.js": "69358e8444ff40714e8b3138cae9e5f94f41fcdd64d0a23ea33251e0de948ea7", + "node_modules/@aws-sdk/client-kms/dist-es/commands/RetireGrantCommand.js": "73539a76863254dc6abdddc77f4f772361d3064ab179ff6e5bb5f3f7a0219a91", + "node_modules/@aws-sdk/client-kms/dist-es/commands/RevokeGrantCommand.js": "ff6db8997f60c49ed832a6989412af633e7fe76284d7b53134f90f8095fd59b5", + "node_modules/@aws-sdk/client-kms/dist-es/commands/RotateKeyOnDemandCommand.js": "3d02e76d119c71f1015aba50c8e80c3dae65e4d067e46471300c124c65c4564a", + "node_modules/@aws-sdk/client-kms/dist-es/commands/ScheduleKeyDeletionCommand.js": "633413b80bb479fdd3cd25e6638bfb3c46cf4b930d8841709ca44164a038c287", + "node_modules/@aws-sdk/client-kms/dist-es/commands/SignCommand.js": "232b8927760227ba58ee9b13900f12748bf7f8403d84ef38929cc0fecad9a1ff", + "node_modules/@aws-sdk/client-kms/dist-es/commands/TagResourceCommand.js": "a4d3fc65fc7020edf2b91121277415e8d5ecc2d7d060126093295b32c6c1f174", + "node_modules/@aws-sdk/client-kms/dist-es/commands/UntagResourceCommand.js": "31c12c703bc1736d63b631f65adf59b50cea33bd1f65fd139f9b36c841073d22", + "node_modules/@aws-sdk/client-kms/dist-es/commands/UpdateAliasCommand.js": "91b7916dffee1f67799c44f0415758b81890670574dc5a92f7fbec30544a4341", + "node_modules/@aws-sdk/client-kms/dist-es/commands/UpdateCustomKeyStoreCommand.js": "7f4c466b277ad795f1d349084bd2b89be1e1eb6666a75afdb0bd261aaf869642", + "node_modules/@aws-sdk/client-kms/dist-es/commands/UpdateKeyDescriptionCommand.js": "9a883d6cf3424eccfa9c6e51fb59c067de65aa11758864fd6b689a2d9547bb57", + "node_modules/@aws-sdk/client-kms/dist-es/commands/UpdatePrimaryRegionCommand.js": "198d066a3df17d07eeabd2aad8f336bb58dc3cb80209c144f145868e0c55ee9c", + "node_modules/@aws-sdk/client-kms/dist-es/commands/VerifyCommand.js": "77d042abce23246626ea639a35cc1e2b8722b8dd7ee3ae623f2db7ee4342eca2", + "node_modules/@aws-sdk/client-kms/dist-es/commands/VerifyMacCommand.js": "1ea94bb2915f5acf4eb43c8e964a6b9651be2737d45941519e865f3bc108a0fa", + "node_modules/@aws-sdk/client-kms/dist-es/commands/index.js": "173c0ae68a645d991afef851a401175bb675379be9fa7441b3a4942b41c48873", + "node_modules/@aws-sdk/client-kms/dist-es/endpoint/EndpointParameters.js": "1ea91cf9c380b9a77dd6082d9e783708fd3565b90177eb46b10d5fb78adeb405", + "node_modules/@aws-sdk/client-kms/dist-es/endpoint/bdd.js": "fd88410c4433156e3885a7bcb0964c8356da2e439087c445181adecd988127aa", + "node_modules/@aws-sdk/client-kms/dist-es/endpoint/endpointResolver.js": "b348b10ade00971a6c9e3de1b9ecedb5388d5f28a411321727f4ad2f84394487", + "node_modules/@aws-sdk/client-kms/dist-es/extensionConfiguration.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/client-kms/dist-es/index.js": "21bae96e61644f524d7cc67da0d3c17708175cfee97ca28836af5f474d33983c", + "node_modules/@aws-sdk/client-kms/dist-es/models/KMSServiceException.js": "fd958d1295d09c785fb8f79ca49bdf4bbd355ca92639939427ea1407e7c1db93", + "node_modules/@aws-sdk/client-kms/dist-es/models/enums.js": "04e5c96ff4654ae3ed8c8e0862612f18ad597ba2a4d2d2d2076e3261e55a3711", + "node_modules/@aws-sdk/client-kms/dist-es/models/errors.js": "8d9558467726fd6c42fb3162fa32ae88e736f7bd3745cd559d56d990a644126e", + "node_modules/@aws-sdk/client-kms/dist-es/models/models_0.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/client-kms/dist-es/pagination/DescribeCustomKeyStoresPaginator.js": "647130a6f3201f9e5849fe851c4ae7df2d66db7e4b08fcd96e709cbf9f20744d", + "node_modules/@aws-sdk/client-kms/dist-es/pagination/Interfaces.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/client-kms/dist-es/pagination/ListAliasesPaginator.js": "8820c8117171691b01c0fbbd72f138d255ed3a53b282807ebed8932164eb02e4", + "node_modules/@aws-sdk/client-kms/dist-es/pagination/ListGrantsPaginator.js": "2b7d11e67b528cc9295df23ad27a8d97d41604f1ef3cb6571674b7a5cac4cfc3", + "node_modules/@aws-sdk/client-kms/dist-es/pagination/ListKeyPoliciesPaginator.js": "cde0339872732ccd04af2cde41108c020aa9cad876d8ea05664f77186d0c8d87", + "node_modules/@aws-sdk/client-kms/dist-es/pagination/ListKeyRotationsPaginator.js": "c4a76027f3732f85e43108b834eaf5a5d39c3f9ca53a39aa937b53da65bcf6c2", + "node_modules/@aws-sdk/client-kms/dist-es/pagination/ListKeysPaginator.js": "734183268e644b0186f1251e7e47321da09e7d66d04acbcbc30108d1f12c0a34", + "node_modules/@aws-sdk/client-kms/dist-es/pagination/ListResourceTagsPaginator.js": "304d18ebe0ff4b51249ac8785a993ee6b8b954b17479172cd2407503d452dd26", + "node_modules/@aws-sdk/client-kms/dist-es/pagination/ListRetirableGrantsPaginator.js": "aeaf1d2978b5c45ba1919d4a8f3cee2a39ce26ea551395cd63f3cf2184779030", + "node_modules/@aws-sdk/client-kms/dist-es/pagination/index.js": "4a347a72f5f2de8132f13ff85c4207022898783be27e4823c8d811840ae4afe8", + "node_modules/@aws-sdk/client-kms/dist-es/runtimeConfig.browser.js": "92319cd5e8fda55a8e19466a6f330326a68d943862987ea3a3b802117f0196cc", + "node_modules/@aws-sdk/client-kms/dist-es/runtimeConfig.js": "3164d3d2e8a6e0d9ea7a9e83520ac700d76f3f5743ab6641cf2622d515b68ce3", + "node_modules/@aws-sdk/client-kms/dist-es/runtimeConfig.native.js": "c0c73f0322ae95adce92f60f138a0e54cc9d686a4ef04c27a0fae1902f47a969", + "node_modules/@aws-sdk/client-kms/dist-es/runtimeConfig.shared.js": "bc62188f227dd6891986c871a0943a615cf79338eb4e8e0b72a16c0757286c43", + "node_modules/@aws-sdk/client-kms/dist-es/runtimeExtensions.js": "91acbc1e819f62b25b16ae8d717eab8eb748fedc63a656ffcf6ae353cd0b5dbc", + "node_modules/@aws-sdk/client-kms/dist-es/schemas/schemas_0.js": "5d8b2ca6fbdc50852591f720f7302983968ab219baa145dc4e15c9ac2c6b30ca", + "node_modules/@aws-sdk/client-kms/dist-types/KMS.d.ts": "b967e123f9cae688f82de683358adbbe1f2df067804caf372ab6ca19af7dc78e", + "node_modules/@aws-sdk/client-kms/dist-types/KMSClient.d.ts": "9de0b39e0e47373f967d18858417246a4ff7192f0219ab52e7f552d2c71597e2", + "node_modules/@aws-sdk/client-kms/dist-types/auth/httpAuthExtensionConfiguration.d.ts": "ace39b5aee44e612eadcdf34d3d1f99ae5ff0a175a3fa8903fabb0953570947e", + "node_modules/@aws-sdk/client-kms/dist-types/auth/httpAuthSchemeProvider.d.ts": "c7074b8eae9a4471112877efb9fccc4ff50d400637b54034f4c697369b8e0c2f", + "node_modules/@aws-sdk/client-kms/dist-types/commandBuilder.d.ts": "e52fd921688268c8827317a2dd6f87bf6b5fff756c1a204839a0d179ad0babdb", + "node_modules/@aws-sdk/client-kms/dist-types/commands/CancelKeyDeletionCommand.d.ts": "2df98790d193c8838b9542c6598b8590031abcd8c86d1805e3ad7a73e3207a49", + "node_modules/@aws-sdk/client-kms/dist-types/commands/ConnectCustomKeyStoreCommand.d.ts": "0de499623c0ece0b2e2dc85b9e0d5016c9fce75580abf797fd315d8597fc73c8", + "node_modules/@aws-sdk/client-kms/dist-types/commands/CreateAliasCommand.d.ts": "133a05345c88a22fa66d114215e4afe2ab4cafe8d25981172d409c3dfb6fd4c8", + "node_modules/@aws-sdk/client-kms/dist-types/commands/CreateCustomKeyStoreCommand.d.ts": "d558baf4cacf306e22a17071c1019f2d6f5b94808052b835ff4002226a2c9491", + "node_modules/@aws-sdk/client-kms/dist-types/commands/CreateGrantCommand.d.ts": "28ca665cef6538e011d338c8719da252d7bf9a21c732a22cf486ee9f41acf87b", + "node_modules/@aws-sdk/client-kms/dist-types/commands/CreateKeyCommand.d.ts": "4e1db12f67973324c008941c26a820a34c53d705220f38db098427350fb8cb4a", + "node_modules/@aws-sdk/client-kms/dist-types/commands/DecryptCommand.d.ts": "a7f27501b5bd1c701f85e889d51d27898d19891a753995600ffaec85f07992f6", + "node_modules/@aws-sdk/client-kms/dist-types/commands/DeleteAliasCommand.d.ts": "2557a7afcc07d8ce80a3074c15c9048d7f115464d0d0edbe6ec8175e51e7f82c", + "node_modules/@aws-sdk/client-kms/dist-types/commands/DeleteCustomKeyStoreCommand.d.ts": "920ac62ab09d7105d14e05014a11f3bf3a9c2e0d4658a49ca79d504674c9ab00", + "node_modules/@aws-sdk/client-kms/dist-types/commands/DeleteImportedKeyMaterialCommand.d.ts": "32c71ab10849713e5736eb4b94459a799a98b34e3ee3061aec53b774ac69925b", + "node_modules/@aws-sdk/client-kms/dist-types/commands/DeriveSharedSecretCommand.d.ts": "40e108e4a70121d90ccaf9ebda37fa591a1c817d60d7e3bb6abffa9ff310f6af", + "node_modules/@aws-sdk/client-kms/dist-types/commands/DescribeCustomKeyStoresCommand.d.ts": "c1ac2ff95f60e4a247e630d4862289d5e492f7b39a93ebe319317bf9f08725e9", + "node_modules/@aws-sdk/client-kms/dist-types/commands/DescribeKeyCommand.d.ts": "911288b0201d522909113f209c6c54508075452c30ab4520772b9d81b1a9b6db", + "node_modules/@aws-sdk/client-kms/dist-types/commands/DisableKeyCommand.d.ts": "01677b11eceac21579de7c53c54cac7b671e01217fd43ae0cbe813f073ddfebe", + "node_modules/@aws-sdk/client-kms/dist-types/commands/DisableKeyRotationCommand.d.ts": "a7897a78f20ade6a96207dbcc3c3abdfecfe64826c79c0a0507a98cbd2267f31", + "node_modules/@aws-sdk/client-kms/dist-types/commands/DisconnectCustomKeyStoreCommand.d.ts": "5eaecaf2379ec446638406fbbd1372649cb6abf2304ec2c5d528d87fc0fe0804", + "node_modules/@aws-sdk/client-kms/dist-types/commands/EnableKeyCommand.d.ts": "a6dd8477965af3aff4fc52833dc5e754bc71463fda11d105d85dda0cb4020d79", + "node_modules/@aws-sdk/client-kms/dist-types/commands/EnableKeyRotationCommand.d.ts": "d7165b907786c770a8cf764b403ac39c330e48fd588aabad672e6dcdf4dbb1af", + "node_modules/@aws-sdk/client-kms/dist-types/commands/EncryptCommand.d.ts": "131e4000c44b92e46e79a9002e253b71d620976d024225181db0c30a3169d271", + "node_modules/@aws-sdk/client-kms/dist-types/commands/GenerateDataKeyCommand.d.ts": "f735c5dc18ab4b5864969b3c9c845e0af30559f6b35e3e69cd93b12de1a99cee", + "node_modules/@aws-sdk/client-kms/dist-types/commands/GenerateDataKeyPairCommand.d.ts": "0dea20f13146bf3bf0f6c8f2642a37f869b18c516fc4ceb9bd7017db62875ec2", + "node_modules/@aws-sdk/client-kms/dist-types/commands/GenerateDataKeyPairWithoutPlaintextCommand.d.ts": "130e118408b2be5aa4dc7efefeb087e626b8a8afc5e136a0f1017605e26eb23a", + "node_modules/@aws-sdk/client-kms/dist-types/commands/GenerateDataKeyWithoutPlaintextCommand.d.ts": "6b3fc1e694f7605470d3e0d51a0a2ba191abffda492ac26edd9ce550028b4140", + "node_modules/@aws-sdk/client-kms/dist-types/commands/GenerateMacCommand.d.ts": "bf684a412fa4f9d23fb3c6b82f4195bafe720af20f44fda9bd48791a2bc90d83", + "node_modules/@aws-sdk/client-kms/dist-types/commands/GenerateRandomCommand.d.ts": "672e9724d40588eafd58584d1bd5e96ae1532b4cdb73cc5d3210688d0e4a3d3d", + "node_modules/@aws-sdk/client-kms/dist-types/commands/GetKeyLastUsageCommand.d.ts": "aaa00c09a01361ce813b5d3de2daf1611b52255e2c1c2ee540272c85d84a1ee3", + "node_modules/@aws-sdk/client-kms/dist-types/commands/GetKeyPolicyCommand.d.ts": "886e251278fd8d7f1cc5d6ae9e6157a98fb9f668b18f6cea81b72361ba565c57", + "node_modules/@aws-sdk/client-kms/dist-types/commands/GetKeyRotationStatusCommand.d.ts": "4007d60c03530d0b7268466861d9c89d3623d09ff5bac6da70ea0397ba364fb2", + "node_modules/@aws-sdk/client-kms/dist-types/commands/GetParametersForImportCommand.d.ts": "7b253c5ccb62ab94222d9f5c5f17861c0468005bcfdff087b772fe234a2df2ff", + "node_modules/@aws-sdk/client-kms/dist-types/commands/GetPublicKeyCommand.d.ts": "d6d316b6403e3147b2e88d820311c0aba0e41c64ee61f1f3ae30ee5090c46222", + "node_modules/@aws-sdk/client-kms/dist-types/commands/ImportKeyMaterialCommand.d.ts": "68828796ad7845540e646c006515aa5094fa388429e5c440e70e5731a98f73bd", + "node_modules/@aws-sdk/client-kms/dist-types/commands/ListAliasesCommand.d.ts": "60428ec46872a59b7a9e8fd41cc5520c0999997f0fcd5f6c9628f285ce71da4d", + "node_modules/@aws-sdk/client-kms/dist-types/commands/ListGrantsCommand.d.ts": "c0ad59e2c52df1c833cb30aeee29aeefbd01c9286e27891efbb418d63e07eaf0", + "node_modules/@aws-sdk/client-kms/dist-types/commands/ListKeyPoliciesCommand.d.ts": "e8b8feccd9b0dff24f92d96a68529ee6c50b734d98432baa9a8c1548aa0abdc3", + "node_modules/@aws-sdk/client-kms/dist-types/commands/ListKeyRotationsCommand.d.ts": "b5bd4e417c3ce2e256027b1af15321799f2d0ac4f77381360353bb50868ffebe", + "node_modules/@aws-sdk/client-kms/dist-types/commands/ListKeysCommand.d.ts": "82016aa432065244444970502a7be7947ff818e58ff83b0b7d2ec0847d3ea026", + "node_modules/@aws-sdk/client-kms/dist-types/commands/ListResourceTagsCommand.d.ts": "c443fbd830346fde7f328be4ea0a84e2f1df2a5ea9a88d3ec1938a05afb76497", + "node_modules/@aws-sdk/client-kms/dist-types/commands/ListRetirableGrantsCommand.d.ts": "6f38ec07aaa32692fdb0a7e0ae94be185c7d00e592c56f039ffcdc958bd71382", + "node_modules/@aws-sdk/client-kms/dist-types/commands/PutKeyPolicyCommand.d.ts": "0ba407a4d0e073f1d5b13894861eeeac1890c31c58bf71a53782389bf5462676", + "node_modules/@aws-sdk/client-kms/dist-types/commands/ReEncryptCommand.d.ts": "e592483cfb5236edb7a47b687ffda4edd5b7187370d188dca2a5411f51373ef8", + "node_modules/@aws-sdk/client-kms/dist-types/commands/ReplicateKeyCommand.d.ts": "f4799af39360e15be094db27491485018e98eeb0123a69106d0ee5e5d2639a6d", + "node_modules/@aws-sdk/client-kms/dist-types/commands/RetireGrantCommand.d.ts": "26082797e884500575e67f59a1be5b0ed755ce129a5af317bd7f06fc8499b59b", + "node_modules/@aws-sdk/client-kms/dist-types/commands/RevokeGrantCommand.d.ts": "11c737879192689229495dfe37ecbbc8e520823028aaad65d5bf13317ec2c452", + "node_modules/@aws-sdk/client-kms/dist-types/commands/RotateKeyOnDemandCommand.d.ts": "01fb8758a15c4bdbe99a309bf438a9b97dacb64a49b7c0d6a1265addb733219a", + "node_modules/@aws-sdk/client-kms/dist-types/commands/ScheduleKeyDeletionCommand.d.ts": "0c21a032d924382767a267a58371b5af3b94c23ddfc76832c57af4357bb92051", + "node_modules/@aws-sdk/client-kms/dist-types/commands/SignCommand.d.ts": "fde327888c9b6a59f028f72ad2ee76ab7cbfc0c4c91e6bba78ded8209b480281", + "node_modules/@aws-sdk/client-kms/dist-types/commands/TagResourceCommand.d.ts": "ebc4254fdf1f3f6b868680edce23457e876acd856869f7f39f60d948f47cafd4", + "node_modules/@aws-sdk/client-kms/dist-types/commands/UntagResourceCommand.d.ts": "b2276c3f3284de922956a29665cadd33fb81b5aa43715fb76e1250e35b3dcf0a", + "node_modules/@aws-sdk/client-kms/dist-types/commands/UpdateAliasCommand.d.ts": "ac3b485852c95007bc0fd2aa31d5624f505cecb96bb8a92b6887482efd125a3f", + "node_modules/@aws-sdk/client-kms/dist-types/commands/UpdateCustomKeyStoreCommand.d.ts": "76ee06ebf3aab80fcc7d8c7cd75345482a8647c761bd25646f0c8494ffe41983", + "node_modules/@aws-sdk/client-kms/dist-types/commands/UpdateKeyDescriptionCommand.d.ts": "347c7a1bad352a9ee185b63b7807188b236276af182eb3ab28954ada1f5a5dad", + "node_modules/@aws-sdk/client-kms/dist-types/commands/UpdatePrimaryRegionCommand.d.ts": "0f4228ac8d2925f52b633c88eaa16bb1fc95ec3fd4019fdbcfb8a639cc192b63", + "node_modules/@aws-sdk/client-kms/dist-types/commands/VerifyCommand.d.ts": "75e50979c9bcae52cb71820e94ec5f5071976a4ca3eb59459e8a6787229ca250", + "node_modules/@aws-sdk/client-kms/dist-types/commands/VerifyMacCommand.d.ts": "3df7e4abe74d033d1e38ff107f267b1a91a15b0a64de18f6714752e73518ef06", + "node_modules/@aws-sdk/client-kms/dist-types/commands/index.d.ts": "173c0ae68a645d991afef851a401175bb675379be9fa7441b3a4942b41c48873", + "node_modules/@aws-sdk/client-kms/dist-types/endpoint/EndpointParameters.d.ts": "db382b318bc333eb7548cc1f0dcd149794d7e1d4969d54f9586f2533b287b5ac", + "node_modules/@aws-sdk/client-kms/dist-types/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/client-kms/dist-types/endpoint/endpointResolver.d.ts": "b82b0ed889b5688a674912c9a0efc4667863d3c9ccf9240673c5a30d9fa8116c", + "node_modules/@aws-sdk/client-kms/dist-types/extensionConfiguration.d.ts": "77940cbeb15504c109f8995cca1b016018e3b45786ef3cef5e92d303ab99b3a7", + "node_modules/@aws-sdk/client-kms/dist-types/index.d.ts": "a44db21ec99fa349f1904b666a51bfb893592c11e8bd29814dd6f717e106e7c9", + "node_modules/@aws-sdk/client-kms/dist-types/models/KMSServiceException.d.ts": "9baff70dda7b028b363b04082c7deae76bf84ff6f70d0aa3b2f78354e3e453b1", + "node_modules/@aws-sdk/client-kms/dist-types/models/enums.d.ts": "d4c4a8ecc23b33b31107b42d5e043ca082071362faf5b7c2f3a84ee7ecafe22b", + "node_modules/@aws-sdk/client-kms/dist-types/models/errors.d.ts": "4b5b98a4821b477df605506a974327753b78086253737fba26553b6ffcaef36a", + "node_modules/@aws-sdk/client-kms/dist-types/models/models_0.d.ts": "3f6550e39da2f2e89b07bd4393dc09e16f42d27e3bedf1a9d7c32d6dc67d8f2b", + "node_modules/@aws-sdk/client-kms/dist-types/pagination/DescribeCustomKeyStoresPaginator.d.ts": "e21f31c0d0e10034621862f5d37868fc9c1d8e05619b16971f7cd3110f9b1be6", + "node_modules/@aws-sdk/client-kms/dist-types/pagination/Interfaces.d.ts": "263b4d2898ad1b86569ca63585374d6c42685ae1048c52ffe964d1f62eb9a6f1", + "node_modules/@aws-sdk/client-kms/dist-types/pagination/ListAliasesPaginator.d.ts": "025ab7b7c805c3e4a5e4bd3f76cb8c092bec146a3240d7004f78a8b25c467e52", + "node_modules/@aws-sdk/client-kms/dist-types/pagination/ListGrantsPaginator.d.ts": "41aa1ef57640c8f90093237fff863a702317ef22e962cf2047460d59d3acadd8", + "node_modules/@aws-sdk/client-kms/dist-types/pagination/ListKeyPoliciesPaginator.d.ts": "c1c8a6cde0253f15cfa13429e43f10cc3944428150a308bb40cdb75c9565f784", + "node_modules/@aws-sdk/client-kms/dist-types/pagination/ListKeyRotationsPaginator.d.ts": "7a1e098bf3c979054761df1cb1e3d09463c734977629576df5ae2be4e4aadb99", + "node_modules/@aws-sdk/client-kms/dist-types/pagination/ListKeysPaginator.d.ts": "ebd61ac5f4d954aeb24e1157027a8dd7252cc2f03f733e864f8e38ac5bfac306", + "node_modules/@aws-sdk/client-kms/dist-types/pagination/ListResourceTagsPaginator.d.ts": "092c372db6eca705f0c4a3a265e8370e1cb5992c96e4bc8a42e8a71d13ac5d8a", + "node_modules/@aws-sdk/client-kms/dist-types/pagination/ListRetirableGrantsPaginator.d.ts": "e69ef73426bca8394b75d76b7bb582666fbf8fafe2f37d0ee8f95cddbcebca68", + "node_modules/@aws-sdk/client-kms/dist-types/pagination/index.d.ts": "4a347a72f5f2de8132f13ff85c4207022898783be27e4823c8d811840ae4afe8", + "node_modules/@aws-sdk/client-kms/dist-types/runtimeConfig.browser.d.ts": "3fd48198d5454fd351062df185df043e5de8e5f313afa733cfce9b537d95d04f", + "node_modules/@aws-sdk/client-kms/dist-types/runtimeConfig.d.ts": "dc04c5239e783ca1a25b437c95826b698030e58e1e209d5ee72ac3436fc96d25", + "node_modules/@aws-sdk/client-kms/dist-types/runtimeConfig.native.d.ts": "1d4c5e45efe8ff11f363fa00cb6faf14aceb79433d56f37dd19067013ad973cf", + "node_modules/@aws-sdk/client-kms/dist-types/runtimeConfig.shared.d.ts": "d1579ae9878a50fb1ed4965715c16e71ac10bb742be5ea2e8125b2c6a10b20e5", + "node_modules/@aws-sdk/client-kms/dist-types/runtimeExtensions.d.ts": "0d6fee47fa4c30b31233fd5c6a69b238ee9944a680c69997c7c4093fe3bd6367", + "node_modules/@aws-sdk/client-kms/dist-types/schemas/schemas_0.d.ts": "a46511cf7d48396a441e0ea60923702e061d5912edb5964517d2d4f0d276ac49", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/KMS.d.ts": "b3921b544d8341cba4c45f28398c3e236c957637bc04371868ebeb593ff317f9", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/KMSClient.d.ts": "8c4877b45f12e6389204aeeefea10896c0dc450c4bc5b8b1932ffed25996a18b", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/auth/httpAuthExtensionConfiguration.d.ts": "3d6a2c1837be0aeb4d66e15248ca0bac7f8fe903a278135d2e909c354d2b0841", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/auth/httpAuthSchemeProvider.d.ts": "8f2c35db6722f63eb636ea3c687cbee7148ba5400b9c9e7f104e00c34ea12f88", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commandBuilder.d.ts": "ee77ae6fa1cf76e1bc6c5f11735b375d8e82022b7265d25ac68dce00236c20b9", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/CancelKeyDeletionCommand.d.ts": "edbe3960f1e83d782e717b3a44c9da867a2ca9ffe3a80bf687e24dba90aff44f", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/ConnectCustomKeyStoreCommand.d.ts": "df6d400dbc72343327edbbf2d18d593c0961e304ddf0fa110d942104db694098", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/CreateAliasCommand.d.ts": "5f63a53fa8743a9a6d4d1173092a7a48e352ae049ddd6cd207430e81d1421459", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/CreateCustomKeyStoreCommand.d.ts": "633aa15107ce62b09655fb524bd8dc35670be0c47c57d9b847be89a0ce2491ff", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/CreateGrantCommand.d.ts": "c511ab2b3ffa874fba66656ccaf03c2f7f36f91c48a29931d9e4aadff2feb1b2", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/CreateKeyCommand.d.ts": "2c2dd5ede42f69249dec2ecc277d45429016c5794886cfc93322a126c37af158", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/DecryptCommand.d.ts": "bd0007a4a1095587b2e2afb49b362dc19bba57e3acb95ebf8c224e75d588e46a", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/DeleteAliasCommand.d.ts": "cda06493983eccac9d28f84af7a4d323b16371d77f7a1b84dfb9bcaf0e5fca5a", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/DeleteCustomKeyStoreCommand.d.ts": "e50ae7c86bb849e859c98d3ba07b9998abefab2ecd3822a895a70eb93e4fa94f", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/DeleteImportedKeyMaterialCommand.d.ts": "bca21ddb93c2e7a71d12f684d94f40283d137e2f29ed2a9606a1783a8bf3c6e4", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/DeriveSharedSecretCommand.d.ts": "177b1e3456b0b7d178a76f4d5e96595e56bc63082b32620344f982cd3a897a85", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/DescribeCustomKeyStoresCommand.d.ts": "a33f179cd055dcd79369900944c15feaca0c8962e573093493f197c00c575186", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/DescribeKeyCommand.d.ts": "3f2040f6d6a41524d4e464c8033a30a2fd431260e1cfb4bba91580565754e535", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/DisableKeyCommand.d.ts": "3a3a7594d28e1bf2cf9ffadc0aeecbb8d6e16a66105b9065b8531498248fef18", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/DisableKeyRotationCommand.d.ts": "46444e6ca865117d5e77d215eac078de799be0e8e4190582f33d8b81b6278edd", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/DisconnectCustomKeyStoreCommand.d.ts": "1cdc16a1ce1f1a8f4388c6d3236f90402c81ad5e69629ae06636da936c15710b", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/EnableKeyCommand.d.ts": "32fc5560c9f3c5038a6f12882b7670a954b66c59780abda02a5850af8d887de5", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/EnableKeyRotationCommand.d.ts": "d96aa0f4f57d4350ea1f638af60cd48df590b0169f034b00b3f9db0f137824e2", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/EncryptCommand.d.ts": "6c3aa61f86936db110b31f0492bb4250eb08e68063c133afc23160d7327f8ac2", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/GenerateDataKeyCommand.d.ts": "47098849a356f6229291d5032ea9075dc0c9f059820cf042f2a8913f0d8cf321", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/GenerateDataKeyPairCommand.d.ts": "56345ef1807d7f1552a9f32437e3d66367d01071513afe2c30cdd8a492698776", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/GenerateDataKeyPairWithoutPlaintextCommand.d.ts": "5d572c41b8f1aef564da8910fb719ad77a28366f3452105b7507f4d2fbb03f4c", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/GenerateDataKeyWithoutPlaintextCommand.d.ts": "502e4366557d34be965316f48bbfc6bc5815812f5353f56b3badb22a30481d6d", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/GenerateMacCommand.d.ts": "389cbc4d1265b4f53c8f7340d76bf765dfc291cc67a9f480a52f782443b7195e", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/GenerateRandomCommand.d.ts": "0a1b6fae1bbe841adfaddc309d4dcea611617e10f2d6650c37239490e1e55039", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/GetKeyLastUsageCommand.d.ts": "ad867a93120f53bb6aaba72467ef423cdeb41aedb4251560d287e1c20ebe521d", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/GetKeyPolicyCommand.d.ts": "fccb5937c62c16e3f1ca2daeaabd1504b550e9c062b32e2d43d5377fb99391dc", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/GetKeyRotationStatusCommand.d.ts": "8e9932e5cf834f9daaaf7f07c84b2852584f7efa3fd2c91495b338631d4cd7e9", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/GetParametersForImportCommand.d.ts": "a440874efe2f2bbc927646286375eb99342779f1df88e4afda0babf9ef2fcbdd", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/GetPublicKeyCommand.d.ts": "90efd023f8a877694bf211e99ab625831814aa18534b99b1ce114a502bfbb5a3", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/ImportKeyMaterialCommand.d.ts": "b4c2e9deb17018c81cedac57494014140951c99344e0f51869fda3df003e507f", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/ListAliasesCommand.d.ts": "8fd7c10eca3b928eb6e32d2ed288b2ecd61ffbc2512fa3c585ffbc6d43f2b1ba", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/ListGrantsCommand.d.ts": "fb6e9b8a63d5dfdab80fdf908c5dad8cd8b428f76eeae82fe8c5bad7a16713c9", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/ListKeyPoliciesCommand.d.ts": "116cec775d80a3da13587cbcdad8f98d048245a2b347219b110b6da4906f711b", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/ListKeyRotationsCommand.d.ts": "9b9a44e2e454935f5fbb89d41b1fde70b6eaa5c32c4ca7405553df2aa18abe7b", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/ListKeysCommand.d.ts": "0c545f593d44eda795f962501acf991c968cd1e359a2c78ead0f88b7c806e517", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/ListResourceTagsCommand.d.ts": "7554b16065bd880d890ee204236454c4f6e9090ce08e6be876dc688daa2a8442", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/ListRetirableGrantsCommand.d.ts": "608f338032bbe0402f28678f9507b9cfeb2f99352ba75afbbc3f777b0743cb3c", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/PutKeyPolicyCommand.d.ts": "1f6999507104a86a3096fb20cc3015c970a400c814b4d3fe466eccae02d8b9bf", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/ReEncryptCommand.d.ts": "a3f651c4da3453134a2fcdb2598776ec290f7264715d8e11554f9ef48fcd36e6", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/ReplicateKeyCommand.d.ts": "33ce00b6b90204230b21012711b1d8bade55ec99b3d5676fff57c6efd137c14c", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/RetireGrantCommand.d.ts": "afbdb290941f8d44038f6f06610b09adf7ec0cedd959334f376d78f424aa6ce7", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/RevokeGrantCommand.d.ts": "bd6ba44b964eecf9a1456ea225eacc5e2eb1b971d41f40fc8b3eb68492adfd72", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/RotateKeyOnDemandCommand.d.ts": "d407ebdbb8aa267ee2261322a5e9233f117f09fa280d733e399b5408a264ae83", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/ScheduleKeyDeletionCommand.d.ts": "b8293044d40573dc952d70d5eae46b3580c6e467f9f91836a9eff53ae91c6f39", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/SignCommand.d.ts": "ae8bb9e1701253f11ba43b4c336b04c1a392b08afea8ca21c7700abe9c59e774", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/TagResourceCommand.d.ts": "7f536e4dede9a736b3ed6f98bf3685a74e43f7746335f2799ea6c300857aa385", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/UntagResourceCommand.d.ts": "5f318d5a4da42c19d3ce16f64152ea69d3138f0912523296b1d8c0ac89d3fd28", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/UpdateAliasCommand.d.ts": "dcf2f8f40875098144c44c1c5515fc51e1db81294893fcc49c85be3363b764c2", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/UpdateCustomKeyStoreCommand.d.ts": "2d834038cad4680d5f09b134b9b8a4a0a9ea39831d5e5d9baf841ecf83e8ce1e", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/UpdateKeyDescriptionCommand.d.ts": "5c9e2f4a71c15e25a2380146aac9d85280384e498032a04dc357f7f236721d6a", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/UpdatePrimaryRegionCommand.d.ts": "483e0147d4bf9aabf1157498fa44a916fdbc112fda18fb1e5b67c14c69d7bd96", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/VerifyCommand.d.ts": "767c8be17e1b928d888e11f752df25b75fe3c685e1b54efec5d81ff8ef0f7745", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/VerifyMacCommand.d.ts": "02eedc6e853bbafe2ef4f08b1bcf5cd96d394394165b091552bfbd6762a64cd6", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/commands/index.d.ts": "173c0ae68a645d991afef851a401175bb675379be9fa7441b3a4942b41c48873", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/endpoint/EndpointParameters.d.ts": "efc5d525e84010e14510c5db584c8182ca6d047c1040218a21df72206c260cad", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/endpoint/endpointResolver.d.ts": "8db6ac77345e04ed548fd662661a0fd1ebd10197510160beb75c3116fde4d2c5", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/extensionConfiguration.d.ts": "f2ae26d4ec23be209a8dada6e62e9e27a77ee25faffa55439227cb03a657e530", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/index.d.ts": "5f5cc1329fb20d37576d73de8c5843880d48a21c31e86bd6c9997627f329a1fa", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/models/KMSServiceException.d.ts": "159ca536400f71da82e98a61f93e33a08ae164966ec0b3a35a46e000b8d11176", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/models/enums.d.ts": "98d1d584b30666cfffa3e42c8f4b290f271a8dddc5be4ed56b30aa460e8c13b4", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/models/errors.d.ts": "dee8d82a557da6b9b7885638a76411b20bc87174a053fc71e8ea83cce3aaccf7", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/models/models_0.d.ts": "bb99b515273d079772e6c69c17a9d2b2637d9a845bf8e1aa455983ee18b0ef2a", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/pagination/DescribeCustomKeyStoresPaginator.d.ts": "45609caf22b5b6a5268a31940988cea615a70707555e43a82fb200565e35367c", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/pagination/Interfaces.d.ts": "a9e7ed7048436cb3136e9cbc7e018e5e0b68506a6390188ce41412135982135f", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/pagination/ListAliasesPaginator.d.ts": "52140924fa1c93b6122454f47722517753ce6b63c28f9526699cfbffec17db12", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/pagination/ListGrantsPaginator.d.ts": "cee01aa482cc89541ad9aaf5c6b440356c160593a38457a0afa08866ccc7b42f", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/pagination/ListKeyPoliciesPaginator.d.ts": "b815e989a0621ddcc76a67f74d3192026a43788720655169fce797a46aed1891", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/pagination/ListKeyRotationsPaginator.d.ts": "a69b47c6a09f7d22c69a319a85a101968c3c276902c32643a028f26c2ab67d75", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/pagination/ListKeysPaginator.d.ts": "786b8d620720fc71348608ca4353ddc8415e523333639a3cc1359c2f22e51117", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/pagination/ListResourceTagsPaginator.d.ts": "23d40146af8659b106bdb67399d56175f744c807d4e830eb977564349eb00a17", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/pagination/ListRetirableGrantsPaginator.d.ts": "e4f0a19e2d46148360b8c432fb5ccb3ceffe32b99b6bd5e29bca62b32d5d4fcc", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/pagination/index.d.ts": "4a347a72f5f2de8132f13ff85c4207022898783be27e4823c8d811840ae4afe8", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/runtimeConfig.browser.d.ts": "1f0d63b5a9f36f4da6fa6556f36f432513c9da72315d054ac64f76f2b933a066", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/runtimeConfig.d.ts": "23017e28d24b7ff883555c753ca2f6bd6d5e491552b48015fb8a1cafed0f7f0e", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/runtimeConfig.native.d.ts": "2853c94063c8793b65876b8f549f041243c3a4d9061010923a8c72608b7021c4", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/runtimeConfig.shared.d.ts": "5ae8a52bbe52d21e6a86014eda67338aa206e07373ee529be33053a672217581", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/runtimeExtensions.d.ts": "03e1853a0ccb4394b1f0e7948fd1130b1699117b51045beaffde760e6e39a6cc", + "node_modules/@aws-sdk/client-kms/dist-types/ts3.4/schemas/schemas_0.d.ts": "ebdd6e268e9f1e80ffc8b3e1e4ff0ae98015e7339b701eb00a13ab68abc255d2", + "node_modules/@aws-sdk/client-kms/package.json": "c229776fd29720395866f56cab9ab9bd0a40c1a768863b2033c780d6354753bc", + "node_modules/@aws-sdk/core/LICENSE": "edea91454b811f127fbdea3d86f378f6719bd372ed440abf82b232f6fca06c3d", + "node_modules/@aws-sdk/core/README.md": "6898525666afb96bb41c858f6c191b51af6bd704ffdee3a484ba93172156d89c", + "node_modules/@aws-sdk/core/account-id-endpoint.d.ts": "43d350eae26f1c1b3cec794c45ab605491dd9e85ea9305c1534f298663ae52ee", + "node_modules/@aws-sdk/core/account-id-endpoint.js": "678bb6fc27f23e8a9002a0f62ca645236560b1424cf0b47ad1c54c68841caaf8", + "node_modules/@aws-sdk/core/client.d.ts": "f80a670faae8df9f4fda7923fa121d6d8d72d6e1c99c7f48c51b29404ab8cd93", + "node_modules/@aws-sdk/core/client.js": "417d11f5b9f74e14c82d4039a220e1cb0935b5fef34d6cfe454b48558ba610a0", + "node_modules/@aws-sdk/core/dist-cjs/index.js": "19bbd0561e3dba27dff9626cc53cbae4b30e46bc604f8a523a6dc031c3e9266c", + "node_modules/@aws-sdk/core/dist-cjs/submodules/account-id-endpoint/index.js": "ac76c36c3a9d9093c9c805a902eb903b0aaf45f6edcda603eb5a3a78b04f2213", + "node_modules/@aws-sdk/core/dist-cjs/submodules/client/index.js": "2513eb655db232b804f895e8b3b24e52dbc83b9d3a98ea84300b61e23b05f04b", + "node_modules/@aws-sdk/core/dist-cjs/submodules/httpAuthSchemes/index.js": "cb5e888d34195c52a43106279c1457f598f88f508124250dde922a14baafa5fd", + "node_modules/@aws-sdk/core/dist-cjs/submodules/protocols/index.js": "ee36508322ba0dc6829eba7c26b3414bfed0bcd98543564618f8c50a5cb92ece", + "node_modules/@aws-sdk/core/dist-cjs/submodules/util/index.js": "ce4f9561be95fc185de8e37401f42a1fc649ca7422c924e874e50bb31ef8bdd9", + "node_modules/@aws-sdk/core/dist-es/index.js": "65d2194cffd1f992c5fe8dc46bdcd45a948d94bab9264de6d29233fcffe47eb6", + "node_modules/@aws-sdk/core/dist-es/submodules/account-id-endpoint/AccountIdEndpointModeConfigResolver.js": "1cd8c3aea5310b32fcb20f07657f7ca6ba3475c5e9dd6ab6243cf0506837e6db", + "node_modules/@aws-sdk/core/dist-es/submodules/account-id-endpoint/AccountIdEndpointModeConstants.js": "7a124eff93cab0be391092e458e2993b49bc497bf1198db2fbf45a2fa670a422", + "node_modules/@aws-sdk/core/dist-es/submodules/account-id-endpoint/NodeAccountIdEndpointModeConfigOptions.js": "148ef3ee2643fbba37bae1c6d713b1e0611230b708afc8bef17fa03de308133e", + "node_modules/@aws-sdk/core/dist-es/submodules/account-id-endpoint/index.js": "331d9dfb44b5f99ac7355173f49bf60d8f453af90f365429d81f04ecad71ea1f", + "node_modules/@aws-sdk/core/dist-es/submodules/client/emitWarningIfUnsupportedVersion.js": "f99efb83b24696c96afe0c5b9a8fd93cc0d8a41a68ccf5f710f68d9db0a893fe", + "node_modules/@aws-sdk/core/dist-es/submodules/client/index.browser.js": "59044d23b34e50a71492fdd9165958a95168bda2706ef330645cf0087d13ac8d", + "node_modules/@aws-sdk/core/dist-es/submodules/client/index.js": "123bf7ec8087b3d6833040caa769b28cf5b9989828191b38d41c2696913d1bc7", + "node_modules/@aws-sdk/core/dist-es/submodules/client/index.native.js": "e6e5363ed3b19ff3b3f690e746877330a4e55c7171f726b7244b308f83ee3a47", + "node_modules/@aws-sdk/core/dist-es/submodules/client/longPollMiddleware.js": "88c448b88b064dbbc2c1f62196291803c25e723d0b7e997418eea64a36b08429", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-host-header/hostHeaderMiddleware.js": "9f31c758a7b8c9bbeac6581d487e43f7db0491fbf804fbc464fb750f90a3d6a3", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-logger/loggerMiddleware.js": "0448f287de1664540e6331241b3ea10d4abf55624400139d57cd5a2e00cb57c5", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-recursion-detection/configuration.js": "cc3a63dc2ff0a142e6f0decd0a9d04e21b4a6db6a93246315bda14ba537d5b44", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-recursion-detection/getRecursionDetectionPlugin.browser.js": "5befd5d66b40e1b36cfde0b310b3aaa44a231b487b0a958d745dc21a592e893e", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-recursion-detection/getRecursionDetectionPlugin.js": "245a5a67431b66683779e6ce4b09340af248643184d252f0a743c2afb6e0bb8a", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-recursion-detection/recursionDetectionMiddleware.browser.js": "f0b209f78394c92c67be615acf1352e94624490f8780e2c3e9a4ad112d325cc0", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-recursion-detection/recursionDetectionMiddleware.js": "cec26b92be1b2ab3723495a8d414acc6e4a5e9f49aa860cab58daf0e8930f80c", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-recursion-detection/recursionDetectionMiddleware.native.js": "f0b209f78394c92c67be615acf1352e94624490f8780e2c3e9a4ad112d325cc0", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-user-agent/check-features.js": "4bb9d81d0f0698c84b3a046cc3f192e9e72fbbc972a9d718f6d6e02eb517af21", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-user-agent/configurations.js": "8ef3d005929c55fea8ab61dc21f79b115687904b493146ba2a7781ce71e1e85a", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-user-agent/constants.js": "e80bc9ca1c77569076817569a981e5a09e982af132b772a68af953a41113681b", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-user-agent/encode-features.js": "51bfb4199dccef94e6dfd780abd942f5f2bf364cb9622273bedfc983ef655bcb", + "node_modules/@aws-sdk/core/dist-es/submodules/client/middleware-user-agent/user-agent-middleware.js": "251a98992a5bf3eb7b9182a5c352f45db690acc37e62aacd32e4328551442ad4", + "node_modules/@aws-sdk/core/dist-es/submodules/client/region-config-resolver/awsRegionConfig.js": "327e6b8372978a39ccc7cb5e43101c16ac78b4a20777e541ab7272acb4689cd4", + "node_modules/@aws-sdk/core/dist-es/submodules/client/region-config-resolver/extensions.js": "5d0173957d69e1be91cb7f8a210f141948f866c90ea18ec1bfb7491b396829d6", + "node_modules/@aws-sdk/core/dist-es/submodules/client/region-config-resolver/stsRegionDefaultResolver.browser.js": "3d040b372d53ab01514a8f466e04d9a601b2f22a84d4546957d46a2e76f8ca40", + "node_modules/@aws-sdk/core/dist-es/submodules/client/region-config-resolver/stsRegionDefaultResolver.js": "1043d09002b6549dcf0ec855519ee797b4b78f7ac97cd32bcce3cbea69be8807", + "node_modules/@aws-sdk/core/dist-es/submodules/client/region-config-resolver/stsRegionDefaultResolver.native.js": "3d040b372d53ab01514a8f466e04d9a601b2f22a84d4546957d46a2e76f8ca40", + "node_modules/@aws-sdk/core/dist-es/submodules/client/setCredentialFeature.js": "f6cb193ddfcbf62eb6b160eb4c55facad570d78958af3bce4a79678b909fed0a", + "node_modules/@aws-sdk/core/dist-es/submodules/client/setFeature.js": "410bf8254abd86edfdc0a227589081cf67ba89b87519333444704c02d552147b", + "node_modules/@aws-sdk/core/dist-es/submodules/client/setTokenFeature.js": "3b25169114b08f6e55d821109d2882e8d04b6f70d72a8c31207efe291646aacb", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/aws.js": "e9427ca885f06c6f64adbd77797cc91a87c5a3a8fd820cb0c31a13a9351f20f7", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/lib/aws/isVirtualHostableS3Bucket.js": "f5c986aac689323c14b1be9f386552ff50bb8e0a65a850960a6654718caf8265", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/lib/aws/parseArn.js": "1231f0252db9d34befa15884283a8ca81fc3afd52e88a7c477c4e6158479d6e5", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/lib/aws/partition.js": "d20fb415a0643b3680fcc0f84597b68c6b3e05e3417adcefd983c8bf252e7521", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/lib/aws/partitions.js": "a48a74a926d208b0158ee9ee64fff6cae7905925609d598f3a3bd5909dbd8dcd", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/lib/isIpAddress.js": "e778484929125e97d196b9ff73201fd609e81e2fba2e7c8a59d3dc8afcfbd4b3", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/resolveDefaultAwsRegionalEndpointsConfig.js": "5205937367e4146adbde62d9df29d3e1dfbeac7433a9694ee0ba4bac33f5161d", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/resolveEndpoint.js": "20b86895feeae4bbdac7d591a3a6bd0a9514857efb34424e47fe50c8876cfe93", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/types/EndpointError.js": "03750d97874c868d7a1b43c03fb4d58c02721797a8a3bf819054397a3c1cdac4", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/types/EndpointRuleObject.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/types/ErrorRuleObject.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/types/RuleSetObject.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/types/TreeRuleObject.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-endpoints/types/shared.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-browser/configurations.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-browser/createUserAgentStringParsingProvider.js": "3ba8e1ef7c60f22fb9e24116cc629f98d808b0211bd4d8aa842cbbdc2b8b8ebc", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-browser/defaultUserAgent.browser.js": "2902041048c01a5cf9beb967f65f081c86baa6499ee282dfbb450bf78009f5bf", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-browser/defaultUserAgent.js": "94250276c4359dc4b0b9e7404cc312c4e7a6e86817d5e9af0776540c899b181d", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-browser/defaultUserAgent.native.js": "64d53ac704480be937e970fc28315b5260c366eace1fd64efc6b341f17382a63", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-node/crt-availability.js": "4c991af0b502296884707b9009c95146c6370a132b2a8448e457ab8951d98359", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-node/defaultUserAgent.js": "d5065fccf755c80c434c6afee4118b4582f7f6e2216b6487e69fde78400cb072", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-node/getRuntimeUserAgentPair.js": "5a41f7a5956815fa03d0b4cae3c598015bf23b233a0f97e49c770346fa031011", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-node/is-crt-available.js": "b8d0cd589059a5690e05e4b5e5600394d9a4102a653841eeb54ee266a87651e4", + "node_modules/@aws-sdk/core/dist-es/submodules/client/util-user-agent-node/nodeAppIdConfigOptions.js": "f254a709374f8d94ab04a6868789c6aa445a0de6575ebd76780f29581ad26be8", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/aws_sdk/AwsSdkSigV4ASigner.js": "bc9ef4fd66809df954ef192250b7bde17786742898d5e15664105902c207d790", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/aws_sdk/AwsSdkSigV4Signer.js": "257aba8190ffa0487409926fec1698e9cc733b4c42c8bb9ae1dac13d7e9464a9", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/aws_sdk/NODE_AUTH_SCHEME_PREFERENCE_OPTIONS.js": "c62b4eb9663fa90c8af3599af4f879587318b4979984e3ae0eff9ac765a2f4c9", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/aws_sdk/clock-skew-defaults.browser.js": "7dab4a0142c90f8cb1d8a2de05c1231c7f29c4b64677e63ec19cf4cb983e9f5b", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/aws_sdk/clock-skew-defaults.js": "498d38eab5b4a75cd04223a35fdd967244f90b35ca9c80cddadcaabb3f33961c", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/aws_sdk/clock-skew-node-config.js": "ec71a2eb37077c7ff64ad704c16fe2c24e5c7281977f83b67ced25e824291093", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/aws_sdk/index.js": "5fcc9277ab5cf6607a6d14a59b950c153c5555377e0452837d510dbf2ca8a66d", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4AConfig.js": "50a1fce6f6150a04f35d3b357ca67d62188a2600151633650c7eb9e058c92155", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.js": "c5c9e653a8875a18bdc23c3c05c2bef3c5e16e86519d1c712c8ccdb2e19dd6fc", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/index.browser.js": "c5786dbedbeca53cbe950f4a700301c8e0eb0a47f23d390777c8f49558653be0", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/index.js": "5c16ee0b05ca55a127b74db3bee75ae6c361cef45536bfb446026a3b997bb8ce", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/utils/getArrayForCommaSeparatedString.js": "d68fac394b0122eb98b1e73153504a038f2ca2cc6dfd15390d6f502b0be9e23a", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/utils/getBearerTokenEnvKey.js": "f16aaa15b9e4c2fc3b1ec4c19ac9769648a96c60da7f1f144b97485306afe4ca", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/utils/getDateHeader.js": "10f5fbb20bb20f79cef0ef0a8507e57871e55bb681143ace73b282f6b27ac49b", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/utils/getSkewCorrectedDate.js": "bd9bf12cf3d0f624c932d347df3a8ef98f6db1a874f39e244d3db68ba77a39f2", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/utils/getUpdatedSystemClockOffset.js": "855d978b88e690cfc9057dec02ec574c1b93a71c3681caa1fbf3069af74ef22c", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/utils/index.js": "4420801134aaf41391bce0f1423430cc39cb22e98ce62e8cc953cf90ff060949", + "node_modules/@aws-sdk/core/dist-es/submodules/httpAuthSchemes/utils/isClockSkewed.js": "ca3fc1477b57b7a13f6be26c88498bfa8b1d27e050483b4d1be71b471dac426c", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/ConfigurableSerdeContext.js": "e35e37c863fee753f36c943c7ba9d2cd6ef31bcc26634acf1fa885d7a96e4c15", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/ProtocolLib.js": "f4183a1076f0dbfee3a61fafbb528d7a967e8f152f4a04a162720eb781d44804", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/UnionSerde.js": "9bef04bc32ed77b2f62ecb6151d5751805daba19cd05cbeac16a2ba009a1956b", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/cbor/AwsSmithyRpcV2CborProtocol.js": "a92e71aee1f13c7ffa269169d4b3adeb72631781485f353db6cd88a8dc22d45c", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/coercing-serializers.js": "69b90d08dd03a2195aa23a575aa8988e5500b8e1cfdc4c637677c5da0684b6e8", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/common.js": "415e24f7ecc8f8f80554835dd85db9cef609a5c0f258f6c40fb2cd3e26708e49", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/index.js": "6b7d2f09ab1d9cf8c89c56f2fab3405935eb9d5849bf102cd3965c6ad7943854", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/AwsJson1_0Protocol.js": "e524d61046a91c04b7917bea3a17d2973c290fc30792de01eff52fc60d7c91fd", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/AwsJson1_1Protocol.js": "78df6a4645b1138c6a9b528c66807fa4d5f71299633512e3487e95f82757f470", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/AwsJsonRpcProtocol.js": "756872bb4e3c7536ca9e69e642b654901b41aa5c8f5e4fddd58052790a01769f", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/AwsRestJsonProtocol.js": "eb6f95b0a9db3b7ea511a9c01775cc517bcb8541d0fa1b9d753645d9fb404924", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/JsonSettings.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/awsExpectUnion.js": "07e89a25ef68ac0290500eaf256bfbc10866221df072b28b50a4bcf8fb3c84df", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/codec-v1/JsonCodec.js": "cb9ae8fed1c3186e39a23c1bcfabc9e08f1f65829f4ca75cf6fb987ad7207666", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/codec-v1/JsonSettings.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/codec-v1/JsonShapeDeserializer.js": "9f1a18f206fbf320545d3e63f224bec9b9b1242ed4067c5614353419eaa973e5", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/codec-v1/JsonShapeSerializer.js": "1a7aa73807795ccf319683979ab33905ff535959cf957112ff7e6250ad7221b9", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/codec-v2/JsonBytesStringAdapter.js": "a2ec07809f31b2c276a46688416f1e6fd73b621bb992df5f359dcadd05f16556", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/codec-v2/JsonCodec2.js": "39aa636ad3896d36bd1ee5af4426de78410333260aa70b0d941df72ca6e7b708", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/codec-v2/JsonShapeDeserializer2.js": "10da8e599c36ab00181a4e3c0fae7e4c2b68c4b0871c52d91ca3f5379bdc1861", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/codec-v2/JsonShapeSerializer2.js": "94955598d93530a63b1a0084d92e498c0aff30f8ae11d31d777c03e6092667d3", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/detectBufferParsing.js": "b4ca3f6b734f9bed1db26d642d6014bd951ebf6b98c8cdbfd3859513d0d16640", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/jsonReplacer.js": "b0e4f46f45e5d390ae1d2c038e60eaa809bf6a24060b24ef6b35cbd48e7e1011", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/jsonReviver.js": "ff676cc50ed28e942f6d08b5909a622a7de15ca6778366ff713d9732b2063c03", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/needsReviver.js": "ce84fe8e0d7a635fb4bb673d46deaefc5157eb3f4c6d7bde5d5b764a163aac15", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/parseJsonBody.js": "649779085944a8f39e18b985e16b0656a8174abfda715a095745e635fc18a04c", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/query/AwsEc2QueryProtocol.js": "12a29319fec510ec05da279251f85d7d26a902eb520667aac2b7bf5b3219825f", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/query/AwsQueryProtocol.js": "48fa3f94c7d90e1c26f387474820cda58f042b4c24c64b9edcd0fcb1b0e0bca4", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/query/QuerySerializerSettings.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/query/QueryShapeSerializer.js": "bff10774a545ff83050b4ff2013ef4c9bc513a20badd940cdf63b5345e8fc2bc", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/writeKey.js": "33b24e0615b11c9745f8a21ffa6c86775a44c7f16b6627a0ca7a552effa9b49c", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/xml/AwsRestXmlProtocol.js": "7c46a6c52afab6f6891c4a2f3e27f48e2a9e3c566e8c845fd99f32d784f1d24b", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/xml/XmlCodec.js": "2df570eb61f07b137d421b95180f3cbe838099f1b9953ec5af471f0e904de1ee", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/xml/XmlShapeDeserializer.js": "ada9587eacc163869f93297839480d6bf0ca8713d1623587ae529e02c8c8c872", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/xml/XmlShapeSerializer.js": "04c3ca79a6545aabcba782be86d75eead38e9d4413f8d70ddb5bb8ecc94deec4", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/xml/parseXmlBody.js": "f8eb5bdad0a0eefb4a0b6ebdc6b4e1628f48921fcbd0a8e8129c34eeac2511cd", + "node_modules/@aws-sdk/core/dist-es/submodules/protocols/xml/simpleFormatXml.js": "1ad8135a67fad4ebe72e55b41588f0b5fd53012436a011b5e17b6b9e3ce617dd", + "node_modules/@aws-sdk/core/dist-es/submodules/util/hasOwn.js": "acc748f68df0148cf9c00254866dbdaf397aa05384505b2044f85c0ab8cdb924", + "node_modules/@aws-sdk/core/dist-es/submodules/util/index.js": "522a92bf491c70abf54377b5cd0fb7eee09e538c318f6048d0e845301959b321", + "node_modules/@aws-sdk/core/dist-es/submodules/util/util-arn-parser/arn.js": "bf0e0e385ebedb2a4f4a17b697783e4b7bd0fb1ebf96758f2a79572eefc277ad", + "node_modules/@aws-sdk/core/dist-es/submodules/util/util-format-url/format-url.js": "c015e02fb68d0bd4fb983d85f06a443a066f284561963ff9e1b70d28a622abcf", + "node_modules/@aws-sdk/core/dist-types/api-extractor-type-index.d.ts": "d4bb5349c7f6687901a793951e72c5841a072083868afb68456b380c241a73ce", + "node_modules/@aws-sdk/core/dist-types/index.d.ts": "208e251f440853aa536325e94b9ae2a06c4a828f829752aeb0b7fdb193b55829", + "node_modules/@aws-sdk/core/dist-types/submodules/account-id-endpoint/AccountIdEndpointModeConfigResolver.d.ts": "37cabd9c39873ea7ce5ad607f718e43b03c5fd806c4ea7ea8eb0490635e39d9c", + "node_modules/@aws-sdk/core/dist-types/submodules/account-id-endpoint/AccountIdEndpointModeConstants.d.ts": "150f25e1df681ac86bf2224814e628ec8c67b3b0bf91c5642007f072fc403422", + "node_modules/@aws-sdk/core/dist-types/submodules/account-id-endpoint/NodeAccountIdEndpointModeConfigOptions.d.ts": "0d4441555b6f5e977d9130ead5a54fffc577d71cf26120e6f07d7e4550b28ee9", + "node_modules/@aws-sdk/core/dist-types/submodules/account-id-endpoint/index.d.ts": "d04ff0e88cdc3130b97a461890d81dbac6661babce2b2360894382377d56d190", + "node_modules/@aws-sdk/core/dist-types/submodules/client/emitWarningIfUnsupportedVersion.d.ts": "c38481c180f39569723e77c0451fe329a0a6c14fee11d6773cc3189287ee8ca5", + "node_modules/@aws-sdk/core/dist-types/submodules/client/index.browser.d.ts": "d002dbf91b07f57e416b91dbc40b54bb0a48864b6d0708a6b23002b3e47874d0", + "node_modules/@aws-sdk/core/dist-types/submodules/client/index.d.ts": "2d6d7aacaa56ce15408bd4ea56a11fb50bddf9cddf228c23f21e6e6e8a43cff6", + "node_modules/@aws-sdk/core/dist-types/submodules/client/index.native.d.ts": "40886e1d42338249622418788f497e5a4209ad9dbe2fbf871bc6cab8c6b27f87", + "node_modules/@aws-sdk/core/dist-types/submodules/client/longPollMiddleware.d.ts": "1511720830e8ae34e38ace695150e6ea3453e68b91b5cd2c1c523fb5a3f04210", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-host-header/hostHeaderMiddleware.d.ts": "b30cc18b84468d3fa20ac04ca5ba9bed5a03431fc8a22bcf2c266c132baa1d3f", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-logger/loggerMiddleware.d.ts": "5e557a5ef621a20d98f5edefeb8fa2b00b335383d2c9415f921bc4dd702d6c6c", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-recursion-detection/configuration.d.ts": "484b607e34ba73a7a80355c576e209de5e5b337a5b0f6b5c4aef17b22311f20c", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-recursion-detection/getRecursionDetectionPlugin.browser.d.ts": "2654171bf7ec29b65131fa19657c350c8708a6e3d9bd3e8c7686bafd6f04da2b", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-recursion-detection/getRecursionDetectionPlugin.d.ts": "2654171bf7ec29b65131fa19657c350c8708a6e3d9bd3e8c7686bafd6f04da2b", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-recursion-detection/recursionDetectionMiddleware.browser.d.ts": "cab17c02a8f8ab49ce3782bfc01a736bf2f8bff5834a1baac092b75fa62def83", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-recursion-detection/recursionDetectionMiddleware.d.ts": "11cf0c79c790bf3bc4ed4091743b33337e3da3c9888c6dc3856df896b0ad4e9f", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-recursion-detection/recursionDetectionMiddleware.native.d.ts": "cab17c02a8f8ab49ce3782bfc01a736bf2f8bff5834a1baac092b75fa62def83", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-user-agent/check-features.d.ts": "4b583c1c1a7bc5c2a761a5ab4a2ddbee80807d0d2ff810d73a37cab3430c50f0", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-user-agent/configurations.d.ts": "a9452e81c28c642c2f095844c3473d979eba5ae89726ad52b15ea86b3e112ee2", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-user-agent/constants.d.ts": "b882a7a6184517f951624f6a0a0f6c2666528efa1768685cfcd781cbbe871fc9", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-user-agent/encode-features.d.ts": "4ae5b1c6021baffab6ab796dd25700b45fcf592ee446dda5e852a88abee4a6a7", + "node_modules/@aws-sdk/core/dist-types/submodules/client/middleware-user-agent/user-agent-middleware.d.ts": "dc4a2cf12254395c8ae3fb4c61e6fd9f7c16110be66483599f9641941416988f", + "node_modules/@aws-sdk/core/dist-types/submodules/client/region-config-resolver/awsRegionConfig.d.ts": "a53956c21f4ddb57c747282a2d7ef056c74a0035acd2803876276d3e3e240277", + "node_modules/@aws-sdk/core/dist-types/submodules/client/region-config-resolver/extensions.d.ts": "9143632638d548e6aab61faed972cb220ec797141eb99acd60b4b6b85e2bce83", + "node_modules/@aws-sdk/core/dist-types/submodules/client/region-config-resolver/stsRegionDefaultResolver.browser.d.ts": "543ea3407c72a426524cd0345bb3007f51e62eb645314a325f6ca1beffe89446", + "node_modules/@aws-sdk/core/dist-types/submodules/client/region-config-resolver/stsRegionDefaultResolver.d.ts": "44850e2b42a72d92d334fe5b0fe369365d8630a8f75e6fa3ffbc8478515c7f9c", + "node_modules/@aws-sdk/core/dist-types/submodules/client/region-config-resolver/stsRegionDefaultResolver.native.d.ts": "543ea3407c72a426524cd0345bb3007f51e62eb645314a325f6ca1beffe89446", + "node_modules/@aws-sdk/core/dist-types/submodules/client/setCredentialFeature.d.ts": "0231f8c8413370642c1c061e66b5a03f075084edebf22af88e30f5ce8dbf69f4", + "node_modules/@aws-sdk/core/dist-types/submodules/client/setFeature.d.ts": "e3771408849a41a4c7cb2b472870c4e8abd4efe639c899d2a8ca2eba6c6c4923", + "node_modules/@aws-sdk/core/dist-types/submodules/client/setTokenFeature.d.ts": "8e1884a47d3cfddccf98bc921d13042988da5ebfd94664127fa02384d5267fc3", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/aws.d.ts": "a866b411640b7d1a0d4835870938c8d5c34ff45425ff07bc4fcc01318dbddc19", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/lib/aws/isVirtualHostableS3Bucket.d.ts": "b7fde9205fb056773df84e31c6c320ebac6610c20e81dd831577e7091d45abe2", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/lib/aws/parseArn.d.ts": "519d4279cc006d9d2a70b61471835827185c39ead41e9aebd98a586cdf499d9a", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/lib/aws/partition.d.ts": "c8005f8a91952d98aa1c772db26326138545a52ef0c1fe14b05fbc96e7a8a4fa", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/lib/aws/partitions.d.ts": "e5917606e638d27c7862124f97dcab00ad7f6d9de38ba1472f0ed1879ed434e8", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/lib/isIpAddress.d.ts": "e778484929125e97d196b9ff73201fd609e81e2fba2e7c8a59d3dc8afcfbd4b3", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/resolveDefaultAwsRegionalEndpointsConfig.d.ts": "ac36f7e7a0cd018944fd483dedc7d97888e224798a687deb267c4b410ffb0a14", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/resolveEndpoint.d.ts": "20b86895feeae4bbdac7d591a3a6bd0a9514857efb34424e47fe50c8876cfe93", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/types/EndpointError.d.ts": "03750d97874c868d7a1b43c03fb4d58c02721797a8a3bf819054397a3c1cdac4", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/types/EndpointRuleObject.d.ts": "0ae299473018d8108d32411734afe1a803bc1aa488654cf09f3d6acb73934b09", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/types/ErrorRuleObject.d.ts": "8cbdcfc78ef9c64552d4a2b3f8abcb2aa3baabb6f148480b01042e768bec580e", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/types/RuleSetObject.d.ts": "7ff6a1165873e055d2a66a6d8513b31281e64c58ccd8a24f365e8afb8d56d248", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/types/TreeRuleObject.d.ts": "3528552b3dfe32b6cbc2d49edc3198adc413a9d78d3b33facb8391381af27dd9", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-endpoints/types/shared.d.ts": "f4d66d230e4e64c1a90e386b6981d4216fec04bdf70c7954b5b0fa61d8ab2f7d", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-user-agent-browser/configurations.d.ts": "f4a795af80885eba93957db860b4b82b4d23a76c5e122c2af5eeb9319094d9d1", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-user-agent-browser/createUserAgentStringParsingProvider.d.ts": "9c779090e775efe37b07ebff3e473e75bac2dac90a4937b606c3b79ac2d141e1", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-user-agent-browser/defaultUserAgent.browser.d.ts": "3feafba49a5b370cc5b9ff673262cacddc31c18c377418fec1c360233fd2aecc", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-user-agent-browser/defaultUserAgent.d.ts": "41e26b987185f2b3705a0618ca6af34013ab505397a346b6c04509769fdb4b90", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-user-agent-browser/defaultUserAgent.native.d.ts": "68f0882889b1fd3c9ce60505ac8c9f4be7986c0898201691f763766cac04feef", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-user-agent-node/crt-availability.d.ts": "58c7fe4a20869e13d24103f0faf9038a8a4319c985a729bfe1af51e0802cb89d", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-user-agent-node/defaultUserAgent.d.ts": "82d1c94da0a4059cec511f57d4c6bf4f1c0a4c7a58014b742b110e5bfd295d87", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-user-agent-node/getRuntimeUserAgentPair.d.ts": "97b0ecefa91c662cc05ff71d64c7504f9a58374765f455731c21a36397c097a7", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-user-agent-node/is-crt-available.d.ts": "7ad8d9f0ce6d4cf09da4a0214f8c60698fa9f0e1eb46657eaea187262db8dcc8", + "node_modules/@aws-sdk/core/dist-types/submodules/client/util-user-agent-node/nodeAppIdConfigOptions.d.ts": "deb5db006a37804b0c2b2e7514ecbc536f42de6667181eb219ef1720b2820745", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/aws_sdk/AwsSdkSigV4ASigner.d.ts": "1de7ee494c7ac185e6abf94428afe270e98a59f1bb4768e4bea7804645a0d57d", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/aws_sdk/AwsSdkSigV4Signer.d.ts": "87a7db06dfc1b4118efa592131a0db864d27d593ab628ff298217ec660aab0bd", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/aws_sdk/NODE_AUTH_SCHEME_PREFERENCE_OPTIONS.d.ts": "40b61395ebada0f0e698d52d9a58cd625b5b268f49286de6348fa66255250bf4", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/aws_sdk/clock-skew-defaults.browser.d.ts": "ccafb88f5216830675565aa751391eee6d35cb69948954e6c6ebbeac2ac48eb9", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/aws_sdk/clock-skew-defaults.d.ts": "1be2355702745c584d5db79037bcaf61015213c7032836356a726afc43429eaa", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/aws_sdk/clock-skew-node-config.d.ts": "43c75334cbce8dd3154e2f1abfa0a5ba92af1a4423fb5e0e7c03d2661fad4917", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/aws_sdk/index.d.ts": "fc7b89deb6136ffe5899418845ded8c9c8b4d83c67065828789eafe9b23a0054", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4AConfig.d.ts": "ae9b847703f87007d92e26f80efacc6cd53999f49aa5c8736f665d4923b34049", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.d.ts": "26bfe18adfed059c4144db42e395641e99ec6a9972e26b4da65ee0cc6f2ac5b7", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/index.browser.d.ts": "9116165b43692e0bc55ac3a428fe73547806b244d0d3887b65cf77bb4a4de295", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/index.d.ts": "8086fb5f51ceab742bfebada6cc847ee24d56a0c60afa44bec416eb07c0a1e81", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/utils/getArrayForCommaSeparatedString.d.ts": "fc6b555d888b860f420e3e6fe6d03e027b15285853d5a0d6c05a598e9df39fa1", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/utils/getBearerTokenEnvKey.d.ts": "50edac457bdc21b0c2f56e539b62b768f81b36c6199a87fbb63a89865b2348f0", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/utils/getDateHeader.d.ts": "ab8f5f8fa6e47d69bc8265a0b787d6136306e9b1df16bcc511d3bc17f14abaf8", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/utils/getSkewCorrectedDate.d.ts": "029a3ef4bc9a23c7a8b8f0dfe79383690d61d5e7d629c532c75979b5856adca6", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/utils/getUpdatedSystemClockOffset.d.ts": "87ec3ea3160d5443af7dff5ce17d9f673c92b6d976d7cd6578fc12a307794248", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/utils/index.d.ts": "4420801134aaf41391bce0f1423430cc39cb22e98ce62e8cc953cf90ff060949", + "node_modules/@aws-sdk/core/dist-types/submodules/httpAuthSchemes/utils/isClockSkewed.d.ts": "668f3b2d370a46528565617cbdd78c84ea9a10a8705ad287b5b018f2420a7800", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/ConfigurableSerdeContext.d.ts": "ed79978235b685e7e9d2ac149c6ddaf602ce7e3a30725c20023e57f011760593", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/ProtocolLib.d.ts": "00c388fa906176cd265261dd984c73d497bee7223c2bfb97e48cf16570df7abc", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/UnionSerde.d.ts": "18afa6378531e460d15288b4f0a4391998a55cf592d11a3b5705e3c28f956044", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/cbor/AwsSmithyRpcV2CborProtocol.d.ts": "3e56d3093cd84dac94761258d6d98226671481d43e93321f9825692940fcc0c2", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/coercing-serializers.d.ts": "25091d25f74760301f1e094456e2e6af52ceb6ef1ece48910463528e499992d8", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/common.d.ts": "02d96d5803557a583b8e06754560bf600cb5d7c22fc76a95adf2f8255d0c9d60", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/index.d.ts": "ea9a0f04fecc89a4f76a851b43536658ae37c851a601047f4936e9bd8be2aa6f", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/AwsJson1_0Protocol.d.ts": "d94d57c33e7a2c752d085eaca4b8c4f982beb6d2d267a56449714f866e146d9b", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/AwsJson1_1Protocol.d.ts": "bcead67677853081442a81268467e0b0c9c65fe70842dbb7d27e3cbd581381ca", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/AwsJsonRpcProtocol.d.ts": "8273c1a180efba389620fd3870e4789b1dad44e1b90aab236c9bc765f04f5846", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/AwsRestJsonProtocol.d.ts": "f302f65af1a9ad1376f82a69a63ef5c95b290a49c3a4a509774ca7d68b7b201b", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/JsonSettings.d.ts": "eb003285f48ab1c7bbb8d6339c567a19a1386df36a16b91dc69e74c7d729c88b", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/awsExpectUnion.d.ts": "853d02f4f46ca9700fefd0d45062f5b82c9335ba2224ca4d7bd34d6ae4fc4a7f", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/codec-v1/JsonCodec.d.ts": "b70d3289e6ba7904b84d0d6d611f3e2f5fccb4e2e7e7bdaabfeec3cc895d69f3", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/codec-v1/JsonSettings.d.ts": "eb003285f48ab1c7bbb8d6339c567a19a1386df36a16b91dc69e74c7d729c88b", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/codec-v1/JsonShapeDeserializer.d.ts": "64ee67c22e9c2ed09fb52db92b45984c1a0a00d4944d36e96059faf0e5681c10", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/codec-v1/JsonShapeSerializer.d.ts": "141d46146c49a31b99db67761bcd19b0396be74e89dc7a36520d079e459f1e10", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/codec-v2/JsonBytesStringAdapter.d.ts": "c52bcecda8c020d70b696bde68d4661a70cb0aed96204ce1be89c7f3a1ef51e2", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/codec-v2/JsonCodec2.d.ts": "9411caab4919b1e16fbc5c85b11bd40fd21bc67d35223337f9afb66f90260a06", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/codec-v2/JsonShapeDeserializer2.d.ts": "26d9b93fadd38320479822e3188b834bebbd203b27442bc2c2bc66e865d0290c", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/codec-v2/JsonShapeSerializer2.d.ts": "161ce72a1e47afe5f20abc73d441a982a74ef1dd72e4d588a0dcf050aaae3e0b", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/detectBufferParsing.d.ts": "b1ae70805b8d6d60b0ade7c7fc4aa5c014f8b220e33bd5a23ef01cc42748b5bf", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/jsonReplacer.d.ts": "b725978a490933aaefd95de903b792cb564849ac84094c1cfef6fd94688a9622", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/jsonReviver.d.ts": "a9b3b14c08258cfeb0fdc197d5d0fdaac3398058146fe5db3f5085dcc1c3a41b", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/needsReviver.d.ts": "69b3dc78e59f7460ad76a7fefdf5ec518d8ce406cc5c2be9be78469127916a1f", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/parseJsonBody.d.ts": "2e1c64e6f6d5105d6f3c89be9a045f0f48fce91f863ed097ad8386b546315f98", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/query/AwsEc2QueryProtocol.d.ts": "506b96750c75c5e5af820ac8356f27b2bcd0b077a02d33d90e87be9f0604f59d", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/query/AwsQueryProtocol.d.ts": "f6067be38e8661d7e68287db5a602501897e2fbb385dfa2ecd170dd9f4524e8a", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/query/QuerySerializerSettings.d.ts": "a6da29e6495bf303eb5f0b65dca3f92711b9cd6729eb1bed3e29dbc8b0fc2604", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/query/QueryShapeSerializer.d.ts": "c35bd33a53356146889d87a05b34fc5a130ba93bc1bb36d021c0a7c817c4cc8d", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/writeKey.d.ts": "197dbe64c2aa9895e09e281221488fbe86d30e440a5fc8b7736830516e2b02f2", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/xml/AwsRestXmlProtocol.d.ts": "d9c87237f57a1ca503eebb89514f65c48800b10b5574f7cda978addab1474dfb", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/xml/XmlCodec.d.ts": "364e53fe15122e9d37aa8ee2c8eb037cde59bf5890b46a8205f4516b529501c0", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/xml/XmlShapeDeserializer.d.ts": "1a577fdc45901cf461d4edc7697860c63a60526f60b7b2ba8ff7c89a9e7a1932", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/xml/XmlShapeSerializer.d.ts": "a0bce0fb40b88d17305f113ed02c4014329be52e8168b01fe825c049e9a37028", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/xml/parseXmlBody.d.ts": "b3cc1bb7311f35569b531e781d4a42d2b91f8dfd8bc194cc310c8b61011d6e43", + "node_modules/@aws-sdk/core/dist-types/submodules/protocols/xml/simpleFormatXml.d.ts": "508bfcd053b4fc2902931711300bb687bfcae87e11db352f6014cdc458a85e6b", + "node_modules/@aws-sdk/core/dist-types/submodules/util/hasOwn.d.ts": "4d5b02bbbb4727d4b20aa4932f30d1b021affb3b1dd4bb5fee9b1e994cbbc98a", + "node_modules/@aws-sdk/core/dist-types/submodules/util/index.d.ts": "f999751358abeee5dbd8395680f4d554e4232c1806e76f1c0a0765405193dca9", + "node_modules/@aws-sdk/core/dist-types/submodules/util/util-arn-parser/arn.d.ts": "1a861d70717866b9303911effb37e06bd237ae1b797e9ddc0d76d6386ec41406", + "node_modules/@aws-sdk/core/dist-types/submodules/util/util-format-url/format-url.d.ts": "b41bee9bdffb5d3fd302e67f5476cb4f8676ea3c553473db7f04bcc5320f516c", + "node_modules/@aws-sdk/core/dist-types/ts3.4/api-extractor-type-index.d.ts": "533c2bff972b414c6955587d626df8afa2e9776df1c8ad52767f43a0e8ea4d83", + "node_modules/@aws-sdk/core/dist-types/ts3.4/index.d.ts": "f069d501f26d945945d64cea67d02ff5d20f7b7e5a54a9b2b377d8a5585d3f50", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/account-id-endpoint/AccountIdEndpointModeConfigResolver.d.ts": "f19211c8fdfb81c7708d3c9a8d4354e3881c5d3a79b3d6dfcf2f86a6e137d3cf", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/account-id-endpoint/AccountIdEndpointModeConstants.d.ts": "e96a7689f0a24828d03a87a25ccfae71cc3608b48772c85789e3d1b07f43a5b2", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/account-id-endpoint/NodeAccountIdEndpointModeConfigOptions.d.ts": "528fbe4e91600706c535e7a0028f0718e44d207db803b257c6fedf158db17e0c", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/account-id-endpoint/index.d.ts": "3a028ff926f658320f332881e6e33de7b813d11ab327e429804c91604c1d3ffc", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/emitWarningIfUnsupportedVersion.d.ts": "d46bac45615fdca39460206c725a1eb4b27e9f4ed6642ef7db523bf9bcd356b0", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/index.browser.d.ts": "8abbac3a989c5c5c8828e661919cddc21aedbd343d04c6568effbd3d018a4da9", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/index.d.ts": "d7450900caea763b714bdc7ae50f6b23bdc657e14ae87398d06fbc4c4a412cde", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/index.native.d.ts": "89e79c8c875abd4f79cfce2c6e7cbefe227ae1edb3aa9b8c86662d3ae07d64e6", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/longPollMiddleware.d.ts": "814a4abc6f48c6b334d90e29302e471ac30ab6f6ab8fd44ad05be4284c4b869a", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-host-header/hostHeaderMiddleware.d.ts": "99dbd41c9643a5dacfd2269a418760c9d41e8e84d6c18d1d01aa0c76c815c4fa", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-logger/loggerMiddleware.d.ts": "1b53e32ccaf98206825098ba31e1dac260836d09f85781c2efc3b8b178aa145c", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-recursion-detection/configuration.d.ts": "0946d65cf65145f78e123433542f283838ddad1d27103495582fe70bc03ad8ca", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-recursion-detection/getRecursionDetectionPlugin.browser.d.ts": "b08e2ab375b6fed9476a09dba1edcf9b929b5d95d8c44fb88cc63295663e2233", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-recursion-detection/getRecursionDetectionPlugin.d.ts": "b08e2ab375b6fed9476a09dba1edcf9b929b5d95d8c44fb88cc63295663e2233", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-recursion-detection/recursionDetectionMiddleware.browser.d.ts": "88b92345b5913b0de8ca349c45656b25c7ee6a531063dfd841788ad837c7f0c9", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-recursion-detection/recursionDetectionMiddleware.d.ts": "88b92345b5913b0de8ca349c45656b25c7ee6a531063dfd841788ad837c7f0c9", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-recursion-detection/recursionDetectionMiddleware.native.d.ts": "88b92345b5913b0de8ca349c45656b25c7ee6a531063dfd841788ad837c7f0c9", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-user-agent/check-features.d.ts": "d1422719381893fba66de1f541117ac9ba86136ae98d37fc93684df24f04f8b3", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-user-agent/configurations.d.ts": "771d77da3c9a3cb9595e6d6ec339c0df6e2b722f02c69f0f66cc3160bb2b5bb7", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-user-agent/constants.d.ts": "b882a7a6184517f951624f6a0a0f6c2666528efa1768685cfcd781cbbe871fc9", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-user-agent/encode-features.d.ts": "a9d6a20c6ea2c75d8fcf6e46ed748f82f823dd0bc6efd0f7348c8b1ca1f7fedd", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/middleware-user-agent/user-agent-middleware.d.ts": "a7019c1729b065b5a6fa184cfd36dfdb2679dbb7a4bbf65dd96964a280dab46c", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/region-config-resolver/awsRegionConfig.d.ts": "e5df0b5e6a30db1d90144db183fea721ef063b21da5bbea499bdc8723aafaeb6", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/region-config-resolver/extensions.d.ts": "68698926e879115db76d6ce03154077763f8ebd35894a42642d0b9b4e17219b5", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/region-config-resolver/stsRegionDefaultResolver.browser.d.ts": "c9cfdac80bd5cb651bf39dda97f352ae31eafdc8f1ddc56dc58056c9d7fabb96", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/region-config-resolver/stsRegionDefaultResolver.d.ts": "aadb9edc4cb816ea36dd68af4374d28ec3c581959cf3d66de2a23559da69955a", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/region-config-resolver/stsRegionDefaultResolver.native.d.ts": "c9cfdac80bd5cb651bf39dda97f352ae31eafdc8f1ddc56dc58056c9d7fabb96", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/setCredentialFeature.d.ts": "e3605399df654c577c600ae9eb9daae3537c74456b4522786b514c716e178637", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/setFeature.d.ts": "b5873a7ca1f0ceb974742208c16756194b9da9d5e3673ff8dff31199324ca35d", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/setTokenFeature.d.ts": "0e3873d95eb79a5f159565948d39e76f5e9042d6ecab0fbf2f17d63dde8996f3", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/aws.d.ts": "00c2fc1775613d5dfb616b73ca1f9b70ca7f8576cc9394592f3755ec8bfab450", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/lib/aws/isVirtualHostableS3Bucket.d.ts": "70092b308e8212c65a15c25913971464a3520fe2ae02f235aba8d97a9528df9a", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/lib/aws/parseArn.d.ts": "f213f65191791e66a107473e36e658501659ff1166cf01549a5b7065da7c8eb9", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/lib/aws/partition.d.ts": "671c102d1415f7a05226f494b634f85e5e59712bbc713b5d81ef18a799a2b2e3", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/lib/aws/partitions.d.ts": "dec25ac16772b0c8b053b9f706cd85e7e7d7d1db8366f4e7d4106ab7f7fdc295", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/lib/isIpAddress.d.ts": "e778484929125e97d196b9ff73201fd609e81e2fba2e7c8a59d3dc8afcfbd4b3", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/resolveDefaultAwsRegionalEndpointsConfig.d.ts": "9ab88063a2070587f64cdac4f626623b37fc66630179196adfc980b01ed23464", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/resolveEndpoint.d.ts": "20b86895feeae4bbdac7d591a3a6bd0a9514857efb34424e47fe50c8876cfe93", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/types/EndpointError.d.ts": "03750d97874c868d7a1b43c03fb4d58c02721797a8a3bf819054397a3c1cdac4", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/types/EndpointRuleObject.d.ts": "228e8392860f4e418a95cb771d61889d1979863bed687527a7a138f33cacc90a", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/types/ErrorRuleObject.d.ts": "a3774fb25c2d4ae6b750926572dd31c6ded30eaaf3dbd34359a50a0469214479", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/types/RuleSetObject.d.ts": "015d7aa04a2843f2657af92c30a5fa51748c45812ec254d060875df157a34480", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/types/TreeRuleObject.d.ts": "67c650d7a4215f4f9ff9ef9a99fc4e2a8965fdc254d3b0e95b1df3e02a7d249d", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-endpoints/types/shared.d.ts": "3b3a3094f5aaa2e1377d030be5754cb4808a3c8e055f30fdb7f00b446c7accfb", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-user-agent-browser/configurations.d.ts": "e17b08e7b1a91949f922982bae81d41c34ab97900f76de766b4ace7a85db69d3", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-user-agent-browser/createUserAgentStringParsingProvider.d.ts": "7f599f1078be8fd79f3d6218eba4f4816b9d077e5553adb5ed7dfd0e5e10fad9", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-user-agent-browser/defaultUserAgent.browser.d.ts": "d7ed4d87c5c4b2d4583028fee4c8c17f987881e20d1de479f0436f6ccae2fd3d", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-user-agent-browser/defaultUserAgent.d.ts": "78d1a545c17991a84543fd90bb933a4d5b359fb68dd1624dc5b9910c4e7cf91b", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-user-agent-browser/defaultUserAgent.native.d.ts": "965d663f595bb64016135e583112f846e3a6e9be36f5965c270c1deb2711bf4d", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-user-agent-node/crt-availability.d.ts": "913106e866513017db1d44fec2f36d0da18b30190fd4c9c63d7b0ae39dd66e05", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-user-agent-node/defaultUserAgent.d.ts": "ed69b9202bede767342b9a46fc266f0e2214367d1fad9af986b96e6cee99f06c", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-user-agent-node/getRuntimeUserAgentPair.d.ts": "f035260b3a7b9451929074820410b46481bf05f090501163525d00394fc8ca74", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-user-agent-node/is-crt-available.d.ts": "130b3392ee1780fa12ceb5a2af1b0b484023b464d62a5ba6dd5355413d79a8e7", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/client/util-user-agent-node/nodeAppIdConfigOptions.d.ts": "fb3a8d54aad7081dbc55cc169b1a9cb92ffa943cea3f97a4f2acfae2ab4e890a", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/aws_sdk/AwsSdkSigV4ASigner.d.ts": "9c060c0704c516afdbee024456d3642f760cf7e75a3286c2c28c1ab9be78fee0", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/aws_sdk/AwsSdkSigV4Signer.d.ts": "4ca9ba9281dfd0d949811cdc43c25c672948a31dd129ded87de614715a54d473", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/aws_sdk/NODE_AUTH_SCHEME_PREFERENCE_OPTIONS.d.ts": "23e9031a505b06fe21fab305310e738adaa40cfca50705ae7255b87b8301a5cc", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/aws_sdk/clock-skew-defaults.browser.d.ts": "a354e5b37793ff0d7412798dd1426e4d8080ab90b0b24e988f5402dc5db54f2a", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/aws_sdk/clock-skew-defaults.d.ts": "75acb705156fc37063b1296717c5451e72586fb1b5e4977ceaa10b1276f1186d", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/aws_sdk/clock-skew-node-config.d.ts": "d1a99d035cc7b9696b791cd8b6200a939537888fa450bfaa95c404e27442673b", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/aws_sdk/index.d.ts": "1f346fdc6578b8db523b112b89edc3c497b6c4c4a262726a653e31a4fdb23052", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4AConfig.d.ts": "5dd5977dbdf893487c908e8662309546eec3f325353e191bb78dd1152b06c2b5", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/aws_sdk/resolveAwsSdkSigV4Config.d.ts": "870c6778f9354cb3a4a50698afc22bc1bfc7e2993e5b4830a775c2a6148e9261", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/index.browser.d.ts": "0cebafa30b05fd14e9752b6a78bd71e01a6711df09092833f5aa9991c5e6aa87", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/index.d.ts": "a8737961e5ff5253ac79d0961dbeaf20d590c21c64462a316d13f3bb0d57ede7", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/utils/getArrayForCommaSeparatedString.d.ts": "e10f49731145aa869b24f62613d9981cc5d7ee93bd7c52a183d79086e6df3de4", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/utils/getBearerTokenEnvKey.d.ts": "52f103ede8376df496ac111c4e1d6db9651b10f74ae47851a0b592e5702ddd0f", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/utils/getDateHeader.d.ts": "5cef91af4ac3537d8418327d83a6df9136185c6c12f4ba0e6d7ab909712eb5f6", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/utils/getSkewCorrectedDate.d.ts": "2a066f511da25d172f86befc42becf427695541070c03ab7104b76ced2911721", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/utils/getUpdatedSystemClockOffset.d.ts": "3ac7720ca5fb3782568c5bf0825865bdfe04a029b5baa6e3f4410bea787877c5", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/utils/index.d.ts": "4420801134aaf41391bce0f1423430cc39cb22e98ce62e8cc953cf90ff060949", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/httpAuthSchemes/utils/isClockSkewed.d.ts": "c0fcaa14d30359bc81b383082ff77cc4ed75154627fcf909414e2f0ec928e264", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/ConfigurableSerdeContext.d.ts": "36d3d8bf05f5d5c24427d5907e330894e2a5c539cada93747b7d4cc21285c11e", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/ProtocolLib.d.ts": "b4b9df2c4701e4fefac95278f40659d3b0c501b4eb5c7dab82ebb62e8183acce", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/UnionSerde.d.ts": "a612439d94574b3bf5a0307d736c7513d756e6610de5aea46d8dcaf096dfa7c4", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/cbor/AwsSmithyRpcV2CborProtocol.d.ts": "e6fee623e78fcabb52387a75e024b9d026e88c76bae55e9b8359a619487e420e", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/coercing-serializers.d.ts": "3c14f082d45303db214da9c9985bbeae60bfcfaa3c0987922f4f95f71010b6a1", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/common.d.ts": "b5c69d0ec148b85a1aa8f30c2e4e66fb54032a067a95b4c415d0015b0cd2012c", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/index.d.ts": "7fba236c59324b550464ea0283ac67497f4554cff001df1fab12f121e134d6eb", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/AwsJson1_0Protocol.d.ts": "5a62911e9fd81dc3fe285062754ed95e7a3dd5d7973352e1bdad751ceb30a53e", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/AwsJson1_1Protocol.d.ts": "6335d1ee547252420aa082a6a64980913e23c1163bad72ef0d59e4eda06535d3", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/AwsJsonRpcProtocol.d.ts": "7062eaf5e4027b3f7ec2bb2798394477490276fa25fdf8aa56bd88c0db212c6d", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/AwsRestJsonProtocol.d.ts": "7e13b65a90a453dd980b7b321fbf6cc3f18c1e71a2b7d6ce37fd5d9f75a20a1b", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/JsonSettings.d.ts": "8dc92297e1de11dddae7a28b513a3534e485a55a1db8b7f8bb7616f485449fb6", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/awsExpectUnion.d.ts": "8882f1a842bf979e755f5062b1c48e7882dbfe1155ef96a33ffc2c505bcebbb2", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/codec-v1/JsonCodec.d.ts": "d4b9d3cca65696410ae3be7cced07f8a4cc5f1e86e4c5ee4f961076de0f4ad84", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/codec-v1/JsonSettings.d.ts": "8dc92297e1de11dddae7a28b513a3534e485a55a1db8b7f8bb7616f485449fb6", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/codec-v1/JsonShapeDeserializer.d.ts": "cec2af04d44a961bd8a67292953b9d1c4e7ff05a7b16642e151505862c2f4906", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/codec-v1/JsonShapeSerializer.d.ts": "06c2dd2f29ab622d93bb99d9b875edacc948914abf5fe4dc61f96aa2a60eb309", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/codec-v2/JsonBytesStringAdapter.d.ts": "ca65bd1867f5d51e7a032b1dee49333078515af0ab3e385b259cf1c0a72bf627", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/codec-v2/JsonCodec2.d.ts": "721a6842d183c30e5a58811be2a1d0cf6094b3edd77985636c7bb10595c669fb", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/codec-v2/JsonShapeDeserializer2.d.ts": "3b772cc44628cef8b2738de35c6bf7df4497ce10c0a65823d363f926b74d6ff2", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/codec-v2/JsonShapeSerializer2.d.ts": "6b85ee6f452958102718cb55963f63041caee18b205bc660271f9baaa4cf2885", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/detectBufferParsing.d.ts": "b1ae70805b8d6d60b0ade7c7fc4aa5c014f8b220e33bd5a23ef01cc42748b5bf", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/jsonReplacer.d.ts": "215cc4b80522e14cd53f4d1db9431ca404f375de11a032d375603df655829a7a", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/jsonReviver.d.ts": "b3acf0b1bf2519df565eccf4646ba665ab77c12573cf7c796c8841a28dd8e2e8", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/needsReviver.d.ts": "f6a1d4447de9f55257f402c4faafdba094c6f7a28f684f79f3fa518ddff83615", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/parseJsonBody.d.ts": "cc4ce482b8a99321b9a58357c6c77ce43055f1f89c34f8000157d56b7a0e32dd", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/query/AwsEc2QueryProtocol.d.ts": "27be364cfb5434c203a054c4ce1c1ef7bc04937471d19767cdb65cde35df05d3", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/query/AwsQueryProtocol.d.ts": "1b3b90d8805863a2b098ebaf759445925fa6a9d4277396428161309bbe75119c", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/query/QuerySerializerSettings.d.ts": "1aa354cc42b9245061e63abd24ff0b404341dfca2295d27d2907fd14c97dae91", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/query/QueryShapeSerializer.d.ts": "42edf75ee5129686756da46191fcd4ddce9d68a837d4f6b8af66a9e735ad273f", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/writeKey.d.ts": "03e2c27e51cca461c838dc72b85861c1e0f9d0055fa35d555b42659cf14e9257", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/xml/AwsRestXmlProtocol.d.ts": "5f5110aa1b6dca63f799bb82996e7b3a4d34128778f5bb4367caf1547149a0f0", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/xml/XmlCodec.d.ts": "d1a3b40192bd7e5514362c9a885d651a2e3a14fb9f0fc27bc708dbe7cd6eeec4", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/xml/XmlShapeDeserializer.d.ts": "60018d3f7d7900c0826c7ac74023acf8e0fb031702dea581d97046b690df4aee", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/xml/XmlShapeSerializer.d.ts": "0098a4b99209b798337af9f30f39eb6fe16e54bad85301552d19f6fa3c765140", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/xml/parseXmlBody.d.ts": "51032105d77b0dcf24a447dc7ce2707b2baeac61b4de078226cc438f7530f1f9", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/xml/simpleFormatXml.d.ts": "213240b00cbb7dae96507d992c7147fe2e3b83a13b3ea282e57ca87da6109b3f", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/util/hasOwn.d.ts": "d718b3565aa26152218f20acae2315c94a9824a3e4646beae9e578490847f107", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/util/index.d.ts": "4d08d744188ff82f34db284024900d0e8519321dd529ee18d17e2da52963e996", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/util/util-arn-parser/arn.d.ts": "4885188341cc3364750f4a7ee676c1d5e5b431d6824d5649bcc4bee578bb18e9", + "node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/util/util-format-url/format-url.d.ts": "50d67c551ea8a3e1606d8e277d649461b71e6417641e235b558fa6c19561d82f", + "node_modules/@aws-sdk/core/httpAuthSchemes.d.ts": "cacf55badb6355cc34c09c34b9b359e07e6dcb4773313fbc9fd553ad27e7ca4b", + "node_modules/@aws-sdk/core/httpAuthSchemes.js": "877963083ffa339cd3d78b1c311b47dc6d10e867e4650ac73e80373ee34f19f6", + "node_modules/@aws-sdk/core/package.json": "f41e316f4145afaa611248576fac940f67e7d1e1c6a1cafa11c444189c98493d", + "node_modules/@aws-sdk/core/protocols.d.ts": "123ed03a3258ddfa73be39733bbf68983db34ca0a8392688d4efbd57100038cd", + "node_modules/@aws-sdk/core/protocols.js": "16a7b6cc1992ce81a549950c3d4618f97a9933f9d038a053511d922414b1550c", + "node_modules/@aws-sdk/core/util.d.ts": "493ce2fd70f0cf2c73760ef41c673137e648643e50f0e4fd4fb93fc6e5746406", + "node_modules/@aws-sdk/core/util.js": "2d2bdd24686d34946980b997bca833589b8d431d6c6fd520f9b86a8a0d6789d5", + "node_modules/@aws-sdk/credential-provider-env/LICENSE": "d82ceb8cbd004a2d4834085c557d55459f528e30268d95159ee1e4030a693a6e", + "node_modules/@aws-sdk/credential-provider-env/README.md": "acf116a09a3aa3975ee795cd7456aa18853de345fa25aa5ba070cc9698e96381", + "node_modules/@aws-sdk/credential-provider-env/dist-cjs/index.js": "d23e2c99a7f9bff60ecc65b297decd4c49736d2faffd33910fe67ba6799e98a6", + "node_modules/@aws-sdk/credential-provider-env/dist-es/fromEnv.js": "d883a9beca73792330be86dd3a0ed9d01a60845dc0c141186601deabc08fced7", + "node_modules/@aws-sdk/credential-provider-env/dist-es/index.js": "93d5517ba7447a1bdb391128ec6c75895bd5261fb53a1ce45ba6962417c2ff80", + "node_modules/@aws-sdk/credential-provider-env/dist-types/fromEnv.d.ts": "1a4126708fa7b625bb0c4b037d05aa6349639a4b6135fcd5cf3154c84e928eec", + "node_modules/@aws-sdk/credential-provider-env/dist-types/index.d.ts": "42b4e654004a1bb4ff767b193938e01db96f680e6d7d91a2919f387e9fbfac83", + "node_modules/@aws-sdk/credential-provider-env/dist-types/ts3.4/fromEnv.d.ts": "8b1c7f6a3922b5e05db0911be31f688b706330e1eda0dfac083d00a2cefa25fb", + "node_modules/@aws-sdk/credential-provider-env/dist-types/ts3.4/index.d.ts": "6a61708bbc0e732b6092917b0003516eee74cefdc5a434fe86176ced258adf7a", + "node_modules/@aws-sdk/credential-provider-env/package.json": "83cb069a705d40c4d8cac65c4df9f3094d64db8c3105cd15670d2676f730192c", + "node_modules/@aws-sdk/credential-provider-http/README.md": "27ef1d7518b40bb68d188dc925e6499fa5fc403e89b3c44eb8b61554980163e3", + "node_modules/@aws-sdk/credential-provider-http/dist-cjs/index.js": "ae4626cb8c8948c8c3f4deef29c4db037d2464adcfb88d63ed2eab39f21e9a29", + "node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/checkUrl.js": "1ad18e82cb8bb8077f68d48293ab26cc32380affb4983d429d55e69d4145ce7f", + "node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/fromHttp.browser.js": "a7c3d3f5fe6c77bc538ec4f518023c0a3bffcd74b6cfc6159e5bd7381830dd24", + "node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/fromHttp.js": "9c0bacfd54bf6abf4ffa15a566049b1d56b36abc446bb6b0a648e2f07f0710a7", + "node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/fromHttpTypes.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/requestHelpers.js": "dd96c950cc89cdc14b4dbf3a45048a8bbda2e218de5788d80a014b54fda3758f", + "node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/retry-wrapper.js": "63b28caeed958e4d45b8466d9c4b6b4ca08d8c18672c53b8db5e7b8cd29c017b", + "node_modules/@aws-sdk/credential-provider-http/dist-es/index.browser.js": "e31f3d67db5d35f94f1749981c3059a7099c5fe9174481d84a1b3cebca96479b", + "node_modules/@aws-sdk/credential-provider-http/dist-es/index.js": "e40119f17630fb71641e33a6641ccff62a9dba5e266c4c4d18f5af825fddfeeb", + "node_modules/@aws-sdk/credential-provider-http/dist-types/fromHttp/checkUrl.d.ts": "a1ccb5329d2f512d4cb9a6b31d360e7acfa4c5a1a432fa21bcd08dde95eb3d9d", + "node_modules/@aws-sdk/credential-provider-http/dist-types/fromHttp/fromHttp.browser.d.ts": "4b352232afde0bd01cfb1b96261019d5668037d32e0476c9ba013954643bb5fb", + "node_modules/@aws-sdk/credential-provider-http/dist-types/fromHttp/fromHttp.d.ts": "4b352232afde0bd01cfb1b96261019d5668037d32e0476c9ba013954643bb5fb", + "node_modules/@aws-sdk/credential-provider-http/dist-types/fromHttp/fromHttpTypes.d.ts": "c2b694d903303007a430acf66980f3b503dbd4e50cf6c183af0daf39c8d79ee8", + "node_modules/@aws-sdk/credential-provider-http/dist-types/fromHttp/requestHelpers.d.ts": "7b878a61af5bb072358d00f2d514c663356f5d007deb504940bddd971db30f5f", + "node_modules/@aws-sdk/credential-provider-http/dist-types/fromHttp/retry-wrapper.d.ts": "ae87125c52a859545013c540dcc4ebd5bcf023c6cf113f13a7bf0f7f037a53cb", + "node_modules/@aws-sdk/credential-provider-http/dist-types/index.browser.d.ts": "0af4db0f54bf37784977510ac0f4568cd73aac9be137874b70f77a8a5e7ce4b3", + "node_modules/@aws-sdk/credential-provider-http/dist-types/index.d.ts": "3c10545e61e9428bebdc65fed8d11e09d203afb4745eea11b98be7b218a2aeb1", + "node_modules/@aws-sdk/credential-provider-http/dist-types/ts3.4/fromHttp/checkUrl.d.ts": "59f7147a0a804f834d7f877fc053b1c3d7864bc75ba21243f33c785d5f2ba2d8", + "node_modules/@aws-sdk/credential-provider-http/dist-types/ts3.4/fromHttp/fromHttp.browser.d.ts": "a5f8de372e79763debe4664c210ff6e462fdf4bb66b1287de52324d0441092ee", + "node_modules/@aws-sdk/credential-provider-http/dist-types/ts3.4/fromHttp/fromHttp.d.ts": "a5f8de372e79763debe4664c210ff6e462fdf4bb66b1287de52324d0441092ee", + "node_modules/@aws-sdk/credential-provider-http/dist-types/ts3.4/fromHttp/fromHttpTypes.d.ts": "1a6aaa8441fdb29a8a6b1bfb890dfae92f815beda349a25e31aa4bc16f785261", + "node_modules/@aws-sdk/credential-provider-http/dist-types/ts3.4/fromHttp/requestHelpers.d.ts": "3a46ff83e52dcb027a641aa39606250acd2f7e7974ee05f755a7e242deef21fe", + "node_modules/@aws-sdk/credential-provider-http/dist-types/ts3.4/fromHttp/retry-wrapper.d.ts": "237a1c4849f267a2aa336711ef6f7badc8d098b910f447d7fcc5f5a052dc564a", + "node_modules/@aws-sdk/credential-provider-http/dist-types/ts3.4/index.browser.d.ts": "96c99f49b6b61c3adee1284279a6e1687583af92dd392e8bc361378fadc6cf0c", + "node_modules/@aws-sdk/credential-provider-http/dist-types/ts3.4/index.d.ts": "071c6d094f57146f8eed5b8ceffe9f617cffe5f4ea9565678ce3a0d567b88c17", + "node_modules/@aws-sdk/credential-provider-http/package.json": "6db5ed1c0c41ca8672b0eb663e539f87ed0deb7fc96d241ef766d2b441fe59a1", + "node_modules/@aws-sdk/credential-provider-ini/LICENSE": "d82ceb8cbd004a2d4834085c557d55459f528e30268d95159ee1e4030a693a6e", + "node_modules/@aws-sdk/credential-provider-ini/README.md": "f69d562b3b8d91d58e23e716efa255d57e6ee1422ab47d029de6bcf03df13593", + "node_modules/@aws-sdk/credential-provider-ini/dist-cjs/index.js": "2d975f8114b17e1e96eea2735c7271f735f08bab35dc922384cd15735bbdd085", + "node_modules/@aws-sdk/credential-provider-ini/dist-es/fromIni.js": "ef022c5bbc6e9709003c4ae021981bc9102ed1b309f87ee49d4b0cd0a8d2a6a6", + "node_modules/@aws-sdk/credential-provider-ini/dist-es/index.js": "9e8f91a918aa0d14ccca9cf92882ebfb9462a2b2b7c86fa9b001180767959425", + "node_modules/@aws-sdk/credential-provider-ini/dist-es/resolveAssumeRoleCredentials.js": "271168e57ec98ca143f04e587c4668c631f87cf34d38b8c27be0659c0fbdb37e", + "node_modules/@aws-sdk/credential-provider-ini/dist-es/resolveCredentialSource.js": "5c411b4b4a8b82c4c043a517e13adc9f330825ab41ec6a492af55e4b342c1cf6", + "node_modules/@aws-sdk/credential-provider-ini/dist-es/resolveLoginCredentials.js": "80d3e7451a4148f02e52fa1d849b59791dd6585bc3ebc5e60b5f7dda74a48da1", + "node_modules/@aws-sdk/credential-provider-ini/dist-es/resolveProcessCredentials.js": "372c866d99a74db4ce629fa7c93f89966a0af8049b8515f4bf647da5fd9dd4fe", + "node_modules/@aws-sdk/credential-provider-ini/dist-es/resolveProfileData.js": "9e65315d68cd12eac1c90a04149e9267a9f6a2c4b72f48cae26a53d657c2a779", + "node_modules/@aws-sdk/credential-provider-ini/dist-es/resolveSsoCredentials.js": "a92d7c7e4a2a255e4d7007472050c18654046d7541b19f0c63a771e2cf714ad6", + "node_modules/@aws-sdk/credential-provider-ini/dist-es/resolveStaticCredentials.js": "57aba34a703deb3840eaf0a0b89ebf1ba36ca1ab1c81bc54ac42d59e43bad1b1", + "node_modules/@aws-sdk/credential-provider-ini/dist-es/resolveWebIdentityCredentials.js": "df9d3a9c097f1a2c74f9e75992072fed9cc5942c9edd483b9a01ead1f4102aaf", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/fromIni.d.ts": "97a83335aa082ad4890206badcc5e82f64cebda120d27e8a19d2efbf396f3cd6", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/index.d.ts": "2b63aaf0ba775bcc1153768373ede7520c7026eadecaede41c1fb95fb9c79d0d", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/resolveAssumeRoleCredentials.d.ts": "faba4469706c2599fe964327c330359536550a9484061cfa7c4ed5ee999aced8", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/resolveCredentialSource.d.ts": "aac22a15723bab219d881c95eb4a4d0b7be00f6c186f6c88eff150bf6ddc3a03", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/resolveLoginCredentials.d.ts": "1352ad8c551cda9dc9f99be9cc749d386f59aa7ea8578dfb260c0b9f566631fa", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/resolveProcessCredentials.d.ts": "f913b6bfa8094a96cebe7b9b3ef6904fbca6a3467e2b6138d050eeff06e1886a", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/resolveProfileData.d.ts": "2be48efad0a1c5c57c4d0a0574ed2672d414aab7d93a6a6177761fb0df788454", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/resolveSsoCredentials.d.ts": "72bb83972d006eda0472eb0101e5e9d656bfd3792a63d4a373b17c515f4d9c0e", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/resolveStaticCredentials.d.ts": "c1399497143d53ebbdddaa8cb1f6fb38e2717896bc4cd9a464512b3cc72051a8", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/resolveWebIdentityCredentials.d.ts": "e3a9b7f0f088e1f527fe60965e94cd2bbf9372874d9732543c12b72054062fca", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/ts3.4/fromIni.d.ts": "b31da1beb3359d88f9bcfa14c69178bfd5bce8776ae85682499339215d555ada", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/ts3.4/index.d.ts": "7419469911a8fe2b19388f98310cbabc2662acc98baf93aaa943b9498bdcf157", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/ts3.4/resolveAssumeRoleCredentials.d.ts": "ef5ef61d7b5a6daa7e23e172341b7e75af2c77d4b796a0f9e873b69eeac6c7ac", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/ts3.4/resolveCredentialSource.d.ts": "eb7a4df167242f78a4690fb0dac9c392a9b0a34ee6d55d3b714e4911e8f048bd", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/ts3.4/resolveLoginCredentials.d.ts": "6ab478bbd2d7531d3eee038c1e2bc8c9af0f49ba02fd1275032cf4371b0adbda", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/ts3.4/resolveProcessCredentials.d.ts": "e199b0ab2f3ba75a05aff9d5dbe956b3eb28b7f950b9b3a027274834c0552034", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/ts3.4/resolveProfileData.d.ts": "efbc42ddcf7ec2622bdb2634992ebc8bcfc8a3ea78016e20e9825654117baefa", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/ts3.4/resolveSsoCredentials.d.ts": "8602fa885ce7bd5d8d7cdaad0c7e50ea87f3c2501a8f1dec11983e9d40477855", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/ts3.4/resolveStaticCredentials.d.ts": "274d51e4c35409f8dc92d4327d05df0a8295f14a52d34ad870b6d3ba3ad64390", + "node_modules/@aws-sdk/credential-provider-ini/dist-types/ts3.4/resolveWebIdentityCredentials.d.ts": "44b93b3127ebac02d970d1a56cdf6959406ea780f30017ca0bec2aa1dc0d1f42", + "node_modules/@aws-sdk/credential-provider-ini/package.json": "825c4827279fad1a15aa72a365f4f91557305259771ab2216682c4f24690c075", + "node_modules/@aws-sdk/credential-provider-login/README.md": "a33261115ded62f80ada337b9ed2193392837a6dd49d3ce89ec81fc48fbe33fd", + "node_modules/@aws-sdk/credential-provider-login/dist-cjs/index.js": "3ada1d4c0fbc6d888cee050b741348fc66ba455b5624203816e408ab5470e910", + "node_modules/@aws-sdk/credential-provider-login/dist-es/LoginCredentialsFetcher.js": "67622b1afbf4b8f5898348abe49a9d087cbda65cf41d8df300d699a851c2ab80", + "node_modules/@aws-sdk/credential-provider-login/dist-es/fromLoginCredentials.js": "039f16925af27d4d2d6c33396358658d474433899e146b136ae89beb0d99a15d", + "node_modules/@aws-sdk/credential-provider-login/dist-es/index.js": "32f61d849774b52227e75204e63edeb0ac2e66c41004501ba5ce1cc2305a5869", + "node_modules/@aws-sdk/credential-provider-login/dist-es/types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/credential-provider-login/dist-types/LoginCredentialsFetcher.d.ts": "1090f0217f35916ab10f11a5e26cfe23a4241357d8eef40d4f94be941c622e11", + "node_modules/@aws-sdk/credential-provider-login/dist-types/fromLoginCredentials.d.ts": "6e7c0bbb5440b5759be26355ade8e4fa84bd2e4fcd800087b0bc0485a219e48e", + "node_modules/@aws-sdk/credential-provider-login/dist-types/index.d.ts": "6fa03973e4c96e13e46869883b7cab0d5bb2dc02e2808e05e55baeec93d7d650", + "node_modules/@aws-sdk/credential-provider-login/dist-types/ts3.4/LoginCredentialsFetcher.d.ts": "3314d996d96bafd753fc071028a01bafe9c188eb706aa506c3a6a32fb107cc78", + "node_modules/@aws-sdk/credential-provider-login/dist-types/ts3.4/fromLoginCredentials.d.ts": "7b5380403248a6e631031bcee2a240ab9bc8bd7e0ef78616b6008a693f211a49", + "node_modules/@aws-sdk/credential-provider-login/dist-types/ts3.4/index.d.ts": "068614fdfed65a21e4224c5e0d5574f7836064c5d29c5afd3566995fb434f2ee", + "node_modules/@aws-sdk/credential-provider-login/dist-types/ts3.4/types.d.ts": "638bbf805a1cbedb63f3465d3032a717d65f580f1fd3a6efe7d6e5d926c179d8", + "node_modules/@aws-sdk/credential-provider-login/dist-types/types.d.ts": "e0f2a44e9312a8be92922f1ad9f33418437cac2ca6a815e732d81a0ed2f2ffba", + "node_modules/@aws-sdk/credential-provider-login/package.json": "5993af12213692c03386b8af68d3894c007c87fcbee64356743668aa9ea39696", + "node_modules/@aws-sdk/credential-provider-node/LICENSE": "d82ceb8cbd004a2d4834085c557d55459f528e30268d95159ee1e4030a693a6e", + "node_modules/@aws-sdk/credential-provider-node/README.md": "4ad36cdd50d6d563560e3104a219c1f21f8d31e64ebd4339dfedebae4b52c848", + "node_modules/@aws-sdk/credential-provider-node/dist-cjs/index.js": "afd158eca6193ed8d8a469013790b9ffa3bdcdea7f6116efda93705f67c6335c", + "node_modules/@aws-sdk/credential-provider-node/dist-es/defaultProvider.js": "f459d07b8f2c2e3173b3e0e65c9d50f094969ca2a81ee8fc31ef23512b41b37f", + "node_modules/@aws-sdk/credential-provider-node/dist-es/index.js": "a952aa83f0c61f1c5399518dc09fe095e793bcd0d93d5e5316e97c8a68f24e25", + "node_modules/@aws-sdk/credential-provider-node/dist-es/remoteProvider.js": "ede419ea0067b315213fae13096b7a69e25cf0aff9aa5ac12d7e90558eaaeba3", + "node_modules/@aws-sdk/credential-provider-node/dist-es/runtime/memoize-chain.js": "116eaa3d9735a0ea546b1803e15d8e795d1e1da4b33811ecb44d56e252e61505", + "node_modules/@aws-sdk/credential-provider-node/dist-types/defaultProvider.d.ts": "12d19d80020e59241ebe663fa68184bbc98bbc0806956b59bd74d11a85ad3bbe", + "node_modules/@aws-sdk/credential-provider-node/dist-types/index.d.ts": "ede9c12b3832f320a96463c957f1cf1a0fd7d2576be6f22c3af6bcd8a45b01b6", + "node_modules/@aws-sdk/credential-provider-node/dist-types/remoteProvider.d.ts": "e8d9d7ac5d15adecb9a303e12d9f6e9deffac2786195f917e7457b5dd807e87e", + "node_modules/@aws-sdk/credential-provider-node/dist-types/runtime/memoize-chain.d.ts": "1a403bf35307aac853f2f50967b6d44f9ed0c1305368f9592cb6eb703a3384ee", + "node_modules/@aws-sdk/credential-provider-node/dist-types/ts3.4/defaultProvider.d.ts": "d0871e79b3fa9bc3d12bf6f397404a8a4d149bc276c9d445390e203194d33353", + "node_modules/@aws-sdk/credential-provider-node/dist-types/ts3.4/index.d.ts": "f04ea5292b952b33033f338e479ff2f296b121bbac1b70fe86821eca15da7770", + "node_modules/@aws-sdk/credential-provider-node/dist-types/ts3.4/remoteProvider.d.ts": "9b72c57b4126c7c3fe2720756548189bd76d0034d5b7efd02157d8fe8c887bb8", + "node_modules/@aws-sdk/credential-provider-node/dist-types/ts3.4/runtime/memoize-chain.d.ts": "cdef12164e0f374ad147747d2dd3b2196f28110040df3d47d6a7a60c594d9e18", + "node_modules/@aws-sdk/credential-provider-node/package.json": "d107f130cf4631f0b687351fd8d2baf74d8a8496f28a0bd463545306ba9e6f22", + "node_modules/@aws-sdk/credential-provider-process/LICENSE": "2ef1e48ea743d9c82313e6887858f7c1feaddd636f5a3f43926aab0143e67ebd", + "node_modules/@aws-sdk/credential-provider-process/README.md": "3ed5662964cc0015c29e25f4c63ab9031e2a20d1d9c285f0f21459ac3fe3e189", + "node_modules/@aws-sdk/credential-provider-process/dist-cjs/index.js": "459dccd69ac50b0cf33d00398dd6b18dec18521d8ed6bc359121e1a96115ea4d", + "node_modules/@aws-sdk/credential-provider-process/dist-es/ProcessCredentials.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/credential-provider-process/dist-es/fromProcess.js": "e86e669e5646f23b161a06e906564ae280a590850a010ef78c3baf95d5150ce8", + "node_modules/@aws-sdk/credential-provider-process/dist-es/getValidatedProcessCredentials.js": "8d9499217114f32a52b29543109e49684f5e4d7ef119ce8c97b285fc1bfd3924", + "node_modules/@aws-sdk/credential-provider-process/dist-es/index.js": "8b9ed35009ec07695df645de1a852e5530a9b741f5719378159f5852d821ee2a", + "node_modules/@aws-sdk/credential-provider-process/dist-es/resolveProcessCredentials.js": "ee165a3a39ad93272d90884dbb15deef91a012b7fd7f6e7edd208007c8346173", + "node_modules/@aws-sdk/credential-provider-process/dist-types/ProcessCredentials.d.ts": "48a409d7cfafa44f6b5a3c66c823639aeb9df5a0abc3d4f22f061fd554bc4bfe", + "node_modules/@aws-sdk/credential-provider-process/dist-types/fromProcess.d.ts": "0bb4b80c3920ee73ef78758d8d9e96c278ab9729d43afda65d74f27cf7b14971", + "node_modules/@aws-sdk/credential-provider-process/dist-types/getValidatedProcessCredentials.d.ts": "aee7f389961a5d9ea3418439619a2cae4f9f651caa2af807a5517ac592b754ea", + "node_modules/@aws-sdk/credential-provider-process/dist-types/index.d.ts": "0a374428bffa44c96ea86fcc1021bef8884e636bccdec35c974ba672c254ebf5", + "node_modules/@aws-sdk/credential-provider-process/dist-types/resolveProcessCredentials.d.ts": "4b680c9e1dbdfc364e18bc5f42106176e5c02e4205c56d63bd80d30e131a95f4", + "node_modules/@aws-sdk/credential-provider-process/dist-types/ts3.4/ProcessCredentials.d.ts": "b72cfa521eaa100942b8afce631341f39b8fc081d60edf3f4d03c2b8724da389", + "node_modules/@aws-sdk/credential-provider-process/dist-types/ts3.4/fromProcess.d.ts": "837175de0eeba569ce46ce63d79ac35ab3c7a1d4f499f51c6cb9213dbb37f9ac", + "node_modules/@aws-sdk/credential-provider-process/dist-types/ts3.4/getValidatedProcessCredentials.d.ts": "d36f6d85c1f3b9a6f1c194f0fa90ff487bc028647fd95e91e17176f3ce9f8df0", + "node_modules/@aws-sdk/credential-provider-process/dist-types/ts3.4/index.d.ts": "08109c1231aa1d7d90f940937ba7e3c604840db0d1ff95ac02aa81ced866021c", + "node_modules/@aws-sdk/credential-provider-process/dist-types/ts3.4/resolveProcessCredentials.d.ts": "58ff948665408fce19c3e98747e83277319cfff3c59b34eadcda09a9c3f2f29b", + "node_modules/@aws-sdk/credential-provider-process/package.json": "37354078bd4f4ffcaeccfafe9e818b4dd298f4685fcbd6695c13437ff2e2360b", + "node_modules/@aws-sdk/credential-provider-sso/LICENSE": "2ef1e48ea743d9c82313e6887858f7c1feaddd636f5a3f43926aab0143e67ebd", + "node_modules/@aws-sdk/credential-provider-sso/README.md": "b99f47f4717db2554141f8713df790b58c283e41a2404fe1e36db7fc5efa2c6e", + "node_modules/@aws-sdk/credential-provider-sso/dist-cjs/index.js": "cc75ada3cbe32e31cca407a81ee5f2c2e81a36134d4f8fec50c898110a60f772", + "node_modules/@aws-sdk/credential-provider-sso/dist-cjs/loadSso-BGYXHf8s.js": "eaf5712dd8b7f2c9c19a5380ab15ada26f393a8ac6f5803fe7051763aeae0c3e", + "node_modules/@aws-sdk/credential-provider-sso/dist-es/fromSSO.js": "119dbcb4b8212192d97f06c2b0f648814ed4a23d9d369760b28692df7301dc59", + "node_modules/@aws-sdk/credential-provider-sso/dist-es/index.js": "7100a5160ace589eb7a1a9200aa7742c926ecf0ee1d281ff3925386502db2f9d", + "node_modules/@aws-sdk/credential-provider-sso/dist-es/isSsoProfile.js": "6766311b6d87224ffa71f1179464a9f2204e25b8e21f1b0f6db3e82131c26f3b", + "node_modules/@aws-sdk/credential-provider-sso/dist-es/loadSso.js": "abfd2adec704d2cfccd60685e21b5b5af3552df86b199021c6f7f83051f0fc25", + "node_modules/@aws-sdk/credential-provider-sso/dist-es/resolveSSOCredentials.js": "ca08aab28a8c05347cded973ff2f2f7a819894a4bccc38f130f9af56a74c7331", + "node_modules/@aws-sdk/credential-provider-sso/dist-es/types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/credential-provider-sso/dist-es/validateSsoProfile.js": "f5992053e3ded5534296a9c703480a94e5c1800937db718a461e9c69bc5b0020", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/fromSSO.d.ts": "6c0ed1e75dec7a5bec1ab4213992706b349a033dd168b8591907f2036bc48508", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/index.d.ts": "61edec648e343fb531db6a0448267601b605ed403753d21d64ba2606f2f50e0b", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/isSsoProfile.d.ts": "3e9a4ba4194d74ccedc1ee63d704b166df18489a782d6bd8093cea1ad3189cce", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/loadSso.d.ts": "a9f21d07919f56f9c007a85f2521e656c003051fc58fdf18a53b01a98bdc6fb9", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/resolveSSOCredentials.d.ts": "828d48dd31a1bd00a984f99cdbb78884dfffc35e9fff519066d5c36937da6097", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/ts3.4/fromSSO.d.ts": "1da169a29b564914d9d2f143a4d3e03aace24636aee3cb3cae6e67a24c45a5bd", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/ts3.4/index.d.ts": "0a3a51179404142d531b5e5f5db7c8fb7ba3f66e7da776ddf9758ddd4d744eb5", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/ts3.4/isSsoProfile.d.ts": "e32eaef21d2925ffeb01ce64ba304b73bae53ad3a5b6e8a29a2787630934fb03", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/ts3.4/loadSso.d.ts": "6cce93e1f0ae83609fcb72e6bc2bee3f6a70276710216c12762496e43ad2394f", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/ts3.4/resolveSSOCredentials.d.ts": "0cab502871d9e1bd4d9988f5098e7a5c5fc69739be160f4a37766fc613efa56e", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/ts3.4/types.d.ts": "f6b9068bdaad8e2a93b577fc2812c0f2467835d237b8c6342e7e113f6dd5efad", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/ts3.4/validateSsoProfile.d.ts": "7cb212911f42953697ec77f9ed88f7f3a21ec6206a646b1367dd6b4a4e21e082", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/types.d.ts": "c25f09e41b08dda6c69b2044dccd68ddd7efd8cf2a3bbb7253164be0f0b500da", + "node_modules/@aws-sdk/credential-provider-sso/dist-types/validateSsoProfile.d.ts": "6f50339450a12de55b70a004d12dc0eca00c23decb873c833a29b64851611578", + "node_modules/@aws-sdk/credential-provider-sso/package.json": "417af24392691fbaa2023130328fe065688b74e4f69a2dbd96319242f86621c9", + "node_modules/@aws-sdk/credential-provider-web-identity/LICENSE": "2ef1e48ea743d9c82313e6887858f7c1feaddd636f5a3f43926aab0143e67ebd", + "node_modules/@aws-sdk/credential-provider-web-identity/README.md": "8f01e1b246f28a2532f8e6e869a4d908799ab85fce9b1a01ae477a8ada8b2de2", + "node_modules/@aws-sdk/credential-provider-web-identity/dist-cjs/index.js": "664792ccf95de183d35f270b09a5b4e1deb924c5d665e40d7a92dfd90c7c0af6", + "node_modules/@aws-sdk/credential-provider-web-identity/dist-es/fromTokenFile.js": "a7228913b62cb5ab20e058b4ff61e8d67b3d156eae91ccb5d2e88c4e2f305464", + "node_modules/@aws-sdk/credential-provider-web-identity/dist-es/fromWebToken.js": "429955288b31cb7c68845dc62973914ac01b8d2ad2115a057f1660ba9e6cb27e", + "node_modules/@aws-sdk/credential-provider-web-identity/dist-es/index.js": "aef6a2cfbd9b8ecf36ea0d6db870c6daa8dae84295ef02c29dec19ef2f742eef", + "node_modules/@aws-sdk/credential-provider-web-identity/dist-types/fromTokenFile.d.ts": "c41892c5315b6e9890cd192f5c4049eee26d77836be86e2c60b7f2f888b6d4a5", + "node_modules/@aws-sdk/credential-provider-web-identity/dist-types/fromWebToken.d.ts": "03c1188cf2d92e1b72e7b21082ec81782aa20e9cf0ba9187ec8d8dd60797afef", + "node_modules/@aws-sdk/credential-provider-web-identity/dist-types/index.d.ts": "bdc9a0d1f2e50a9c575a42c4d1b9e82ffe6f4f841f1f0bdb11f3d36a5b298341", + "node_modules/@aws-sdk/credential-provider-web-identity/dist-types/ts3.4/fromTokenFile.d.ts": "0bda129d5b074d34fa14fac907ce86f46cc03ef067567646e2f949b5a9460623", + "node_modules/@aws-sdk/credential-provider-web-identity/dist-types/ts3.4/fromWebToken.d.ts": "0ad89b38cbc5d02a428c5eb005c569dad813f0be91e2bed6315ba347f8ce12cd", + "node_modules/@aws-sdk/credential-provider-web-identity/dist-types/ts3.4/index.d.ts": "f77238878e20fe06caad33c2b7979177a4134a31fd4fbd104fd24b3c75ad79df", + "node_modules/@aws-sdk/credential-provider-web-identity/package.json": "f7b5562bbccef5b2387e5b282a7edaf1645da0cab95f2855dc33e8f38c4189af", + "node_modules/@aws-sdk/dynamodb-codec/LICENSE": "d82ceb8cbd004a2d4834085c557d55459f528e30268d95159ee1e4030a693a6e", + "node_modules/@aws-sdk/dynamodb-codec/README.md": "def6677f97829066b65f885d757c5022b10249ee63e8305ad73bef97e2865207", + "node_modules/@aws-sdk/dynamodb-codec/dist-cjs/index.js": "cdeb5aeb56509c5299da7b7b9f60f1e0e410ba18d3cc0722704f7b4cf64d084a", + "node_modules/@aws-sdk/dynamodb-codec/dist-es/codec-v1/DynamoDBJsonCodec.js": "37e139174bb589644bbfbf704062ea75546a22c0e5958aa50bd333fad6f35e42", + "node_modules/@aws-sdk/dynamodb-codec/dist-es/codec-v2/DynamoDBJsonCodec2.js": "756a7a1e09ea0b1382e251b659e65cf3525e9cdfc3a6fec8ec68b75079028313", + "node_modules/@aws-sdk/dynamodb-codec/dist-es/index.js": "e2828a1e21bf6c5580243c5e49974f30f5b25ae667e29f529587d45cc9dec29b", + "node_modules/@aws-sdk/dynamodb-codec/dist-types/codec-v1/DynamoDBJsonCodec.d.ts": "7bea54661c2903ab90ee790c3d1d86a6fe2577a23171e7a198bc2e3795f81c70", + "node_modules/@aws-sdk/dynamodb-codec/dist-types/codec-v2/DynamoDBJsonCodec2.d.ts": "844eadb39c294aabb8fc6d73b0b42a5a5ca0f4b43355aca3588272ae7a4b5ffa", + "node_modules/@aws-sdk/dynamodb-codec/dist-types/index.d.ts": "e2828a1e21bf6c5580243c5e49974f30f5b25ae667e29f529587d45cc9dec29b", + "node_modules/@aws-sdk/dynamodb-codec/dist-types/ts3.4/codec-v1/DynamoDBJsonCodec.d.ts": "ad7f31a4ed80a6f93d469c5d6c0a1b92d31931a4dd94cd3e7c2e4cc39b3a27f5", + "node_modules/@aws-sdk/dynamodb-codec/dist-types/ts3.4/codec-v2/DynamoDBJsonCodec2.d.ts": "bcc662cc76a089b19f7341b14887c46d475944ab20bacbaff934eff29484d33d", + "node_modules/@aws-sdk/dynamodb-codec/dist-types/ts3.4/index.d.ts": "e2828a1e21bf6c5580243c5e49974f30f5b25ae667e29f529587d45cc9dec29b", + "node_modules/@aws-sdk/dynamodb-codec/package.json": "c2b3533adeaff02d3fe72862b3af02ade8637c30688eba5f14f6cb78e5c1ad3c", + "node_modules/@aws-sdk/endpoint-cache/LICENSE": "b50925b3e63e2192e04323a29e905154bda999c8dcc4c78e96733a00a10fed9a", + "node_modules/@aws-sdk/endpoint-cache/README.md": "443dbe818562457e6ff1075cee6ee8ebbb5008da490c6290f07c19d9b6fd4a75", + "node_modules/@aws-sdk/endpoint-cache/dist-cjs/index.js": "e9cff48797863efda153dbd8ab96e35debf2f7df75702b2f4d59c036ad9798ee", + "node_modules/@aws-sdk/endpoint-cache/dist-es/Endpoint.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/endpoint-cache/dist-es/EndpointCache.js": "725c1b5ac2d5d83dbfe93629f865d157e047dc0e6b5bc4673109a59568185860", + "node_modules/@aws-sdk/endpoint-cache/dist-es/index.js": "c7ff29245ddcb6a70b5f2410a372d3f846cf326ab5a0119c03f69e476b6acc94", + "node_modules/@aws-sdk/endpoint-cache/dist-types/Endpoint.d.ts": "1422b6b55f4a5376d4b405713ed418353587131b12e3857eb5d68a01cba929e2", + "node_modules/@aws-sdk/endpoint-cache/dist-types/EndpointCache.d.ts": "ba6111959356c14abad1d9c481509008c12a8ca328d73117a7beec6d917164cf", + "node_modules/@aws-sdk/endpoint-cache/dist-types/index.d.ts": "fae569759406998b49339a7bcddfdc56a98779cfa8484db4f2b4a985933b237c", + "node_modules/@aws-sdk/endpoint-cache/dist-types/ts3.4/Endpoint.d.ts": "f5bc93d0d555676e07302ce94f573249262098c615a1ea3f36b6210006a313e7", + "node_modules/@aws-sdk/endpoint-cache/dist-types/ts3.4/EndpointCache.d.ts": "d9220de6fa61d4da18ed03219fe972138ca38215ba77375001219295da03c343", + "node_modules/@aws-sdk/endpoint-cache/dist-types/ts3.4/index.d.ts": "8ec0af034ac899e4be6ff06d58de4b8162fc608d38e12b408044a45f7f7b000c", + "node_modules/@aws-sdk/endpoint-cache/package.json": "35a59d208ec5a988a459eac203b993f9c8b44c36a35c76d68a58eb1b4b731447", + "node_modules/@aws-sdk/middleware-endpoint-discovery/LICENSE": "e345c2ee7df4446e739de7b91535f0c10ddcd909b9753889e943ee637b05db8c", + "node_modules/@aws-sdk/middleware-endpoint-discovery/README.md": "fd53b246ce9e8d126ae403579e7d97780fa64f25a4f4ac358c78af8e57405d8c", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-cjs/index.js": "dd024c026ea19e89273c213047c20a6b8344776ad6f12cdefcb660040e712954", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-es/configurations.js": "82969a27feaf256d5627d14a5804a21ab55d98c48d74390ff110e339927219d0", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-es/endpointDiscoveryMiddleware.js": "77aa20cf484ce3c343884b464e6a42000234375955eb1710fceb14ca3fb5aa31", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-es/getCacheKey.js": "a147b91be10685a1324a391ca5a11deea21c1866dd0d192c5ab106a3e43484c8", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-es/getEndpointDiscoveryPlugin.js": "02a8793c644d79295accaa97a6869c84aa33bf484ac4b8ceff3322fa53e77d4e", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-es/index.js": "72f0d23de5530dcb3c9d9f58c3197fd569c718794dee07bd17bab78af257dcec", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-es/resolveEndpointDiscoveryConfig.js": "ef7764088092f26f12444667996125505bf3636cc4c5122c8c9e63eee24b467e", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-es/updateDiscoveredEndpointInCache.js": "6625c7820146edba6e88fcdfb0dbcf5af242af8de221448da52a6e1b4e1a9b76", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/configurations.d.ts": "f3c27013a4603ec30757c38f7e17c01ce0e150378a6334bf93c346f6a1a4695e", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/endpointDiscoveryMiddleware.d.ts": "51716207ce155ac2174ad12008416bd8721f674b19d5e1d7c802ff780e755b83", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/getCacheKey.d.ts": "dbc1d3ea513031a49bdf3ef6d6172919d81e4235c88975c9d7ff649a01204db5", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/getEndpointDiscoveryPlugin.d.ts": "b83dd20a7413b3bb7448da4e571f2b5f02a85b0d8fc46c40afd7182d3137f147", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/index.d.ts": "cb8b68d147cf2fffb962b774366a7d2cfb4385de2078b24c73a4c6ebe471ba26", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/resolveEndpointDiscoveryConfig.d.ts": "52fa6e291e30f0390897e7226a6acb9b15846c66f05a1796b9942433ca5ad7a9", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/ts3.4/configurations.d.ts": "d23d137de22c43936ed1ddf02df834ceae8cf1cfc7e065450d10da67543e036f", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/ts3.4/endpointDiscoveryMiddleware.d.ts": "c2cf17bc448326f461024929f787c6016cb7c93463b002fe833f5fa0c2bb01ce", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/ts3.4/getCacheKey.d.ts": "4b3ffd694d4139f26b2ec536fdefa9f24dcbb861240df336c865ae185548d2d4", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/ts3.4/getEndpointDiscoveryPlugin.d.ts": "e1f2e37b4a927d421cf8dc82d011ec18a037fbf95551835debda7ce1448aed10", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/ts3.4/index.d.ts": "ba5c48b27c2d153aa968995de18fac8ba2943ec204c9fdb323dc38863f58788d", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/ts3.4/resolveEndpointDiscoveryConfig.d.ts": "f4e9a1f2c0d35012fceac92b589d0aec5911cd38b7e2cf7f4e7467167343a08d", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/ts3.4/updateDiscoveredEndpointInCache.d.ts": "5c93df08679b337cffbf6e392dc04d99f262ab0b96819ba0a69027982d8d5b2f", + "node_modules/@aws-sdk/middleware-endpoint-discovery/dist-types/updateDiscoveredEndpointInCache.d.ts": "2e73c933f53d73d37c76943609c679c71409ea1a7b5705cbec3c348bddf1b154", + "node_modules/@aws-sdk/middleware-endpoint-discovery/package.json": "aaf45b4f5442bc5c456d72a1af750113c453edd8a6a19d2303c961ccbdb9c533", + "node_modules/@aws-sdk/nested-clients/README.md": "92cdad5ce2f6c085d7b65ee8c701340a75799ae6045f1b526b49a28caad27c30", + "node_modules/@aws-sdk/nested-clients/cognito-identity.d.ts": "0ba5ee7f9f44f9d4eac4d772a412f1b22ff9e6b98258c32463f019d5044f4d5d", + "node_modules/@aws-sdk/nested-clients/cognito-identity.js": "3469dc002e06eb54f1aadc57dd7ef98d0c849724de9821a61e217b775493e7c1", + "node_modules/@aws-sdk/nested-clients/dist-cjs/index.js": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js": "120d8b80bf26743d1dac7f34f345f90fcd22d6a487c4e39776a0bca3a8f49fe8", + "node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js": "4ba11422063637399e062ba47ecaedb49e05b142f306ce5008bdb008968af0cb", + "node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js": "0b59f7abbd2219b9fc29d8a145df0a6f83c566dda215e2e4043534622c88a3fb", + "node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js": "31b3cb081ad4bd3a53c164fd6e298b651258539aae2ad1371bb27917903942f2", + "node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js": "c60895be15668e7429cd1eeed090fd82a8a3e1c7b24461e3bffea2778b4879b6", + "node_modules/@aws-sdk/nested-clients/dist-es/index.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/CognitoIdentity.js": "1e71106d99bc8787cb298d81a6ebd45ef71c1169ddcbc36d0e649f8835b6d4d6", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/CognitoIdentityClient.js": "a7a315a303a1ddcf0cf3e4416a1b1368db7e47b995543cf3b5c6b9ddd978fdac", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/auth/httpAuthExtensionConfiguration.js": "331902cc5df5060ac9644261de8711ff641d6b91e60b5fcf77d2facc441c8cf6", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/auth/httpAuthSchemeProvider.js": "8932742b7d3cd766b63e50a64b2af1af5764e6214e059522af9a20e834446fe5", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/commandBuilder.js": "00dc29eda9365e4ad150fbcc86975995fd9f64f7097d1e4c8cc15131cb0a1175", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/commands/GetCredentialsForIdentityCommand.js": "2480df5440f74316d02728e121f542f001a2815f368797d871460735e441a1c3", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/commands/GetIdCommand.js": "5dcdde2d07f8e13925151c698c6a6a7463edc44bf8a1768721aac3dd7708eb5d", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/commands/index.js": "d8b6c71d7de5970f348d171ca1472330360d4323c571dce284a55ef9b89514d3", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/endpoint/EndpointParameters.js": "c8f186481f822909bfe0e3762a35c04c9ccc72529546a532d7be0fa2d81a518d", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/endpoint/bdd.js": "5bd383bb2624406dfa74ac3502a56a672c086b5a752a961266339675e496f884", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/endpoint/endpointResolver.js": "b348b10ade00971a6c9e3de1b9ecedb5388d5f28a411321727f4ad2f84394487", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/extensionConfiguration.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/index.js": "a03fbf049e89111f359e73d4f5886f2e3d506efe3d81933eb5fb2069d8936124", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/models/CognitoIdentityServiceException.js": "8684dace782e0040cc32c12a81e821ba5d146798dbaca4ebfad1825b2d38b8f1", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/models/errors.js": "33ffbbc586d7610fe9a7c67f5bed28b21b833fe07920202a7c38185605c0de16", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/models/models_0.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/runtimeConfig.browser.js": "eb9e891661fdd1628f8a87effdfcf31ef982876816a26229f17326eba76c57e3", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/runtimeConfig.js": "ce5b27ec896201b2ea782b7c26c81b0640a0ccdb88217a604575201345a02fdc", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/runtimeConfig.native.js": "c0c73f0322ae95adce92f60f138a0e54cc9d686a4ef04c27a0fae1902f47a969", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/runtimeConfig.shared.js": "f3a26df531a725e04617e43c950c013ced9af23b3ee4452d15d00d2a2d79e762", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/runtimeExtensions.js": "91acbc1e819f62b25b16ae8d717eab8eb748fedc63a656ffcf6ae353cd0b5dbc", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/schemas/schemas_0.js": "809cea41ce66eb63e713cfa52fe022f8c5b02d3b1fd1f85e42ef931d13dff153", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/Signin.js": "a91790070f946f0db5f41fa54d9b470cd7dde4c6228375ec1346ca5b500d2b86", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/SigninClient.js": "84048cd8c083393d1fd68fc45ea937396d12c7e87683d2347963c925261a85ba", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/auth/httpAuthExtensionConfiguration.js": "331902cc5df5060ac9644261de8711ff641d6b91e60b5fcf77d2facc441c8cf6", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/auth/httpAuthSchemeProvider.js": "72a23904f67e19c05564ebf29969e23bad0c3dbbb88fe8c647fc44c7ce028ae9", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/commandBuilder.js": "843efa0c461084ad839fd8a05fddb0abf4329cdf7a8950a27ee42ac6e01bb9c3", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/commands/CreateOAuth2TokenCommand.js": "84d78d30c38ee35c8e019373cdfc10fa525e1c425385921c8ad532afe74401d4", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/commands/CreateOAuth2TokenWithIAMCommand.js": "d2fc4408a35e77d51db9ad6c90fe0a02bdde41aeff6a0c703bdc1f80f8b8be81", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/commands/index.js": "9eb302610d3ddee034a452430399d7196122dba091de204c7a4b909d4b31c91e", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/endpoint/EndpointParameters.js": "9fe684a00b59b2721c19294cf472bae13ff3fb76bfd7e6056e5eaf152aec4d82", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/endpoint/bdd.js": "201c38f1444d58c87d6ecff1f0241af8ead291ae2f9c1bb702c7adeb939e5d70", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/endpoint/endpointResolver.js": "40edea4968dff2e2e4df234748c4d4f3c590924f45405cabcf7a0869b0eef8a4", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/extensionConfiguration.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/index.js": "90dc285df127980c78218855f61d20186b71cd5b1c360b1fcb083ea335741ce8", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/models/SigninServiceException.js": "a40fd14467a7f26277150fc3774879484dcfca2fa88f66bbc3aae2c740c46101", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/models/enums.js": "f5771fafecbe39f210eafc7f630cfb34714125f16bc53932a19c7a118357bc36", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/models/errors.js": "ef8270f87938b071c0b68ababbda4c33c44de3e1cb4e87e50d488160ee9dc585", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/models/models_0.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/runtimeConfig.browser.js": "55076a7830c797d414a5622af90a6d065d5c9e487b2925c838166e4aa78de399", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/runtimeConfig.js": "ce5b27ec896201b2ea782b7c26c81b0640a0ccdb88217a604575201345a02fdc", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/runtimeConfig.native.js": "c0c73f0322ae95adce92f60f138a0e54cc9d686a4ef04c27a0fae1902f47a969", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/runtimeConfig.shared.js": "5afba145d20cedf4a69fad0f5e255b394554660db2f08b49b5eb55019dd103a9", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/runtimeExtensions.js": "91acbc1e819f62b25b16ae8d717eab8eb748fedc63a656ffcf6ae353cd0b5dbc", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/signin/schemas/schemas_0.js": "dd745bf987277d5924292d7cf577234eb797deea5b0bb7060c76d07c2268d045", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/SSOOIDC.js": "4d054213691231d719fc7bea4dbc45263073d084ef69fdc9ddca4a19bd498e51", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/SSOOIDCClient.js": "e4e97c4249e3acd0d50ed3cfc751ab301b076a75b8d39cfd91d2aaf1af7f968f", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/auth/httpAuthExtensionConfiguration.js": "331902cc5df5060ac9644261de8711ff641d6b91e60b5fcf77d2facc441c8cf6", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/auth/httpAuthSchemeProvider.js": "a5fc1da9e93d8823192dfb3f8c11e8eb9f110b66bdcb09b8971ad2ff3e83d055", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/commandBuilder.js": "7ddee4baf314d8365481e14719922e711fd0c53395833bd297cb119c94afde76", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/commands/CreateTokenCommand.js": "7cf7c3787d51ea4a4e5ca1e48b9e3f22ddb7c3cd8ad648158fdb7e604f262c76", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/commands/index.js": "68408a182f77a5a894f246dfe7ac82745e12c95d9914e042a7150ecb537f82c2", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/endpoint/EndpointParameters.js": "9735f242587f7326aa3cf5878298463c96f4695ea66c7a1d73f5e0da676ccdba", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/endpoint/bdd.js": "4d400978e8d55b3950f72d895a9e68331e9cb4805c29f8ba52c33b00c52ccdaf", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/endpoint/endpointResolver.js": "b348b10ade00971a6c9e3de1b9ecedb5388d5f28a411321727f4ad2f84394487", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/extensionConfiguration.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/index.js": "0a0f8055a59afbbc2f12dafb03f88181cce0a91f9cf35ed7cf8afc214af3c3e5", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/models/SSOOIDCServiceException.js": "e13c483fa9c545d54b3bc56d97eb67dead77000ece8be0d1c307f2962ae31a38", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/models/enums.js": "1f110fe96c668e8ed79aaabe7700d8b909db93bef1d90a4865587c8af7266327", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/models/errors.js": "a777e0d01869da54bfa62d17af78257841fd2c0d140ccf6e794ef22aa56223a4", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/models/models_0.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/runtimeConfig.browser.js": "eb9e891661fdd1628f8a87effdfcf31ef982876816a26229f17326eba76c57e3", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/runtimeConfig.js": "ce5b27ec896201b2ea782b7c26c81b0640a0ccdb88217a604575201345a02fdc", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/runtimeConfig.native.js": "c0c73f0322ae95adce92f60f138a0e54cc9d686a4ef04c27a0fae1902f47a969", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/runtimeConfig.shared.js": "dc4abf5823f3d04c6405b9c6d75b87f94e1630b36d7498a127da5daf122ff498", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/runtimeExtensions.js": "91acbc1e819f62b25b16ae8d717eab8eb748fedc63a656ffcf6ae353cd0b5dbc", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso-oidc/schemas/schemas_0.js": "21258ccd10e4da9d67d346363de8883f698dec5936fd7743feb486db44024ee2", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/SSO.js": "71ac2820cb65b89bd60d3eba30e493dc332d4ea3e1862d2c43fe142c0baada62", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/SSOClient.js": "db44c6ab924f0ca8cb68aaf426bc13ea9d29acb168e3360aa5e32e140f5bcf17", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/auth/httpAuthExtensionConfiguration.js": "331902cc5df5060ac9644261de8711ff641d6b91e60b5fcf77d2facc441c8cf6", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/auth/httpAuthSchemeProvider.js": "f1b3e1f2c7e7b86ee8d58e2f5c1ac8ba6cbac09c274103ca6fd58701a6465df4", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/commandBuilder.js": "208005656527a40ef0dd02e3609c75e9854d70e9171feb3ecfbc3cc5d16e50c7", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/commands/GetRoleCredentialsCommand.js": "659c4baafcb7ff3763723e87bf40ead5d22de14ff6c2b8062a89741624556c07", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/commands/index.js": "c58d3b5cf10a9a4221371dea0a700d4666ecd1fb8f91fe697a5b7d7b71619b86", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/endpoint/EndpointParameters.js": "2e8ddced4e19f40bc3866ca3f4e567efd88acc505ccdd3531f04d91d138b13a4", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/endpoint/bdd.js": "858273a44c99057edf92a424427bfdca9457d97059233f70942dabd4a7a64b7b", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/endpoint/endpointResolver.js": "b348b10ade00971a6c9e3de1b9ecedb5388d5f28a411321727f4ad2f84394487", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/extensionConfiguration.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/index.js": "5a0828b2f69d2a093d544376552cf9d22617f97876cc80d5946a6f1b9fdf21df", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/models/SSOServiceException.js": "7ca2f3771f1c706b7de5474eda091e4964cd606fa468a703411b5357fa45094b", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/models/errors.js": "bfe45537dc91a45fabf8f5156b80d0bd1d0083fd8e3291c2b4019ad2d9d8c0e7", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/models/models_0.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/runtimeConfig.browser.js": "eb9e891661fdd1628f8a87effdfcf31ef982876816a26229f17326eba76c57e3", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/runtimeConfig.js": "ce5b27ec896201b2ea782b7c26c81b0640a0ccdb88217a604575201345a02fdc", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/runtimeConfig.native.js": "c0c73f0322ae95adce92f60f138a0e54cc9d686a4ef04c27a0fae1902f47a969", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/runtimeConfig.shared.js": "876bb4d0854b6b75557a019857637df7b68a5b1f31fed49e0344e2decf9bc1ac", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/runtimeExtensions.js": "91acbc1e819f62b25b16ae8d717eab8eb748fedc63a656ffcf6ae353cd0b5dbc", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sso/schemas/schemas_0.js": "b1af8a2c653aef9e38884fbd0a65e98c654e65fbae5f846f7d6ccc307625f9f6", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/STS.js": "1f82f0f47af3bb22b6efe2d69064494716dcf941de4324e203b1e33e7cd67977", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/STSClient.js": "6f0a1dcb689b809a1daf4ece20d0bcfbb9937493ea1bcc4fd727a141fe49b34a", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/auth/httpAuthExtensionConfiguration.js": "331902cc5df5060ac9644261de8711ff641d6b91e60b5fcf77d2facc441c8cf6", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/auth/httpAuthSchemeProvider.js": "f8433a6c9124068f5a421ed873505c7ea77902e7c2259af1390e23164c5da626", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/commandBuilder.js": "4bf23cba9db93e3d77b3c6970114266e787c82bb9f3fa931f53cb09b13fc457f", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/commands/AssumeRoleCommand.js": "6f8506634b0dc2e60791431e5bffdbe22dc724cf6235aadd3a53e40eccf87fbe", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/commands/AssumeRoleWithWebIdentityCommand.js": "5ad1bcbdbe14e0a08cf84359361a450ea9209fa74c8f5bdae45050e18185306b", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/commands/index.js": "1fa52becf7bd2ca0086f6c724a8c591a6a4f45cb8ae65d98924c34d45ffcb619", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/defaultRoleAssumers.js": "3080341a3560a1ecc7c6dc45b18deac11db55cfb8db75457855bcb68081321e3", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/defaultStsRoleAssumers.js": "461c1ff0b375f55406bb413537cf1f64e6f541f99a9061ae7a084bb6a784744f", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/endpoint/EndpointParameters.js": "f8ad1ff4e80734123ee4b414042e32dcd2165beffcda8816a3d15bfd07bfc701", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/endpoint/bdd.js": "ffd40553d0b97c3683f530e0e695a8dfa18bb0f90b8ec0e3f98e5541976b0eac", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/endpoint/endpointResolver.js": "85d0f4cfcaab244f8a0e3020a61057327cfbe800e69103c17d64b94b42d0dcb8", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/extensionConfiguration.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/index.js": "28e42e0e4fe4aca6e765d5440aa5f7bdc016d1d2657f3b0166e5fb9501205a13", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/models/STSServiceException.js": "3bf07f0767c077ef4b715bdd77c23a313953b1a59690d9d0d9c11a799ee038f1", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/models/errors.js": "e981f8ac63d03f2f9afb673f321abeb13e73d388e8a082567cfc69ebed63fca0", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/models/models_0.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/runtimeConfig.browser.js": "55076a7830c797d414a5622af90a6d065d5c9e487b2925c838166e4aa78de399", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/runtimeConfig.js": "50d13c142c54b107866c8067d9458eb7057d0ba951891da1681333757f7a553a", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/runtimeConfig.native.js": "c0c73f0322ae95adce92f60f138a0e54cc9d686a4ef04c27a0fae1902f47a969", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/runtimeConfig.shared.js": "8d6d45d9c7cde0774d047618ab00e3058598d2de7f64c5a2c6e65841ddaba656", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/runtimeExtensions.js": "91acbc1e819f62b25b16ae8d717eab8eb748fedc63a656ffcf6ae353cd0b5dbc", + "node_modules/@aws-sdk/nested-clients/dist-es/submodules/sts/schemas/schemas_0.js": "6929c0f14abfb5e09010d4b55c4bbf8bf1816029fe5a75e250a6fcc2e24b7980", + "node_modules/@aws-sdk/nested-clients/dist-types/index.d.ts": "7db58b6bee008e48c6907afff5ecab72f23e7d345ac7f42251e4e96b8037cd35", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/CognitoIdentity.d.ts": "62f8cb5e0bbfd69ee6776b0e216579517567efcd426b76fb190304bbf8937965", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/CognitoIdentityClient.d.ts": "e3470e6852165578c7b32474d0bf52616a0e1b1b37d3762614fa90aec9369445", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/auth/httpAuthExtensionConfiguration.d.ts": "9cdb379f2eccde100eea3ef5371bad9ac8a9a85ba23405a65b88b00e2c0c3c1c", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/auth/httpAuthSchemeProvider.d.ts": "a59152c7d808b44766932e8caf55838c760ec89521ce21477c930a6df0ee88e3", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/commandBuilder.d.ts": "783fe144c2ec929feea1b4dba55cfc69a5ebcf37ee782f5ada5591fb2d72170c", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/commands/GetCredentialsForIdentityCommand.d.ts": "2398015b0ff00b538dc1729c41c389b154e1feaa1466464ca8c2c923ec8bb5cc", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/commands/GetIdCommand.d.ts": "456ffe9906cc841d3bc4e2008c85a9987b687d040c4f581db1c53ff2ce6890d5", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/commands/index.d.ts": "d8b6c71d7de5970f348d171ca1472330360d4323c571dce284a55ef9b89514d3", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/endpoint/EndpointParameters.d.ts": "db382b318bc333eb7548cc1f0dcd149794d7e1d4969d54f9586f2533b287b5ac", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/endpoint/endpointResolver.d.ts": "b82b0ed889b5688a674912c9a0efc4667863d3c9ccf9240673c5a30d9fa8116c", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/extensionConfiguration.d.ts": "66cb219790c35af5b53ad71741eebac719e63d372e47f1e10bbc459412147e27", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/index.d.ts": "aad346a15283be01c0606508de11870785e545cf5f84f6304d172e5f7a4955cf", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/models/CognitoIdentityServiceException.d.ts": "a3c4e1e95a79fb7d54e0d741e88d69e628bca0a062c40384fbdc6b03228ece13", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/models/errors.d.ts": "c963b07210f476afe23abbe3abe5e9deeab623fb9a800e55fa1fa98beb37a9ce", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/models/models_0.d.ts": "bf0f9833a80fe4637434a7e5e987fc4020ef9b5022a2aa15b2118e8aeb3a045f", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/runtimeConfig.browser.d.ts": "e8533006cd32a2d76da9949a41296b183d902bd317eeba35e286433f180774b0", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/runtimeConfig.d.ts": "07a402869f536c05a865018a33ea1816861a1f567fca39574433b61947e0abcd", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/runtimeConfig.native.d.ts": "d0eab367674a1bf85b0094e12771c926b7099b20223dd492ee77a481c6742ae8", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/runtimeConfig.shared.d.ts": "8cf3f25facfb334e60e21b4183af42a171c0667fcc7f6fc52295904932f9d865", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/runtimeExtensions.d.ts": "cc78ad8b375649f1539090c3a5d905783b0ee4af3732272246118119d797ea57", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/schemas/schemas_0.d.ts": "e1f93dc14d4be8bd52c18ac57df0fd28134b15cd630caf5ca0b3553775fd802c", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/Signin.d.ts": "83a79d93f806a888e5926ee129e9fbef8a7744d74bb5eb05e94e4e33bceb4b9c", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/SigninClient.d.ts": "c1babef021ba71e1e929752ccb3f70daf617ec8fabed0d6ca0592ae654ac4c32", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/auth/httpAuthExtensionConfiguration.d.ts": "a727b26f97cb31a81ab109ad3f1d6294253dc2d485c8326abfd70c6eb1945cd2", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/auth/httpAuthSchemeProvider.d.ts": "c28c0f54fb500d601bfd552ca678cb131a83f0cecba8bd0afad80314356a8052", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/commandBuilder.d.ts": "84607ad88c925c56aead629e88474465c7a0a7b79856a42e571053dbee6dec36", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/commands/CreateOAuth2TokenCommand.d.ts": "a2618b82702f5eb61e5377b80a18817630e669d3ab5e341446f720447febc1e3", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/commands/CreateOAuth2TokenWithIAMCommand.d.ts": "b32d24f4a86049e8b2787461c2e5557ae2a1d03534d7f531318073b7e8056459", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/commands/index.d.ts": "9eb302610d3ddee034a452430399d7196122dba091de204c7a4b909d4b31c91e", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/endpoint/EndpointParameters.d.ts": "9e942fb7f360e6f84fa7c24403e57c9a89592794908e9ec0717e2c1ac19cd43d", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/endpoint/endpointResolver.d.ts": "b82b0ed889b5688a674912c9a0efc4667863d3c9ccf9240673c5a30d9fa8116c", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/extensionConfiguration.d.ts": "1860cc3053db9c9a7e84b257fbd2b85a1a65add382f37140590d1ec9e621ba17", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/index.d.ts": "4792b5ea21714a1b11a7751d537d20a93054c409edce65acf3633d05abc23920", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/models/SigninServiceException.d.ts": "e37a0dca9903f16fdb2c001feec85cb4aea4dea5484084e96a88bf48f0b3b304", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/models/enums.d.ts": "d2af77a0778494c12cb8a45540da0bb60fc47ea06d59e2f929ef9a7adc528ca2", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/models/errors.d.ts": "644cba06cc16f80bdbee687bbe31bd8b87963b7a3c7f1f2a10bf3d9d9586a8f3", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/models/models_0.d.ts": "625e17fa8f1b0f8c4c22a44d7add7d80fe7c7e69900fd0359b05a7e064766db8", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/runtimeConfig.browser.d.ts": "b77188cf95c3e8b23ee48e46431b1bd019d94ed82a3e45ee21ea3bb98b9db4ca", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/runtimeConfig.d.ts": "501a46353a37747bdb5ed5170d4f05e91626d2c5ee994e59da4a0d4f2505efe0", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/runtimeConfig.native.d.ts": "ea582ab7d9d1be72bfa460dd59c95c99c4d7711c49c0e54449582c1c3bd0a894", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/runtimeConfig.shared.d.ts": "1eeb5213b58729c49b409cbf7afa9aa421ab621ab5d5b4bc3db90cea96a9c332", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/runtimeExtensions.d.ts": "07ced9c38fbd94bbb23bcb421c1ec04d8accb40c964b2eed3cb6c523852aded6", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/signin/schemas/schemas_0.d.ts": "acb1e05af4509b68d675bb37a98fd1491848010b18e95e8f8f0942f864c392e3", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/SSOOIDC.d.ts": "2004347ac746484e19779d649193c8ced27191c266475701ce99cf799cf4973f", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/SSOOIDCClient.d.ts": "2723d7cc737093f6eb13f93a63910a20ad63db3bc4ea727a7c1968ddf3de2860", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/auth/httpAuthExtensionConfiguration.d.ts": "c83b67f91b470963a7f1bf2d3c856b998f6ae3f73889a53aa76b67b3b596522f", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/auth/httpAuthSchemeProvider.d.ts": "b29c7cedf228702fc1133376ebc6242100e28444992b6f0ba93aca6f06781de3", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/commandBuilder.d.ts": "80d1c7a902ae62d1f1eaa346148a7c5d70e94b9b73e81e6fa18a2a5396428943", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/commands/CreateTokenCommand.d.ts": "8fe8f076a7acd3ce108c090daff013c07dfe23fe89113408caa789f7c7fcedb5", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/commands/index.d.ts": "68408a182f77a5a894f246dfe7ac82745e12c95d9914e042a7150ecb537f82c2", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/endpoint/EndpointParameters.d.ts": "db382b318bc333eb7548cc1f0dcd149794d7e1d4969d54f9586f2533b287b5ac", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/endpoint/endpointResolver.d.ts": "b82b0ed889b5688a674912c9a0efc4667863d3c9ccf9240673c5a30d9fa8116c", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/extensionConfiguration.d.ts": "1ac7773e40c3cce0d479ff4f531145784d8cef368be46c0182d24c48bc1f787c", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/index.d.ts": "4bc375a41ad26817383d125b05e407010dcf95bd28ee8d73595b3f361094fa82", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/models/SSOOIDCServiceException.d.ts": "f17154bcb773e9d87af8a8922e8003cfafec3750dac03df529391d784d78e53e", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/models/enums.d.ts": "94101f19f85cfa582038665c05c540f52323f7806d834603b01ded02ba037e82", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/models/errors.d.ts": "2316f84f64afcb41185233ce28fc39bb1323f206db9453a2f0b91b131882c185", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/models/models_0.d.ts": "2d61e72fd248279f0efab784c870880b722d805b2648f74058cb3acd1c622f40", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/runtimeConfig.browser.d.ts": "c5220c8ce120399d54b2a9d887cec9c4624995569b4deb299bc63718baeef14a", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/runtimeConfig.d.ts": "3cf8243294c216c6c3b40ffb92324fe67d4535934a2730357aeb8a147bf43fed", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/runtimeConfig.native.d.ts": "f9eff9c5eaa82424e4227ddb3d2b70f2726abfbb6b7e32b4be57861ec50b8fbc", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/runtimeConfig.shared.d.ts": "cf7c0ff45b67c15e1e9bf544d16e4625431fa2a1519d50c503dc2dc963562109", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/runtimeExtensions.d.ts": "c604ce58e7a08de2f6ed3350f970a7cc5f49a90470b60514def20cf4652d0ee1", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso-oidc/schemas/schemas_0.d.ts": "133e72304cb457bf0828b5b50f2c98195963589e6315f89a233391737dfb0bd7", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/SSO.d.ts": "52cec030a5dae8612b0079567617f1964f6552e45c52c4bb1a3e9d7207952b5c", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/SSOClient.d.ts": "36c6ae58d0b0c48c9859636bc792abdf180d6831f7d6a01deb5f7faef037d996", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/auth/httpAuthExtensionConfiguration.d.ts": "987da9c96bccfd2074b21d43505b201822e04fc839c832d11a4150807cfa05c0", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/auth/httpAuthSchemeProvider.d.ts": "cacb3d6fe04cc41b7053655ff913cbb85d37cb838a5bd773566663938bedfc93", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/commandBuilder.d.ts": "7e0107d251e8910d112e6500b43417c1bf1a8995b71e212cfe93b27b6c8c8d7f", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/commands/GetRoleCredentialsCommand.d.ts": "2b1160b84845a7d3b30bc9f1e9b1ffd500e4f9d1b6a305c2134f7e65cfa2fe24", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/commands/index.d.ts": "c58d3b5cf10a9a4221371dea0a700d4666ecd1fb8f91fe697a5b7d7b71619b86", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/endpoint/EndpointParameters.d.ts": "db382b318bc333eb7548cc1f0dcd149794d7e1d4969d54f9586f2533b287b5ac", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/endpoint/endpointResolver.d.ts": "b82b0ed889b5688a674912c9a0efc4667863d3c9ccf9240673c5a30d9fa8116c", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/extensionConfiguration.d.ts": "3f0bd6a0b8e05914a176e7d66f1dca68b0dea5ee621465ca9b353f6f0f94d8ec", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/index.d.ts": "4933a7061d9c0128a4d0efbaeda7653bbfe67a5361793696de8ec2a6fc4d677b", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/models/SSOServiceException.d.ts": "f99a84ea4959c5c1a15a7dd875af7cd9d7a44499a650d2dd269451fc1a639d04", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/models/errors.d.ts": "069fe24408ca97497c22018d6cb2a57a88783c5fe202d13fafe4ad6dfa1a8a5c", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/models/models_0.d.ts": "250c1f62c95b9c38716ca428ac40b89b73bebce67a44b6c07963934c2259c81e", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/runtimeConfig.browser.d.ts": "4b915e5847bfdad6f402569e449361fd09e3f34d37384ac3d538b38b894d0e93", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/runtimeConfig.d.ts": "600ef4e909de2dfb25bdb75463d8d24a21fd85ed5fdb5c3ffdc48039ad891c07", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/runtimeConfig.native.d.ts": "9098639d67ba1bbb865bdb786585f2867a181a9c8b44af8869caf189436e0fbc", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/runtimeConfig.shared.d.ts": "12fea96120031b7731ea3d7c6353da58c4e1fb0c6382c4af8ca82e8edb29cca5", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/runtimeExtensions.d.ts": "a907904c1002f012b2b5aa86029f454aab7c1478c150a3339d56bf60237a37c6", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sso/schemas/schemas_0.d.ts": "bdc3cdfac3327638f43679500d18cc25068169788b9fc0c82dfd43b3cb0a4db4", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/STS.d.ts": "42225d7ef03ffc5ae1f1ab581457e6ce16790cfe161f5a69374d6c48bad4c98d", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/STSClient.d.ts": "58a158fe6bab52bee4c7e321a7c2468bc3a818f2708186adefea6927b6c933c4", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/auth/httpAuthExtensionConfiguration.d.ts": "27520abeb725457b4e2289adbfab80b32e7f8deba1e82bf0fe8eb7f8fb2adede", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/auth/httpAuthSchemeProvider.d.ts": "9393b59a833e430cf7d4aba98d78fec3958192836ee8bd505576f4272b65a410", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/commandBuilder.d.ts": "f8e507b34aa45c59724d314d2cefa9c63630410a1a6497ff2fb4dd3a55e1b730", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/commands/AssumeRoleCommand.d.ts": "d6b5f96e31cdfcf6955ca63778db40cbf392b87934b6c0db399b5b71206ff609", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/commands/AssumeRoleWithWebIdentityCommand.d.ts": "93271217ff1125ad4250d78a14941f3cc59aa477eb10207bc72c709e82790bbe", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/commands/index.d.ts": "1fa52becf7bd2ca0086f6c724a8c591a6a4f45cb8ae65d98924c34d45ffcb619", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/defaultRoleAssumers.d.ts": "03414c90f925d988df2125a1f82c237b988e44b1be675fcb95135e72d5d374f5", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/defaultStsRoleAssumers.d.ts": "3282d09429d6ccb66f52e62c9322a31a31b0ec4e8f446398c56cafff83ea49f3", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/endpoint/EndpointParameters.d.ts": "8a62055c1a71b5519baf10186441fa77f9d6e7d39b120f4c4481c2bd7793d7c9", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/endpoint/endpointResolver.d.ts": "b82b0ed889b5688a674912c9a0efc4667863d3c9ccf9240673c5a30d9fa8116c", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/extensionConfiguration.d.ts": "95c8c436a96e926ff139b92703426ba18276b596bea0a998f82a479181bad5a4", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/index.d.ts": "57efc41b17000790cf205fd374fd715c8c727861c07dd017eae360f479ac183e", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/models/STSServiceException.d.ts": "a1101c623ef70171238b7f27ebda11975ec202ec3aa2089c751d836b772263c9", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/models/errors.d.ts": "8991690ecf377f305506314154be8b9e9fd584cec6ce60ecdbaf94f03627ee23", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/models/models_0.d.ts": "4206b4452ad53e9edef87ada89c65270618e43be19037cac1ecc466991b088ac", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/runtimeConfig.browser.d.ts": "73d92c4a6548973521845e32ba66b2afd12c313141bc4badd7807f977ea80999", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/runtimeConfig.d.ts": "66136ff0c190d04890b39a6218edaa41d3c48805c37fba17693fb715d09ab60a", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/runtimeConfig.native.d.ts": "0ac2939d5c4d9461e65684735b222ea16faf98e8d7718e027a673c9f2f07431d", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/runtimeConfig.shared.d.ts": "4f532bbd8047d342972d97806a6b89044155d5d22ad8f910686da8173a7fc531", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/runtimeExtensions.d.ts": "9fb9e0b6e295813c230258b74f55515408168bcf2d5d25bf8c026fa3b7a7d839", + "node_modules/@aws-sdk/nested-clients/dist-types/submodules/sts/schemas/schemas_0.d.ts": "d93eb79dbe58aae7fa9247a6e35264a090ae5080b9dcd2bbf90ec53d54384d6c", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/index.d.ts": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/CognitoIdentity.d.ts": "8afef0ea80f2cdab93abaf8bc54bd269f8d8a4c8f7d3dc0456e4457200280c0d", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/CognitoIdentityClient.d.ts": "f4bc5b8e32265bd247c29a3c42a70564bda8029cae83c12b9238597384e6d5ed", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/auth/httpAuthExtensionConfiguration.d.ts": "c2044a77bbe50f640f7e2484ddeebe7bc52dba5cd91d986220a578b70adc5fe7", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/auth/httpAuthSchemeProvider.d.ts": "f48c5ba40c589eda5189bedd0aa5d780d4a75016fad86f4eff8925eb4f75d7f5", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/commandBuilder.d.ts": "e425006d3168e7bf04cbcadf75a4eb53129951a6e735c4a6ef460f99ea275dfa", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/commands/GetCredentialsForIdentityCommand.d.ts": "0abbda5b34354206ea96bcca3088990ec44c2791f8a003c0cd31c481f58bc56c", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/commands/GetIdCommand.d.ts": "481fbfc78c595930730bb3a0f393f18922400cbc64fbf1dee80abcb7134edebc", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/commands/index.d.ts": "d8b6c71d7de5970f348d171ca1472330360d4323c571dce284a55ef9b89514d3", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/endpoint/EndpointParameters.d.ts": "efc5d525e84010e14510c5db584c8182ca6d047c1040218a21df72206c260cad", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/endpoint/endpointResolver.d.ts": "8db6ac77345e04ed548fd662661a0fd1ebd10197510160beb75c3116fde4d2c5", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/extensionConfiguration.d.ts": "328f6efad13697207ac4015c7c383fb48238eeb090515ac85d8b8ddde143b240", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/index.d.ts": "faedc86ae71f2ed06bc0d2946a72fc36c3725c1f5d2e676bb4389ae9c3b62987", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/models/CognitoIdentityServiceException.d.ts": "9039f7cd080827a869990a8e102d53f119f3ba2e733a5f82f7f5a437d6e68f60", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/models/errors.d.ts": "6cf8e49dd9da33d9892a25117ec508fb39fd9c9fd2c201354875af00409dc613", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/models/models_0.d.ts": "85b4004dd7f98921877b4b13375fdb5bd631be82f1bf139cb3e487f49c8ba2c2", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/runtimeConfig.browser.d.ts": "8921ee1e7682124f4ad60928b93c59bd8aa511ff069d24a015663c74b3325b61", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/runtimeConfig.d.ts": "4267993f94a7d56198852f0e85e71b1c0716709bdb35f27f97ab92273fe7f15d", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/runtimeConfig.native.d.ts": "46eb2b3a386f02676fd9180e4f1dced32eb762acf4207d18632981ba605e7139", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/runtimeConfig.shared.d.ts": "0b9a9a67deaf7f5ffd7e01798acea2ceca4229192b57944ae978603816f60c99", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/runtimeExtensions.d.ts": "cb1c475f56d1a49c48dea7efec5be711c32287cdfb992413774c480d147a10e0", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/cognito-identity/schemas/schemas_0.d.ts": "c8120de235fda8dbbaca1b61cb7e61f1dd58379ac415dad5161ddd09caa06207", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/Signin.d.ts": "fd4a7f64fa0c56b78882555b0a03cfa9988581712bc313bb309546b83ba7da7f", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/SigninClient.d.ts": "c10a9c1898f19dfbdacbcbf8b39644adc83c916c145e9f604b2a6d3ae531de65", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/auth/httpAuthExtensionConfiguration.d.ts": "915152d4158592ecb3de868868ad6c3e84153d31744672e970a5db2a9228e54f", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/auth/httpAuthSchemeProvider.d.ts": "71f63566b3998ade59c1a4273aa8e8d5c225bbcfd0b4a20c47162a389101135a", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/commandBuilder.d.ts": "baf7e67bebf6e23315ed7bce0b0cb4e6064a0e68d920fc7e24879fbce2dd79f2", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/commands/CreateOAuth2TokenCommand.d.ts": "eeedb0c96bee79781c5b209436daf34206e980b00f3a849242fc7136f504982c", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/commands/CreateOAuth2TokenWithIAMCommand.d.ts": "f987baacebf8f4f1937c41966926c81c724b51c723d6373429f5ec45b5692f83", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/commands/index.d.ts": "9eb302610d3ddee034a452430399d7196122dba091de204c7a4b909d4b31c91e", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/endpoint/EndpointParameters.d.ts": "4498d6b3b1dc1b7796c4181942733965d7271725a10daf8b4f82127f57213637", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/endpoint/endpointResolver.d.ts": "8db6ac77345e04ed548fd662661a0fd1ebd10197510160beb75c3116fde4d2c5", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/extensionConfiguration.d.ts": "4411c2973b6c87ec3916169a20c664b5fe8aebc61bbabe2e0150efb735b27859", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/index.d.ts": "36956776f4a111975b2d963cbfd73fc46efbaad81679de903841d2e7c34e61eb", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/models/SigninServiceException.d.ts": "6d214c446a1f5eaa3e93aed2c29a2433fe2b942d9f7646f891241c322faf5546", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/models/enums.d.ts": "c2cf7d6ae4b25fb670b2082fbe51618a8a9dd3dc456723a05774ec28fa3added", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/models/errors.d.ts": "0a63123467b9ea99badd12f87d811e2b9b9abb055424fab702019831e2ed00bd", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/models/models_0.d.ts": "ef9cf945df9a36a87551be496b369a9f5c1fccf3202caea809361dc685230a50", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/runtimeConfig.browser.d.ts": "897a932d3bf1b8f4ee07fcc5ccf0ea590289a87074f348ce2708900bdb626808", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/runtimeConfig.d.ts": "edc458e82d942f0d19c484d133ee449a404cd4915d36546fb0988fbc63044b11", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/runtimeConfig.native.d.ts": "16193eb58eb6047b68cb511a5d4afb285617f8bbccf66ce6bf8e68f41cda56ad", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/runtimeConfig.shared.d.ts": "17b7302a9051d55153b5043317fd617feecf8fab3629cac1180f1e9336a50a2c", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/runtimeExtensions.d.ts": "decec9b0b769e7167be5b78977c92009075b88f77606aa7d2aaf6251fdef1bc3", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/signin/schemas/schemas_0.d.ts": "80e74d435dbf325fb4b2297be97bbe6c7e7ac0c58b8c608576c82373cc4baa21", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/SSOOIDC.d.ts": "105e61fd80004a3c22262e5520a8463c4ba080ea749fa4ef03a7eaeec5480e23", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/SSOOIDCClient.d.ts": "88c6689650d07f5461a3c1b60aa60561dfc70c2023c4804a32322699ecbe9dd4", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/auth/httpAuthExtensionConfiguration.d.ts": "4509aae1cd9975b3317d40cf5ca71b168b1b0d94c5a07215a1a4437bd8435d3a", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/auth/httpAuthSchemeProvider.d.ts": "236a1f0410f9ae71d78c8df96bd0ebc047ba9a3e3c451a21919ba53434ac4eff", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/commandBuilder.d.ts": "e8fbdfc8ccff0011259ebafcb5482f09111593d98257825601e6433897559cf0", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/commands/CreateTokenCommand.d.ts": "da55c9a42ab02ab206587a233a53da6bf65b2addc41bbf9a711a00026d8220e2", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/commands/index.d.ts": "68408a182f77a5a894f246dfe7ac82745e12c95d9914e042a7150ecb537f82c2", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/endpoint/EndpointParameters.d.ts": "efc5d525e84010e14510c5db584c8182ca6d047c1040218a21df72206c260cad", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/endpoint/endpointResolver.d.ts": "8db6ac77345e04ed548fd662661a0fd1ebd10197510160beb75c3116fde4d2c5", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/extensionConfiguration.d.ts": "3ce3f0cf76790d412cd4cba0db38a5407f2de031eda8a4ae4ae3399c4ebc3085", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/index.d.ts": "f1b81e710f12449ca3f6ce112b20ebd2b8446345e287cd8040158ef49496dcc6", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/models/SSOOIDCServiceException.d.ts": "b65a9314b23f4a4f11f73f79863245fbc1090516940a01a3f15b605919a571a7", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/models/enums.d.ts": "216b7d7af72095a5b632b3abe0736ab6ff398479841ff1d21747939460a1f908", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/models/errors.d.ts": "569b2b3d85bf7a50e7dec3a759e298038a185f12196604d64a33c23cf446fd80", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/models/models_0.d.ts": "b460ba0bbc4d1e119cead8388f0ee364ce09b73641f6dfe840947864d4184331", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/runtimeConfig.browser.d.ts": "2d6cc081118beafb267a9248f7cf536be4379da9ab0a6c34cc3a1749ccc0bf28", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/runtimeConfig.d.ts": "7f86d4b8722583ef216f8aa90409011f383f868bcb451d6c0dcb566a9bd82a5f", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/runtimeConfig.native.d.ts": "d190577ca9669efbef65a028ac55aac67ab87a0aa0d2bc738d313efc8817bb86", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/runtimeConfig.shared.d.ts": "5a4241bb7daf52b4d070557391252a82113cf382699fa98ba9b247bfb9f28a8d", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/runtimeExtensions.d.ts": "9520bb238ee8be481c82f1ce97b76ac41c4be7088ebc395e1851c3b8fa06bfb3", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso-oidc/schemas/schemas_0.d.ts": "c6cf177b6c4eee29354f79580a42638b841a8fb5fd2a037b79e003340b9c29b9", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/SSO.d.ts": "1f87e6a7a50fd089edb838945cd09b6c54878d0816b045831a7edc33ae12b8d8", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/SSOClient.d.ts": "c6f42f91dbb128254849548e4d57db1fb2bd60ca8722eb7a9851608dac52a0d1", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/auth/httpAuthExtensionConfiguration.d.ts": "3c991e4ce8980d57dfc423ef99a7cd879caf62d1d10a9b4c371bd2c3c8c5de36", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/auth/httpAuthSchemeProvider.d.ts": "2448438594904877740cfdb9d1dd186ae06be2811e256781958245fcf1e1cc38", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/commandBuilder.d.ts": "e7ae33d74dac1f64dfbc7c94b6a5be541a2c08a4640ea640510a4b6585d7e5d9", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/commands/GetRoleCredentialsCommand.d.ts": "1630886d1028a015ae94e253d3698bd2bbf0a9aff33e4006e2dbcf1f6aff94b6", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/commands/index.d.ts": "c58d3b5cf10a9a4221371dea0a700d4666ecd1fb8f91fe697a5b7d7b71619b86", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/endpoint/EndpointParameters.d.ts": "efc5d525e84010e14510c5db584c8182ca6d047c1040218a21df72206c260cad", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/endpoint/endpointResolver.d.ts": "8db6ac77345e04ed548fd662661a0fd1ebd10197510160beb75c3116fde4d2c5", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/extensionConfiguration.d.ts": "51e07ea61e4cb063fd7f33b3adb3cec55926732d363b954425ea7d17602e3958", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/index.d.ts": "89189e3540491b89e2c40eda7b0cd21aa76e589d66483810f1e9f3bfbdb065ed", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/models/SSOServiceException.d.ts": "6ad24ea162fd8d2e028c781a51c61616e9f5a5692ce8af7ffdab76d6665d5ce1", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/models/errors.d.ts": "c28687cbf194ba32e8686656a0e9696bb5bd2ac7cf00fccf687866c7575215a1", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/models/models_0.d.ts": "9749e31bdaf5743e22fb4610fe2dfb2c5fd8fd0c206565809e9faaa578b4a369", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/runtimeConfig.browser.d.ts": "330680d32d9f798a1169f544f9816ebf7459ca16409f603e27d222450f727342", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/runtimeConfig.d.ts": "e39cea3605d7e092af659e17082824d3817884bd6b8f75c2c323115603f67912", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/runtimeConfig.native.d.ts": "1918f02dff38d8c2f73c52fa0ccf5931f5aaa6a1e3189453d24d9f974fc293c5", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/runtimeConfig.shared.d.ts": "c3f334a133ebf4e052ce21a88b32895e68e65d9228c2125991e0b69ecc3cab55", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/runtimeExtensions.d.ts": "4ed3dd020ce5c4ea87c56b4e32de19c91a0eb3be6256f621c5a793f5fb0740b2", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sso/schemas/schemas_0.d.ts": "0b39ff63ab825339b63346c1d3951749e23d05a686d7d4eaa4ab7b931829cc24", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/STS.d.ts": "debe3eec160fe82a8371aaafcb322ea25a144574dfe7a7f3155c6d9b82175c0a", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/STSClient.d.ts": "2424f89e3ad8b40d25d55a86fef6a8ea335ac64ab8b59d05bac5916c0bc97013", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/auth/httpAuthExtensionConfiguration.d.ts": "9a434b521f653c4d2340b4bc283066e0340d736faae06b192a466760c53a3441", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/auth/httpAuthSchemeProvider.d.ts": "f80d467979f5de8198572eca1da00641a903fa7a0e2f2282b6c72fd869d0c0a3", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/commandBuilder.d.ts": "647c82901b0650bb01120742fa6178d4741fa48455e06db4bd49038d924e59ad", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/commands/AssumeRoleCommand.d.ts": "ca69f014046066d072f60e347595ce692f3c2de7c61a18fa40b1bd8be4f47704", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/commands/AssumeRoleWithWebIdentityCommand.d.ts": "3c37bb041b76091b92bcd251a419e69309eee29c080b5a49c40dfb9ab95d7a84", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/commands/index.d.ts": "1fa52becf7bd2ca0086f6c724a8c591a6a4f45cb8ae65d98924c34d45ffcb619", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/defaultRoleAssumers.d.ts": "027029d34231af680b6503a081bc35c02bada18f95e9f8fd28e782a8c7481178", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/defaultStsRoleAssumers.d.ts": "de9380cd3970ebee3a40e2787b2a009cc5e95ff816e8e7bff773d515d84358d4", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/endpoint/EndpointParameters.d.ts": "d93973a3155b01a05ea3dee58efed5fcb68bf183582b916af1a3b760d7f349eb", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/endpoint/bdd.d.ts": "931e2065cbee9695e90c2f6dafc92ce68c3a71f8b2f77de84191df0f376896f4", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/endpoint/endpointResolver.d.ts": "8db6ac77345e04ed548fd662661a0fd1ebd10197510160beb75c3116fde4d2c5", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/extensionConfiguration.d.ts": "440c629622297ebf0ba9cfe114c0ffedd393f638477bf7554eddbdad4a10c8d0", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/index.d.ts": "b48f7964825e7a5b4d3f3b9962416386c64ae23a5dbd49a0953cafa27cbc3f30", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/models/STSServiceException.d.ts": "345b7efa74c8e542fafbeb0547f137bb80a3fb3de5799354d75411882d3c08e1", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/models/errors.d.ts": "85bb7cd7c9d4b6e5a5e0ba9692ede0c9c9425fba6bfb51d68810816c21cfa0e8", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/models/models_0.d.ts": "3da7bb36a739f5fe7c19b63661700e57820b09febec7ab3ba679cdd0e589a446", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/runtimeConfig.browser.d.ts": "1f5c31995bb67d7e38fa2e94a9d81fb671ba95707a712c0c201ae0ec9854a0f5", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/runtimeConfig.d.ts": "63dc01b078ab45a21f7a60caab92b711e792714bf4fb749290324152ab0f7daf", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/runtimeConfig.native.d.ts": "8c5205d67ae7639bb10ae2773e9817f887d55b023346f5101f8767a85914ccbe", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/runtimeConfig.shared.d.ts": "c0930e75670375d5fcd09be70a3ac49323f4a28874965736f94625e28aca1e2f", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/runtimeExtensions.d.ts": "f8843f862b2628ff773c557a04ce24107947a472bdb086e009333555db554334", + "node_modules/@aws-sdk/nested-clients/dist-types/ts3.4/submodules/sts/schemas/schemas_0.d.ts": "a2ea2712aeeeac21129dd25f52b70ae5d7f4dadd9125cdbacb84bc43d5e600ee", + "node_modules/@aws-sdk/nested-clients/package.json": "dd334b9f6453d2e9094bf36a10e3a1a5637fa6c43fe38ca42e649e3fae4fdbd3", + "node_modules/@aws-sdk/nested-clients/signin.d.ts": "433585a9dcbcc60cfde88ad7e0f68c71995a3f719e4db2067368dacb86bfc0ff", + "node_modules/@aws-sdk/nested-clients/signin.js": "174390745a77ae03f1d63e60ec02401577b3c4d3fbc0da038246f20ffcae7b80", + "node_modules/@aws-sdk/nested-clients/sso-oidc.d.ts": "2a40ec180c0a52526764378cbaa5fa179f3add318555564671c5dd727f95937f", + "node_modules/@aws-sdk/nested-clients/sso-oidc.js": "156e82a784c6d2895620f36ca64c60d34ebbe692e7007b8e110f2461196e5a15", + "node_modules/@aws-sdk/nested-clients/sso.d.ts": "18c0c5840662dace06a3625f8f1df4ba48991967eddf5c5ee1e4831a771de029", + "node_modules/@aws-sdk/nested-clients/sso.js": "727df763bed4f1f51ce69807fb4d3156a1d26650e558f3f7df760333f35490ad", + "node_modules/@aws-sdk/nested-clients/sts.d.ts": "e44090d03c84cc790a965f7cc9f6fca5c01cb4d6c6723295e0a27a0e3c9ea1d7", + "node_modules/@aws-sdk/nested-clients/sts.js": "f08029b2fa110d676126c575d0bb18f268db788a685304e3c30e7bab3b46a9e8", + "node_modules/@aws-sdk/signature-v4-multi-region/LICENSE": "07364a5f7d0177a59a05a0002187431f8f899320983c0dc049d72df015e8064c", + "node_modules/@aws-sdk/signature-v4-multi-region/README.md": "aa7cd925c7686ab775dece8f17b4ece2fb34521cd54d247f0bab38ac61b9c118", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-cjs/index.js": "13e4f99866310f2bb966554b9cd52598b4701ded5e4f5056dfa65fb6eb71b5f1", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-es/SignatureV4MultiRegion.js": "b85448d40b79c5a9a58583381694f2f079dd7ef24c880110be36ef949a7bbab8", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-es/SignatureV4SignWithCredentials.js": "c023e60515473184c5dd60ffcaf7639923f5014331678fcc19ce4dad6844896b", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-es/index.js": "d9bccc4c38b621e9b57e9d8b7d68a88ab83b6de40223efd7a96e0f60f2928b52", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-es/signature-v4-crt-container.js": "6694fadc28aaa50d9891d9c61e374b956dafe35819fcf9856d6cd92f1fecad15", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-types/SignatureV4MultiRegion.d.ts": "317a16460c2d3dfaef0a1cf74dc13129781dee378fb60e39064f09ab810031fb", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-types/SignatureV4SignWithCredentials.d.ts": "9c5668dfee3261d3426312b1a52266ef6a25426bd3e51c3888b1ee6c2f506b46", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-types/index.d.ts": "ae7ba4b8fca6bf98a7f593a480a5c0bcf19a4638b26399c5ea3a13544c6a01da", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-types/signature-v4-crt-container.d.ts": "87b266d84f88f6e75394ff6cf0998bd25ad6349fb8816f64c42d33a5c19789c4", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-types/ts3.4/SignatureV4MultiRegion.d.ts": "7993a33e7230887163a107021787af7116f2b9dd5a06ff233547fd0180dea9ff", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-types/ts3.4/SignatureV4SignWithCredentials.d.ts": "20a491184be11644804c175fa9674fa602021dff7361e3a8fd29a39ddff20ea1", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-types/ts3.4/index.d.ts": "803e72174441d5b5c71cac0c47bfa15da03381a96462ad44ec9ee51e420f50be", + "node_modules/@aws-sdk/signature-v4-multi-region/dist-types/ts3.4/signature-v4-crt-container.d.ts": "8c47a3398b3311871d5dcb732a12501d9d9a3c0333e8ef2a0329d391ccfa2ea6", + "node_modules/@aws-sdk/signature-v4-multi-region/package.json": "a500d3298997fbe442acc76fd217dabd4c8310a0085a65578ba868a37d4e3f79", + "node_modules/@aws-sdk/token-providers/LICENSE": "d82ceb8cbd004a2d4834085c557d55459f528e30268d95159ee1e4030a693a6e", + "node_modules/@aws-sdk/token-providers/README.md": "989b25ab630af971c6881029713741f513ee58a6a884e9405fb1803bda2d0245", + "node_modules/@aws-sdk/token-providers/dist-cjs/index.js": "d45a861be3abd682fec912c954858283da2ac251bc227946a6445a84e35e4a75", + "node_modules/@aws-sdk/token-providers/dist-es/constants.js": "2b5f8bd4d0c89717df64cbace3972b5770bdb112f62fcb6446073ed56ff81b7b", + "node_modules/@aws-sdk/token-providers/dist-es/fromEnvSigningName.js": "c392092eab40fcb9e375008524b56907909b5b7d28ea11ef299bddfdc6cad647", + "node_modules/@aws-sdk/token-providers/dist-es/fromSso.js": "a62c31d3b2da5b6fd698df6bff0fe4d9cb8a7fbbdb7305e2e196ea0a621b4322", + "node_modules/@aws-sdk/token-providers/dist-es/fromStatic.js": "05622aa237e468bc58f5aa973fbcc3b3ed325ceb8aaaaa0f3519178c5b668a0b", + "node_modules/@aws-sdk/token-providers/dist-es/getNewSsoOidcToken.js": "bfe7cfbd4f45751feab5f3055a27429b9b9549e6995ff18fb6d6fde7c671d2c3", + "node_modules/@aws-sdk/token-providers/dist-es/getSsoOidcClient.js": "36472a164fc27c6d8b667b85d05c0d454ffe212308ffbbdf23097ec45b9fa899", + "node_modules/@aws-sdk/token-providers/dist-es/index.js": "322cdcfc79e280b7ada8c8bfdb092cf1cffc33ea5687a016009ca293aaf242b7", + "node_modules/@aws-sdk/token-providers/dist-es/nodeProvider.js": "139f04f2c22f1f7f49b67eb113380e7cc9125d71f2819496fe63dbb650a2d148", + "node_modules/@aws-sdk/token-providers/dist-es/validateTokenExpiry.js": "194d61e048d14cc3def041762f71ab9601a3dd0fd4f1141a09cdcdd170daaa81", + "node_modules/@aws-sdk/token-providers/dist-es/validateTokenKey.js": "5813f57538a8adcf651b3ea32f622f836d290818fa9a813e970f585b8ea758b4", + "node_modules/@aws-sdk/token-providers/dist-es/writeSSOTokenToFile.js": "27e6bb6fe7d644431742c536640eef3aace2b62d341ccb0295c564c8d8069e63", + "node_modules/@aws-sdk/token-providers/dist-types/constants.d.ts": "e6aca89959e04aac50acc995dbeb12bc5829cdcee7b6b58474d4248c52e997dd", + "node_modules/@aws-sdk/token-providers/dist-types/fromEnvSigningName.d.ts": "19d7176608ce90e0b1e97d98b4ce8ce2decb7cb18c91f1cb8da42dfe8ec2f418", + "node_modules/@aws-sdk/token-providers/dist-types/fromSso.d.ts": "056b802ccfdbc9f67fffbf9d47f3405f628b502570e0d9b1246156cf1130a4d6", + "node_modules/@aws-sdk/token-providers/dist-types/fromStatic.d.ts": "16e969ff4f6da07bd76be872a6b91ca1083bed081a1cdf2e7e6a5e1089177564", + "node_modules/@aws-sdk/token-providers/dist-types/getNewSsoOidcToken.d.ts": "f54bc1658ee89fc892b4b6277575d008c31ae202fa4c6f9d3ef03d47c9fbbc3d", + "node_modules/@aws-sdk/token-providers/dist-types/getSsoOidcClient.d.ts": "11563fadda9ef6e2292267f499c8b7ae7e27bd775cdc8d12c35a228d96a08847", + "node_modules/@aws-sdk/token-providers/dist-types/index.d.ts": "b652b478a6aeed7ae55e625506af77b5388df1c2fcd1fb2b7ed90a5726b40d49", + "node_modules/@aws-sdk/token-providers/dist-types/nodeProvider.d.ts": "c528778ab8f142a1aa0d10399a2086de5a3b145682a3f1e6ec19e65879950226", + "node_modules/@aws-sdk/token-providers/dist-types/ts3.4/constants.d.ts": "88160adfde061f287fb70cd73b77be9cf7d17057408716f2b69519f5f7019684", + "node_modules/@aws-sdk/token-providers/dist-types/ts3.4/fromEnvSigningName.d.ts": "230eb308847e2561f0111596a4f9d77e14a7fbc679fab66b4d8fb29485024b52", + "node_modules/@aws-sdk/token-providers/dist-types/ts3.4/fromSso.d.ts": "4a16970634b5e3c1bbf6d7ad2e62c099fa6e9f5e2b5b0fe381d866e30f12d9e9", + "node_modules/@aws-sdk/token-providers/dist-types/ts3.4/fromStatic.d.ts": "2f8fe24c56b85c76d84722c23a4daf288cdc428338dd3abc582641af50378964", + "node_modules/@aws-sdk/token-providers/dist-types/ts3.4/getNewSsoOidcToken.d.ts": "1afa3e6030c81b2cdc3913be14bf521fc76960dd7cea99a9858bc409d7551d2b", + "node_modules/@aws-sdk/token-providers/dist-types/ts3.4/getSsoOidcClient.d.ts": "30bdb5b4c38336a07f5772db359aa01787d568c0f1b5312cf5384e7edc425283", + "node_modules/@aws-sdk/token-providers/dist-types/ts3.4/index.d.ts": "f80f855e09b606b74f76cf5992852ed4c0ebb74a54461691fdb03a7e5a5d918a", + "node_modules/@aws-sdk/token-providers/dist-types/ts3.4/nodeProvider.d.ts": "61fb5a8d031772200af2095df73fd53bacd223e5da56942aabd671a9510b634f", + "node_modules/@aws-sdk/token-providers/dist-types/ts3.4/validateTokenExpiry.d.ts": "4738a8970ab75d8eb2265c5cc84e5393705f6b230856b2c576fa1be659cd8f80", + "node_modules/@aws-sdk/token-providers/dist-types/ts3.4/validateTokenKey.d.ts": "e5b3778e06a95a57c7030820f1eb19a4c8d0cdea9ca5e29d950606f292bbc89e", + "node_modules/@aws-sdk/token-providers/dist-types/ts3.4/writeSSOTokenToFile.d.ts": "89312848cfb52cd6cceb4ce938216f77247ef7a170e18e55e0b497833e53f9de", + "node_modules/@aws-sdk/token-providers/dist-types/validateTokenExpiry.d.ts": "7b16d435bb3017da218d453252e0ce33d1f272df3c26eaced5afa70ebd31d704", + "node_modules/@aws-sdk/token-providers/dist-types/validateTokenKey.d.ts": "befc01ecf3d6e2f3ee329673d53a7a3acb5edc41782a0ea18e327de0d47eadcf", + "node_modules/@aws-sdk/token-providers/dist-types/writeSSOTokenToFile.d.ts": "429c81406194264a3aaa9e15478731ea986781b2fabad17ea38c76775c0ed0ad", + "node_modules/@aws-sdk/token-providers/package.json": "8680751f73f36f637b0b96dc5ff4b39578af9b3186195726eef4147758153dee", + "node_modules/@aws-sdk/types/LICENSE": "d82ceb8cbd004a2d4834085c557d55459f528e30268d95159ee1e4030a693a6e", + "node_modules/@aws-sdk/types/README.md": "97e012de14b17cd874a2bb523b6cb43ac88123d50018f10e9611df00d816dc93", + "node_modules/@aws-sdk/types/dist-cjs/index.js": "1df5ead1958b8de220606f08f78dbdb30c2996aee2618d91a4766123a6e114d2", + "node_modules/@aws-sdk/types/dist-es/abort.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/auth.js": "c04f20f339b78d7c57d70056c165ee9e012f99d4843e34d5a9387411a847e271", + "node_modules/@aws-sdk/types/dist-es/blob/blob-types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/checksum.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/client.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/command.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/connection.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/credentials.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/crypto.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/dns.js": "7170e9fa2941152bc1bf2abcf3e7a395835febb9a06e92172d8f1b19d58f7e58", + "node_modules/@aws-sdk/types/dist-es/encode.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/endpoint.js": "609d8717d93551cf25e5d5bce4d36938c0cce01b3f48807e498e9cb454a74108", + "node_modules/@aws-sdk/types/dist-es/eventStream.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/extensions/index.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/feature-ids.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/function.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/http.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/identity/AnonymousIdentity.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/identity/AwsCredentialIdentity.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/identity/Identity.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/identity/LoginIdentity.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/identity/TokenIdentity.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/identity/index.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/index.js": "c071d604f18bd840d65d3f63a26545959168fbb090e0b0dde8bc705c07f1bc9e", + "node_modules/@aws-sdk/types/dist-es/logger.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/middleware.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/pagination.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/profile.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/request.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/response.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/retry.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/serde.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/shapes.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/signature.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/stream.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/token.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/transfer.js": "2bd87b120e0c43b20ac1fa81e450618b0aa22d62f5193f1b09ca6813cb6cd92b", + "node_modules/@aws-sdk/types/dist-es/uri.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/util.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-es/waiter.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/types/dist-types/abort.d.ts": "d522f9eecdc515557485337ea82ef1f81f871cf12f31eaa450245863542e09fb", + "node_modules/@aws-sdk/types/dist-types/auth.d.ts": "c1cafdc54cc03fa644fa54a8e50614e2d18106739faa959edad862a0461963a8", + "node_modules/@aws-sdk/types/dist-types/blob/blob-types.d.ts": "0a2422e5e66df6155a7e1d8a769b964123e1d0b532158c93f1ca0f2e90d98299", + "node_modules/@aws-sdk/types/dist-types/checksum.d.ts": "4520cb90ebcc789d8317c59cdfa77e45b03159d15037b035eca07a3f17865c96", + "node_modules/@aws-sdk/types/dist-types/client.d.ts": "3c294b943dedf4570d539a40927a6db0debb1bfed087e09333a75f9efa04cace", + "node_modules/@aws-sdk/types/dist-types/command.d.ts": "b7cb27b6429fed33062629929c1575f5a35a41e936582758444d1673153b8e72", + "node_modules/@aws-sdk/types/dist-types/connection.d.ts": "07f6e2bab61559b048b29088878bdd43fef13dd5334234713b5d62a2e027990f", + "node_modules/@aws-sdk/types/dist-types/credentials.d.ts": "224d293a02b7d22edb77b4ab89c0d4f63b95ecd7c0698776719f33863a77ffdc", + "node_modules/@aws-sdk/types/dist-types/crypto.d.ts": "0689d46a8fb60b31e042f4c6fddda5efa0a4dfbd7513412919ba2ffd1110071e", + "node_modules/@aws-sdk/types/dist-types/dns.d.ts": "ede55f9bac348427d5b32a45ad7a24cc6297354289076d50c68f1692add61bce", + "node_modules/@aws-sdk/types/dist-types/encode.d.ts": "ff606576bec3d46875b29171044917981d4f004329fee7d6ed162434605f846e", + "node_modules/@aws-sdk/types/dist-types/endpoint.d.ts": "0881f2458db63d2d7f844198e65e9e2998a20fe7ba47feab8521e509a3fa911e", + "node_modules/@aws-sdk/types/dist-types/eventStream.d.ts": "6c3fa751858c237a537b9ec126fe2f8f682ddaa7b8bd983f7e0392654826dff1", + "node_modules/@aws-sdk/types/dist-types/extensions/index.d.ts": "7ffef1ed1c2bc7d9cf2fc134a7e8c68b10416cdbe8e70da8a4bd7ad5c8698d9c", + "node_modules/@aws-sdk/types/dist-types/feature-ids.d.ts": "8ccff36ae040ac45c92b7e4a1f14ead4a1cd7da028581e2e14c43acbbd880e3a", + "node_modules/@aws-sdk/types/dist-types/function.d.ts": "63c0926fcd1c3d6d9456f73ab17a6affcdfc41f7a0fa5971428a57e9ea5cf9e0", + "node_modules/@aws-sdk/types/dist-types/http.d.ts": "6fd117ce77baea6217ae8b696df8723ab1f904101008800b17f62c8d730537d6", + "node_modules/@aws-sdk/types/dist-types/identity/AnonymousIdentity.d.ts": "e70d18d1352550a028f48d74e126a919c830267b38c76ddae4dc1571476a462a", + "node_modules/@aws-sdk/types/dist-types/identity/AwsCredentialIdentity.d.ts": "ae84c3922d4e44d3d0aae016ef31efe002b006233ea1d87378279117a038bad5", + "node_modules/@aws-sdk/types/dist-types/identity/Identity.d.ts": "eb066ecbbfa31a015c67a6be1e198b5191beabc9372fa34164f9f53afea6d4fc", + "node_modules/@aws-sdk/types/dist-types/identity/LoginIdentity.d.ts": "8899fd9f8ab5ce2b3af7ba0e1a47eede6a2a30a269283cc4a934ab755d0aadaa", + "node_modules/@aws-sdk/types/dist-types/identity/TokenIdentity.d.ts": "09de93db5af889d7f4946e2c2c1c60b1898d7ffbcc43f96cc1001dc3d1a0e76b", + "node_modules/@aws-sdk/types/dist-types/identity/index.d.ts": "7e3bb5e389349e9c4ae8cac42f45944ca2a3158604d5cd90a87ebe9e55e9655a", + "node_modules/@aws-sdk/types/dist-types/index.d.ts": "98732843a83a860ded1d827f47c806148cd27074da8d9131da73674c4aa7d5d1", + "node_modules/@aws-sdk/types/dist-types/logger.d.ts": "4ef0a17c5bcae3d68227136b562a4d54a4db18cfa058354e52a9ac167d275bbb", + "node_modules/@aws-sdk/types/dist-types/middleware.d.ts": "f0d090582fab63f714d6289005c6d0342d4d0745c67c3597011c00230afe458b", + "node_modules/@aws-sdk/types/dist-types/pagination.d.ts": "fee39c16287eed8b541d2a6e0dbbdd0912629d2c01aff1213d56afcd8482af81", + "node_modules/@aws-sdk/types/dist-types/profile.d.ts": "20aac65b61c93134db9e2def2b34041c5f3a1d588b209d89841760da79fe8a97", + "node_modules/@aws-sdk/types/dist-types/request.d.ts": "b0a8bf71fea54a788588c181c0bffbdd2c49904075a7c9cb8c98a3106ad6aa6d", + "node_modules/@aws-sdk/types/dist-types/response.d.ts": "6c04172ce0cfa28ebb2a7a985a3ae476f5cfb429102e0dd68f1c5b6d2c5f7d15", + "node_modules/@aws-sdk/types/dist-types/retry.d.ts": "3bbf0ccac9be929f3d53087e050e803ff24046db8a335d77ba536f12503ef87d", + "node_modules/@aws-sdk/types/dist-types/serde.d.ts": "688fd9fbc6509cd3a11abbb21142aea6936caf65dfb6c35218a03ea30387cfa6", + "node_modules/@aws-sdk/types/dist-types/shapes.d.ts": "f2bc23e01ddbde206df946f8ef3b942ba88fb342a257f666b7fbc9d9d7bf5ae1", + "node_modules/@aws-sdk/types/dist-types/signature.d.ts": "cbfb0ee0ecdb3407ac595530598aeae7415669f4cf288657e52891c70b3dd0b4", + "node_modules/@aws-sdk/types/dist-types/stream.d.ts": "138b6bb648c6bd8ca39a1ad908449a80fd7efa41849654fb155711237da28f6c", + "node_modules/@aws-sdk/types/dist-types/token.d.ts": "1810b0b14614e53075d4d1b3e6be512bde19b1ed3a287925c0d24bae8585fa1b", + "node_modules/@aws-sdk/types/dist-types/transfer.d.ts": "25648c5740a9b678b11d8f375c287342cf07a68c631d38c65c791a778af7987a", + "node_modules/@aws-sdk/types/dist-types/ts3.4/abort.d.ts": "a54f60678f44415d01a810ca27244e04b4dde3d9b6d9492874262f1a95e56c7d", + "node_modules/@aws-sdk/types/dist-types/ts3.4/auth.d.ts": "13925cdd93c8cc4576272c4a5479e26b750696f6dc4c66b3c061200a63177a29", + "node_modules/@aws-sdk/types/dist-types/ts3.4/blob/blob-types.d.ts": "27abd2f2ed5aaac951b12b8332aac7970c9cf0cfd88c458f0f016228180b4293", + "node_modules/@aws-sdk/types/dist-types/ts3.4/checksum.d.ts": "901c640dced9243875645e850705362cb0a9a7f2eea1a82bb95ed53d162f38dd", + "node_modules/@aws-sdk/types/dist-types/ts3.4/client.d.ts": "ebb0d92294fe20f62a07925ce590a93012d6323a6c77ddce92b7743fa1e9dd20", + "node_modules/@aws-sdk/types/dist-types/ts3.4/command.d.ts": "b499f398b4405b9f073b99ad853e47a6394ae6e1b7397c5d2f19c23a4081f213", + "node_modules/@aws-sdk/types/dist-types/ts3.4/connection.d.ts": "5e622e6fba584f9ceccda5aab6149230190dd02feb81c2593814ff47d96bc371", + "node_modules/@aws-sdk/types/dist-types/ts3.4/credentials.d.ts": "e81df75895690202c88308dea40d77343d3706c2a6c12281dac10032b0656404", + "node_modules/@aws-sdk/types/dist-types/ts3.4/crypto.d.ts": "1518707348d7bd6154e30d49487ba92d47b6bd9a32d320cd8e602b59700b5317", + "node_modules/@aws-sdk/types/dist-types/ts3.4/dns.d.ts": "607fbf1243511a2114e5c9eaf1f99d999dfdbb1111f2d36facc8ed1fe891a227", + "node_modules/@aws-sdk/types/dist-types/ts3.4/encode.d.ts": "d53a7e00791305f0bd04ea6e4d7ea9850ccc3538877f070f55308b3222f0a793", + "node_modules/@aws-sdk/types/dist-types/ts3.4/endpoint.d.ts": "b4981d2db45f3cf5d5e4bcc4eb5151c3a654bd3db88f10a9febf427afcf4cc85", + "node_modules/@aws-sdk/types/dist-types/ts3.4/eventStream.d.ts": "756ac658bc77f68fd568c4f94f908e76670be226be89861155850540c263c156", + "node_modules/@aws-sdk/types/dist-types/ts3.4/extensions/index.d.ts": "18c4db0c5e3774e7b0d98dd3b3955d22f2d7c14774e5ea5e3b5446d46cdd660f", + "node_modules/@aws-sdk/types/dist-types/ts3.4/feature-ids.d.ts": "986938c699100c87eed420bc1ee3a928cea498046e76ceace2fdf4c37e108e8c", + "node_modules/@aws-sdk/types/dist-types/ts3.4/function.d.ts": "801ae804d876e9a00d893582505b184979d59c84391beaab9160401cda06a650", + "node_modules/@aws-sdk/types/dist-types/ts3.4/http.d.ts": "58da11015b39789b58a8f1566b6aae181ebda606ba71548651452bc142ba0eaa", + "node_modules/@aws-sdk/types/dist-types/ts3.4/identity/AnonymousIdentity.d.ts": "5d0d587c729ddd7f3ffcaf3a256b00902e28567c39a834ec824f576eb68e2545", + "node_modules/@aws-sdk/types/dist-types/ts3.4/identity/AwsCredentialIdentity.d.ts": "7151c70f5f7c09e2bc20cf0626915e7dd4c04e498f2cc48566d6bbf186c9df75", + "node_modules/@aws-sdk/types/dist-types/ts3.4/identity/Identity.d.ts": "068a22b89ecc0bed7182e79724a3d4d3d05daacfe3b6e6d3fd2fa3d063d94f44", + "node_modules/@aws-sdk/types/dist-types/ts3.4/identity/LoginIdentity.d.ts": "8f2cb11e73dfb40bb408300559c70a40dcec462154c9a84b7d2b19fb08069e16", + "node_modules/@aws-sdk/types/dist-types/ts3.4/identity/TokenIdentity.d.ts": "dd034834e07d731cb68962135031efb602b2b1b1ec5223381c541297e1a95540", + "node_modules/@aws-sdk/types/dist-types/ts3.4/identity/index.d.ts": "fd82972d49b655e4d6053e4e11a1eee8d7262a59c8cf7d61423934ff0ad0c439", + "node_modules/@aws-sdk/types/dist-types/ts3.4/index.d.ts": "304cad4da222b60da356a0417f333a7ef727b1cf8c405f9cbd00d57d044fca6c", + "node_modules/@aws-sdk/types/dist-types/ts3.4/logger.d.ts": "eeb4c971ca059b598a68cf2892b5c4699f5fc23935fd3ba3d291258b2056516d", + "node_modules/@aws-sdk/types/dist-types/ts3.4/middleware.d.ts": "b09dae82dfe1fbb95f40c6e75cf9ffaebef293b39641cfb976e3dfde68533685", + "node_modules/@aws-sdk/types/dist-types/ts3.4/pagination.d.ts": "64269ed536e2647e12239481e8287509f9ee029cbb11169793796519cc37ecd4", + "node_modules/@aws-sdk/types/dist-types/ts3.4/profile.d.ts": "c06fd8688dd064796b41170733bba3dcacfaf7e711045859364f4f778263fc7b", + "node_modules/@aws-sdk/types/dist-types/ts3.4/request.d.ts": "e978a8a1cdda0908df9d74a3bada41e14fb0a346cfa4696f573ba25b475e0296", + "node_modules/@aws-sdk/types/dist-types/ts3.4/response.d.ts": "8e27d6c754ad5af35e3a4c40db7c3b049c34bc7f7bc23d6cb87e131227ccbcc9", + "node_modules/@aws-sdk/types/dist-types/ts3.4/retry.d.ts": "9dc420866da49430e1f8165ee2dd5ef5ce820fb970e15b3b4d87feb90b526ec0", + "node_modules/@aws-sdk/types/dist-types/ts3.4/serde.d.ts": "2a461938096bf6ff3338032c9aef4a4a915c6ec4f877cd4c188a0ae3e9c0e3bd", + "node_modules/@aws-sdk/types/dist-types/ts3.4/shapes.d.ts": "14a8ec10f9faf6e0baff58391578250a51e19d2e14abcc6fc239edb0fb4df7c5", + "node_modules/@aws-sdk/types/dist-types/ts3.4/signature.d.ts": "2f012457ed41d60e6ba2d4908987cd4b9dfc6576b5d9bfe579c0dd3c2d35a4a4", + "node_modules/@aws-sdk/types/dist-types/ts3.4/stream.d.ts": "4992afbc8b2cb81e0053d989514a87d1e6c68cc7dedfe71f4b6e1ba35e29b77a", + "node_modules/@aws-sdk/types/dist-types/ts3.4/token.d.ts": "3bfaac0cdaee0a3035b9c5200007f455357241b5e27a746af86608cbacf9bbd4", + "node_modules/@aws-sdk/types/dist-types/ts3.4/transfer.d.ts": "4052f320afe32f86cd769505fe2354cc02f697ae9b393261d18079e51d9a5bfa", + "node_modules/@aws-sdk/types/dist-types/ts3.4/uri.d.ts": "ec8b45e83323be47c740f3b573760a6f444964d19bbe20d34e3bca4b0304b3ad", + "node_modules/@aws-sdk/types/dist-types/ts3.4/util.d.ts": "f28a5f5016d5ec14fcb280c02ae234dd9e51801d8710749f6dd694cd6e24e949", + "node_modules/@aws-sdk/types/dist-types/ts3.4/waiter.d.ts": "ab8b86168ceb965a16e6fc39989b601c0857e1fd3fd63ff8289230163b114171", + "node_modules/@aws-sdk/types/dist-types/uri.d.ts": "2463724d2e8f65699d108a5bd2e1be2dcd0ca58c6958a2c1f5764c2793bf3024", + "node_modules/@aws-sdk/types/dist-types/util.d.ts": "3b475af000da9b8fb2ce4bc3cc4e7fbf3658d4c828c3519785c5df6ab2fb115e", + "node_modules/@aws-sdk/types/dist-types/waiter.d.ts": "3213386d3d6049137ab26a2d04ad0728c968348cbc459e5fb9456b07a01e8944", + "node_modules/@aws-sdk/types/package.json": "bdd916bfcfac454fb2ba9ff2dd2e581d10f946654515ca860bf4b8f4e4b82561", + "node_modules/@aws-sdk/util-dynamodb/LICENSE": "d82ceb8cbd004a2d4834085c557d55459f528e30268d95159ee1e4030a693a6e", + "node_modules/@aws-sdk/util-dynamodb/README.md": "4d034c1997f7c1b8de80381d85b7d646e6f75299c7b4901c79cdac36a523f27c", + "node_modules/@aws-sdk/util-dynamodb/dist-cjs/index.js": "33037a82a527566ae12771f3f5b11ebf6793922cb87a4027681de921712461a1", + "node_modules/@aws-sdk/util-dynamodb/dist-es/NumberValue.js": "c168e6a23e443cb043ab798ea60292166e2c8cb911752f38b12d9d7f7ba30442", + "node_modules/@aws-sdk/util-dynamodb/dist-es/convertToAttr.js": "185e142763036e74e8cfe8ed51e023abed6dc385b269c0883d53cd621353f623", + "node_modules/@aws-sdk/util-dynamodb/dist-es/convertToNative.js": "38b8376dcc0b203cfa6183b54644313542b77858e1d083f29b681a821c71dfe9", + "node_modules/@aws-sdk/util-dynamodb/dist-es/index.js": "c330100ed846e4690fa0380b72d954fc42a09226fdc5052bedd13d58242f03ac", + "node_modules/@aws-sdk/util-dynamodb/dist-es/marshall.js": "7b056e6d3b0f4891081852a3fe3792bb54668c8096156664ce82e4cb1f8bd429", + "node_modules/@aws-sdk/util-dynamodb/dist-es/models.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/util-dynamodb/dist-es/unmarshall.js": "0d8d2d619634a9d0442d60cef7c3e36908ce23232f840c6aca434730f632baa5", + "node_modules/@aws-sdk/util-dynamodb/dist-types/NumberValue.d.ts": "07df30c41e7b42e5e216f17330abad57218d482c1dddaf5e71805bd8eefb9fd8", + "node_modules/@aws-sdk/util-dynamodb/dist-types/convertToAttr.d.ts": "956431213cc492c56f5688bcaa9c9c2a5a7d726780ff9e91181ade6b2b26c7c7", + "node_modules/@aws-sdk/util-dynamodb/dist-types/convertToNative.d.ts": "29769f3c9a82e4ccde4875c2c80f9ee9e74732eb50c50ddd03cb58c6ebab0cc9", + "node_modules/@aws-sdk/util-dynamodb/dist-types/index.d.ts": "205d7ad0f080bbef95d54594afeda129a2afe6d98d0034cf3137eb61182bfcec", + "node_modules/@aws-sdk/util-dynamodb/dist-types/marshall.d.ts": "f919d464eda274851e3fe772e6ae2c3ff5dcbf9ef35c65c0003784f0cc019780", + "node_modules/@aws-sdk/util-dynamodb/dist-types/models.d.ts": "c960d2aeabc5a7fd8e4b43e2f311a9f3e3f4693b7979262c23b46fc73df7304a", + "node_modules/@aws-sdk/util-dynamodb/dist-types/ts3.4/NumberValue.d.ts": "79d191a9ec7bf3edd1bb2febf67d64d874e93d748be6848f659b2b1385debf67", + "node_modules/@aws-sdk/util-dynamodb/dist-types/ts3.4/convertToAttr.d.ts": "2c8844c452e271a26518c60ea787a75b8e7108c37281c751a6486fd78e2a6ee9", + "node_modules/@aws-sdk/util-dynamodb/dist-types/ts3.4/convertToNative.d.ts": "0566a7f386012f3d3cdb744b5eae51d4cf5a3d5f482bfb887470e39ed0ba2d01", + "node_modules/@aws-sdk/util-dynamodb/dist-types/ts3.4/index.d.ts": "8ad4f6f8dacaa0fc9886dec4830b9e0ce0bfc8b6ae87b171c99daf599362105f", + "node_modules/@aws-sdk/util-dynamodb/dist-types/ts3.4/marshall.d.ts": "034db16568dca61080a7375bd19d0bb03e43ff6f5a12b2044839b5faccf222f9", + "node_modules/@aws-sdk/util-dynamodb/dist-types/ts3.4/models.d.ts": "02b1408934946bae504f84d62b4636ddcfbe585dbb8ac096f84df0a6fd92522d", + "node_modules/@aws-sdk/util-dynamodb/dist-types/ts3.4/unmarshall.d.ts": "478a72794af79a069179271103759fc65423e6ad01bfdc94ae048f9d7c3fc188", + "node_modules/@aws-sdk/util-dynamodb/dist-types/unmarshall.d.ts": "0df3352b99f44816e8d935f2ebe44a1ec5529c7bda5ffc26813cce920145af86", + "node_modules/@aws-sdk/util-dynamodb/package.json": "a008493d0b99dffa77962e9beb74cc87eac504e3fdf2bb5b827cef7e5554ffd5", + "node_modules/@aws-sdk/xml-builder/LICENSE": "d82ceb8cbd004a2d4834085c557d55459f528e30268d95159ee1e4030a693a6e", + "node_modules/@aws-sdk/xml-builder/README.md": "fc63b958e69bf69ab364af775a058b012a41a78dc27462eb4bee680b54546c2b", + "node_modules/@aws-sdk/xml-builder/dist-cjs/index.js": "ebaa02fe69fb4bf244f2ed10cdbb3ef9c725ab5b39be4b36532a68308804ff81", + "node_modules/@aws-sdk/xml-builder/dist-es/XmlNode.js": "7368beecab2f5ad013933c918ada5f855d33d880bcf15e842c9472837f109043", + "node_modules/@aws-sdk/xml-builder/dist-es/XmlText.js": "58749c40d076a192d80e9da718a1d67362171724bee766a38c7f1146f575ca9a", + "node_modules/@aws-sdk/xml-builder/dist-es/escape-attribute.js": "3f5c273f64f60ea1532b085a3c9c73de17280fe697063a4f75f38ccfbc8eb63c", + "node_modules/@aws-sdk/xml-builder/dist-es/escape-element.js": "6d266b41de3387f1439d5e746423163a44920f16ab0790df86e18194058c8a5b", + "node_modules/@aws-sdk/xml-builder/dist-es/index.js": "53cbfb67f7683935f0a7cb57e82234cfb02feaf838cae1e3bac3419b9c20eaa7", + "node_modules/@aws-sdk/xml-builder/dist-es/stringable.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws-sdk/xml-builder/dist-es/xml-parser.browser.js": "f0283367a322119ec2c0ad6df8b66d38074215b7d19cae4a031728c0e70fdc5e", + "node_modules/@aws-sdk/xml-builder/dist-es/xml-parser.js": "5cf78ee442ca71df8037c69a182be175727362c3591b4007d31768f20b25c25b", + "node_modules/@aws-sdk/xml-builder/dist-types/XmlNode.d.ts": "def658e89135ea3523547a8e43fce5836b880c7a20639d1038434f3caa4387f9", + "node_modules/@aws-sdk/xml-builder/dist-types/XmlText.d.ts": "abc1ec65c3513a5e18787bf46602876c94ccaab78d3efe40149502ffd5fe7e94", + "node_modules/@aws-sdk/xml-builder/dist-types/escape-attribute.d.ts": "0d11aa52518c3cced9a396f38e814a9790ed537e98c2cec8bdd8d380a473d686", + "node_modules/@aws-sdk/xml-builder/dist-types/escape-element.d.ts": "cbe05325fd5fb0f307c101f0ef848ae48ea83e01b559bd677bb15c56fca9defb", + "node_modules/@aws-sdk/xml-builder/dist-types/index.d.ts": "53cbfb67f7683935f0a7cb57e82234cfb02feaf838cae1e3bac3419b9c20eaa7", + "node_modules/@aws-sdk/xml-builder/dist-types/stringable.d.ts": "22ab0b7c7c3bea5818cdcce716a71ac32e12e6adc00a79c939498ace6e44abba", + "node_modules/@aws-sdk/xml-builder/dist-types/ts3.4/XmlNode.d.ts": "c8d03c458acc7bd80c7c5f6f0bd5659b83b5517b5aca2eb284e0ef19cf850272", + "node_modules/@aws-sdk/xml-builder/dist-types/ts3.4/XmlText.d.ts": "a93592ce609046ad2a5a5714d3efe0291b726eee8bfaf2b0fbd4abfbe6dabcf7", + "node_modules/@aws-sdk/xml-builder/dist-types/ts3.4/escape-attribute.d.ts": "4f1748224753a97d466a35c828b0e1f0ccb9a510b3b3648276e35ac1a27cef3c", + "node_modules/@aws-sdk/xml-builder/dist-types/ts3.4/escape-element.d.ts": "b16ad129d2a54bc514b59a1b722fc950df09ec357eb4baa7455e9d813cf6f129", + "node_modules/@aws-sdk/xml-builder/dist-types/ts3.4/index.d.ts": "53cbfb67f7683935f0a7cb57e82234cfb02feaf838cae1e3bac3419b9c20eaa7", + "node_modules/@aws-sdk/xml-builder/dist-types/ts3.4/stringable.d.ts": "6328186b90835acd7da1459ed3624936c1489f9a2ac7330ceffc94aa3ccb975e", + "node_modules/@aws-sdk/xml-builder/dist-types/ts3.4/xml-parser.browser.d.ts": "627b7c81ea391d34023c90e20cda5a53342075ad88e9257555b1c2afa66ec421", + "node_modules/@aws-sdk/xml-builder/dist-types/ts3.4/xml-parser.d.ts": "79778d98ccbf7ad3ca237de1afc10fdd44527b8d4c7c28b7ecd5a12ce1c87833", + "node_modules/@aws-sdk/xml-builder/dist-types/xml-parser.browser.d.ts": "285766822459ce2dad2a2f6ced8f3533ab8685e5c3e59f902f03c804c442b53f", + "node_modules/@aws-sdk/xml-builder/dist-types/xml-parser.d.ts": "4bc3c7b50e22556529235696faff355bf866fbe0c02abeeb51e17e7adb2679cc", + "node_modules/@aws-sdk/xml-builder/package.json": "103e94854d3f01cdb7eda3d170d3a8775e4fb107da472a1347418abb76116e08", + "node_modules/@aws/lambda-invoke-store/LICENSE": "09e8a9bcec8067104652c168685ab0931e7868f9c8284b66f5ae6edae5f1130b", + "node_modules/@aws/lambda-invoke-store/README.md": "c683d7aff8dcbf7e6cdb1a18c87564b6907aca736d8760d287ee449ed6481504", + "node_modules/@aws/lambda-invoke-store/dist-cjs/invoke-store.js": "72f6c53af1d1604f883c9951f96f83ee507be24a165dc9e8199261efa872c52c", + "node_modules/@aws/lambda-invoke-store/dist-es/invoke-store.js": "526b850ed7866fa67d824b21b81d9e4b9cc2784d3be8903b0fafd91902fee87a", + "node_modules/@aws/lambda-invoke-store/dist-types/invoke-store.benchmark.d.ts": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@aws/lambda-invoke-store/dist-types/invoke-store.d.ts": "b054fd9f07794cc6a3a8cd54a7aac7a2e0e3eee762ea77ea15c2437bc70e075d", + "node_modules/@aws/lambda-invoke-store/package.json": "67dd0cc15cb17df9e12654a78486e710e13a2e96623cb28c683056378fa973dc", + "node_modules/@smithy/core/CHANGELOG.md": "8163f31980f790dbfd7364157471c3640729ac258d39b2e863b1dbd5fa0eebcb", + "node_modules/@smithy/core/LICENSE": "07364a5f7d0177a59a05a0002187431f8f899320983c0dc049d72df015e8064c", + "node_modules/@smithy/core/README.md": "9117221029fcfd403f75f0d3d72710ac0bebac89a200469cb160a5f35dc101a4", + "node_modules/@smithy/core/cbor.d.ts": "9aeae3abe0cc1fe721e01a04cdc5ab72438aa8a3c9297bf2d9137002f5ec81aa", + "node_modules/@smithy/core/cbor.js": "311ed187bd28ecc2e7f8f9da85a15ea59c76a623d0b4d0673b1bf4d704729ba0", + "node_modules/@smithy/core/checksum.d.ts": "da808a64068b142150ef0cab1b7677306b91aaae613973cfce363722a3e4b643", + "node_modules/@smithy/core/checksum.js": "a59e83d971117013e5f4ab519d4408ee44722d732118eaacdfacd035db2b9c9a", + "node_modules/@smithy/core/client.d.ts": "f80a670faae8df9f4fda7923fa121d6d8d72d6e1c99c7f48c51b29404ab8cd93", + "node_modules/@smithy/core/client.js": "417d11f5b9f74e14c82d4039a220e1cb0935b5fef34d6cfe454b48558ba610a0", + "node_modules/@smithy/core/config.d.ts": "5f2521eeab560f30610c1f273d160970a37e667bb35fc246cd7750cd402b7e96", + "node_modules/@smithy/core/config.js": "f37a612df0f8b3812120d1dfb6aabaf9e11d54847b1bd12a9fe09faee4af1b58", + "node_modules/@smithy/core/dist-cjs/index.js": "83e42ec320536aae02b30414d92e3d8444378dafd7917c017c403cd6f0d18b7a", + "node_modules/@smithy/core/dist-cjs/submodules/cbor/index.js": "4997210ba6390531bc5bbafee23546b0b51149e7d5fe160e9fc964959a9f65dc", + "node_modules/@smithy/core/dist-cjs/submodules/checksum/index.browser.js": "30afc2fea5929c914995551f1b517b63e67560e6aa5e230461fe0fc4ea22fcaf", + "node_modules/@smithy/core/dist-cjs/submodules/checksum/index.js": "20dd32732b12c09fe4daaa474cad31024708c0dd8b4615f2190a8a5ba99bca48", + "node_modules/@smithy/core/dist-cjs/submodules/checksum/index.native.js": "60e9a00f9c6165f187374f005a29adb3a05bfd14d9df3f874115e8d248aaebaa", + "node_modules/@smithy/core/dist-cjs/submodules/client/index.js": "303fb99a463ede48455751f02a1001ffa2d6737b70407647501eeba292ceded6", + "node_modules/@smithy/core/dist-cjs/submodules/config/index.browser.js": "c1311490f186e47becc5947f71e72ee2504c43933d12771e8676d84d4ede3076", + "node_modules/@smithy/core/dist-cjs/submodules/config/index.js": "9baacc5d01f356ac128466faec6878d298a5598212e1adad6a6e527048fbb480", + "node_modules/@smithy/core/dist-cjs/submodules/config/index.native.js": "3eb82aef9caa0613958482e09d9dfdec1b560f6109466cd99ec8d6662736eff6", + "node_modules/@smithy/core/dist-cjs/submodules/endpoints/index.browser.js": "59b8ce8c68873485a9feac2e9edcdb3f6d7c0893f16e4292c08d9dbecab0fcd9", + "node_modules/@smithy/core/dist-cjs/submodules/endpoints/index.js": "03fbf5509b2184918708e8b841b3e54d5ff99d790394e4c62ff26829802aa3fe", + "node_modules/@smithy/core/dist-cjs/submodules/errors/index.js": "02e1ae9f962f6db4799cee85912bac0ff10f7e25515b721070a83219b7e313e0", + "node_modules/@smithy/core/dist-cjs/submodules/event-streams/index.browser.js": "3af1b2d115cac54ceb308c0143f21c4a38fbc735b9117cd263e57ab15a27a7f5", + "node_modules/@smithy/core/dist-cjs/submodules/event-streams/index.js": "cee851751aab1e92ed25ec4b11a14671f832bd779b42275b2f1d8b4b583467b0", + "node_modules/@smithy/core/dist-cjs/submodules/protocols/index.js": "3b90ccfb57034cee8ed5aa684b9e97b3b42f3ae9a643637e4485928da0e86ea6", + "node_modules/@smithy/core/dist-cjs/submodules/retry/index.browser.js": "33fec5516726cb214418636224fded0212f171f77d2eaa5a6a5edff92d567451", + "node_modules/@smithy/core/dist-cjs/submodules/retry/index.js": "fa9bbb94190f640b166ca4a1a10cddaaef12faac6713ea9f01eed8787f931689", + "node_modules/@smithy/core/dist-cjs/submodules/schema/index.js": "e8b2146760df564a80152db16fa1d3ef9bc8ae4bc6e935cd9ba6887f954404e3", + "node_modules/@smithy/core/dist-cjs/submodules/serde/index.browser.js": "660b014a84a37b1b26ec56be33b85f9f3fdcc4612f42cd768ddaddae694ae077", + "node_modules/@smithy/core/dist-cjs/submodules/serde/index.js": "13b829fb16857d4544ed918eb377f563a9211d47729c3b1889570441e209db1e", + "node_modules/@smithy/core/dist-cjs/submodules/serde/index.native.js": "660b014a84a37b1b26ec56be33b85f9f3fdcc4612f42cd768ddaddae694ae077", + "node_modules/@smithy/core/dist-cjs/submodules/transport/index.js": "aafc7119f52b853c11b5f3a02bf33a7f350770630d8ea9e149f374adbc69bd14", + "node_modules/@smithy/core/dist-es/index.js": "04a0618d336963025211e57271eb0c71c77553eca1d8a73ee3765e255aa0edbc", + "node_modules/@smithy/core/dist-es/legacy-root-exports/middleware-http-auth-scheme/getHttpAuthSchemeEndpointRuleSetPlugin.js": "5efb92f63c5e5227e43c6cffc1bcb35eca43e916b34a79f051d1fb230729914c", + "node_modules/@smithy/core/dist-es/legacy-root-exports/middleware-http-auth-scheme/getHttpAuthSchemePlugin.js": "b0857909091775595857574611d5c70b1054c6f390dc8682e2fff9560d166ac1", + "node_modules/@smithy/core/dist-es/legacy-root-exports/middleware-http-auth-scheme/httpAuthSchemeMiddleware.js": "fa279e03dd04d557d4d7487a82cd18a31911b41c401378250f8b469a7c235ef2", + "node_modules/@smithy/core/dist-es/legacy-root-exports/middleware-http-auth-scheme/index.js": "81c8e949e93617be4f3f95e5d9e97e3cdb2f169661e291b6d0c1bd7864ce50f0", + "node_modules/@smithy/core/dist-es/legacy-root-exports/middleware-http-auth-scheme/resolveAuthOptions.js": "f0262f541cde5dfa93a26906c4760462a14ecc1dd89f9dc66a93607f2c7d02e5", + "node_modules/@smithy/core/dist-es/legacy-root-exports/middleware-http-signing/getHttpSigningMiddleware.js": "faf80fd09eb4ef9897aef2a3c4713b7ef1ec6ef0be09dcaf7d5b581b2450de82", + "node_modules/@smithy/core/dist-es/legacy-root-exports/middleware-http-signing/httpSigningMiddleware.js": "3b7e0f86966053000e218d2031cbbb9c8530bde3cd9cc4f169f2716f2f544ec3", + "node_modules/@smithy/core/dist-es/legacy-root-exports/middleware-http-signing/index.js": "b9dae05d568913b25e3346a1fc51337c0def3a9534765bdf56af59fa4387aa6e", + "node_modules/@smithy/core/dist-es/legacy-root-exports/pagination/createPaginator.js": "077d55e46108b1b8bbdeee7534b26f0e1292562940bb284130ca288a35ae4ae2", + "node_modules/@smithy/core/dist-es/legacy-root-exports/util-identity-and-auth/DefaultIdentityProviderConfig.js": "11a539c9128d0a8da65d42df7e4cafd76077406a0553781ee20bfde7ed0475d4", + "node_modules/@smithy/core/dist-es/legacy-root-exports/util-identity-and-auth/httpAuthSchemes/httpApiKeyAuth.js": "bfafa715c295758a0b7ba63547b803d18bc7774ca0f4d5abd67e81df3fb98039", + "node_modules/@smithy/core/dist-es/legacy-root-exports/util-identity-and-auth/httpAuthSchemes/httpBearerAuth.js": "f6d143492dee6d7ecf4b054af867032b509d47ce5dc8e56b0a3ca270d4a1c9ec", + "node_modules/@smithy/core/dist-es/legacy-root-exports/util-identity-and-auth/httpAuthSchemes/index.js": "f94f44826912961b5fd26c56ae20fee6a78c2c5a181280184dfc56480f3226b3", + "node_modules/@smithy/core/dist-es/legacy-root-exports/util-identity-and-auth/httpAuthSchemes/noAuth.js": "7c87b25d9347f430e74566bc052a9143217f8b9966cdfcfa4cb249935602b80a", + "node_modules/@smithy/core/dist-es/legacy-root-exports/util-identity-and-auth/index.js": "86ccd7d964d46ffec14c1f38423accd4f50f942d4497ed0f5bcd73916f3fc701", + "node_modules/@smithy/core/dist-es/legacy-root-exports/util-identity-and-auth/memoizeIdentityProvider.js": "593d8a63184db131ca28988901611c2120c07a47f87378bad3b0b713d3ade6a0", + "node_modules/@smithy/core/dist-es/normalizeProvider.js": "e927e9b665c202b4703bfedb3ed09a8ec8b9c9ef22d18c4e496585b16483307d", + "node_modules/@smithy/core/dist-es/setFeature.js": "17942102089d90bdb390ad51454520d90472ae93c43363f1efa7be5ba5d311a4", + "node_modules/@smithy/core/dist-es/submodules/cbor/CborCodec.js": "76d0189266409108ed24ded56a037410ad2f115bdb63e5f166872812ca68a9e8", + "node_modules/@smithy/core/dist-es/submodules/cbor/SmithyRpcV2CborProtocol.js": "a52fce3408bc6d67a5023628e3e79e7651d341f4db3d5f4422a6232ec1be72cc", + "node_modules/@smithy/core/dist-es/submodules/cbor/byte-printer.js": "b25dd6c1130aefac0ec8336ddb952c41e555cab92b373eff530b5b658b239d7a", + "node_modules/@smithy/core/dist-es/submodules/cbor/cbor-decode.js": "50d80b35906a3aa6fc12655b9fc8c2cd6c6018b429be43bdd6557b9f4cfa5445", + "node_modules/@smithy/core/dist-es/submodules/cbor/cbor-encode.js": "053c7ebbb4cc40dfcc1fd9008324bfd802dc695d7b89141d8616e3cefd053dbc", + "node_modules/@smithy/core/dist-es/submodules/cbor/cbor-types.js": "69298926456a17b1ec626251c72303264f8347a2877fc6f7175691aeee9867b3", + "node_modules/@smithy/core/dist-es/submodules/cbor/cbor.js": "3940d176cec6c8fa305ba006cbf0a6ffba486a9d1e8c7cf9b8722d90c2a0fd3c", + "node_modules/@smithy/core/dist-es/submodules/cbor/codec-v1/CborShapeDeserializer.js": "d9efca2eacf84095cdcb938af2602cacd89c47a5adcd268bc6db11dfbe9d88da", + "node_modules/@smithy/core/dist-es/submodules/cbor/codec-v1/CborShapeSerializer.js": "a8ea36daa4987597ae8483556a08aed89ee7b32c2bddd1c4ee59eb61655a3938", + "node_modules/@smithy/core/dist-es/submodules/cbor/codec-v2/CborShapeDeserializer2.js": "b33617ec50d36aae374a20d625ebdd27b8d98b47af4b0f2e74c8acad3d91fab2", + "node_modules/@smithy/core/dist-es/submodules/cbor/codec-v2/CborShapeSerializer2.js": "a723f75b1220b3e763c9131a96fda50e39826c380b0dd1c7f1ea4577c0f5b9c2", + "node_modules/@smithy/core/dist-es/submodules/cbor/index.js": "4c61fce184e7305205bd06bbc13fbe3dffab69a7824c771ac03b8c1840d5432f", + "node_modules/@smithy/core/dist-es/submodules/cbor/parseCborBody.js": "819aef7256ef955eb825570d6e0590e1f51c017e4b8ec679ad1434cab4c00e7f", + "node_modules/@smithy/core/dist-es/submodules/checksum/chunked-blob-reader/chunked-blob-reader.js": "8ac3a5bab54af0ce2e04b154b70d2bb1b010593094cfb37a248771e9f82b4e5c", + "node_modules/@smithy/core/dist-es/submodules/checksum/chunked-blob-reader/chunked-blob-reader.native.js": "8b71987e005cc5fe4cf62f593293e4ebe03c4cfca3ddd174914cb16ae5eb8d91", + "node_modules/@smithy/core/dist-es/submodules/checksum/crc32/Crc32Js.js": "3edb3089f1774cafe7ceda7ce598ee0ea07b92f41f395c511963460cdfd46e36", + "node_modules/@smithy/core/dist-es/submodules/checksum/crc32/Crc32Node.js": "ede4151e2e19d7a2c54bede9530bac7476ab2d1d623952354d0fd718bf4c87f8", + "node_modules/@smithy/core/dist-es/submodules/checksum/hash-blob-browser/blobHasher.js": "0a3df70e39448424397e522a3a26a5fb3455f15d4de91a0c37492d635fad0cb1", + "node_modules/@smithy/core/dist-es/submodules/checksum/hash-stream-node/HashCalculator.js": "d5688cc0ffd5dcb8fa1d49e036896fd5836f5fa2cbabd82aaa2e54e61daa3b6c", + "node_modules/@smithy/core/dist-es/submodules/checksum/hash-stream-node/fileStreamHasher.js": "fb70bae40768b5868748fd49d5b7e20a38850c9fba8d5f567aa7d43ca9d07e7e", + "node_modules/@smithy/core/dist-es/submodules/checksum/hash-stream-node/readableStreamHasher.js": "8d5b5887eefe6e88820fb115eaa007e04555d8819f02f07473c3cbe8287dd156", + "node_modules/@smithy/core/dist-es/submodules/checksum/index.browser.js": "4340af669e38ac03c83214a1e9e42223e3646b86f2fe7c6187bb91ba47d011b2", + "node_modules/@smithy/core/dist-es/submodules/checksum/index.js": "f310e60b371cd8d16e5eb17499ec3ea478029b4a3f779024caf729f3d4174f8c", + "node_modules/@smithy/core/dist-es/submodules/checksum/index.native.js": "6b9b572bf4ead6f028caa64c7abaeb242e4efe7bf02f18f1c8254c9b238d469a", + "node_modules/@smithy/core/dist-es/submodules/checksum/md5/Md5Js.js": "b80b06322eea17132dfa625f2a160518b3cee719d77fe2a2b81b69e0d15028eb", + "node_modules/@smithy/core/dist-es/submodules/checksum/md5/Md5Node.js": "e918d4688f912a132c8d007d9c77d686de48a972f1a4d6ad15f77c8e0ebd8bc6", + "node_modules/@smithy/core/dist-es/submodules/checksum/sha256/Sha256Js.js": "54d26649ccb5c6f5d34f89bf22167db9f91eeab6c05af115f7b0dbf041081ec4", + "node_modules/@smithy/core/dist-es/submodules/checksum/sha256/Sha256Node.js": "0599f9253cbb8ca468a840d1eab931c5029fa0c97d4eb47e1017a3f32d45f33c", + "node_modules/@smithy/core/dist-es/submodules/checksum/sha256/Sha256WebCrypto.js": "66ba14eccbef4a63912dbfbbf885d62ff1c2bd5b530e9b41642c576c47aa9285", + "node_modules/@smithy/core/dist-es/submodules/client/index.js": "448be5fc1c12166139a35add4363415d18a520ca9eff7e92b9ba8424f025c112", + "node_modules/@smithy/core/dist-es/submodules/client/invalid-dependency/invalidFunction.js": "249e731ec4ad963b6b9dd7cfee1cb1625dadcf9dcbf07e93784b715c5820402c", + "node_modules/@smithy/core/dist-es/submodules/client/invalid-dependency/invalidProvider.js": "b3b5899098cacda6f82f5897f6476272b09595707bcb7c4d952edec7ab962f3b", + "node_modules/@smithy/core/dist-es/submodules/client/middleware-stack/MiddlewareStack.js": "f42af4aef1b6582c76f7ebfc3ec350ac85db5f7f74ba04e5f0dd6f3530f47008", + "node_modules/@smithy/core/dist-es/submodules/client/middleware-stack/types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/NoOpLogger.js": "ff7a367cade6bc53cd94cd68437ddfa798683b1416f19c6a26fafb79695a79a7", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/client-command-builder.js": "0b35bfe414ae3124af3036f7912a6fa273d345e80350430dd159e9e1cb8ec6cd", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/client.js": "aba79a9f480b403e683f92b868c4b0a98cef0d0425b91c8b5fac93eca92bc55a", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/command.js": "b1f2b9f760a088a735f312826199079dee851be01ae0e8a18c4386d92bf91e92", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/constants.js": "0bffe9d7b8beb2d44d82bbf98a0f2e0057a7ac2fab47ea07b4d4a2c7a5f4fba0", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/create-aggregated-client.js": "37a599a0edf7536dcdff8ddff34b56a6db001e7238b03aa54f77bf199bbc36a7", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/default-error-handler.js": "0ed06c24a6f3791ffad9a8576c32778a020595de0bd3fa7ef1aa87f49e7dcd7d", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/defaults-mode.js": "9b45c8f3f8d88787b47093e4b8cbff90a8f398b2e1b0601e5417a17a142c9ae9", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/emitWarningIfUnsupportedVersion.js": "6d7ae8d753c32c1515a791d5ae327e6930c675310790db27a3b43f4d292f9ed9", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/exceptions.js": "08faa62f21eca1cd5622927f359e324cb71fd0a784f10115f3088706d5d17bd0", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/extensions/checksum.js": "1d1c58f7f5ea04f63d9563616ad507300becd946321c5f52c707d6c7fd7d1809", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/extensions/defaultExtensionConfiguration.js": "860a566aebb9aed01d99b883527bd4c60b49ef505a03e5e35dab7956d9bad7a9", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/extensions/retry.js": "dd290468245ba9c8b1878c9225d11bccad7da7b9cdd2d448eae0bd99109b9975", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/get-array-if-single-item.js": "f88f6c3500813516da7f912d3e8cb1a70483d331a52def0a425bedd3019d77d6", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/get-value-from-text-node.js": "3bd5f48d7615b84df471d77c3894ebc66306a3be0f6eebf59e9722226db541ba", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/is-serializable-header-value.js": "e3ab3d19b8faa9f60689c28e9fc23ee9694cfa90d247a9fdb46320c619547ebe", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/object-mapping.js": "46e6c8c3c71f557642b448cd71a570f200a8bf285f45c4e09224313a833230ce", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/schemaLogFilter.js": "7df786e7c7bd56bb8b9741a8941018bb797b3abcba92e44866bf841b8593ac41", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/ser-utils.js": "6ec809cd94327f9ec91cf4f218a6ee5c9c772b144abf639194cd65f1d88d36e2", + "node_modules/@smithy/core/dist-es/submodules/client/smithy-client/serde-json.js": "ede3e463b39331074dc45df56f7919011f32a854f9f0947b700cfbc38b51d44c", + "node_modules/@smithy/core/dist-es/submodules/client/util-waiter/circularReplacer.js": "13ed2d5469c606c833ece080863d2a9f375f352abc032f50aff276594cb0ca0c", + "node_modules/@smithy/core/dist-es/submodules/client/util-waiter/createWaiter.js": "6cfdf3489d0298542a51270112c495cc8e46c7baae9b8ca6b15b0ccd7a11b9f5", + "node_modules/@smithy/core/dist-es/submodules/client/util-waiter/poller.js": "464bd1b698b7c3282f31a8f964420dae8f0128bba553cbdf41e09083e6d4fd10", + "node_modules/@smithy/core/dist-es/submodules/client/util-waiter/utils/sleep.js": "8c7e23c07ca30b54056ffc02b4a8838dbe19b665f7e09f9fc08bf4e3303ef08d", + "node_modules/@smithy/core/dist-es/submodules/client/util-waiter/utils/validate.js": "badb1e1db39cc9278f6ccef0c2d74112c6c6d43ca86fad8a11fe48e823019d91", + "node_modules/@smithy/core/dist-es/submodules/client/util-waiter/waiter.js": "08e5b5ced4dbe2cbd23a8e85fa5652ae6292b917232088b41ba41047f595271e", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/endpointsConfig/NodeUseDualstackEndpointConfigOptions.js": "4fc54c57b99d96d91fb1b1d868491b574ca71612af1607b14044e2a7176b5247", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/endpointsConfig/NodeUseFipsEndpointConfigOptions.js": "ce4134a1975a92f62d8b8ca5d28b3ccb2f52dc8af381edbd9401763d548bcfdd", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/endpointsConfig/resolveCustomEndpointsConfig.js": "2df7e3e7eba9a1c7e73fc05a49be9ad9d03c3321a554ec2d82f6aee510d44294", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/endpointsConfig/resolveEndpointsConfig.js": "d79d165de55dd282d5f3c70b6e851af09d4756cb8c672e6a3d0b162555d7c8c2", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/endpointsConfig/utils/getEndpointFromRegion.js": "0ef95d32bb47fdee278fc1f5c51d453abe1f5e5959b6db82f6a6f3d191ada3e9", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionConfig/checkRegion.js": "a250248cde8752c419c677c051bc195f40ea18302e22d9b71ce6c7e0b1a65353", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionConfig/config.js": "2831f2b499d882b9da1905360170465d24b08468c5f892b15631c45240d8088b", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionConfig/getInstanceMetadataRegion.js": "da6d439b0eb4d93a17f5919d6e9d349b3931b2ac9e913b13b7abe3e4f61ee9fa", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionConfig/getRealRegion.js": "7d6d48b6b1639e28a0f001e5773ee2cea112deddef1fafa60e7c5fe8e004b9e1", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionConfig/isFipsRegion.js": "5b3f819f6c1eb87ce48bacc576c65525599f7989384122ed03508464dd739b79", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionConfig/resolveRegionConfig.js": "c29343b0e4d0e0796093ff484a6248cfc9831f2521b4decb884ed2712dd14768", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionInfo/EndpointVariant.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionInfo/EndpointVariantTag.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionInfo/PartitionHash.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionInfo/RegionHash.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionInfo/getHostnameFromVariants.js": "805d251961c6ec88fd519a4e28f05ed5407d259c3be6afc0dbb35e666e764cda", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionInfo/getRegionInfo.js": "5bb5e5cef153efb30e0cd51e19465c08ddb30cdcdda9f2618cfb08ec342f9c0e", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionInfo/getResolvedHostname.js": "c345afdf07a0192df80b7e3f7fc7ff8a4794a71ea6fbe1a6bcda67c94804c472", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionInfo/getResolvedPartition.js": "014e979ae17ddec1b6778d18ddcf6ce6bea7c9d3f206ce9dd7859dfd432c1f89", + "node_modules/@smithy/core/dist-es/submodules/config/config-resolver/regionInfo/getResolvedSigningRegion.js": "151cc46bc7eddeabfe97ec9115694af73b0683b2fcef7e1c8620d049138ccf9e", + "node_modules/@smithy/core/dist-es/submodules/config/defaults-mode/constants.js": "2f5c758f38748f7b16bc72396ea6881d09958e533bcc86dc2eb65a85c79b276a", + "node_modules/@smithy/core/dist-es/submodules/config/defaults-mode/defaultsModeConfig.js": "26ad37473bcbf504376134624bfe8524c195783bac7b9bfe5e191a258af6acb4", + "node_modules/@smithy/core/dist-es/submodules/config/defaults-mode/resolveDefaultsModeConfig.browser.js": "e9983fa7217f43d3f2e98324c6170297c1e3654bebef755f73e8ea721ea6e2ab", + "node_modules/@smithy/core/dist-es/submodules/config/defaults-mode/resolveDefaultsModeConfig.js": "9be007b29bf82fc421dd3389c8d28881b606e69788e9d25642abc9e22c2e3d76", + "node_modules/@smithy/core/dist-es/submodules/config/defaults-mode/resolveDefaultsModeConfig.native.js": "2a8df02b8fff70d2ba288f19b01a2246e445b45cc37ebd529d59f9de1cf84dca", + "node_modules/@smithy/core/dist-es/submodules/config/index.browser.js": "fa7949d0009e0249d036c2b8c7ae049ba6ff3a1f1fb593d72e1cffb49b6ac139", + "node_modules/@smithy/core/dist-es/submodules/config/index.js": "b3508217d0cd3a81e49bb1f4aa568428e2f8b058e5c718ffef08a0b51f86d1dd", + "node_modules/@smithy/core/dist-es/submodules/config/index.native.js": "bac3a705b02c4ef32a2fa090c58f7665e942ecdaabeb3863df5609d88718fce9", + "node_modules/@smithy/core/dist-es/submodules/config/node-config-provider/configLoader.js": "52d078dae8acaf82da89c16c94bb6b0a7f6e60ddac4662289e9154fe581a03fa", + "node_modules/@smithy/core/dist-es/submodules/config/node-config-provider/fromEnv.js": "5f3a6ce8d3c5188471dc7a6c0783f668f14f632b16a336c4c672c55267f4053b", + "node_modules/@smithy/core/dist-es/submodules/config/node-config-provider/fromSharedConfigFiles.js": "2a225eb2b92529c0ffa3773bfd5dcbf41d8320d855bccf286dd2755b7bb68482", + "node_modules/@smithy/core/dist-es/submodules/config/node-config-provider/fromStatic.js": "483b8df11c559ac98282a547a771a8e6693cfe3963126cc28191979f1c1021fa", + "node_modules/@smithy/core/dist-es/submodules/config/node-config-provider/getSelectorName.js": "fa8415cbce82ace8cb6becf56f6a30a5ae438054da339cd2712bcc53fba2548b", + "node_modules/@smithy/core/dist-es/submodules/config/property-provider/CredentialsProviderError.js": "617f7019b3f2b886e2f7ce1268f29a1b4c5397504da2b60ecd2cb8ad2258dd34", + "node_modules/@smithy/core/dist-es/submodules/config/property-provider/ProviderError.js": "909894f441e3dab7af577c86c45a16bcd17746d6ebc0c3fe15dd6e1d96beef32", + "node_modules/@smithy/core/dist-es/submodules/config/property-provider/TokenProviderError.js": "7c67104c9fd4159e0184c6a7664b0212793dc1dc9dd789af81670fe7d3b5e619", + "node_modules/@smithy/core/dist-es/submodules/config/property-provider/chain.js": "5b15c72a0bc6fe43e789b53f4f932b99ff495f8b5ef7b8b0861e1adb4434839a", + "node_modules/@smithy/core/dist-es/submodules/config/property-provider/fromValue.js": "e5235e65c784464057858c1178a89960e36348a53f36a05698817fc799bd6ea1", + "node_modules/@smithy/core/dist-es/submodules/config/property-provider/memoize.js": "31b31dcd1eb235d912cc5eefb36fc57305e55ea9975ff3f6c5ab0e3c2e8d349e", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/constants.js": "1e369aefe122f677b41844b862357bfeac3e723d892b6d1182350f21ffcc0c39", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/externalDataInterceptor.js": "10746ed7025371a831cc95b1a022c49615d11e8b766584c3087faa9a3bdac6fe", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getConfigData.js": "35894afdd799e077acdee8c9e47b9f98169906be68361b01689ffffc76f19590", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getConfigFilepath.js": "97b4a9cc73f8bbed9c990c0960f70e30807a7f641f3215a92954c9981dac0ba7", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getCredentialsFilepath.js": "eaa8f74562459dcb899e1ee85c3fdbff70a9df7074b42ef711d9f176cc28797e", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getHomeDir.js": "1f07188d27fbdfa5c11d78950489a9c711a4f7aee0dbc96a2c4d89c2130851cd", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getProfileName.js": "8b6c9314a92f1cc72bbaa45caf0115497ae478f272d32e7b992d557919fdc449", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getSSOTokenFilepath.js": "6edf2b6103a6886cfb1da04c1a79efe90b08d534215a0c2c11cb1bba03f4f893", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getSSOTokenFromFile.js": "79c0274a2f7e6347bd5e254d485f915e34e76a38069cbcd5e536ff655e9c9a77", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getSsoSessionData.js": "265fd92734989ce2d561c4cb9e6d570eef33341d205fb0dbe577bc559a557753", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/loadSharedConfigFiles.js": "6254ceb3bfa2d608c8b279c0171e80bc10025994dfad05dc978e597aa3471876", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/loadSsoSessionData.js": "0b1cb1f9b076c1006b4078d94036a1cf24942824867bb71fcad6217bdc9e1a4e", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/mergeConfigFiles.js": "2f9b10869902338fe3af11a714b5a5de9cff93493cedf23669e9db4a2d6c452a", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/parseIni.js": "86c7fdb64c2c62078391c000676131e6e5a163dbad1dfbe93d71991ec657b7f1", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/parseKnownFiles.js": "e6821544bc28886241deca133e8c9c799a8587dbff586ef23bf107c88a8f6900", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/readFile.js": "eee0d043d18804efe2e066dab0f51f7c631eb84490840ea8cab9d4cce0993482", + "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/config/util-config-provider/booleanSelector.js": "051dd6f06872d6fe04427157824f7dae0499ec7a31790c64cf3a7e17effd5f25", + "node_modules/@smithy/core/dist-es/submodules/config/util-config-provider/numberSelector.js": "2e51669cd9049dc820a915d7a1eda7de30301ad41e5693ac22ea66253dd764ba", + "node_modules/@smithy/core/dist-es/submodules/config/util-config-provider/types.js": "296552af7079d0f6022148f7d6d44e611170ab0626ac71a3fee5e329083ef2e6", + "node_modules/@smithy/core/dist-es/submodules/endpoints/index.browser.js": "009fd01ad5a97d32c5bb1dd2713b78cb7bfc45609622515124625a12f8432427", + "node_modules/@smithy/core/dist-es/submodules/endpoints/index.js": "fbebe46ce4d4210e86b62c8461ecb434d347faf8f62a3f974090260365adb47d", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/adaptors/createConfigValueProvider.js": "33b30756ca8300b37e5365e41e0b6f5fcf7b43d52fcf98df76a5a36d052fde9d", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/adaptors/getEndpointFromConfig.browser.js": "dd525850b6a784edd1a03e5444247daa6745b5ea8e31420c68e851e39f407b61", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/adaptors/getEndpointFromConfig.js": "32fbb2eefc219a4d0b24d0e7cc0cb7c72a3390fa88a8c58716d0eef5c8471161", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/adaptors/getEndpointFromInstructions.js": "e081e7c361d398bb9868bf3e35cb56f244a8255e436169d436b91932c0117738", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/adaptors/getEndpointUrlConfig.js": "f90b13b6c30d211f7b46d095e3bc800eaa3f86ba6a1f5feaa08fbe2cd2f383da", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/adaptors/getIgnoreConfiguredEndpointUrls.js": "175bbb2e3d22f5a299af0389a112c8ad39e3f00e98809767c225d35848c9c168", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/adaptors/toEndpointV1.js": "6ff032d516e82cdc104ede5ca8dff25dc6eb34064f8c6a82cfdcd674b8c46244", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/endpointMiddleware.js": "e0d03d523e8b7f848b29fb19b1f43f2b9940cf5ab7184f0709d3fb61381819b0", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/getEndpointPlugin.js": "fc8b400bc842e13c01382cae0c618b23144322a0813da9bfa1aa5f3bd940d207", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/resolveEndpointConfig.js": "85bc51e0a2e0cdbd80e222c4f9f8b3271d1d04dad1d1ab100457802c798fff18", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/resolveEndpointRequiredConfig.js": "71aeb01ce4a273c37112cbab124972642c4e965f68c4517eebc9695a5fae1ee5", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/service-customizations/index.js": "31d56b8e7f69c0656cd411edea5ff9e0b1cba44f1321065a7d51629ea2cb2bc6", + "node_modules/@smithy/core/dist-es/submodules/endpoints/middleware-endpoint/service-customizations/s3.js": "e2df99b94810a69b4d4385d4aaceda3d954ed662c48d66adfafa8296906d5f23", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/bdd/BinaryDecisionDiagram.js": "6d10977ca06107dd40b14373c7a01045e95e0837fdfb4a14e0b9c3e45ac911f0", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/cache/EndpointCache.js": "1edb810ed94e838b00cf03f8f0b2ecb5ae22fdd13bf9f3c8bd63c2686ffb7c29", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/debug/debugId.js": "91402717ff1ecd523c4c52f8db79cea081aea59c3f1b4fa47b220dec5ad4ee69", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/debug/index.js": "42d84feb8881334aca85051be702b70c7fe395530172850e71620d6729fda20f", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/debug/toDebugString.js": "7ec02a90dd705ebf40f8cc231c56340fe975e1073990815a4c41effa31dc547e", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/decideEndpoint.js": "9807567aa824091ce51e9f708123211fae0563e8e46224976fc7d9156cac16ef", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/booleanEquals.js": "a906a623414e9bb1a4c3dfc46b81d370601244e95f96643c40c0684c5c165333", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/coalesce.js": "2ea566ff4b9fdff84233a6240c729feac33178951ca0df7791523e16952735dd", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/getAttr.js": "463b732f796e58a6e3bde6f7b9ff4e7a6b55c7868204dff0be0055f286cf169a", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/getAttrPathList.js": "10180876d59c78ba79072aa2202f4b66e428806212174f84f298cf9f0c020bc4", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/index.js": "0ab5d82b0af61bba204ef66e5ae9aa2f90f88823a7f10a37947c3ea5713bc386", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/isIpAddress.js": "77b27456c55f875f4d781e67eb355c374cabd4b8733c01302091b04212e51638", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/isSet.js": "82f85e9144c60a90765b533c65aba7fc7d671b4d8b4d9853b30ddb62de402dab", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/ite.js": "58229a479bc1a6e5524cd690f08ccf41edaa90a0ab91c6c5a1977ec66249a33a", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/not.js": "373a690186a90c7392dd3d7fe4264dd8481de64b713ec8b31b7d9f2a4aeb0b3c", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/parseURL.js": "e73b2c2a362fd8a8b339bd7de3151f248b2ad3d585cf9cdb2290af61e4cc338d", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/split.js": "559eeb75771dcbc22e91579253045fccaa5c16dfcbe478a9d4d64a26008054e8", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/stringEquals.js": "c8db5a7db869d94581cc01b85a681e3d281f44c5512bbfc055b37ceb26d2165f", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/substring.js": "bc3c2f1cf638fa8bdc9f13e1807272a0127c8c5c77fa842469f3d2c91bada89d", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/uriEncode.js": "026c4d54831fd8d5b92896c0fc77fae09ccd5730e595c077e3ebbd7656e1f9b1", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/resolveEndpoint.js": "5249fb2ea12393db89d71d8031ccefc8dac85e5b7c923ef628e58cee181ebfdd", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/types/EndpointError.js": "8dc2506c3c710f4b377ac589439e7897e5f0d06f1e344dcc84cf4a272dff6f91", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/types/EndpointFunctions.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/types/EndpointRuleObject.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/types/ErrorRuleObject.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/types/RuleSetObject.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/types/TreeRuleObject.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/types/index.js": "47f003b1272bc007a890f038ab7a74599476d89f127b9644c26d120345b2c521", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/types/shared.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/callFunction.js": "c42317a90a033e8332c3f0521fe49f8f959f6b9acff992b5add7c765d33760e0", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/customEndpointFunctions.js": "c9a84552df215bd2e54cad9b3e3b2f43c3141080e41e982dbb6a24f3ac4451d2", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/endpointFunctions.js": "cf85a584aba82b49e1f588b74b7cd29a41a9d4aa68e39eb0008320de43fe7e70", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/evaluateCondition.js": "e0b0ea0796fac4ddb4a8ae2713fde1625ab353199f537db6fbd3087640da2757", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/evaluateConditions.js": "d62784ee2e58641148d4f0dc91e0fefdbe4849caf8d064b5f4d20382c2b61a0e", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/evaluateEndpointRule.js": "331759ba283ad936cafec284af0f840303156ae8027474f9fa8ae8a5539309cd", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/evaluateErrorRule.js": "681bbb53d87b85980b2bacdaa5edd5b08eab468f9f135073ac858b299e3d6b4b", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/evaluateExpression.js": "299dd7840e06c2364ac1d325744b323a8da66e964f39c0e3c28591640223319b", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/evaluateRules.js": "af2f22d51f2b7d96dd22568f4df59c33c153744c67adebc63198a8f99d678d8c", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/evaluateTemplate.js": "4ca9f35f664b96ea1cf0a540205a147c72f66ab3737ea06aa564c5af3a41b1a4", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/evaluateTreeRule.js": "7dc10269606c701fb6b08294055aa971aa8b00028c12212c92648ec1954b0754", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/getEndpointHeaders.js": "9f181a24e39be340f9eaf1b5fa6b5f810ea15c9bbd704a9aba4ef798fabee2c3", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/getEndpointProperties.js": "ad878ed5a3cf8b11d5bde3723afcb4151a85d93ce1478c1b753cd90a98fdc708", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/getEndpointProperty.js": "60ab76060d0e2a3ea300b33288b148e8f2b59b580eebd75060045f83b6826aae", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/getEndpointUrl.js": "6027de93aa081b4adefc11e049e37c3748c78727f1604fafa82319e4f783e5ca", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/getReferenceValue.js": "2c905d0e5f71e5cf1250cd113ab15d0aa3dbadc96fee109f9d0c8527d5cb7e0b", + "node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/utils/index.js": "caa2c9b94957382f91ddeafed896aed1355b768f689aee60734a21e438cca598", + "node_modules/@smithy/core/dist-es/submodules/errors/errors.js": "1aec4cae1bd0a43d56dcbabb16ea0e1f5c7f18ef23b2880f4dfe152ce2ea02cf", + "node_modules/@smithy/core/dist-es/submodules/errors/index.js": "ea7d5e32f5effb0893bd2dcafa0a7ae8cfbb6eac76b1fabdfb1e74715b49022c", + "node_modules/@smithy/core/dist-es/submodules/event-streams/EventStreamSerde.js": "afae7e2af7ea04955758b20426680a9e94c84acfc05292f8e23f31de0768e347", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-codec/EventStreamCodec.js": "fed30373aed345c27ac6069bb94211c65ffdba262d0b16c37cf8778faf80da18", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-codec/HeaderMarshaller.js": "e6fa1f2c18035d155675022a8253ed8c2828f2ae57768ecb3f2589bb98729647", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-codec/Int64.js": "c1ab822008abda7210f72d1823101dcf3021d08c902724831c1dbf3de7504ed6", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-codec/Message.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-codec/MessageDecoderStream.js": "0e8134433846f2ed5d201d846d7bf55005a8193ba184e7877adbf1ce53ead32e", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-codec/MessageEncoderStream.js": "d07d64d4ed3775a68d2c83ba29d0938ff4a4fbda3713e52c18d4cd69a639871a", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-codec/SmithyMessageDecoderStream.js": "369cf9ac6b781763701a91ec9260596cff80088ccba5424533714377873cf2ad", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-codec/SmithyMessageEncoderStream.js": "2266dd0cca18961267c14c6328bffbd06b17d1d085a1a96469761ae3ed543b55", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-codec/splitMessage.js": "07313a84ae7ad47839a15fb36c273991e64f6defc31e9dd38a0425e0229b1b00", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-serde-config-resolver/EventStreamSerdeConfig.js": "ff078459f501c1643b21dba4c89f882b8ed33f6228b85880ea3ef5e34491611a", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-serde-universal/EventStreamMarshaller.js": "9114feba717b19135065ebf8ef547aed1e25459fe88813e5e7e5bc99e7cf8efd", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-serde-universal/getChunkedStream.js": "8312b00c50882caf7fe78e60c49b331db4e34e40ad44ee6bfe198e8540be3d62", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-serde-universal/getUnmarshalledStream.js": "e39853ccdf4a165b06cdf22328c076c8d72a04c4690be8740d1bc34037dc64a7", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-serde/EventStreamMarshaller.browser.js": "af9d74646c89d1352d506c89776193103f5a3a9613d0af38a327b4b03c2eca86", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-serde/EventStreamMarshaller.js": "8da87da66d54240cd967d62de84fbd1cffaae534bd822f0e66f91b5f23db0e14", + "node_modules/@smithy/core/dist-es/submodules/event-streams/eventstream-serde/utils.js": "eab63cbeab5ae8a5cce0dd82470a92d33bb00d15efb482110c3d07d126520b83", + "node_modules/@smithy/core/dist-es/submodules/event-streams/index.browser.js": "8ccb7c7e0c6a31f3c7c3caf254406c4d0c3fb35eb71b72d090b912c83920d014", + "node_modules/@smithy/core/dist-es/submodules/event-streams/index.js": "261c2b96dec39e36f39a15e0ba77b854bece91ded16073c53f8a242998c3821c", + "node_modules/@smithy/core/dist-es/submodules/protocols/HttpBindingProtocol.js": "00b9a76afb7bbb905dbd629889c8ee69a0ed82bb1372a43410de4bd81695cfb8", + "node_modules/@smithy/core/dist-es/submodules/protocols/HttpProtocol.js": "d1b592f7ca4b011e69bc0b4aae809d10bee40092a98c0fac14f93e5e7bcce1b6", + "node_modules/@smithy/core/dist-es/submodules/protocols/RpcProtocol.js": "8024f3931a1ae54d621604e1e13eccf85375304d4336477a844efd9aa66b490e", + "node_modules/@smithy/core/dist-es/submodules/protocols/SerdeContext.js": "248b7a1278d4773815beb22fbd76bea8c83af6a9089380e46008f09b84888be7", + "node_modules/@smithy/core/dist-es/submodules/protocols/collect-stream-body.js": "86b348bba3213d8987189e06de2a327f76ab3e9d5f0c62a201b6955d556ef4b0", + "node_modules/@smithy/core/dist-es/submodules/protocols/extended-encode-uri-component.js": "460c66165d39fb54624c263e9fa0630af5fc3aa8065d52a4d2baa0e0466f00ee", + "node_modules/@smithy/core/dist-es/submodules/protocols/index.js": "5026fd8c31d56dfa7f662e9e7e158058aba49373d49ec8cf943269cc0ffd5229", + "node_modules/@smithy/core/dist-es/submodules/protocols/middleware-content-length/contentLengthMiddleware.js": "f9db2511b02d1bc9575c5c512e6ee60b31cb32e0bff9c5bc7f263146f517c029", + "node_modules/@smithy/core/dist-es/submodules/protocols/protocol-http/Field.js": "9e9196026f191dc584e72de877ce04d8409c09956ee9da6c055ddb1fd591a2e7", + "node_modules/@smithy/core/dist-es/submodules/protocols/protocol-http/Fields.js": "c2a0fc054ef0079dbea900f2aff8eac2cb66b8d1f4464ebed490cb3c7e7aabbe", + "node_modules/@smithy/core/dist-es/submodules/protocols/protocol-http/extensions/httpExtensionConfiguration.js": "4ebe738c86bbaee6481699ffa2135d3a1dee4213a0dd0236312f420413b2fb3a", + "node_modules/@smithy/core/dist-es/submodules/protocols/protocol-http/httpHandler.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/protocols/protocol-http/types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/protocols/querystring-builder/buildQueryString.js": "e4633f6822bd5df78778336a47c77bfbe6c50e66f0a172dbaff08365a60689fa", + "node_modules/@smithy/core/dist-es/submodules/protocols/requestBuilder.js": "2243528c9e772c6872a67352bc4a1a231981f56d8e52ad236bde9f48c5d4c802", + "node_modules/@smithy/core/dist-es/submodules/protocols/resolve-path.js": "01b5ecb8f62d558fa77d96f5f47d7fb071134e00f4935fd3a745d685d69f59e1", + "node_modules/@smithy/core/dist-es/submodules/protocols/serde/FromStringShapeDeserializer.js": "3458f7f823256790515dc0f62b5462e5dc72199bbe2e9aab576bbde87809922d", + "node_modules/@smithy/core/dist-es/submodules/protocols/serde/HttpInterceptingShapeDeserializer.js": "00109ae11a410d438ff839090c84d1f6450bf81720bbbb4169fce62c9a94f40e", + "node_modules/@smithy/core/dist-es/submodules/protocols/serde/HttpInterceptingShapeSerializer.js": "39d8b228ae9ef358028e44af1d8d4cf07c29b20be594ffe46339ebb0f416fe86", + "node_modules/@smithy/core/dist-es/submodules/protocols/serde/ToStringShapeSerializer.js": "3f2e098fcf70c963c8579269a7c47ace9cc5f12956316f9e7eba995a999aa9b8", + "node_modules/@smithy/core/dist-es/submodules/protocols/serde/determineTimestampFormat.js": "bb2b7625e69a7708ddc819942fc63929c16f640614b59da52367520397efbdaa", + "node_modules/@smithy/core/dist-es/submodules/protocols/util-uri-escape/escape-uri-path.js": "8d9c92abebc1c0bbab87bd8802e1dd791b36b51d0d87cc7be7a9caaf0bbecbbd", + "node_modules/@smithy/core/dist-es/submodules/protocols/util-uri-escape/escape-uri.js": "bdda98d0c1c01070575d5f48f8f52c67df770bd19196abc59e069dfa1756b414", + "node_modules/@smithy/core/dist-es/submodules/retry/index.browser.js": "99d04cc268398e35a86f85ad71eda8b8972da16741c711684b300d3c70b34fd1", + "node_modules/@smithy/core/dist-es/submodules/retry/index.js": "677e2afbdf1fd77df08eff76bcf7f8ff2b8e73e698a022765dec7a209bb6db9e", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/configurations.js": "82a221fd1dad6ac0f02472d5a1274f32ab81d76b2d8c886d18c48e3b4f6f82b6", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/isStreamingPayload/isStreamingPayload.browser.js": "0a6976bcbcc98c1bfe042a52085fbe2fda9173730327773d35d8a7c5d5f9c425", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/isStreamingPayload/isStreamingPayload.js": "1f2d9e568bb9f70b4ab16dbc2323a77fb4047e2c01062d1a924c8716cacf5602", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/longPollMiddleware.js": "88c448b88b064dbbc2c1f62196291803c25e723d0b7e997418eea64a36b08429", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/omitRetryHeadersMiddleware.js": "31f309522911edf36c7670462a8e5779a471bc61d31fdedeec3ae489f58a51ca", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/parseRetryAfterHeader.js": "7ecdc926ba631f43ceb5357e7f2e8faf7c7b10be259d344a2b05aff48a5bf3f2", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retry-pre-sra-deprecated/AdaptiveRetryStrategy.js": "ebf73c1b6416f4063d792950f52f4f2b8ea4576faeea37a1684f953c1afe08ec", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retry-pre-sra-deprecated/StandardRetryStrategy.js": "91c6c9f4dd1c82ee762d6520e4c2a3e3248a2b2c0a85d82348f2647bc2033c3d", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retry-pre-sra-deprecated/defaultRetryQuota.js": "0bdbc160d1a0afa297d1bf7b4127b07711dabfe835ba9b824f3b5186c7e577e4", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retry-pre-sra-deprecated/delayDecider.js": "030b21ce6f986b6a153d0fc3308ec8cc201c19fea9b9f0ccb9565dbcd871b2df", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retry-pre-sra-deprecated/retryDecider.js": "3a068983601f973486e3790b219abeb192e94c269c8a464afe838ac126baa62b", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retry-pre-sra-deprecated/types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retryMiddleware.js": "8222c7835e9ad063cdcb3321abcc7e0760319e21651176933111c061a9811c24", + "node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/util.js": "c5f4e3403296206c2d6445445733e228cf07cbeffd9fd1823cf19b7e00619982", + "node_modules/@smithy/core/dist-es/submodules/retry/service-error-classification/constants.js": "f9ad73eae8479da6a54c2593df61db21c376d21bcc94acb27b6f4752d81bee36", + "node_modules/@smithy/core/dist-es/submodules/retry/service-error-classification/service-error-classification.js": "27a651a3e8ad67e15951d6b8bde08d4350bc09c7b087fadd63fedc02a7147efc", + "node_modules/@smithy/core/dist-es/submodules/retry/util-retry/AdaptiveRetryStrategy.js": "826965148cb6b0f7aae779bcc88909a275b3551f139da9d848b8558cd4463c8e", + "node_modules/@smithy/core/dist-es/submodules/retry/util-retry/ConfiguredRetryStrategy.js": "c073ca5b34e8c3f4a000c9504191ca528d1975cb8e2a1dfa3ffee03f0023d3c7", + "node_modules/@smithy/core/dist-es/submodules/retry/util-retry/DefaultRateLimiter.js": "32b28572e13668dba2520b02298ae894bc922a5523690f6ffb052fd1273c879b", + "node_modules/@smithy/core/dist-es/submodules/retry/util-retry/DefaultRetryBackoffStrategy.js": "3764e919ef6daeba0a8531bac3f6935dfd5e9067fecd14dea17d4d8798b90fe0", + "node_modules/@smithy/core/dist-es/submodules/retry/util-retry/DefaultRetryToken.js": "d38ebea2d523743cf12ca49ba057bfe390c065c3020c52a8be50e0c64e87ac24", + "node_modules/@smithy/core/dist-es/submodules/retry/util-retry/StandardRetryStrategy.js": "e8271020a1032df507937cd713e545e6e87c181a5243fbe4a92a54714d96d288", + "node_modules/@smithy/core/dist-es/submodules/retry/util-retry/config.js": "f212efaead76bb289cbe9d1f7da1298544a218fe308658d9c412e2a7c32d12b8", + "node_modules/@smithy/core/dist-es/submodules/retry/util-retry/constants.js": "58746cf7cd7304538717d49d624e8d38ee9c72d9ae78cebf82b2fccea0289349", + "node_modules/@smithy/core/dist-es/submodules/retry/util-retry/retries-2026-config.js": "beb225bddd3c9664d922c242cb2bba2d71ecd15a2d7c6ea1a6d5a4a71c62693b", + "node_modules/@smithy/core/dist-es/submodules/retry/util-retry/types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/schema/TypeRegistry.js": "6a783840721161ff40b79cc7477c03acc9b7ea7a4e35f9e789f249a797598546", + "node_modules/@smithy/core/dist-es/submodules/schema/deref.js": "6e996e1191654f49b5d89252f79f24b02b6d818a378048a0a834e4f7b86fe472", + "node_modules/@smithy/core/dist-es/submodules/schema/index.js": "f2d5fcaf1bc1f50df8a9605413065cbd815505004026f1a15e733949dac4c76a", + "node_modules/@smithy/core/dist-es/submodules/schema/middleware/getSchemaSerdePlugin.js": "7bfb8ef14d9ce3548f9e75879c8c9cd885d113019a9f46658febc025e7f607ca", + "node_modules/@smithy/core/dist-es/submodules/schema/middleware/schema-middleware-types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/core/dist-es/submodules/schema/middleware/schemaDeserializationMiddleware.js": "b50e0a8f2109ee0c447ecbd22b76fe3899867d65b59d8f78cb6207cf9924f1fb", + "node_modules/@smithy/core/dist-es/submodules/schema/middleware/schemaSerializationMiddleware.js": "5c0658ed35f087d691a1beec1b87cc79c21a1a0aa40ccb21f4496c8e3f4a2e73", + "node_modules/@smithy/core/dist-es/submodules/schema/schemas/ErrorSchema.js": "12f764aaa426461bd9382fff1e6cae4405fcab8632288b2c12966685db2ab602", + "node_modules/@smithy/core/dist-es/submodules/schema/schemas/ListSchema.js": "d099ffe5918e7ac35be834dc09f8fa512f43ff65b83c97d40608933091b42fbc", + "node_modules/@smithy/core/dist-es/submodules/schema/schemas/MapSchema.js": "32f9ebd34f361e4444f3d7545323eb48864ca5a41c793cc508a1df19537e1531", + "node_modules/@smithy/core/dist-es/submodules/schema/schemas/NormalizedSchema.js": "7891746d10a9e4ebed9709f293ecd5ce90e582c65ae3b11d9652e9222d33ef56", + "node_modules/@smithy/core/dist-es/submodules/schema/schemas/OperationSchema.js": "6cb52bef9bafc06108009b770659b8ba5bf5c36dbaaedd039147e439e8ea1982", + "node_modules/@smithy/core/dist-es/submodules/schema/schemas/Schema.js": "1a044589c68c3e7ce3b0b99ce15490da4edb8b16f7f47a49097ee3e9cd6e0abc", + "node_modules/@smithy/core/dist-es/submodules/schema/schemas/SimpleSchema.js": "1221a744466f1629b3c0c902fa8fe7033f592c04e4a736500e7cdbe7a2a1956b", + "node_modules/@smithy/core/dist-es/submodules/schema/schemas/StructureSchema.js": "97e0b991310692d63b0b9034e438204055b53976d0716e085b5228371111f429", + "node_modules/@smithy/core/dist-es/submodules/schema/schemas/operation.js": "a5da0d7d106ec949520cb93bc0b6df9514570a5482ccb5e95cb448a5cb6e91af", + "node_modules/@smithy/core/dist-es/submodules/schema/schemas/sentinels.js": "51b8b5a0bd66acb7c5b69a045c52beb6b50fc9feb338af200cd02f1987137a13", + "node_modules/@smithy/core/dist-es/submodules/schema/schemas/translateTraits.js": "226f58eeaeb4b07149cebaa1a592b05df4027a2ce658d5cd292341c2ed32600e", + "node_modules/@smithy/core/dist-es/submodules/serde/concatBytes.js": "338796b588e3ce435babf0b4c7e49bc55e0f9fdaba64399b3754623e68d6cd47", + "node_modules/@smithy/core/dist-es/submodules/serde/copyDocumentWithTransform.js": "56b2796ece7820ee33f2a2686d4bced5bd42bbd0ce84718940dd852c8201f2df", + "node_modules/@smithy/core/dist-es/submodules/serde/date-utils.js": "9b64f13c90463b44c0183721802eb8e482f3207d12b4f397c92a9da9ed688590", + "node_modules/@smithy/core/dist-es/submodules/serde/hash-node/hash-node.js": "712bd7948bf56ce583056971ab5c13dea53c45e3d3234279c5a69261c05bb0e0", + "node_modules/@smithy/core/dist-es/submodules/serde/index.browser.js": "35bfc8b12e0c14c950066276daa22031c4e436cc74035b9445beef8ee658aae8", + "node_modules/@smithy/core/dist-es/submodules/serde/index.js": "d5aa832be47d888b3b6c7a43061ea9b682bcfe25043e43aa2aa0f68996587a86", + "node_modules/@smithy/core/dist-es/submodules/serde/index.native.js": "5b1524abfcd86bd682ad6260ddaa07b33e23c8ed31f44f384c65a97411093644", + "node_modules/@smithy/core/dist-es/submodules/serde/is-array-buffer/is-array-buffer.js": "1cadc42bc291fb5519761d6d6f99634a0d032fc9190f9754ae995cd656f451e7", + "node_modules/@smithy/core/dist-es/submodules/serde/lazy-json.js": "119ec70e7dd90c1333b1207531c9de0f9dbad2a120952e82396bdb17cb12b37c", + "node_modules/@smithy/core/dist-es/submodules/serde/middleware-serde/deserializerMiddleware.js": "d6c82fb90844b2da1d4dbbd208a09ef69060451b247bfec1eb96bf8601baaceb", + "node_modules/@smithy/core/dist-es/submodules/serde/middleware-serde/serdePlugin.js": "b79f14ac37db103b275069292e560a6ed7eec5c1b3f93cdd31baeb9aedf62a1a", + "node_modules/@smithy/core/dist-es/submodules/serde/middleware-serde/serializerMiddleware.js": "3810f2e52ce70a1ed5103803ba19e500bafee39be653f8f6c1a2be5418c7d596", + "node_modules/@smithy/core/dist-es/submodules/serde/parse-utils.js": "79e7add3b12e9e81a6fdb260aa6953f593a0772b10cfa45596352b2fd6e53377", + "node_modules/@smithy/core/dist-es/submodules/serde/quote-header.js": "49e123d0f32ab258059e0cd8ffc23c1da9faa748196ed6199e2eb7c1fc4261c5", + "node_modules/@smithy/core/dist-es/submodules/serde/schema-serde-lib/schema-date-utils.js": "3321f9ebd0a4057879a521e476599eeeccd14643d990d3420f0833552fca0729", + "node_modules/@smithy/core/dist-es/submodules/serde/split-every.js": "d9b896d5a1e9663e1cf71507bf13805eafb5dfc4c7cfe5aa045a20d35b892db9", + "node_modules/@smithy/core/dist-es/submodules/serde/split-header.js": "e0d7edca88c23867580c26cf658822b4c40b420c51e2cf6eaf7daf8fcab36b45", + "node_modules/@smithy/core/dist-es/submodules/serde/util-base64/constants-for-browser.js": "0a4acf4aca0c828ed642ac3779b3995feddc51ba053bd1e59e1614ff0fc45557", + "node_modules/@smithy/core/dist-es/submodules/serde/util-base64/fromBase64.browser.js": "af8e390574b86ccade87e3172c3abd97e8fad199e2f8b4c8bbbbc2375627d019", + "node_modules/@smithy/core/dist-es/submodules/serde/util-base64/fromBase64.js": "40969ef2ac8d1ee68269739beb545cbf7da6170ef7f745b5be26c30fea5dfeb2", + "node_modules/@smithy/core/dist-es/submodules/serde/util-base64/toBase64.browser.js": "dc03858a24907d2d87d4ce61f3776063371b5134ff12347bffb88031021540f7", + "node_modules/@smithy/core/dist-es/submodules/serde/util-base64/toBase64.js": "57813a013bf8e078782e0ef845f81075216db0b2ba57b198d9341ea8e73e13e5", + "node_modules/@smithy/core/dist-es/submodules/serde/util-body-length/calculateBodyLength.browser.js": "8925afcd813229070e1440a486ad7707c897c89a8805a0018b14b7ec346292b3", + "node_modules/@smithy/core/dist-es/submodules/serde/util-body-length/calculateBodyLength.js": "ee0d32ae531f45709deae546a32924cda94c1303bf5f382c1f6f5144b6d56487", + "node_modules/@smithy/core/dist-es/submodules/serde/util-buffer-from/buffer-from.js": "67233738743fe391f82356fd8f5bd398eb9833ddae652188326c8fd08d3fdeb3", + "node_modules/@smithy/core/dist-es/submodules/serde/util-hex-encoding/hex-encoding.js": "d33fc907a8b44a218b29f1f50a718e231301fdf0b89167118ab4845fcc21205b", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/ByteArrayCollector.js": "32ef73a55e5ecb81e2ab190b0621a8754ee26f0e957dc7986f93a18b819004a5", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/blob/Uint8ArrayBlobAdapter.js": "4cfbdc372b08a9b962f3617a3974d5e40128e7a84f00d765d1783ca241a41a9b", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/checksum/ChecksumStream.browser.js": "403e9ee7106656364584cc5d52fa2a7655a992814fdedad5873812fa508ee7f6", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/checksum/ChecksumStream.js": "061046fdcfa9e7ec547995a5fd6a24f6174aff1f387840194d3d4d950833bef0", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/checksum/createChecksumStream.browser.js": "8ddcd61ae097d562b267a16c1c483553c4e76b6bf196b416092529334335c7fa", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/checksum/createChecksumStream.js": "739bb0503f993001b7e20b3512d8f4c172ce18630ba943248c0b9bc5b89a5e76", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/createBufferedReadable.browser.js": "7a706b053e5b1be080039f18b2c26ac808eb4fb2ae6008867da6bdb0f05c1fd2", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/createBufferedReadable.js": "007edcc7d793d91a633d0d1cb3c34be27346e1a9348278b6afb3eddcd38d585e", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/getAwsChunkedEncodingStream.browser.js": "b2668e7976ee474f93d532aeac45408356a29a6fdca1208e196add42af584b69", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/getAwsChunkedEncodingStream.js": "5d48b096d8dbdab3278d0af894eacd429636f69ca43bc8f676f6a941994177bd", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/headStream.browser.js": "a6862a058a983cf9dd465fc364840e076ba23a002b918aab73572b2293e0444e", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/headStream.js": "654be80964a63ca8244070f12bb88f3b3a33ee9e9d98b09ed83132319e82b45d", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/sdk-stream-mixin.browser.js": "b5913e68a53d5f89b873ae526658605a300d754ca6c4f1a0d92f89328066cf4d", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/sdk-stream-mixin.js": "c409e3da2f1bbed47c342a24fc8e69f75c73546fd579840374ab776d0343ca95", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/splitStream.browser.js": "c39ea9db5940a630696f4bc1582932c6c207bb8de88b15cf81a6cc6b2d81f916", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/splitStream.js": "cf287217fef75672e162f0e5526b1a88a037bf3a8423866ae59194f54062c96e", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/stream-collector.browser.js": "d60530c8987df15c658828abbc7e1e276b672203a873fbca1c14468ebd7e3028", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/stream-collector.js": "021f010619155906c5ec9767df3adeb431497e26bd7ce12731e28560dd655c36", + "node_modules/@smithy/core/dist-es/submodules/serde/util-stream/stream-type-check.js": "23dd8d4e6bf7ab9f5119bbe1b6ec09d10a2465bedb99010af620d6e9b91a6f1d", + "node_modules/@smithy/core/dist-es/submodules/serde/util-utf8/fromUtf8.browser.js": "a9af222fae9485d50eb9f31c8fff29934f915f009dd9ee6366f91b3c667025cc", + "node_modules/@smithy/core/dist-es/submodules/serde/util-utf8/fromUtf8.js": "f148ed9ef862006e884891fbc8997f3785fc04d38659c722b0d15e35de74b3ef", + "node_modules/@smithy/core/dist-es/submodules/serde/util-utf8/toUint8Array.browser.js": "6aa6d0eb42d704445d6170f221f22cf9eab79e8337d1e856a03dd6a027898d49", + "node_modules/@smithy/core/dist-es/submodules/serde/util-utf8/toUint8Array.js": "01211d5f05220154b9b28ac732186c9450e996d60d4bd007569bbe4d2f401297", + "node_modules/@smithy/core/dist-es/submodules/serde/util-utf8/toUtf8.browser.js": "2ba2be84d25b802ae9e0a41cfe62662ba3e60847c8d3d7ab68e096e3d5671535", + "node_modules/@smithy/core/dist-es/submodules/serde/util-utf8/toUtf8.js": "82e14f8b0be03a3b7154116e1ab0a48f279c54313af9cecd89901b664dc76770", + "node_modules/@smithy/core/dist-es/submodules/serde/uuid/v4.js": "a350c133f995aec4f692dc67df2d4992499d9b4b6f97f7ad7b0bdac03de798e2", + "node_modules/@smithy/core/dist-es/submodules/serde/value/NumericValue.js": "40648a94225e90efb6c66bdd918881089bdc2c18ff64572842d4016b95458806", + "node_modules/@smithy/core/dist-es/submodules/transport/getSmithyContext.js": "88d85f910cd00073aabb21d7ac7a148fc4f84397a470cb4330a6b7994956ba00", + "node_modules/@smithy/core/dist-es/submodules/transport/hasOwn.js": "25e5de9d2bee6ebd411d91987f9fdfb4f790d0cb4ed6bb56cf3d5e9a7932ba5f", + "node_modules/@smithy/core/dist-es/submodules/transport/httpRequest.js": "af681fcfaad3339a0545047f3cfd4c93ad09fa6523f75d8a4545a55df793765c", + "node_modules/@smithy/core/dist-es/submodules/transport/httpResponse.js": "428771784daa2e62ecd932a11d70b47de4be5bfb3a2bbc7e79cd86d763fd6e34", + "node_modules/@smithy/core/dist-es/submodules/transport/index.js": "32a64e94aa2d1e085dad7dcda18c8e440eb2068c3a4bfb1daaab0e0fbff7490f", + "node_modules/@smithy/core/dist-es/submodules/transport/isValidHostLabel.js": "16d62f74ed97e93e72faecba4ab5cfe4c6a3749cdcb0c9ddceda8317324a6a08", + "node_modules/@smithy/core/dist-es/submodules/transport/isValidHostname.js": "b2795927982d1a2bb1dc04ca2ba1d1564066ccd282803078b250417dd933a94a", + "node_modules/@smithy/core/dist-es/submodules/transport/normalizeProvider.js": "e927e9b665c202b4703bfedb3ed09a8ec8b9c9ef22d18c4e496585b16483307d", + "node_modules/@smithy/core/dist-es/submodules/transport/parseQueryString.js": "0fd6e80e261f30666311ae321611388ec5cc1889510e194edc24e87fcc292633", + "node_modules/@smithy/core/dist-es/submodules/transport/parseUrl.js": "b981a24f81594ec898f1fdc00ba3723868cdec522d3485a10c25ce0a4a23f24a", + "node_modules/@smithy/core/dist-es/submodules/transport/toEndpointV1.js": "d5e8fd8df4e90b4a59035655c7b757371bd0561bbcf5d0d9bb4cba9c066b10c5", + "node_modules/@smithy/core/dist-types/index.d.ts": "c1e3b5df468ff860089d0f188c47082efdb9278bd1dff7fea4c4806f306c5b1a", + "node_modules/@smithy/core/dist-types/legacy-root-exports/middleware-http-auth-scheme/getHttpAuthSchemeEndpointRuleSetPlugin.d.ts": "22056affa22dfe06d884207d70b1d0483bae6534a768b3a113124ca599477fd6", + "node_modules/@smithy/core/dist-types/legacy-root-exports/middleware-http-auth-scheme/getHttpAuthSchemePlugin.d.ts": "8cf4d7c1d196d7dc1dd2e9df2834dae9ef55d0b8a1ed358a39fd83961dbd0abb", + "node_modules/@smithy/core/dist-types/legacy-root-exports/middleware-http-auth-scheme/httpAuthSchemeMiddleware.d.ts": "d3c1038b0538c709300e93289297ce8fb450ebeedc8ebe99160f5ed25006666c", + "node_modules/@smithy/core/dist-types/legacy-root-exports/middleware-http-auth-scheme/index.d.ts": "8d213f0112d068d3e23ae679bf4f3f9897fbba58ffb90a0615954e64ddbd06e3", + "node_modules/@smithy/core/dist-types/legacy-root-exports/middleware-http-auth-scheme/resolveAuthOptions.d.ts": "6e6625ffa58d2b0e3f8070594298ce19a1e3a0d9bacaa9f4a98dfa71dcbc1b98", + "node_modules/@smithy/core/dist-types/legacy-root-exports/middleware-http-signing/getHttpSigningMiddleware.d.ts": "75a988b258328baee883aba96aac7ba5a363f2087ffab282a15fab7851d8d971", + "node_modules/@smithy/core/dist-types/legacy-root-exports/middleware-http-signing/httpSigningMiddleware.d.ts": "76457635c0ead3fe85007c5d1a1de628361df9eeba8e3ec1686de12e347e4892", + "node_modules/@smithy/core/dist-types/legacy-root-exports/middleware-http-signing/index.d.ts": "b9dae05d568913b25e3346a1fc51337c0def3a9534765bdf56af59fa4387aa6e", + "node_modules/@smithy/core/dist-types/legacy-root-exports/pagination/createPaginator.d.ts": "4ca0d76b07ac3a64f49275cc274625ff1f1fee3d9b79c78e16ca9140f2766742", + "node_modules/@smithy/core/dist-types/legacy-root-exports/util-identity-and-auth/DefaultIdentityProviderConfig.d.ts": "4936891df0c1580045ee06fb28f38d544b77482d44a5756e71da4d2c5c209dd7", + "node_modules/@smithy/core/dist-types/legacy-root-exports/util-identity-and-auth/httpAuthSchemes/httpApiKeyAuth.d.ts": "3f763c36b0d1596ca8a55ce405ec0130e42c192c9df83e5f38323f5633296916", + "node_modules/@smithy/core/dist-types/legacy-root-exports/util-identity-and-auth/httpAuthSchemes/httpBearerAuth.d.ts": "87eb846a0c246b1700b33eff2aef2907ff755bb5741483c8183c531e41f89db9", + "node_modules/@smithy/core/dist-types/legacy-root-exports/util-identity-and-auth/httpAuthSchemes/index.d.ts": "f94f44826912961b5fd26c56ae20fee6a78c2c5a181280184dfc56480f3226b3", + "node_modules/@smithy/core/dist-types/legacy-root-exports/util-identity-and-auth/httpAuthSchemes/noAuth.d.ts": "cbf7b7545625cc82a3648b56f34860d6eb12281084f8f679e8023c49e632f663", + "node_modules/@smithy/core/dist-types/legacy-root-exports/util-identity-and-auth/index.d.ts": "2b4aafed86272b698b32ad8e558f2e3a401cde6bfaad7796410beb46a56a94f4", + "node_modules/@smithy/core/dist-types/legacy-root-exports/util-identity-and-auth/memoizeIdentityProvider.d.ts": "8ea3fae8daec5aa4b85c3dbb8de68d7df152754919ed21d7f450754a2dc4c7e2", + "node_modules/@smithy/core/dist-types/normalizeProvider.d.ts": "5ccfa8ce75725948efd6c792041adb831ee0d3629beb66d0621bb9ca7dcd0974", + "node_modules/@smithy/core/dist-types/setFeature.d.ts": "c1a89b9d5cea846b1e34e2abe7f94531ee3c6ecf66032d764ed27bd60d9e55a2", + "node_modules/@smithy/core/dist-types/submodules/cbor/CborCodec.d.ts": "c91ecb7d16166f6d5d9455c072f269ed28324b3667ac68dae0aa327846174928", + "node_modules/@smithy/core/dist-types/submodules/cbor/SmithyRpcV2CborProtocol.d.ts": "ba71ee94c6dc9dfeef63432529e152e17071251488b6455fb93e97168168220f", + "node_modules/@smithy/core/dist-types/submodules/cbor/byte-printer.d.ts": "0332cbd982111e95082ed21b8bdf74192166143fb834a3339d98ea68337b4f10", + "node_modules/@smithy/core/dist-types/submodules/cbor/cbor-decode.d.ts": "d9e6ca7aaddc213fd018c82328fa00d28d292210e76b52f9c4b49353dbfda13a", + "node_modules/@smithy/core/dist-types/submodules/cbor/cbor-encode.d.ts": "7fd725127eab3e26f4889d873eb8e87b099798c8d1afb7147d8052f2d74d210e", + "node_modules/@smithy/core/dist-types/submodules/cbor/cbor-types.d.ts": "e066799a1a10741034752b59db622f2491ac7cb7476e591506b55940f7af156b", + "node_modules/@smithy/core/dist-types/submodules/cbor/cbor.d.ts": "d039a9ca18e04e952f3cb8ba0b0bf7949c04d2ab40fffc364883ce00d8a7c26a", + "node_modules/@smithy/core/dist-types/submodules/cbor/codec-v1/CborShapeDeserializer.d.ts": "7fba962378655b3f1325034876212f8b51825e41d53014b80a0cc565a385f4d7", + "node_modules/@smithy/core/dist-types/submodules/cbor/codec-v1/CborShapeSerializer.d.ts": "a5c3b1d27230e28043325153ff88829fda703b2dfdbd4c6799084de977c21a4e", + "node_modules/@smithy/core/dist-types/submodules/cbor/codec-v2/CborShapeDeserializer2.d.ts": "9b97041652322c9f67cf742450a7b085160de63d87dfda82f117abba09fb2eff", + "node_modules/@smithy/core/dist-types/submodules/cbor/codec-v2/CborShapeSerializer2.d.ts": "e9bb8265baabbef7f5e7abcac45296d3bc2d3d9b26c30496103ee38b82598239", + "node_modules/@smithy/core/dist-types/submodules/cbor/index.d.ts": "4c61fce184e7305205bd06bbc13fbe3dffab69a7824c771ac03b8c1840d5432f", + "node_modules/@smithy/core/dist-types/submodules/cbor/parseCborBody.d.ts": "4d0789b45f2c5a3e2ad91d59fb04f28c633520efb989fd612d13daaa9d40ff88", + "node_modules/@smithy/core/dist-types/submodules/checksum/chunked-blob-reader/chunked-blob-reader.d.ts": "b643fcec14d0a681844abb45c485c3f511436d77d8b41b2b9f731eb744e89cb2", + "node_modules/@smithy/core/dist-types/submodules/checksum/chunked-blob-reader/chunked-blob-reader.native.d.ts": "555274d7c8cc65bd394cf5eded36db8a2bb53b161180e15e900edf21a4d622e0", + "node_modules/@smithy/core/dist-types/submodules/checksum/crc32/Crc32Js.d.ts": "71d0d1a2b950b7112538d7ea938ac3cc1f608a0abb6869a15cf3f1bcd61b9236", + "node_modules/@smithy/core/dist-types/submodules/checksum/crc32/Crc32Node.d.ts": "bdb7e848f4d48c950c996ee0a98d00516be3539a00836a9b3a2e5cc13df766f2", + "node_modules/@smithy/core/dist-types/submodules/checksum/hash-blob-browser/blobHasher.d.ts": "9a97e5ace212d7547bfa26dac562630ec7e24cadc0e46e8b48189ca765894f44", + "node_modules/@smithy/core/dist-types/submodules/checksum/hash-stream-node/HashCalculator.d.ts": "c1fb4aae1f1db801c31b61128f358165944c0fa7a9c6080428500c0f0f653ffc", + "node_modules/@smithy/core/dist-types/submodules/checksum/hash-stream-node/fileStreamHasher.d.ts": "13ac1eb520d813098d7278d101ae64fc68d494bc13e4304d7f7eb3a85ef3b1ea", + "node_modules/@smithy/core/dist-types/submodules/checksum/hash-stream-node/readableStreamHasher.d.ts": "7c294f14064e2b4f7d3dc4a0ab1010a8e0a5673ab0dc9a16a91182c98de882f2", + "node_modules/@smithy/core/dist-types/submodules/checksum/index.browser.d.ts": "5e2eb46ed2cd2ba5f82b1693d2072f7147140ec8bd48d71669be7301e11522b9", + "node_modules/@smithy/core/dist-types/submodules/checksum/index.d.ts": "f310e60b371cd8d16e5eb17499ec3ea478029b4a3f779024caf729f3d4174f8c", + "node_modules/@smithy/core/dist-types/submodules/checksum/index.native.d.ts": "390b4ea5a8f58904972e1be62dd755cdaaa93fa2d7ee9c275741c34d60044c14", + "node_modules/@smithy/core/dist-types/submodules/checksum/md5/Md5Js.d.ts": "356fcbd768d6d0e036f574359e58321d947aea6145fb40a5c880ec5aba7baf83", + "node_modules/@smithy/core/dist-types/submodules/checksum/md5/Md5Node.d.ts": "95e43697aff76c701ff58b6aeb1c883e144d533c8c5216be025f8647e1b77934", + "node_modules/@smithy/core/dist-types/submodules/checksum/sha256/Sha256Js.d.ts": "af97d71f9856e83a0cfbcd9e6bf4d28e93786c05d93146210f5c01848722769e", + "node_modules/@smithy/core/dist-types/submodules/checksum/sha256/Sha256Node.d.ts": "6d985ff72f5fc7d4366a56aa15e3ab80c35c623b03feae2badee80232b97404a", + "node_modules/@smithy/core/dist-types/submodules/checksum/sha256/Sha256WebCrypto.d.ts": "b4f2b2a795b0f5ce242cf1d429afe002c00a92aabef8c15a67eb06341ca67764", + "node_modules/@smithy/core/dist-types/submodules/client/index.d.ts": "8de7780047abbcc2fcca9b0c1dc7350b1c9f0b002747b1ec685f0e0a54d39593", + "node_modules/@smithy/core/dist-types/submodules/client/invalid-dependency/invalidFunction.d.ts": "5f932457c501d03a68bee9ae0ab26ef9df2fa1f789a981483ec1f56c120ea5c7", + "node_modules/@smithy/core/dist-types/submodules/client/invalid-dependency/invalidProvider.d.ts": "5f892fcaaa4ec169e3fecb51fd2abb4bca5e4f481ae149147c73c77d513695b0", + "node_modules/@smithy/core/dist-types/submodules/client/middleware-stack/MiddlewareStack.d.ts": "94d4a5f49b20135837d53756572e3356e7458dc699093596ed0bc5937ee0ae1d", + "node_modules/@smithy/core/dist-types/submodules/client/middleware-stack/types.d.ts": "1e3ea89b2d39baadd972cb0500165292ae1b2b1aaee9ce0dc85ed23f162bb7d7", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/NoOpLogger.d.ts": "b991d92a0c3a48764edd073a5d28b6b4591ec9b7d4b2381067a57f36293637d0", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/client-command-builder.d.ts": "b6f48a4939a84a0766d4bf8f14a06882ed519b5c7c3b7939f1e2f4dd84a7dd4c", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/client.d.ts": "9e0a864bdb79b82c7f9f42db541a68afd869149d1faa3d8445b6b1cccf414e62", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/command.d.ts": "c80a2152bceb1948363b93dcb91ef89ff5fbfad06bc23ab7ad336fb927534129", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/constants.d.ts": "7f5a6eac3d3d334e2f2eba41f659e9618c06361958762869055e22219f341554", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/create-aggregated-client.d.ts": "e1bead3baac08a09faac9a25157738abce07a4f5c0f623fb527ecd37e793d08c", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/default-error-handler.d.ts": "62b399d376ac037dbb6cdf238e60dd829f010af81ae3efee9bfd376b85b91ca6", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/defaults-mode.d.ts": "4fc9939c86a7d80ab6a361264e5666336d37e080a00d831d9358ad83575267da", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/emitWarningIfUnsupportedVersion.d.ts": "ad4d9c273751dac976b662395f2e3d18e237ffdac6858971ba39195288c26afc", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/exceptions.d.ts": "6bc29acebd5d030ef00b9c72cd42aad1ac7e1950b58c1a2a073d920997a65f32", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/extensions/checksum.d.ts": "18f7016d205b5537328a1e1598c74b9537bb4692feec6b3db6d19c845d5bbe6a", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/extensions/defaultExtensionConfiguration.d.ts": "1fb56e6f4d09037941dc6980f13c5cadde3f6b2d93e926522547607097129c28", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/extensions/retry.d.ts": "4116c4d61baab4676b52f2558f26fe9c9b5ca02c2792f9c36a577e7813029551", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/get-array-if-single-item.d.ts": "15735f3084dc593c5bd19ecbe267a07c378703e14efedb6ad50e39962ff99d82", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/get-value-from-text-node.d.ts": "74a2ec4236b64b93319539e85d1999ef872d875ae224105db9ec5d4a24c9fb0a", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/is-serializable-header-value.d.ts": "00e1da5fce4ae9975f7b3ca994dcb188cf4c21aee48643e1d6d4b44e72df21ee", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/object-mapping.d.ts": "baf3d8852d8f7a89e0c0be91945cab22b7999442d0a8253b204304ead6ed6de8", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/schemaLogFilter.d.ts": "e451c032d71cb5cc0a72af939c3a00cb9e60ca9671bb5a5bc99e478456478f05", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/ser-utils.d.ts": "2bace0da26ed1e71c8bdf9ab64fe9c19fddba2a62e71391ab925c42f82774f86", + "node_modules/@smithy/core/dist-types/submodules/client/smithy-client/serde-json.d.ts": "090c41926e92dd0dae49198b8fc0061c4b33df0ebf4cc2613fc513c37a327d52", + "node_modules/@smithy/core/dist-types/submodules/client/util-waiter/circularReplacer.d.ts": "e052b609f9519e9d5491ff9086b5cb2d4594b1f4c083449bf7181d8b65492de6", + "node_modules/@smithy/core/dist-types/submodules/client/util-waiter/createWaiter.d.ts": "1d87e15948b9a7eb98d949b51e9e2e95c0dceec106cc73251332bd6a2a7fdd86", + "node_modules/@smithy/core/dist-types/submodules/client/util-waiter/poller.d.ts": "db4c2659980298c054b8847665bce61dcda952d6a239abc98227ac374ce71896", + "node_modules/@smithy/core/dist-types/submodules/client/util-waiter/utils/sleep.d.ts": "2ff4e02a62c2142d9e00034c8e62ff9972d87c4c8c84ea1902264bafc524d82d", + "node_modules/@smithy/core/dist-types/submodules/client/util-waiter/utils/validate.d.ts": "0ab0aef9418e97185f90080518bd28ba264e1e409c2a676236cce91d314d1e76", + "node_modules/@smithy/core/dist-types/submodules/client/util-waiter/waiter.d.ts": "247e12f468a458be6f258f1f9fd59fb83c017faf748f5b07ee09a263ebc9dbb9", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/endpointsConfig/NodeUseDualstackEndpointConfigOptions.d.ts": "abc37ca70be4c98735e1d2d115886f15ac5861839804ef24449268024feb3176", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/endpointsConfig/NodeUseFipsEndpointConfigOptions.d.ts": "b6aaea1c64e242d51eb18ffc98b78b6747f3d8b75eb04a9cfcf747cbc83fcab3", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/endpointsConfig/resolveCustomEndpointsConfig.d.ts": "81785a3ea03d6db981ddfcf8fb1bd1377f985564def845c55e49e16f171deec4", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/endpointsConfig/resolveEndpointsConfig.d.ts": "fe848a0485e45778a224cbc1a66af4eef5d51e07d01289b73f54bc384ae51b39", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/endpointsConfig/utils/getEndpointFromRegion.d.ts": "85a48f99b974c4e90eebb21765ba0a40d9dc4a0f1e02d5f841ae76671cf1497a", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionConfig/checkRegion.d.ts": "f2237d5f5df59b015277df5bad149d8338d86f30515130f02af5393d3bdfc4de", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionConfig/config.d.ts": "74d0aa7bc76e9be864e25574a89218cc03fb0a5da4f6bbbadae50c2091d74be9", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionConfig/getInstanceMetadataRegion.d.ts": "b05a85044887375db80ff583116d8091589d9f0aa86c43008b97a9639612d9df", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionConfig/getRealRegion.d.ts": "8ebd5e4c63c53a9b9754b223161be8d9e3b445520f974cdb7b9995596231d0af", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionConfig/isFipsRegion.d.ts": "65bc8b0402c7ffee53b82ab07d70b60ab8840e342128ff4a87420efecd605ccd", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionConfig/resolveRegionConfig.d.ts": "e05e03e1687d7f80f1569fdae117bb7b97feef1e839a61e1b3c61ffca8cc67c9", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionInfo/EndpointVariant.d.ts": "fcb26ad5a6c39ce71dfac5dc16b3ed0e1a06a6dc8b9ac69112c935ad95fcad69", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionInfo/EndpointVariantTag.d.ts": "8a49e533b98d5c18a8d515cd3ae3bab9d02b6d4a9ac916e1dba9092ca0ebff15", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionInfo/PartitionHash.d.ts": "6acdef608420511aa0c9e3290b37d671bab4f719ffc2a2992c2e63a24605a657", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionInfo/RegionHash.d.ts": "291df5da0d84d1452cd68abfbcca08a3f96af610bf0e748528ba8d25784ce2b1", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionInfo/getHostnameFromVariants.d.ts": "fc2f7ff86b6c2af317af5ea22861d225f5d592488b8601ceb486eeb4ee1bc712", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionInfo/getRegionInfo.d.ts": "176cda558a7f76813f463a46af4607a81f10de5330c0f7a43d55982163aa0493", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionInfo/getResolvedHostname.d.ts": "a929ac58c9bf2552a47d418ffaa603c3944968880650da4ba4b993328fe05e4f", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionInfo/getResolvedPartition.d.ts": "b9c04a19f784ded14ab501cd3e0ca6568cb611fe2449e3791be4b9f82893035c", + "node_modules/@smithy/core/dist-types/submodules/config/config-resolver/regionInfo/getResolvedSigningRegion.d.ts": "cd0cb400b1436b2e457b54faaa3ccc1008b2ca240abb1b994946d0d77a96c132", + "node_modules/@smithy/core/dist-types/submodules/config/defaults-mode/constants.d.ts": "3a75b84e1a5bd69e6d106d0d213c0e069318aafc2e5f9f8a09af9bac7c9f1371", + "node_modules/@smithy/core/dist-types/submodules/config/defaults-mode/defaultsModeConfig.d.ts": "b90702abd828cc3273d36abbce9b850ff3ee752279bc7fa76a93da670b597949", + "node_modules/@smithy/core/dist-types/submodules/config/defaults-mode/resolveDefaultsModeConfig.browser.d.ts": "ee8c3cd375290eb27265825e79d1f65a11c67cf1e89f2ad7c9260da04e45dc00", + "node_modules/@smithy/core/dist-types/submodules/config/defaults-mode/resolveDefaultsModeConfig.d.ts": "a307865123e601887b504cc04a7b9de86a05c3d6fee8bef410fb3a796c7da40c", + "node_modules/@smithy/core/dist-types/submodules/config/defaults-mode/resolveDefaultsModeConfig.native.d.ts": "d0038c365df49d045e01ba23c953c5a2998a18b9986c98b9b9cf06d734e106bf", + "node_modules/@smithy/core/dist-types/submodules/config/index.browser.d.ts": "36b2497e478cbb77521f3a403fb19cda736d4817028139b1622a52b9bef19f1d", + "node_modules/@smithy/core/dist-types/submodules/config/index.d.ts": "44a5ebd5a6660d7f84e646d184771f78e901120fd6b5dc200500c1a039f423c5", + "node_modules/@smithy/core/dist-types/submodules/config/index.native.d.ts": "b99379853836ae6827bf51cd9dc6b7a0b0ae38f95cb36c3a2997ec0f2af41a75", + "node_modules/@smithy/core/dist-types/submodules/config/node-config-provider/configLoader.d.ts": "783ffb7c8d3ba3feff3e7ae42966783e4a7dd9dab44e63de558ac02bb8704307", + "node_modules/@smithy/core/dist-types/submodules/config/node-config-provider/fromEnv.d.ts": "3b10140aae26eca9f0619c299921e202351c891b34e7245762e0641469864ffd", + "node_modules/@smithy/core/dist-types/submodules/config/node-config-provider/fromSharedConfigFiles.d.ts": "134d2affa5bca83e1c8d3a2fce17388d757de69b213eaee39fdb1a693565db22", + "node_modules/@smithy/core/dist-types/submodules/config/node-config-provider/fromStatic.d.ts": "148634fcee440c7bd8c1339b97455aaadc196b0229ffc8dc8b85965a7d65b380", + "node_modules/@smithy/core/dist-types/submodules/config/node-config-provider/getSelectorName.d.ts": "9615358df3d2006534c542f7717bff8de3369ebdaae74acf748d8d8eb027b314", + "node_modules/@smithy/core/dist-types/submodules/config/property-provider/CredentialsProviderError.d.ts": "d421fe9a68ff83f2f318d5198e076dd9c9fd4bd69a1244a945f3e669751cc34f", + "node_modules/@smithy/core/dist-types/submodules/config/property-provider/ProviderError.d.ts": "b6700b24f28411b6d4903c975676715da17d689e848a52420ea811b63ccb6615", + "node_modules/@smithy/core/dist-types/submodules/config/property-provider/TokenProviderError.d.ts": "52887898504d0dabcfd7d6aee59f04386fa1b62ceb1c742d141d64cf9820ddaa", + "node_modules/@smithy/core/dist-types/submodules/config/property-provider/chain.d.ts": "43de091a9d7c45f21e51a147f914368e8aacef2a911b010a1a459e9d77d998b4", + "node_modules/@smithy/core/dist-types/submodules/config/property-provider/fromValue.d.ts": "8207a8b85fea96f4ba38bf816159ce2f624210aedd7d829eec370b5bf2c6eb2d", + "node_modules/@smithy/core/dist-types/submodules/config/property-provider/memoize.d.ts": "46f482ab7bc6ff88ca10379dfbb11cb298d3a13b729af584f8fd0d0645894862", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/constants.d.ts": "7eca5b6e1cd1c28637103d2b6c44e8b89035a53e515ff31ae3babc82e6c8e1f9", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/externalDataInterceptor.d.ts": "d228c7773484140fac7286c9ca4f0e04db4a62acb792a606a2dda24bef70dc21", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/getConfigData.d.ts": "90c632faad91dbbe0cf6cef2f898aebfed71586b4bbed957bbf9c6416808c67d", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/getConfigFilepath.d.ts": "90bbb398d5587d4b183eb6f5d3f1b0a41a531ed0c64a86482ca02331608ca0cf", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/getCredentialsFilepath.d.ts": "fb377927bfbe5b27938ffd08398ce4e1ce60aeb334b5673836dd9f2ab55e1d98", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/getHomeDir.d.ts": "c0c0b22cefd1896b92d805556fcabda18720d24981b8cb74e08ffea1f73f96c2", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/getProfileName.d.ts": "ceec94a0cd2b3a121166b6bfe968a069f33974b48d9c3b45f6158e342396e6b2", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/getSSOTokenFilepath.d.ts": "49e35a90f8bd2aa4533286d7013d9c9ff4f1d9f2547188752c4a88c040e42885", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/getSSOTokenFromFile.d.ts": "33b186da4b59bf76f82f9e99dee3bfe3b098456139b870887d4a1c01a216ce0e", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/getSsoSessionData.d.ts": "d5fe4fcef810fbc4c6e5e887ef12690f3e1fbf4c4202a6a0babc38eacbe3b6d1", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/loadSharedConfigFiles.d.ts": "49c9c8316d59f6175e6e0439b1d5ef1218f02ce622d1a599449de30645559eed", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/loadSsoSessionData.d.ts": "e4c48be0ffac936fb60b19394739847145674582cbc7e24000d9fd35ab037365", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/mergeConfigFiles.d.ts": "dad10673a89a945ca176e3cac1123d3644641d440bc6de00b18ecfe23ed98af2", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/parseIni.d.ts": "1c3d5cd996eaed46137ecf12d6dbfe877a0ebe5cb447fe17d1d8368b673e57e5", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/parseKnownFiles.d.ts": "149ee951f88961c6151d764bf657b99011b3f6eae8f5dede177c7177169b086a", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/readFile.d.ts": "fb0135c4906ff44d3064feebd84bae323ebb7b59b8ce7053d34e7283d27c9076", + "node_modules/@smithy/core/dist-types/submodules/config/shared-ini-file-loader/types.d.ts": "8e464886b1ff36711539ffa15ec2482472220271100768c1d98acfdf355a23ba", + "node_modules/@smithy/core/dist-types/submodules/config/util-config-provider/booleanSelector.d.ts": "5affcbd718a136d16f7909e635c80a9d4e1f1b6e54cc5318a2be1482a1f81642", + "node_modules/@smithy/core/dist-types/submodules/config/util-config-provider/numberSelector.d.ts": "8960c4375d679c05a1e97cd185a7d6efa7637612fdf3723f7c6d41960464016f", + "node_modules/@smithy/core/dist-types/submodules/config/util-config-provider/types.d.ts": "15e60969067d31da05b5f4fd5bfdc35f9b6a10240729cf428d6539f79c1d6bad", + "node_modules/@smithy/core/dist-types/submodules/endpoints/index.browser.d.ts": "ea3c13616db4ea8a99e20fb7b09a7e57a8b6f6b77d0eb1d710b983fdfa363e67", + "node_modules/@smithy/core/dist-types/submodules/endpoints/index.d.ts": "ea3c13616db4ea8a99e20fb7b09a7e57a8b6f6b77d0eb1d710b983fdfa363e67", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/adaptors/createConfigValueProvider.d.ts": "9900025b4ca3c10f167b22bfc226c9d857adc90d4d77fb6f90bf4a11d7eb1082", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/adaptors/getEndpointFromConfig.browser.d.ts": "3e0b6c0883a2e933dbff0c7c847c4405be3f7f438d639edbdd91171a514dc50f", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/adaptors/getEndpointFromConfig.d.ts": "243b36a1bdb98d1f1a27f6d85b8ae2417eb34f83093cc7c06d846d61d1d6392b", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/adaptors/getEndpointFromInstructions.d.ts": "9ff0b897c34abe69251fe25a942ce38ca17bc086f3054d99f56be9ec76495f00", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/adaptors/getEndpointUrlConfig.d.ts": "7cd640fe4f01578325ac0dd1fb639dccac85845656dfc5067dd8310c903e78a1", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/adaptors/getIgnoreConfiguredEndpointUrls.d.ts": "7b1cfb5267d8ca108674d8faf54cd7b5fda894c922618eb2ae3ca078a1e1ea23", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/adaptors/toEndpointV1.d.ts": "45174e66f10b328efbd981549c9d3275815106cc712f5e5112491b85a5b35e6e", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/endpointMiddleware.d.ts": "994e3744105dd0f4af8f08e1014d1fc72f5c6af24e9032abbddffc568d7bb83f", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/getEndpointPlugin.d.ts": "ccbd0078dbf8f4e4421764a59d430e71d83960f3625e0c2bcc41b9db0683e74d", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/resolveEndpointConfig.d.ts": "abfdae81e181405b07b9e373b22fdc4f638ea97623b04fe92e347776179cc603", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/resolveEndpointRequiredConfig.d.ts": "e90bd7922cb6d591efd7330d0ba8247ec3edf4c511b81346fd49fff5184e6935", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/service-customizations/index.d.ts": "ce7ae489d5cc1edf5c4a89cdebe9faadff81df5a293072b316086f24119bcf3b", + "node_modules/@smithy/core/dist-types/submodules/endpoints/middleware-endpoint/service-customizations/s3.d.ts": "96ec860a8c3e6af7e4caedb9f71d1eeeaba3a7fb0944ea46973f612795d87d94", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/bdd/BinaryDecisionDiagram.d.ts": "286ff377d672f3fbf04d48bf01c712dbc50082a7c6484c83d10fb2088bf78d90", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/cache/EndpointCache.d.ts": "2566a6785cf3417880900d4b9cae9d6587ac3c5af025143e0c022fb68f798f95", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/debug/debugId.d.ts": "67a3036807cb49c3c219ebf77b0f7b09d1932f107221390c8b1a0e871d83d912", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/debug/index.d.ts": "42d84feb8881334aca85051be702b70c7fe395530172850e71620d6729fda20f", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/debug/toDebugString.d.ts": "c788e0a964a7bfe1b08531c6bd7e75f1d50e89c7c99dcca01b10af85a4460414", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/decideEndpoint.d.ts": "4f5eb3521845c9554a3f39bfc7519398b2a85069231f2bd9ed3d94ef6d5683aa", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/booleanEquals.d.ts": "ec8c02180a0cb8f205c37edd7cd96c5485c3c1ea83d0dfcb66a1df11d10095f0", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/coalesce.d.ts": "b762184fc13243f20adba741175466762961089804d08c0c859c03807b35f517", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/getAttr.d.ts": "e608daa0e5be99c0d221e06b140e4812afc9e67101ba19a2705f4a86e0619e49", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/getAttrPathList.d.ts": "3ff38666624e200575d0bc905735779948fda20d51e94cda4e1d156651fcd719", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/index.d.ts": "74ccbba718e0ff5ec0603004a36d93fd22dc42b36c365db670ab8325ee3a2077", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/isIpAddress.d.ts": "1707f7a4866728245f4b5d3c510eca32bba08662da7c9e2219685d18f5448f1c", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/isSet.d.ts": "65aa5cd036f55d7de76acf381f2494411709d770fc89959d709cf39537efd4f7", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/ite.d.ts": "8a430167f4c6faf49fe5fd920a91bce7cb54fb9a0215eb76d4b1c7643e8ebdd0", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/not.d.ts": "ecfe27b02a99d93839d791b4727c1f97f9688d9a7f55887e26429c064a50c428", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/parseURL.d.ts": "9a69aaf69af218e520660e8331cfb6bee9f400a1734c9af6db0daffd4205dab3", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/split.d.ts": "b66e55ffb2343d8afb50ce11a7d6ca88a56b1cac0ef74a7e098934f5abc5c294", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/stringEquals.d.ts": "ed498252ca8a572e663224dd6197133660bf0156c1bce56ff8479cb1db7c3998", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/substring.d.ts": "4a275b1fbb99fc3ae7cbc023053984e6ffb28f4c72a18fdc2f7bcbba852784d6", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/lib/uriEncode.d.ts": "4936f1e89019db57b507994a2a3e244185d09630741233ba69daf7e245c0db7f", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/resolveEndpoint.d.ts": "5f5fdda53d4fc2c14438c579511a0fced4c692fd6bf1a6087c314cff6d1c3010", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/types/EndpointError.d.ts": "aa0059d2ba74d5d1d866bf5e1ca2be9bac8d37d55b42c43bab45b098edbe078c", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/types/EndpointFunctions.d.ts": "c81746776721126aacff5d25b3410c2f46768c2715a673b540a5e503ac13a02d", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/types/EndpointRuleObject.d.ts": "a5f88f5f9bf5aaf93a88631347678de7eef05aa3f13045d7173c232928836511", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/types/ErrorRuleObject.d.ts": "eb1688755bff43e088f7631d4cc63f6a679cc34d0360c0c10def02523d23010a", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/types/RuleSetObject.d.ts": "392b9031cf6cd2b959183df0b970ffacc78ccee32a8eb89cd7f6588ff759f5b5", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/types/TreeRuleObject.d.ts": "00ba5b67972274a6ed935a753d2200ca7d8021cc27e9980ec6bc78c0903f1b8c", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/types/index.d.ts": "84250a86a07e69ac1b64d9686494e8e4cd119f1e8fc3f6c4f680b43c1000ceb8", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/types/shared.d.ts": "0bc1f52edd93536932d1574a50a9f2aa33df0d69320bbafb03788503c77a2213", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/callFunction.d.ts": "c42317a90a033e8332c3f0521fe49f8f959f6b9acff992b5add7c765d33760e0", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/customEndpointFunctions.d.ts": "d288bf29249d6dc83bc7afbdea0dd06003be9998dd763dfb7e991a5a840e7647", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/endpointFunctions.d.ts": "61874c05adaf235eba0a3e31b46989bdb7d453a9c91a604fd560c058897e7999", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/evaluateCondition.d.ts": "6f69f3230bb25959020a9aec8b9780d6dd0631c460c18fb182471bfdff575e43", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/evaluateConditions.d.ts": "c30046446eba85674d661c6ccafff95aa0e7742baee3751a7f470f50ce5548ab", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/evaluateEndpointRule.d.ts": "b19ccf2e59cfa6e45ad8c2ad7a82f71543ca5b066a7483612815f1f28e4f9c01", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/evaluateErrorRule.d.ts": "e704fec38d7d2b0af5fb589b8ce00f65f239e8cb697913f5927384fdf321bf92", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/evaluateExpression.d.ts": "be87892ef45e36a1a0243ee88769b0385bb3c60dd5fbc35d82caf2f03a993f9e", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/evaluateRules.d.ts": "17462b1ded734b6af92e20e1ea31257b461823cf02a419027eeeb621e8694de7", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/evaluateTemplate.d.ts": "0326fb75460418d8f9375e21d517951225cd87e4e1f7518dcae08ad80fa20ebf", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/evaluateTreeRule.d.ts": "7dc10269606c701fb6b08294055aa971aa8b00028c12212c92648ec1954b0754", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/getEndpointHeaders.d.ts": "1a5d104da51d667d1d90a0f511f6cdf32427cf02facefaa31c4c7c1bd4bee70f", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/getEndpointProperties.d.ts": "18efd5d2b4e94d185da1fe08ddb297c88cc2f3128463e92d146f27e504459216", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/getEndpointProperty.d.ts": "60ab76060d0e2a3ea300b33288b148e8f2b59b580eebd75060045f83b6826aae", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/getEndpointUrl.d.ts": "34540e896660291d6243ab85d5a679cf991e6e435c58c11089749504e9d0fc9a", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/getReferenceValue.d.ts": "9e72b3d7f33a3072bf8badd34391a506da791c614d1d9948af81a3f76a51913d", + "node_modules/@smithy/core/dist-types/submodules/endpoints/util-endpoints/utils/index.d.ts": "caa2c9b94957382f91ddeafed896aed1355b768f689aee60734a21e438cca598", + "node_modules/@smithy/core/dist-types/submodules/errors/errors.d.ts": "1d5e067091978ec3d11e48d8705a1a8e6f48ce8d746be5def109da974a67074f", + "node_modules/@smithy/core/dist-types/submodules/errors/index.d.ts": "7fdb4b0d25f4ce4be7f37fbd0d4f075f1717c79f94e683749d74aa34e347339a", + "node_modules/@smithy/core/dist-types/submodules/event-streams/EventStreamSerde.d.ts": "ff7ccdf4949fd4946af7917c30b464c5d0ad6447e88067a5e6dfa92958c74051", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-codec/EventStreamCodec.d.ts": "f70851b7d3304122646077ed7abd9399f3153e79619f318d5fa5c9ebc382f26c", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-codec/HeaderMarshaller.d.ts": "29e049c312ac843c41802199f747cae5eb2a7805f36a7655476502d1d2758f02", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-codec/Int64.d.ts": "e1968aa75a7388ad5114bf8bb72a5d834203a15a4d508c2c9c05d0f47718340d", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-codec/Message.d.ts": "9f3e08ad493f82afa128127286f468892385fe6e72a1f4191a2cf9dded3d35bc", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-codec/MessageDecoderStream.d.ts": "497406148a7a21be65d1449e4095ef8ad35e405b60a4e7ddbbfd762543837992", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-codec/MessageEncoderStream.d.ts": "fd0839989516a2c0247b7670946286e054b26e76a92ff6c61376e05f209b94cd", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-codec/SmithyMessageDecoderStream.d.ts": "7ee24a42010eb0b2bc3c352bf09c824fe94f7b76da41c6370083c40e1aa60362", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-codec/SmithyMessageEncoderStream.d.ts": "705d1ab1e4d1eacd9170f7ee80467adb5a00e4a2808c744ef4cc2dafe728ba63", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-codec/splitMessage.d.ts": "7cd3e52142aac01fbffc2dfabff027e1f511d6688414532a6f3743fa18c6928b", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-serde-config-resolver/EventStreamSerdeConfig.d.ts": "6b08ada439e3c7fba3e6d18c19f934e7bbea3f34979f2490074f0623b849e8e4", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-serde-universal/EventStreamMarshaller.d.ts": "9de606525f845076e0c16236857cee0d3b35dc4b48e2c24b4f3007aac2d87d82", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-serde-universal/getChunkedStream.d.ts": "bb81bd4d4069d1c875fe898a6fd1c9d4aa2e07556aa0f119ba090ab635e613ea", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-serde-universal/getUnmarshalledStream.d.ts": "12191c86b1d7bfd4e123b32298bb8d12dd8eef498281ea38bb2ea08b28540680", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-serde/EventStreamMarshaller.browser.d.ts": "28afdc47c8321e7592b069d5ce05444642bf943c5f345a1a6d5c3df710e7f743", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-serde/EventStreamMarshaller.d.ts": "beeae79bdb272c7701332c77adffe2dd170dacef029a38f072bd08db1b437fae", + "node_modules/@smithy/core/dist-types/submodules/event-streams/eventstream-serde/utils.d.ts": "53425e48d63f05b14251b3d02bfe772467d0c91904e321a646a7729bec519f9b", + "node_modules/@smithy/core/dist-types/submodules/event-streams/index.browser.d.ts": "9e2f2f3442a5b81e226ecada8b0012353fd31e4fd0166ffadda4997c6b60c3de", + "node_modules/@smithy/core/dist-types/submodules/event-streams/index.d.ts": "89ad1c1f02174eb3c85aded37a8e238e27774670f6376c384b0b04215fd5fe1c", + "node_modules/@smithy/core/dist-types/submodules/protocols/HttpBindingProtocol.d.ts": "b35f6110df4ca069ea31677a04acd0e13286fe87ab1e3ac080513d55cb115571", + "node_modules/@smithy/core/dist-types/submodules/protocols/HttpProtocol.d.ts": "ba511320bd4ef50fe91058b23ce5b8ffb9341c062e98fc5f6561833fee5796f7", + "node_modules/@smithy/core/dist-types/submodules/protocols/RpcProtocol.d.ts": "8c38034476af70d7ad430f69cb960c5bd6efc9962f266b39ed54dd8e9cad566c", + "node_modules/@smithy/core/dist-types/submodules/protocols/SerdeContext.d.ts": "666d6d6d9f2298f8d8d17ac7a34ac9ca9a59e09fc97b1ae505df6ab4934e2dbe", + "node_modules/@smithy/core/dist-types/submodules/protocols/collect-stream-body.d.ts": "b32af41e81c131a4b46fb768108f7a9e49ac103c9b9ef03c094ba2136af0587c", + "node_modules/@smithy/core/dist-types/submodules/protocols/extended-encode-uri-component.d.ts": "6824145b7ff437b1f9c195aff5df5c3358f743af2773dc920b9f66316d4a3aee", + "node_modules/@smithy/core/dist-types/submodules/protocols/index.d.ts": "a661f77fa4e7a5e6aa322916c8fced5422032a24b7ea54d66af4e0846004e607", + "node_modules/@smithy/core/dist-types/submodules/protocols/middleware-content-length/contentLengthMiddleware.d.ts": "78f1155b9e465a8fef9726262ceed944c43fae67c69a863a5a217d07ed605e41", + "node_modules/@smithy/core/dist-types/submodules/protocols/protocol-http/Field.d.ts": "a484101c5db5f7c9641a05751216345af8e15224808965c58428000cc5aab64d", + "node_modules/@smithy/core/dist-types/submodules/protocols/protocol-http/Fields.d.ts": "3b55c93b5d7a44834d9d0060ca8bad7166cf83e13ef0ed0e736da4c3dbe490a2", + "node_modules/@smithy/core/dist-types/submodules/protocols/protocol-http/extensions/httpExtensionConfiguration.d.ts": "442d632c7d198fb8aaab2f454668c9c822fd41874d306e3b0c615c58f44a63a0", + "node_modules/@smithy/core/dist-types/submodules/protocols/protocol-http/httpHandler.d.ts": "e0538431ca8233e5c3bf795eb70783549750fc8aa31b4fc60417e5b292f183f9", + "node_modules/@smithy/core/dist-types/submodules/protocols/protocol-http/types.d.ts": "3517c54fba6f0623919137ab4bdb3b3c16e64b8578f025b0372b99be48227ad7", + "node_modules/@smithy/core/dist-types/submodules/protocols/querystring-builder/buildQueryString.d.ts": "17e70793315af7229f17a087c61343eba8f02fbf8407efaf7cece1d51596e296", + "node_modules/@smithy/core/dist-types/submodules/protocols/requestBuilder.d.ts": "213d0e70516dd241100677322b879cd4893c15388d269f888832522acb6e5271", + "node_modules/@smithy/core/dist-types/submodules/protocols/resolve-path.d.ts": "786691c952fe3feac79aca8f0e7e580d95c19afc8a4c6f8765e99fb756d8d9d7", + "node_modules/@smithy/core/dist-types/submodules/protocols/serde/FromStringShapeDeserializer.d.ts": "734614c9c05d178ceb1acf2808e1ca7c092cf39d435efc47417d8f744f3e4c0b", + "node_modules/@smithy/core/dist-types/submodules/protocols/serde/HttpInterceptingShapeDeserializer.d.ts": "d65a7ea85e27f032d99e183e664a92f5be67c7bc7b31940957af6beaaf696844", + "node_modules/@smithy/core/dist-types/submodules/protocols/serde/HttpInterceptingShapeSerializer.d.ts": "9c752e91fe237ce4857fbbef141bee357821e1e50c2f33a72c6df845703c87d5", + "node_modules/@smithy/core/dist-types/submodules/protocols/serde/ToStringShapeSerializer.d.ts": "5c26ad04f6048b6433f87556619fd2e50ba6601dcdf3276c826c65681197f79d", + "node_modules/@smithy/core/dist-types/submodules/protocols/serde/determineTimestampFormat.d.ts": "f926160895757a498af7715653e2aedb952c2579a7cb5cc79d7b13538f9090bd", + "node_modules/@smithy/core/dist-types/submodules/protocols/util-uri-escape/escape-uri-path.d.ts": "ad68056a0dd2fc377ff7d80e0390fc82fd4d3cfccaa4fc253d0ddaf363008512", + "node_modules/@smithy/core/dist-types/submodules/protocols/util-uri-escape/escape-uri.d.ts": "8b99b1a44f458d053246cbba3fcbd5dfd77f7cf6b467ee0bde0412d1ce75fc45", + "node_modules/@smithy/core/dist-types/submodules/retry/index.browser.d.ts": "898bf02e1502e3b95facefb969d825e4d5733ff32f92ecc4117377727cb0abc4", + "node_modules/@smithy/core/dist-types/submodules/retry/index.d.ts": "089b09fcfe8e96f2b06e060aebfc410700e59f0afacb2d4351d928f51ded40a5", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/configurations.d.ts": "a22725d21a62e48939f21214805f56d15c23edea4e831408034eb174d8a56dd5", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/isStreamingPayload/isStreamingPayload.browser.d.ts": "a6355fd0ef14c7d46177beb83812e7707ca1bd32d997bd74f01fa689868440ab", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/isStreamingPayload/isStreamingPayload.d.ts": "a6355fd0ef14c7d46177beb83812e7707ca1bd32d997bd74f01fa689868440ab", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/longPollMiddleware.d.ts": "87a938f91fbcfcbc89a77a9ec320f9593507839b1427b876f5c534281b7b3d9d", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/omitRetryHeadersMiddleware.d.ts": "4fce1ce36a7f6fa69d3954cd685d27995123b683d31819218d204ca6bdcbfc53", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/parseRetryAfterHeader.d.ts": "5bbcd14f0138f4e65971ed5cb5606e8591ffefe3ac78ac310b164a975ea38f4f", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/retry-pre-sra-deprecated/AdaptiveRetryStrategy.d.ts": "1f7313f5f2bd2d59ea584436361a213ea0275cb17c2f965573048d5862dda463", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/retry-pre-sra-deprecated/StandardRetryStrategy.d.ts": "b4ec75c8a71c180e886ffccb4b5391a5217d7e7077038de966e2b79553850412", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/retry-pre-sra-deprecated/defaultRetryQuota.d.ts": "272e2e3a84ed190bc0eb919435dd640ae95d9842f3916da63894578303fba2a7", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/retry-pre-sra-deprecated/delayDecider.d.ts": "d1666062675fe2f5408bfc458dec90de7279820eea20890b19484250c324b8ea", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/retry-pre-sra-deprecated/retryDecider.d.ts": "aed88228359e87a1b1a4d3d45f5b6555724c01ac81ecd34aa56d4a0a01ba6910", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/retry-pre-sra-deprecated/types.d.ts": "3a6ce66cd39bc030697a52508cfda7c248167467848964cc40bd992bd9ce71e0", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/retryMiddleware.d.ts": "f6b7ac8ea7cd5e6ded8fcbb961d952ff2130b065b02bffe40a1770b9269e7778", + "node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/util.d.ts": "ca0826ac184b5645407cd764ca34352af88b09f15ba77fac740e76b6c3fdd811", + "node_modules/@smithy/core/dist-types/submodules/retry/service-error-classification/constants.d.ts": "5cc54c4acd667c2a9448bd0aca3dac4c2b2bf17ce9577b0e51d1db552d550821", + "node_modules/@smithy/core/dist-types/submodules/retry/service-error-classification/service-error-classification.d.ts": "420eb19e93ce1ae59a6b94fd2d13c73bb265c73e14eed7aa5d15996b708c89fc", + "node_modules/@smithy/core/dist-types/submodules/retry/util-retry/AdaptiveRetryStrategy.d.ts": "087a509ee3fd001475d652df04a341ce775c378a3ecbdcbe331f27f90b89502b", + "node_modules/@smithy/core/dist-types/submodules/retry/util-retry/ConfiguredRetryStrategy.d.ts": "74408a53b4b4be1f746f966075dfcd58e9840d26d86f0eca7dd389862cecb563", + "node_modules/@smithy/core/dist-types/submodules/retry/util-retry/DefaultRateLimiter.d.ts": "8422f4ff58293a827a8bf401bb36f7eefbf981ae9aac48643d19c1e5439ee1bc", + "node_modules/@smithy/core/dist-types/submodules/retry/util-retry/DefaultRetryBackoffStrategy.d.ts": "28445ada88a765bcd0e669dc11ed3f69415f089fba6646c9f35634d910716c39", + "node_modules/@smithy/core/dist-types/submodules/retry/util-retry/DefaultRetryToken.d.ts": "f3d97515bab2fbf6a01e4095ff313f84d3d4c47d3cf1edacf33ffd2b68742f48", + "node_modules/@smithy/core/dist-types/submodules/retry/util-retry/StandardRetryStrategy.d.ts": "cc4dd94bdb10aaaeef358806fd2f51525aa2e9ec5c7ec767df31bc34f5cf4341", + "node_modules/@smithy/core/dist-types/submodules/retry/util-retry/config.d.ts": "f70ab2e7bd23db437c2d5ed8690c401a921afbd5d3998a6dd2aab90d9efbaf35", + "node_modules/@smithy/core/dist-types/submodules/retry/util-retry/constants.d.ts": "89e7a7b3210bc06bde6919f093d48dd1548c9ee041cb2999404a894346cd7cea", + "node_modules/@smithy/core/dist-types/submodules/retry/util-retry/retries-2026-config.d.ts": "72c30d3c40dd289f7a201f85a57a690038e6d8d5269d594b2d91175324882faa", + "node_modules/@smithy/core/dist-types/submodules/retry/util-retry/types.d.ts": "c749b03596746c41abf1e8ed6b5a6a1bcd316c00dc39a337cc152780efc593bb", + "node_modules/@smithy/core/dist-types/submodules/schema/TypeRegistry.d.ts": "e830d891f90bf4f72cddee3ed502b6e176a628535d16fd99f27c5be4476fe44f", + "node_modules/@smithy/core/dist-types/submodules/schema/deref.d.ts": "4dbfad496657abd078dc75749cd7853cdc0d58f5be6dfb39f3e28be4fe7e7af5", + "node_modules/@smithy/core/dist-types/submodules/schema/index.d.ts": "f2d5fcaf1bc1f50df8a9605413065cbd815505004026f1a15e733949dac4c76a", + "node_modules/@smithy/core/dist-types/submodules/schema/middleware/getSchemaSerdePlugin.d.ts": "becdfb07610e16293af2937e5f315a760f90a40fec4ffd76eb46ebcb0b3d6e16", + "node_modules/@smithy/core/dist-types/submodules/schema/middleware/schema-middleware-types.d.ts": "348d2fe7d7b187f09ea6488ead5eae9bfbdb86742a2bad53b03dff593a7d40d1", + "node_modules/@smithy/core/dist-types/submodules/schema/middleware/schemaDeserializationMiddleware.d.ts": "9004c2e08b2aafa22fe5736e50e7b1c52ba0ed5fde15eecb0952a62c01e9406c", + "node_modules/@smithy/core/dist-types/submodules/schema/middleware/schemaSerializationMiddleware.d.ts": "866deac6c5d0ccb0fb1588bf1bfeb5bffb03c4701ca52215ccaf883c6dadaa00", + "node_modules/@smithy/core/dist-types/submodules/schema/schemas/ErrorSchema.d.ts": "855a96b04628f85b0308d9bfbae57f8006ea60abf19be3d4361bf299f9352b8f", + "node_modules/@smithy/core/dist-types/submodules/schema/schemas/ListSchema.d.ts": "3888f0e43cd987a0dfa4fc16dd2096459deea150be49a2d30d6cf29d47801c92", + "node_modules/@smithy/core/dist-types/submodules/schema/schemas/MapSchema.d.ts": "f4300c38f9809cf811d5a9196893e91639a9e2bb6edf9a4f7e640c3c4ce765ec", + "node_modules/@smithy/core/dist-types/submodules/schema/schemas/NormalizedSchema.d.ts": "9b82a268ba0a85015cb04cd558582c7949a1b91b6761292b9360d093c18e1dd1", + "node_modules/@smithy/core/dist-types/submodules/schema/schemas/OperationSchema.d.ts": "676c3327721e3410b7387b13af857f4be96f2be91b3813a724eedc06b9ce52d7", + "node_modules/@smithy/core/dist-types/submodules/schema/schemas/Schema.d.ts": "710926665f4ada6c854b47da86b727005cc0e0831097d43f8c30727a7499788c", + "node_modules/@smithy/core/dist-types/submodules/schema/schemas/SimpleSchema.d.ts": "ccfb77fcac04c34442ffca82ae90c8dd2a0ec1689ace547fab9a0ae337dd4752", + "node_modules/@smithy/core/dist-types/submodules/schema/schemas/StructureSchema.d.ts": "4e3db0e3bad939a6be8cd687ead2f9c035bef1572322f8504d00385025323fef", + "node_modules/@smithy/core/dist-types/submodules/schema/schemas/operation.d.ts": "10716e50bcd2a25cecf2dd993f0aadf76f12a390d2f7e91dc2cac794831e865e", + "node_modules/@smithy/core/dist-types/submodules/schema/schemas/sentinels.d.ts": "7b464488950d74ca5037da375308fc0c94a539378fd0e9554556df45483aad02", + "node_modules/@smithy/core/dist-types/submodules/schema/schemas/translateTraits.d.ts": "beebde754323e430b4ecf5b9f837a05b1667b3df86bd924b52c4f80f20b3d660", + "node_modules/@smithy/core/dist-types/submodules/serde/concatBytes.d.ts": "028a0d13c13c5e2fac6e81f5f01df2b38159d502fa640f2a0e6f3e67b018d56d", + "node_modules/@smithy/core/dist-types/submodules/serde/copyDocumentWithTransform.d.ts": "d2b5b999ac0b2fd9dcd388290c2fb674145153c3e3c4ecc406f8f1f1ef0047dd", + "node_modules/@smithy/core/dist-types/submodules/serde/date-utils.d.ts": "d55f5646918392f8d08ec54942c59619f4ea781d10de7e9d94855aad22d0329c", + "node_modules/@smithy/core/dist-types/submodules/serde/hash-node/hash-node.d.ts": "68e3be1d28dd32c56fb0ed01eea764051cacf7a7f2b281e057e067251404c70b", + "node_modules/@smithy/core/dist-types/submodules/serde/index.browser.d.ts": "9fe259abfafbbe38614f874cd6e517bb5c3715a9feb09063195b770ed55fb47e", + "node_modules/@smithy/core/dist-types/submodules/serde/index.d.ts": "182dc1ef26d203d05ba03d2e2d7757d3374a309b26220b863c349fca8e73cb0b", + "node_modules/@smithy/core/dist-types/submodules/serde/index.native.d.ts": "86a3fcf1ef6e51dcb7ddb5bd8fdb43c1c3507703b7cc02814bc07f7c8b583eae", + "node_modules/@smithy/core/dist-types/submodules/serde/is-array-buffer/is-array-buffer.d.ts": "c618e24e036f668e12357295faeb073db7bf0559cb9fdd510f1f9a0213acc291", + "node_modules/@smithy/core/dist-types/submodules/serde/lazy-json.d.ts": "1b131dbc3fab3a624be8d3d7d2e612d0ba25f4965b2d075dc35af46c4e4f1352", + "node_modules/@smithy/core/dist-types/submodules/serde/middleware-serde/deserializerMiddleware.d.ts": "5485ec534af78dba0dcc4ddb944aae46dfc612ad8b1ee8277e996cc941d2ae9b", + "node_modules/@smithy/core/dist-types/submodules/serde/middleware-serde/serdePlugin.d.ts": "ffa3c46e2caa9af637aa3521042948256e19ae4013c7c27d8245e8ecdc39c81a", + "node_modules/@smithy/core/dist-types/submodules/serde/middleware-serde/serializerMiddleware.d.ts": "5acb5ec7ebb93bd0b3292abc1321dd9d5900b6f0c5a7f009dcc115e0d6cf1dcb", + "node_modules/@smithy/core/dist-types/submodules/serde/parse-utils.d.ts": "f10d1ad927953dfebbc351529dd98bc84601100729d6cbc94404fe2a60c023b0", + "node_modules/@smithy/core/dist-types/submodules/serde/quote-header.d.ts": "26f7f55345682291a8280c99bb672e386722961063c890c77120aaca462ac2f9", + "node_modules/@smithy/core/dist-types/submodules/serde/schema-serde-lib/schema-date-utils.d.ts": "41bef51b0ff6a162c930c54a430e1526ec1a8ecb55f778e2b345ee16f31ccf46", + "node_modules/@smithy/core/dist-types/submodules/serde/split-every.d.ts": "579690c6076811a09239b9b01a9bad4f0d62fcbefe9741d06e2da38e6e2006b5", + "node_modules/@smithy/core/dist-types/submodules/serde/split-header.d.ts": "514321f6616d04f0c879ac9f06374ed9cb8eac63e57147ac954e8c0e7440ce00", + "node_modules/@smithy/core/dist-types/submodules/serde/util-base64/constants-for-browser.d.ts": "c237590b7936eaeecb9d6d6289075fe7d0fe961fe2db387f9304fd74ebf4a9b3", + "node_modules/@smithy/core/dist-types/submodules/serde/util-base64/fromBase64.browser.d.ts": "f8e3c8ceb1efa74a90055528f49bbb95816c83c18c5c79081830701d15a8281f", + "node_modules/@smithy/core/dist-types/submodules/serde/util-base64/fromBase64.d.ts": "4c36f9d0ffb25cf61b696b2777ba06d553d1b0cfd12d9eed8a1e3b1a50beb2f7", + "node_modules/@smithy/core/dist-types/submodules/serde/util-base64/toBase64.browser.d.ts": "3a94cb4634afdc0e8fed5eb11a05cc7350bfd5b16b1dafbe99ae17e8dbbe218c", + "node_modules/@smithy/core/dist-types/submodules/serde/util-base64/toBase64.d.ts": "dd478451ffa00f4352bffe4f55b4531c8dec0edafb5777272089e5127dca808c", + "node_modules/@smithy/core/dist-types/submodules/serde/util-body-length/calculateBodyLength.browser.d.ts": "ccb0f78df0c3ce916cc29db5da9d3ebd990bb4b6b702da8f905c011625cf4620", + "node_modules/@smithy/core/dist-types/submodules/serde/util-body-length/calculateBodyLength.d.ts": "ccb0f78df0c3ce916cc29db5da9d3ebd990bb4b6b702da8f905c011625cf4620", + "node_modules/@smithy/core/dist-types/submodules/serde/util-buffer-from/buffer-from.d.ts": "72683b6629c584c3a140f2283209ff40e800f087d11866bf37d3614a1da50ce1", + "node_modules/@smithy/core/dist-types/submodules/serde/util-hex-encoding/hex-encoding.d.ts": "c0209cd42d48d5ec4646b2e2b23186bd8a54ef41da47ef445518966e059e6a40", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/ByteArrayCollector.d.ts": "d5db230da24e1ad43bfdd195ac8a1e514f8ce586cb2da77ceca979e2a0385fc2", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/blob/Uint8ArrayBlobAdapter.d.ts": "e941e983e0b2a73b40d237f0283f71ded3bb9dbf1c7dc465fbe871e11f9ed3a2", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/checksum/ChecksumStream.browser.d.ts": "3f20a041a051abfb2b47a66611cf4bcbf263605f5469ed7e8b51b3977892d83f", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/checksum/ChecksumStream.d.ts": "af0d84bdb7854e183368b42ad670427f1580af66dc46ec2ed7e9b74c04511026", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/checksum/createChecksumStream.browser.d.ts": "2c82ffc35416d06c788832db3b6164e193ffc78d00157f85b6d08cad073eeb66", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/checksum/createChecksumStream.d.ts": "1b08bcaeb09727b77365c0138928627257b5cf69ed10bb16dccd90da64780e94", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/createBufferedReadable.browser.d.ts": "3e4122961749fdd0e3cc3f3f8047287e63be7c156ea9019a41152cb59251dceb", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/createBufferedReadable.d.ts": "a23aad55f65e461f165df636b0472745608291a8ced99bd3e2aad75f3bb7ee16", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/getAwsChunkedEncodingStream.browser.d.ts": "53ec5f4fcd98334d411329de719b7d13d4bf59f7ee5fb69339732de86ba76c95", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/getAwsChunkedEncodingStream.d.ts": "fe197c539cd352782c27007960236af819bd28ef8fda67e00dc4d9a81419782b", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/headStream.browser.d.ts": "2c8486ca9e9ac276701efb7b04c51a7c2a206d81f550f419df7205c4f45cb464", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/headStream.d.ts": "af5f2923236ed950df29ee0bd7a51e4e93013d93bdc6cbe665017052a52f42bd", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/sdk-stream-mixin.browser.d.ts": "08ddc08d9f3efc20a17d7aaad7178ccaf89109c050a304024f0c1703a2682d43", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/sdk-stream-mixin.d.ts": "8426fcb0550ddfb759de9d42e8d29ee703294f9925351b03abf2ddfca9b286dd", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/splitStream.browser.d.ts": "d242426b495bcafde55e833abeda493433b5dc6672a6baefeb48ae65039d529b", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/splitStream.d.ts": "9be3ed310f7d164b18be077731cef9ab0a18fdde7acaed11c43e55f6b61a7da9", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/stream-collector.browser.d.ts": "67c604a17cb23c81967320c7b5ae008207e73b0867a9f3871e781eb146a41191", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/stream-collector.d.ts": "d652589a149d9458523ffa1ae9ddda89f9a0a4eee0b1337a203da5a104fd5bf1", + "node_modules/@smithy/core/dist-types/submodules/serde/util-stream/stream-type-check.d.ts": "19527fc5a08c68414a234b02ae9b9619cdb4b811435d12c0af528e5640236f6b", + "node_modules/@smithy/core/dist-types/submodules/serde/util-utf8/fromUtf8.browser.d.ts": "5d9a5cc1712870f91f66850e7056e0d03b4046de5558a00e7190b6a9c2f7d432", + "node_modules/@smithy/core/dist-types/submodules/serde/util-utf8/fromUtf8.d.ts": "5d9a5cc1712870f91f66850e7056e0d03b4046de5558a00e7190b6a9c2f7d432", + "node_modules/@smithy/core/dist-types/submodules/serde/util-utf8/toUint8Array.browser.d.ts": "e93478995031e1a68248d4f5b9062be2f07fab9212578eeb2ed69c0f454d6ed3", + "node_modules/@smithy/core/dist-types/submodules/serde/util-utf8/toUint8Array.d.ts": "0daf877cd2dcb81c0e39a96ee20262dc07ecc6f68d65cdb9cc6e6cf2f31d29c5", + "node_modules/@smithy/core/dist-types/submodules/serde/util-utf8/toUtf8.browser.d.ts": "6206a6984c6210c7e02e8cec6c2417f6d2458ec36ac97b80ce9f894933a08082", + "node_modules/@smithy/core/dist-types/submodules/serde/util-utf8/toUtf8.d.ts": "6206a6984c6210c7e02e8cec6c2417f6d2458ec36ac97b80ce9f894933a08082", + "node_modules/@smithy/core/dist-types/submodules/serde/uuid/v4.d.ts": "108ce794cbbd1944a07b95aae69342db42ffdfcf60cbc77e07e18e26845b392a", + "node_modules/@smithy/core/dist-types/submodules/serde/value/NumericValue.d.ts": "3c583256798adf31ef79fd5e51cd28a6fc764db87c105b0270214642cf1988aa", + "node_modules/@smithy/core/dist-types/submodules/transport/getSmithyContext.d.ts": "67f9d293cad902d4be34e1aee30c22361d39801d73a4450474ffceb764528950", + "node_modules/@smithy/core/dist-types/submodules/transport/hasOwn.d.ts": "6db42be299c56238c06e56cd258cdb3fa9f7ab25bfc4c74ccc400bccb803ef02", + "node_modules/@smithy/core/dist-types/submodules/transport/httpRequest.d.ts": "6b1b54814d2ada2a3ca9182d056750aebbeee13cf0687f9e33d9b055a72709c1", + "node_modules/@smithy/core/dist-types/submodules/transport/httpResponse.d.ts": "cad0f26943006174f5e7508c0542873c87ef77fa71d265968e5aa1239ad4459c", + "node_modules/@smithy/core/dist-types/submodules/transport/index.d.ts": "96ff0706c044e1e9e33bf80ce6f464a61721d4b73a27b9f8d3ff6d9f4ee17dd6", + "node_modules/@smithy/core/dist-types/submodules/transport/isValidHostLabel.d.ts": "2d55f0b72f108339a087e3c14e4c38d7d0114b26d9c6980bc4f1f06fd59ed748", + "node_modules/@smithy/core/dist-types/submodules/transport/isValidHostname.d.ts": "d1f8a829c5e90734bb47a1d1941b8819aeee6e81a2a772c3c0f70b30e3693fa9", + "node_modules/@smithy/core/dist-types/submodules/transport/normalizeProvider.d.ts": "5ccfa8ce75725948efd6c792041adb831ee0d3629beb66d0621bb9ca7dcd0974", + "node_modules/@smithy/core/dist-types/submodules/transport/parseQueryString.d.ts": "02bea5cf058a8fce7fe537b9e70d3ed506c188c3d0df132be355a2cb672c877c", + "node_modules/@smithy/core/dist-types/submodules/transport/parseUrl.d.ts": "6a3d21114b6736612210531e1a2dc7a0e58d931e43f7c21260a7e4c3e8840eab", + "node_modules/@smithy/core/dist-types/submodules/transport/toEndpointV1.d.ts": "7065dd99492aa108614383a0aa1f229e02e6d1bd4968473eb205350e58a4bc80", + "node_modules/@smithy/core/dist-types/ts3.4/index.d.ts": "bf4c6bcf1c901407bd2307f4fe268deb7dc1aae96e47ee2c8d05e2d7747edc9e", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/middleware-http-auth-scheme/getHttpAuthSchemeEndpointRuleSetPlugin.d.ts": "6ea4a27eb4f6916ce6f6b9e27c3213078b55e1d0ccd9a3c11d0761db489ac15f", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/middleware-http-auth-scheme/getHttpAuthSchemePlugin.d.ts": "a70c54e6344d354e0b2e8cced05d7074f0a4f2bc17bbec71b4730599d229b8a4", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/middleware-http-auth-scheme/httpAuthSchemeMiddleware.d.ts": "faf835bc8bcc67fadfe5831200fc0cad2f080300bb2548376c3e2ebe0fcaddc9", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/middleware-http-auth-scheme/index.d.ts": "5471184b14ef49c65e6f1fd1af45dd10733f9224a8dcd3b28ecc60390cd490d2", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/middleware-http-auth-scheme/resolveAuthOptions.d.ts": "8fcd718f674271a9e3d325a987672b66b5a37e372196bfbb55b16e0e4f1e05c2", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/middleware-http-signing/getHttpSigningMiddleware.d.ts": "2c52259eb82cb43e7c1f47404e2f33d1689d7ca53b05165a9163527dc0468c5e", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/middleware-http-signing/httpSigningMiddleware.d.ts": "9acebfbb0cd68fcbc17282614a7beda276719b65dded5a5869bce71a0d172306", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/middleware-http-signing/index.d.ts": "448235d17c2d1c4d5c0e7fc1f3dd97173617bbdd5146d04294119af0b29d84bb", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/pagination/createPaginator.d.ts": "f3ab0a5bc7e5eb92cdc0ac1a42efdeefbd51d96f30212f219fd67d520ab016d0", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/util-identity-and-auth/DefaultIdentityProviderConfig.d.ts": "daf9f0aebc7f8e460536ac58df6b5536b509b2d92402c5ebc91d9d954af61052", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/util-identity-and-auth/httpAuthSchemes/httpApiKeyAuth.d.ts": "958f971c3c6f7b3e43ac1f0c07dfdacc52fa4c2739ae6f6da0e70940aa748353", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/util-identity-and-auth/httpAuthSchemes/httpBearerAuth.d.ts": "8671cfcdfeddf6314b5421178c5716cce1f9b19f90a1db238116d13bf8581c50", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/util-identity-and-auth/httpAuthSchemes/index.d.ts": "9851d41b68e108614cc775507de0cd1b6b1951730e5b1c692c502ce5be0d68e7", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/util-identity-and-auth/httpAuthSchemes/noAuth.d.ts": "cffc5df25692e80221f371b6b6a07e90bb0732e218f937ecd7960242ce3cbdf6", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/util-identity-and-auth/index.d.ts": "a3c2f9e493c8a5ab609f090817059a9de37319fc87d426d8e5682d1ddf2fbd34", + "node_modules/@smithy/core/dist-types/ts3.4/legacy-root-exports/util-identity-and-auth/memoizeIdentityProvider.d.ts": "8ced3e79a585f8900622fa0feeb8d16a49263490433a45227abe5faa438da476", + "node_modules/@smithy/core/dist-types/ts3.4/normalizeProvider.d.ts": "2ae80cc9029e6addb30af3c34b140cf673ae9ff06b0a34180dd023610b5e6564", + "node_modules/@smithy/core/dist-types/ts3.4/setFeature.d.ts": "2e97fc8cd49a0cf87266ae684f1f2819df343fb354897e7e510ccd877098eee2", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/CborCodec.d.ts": "f49fc551bc872240acd28e7de63322da97cfef0a615ad97fc4b07892be69a57d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/SmithyRpcV2CborProtocol.d.ts": "aaa6640c8020618def1dd0ab2d15b2b6411f9f7297d0fcfe2542cc191c771c46", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/byte-printer.d.ts": "9b78388d9ec6edae00f45c509bfd8a44bf74c7d8a5c5fbbc5db714b0c3bcc7b6", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/cbor-decode.d.ts": "bd0aff5d4ed61cd0f04686432a4624da37c1d85a334d699e82a275a882005795", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/cbor-encode.d.ts": "f745eb6962c8f36391600cb844bee38b958e423e6b53d768fc0f4d8c4f09050e", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/cbor-types.d.ts": "0484ac1a750685303f3962c4af2b2f5d291fb9de2b77527e204c4aaa63dea305", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/cbor.d.ts": "942700589c483cf7e1b74465240a8e57b8e106903c53cf4ac1314f6127d0462c", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/codec-v1/CborShapeDeserializer.d.ts": "315aea4eaf0ff5a40594f6e5e67ac412047f0698753954d58ae59047773f06f1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/codec-v1/CborShapeSerializer.d.ts": "5e0c31153de4f67a5ceedcb1e642d99623e342af9f5547714b4a6e728907c80a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/codec-v2/CborShapeDeserializer2.d.ts": "8cc67561d2afd35dddf8373e3ccea3ad3940b8d7ce699b1648cfde8cece825a6", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/codec-v2/CborShapeSerializer2.d.ts": "346e354aee87669ba1e2de9d87bdebf11b682c80a6f7cc4999ad01b71d5ed2af", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/index.d.ts": "2c41e6a6167cd9f6e332351bf3a563831d328180f43e692413d10703c9b36e57", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/cbor/parseCborBody.d.ts": "dd70b035b1587e45f3d48485fd92e84d03d593551cd7c6e83130bc89c7231980", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/chunked-blob-reader/chunked-blob-reader.d.ts": "4bb3fa8df72eb14d2f79310f5a38e50a33857b20eba861ecf748ecd1fcdd73bb", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/chunked-blob-reader/chunked-blob-reader.native.d.ts": "aadcd42e8f3b6b356ea384431d9d4dee183d37fbdd55d92fb43394adcb08d367", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/crc32/Crc32Js.d.ts": "c9da8b6e1b06ac5b733877e046a717305baaecf6261fe71aeb0f264b11e2f734", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/crc32/Crc32Node.d.ts": "71074c671b5f1196357977a759d0811ebed7ca63b7ea14cfc9d3afd437420bd6", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/hash-blob-browser/blobHasher.d.ts": "6017e20f4bcc2a0cfe553213b89b45a3325ffd2cf3e3b297fd90f934e4a0853a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/hash-stream-node/HashCalculator.d.ts": "1dc74b429d751b6930b038ec6801e72881dc280ede009091a2ad7d73ca00c7ad", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/hash-stream-node/fileStreamHasher.d.ts": "a9a86c5640f31a0abc71750102178c384f76c1bb95022e3e2f6cfbf0d26fb683", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/hash-stream-node/readableStreamHasher.d.ts": "7eafc603914aa926c9bed57bfde898b0a7e2fd64fc7e41353f19265f5eb72a29", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/index.browser.d.ts": "8e225728a4c37ec7bd1e14abb53d68a58e12be7d098ea84244422048e2aaa9ef", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/index.d.ts": "1fe798309bc278367a77cf2bf9f4d5f738f54b2d49bf8e0ec3a9288cfa0e2d90", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/index.native.d.ts": "a0bedab0cefd2382a5bc3c0ba1f5920c204ecb077bee53bcb455691afb9b0cfa", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/md5/Md5Js.d.ts": "7170141cf6c88c3976a7eafd36ed6dfde7c134b1f96ce439cc10a2b7f06a6df0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/md5/Md5Node.d.ts": "a39ff72c2cba1e0ab9838ead740edfa39801be10ce334eb280cc08b8b6b422c1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/sha256/Sha256Js.d.ts": "17d59ea9a54339a6581ab9b5dae6794cdbedb49158ef94e08383fa75784d9672", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/sha256/Sha256Node.d.ts": "54401850209f5a25591de40228f5402479a6316a9731df73bc51b33baef72b29", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/checksum/sha256/Sha256WebCrypto.d.ts": "ca80e77ffb352b1ff7677d84c4a8ea3e331651c589a57dff9be844a4941271d0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/index.d.ts": "858a821490c443dd77ac9b659adc832ab253f2bae10ed507cf14189f1e516bc0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/invalid-dependency/invalidFunction.d.ts": "83955c7c2b6595be5e9cb7195216a39ae26e59f84a4f7db5bec30520a0201551", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/invalid-dependency/invalidProvider.d.ts": "64ef1a8483e90725d33981fd88c4409d30e8bcc11d884d2f8f867f238a897404", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/middleware-stack/MiddlewareStack.d.ts": "396a539ef6095cbf2f99693adf2435090f49ec9effa291ff90410141b075f3ee", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/middleware-stack/types.d.ts": "5b1b5b5fb1bc42f192c3d927a4ad916b622ea8aa4314df163258b6ca0462aa03", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/NoOpLogger.d.ts": "2446d1459f4c430aff36dbc409a821bdffa26290d36051e05f656e06895009c7", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/client-command-builder.d.ts": "66758a01ed63027008ac1c13c5535ba303e16ddd947bdc5262e53c7c87931b96", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/client.d.ts": "d376196acef27e70189367ed053c0430162da6c6dad41b350a89360f8e031889", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/command.d.ts": "3eb96f66058d2c21af2d2ee9cb422b7fb070205d86d49431a8f904a362719ceb", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/constants.d.ts": "ad061a2863c50a01c46cca64bd6fb76993af12d8764048ca66e7587315b791de", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/create-aggregated-client.d.ts": "b30dbdf15fdead63162ca2d412bf3adb3cedb0376f51c875b37da36a69d8dd11", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/default-error-handler.d.ts": "51fa5f953987f9cf599024acf6290c37cbbf73e35b7a94cad5a7a65ec2ecfbb0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/defaults-mode.d.ts": "8927f57814131e454a4493900acbccf8a2cac9707850b7549b7eadbd6fbe6c19", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/emitWarningIfUnsupportedVersion.d.ts": "f90ae0d31a82bebee558940161db33d8db533b50aa6ae63c7e68e443ee127f18", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/exceptions.d.ts": "b0c73437a6774bfa8003ed74e213f368ddd048e703466665ab78bb23bce851a9", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/extensions/checksum.d.ts": "73e087556c40e9406cdf71c5c62d8396fa5e569c7d986ff7e6bbe25a9880719b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/extensions/defaultExtensionConfiguration.d.ts": "1b06ad101fa6cf684904724e1c1d3fcef2bebb7bbade80d339cd31cf0aad14ee", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/extensions/retry.d.ts": "1baa816b462dcc49226ee512e2c7c28d0c66148304202f352e67c79aabbb4236", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/get-array-if-single-item.d.ts": "2d05eac2bb375f758479dc8a159836d88dc812f1a5f9a25a0c77004d18fafa99", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/get-value-from-text-node.d.ts": "2f30d748a704ad96654b141c12940f54d8c97ed15ced66990a2cd912814c45db", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/is-serializable-header-value.d.ts": "2d2982e63d8c135a5008ca97d5ee8179b70553ca9f4d62c06122ffa696595483", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/object-mapping.d.ts": "624914712593b668868c6a84f97364c822fe7272c5f4496e675dae59c42fc84b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/schemaLogFilter.d.ts": "0210c546ecfad46507d1a6954cec4de4db8d9a929b7a0ed66256fec075a8b0e7", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/ser-utils.d.ts": "6268dcb0a279140f8de25a7b2eb16c3b4167409960bfd52526689d949513ab4b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/smithy-client/serde-json.d.ts": "2a44d2a5a623c373cc2ee0e35cedeed5bc1bdbc5045b39dc520596c42f796799", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/util-waiter/circularReplacer.d.ts": "3671de21b128c7eb8e0f9ab144ef0795cd73bb8820fe2716f2ea8ab43171836c", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/util-waiter/createWaiter.d.ts": "7f52c3f032dacaaaf7c8809e692b6bcd4713f1bd37c364d0dced91c9364fccd0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/util-waiter/poller.d.ts": "085c20753aed1127d946fc63a27d7de0378ef29280a77475dfc7620b9204be63", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/util-waiter/utils/sleep.d.ts": "409850dcf5ab65829617ee3cd68256d1c0d46837ab79c07e31e2264203623a93", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/util-waiter/utils/validate.d.ts": "15398891ddd4c1e7bcd4ed6f2df9474df409576facdac6a7d07e45e76264ae5a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/client/util-waiter/waiter.d.ts": "12101dcd687d013344c71f2ce2eeb80c0f0ce1d31872f277966a36379086890b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/endpointsConfig/NodeUseDualstackEndpointConfigOptions.d.ts": "82f5234d3177f03f017c579328dea7facc1c88f667722167b1933d24080a29bb", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/endpointsConfig/NodeUseFipsEndpointConfigOptions.d.ts": "ec753ddda1a38129bbfb70412e5d5c6dc276cf613321cc3d35bf63e08d2f6d59", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/endpointsConfig/resolveCustomEndpointsConfig.d.ts": "2add0fbaff9acf1f9d4737ce22084c81a39993858a4a09d154e07b9cdc7f5693", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/endpointsConfig/resolveEndpointsConfig.d.ts": "d50d8e0b64e5880be4a4e6da22b2fab77e1c0db3ed88f967a9d927215f722f9c", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/endpointsConfig/utils/getEndpointFromRegion.d.ts": "6ed8b8d4c27f8fb773ad431145eaa8be125af2f5ebfccfbeaf006a14ae5430d8", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionConfig/checkRegion.d.ts": "b1f27b414d37b6c8284788522c1b57bb484bb5eaf0f937f549817aa43971b2d0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionConfig/config.d.ts": "c710fd373510b79992ade75997d7e1fb25a43faeead319ecad7dc994a8aa6250", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionConfig/getInstanceMetadataRegion.d.ts": "34176a1a122150aedee0c03aa62f26c861a5d58eff47ebaf06633ad545e01f27", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionConfig/getRealRegion.d.ts": "8864b93af30600dbdd7edc6f27dd621eefb1434806f7ebc287e2ad9be4e3815c", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionConfig/isFipsRegion.d.ts": "d2cf0ee5ab41b1540b068e442b9e4d9b812b6fff9f67db6249f47152e1c61728", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionConfig/resolveRegionConfig.d.ts": "cfe84791ef9b041629382db2b6db5c0d9a9e1d16e1e165ae802ddb233933cf48", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionInfo/EndpointVariant.d.ts": "bae29775b2510b99fbdd0b15adc45709950558e9c513e49c13cf67e8832f2d63", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionInfo/EndpointVariantTag.d.ts": "0478de4a822649a76fb62978df048ada75b5ccdcda9465618b4b93ebb990a934", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionInfo/PartitionHash.d.ts": "65f796e281f7d09c5f34cbc6569764a9ea7af5d0081c405d132e8cb122ab9f78", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionInfo/RegionHash.d.ts": "d6e1a66407bc80310a0ecf91b021120ceddadbeb344fcd99f43768705546909c", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionInfo/getHostnameFromVariants.d.ts": "801a736949d89230ac42075f1d53fd2b24052650e8ea459a91c664b0300adb6a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionInfo/getRegionInfo.d.ts": "875f0721a713c8c53c7f5ff14911f667e2654680239ab01be5b17fe5c72855e8", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionInfo/getResolvedHostname.d.ts": "fe54234b712420745db99eed476dc9e1fa8bb191dc357ddb8a99e454124c84ec", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionInfo/getResolvedPartition.d.ts": "e678ef9363f245564b3640fd073cfe258958728f0498f5f0668d594da555ba41", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/config-resolver/regionInfo/getResolvedSigningRegion.d.ts": "535012ec793b4375a1a0753b606a644f5b573cf8f78bfe4981a35b6cc126e625", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/defaults-mode/constants.d.ts": "c7cf9f9484c227a4aa7617be49b65f5514dabd0d1ea1628f46d16174ab50197d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/defaults-mode/defaultsModeConfig.d.ts": "c5b589da23d0d493f56e1c240f28505d7e8382cf42841703ae0755387d092b31", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/defaults-mode/resolveDefaultsModeConfig.browser.d.ts": "1f3573b37718547067da52237068dc35ec9e713324c26e80be14201e506a6701", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/defaults-mode/resolveDefaultsModeConfig.d.ts": "391ee3e0f009539c112f160371c183f7fc2fe90a5d917d57498d765691e2071d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/defaults-mode/resolveDefaultsModeConfig.native.d.ts": "6785b480873a7638d31177dd9d525899de95e20ec3eddc7a4052860097f321e2", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/index.browser.d.ts": "78d7f8a6df79fb7804de91a168bf2e1038357bb127202cf1294eb05680eded5d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/index.d.ts": "85debc148a8d31752ad9f983c60ad4d6078d9f2b525b07916fd437d3403ee4a1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/index.native.d.ts": "2751f0d0a310606a5d56a80a127684a251ce45fdb5d9cb91437f0efb40c958a9", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/node-config-provider/configLoader.d.ts": "7975917b5806878fbc04099b692c9107927de4fa4394bcb086929d98f8884f9a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/node-config-provider/fromEnv.d.ts": "15363e0f51f238557835ffffb882d3dbc7fc0d94e5589f2efdaceff570d40108", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/node-config-provider/fromSharedConfigFiles.d.ts": "bf387dd8155ee9e17a27690464bd763de1dfff8fe4e897e4407464c5ca64b1ae", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/node-config-provider/fromStatic.d.ts": "034b159a90f32857f000098779f7362911328eb70582488deba72a5d56ddcfc9", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/node-config-provider/getSelectorName.d.ts": "a3dcae08dbc551f2452e11631192fe035c3ec0611df722ee58666e8afaab4edc", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/property-provider/CredentialsProviderError.d.ts": "ecc5a55ee5c51f26de9bf208e5f9aae410628bfd804812ea5b72e6dca05ca2f6", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/property-provider/ProviderError.d.ts": "84caec22a6c8238d9cccf9e17761e2a57394dc38bd23230b122e707b2d1c88c8", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/property-provider/TokenProviderError.d.ts": "d923f20bc744d7daf5b01f3aecee6accd3e03c9fa5cc4a8aeb1d841f80ab06f5", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/property-provider/chain.d.ts": "c4a4669af64839dd58b3221fd436a90c19c0456ba6da8cc61e54061ca01f4984", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/property-provider/fromValue.d.ts": "42feb322c2bcda439b1f91f03a87909cc72c0319e297deaa0e199264dd568852", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/property-provider/memoize.d.ts": "ca7e17475ef76b3def220c52d475d9af88375fe4bc63cc5f29d94b502bb354cc", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/constants.d.ts": "7936d8db386ee24d8140a6b07eebce63798675682af53a9a7dbdcb80e3c5c4d7", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/externalDataInterceptor.d.ts": "04ba3374c6930f30367172f890369d0e889a3f839cf1a373b323cbbf02b00e54", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/getConfigData.d.ts": "4ad088a3202f7f44d726ed2a7eda4769216f15de07ad78dc1f77224b0ba8241d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/getConfigFilepath.d.ts": "c6b5dda8c3357c21c2cceedc7f09bf2f5442ba4a22dfc092002670bda455316b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/getCredentialsFilepath.d.ts": "32484caba3ed30faed38de9e1936182b4c2164fe429876ba09f4be6bf737c62e", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/getHomeDir.d.ts": "80f81a68a9d9d3ba46144c163cfa0d3711a1a3c8d303dd6a815dc4f72adb29af", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/getProfileName.d.ts": "03b0d3095594ebec7040b79884824fe756252d8576e944d3a8e13fd42eb28f5a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/getSSOTokenFilepath.d.ts": "6b30e41c05d8148d56b092f5edb107b0f9bea5d3d4c212834c3fa04a5a41e01b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/getSSOTokenFromFile.d.ts": "37f99364c299a71cfad93f79ea26509a7a85aa4cac7ff2330a516f4c9377a409", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/getSsoSessionData.d.ts": "0c521ebcfa34acfb2bb859580db55be1e4404c85af7c52177d419f75fd8ebada", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/loadSharedConfigFiles.d.ts": "cac71ce9cf18ce38cb30937609fc1b4de47fbce088fabed06bff8950f052ceb3", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/loadSsoSessionData.d.ts": "6b20e39ba0c3cf6e20d8dfb014188b74e6b6d79e60643f0696efe0b9b4a71a6f", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/mergeConfigFiles.d.ts": "501ce19e579c83413059bfbc4b16875cde52112b2ea18ac84d8ae1df2ad1e3a0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/parseIni.d.ts": "9d07f922e259473975911cc3c7bd4285fa504e196c065c9a463ca25138d83842", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/parseKnownFiles.d.ts": "04531ba27ac2b082e14b466ad4181adf8e6c48ea86669653ff8b214b101ec9a8", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/readFile.d.ts": "d498c4c6eea825cb07f4ddad28ebeba672b9abd931e04fb78fe57ce5398a2835", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/shared-ini-file-loader/types.d.ts": "db02d15ddfa09e7d7a16690114e4dc3fe5734923cdc2cf66b7415aa9ce2feee0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/util-config-provider/booleanSelector.d.ts": "e681bb83ccbd2df23394f6c27a9084f417759f5b3d599830bb8c87c5496f304d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/util-config-provider/numberSelector.d.ts": "03cd017388a883e8972af1943b7f0d65e4ab8500de5aaf99206f7f49ed240de0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/config/util-config-provider/types.d.ts": "d05017b828b4be537aada525c4969f52959e70517e32825e91f7629e11417db5", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/index.browser.d.ts": "c6d7bac5ea1e6ea2f72ae901337dac7f0a1c3900809686f738b3fa05f1480f43", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/index.d.ts": "c6d7bac5ea1e6ea2f72ae901337dac7f0a1c3900809686f738b3fa05f1480f43", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/adaptors/createConfigValueProvider.d.ts": "fc15147ead142e5e27e1766a957e07540425bf6284954449e2bc5be1e7732be1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/adaptors/getEndpointFromConfig.browser.d.ts": "8df5d7458a0f2cf4509092e1e646430b16e8885d7f24a791cc667a9e20620b76", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/adaptors/getEndpointFromConfig.d.ts": "0e32002a2efa7d9b44d2c5d22257ae5e938c8b8da15f3dfe8afe8ba29d3f4c13", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/adaptors/getEndpointFromInstructions.d.ts": "84b27d7248ca4b3f9635241a631dc4fd534c85f8edc9afe93355183b483fc48a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/adaptors/getEndpointUrlConfig.d.ts": "62240145ea1b7e5593cad1b79e4025d93095591f881df8cc6fcfb697f193e027", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/adaptors/getIgnoreConfiguredEndpointUrls.d.ts": "551600e9968f276e10cf8ee54f4150ea5406da479c2baa5aef78eb0f633d0915", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/adaptors/toEndpointV1.d.ts": "e38fc9540d52fe25d54431aed697b16e14d433e11fe2e458cd0df9cb69f1b91a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/endpointMiddleware.d.ts": "252c236e64367ec9fff032bfcf4e83adf5f9f091286a486aac121ef2683d038d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/getEndpointPlugin.d.ts": "2156f3850794405933d42ea79fd5e11191f8f391900aedc705254a89b46b3a65", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/resolveEndpointConfig.d.ts": "c63874469bbc2f99daaca312f5cbc73c740905a8a01125ef2cf24e03330a78df", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/resolveEndpointRequiredConfig.d.ts": "f9bfe9c667187fbdba58c5750339ae68390d8b9d2432e547d4c1f1ff48bcdce0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/service-customizations/index.d.ts": "ebde6b32f37cb883da80cb917c71e4703c264185b2e3daf036861d0e3c7c51a1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/middleware-endpoint/service-customizations/s3.d.ts": "d9f8fcd5a26ff048fb5a2c8459ffa6f31f2f27e586518437004e88b9a29e4cea", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/bdd/BinaryDecisionDiagram.d.ts": "87a792172f79920f0860fcfc352e38103ee57ecfd088db37ecae9723fd26face", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/cache/EndpointCache.d.ts": "b30b9dbae325e9626c3402d74628af2ee1a68bfad8341aac4d32fe0818406f16", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/debug/debugId.d.ts": "3faa51757d189baefc82ccfcfcb589c41f9d2e90ee53f4470fa9b617b19cb9e1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/debug/index.d.ts": "930df3cafeeb85ca0894110e9c41e3f5d4eb6b43065a6f01ab947bd985222f63", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/debug/toDebugString.d.ts": "7c4808552431149326712075698f0a8be9a8a78716761d5c72b6abc734952404", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/decideEndpoint.d.ts": "1569b46d38236445751e131d58291f6e563d13d190c39c667dce3fded613d914", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/booleanEquals.d.ts": "34fc35538347f86151afe30d86f52f677b78908454d6e02003e92f82c9edff68", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/coalesce.d.ts": "b8fb5f25732c8776e7005aae8b8d3e4ce4acf96686736f96ff47265784956cb9", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/getAttr.d.ts": "445fcd321376df7010de4f937eb5f4a9d39cdb00bd3601f11f40f73e1e60a193", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/getAttrPathList.d.ts": "647cee49319b0eb1c5312db1948f932999038b3bfff923b619add800c8746613", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/index.d.ts": "58e47f0f43aec4ce2e87078c068c249c4f42afe69d6609c26fdf2001dbbe0c4a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/isIpAddress.d.ts": "4f3ac955dacf8eb7f715fc81583b0fe050207961a64858c464aebed13dd8efb2", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/isSet.d.ts": "3f8878b1609a77736bf5b5cbd32f300bd4ad19df45bd59b37cc9625659db79c8", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/ite.d.ts": "5b18a28f2ff0081df4d6ea11d7778fec5a801b115d262036b34d65b7904a9500", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/not.d.ts": "f367352f0fbf7b3554862e7cbd146ab67fc2a7a3af5fc90eecf2c73dcd52539d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/parseURL.d.ts": "2ebae7169d4b7b79a0d68c14130cb67c666820a5e9f8a1c8a546e4977946a145", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/split.d.ts": "7dd5a50070502109631a840cd6a3ff6801746ff60265cbc4c370e3c617ca7496", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/stringEquals.d.ts": "ee4ece12cc94dac5642b63b210d1d899b211fbb1a3528a8169095944cff89284", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/substring.d.ts": "1f92ccd2f557588ef11249dd178e547aa63a1fba7209ba595edec962e38bc983", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/lib/uriEncode.d.ts": "93715e3d5120580596a6a2d0e18300713d1c541f8d18810b307e5c161024c19d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/resolveEndpoint.d.ts": "99db45fa086712752e3c427b726572d9a5d8f4302d674553d77c8695959d3e6a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/types/EndpointError.d.ts": "3caafcd4580491aef37e66bb8b9f1d97d9e0eecd712b3a5bbbadf8049b23f360", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/types/EndpointFunctions.d.ts": "b331a0637d366164c5821cbb43a47f1947afcea3e486827c3e3b64bbd25dc39b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/types/EndpointRuleObject.d.ts": "cd61ce82bcbd250a477cf57e142ea14bd36657589681a2539e3e05afc92247fb", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/types/ErrorRuleObject.d.ts": "fe126bde012fed5563b01b2afe6ce55df0be7340942d7148ecfd7f31c5eccd1a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/types/RuleSetObject.d.ts": "e4dccb0aaf507b006ceb8f015a40981c8e452fe5463ca2af3037068ef89eda6e", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/types/TreeRuleObject.d.ts": "4440e44f7c10643433461184c5a747495bb87c4112ed62514fd2bb87f7ed2db7", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/types/index.d.ts": "f919c5fa718faebbc605d73baa5003f0ceea01f576759479756ca6db454b5b6d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/types/shared.d.ts": "e49e737a136b7aa9a973ba997dee1d0b180e158d56221875628ac0e9485c08fc", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/callFunction.d.ts": "01909804aa67bb64a5767f042d4c1a556b4241124b51865eb737b3b453d048a4", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/customEndpointFunctions.d.ts": "12877154767a79a955523cf4128d91541a6493a4c0c86bd59bfe30231385661e", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/endpointFunctions.d.ts": "970dcfb405b7a0979e5ea627bf1fbd358c80eeb989c9d7f490d78a5e1bafab9e", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/evaluateCondition.d.ts": "84866c4b2ad1e4bd210d4f9b6e87ac6d5ee64ca044b13b2190cde31720764963", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/evaluateConditions.d.ts": "98025ea2a956ac2cf3659c2abae0c6c314903e881965430e28e855a88fa5c06f", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/evaluateEndpointRule.d.ts": "aeb74b08620a99ab1697cb9353f6777ef0e27f83817f093159a4dee5670f67b8", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/evaluateErrorRule.d.ts": "16fa19f2402afc07d07f55b3ab001988e3094c259806f2d8e89a0c417157f160", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/evaluateExpression.d.ts": "d12668f3b2a300ef46ecb19717440e834b5fddaef063eb34fc844b396874a3f2", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/evaluateRules.d.ts": "b2cf0071674c3449cdbd144649ba81a61e02616e9571e3650578cb8ecfc0f1ef", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/evaluateTemplate.d.ts": "10b3c6b1b05c67da6c89ae688d28a6616c88ddd6233cabf32fa0d13ab36190ee", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/evaluateTreeRule.d.ts": "6c09b72eddc20832b678e3e47f0570185add36a53b1bf7300edfc06d96fb56e3", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/getEndpointHeaders.d.ts": "8019091cb46bfe79839b5f9be83c8fcd477c7fe3c4065cfd5154e587fd7ee53c", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/getEndpointProperties.d.ts": "6556ab125fd8859787850ac309c4eae8ed5526e5563f7eb14c7ca6480d123a8c", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/getEndpointProperty.d.ts": "73c1f7d5c215fa011b9ac7632ce64fa560ef836785ebec27f64ade53dfbf20ea", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/getEndpointUrl.d.ts": "63509228a3802000fe5e767b7205f58b51b0e7869e6f3005f0f065ac6f1ae586", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/getReferenceValue.d.ts": "312fe4a91e69fffb62ca6f480a0b45dbed8d73afe009638a37965bfea3db7df6", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/endpoints/util-endpoints/utils/index.d.ts": "325807276ef7a62ba9253fd5e9b610e0234a3dedb2f4a9dae6b40360587f2b6d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/errors/errors.d.ts": "0e6e9b08385bbd99f6e98a791f675867d162a0652a39f300a327d2b8c4917ac5", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/errors/index.d.ts": "6106eb042171930dbe309b7ca95d319e998fdbfbec12914f50a669e60cca8cbc", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/EventStreamSerde.d.ts": "40a7a9b63e5d55cc0d4ea1bce60d4839375c71a5a9834561f726fa0ab4031fe6", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-codec/EventStreamCodec.d.ts": "632865e0a1f7621f7ebe007c0e355c1c425d711a30fe5efab2b52def5ae74b5f", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-codec/HeaderMarshaller.d.ts": "3f8a48f34c32ed7af5d91aaea7cb22c2d26113198ccff4fc9da9eddccc634bcd", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-codec/Int64.d.ts": "7f3696ea8f8b3b202e8e49eef34d8fe6773f383dc0e2902465bdb3a359279208", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-codec/Message.d.ts": "c2efa904f4e173f0b9debe751f54f798ba71fc99891278e3912da4b5e7ac27c5", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-codec/MessageDecoderStream.d.ts": "0f935c62d72691c06d658c30553f1cd970e303c83298e0a1bb310241a4693507", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-codec/MessageEncoderStream.d.ts": "77e459fef40d561c9724d53669bde61fb0475e77b939b46778ff580f9ed38380", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-codec/SmithyMessageDecoderStream.d.ts": "8b7daf148c6e4639280f3cc567797959e2157b14ad4f3b6692d8590dfad9a17f", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-codec/SmithyMessageEncoderStream.d.ts": "19d49f61d7243870aa843f6098f64f7eafabb4e6cf3eaea54f1246c5166e0f82", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-codec/splitMessage.d.ts": "a63bf62d74efe47cfde870654afdfb8fe2a16823dfa26f4ba57bc1af1884ee43", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-serde-config-resolver/EventStreamSerdeConfig.d.ts": "a904f5af9c084e9f4310013d4e9a0963d0b3ad2a7dbef7c391f52a0a77f62d2e", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-serde-universal/EventStreamMarshaller.d.ts": "676aaaa527b36f82b29a9dd98ec32eb97b139cc85d813220b7b825d1e9054dc4", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-serde-universal/getChunkedStream.d.ts": "ca30c48f0c31bf75e85b26d2d9eee1061724067e68c5badaeb3deffa02892c04", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-serde-universal/getUnmarshalledStream.d.ts": "6feeea21717b89c047ffcb0a9d96e431aa63545103cede8792d01575129ad4bf", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-serde/EventStreamMarshaller.browser.d.ts": "98ac02e1e22e60ad1fac19947089b561c2116b8cb5148b3ddd039e3c95990db1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-serde/EventStreamMarshaller.d.ts": "80095adb5965b4ec9b438fc2188425c6c432d93bad78d530519f7c1fa33e2d58", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/eventstream-serde/utils.d.ts": "16686f342a700f228e0b7fbf48620960bfdd628b44bc7e804c8097c963e30e56", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/index.browser.d.ts": "3c605c28f180ec86f1158c758221f48058ed56b865bf2c3c3653812d612fa5f1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/event-streams/index.d.ts": "0e1f2f1b8625b71116622f6fd8be65ffe7b4a85f61551017f455b7f0f9982dc0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/HttpBindingProtocol.d.ts": "cabe97ce06a6d3398731cf6833571809a41bc9b575cefef94dfa56cfba027abb", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/HttpProtocol.d.ts": "1ccb865587a44753ee585ff98f48e3ac1134dff51f1f8634d75e88df242a7037", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/RpcProtocol.d.ts": "577a95e6208d67937f05a5ab1433fefd82d7b140ece997b9667d08cdcc4cde6d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/SerdeContext.d.ts": "16b584ca2e260b9cb3c251d2fdb5b3d4f7588245927eff0e4856986a2d11e773", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/collect-stream-body.d.ts": "9862bc5221726fee5d9669f7d317a6dcb3cdd61422870aae550f92b5f3198ab8", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/extended-encode-uri-component.d.ts": "ee0da840a81501de07cac4bf819db2dbcca6020d63857d30c35c72e85ee2053a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/index.d.ts": "38c91efc5078b75d051f3368d8efc6d14363ff48520fd8e7e8d1990247dfafc1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/middleware-content-length/contentLengthMiddleware.d.ts": "0f5a7f09a123bcd08562d90a48d94d8a4272d99356b3bbc964379a6d743af6bf", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/protocol-http/Field.d.ts": "1c7966c6a43f1aeda06fd1696ad5e575c6e1da7adc58cea5d362800905b41655", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/protocol-http/Fields.d.ts": "463d0fe2765a33e6f4130916dffd421faabae3a097b1ce0f5ea9b034a1addafe", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/protocol-http/extensions/httpExtensionConfiguration.d.ts": "dfea8d644e96e8ff358aa70f5b91c55c6cdff211816f156290a72fb0b9e8d68b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/protocol-http/httpHandler.d.ts": "c2d031e54e5b3bca472601244ba3edefbf3eb43508bc7ad26d0c87ec77b8bf1c", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/protocol-http/types.d.ts": "0db05ead7cfa66dc6e35bf7275c562bb5328e6bae77bd4786515cff8735c64eb", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/querystring-builder/buildQueryString.d.ts": "77387ef5d864e66b22cedf43f4c0c71988bc67bb40f2885137268db1ad02dad3", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/requestBuilder.d.ts": "698f574b15c4ebf7aea2034139f3f90df1b6e8df35195973cb2d005f3a956103", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/resolve-path.d.ts": "83baec227f6f942b206b6dbf74c53c7a46ad101953e5b0a4410d2abad3cfcfe0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/serde/FromStringShapeDeserializer.d.ts": "6e285a10b278d3c886dc2ba4a61cf3155bc8db580e2ce34fdfa8689c3e311baf", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/serde/HttpInterceptingShapeDeserializer.d.ts": "9f464b710685e9e212896d0020e34e75ce64230450bce640a732653a52b2cb16", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/serde/HttpInterceptingShapeSerializer.d.ts": "350a63b04ff458d062dc18c19367073e07384902f6296f3d00bb933902942646", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/serde/ToStringShapeSerializer.d.ts": "721739c357010c433cb50c031be0df2b4f13bedee208d82f671274c235b22d6f", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/serde/determineTimestampFormat.d.ts": "baccb1dfffb415febeb85e969b81157ca510d097530da22f694b3665491e6ba8", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/util-uri-escape/escape-uri-path.d.ts": "9816ae663f5381bd06f3086c50141035bcd4d3c25d55a994c77ab3656763e7ef", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/protocols/util-uri-escape/escape-uri.d.ts": "5c7b01721d29fd758ccef4392cf8dd692fc383c809102da53399983bfe033a81", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/index.browser.d.ts": "57d7097e3b1b85959a5763c636ca1ac8c32d679afe42b5d328682bb95b6ef2d5", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/index.d.ts": "6c5a568c8028faabeb39182233d4dcffd7fdb791db270315da1abcb268d061c8", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/configurations.d.ts": "dd3ad564a7c3cdfae6a61e53e674f3b120997cb2e74c97c6b802825f41bea4ba", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/isStreamingPayload/isStreamingPayload.browser.d.ts": "05ec35f30c930632f34efe5a8954ac1907829f413b35bbb0c127d94ebba515b1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/isStreamingPayload/isStreamingPayload.d.ts": "05ec35f30c930632f34efe5a8954ac1907829f413b35bbb0c127d94ebba515b1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/longPollMiddleware.d.ts": "4b29a0681aefe79eb0f18b4b732a2b156c0811133cb1267659ce5cee17bc66e0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/omitRetryHeadersMiddleware.d.ts": "8e85e4ecc96f85ead73be75f4cb3f5b15bb8f5836b2b28eed2c05cb73450eda7", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/parseRetryAfterHeader.d.ts": "69ec8a2203d6fd0d26ee0cab38fd34f206f7579c1be91ec7589a45aaf4ad287b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/retry-pre-sra-deprecated/AdaptiveRetryStrategy.d.ts": "b4c730e0aea9ac513bf05ebae6901860db96019022ae65e887f6c5e5b6f9f46c", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/retry-pre-sra-deprecated/StandardRetryStrategy.d.ts": "110548c79705848f437e5cc457b92d8d2d0607bd4e77516c70f489536a45bd74", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/retry-pre-sra-deprecated/defaultRetryQuota.d.ts": "7eec13f40e667750ec2323aec9c647f816d4f2f944be9b58763624870ba86a0b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/retry-pre-sra-deprecated/delayDecider.d.ts": "d3434f248f1abdbe3f72dcfe2a1747d9933275003b02f1d6568fa0fd14e9e5aa", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/retry-pre-sra-deprecated/retryDecider.d.ts": "3a98295df0e8b18ef254938bc0706f53e51d1af9c472fced01cf7b0f5cc10251", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/retry-pre-sra-deprecated/types.d.ts": "dd54f96775930ee2c284d2d476f69b5df602e0382e0c5fb79b5d1e7d9d4db90f", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/retryMiddleware.d.ts": "129b6948bffcf98396890e14b79bb1d17456f8f8f4bd839d07bb11a64876a306", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/middleware-retry/util.d.ts": "fd7f146c0780e038c7a132ff90d7853cfc1c7f91bae3dd35cab919926efa95b4", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/service-error-classification/constants.d.ts": "32728f03079c85b34e71ac9032940edaa59055a6f6dfbb7949d2339b65e55b35", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/service-error-classification/service-error-classification.d.ts": "c793a70666a685ff6d3031729004a6a86b6bd5dc75a4684c0872463ef4a317f1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/util-retry/AdaptiveRetryStrategy.d.ts": "c51d736acdc1aecd4f101372fbae6f1ce11a89e08ab94e309c6d1fca776a2e4b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/util-retry/ConfiguredRetryStrategy.d.ts": "7810011fdcb4fba88c9578ee8ab74934010bb1e49573d6494abc463af9d867c0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/util-retry/DefaultRateLimiter.d.ts": "026e73cd154f6ba30d219cb3fd8a48cb5f2420dd00fcf4306cf804ebc1bcd181", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/util-retry/DefaultRetryBackoffStrategy.d.ts": "a3246caa2db0f9f16f6d1b3e0390a79b477e4200769dbb7ff034c1ea591f0786", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/util-retry/DefaultRetryToken.d.ts": "ce033c481d2a356e253681f05b8bbf92faf022e0fc9dad54822989ec142057ec", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/util-retry/StandardRetryStrategy.d.ts": "955583a6bef0a9010eb54afc04bbb0a8f6120d96ef5a73396318f8d7f3e30b05", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/util-retry/config.d.ts": "1156ecb63cf23e33595c88122402026dfebc67a1db283405c01e9cfc162d761d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/util-retry/constants.d.ts": "29849e5432fe87f35f91889546ec4690a46e6d695026101ab86e9c70ddfff5db", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/util-retry/retries-2026-config.d.ts": "28c4e58be2d02c6d7c740f3a4a4ce282a4559931ad0c083741fcad21851bf417", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/retry/util-retry/types.d.ts": "f733c78d3596919bbc29e701c68d2d98e4daff6eb1f0e65e8d1475af4e3d8a98", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/TypeRegistry.d.ts": "aaaf7b8ab12f601232e4e7cb522fb542d557027419109f23ddf26d41801a802f", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/deref.d.ts": "664935d69d15d46c8e6dab81a6016d9a165445d352bd710b39622a033c221cae", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/index.d.ts": "b5b271cf00557b8366dac4e1baef8fb7bb1833c44fb1a1beb459b1c07ff97d60", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/middleware/getSchemaSerdePlugin.d.ts": "fea589c2b817ec1c14b4b1e22c4d8c6382cc68c535859feaa61e2b58bbdf27e2", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/middleware/schema-middleware-types.d.ts": "28bcec4f215e3a87029898ab6319adf8b8077f8c89c42535d5a4475c70e74194", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/middleware/schemaDeserializationMiddleware.d.ts": "8dea07abb09e4543773bbfe82c6d27fdd14cbef96dbc1d70f1711737fb73fcea", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/middleware/schemaSerializationMiddleware.d.ts": "e8590758a0eeed724db478da9e60e9dea26a2a7545de91b20080de8008ebec03", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/schemas/ErrorSchema.d.ts": "f5bced449ddd805666270fa3521cf588ae9937a38b8cd9b05570fccc6ab9843d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/schemas/ListSchema.d.ts": "a3c2e4b9e4c4eef5548326c2793f3c50a8420522197b5a92044f0d1fafd75772", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/schemas/MapSchema.d.ts": "299d6977c037ab195abf65f0613fc564c5b15ae9b3807fb8229a46f80d1baf27", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/schemas/NormalizedSchema.d.ts": "99b49e9921db47b24ead3fa9ce7ec222dfd26b7bcf6f3e4c1f5cb8a3b131e120", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/schemas/OperationSchema.d.ts": "9f015ba022284516ceec12e5db768f5556d7e025c35498de1b0a1856889831c1", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/schemas/Schema.d.ts": "6f727e19aa19d76d58da9eb19711d5695ef2091d68ef37939275cff703bb2410", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/schemas/SimpleSchema.d.ts": "cc9a0094694be19af57beb93afb931788885d241f53402df9b4bf5bda2b924bc", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/schemas/StructureSchema.d.ts": "3ad82893978cddee6369ba0fe0e3cb2966c10aa0aef2bf76dbc60f7c5d37f15b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/schemas/operation.d.ts": "c396404d3eebb05ff2ca1b5efe4f1ba5026341e440288dfb157ca3d312e93b03", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/schemas/sentinels.d.ts": "cc5f8b805dfd614c75bfe16a1c1ca2c40c23f11cdd0d879acb28680206b459f9", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/schema/schemas/translateTraits.d.ts": "7072c8d47ee263d432f81140177283cf6aa2375e49f9fab11ba979fbcba4da7f", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/concatBytes.d.ts": "bbb5f4691e7227f125424132a165500c7dc39bd9c5f6cabd0dabbb5d19470e04", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/copyDocumentWithTransform.d.ts": "d1e72291c03776842b355b07af413a426d54b47c49ad7176febf9979603fd36b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/date-utils.d.ts": "16777aedfdd9e413380aae387dc6f57f001e3168d044f59773c1faaefa60fc65", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/hash-node/hash-node.d.ts": "3ceb66cd3bf42e386fcf282ca5e27da58b0402105cbd88f23054ecde822f5b03", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/index.browser.d.ts": "089dee6bb4967d87a83097027b64e71af54f4595db5afb1e23c0acc2b343e485", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/index.d.ts": "37fa5b0e98fe469420fa2536d9e3300638670d3c862bbac8c155c7f395f967a2", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/index.native.d.ts": "f512173c9474cb6f6711911a1fe9fc0906cf7fec8bf128ff236793b55ea4005c", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/is-array-buffer/is-array-buffer.d.ts": "69a2ea725fddda0c7aca29bd81e0f31ebfa489268cc141575c7e48458b03bdeb", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/lazy-json.d.ts": "3b7a04393b874bd0bd37952594ac3534ad75ad9422eb3aec184b8d4ec0749a41", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/middleware-serde/deserializerMiddleware.d.ts": "0f39bbecbc8b9ea10ebdd5632ae4a5863c9e505291ba29b0614fe59a8d6b0976", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/middleware-serde/serdePlugin.d.ts": "48c912de69dc3fe0f570f850874203d5cab24690a2eb40e2b3db937486602808", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/middleware-serde/serializerMiddleware.d.ts": "e334eb5ebd979048df20baa68b79fe42c4a9a6b997d2660f3d5bba4213d6dd5b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/parse-utils.d.ts": "157953fa8c0ccf353852aaa6375d82f87f0692a2c3668d054aabc732a4d5b4d8", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/quote-header.d.ts": "0a57fd0d9f222d325517988e80391aec02b007e6c18dc3ba903d785118b2db3f", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/schema-serde-lib/schema-date-utils.d.ts": "5f26e31fbb47c97e98ac50533e6c21ea2efdddeff61ea64670542d39edcfeddb", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/split-every.d.ts": "8fe3c1de9f6e82a362103a49218ec1cd3e268826756692f8200f4ce3d841e6bc", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/split-header.d.ts": "675794933c4e73addf3f0875d1fe7ffa41bd431730fa8294b8f0b61594489b54", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-base64/constants-for-browser.d.ts": "a5aee57ad3887206cb257254a3dc3f7dfde8768506db05edc53db84f178dd254", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-base64/fromBase64.browser.d.ts": "366bdaee93bb8bd645720cfb292e95a6c79a8eb671b930a0cfff46e203a13888", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-base64/fromBase64.d.ts": "a3b7f281a88ba15439b302b6c5c5c130f26dc0c9686117fa97778c0f0dae1dc6", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-base64/toBase64.browser.d.ts": "4dbd7d9ac3e60b2bde09c551ce125a607b87fee71f5b72609597c9b1411cadeb", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-base64/toBase64.d.ts": "98df7c89c092ee9b34e95667947e1c9494193e0f8da13278f58f8dc25876692e", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-body-length/calculateBodyLength.browser.d.ts": "60f003f27d9f1e95e4907b641df8d72a57e577418f1d226de31cdbdf2dfc23e5", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-body-length/calculateBodyLength.d.ts": "60f003f27d9f1e95e4907b641df8d72a57e577418f1d226de31cdbdf2dfc23e5", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-buffer-from/buffer-from.d.ts": "52c35fd6136df12094b30da1e1a4ebc8760a813fb255e73c5f771d17758a5c26", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-hex-encoding/hex-encoding.d.ts": "2a6a188dd68fb900fea933ecc4c3ec5e4212323fcbc26222d189b31bd567a58b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/ByteArrayCollector.d.ts": "1f2276ca76d661c22ff504008ceca38a5e94fa86b68a2f36e2a8e156216da0cf", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/blob/Uint8ArrayBlobAdapter.d.ts": "edf63ab29a024f121eda8982fd852af35d281a4acc1fda808ebde2eb0145b504", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/checksum/ChecksumStream.browser.d.ts": "4171d99280c240ae501d925c17e61c84e5e913d3dc31e9c14259d75e3a843057", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/checksum/ChecksumStream.d.ts": "6a520a109e4c3f536bfce7132bad36866e901c889730b54f8766602cb36eb85b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/checksum/createChecksumStream.browser.d.ts": "10f7e15ada080a485f3d13122847f30ca073e55139a77254a0c01d93a53583f6", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/checksum/createChecksumStream.d.ts": "bcc3632b1842f4fdb4ca28af758e53545f8db5acb955d74c463d3fcff7ddfeca", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/createBufferedReadable.browser.d.ts": "b1d247736fc11a2a9032fb9e6d078d9ec491383048bbab23154bbfbc3dd499e6", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/createBufferedReadable.d.ts": "b4069829dce010d66b73e88e51befe64074ae3cc867ef479178fa4b8916d033b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/getAwsChunkedEncodingStream.browser.d.ts": "f74936309bca465c4c5ddb55f6b51180c568950a8e0198004b0ecaad75e49ce4", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/getAwsChunkedEncodingStream.d.ts": "b06c2d8852303c1608701e3bbf14233c4a8f02dbd3b0ef47a20638d50bcb605d", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/headStream.browser.d.ts": "71ff046b398448fa521090c42b1d9377538ce942a6ccfee50c318f724d204274", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/headStream.d.ts": "70d8ba28bbf364c16ce6a8f1c6c8e9aed3a4b07e9f8d99750e6392432b206927", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/sdk-stream-mixin.browser.d.ts": "b8f9931ac21ae4f95aedc381b926018890bc08e4d4ea874f45a2608e3acee567", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/sdk-stream-mixin.d.ts": "ca60878a6b30c99cb422206962e5e9d0fe75ed46d0fb5ce46546d491b919aace", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/splitStream.browser.d.ts": "679e2b654b184dd323b4ac53b7efdc83ddcdc149ce3a09659f35efa9e0cd3fec", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/splitStream.d.ts": "aca26403b1704c9ab706c6375a12a58c7f31d1bcb721608d2dc7d191f6316854", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/stream-collector.browser.d.ts": "28b51c2fac4b59e449a582edd5b30713fa8c9c822343fa55e5f9b91dce82d932", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/stream-collector.d.ts": "26e6b9aef8e0e3f089945b93d2359903c084b47f04d6131b74e62a19e8ff5ab3", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-stream/stream-type-check.d.ts": "d655db134d2e852482f80838a162bb77fa69afb14dc8d23087857ed25aae45a7", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-utf8/fromUtf8.browser.d.ts": "a6bb656df76c14930cd7295417f3f5dcb912ef2a9a7607179111793763bdcf0c", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-utf8/fromUtf8.d.ts": "a6bb656df76c14930cd7295417f3f5dcb912ef2a9a7607179111793763bdcf0c", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-utf8/toUint8Array.browser.d.ts": "d6962664266d284bd8e6c6bb0ed34101fe8c1c8c87e64bbc976fb2194707c107", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-utf8/toUint8Array.d.ts": "11432a3c24f4a9abb69bf5d4e4d9159bc8be524fca6a88e820df55f89a5dbf5b", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-utf8/toUtf8.browser.d.ts": "cdd346a04cb50734d334ec12bfefaa886589490f03dddaa152115d1c5f6d1232", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/util-utf8/toUtf8.d.ts": "cdd346a04cb50734d334ec12bfefaa886589490f03dddaa152115d1c5f6d1232", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/uuid/v4.d.ts": "53d635439e4ab23e53539e59781b98120be15023e50c22c1133eebdf9757a3c0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/serde/value/NumericValue.d.ts": "3142a67deb09f7429a80c72b6a115ebc6b945a8ef9d9abc615dd2557968abec5", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/transport/getSmithyContext.d.ts": "ad8345bbd7e05ce56bf5973ac4aecf334e81276634a7c920028aa354879f2518", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/transport/hasOwn.d.ts": "e385f845168d67b8f3377ef1fa4cd75a52f3b92d7f876c4dea43e6f6799ad9a3", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/transport/httpRequest.d.ts": "9d407048a33931bda2fce1d7e4b5ebd586af5038f27cd69b3dfd9e6eb910ffc2", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/transport/httpResponse.d.ts": "58cee26f4ab6d1979848cca4e1ebd914b8d1f4b0a2bc2ed619398ac9111bb432", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/transport/index.d.ts": "fba4a002ba4661ab451809748fd48264debc7c23d0c7ad59c2a7dd02ccbb31b0", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/transport/isValidHostLabel.d.ts": "1ad9d315e9d8362e38c9c7e7a71fe208e41d3bc9c7d32749ed2b91fc5dbd0b77", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/transport/isValidHostname.d.ts": "9c5cdf7a7248298536129b3e4863be0ea3c14a6dd21b3599a9b626198e9b2a73", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/transport/normalizeProvider.d.ts": "2ae80cc9029e6addb30af3c34b140cf673ae9ff06b0a34180dd023610b5e6564", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/transport/parseQueryString.d.ts": "073923c94392b6f8061782e6396f6f17ff88784c9fd5187b10badab13a31c29a", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/transport/parseUrl.d.ts": "e61da69bbfa07e43c49c965341b34b723cb3d2705a4a3682abf4f7d90800ab71", + "node_modules/@smithy/core/dist-types/ts3.4/submodules/transport/toEndpointV1.d.ts": "f29d07d8c3ced0f246b7d046e2e60ec336de12d04c0391a517d0efd96be004e0", + "node_modules/@smithy/core/endpoints.d.ts": "7fffea98aaf3ef9e49a70fc0ff2ee2954b1c9842ea20ffd97e9091af01ba5660", + "node_modules/@smithy/core/endpoints.js": "7fba0ecf073567f6d1dc6cf185459181a789e0467a4aba86b6b42b69819331f8", + "node_modules/@smithy/core/errors.d.ts": "097484a7ec9893d799ac3933ab4ea9b030a295e889875c081a988929136c6dd9", + "node_modules/@smithy/core/errors.js": "265d8892bfbca7244878a1bdf3684a292e10881636d4bd140dc93b0959d29cb4", + "node_modules/@smithy/core/event-streams.d.ts": "48028c8c551ab03f393dc03a257cb94e24708cbca89077f1983b3fe4540bbb2d", + "node_modules/@smithy/core/event-streams.js": "eb2606189778c44780a353d184c5fbf978a77a18f77ef5540573c87814ad4b66", + "node_modules/@smithy/core/package.json": "b3a06d1c549d4b1ead3b19f7259d3506f3eaaac4b8fc0d3e9027c67aaf71a30d", + "node_modules/@smithy/core/protocols.d.ts": "123ed03a3258ddfa73be39733bbf68983db34ca0a8392688d4efbd57100038cd", + "node_modules/@smithy/core/protocols.js": "16a7b6cc1992ce81a549950c3d4618f97a9933f9d038a053511d922414b1550c", + "node_modules/@smithy/core/retry.d.ts": "de2f0a85f528ef7d43d06e54516ad743dd6e510ebce5fc0c6f996bffa6035cb4", + "node_modules/@smithy/core/retry.js": "da2d935bb1e733976cd894f642fba0e3d533170abaa2fa49e2fd35fd97c83806", + "node_modules/@smithy/core/schema.d.ts": "52d85d61c3ec7d42cfc394350c891015f8e191812090e383e30056d70d6003b9", + "node_modules/@smithy/core/schema.js": "dc63106c61c4566bb7771a55d9d1683763b34ecc48c18daf72ea9061a585920a", + "node_modules/@smithy/core/serde.d.ts": "1d77edfd43bcd865a2559856b4baef6e6a6fe55f9548c7d762d168cef6ef1087", + "node_modules/@smithy/core/serde.js": "b5a192f313acbbc2bda0577ac957027706dfc8c00c1d3a335bb1a471afbad6d4", + "node_modules/@smithy/core/transport.d.ts": "4131ec9afcc3fc1e9c8b1eb1771353283deb688c15650d5d066c11d125a21d6e", + "node_modules/@smithy/core/transport.js": "e7c26e0550efe57d6718126f75b8a6486a711950a034b9d1268caf9effdc6ae0", + "node_modules/@smithy/credential-provider-imds/CHANGELOG.md": "6a84c4bc3f6207da510e91820303a9b2facbde30ce1d1f91c40e3cdcb42d3bf6", + "node_modules/@smithy/credential-provider-imds/LICENSE": "d82ceb8cbd004a2d4834085c557d55459f528e30268d95159ee1e4030a693a6e", + "node_modules/@smithy/credential-provider-imds/README.md": "2ccd673764cb8c14f790e24f0e928995e3894b05ec898c313078d865a81fa820", + "node_modules/@smithy/credential-provider-imds/dist-cjs/index.js": "b26d7faec162460d7416db210743260b3bd1a6a3f3c565216feb75e9123d5cb7", + "node_modules/@smithy/credential-provider-imds/dist-es/config/Endpoint.js": "916d828bcd2d134a7971671229dc9be37d1998c66dd048762f5f04beb6cf781c", + "node_modules/@smithy/credential-provider-imds/dist-es/config/EndpointConfigOptions.js": "298d1191ac60cd07e7c3a69ee96a81228a45a4c1f7bad1705c04eba2ad3ab9db", + "node_modules/@smithy/credential-provider-imds/dist-es/config/EndpointMode.js": "d92e3d486d9fe3bd2c2e77120f4fa4323e7a223707cc46a3345430d26c2bfea1", + "node_modules/@smithy/credential-provider-imds/dist-es/config/EndpointModeConfigOptions.js": "fc945062e1b5c9227177c28bab4c577e1cac42060e02d26ebc00a81d6ac764e8", + "node_modules/@smithy/credential-provider-imds/dist-es/error/InstanceMetadataV1FallbackError.js": "fe3eb3027ab6e5c6c5e768500a784c19f001ee480b39d8e85a51b9cbab06d6a2", + "node_modules/@smithy/credential-provider-imds/dist-es/fromContainerMetadata.js": "cd444a3f0eb5f0feba7ca3ef646de47788013fbb723b7ab9c8fe2c8c3adc6e67", + "node_modules/@smithy/credential-provider-imds/dist-es/fromInstanceMetadata.js": "96c7bb7f310dde37cdf8be95057483605cd72c8bca6c7c3710c56fccf09328b1", + "node_modules/@smithy/credential-provider-imds/dist-es/index.js": "0e334901726fd9fa1230bfa466479dec6b1f00116a77ee7ccde5f63a836bbace", + "node_modules/@smithy/credential-provider-imds/dist-es/remoteProvider/ImdsCredentials.js": "c29362e060501f96325f97bafd880ade5504c186682423319af8aa7e2e7bfd3b", + "node_modules/@smithy/credential-provider-imds/dist-es/remoteProvider/RemoteProviderInit.js": "63c246802ccbb03b51cc9ee912ec1b98b74cb3b331c9eb792539d3b20fab4322", + "node_modules/@smithy/credential-provider-imds/dist-es/remoteProvider/httpRequest.js": "0bee64aba6b664a72c8755a29fc709cfdc01a42360eba74b6976cf4c427a57f7", + "node_modules/@smithy/credential-provider-imds/dist-es/remoteProvider/node-http.js": "dc55f5b8829638ccaf985a153933b4c32bf2d59b59ee30ddd3367851e27a586e", + "node_modules/@smithy/credential-provider-imds/dist-es/remoteProvider/retry.js": "815293ecc73079fe3953b73378721ac87832bd76d7d89a1f159f7d051027a9b7", + "node_modules/@smithy/credential-provider-imds/dist-es/types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/credential-provider-imds/dist-es/utils/getExtendedInstanceMetadataCredentials.js": "9ac335662b2ce9f2ab08515b9cb3da02faa3f5bd3b25c3de112db50f5b5d803e", + "node_modules/@smithy/credential-provider-imds/dist-es/utils/getInstanceMetadataEndpoint.js": "8187499fe7ed0209f3389ef6cee1420ac4ac4af8220ec3057b4488626860086b", + "node_modules/@smithy/credential-provider-imds/dist-es/utils/staticStabilityProvider.js": "6089f012682c0b4fd3fd841f21cb491b359baf3818c3947c336a313fb1a87a34", + "node_modules/@smithy/credential-provider-imds/dist-types/config/Endpoint.d.ts": "4bc6dbef10e6d46dad778f5fc190eef94e5568d612140b8791b00b01882ec1d6", + "node_modules/@smithy/credential-provider-imds/dist-types/config/EndpointConfigOptions.d.ts": "de84a9fdeeadf9ca625265e62caaab126682905024ac9225a1cd6cee2d067afa", + "node_modules/@smithy/credential-provider-imds/dist-types/config/EndpointMode.d.ts": "96ee05b1d4ccca90aeb5d6a78b922bdea32728af1c795370165a23c616478665", + "node_modules/@smithy/credential-provider-imds/dist-types/config/EndpointModeConfigOptions.d.ts": "7106b92e15addbc821e1c2cdbcba1f0dc9e582d2b58cbbfcd0907edcde6466fd", + "node_modules/@smithy/credential-provider-imds/dist-types/error/InstanceMetadataV1FallbackError.d.ts": "433c12c85f052730d590413b3d9e2b8b22a5396f1547e6f7dda47ef76b5fed7f", + "node_modules/@smithy/credential-provider-imds/dist-types/fromContainerMetadata.d.ts": "4dc06956e6754397197d6144859c5c8c4d230cbf7264212408b2ee9bc231c99a", + "node_modules/@smithy/credential-provider-imds/dist-types/fromInstanceMetadata.d.ts": "924dd4dd8962392ed22cb6035840c70fb8fb88a0c8bfcdf2b720c7cf5fd1bafc", + "node_modules/@smithy/credential-provider-imds/dist-types/index.d.ts": "40048188891678c9c89af4506f51d0eadc06f50b74d2a50c96de93ed515f4d34", + "node_modules/@smithy/credential-provider-imds/dist-types/remoteProvider/ImdsCredentials.d.ts": "04e03498e03d443f237c538765956db262297b06f74f3190e704a154915cb034", + "node_modules/@smithy/credential-provider-imds/dist-types/remoteProvider/RemoteProviderInit.d.ts": "f62f19c44ae8e7f41fcca5771e1f1d2f18faa9c5148afb5c10ca2b9b2bf47c4c", + "node_modules/@smithy/credential-provider-imds/dist-types/remoteProvider/httpRequest.d.ts": "e6fd113e9f008ab731e534d7c4c2d68935f9e49aecfb43d5c8a9f395696684a0", + "node_modules/@smithy/credential-provider-imds/dist-types/remoteProvider/node-http.d.ts": "dc55f5b8829638ccaf985a153933b4c32bf2d59b59ee30ddd3367851e27a586e", + "node_modules/@smithy/credential-provider-imds/dist-types/remoteProvider/retry.d.ts": "0e2e85adc7bd91e015b62e82698e07e0a605032c35c45b79aa5ea75908ee9b5b", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/config/Endpoint.d.ts": "7571a26c538d0a97d62b5cdef46fb807bb8da4ed6c8713d36f0a61558a406022", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/config/EndpointConfigOptions.d.ts": "13960827c9e40b73de5ff67d7acee1b7aec57e1ccd484cf4fc7a4b12f5304c0d", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/config/EndpointMode.d.ts": "5f614fc2e8f1868ea2c8190a4d79d1d77c5faada9c35e206297c4edc868b39a4", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/config/EndpointModeConfigOptions.d.ts": "b1279ceba045d502a546999cec5f7aa53c418e341e2b5a67917f1d48284b72e4", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/error/InstanceMetadataV1FallbackError.d.ts": "2c4d379891e146f8843feb55847d9456b6dee14b66ae92b4247d423df65e5d36", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/fromContainerMetadata.d.ts": "486947d7f5eec7b325731b4769b1c243d9603c34b7f90b11ad2b2e1de577e276", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/fromInstanceMetadata.d.ts": "49846679a5b186a3c9dca70a05f1a4f4c36529c67bed83b1f2634a79b06199ed", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/index.d.ts": "52313150cbe316d3625a682e5b2b215665b9bd70f34a90ad09e0ad6729136f1a", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/remoteProvider/ImdsCredentials.d.ts": "231db27a8847b59d0abc3e60bdf122eaf2d3d77e37fac4f3f5b5e6c03bf28cbe", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/remoteProvider/RemoteProviderInit.d.ts": "b6a09fbe8a44b4302954c10b0c4811d7ae1a1f464b4c105cfa0b4aa562141eb7", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/remoteProvider/httpRequest.d.ts": "378ab5f6fd7a4ad4def3f2318c9ee5959b936cba4353e7501687e72d6c90081b", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/remoteProvider/node-http.d.ts": "8f15ea5caf6700932a228c681f3b4c19d5fa832b4ab5e9a1a2360b6cf91efbe1", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/remoteProvider/retry.d.ts": "181cd7c7a1d4a4b32a8309ce69355bec8c76a57b864462eccf7fec57d4f38297", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/types.d.ts": "1645dd01e806c020461b6045325739d499fc1e2f6053b1e1b7bb6e36c66e2900", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/utils/getExtendedInstanceMetadataCredentials.d.ts": "615d69d0e7a38fcb076f81f9721ddf1440068dcc21a40fcb30ef660f0c471629", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/utils/getInstanceMetadataEndpoint.d.ts": "0dcee4f49dfb3af2e4dcc340cde877411b142c95e7ae5ef8cfe61a7ab933faef", + "node_modules/@smithy/credential-provider-imds/dist-types/ts3.4/utils/staticStabilityProvider.d.ts": "ccef8e3eb09456ef578a7f433e0bd80fc98fa586e62846561fe70ca0bd65d845", + "node_modules/@smithy/credential-provider-imds/dist-types/types.d.ts": "d119a258ed85bf4aba645fa5ae52e76ee139e3b698e1bfb4cc953f70c6a5d54d", + "node_modules/@smithy/credential-provider-imds/dist-types/utils/getExtendedInstanceMetadataCredentials.d.ts": "196903addaca5195928e69018da3ff8ce7d3b2b7bbc62aa6a548cda0afb4ef52", + "node_modules/@smithy/credential-provider-imds/dist-types/utils/getInstanceMetadataEndpoint.d.ts": "6990c254341fe09d3e981e04949ab5534c92586ce2e96eda60193a0d35d0d63f", + "node_modules/@smithy/credential-provider-imds/dist-types/utils/staticStabilityProvider.d.ts": "ae8e70b87c0eeb1c075cca47e37e40c49d6d31d7f5fef1ec2144f250b8b382b2", + "node_modules/@smithy/credential-provider-imds/package.json": "1e3b437bfaf7800cbcd770cc744084ab9e3b6c445196b707fde90e2f66e0403c", + "node_modules/@smithy/fetch-http-handler/CHANGELOG.md": "4c3c5206f9b138f21fb5ad63a76346cbc02399cfdd6632cf854e42b9cbdca3d9", + "node_modules/@smithy/fetch-http-handler/LICENSE": "d82ceb8cbd004a2d4834085c557d55459f528e30268d95159ee1e4030a693a6e", + "node_modules/@smithy/fetch-http-handler/README.md": "d6aa2a0877f5b6cc684c891157d2d9e520fb2fc0cbfc894c18abec4492801377", + "node_modules/@smithy/fetch-http-handler/dist-cjs/index.js": "f2c754f3bd4977312c0131a833f25a48586c0516622a082d48830d7cd997373f", + "node_modules/@smithy/fetch-http-handler/dist-es/create-request.js": "65e0f868a9f85722a3911b3f3eac8c70832d0ac62a55f8c1834366e8a946e857", + "node_modules/@smithy/fetch-http-handler/dist-es/fetch-http-handler.js": "f700494108769eff447a6bcb34f0aac060a105b2321c046eecb301960d72275f", + "node_modules/@smithy/fetch-http-handler/dist-es/index.js": "7a8ddc39b0d408c07c3eb22259db9c19fe239e57cdae26dbe5601b095200667c", + "node_modules/@smithy/fetch-http-handler/dist-es/request-timeout.js": "ba845215207e897b4af103ab173d80594b52a5e1f8cef024a78f444dad6d68f2", + "node_modules/@smithy/fetch-http-handler/dist-types/create-request.d.ts": "7ba314e9c62ba803c9b7e3b2ccb2e163d81002f7fcb6f938c548a860e6f0ec8f", + "node_modules/@smithy/fetch-http-handler/dist-types/fetch-http-handler.d.ts": "c9b0c7b823a6b89be20337d5eeb8adb31196535393a3079e9faa2717aa4a9a1e", + "node_modules/@smithy/fetch-http-handler/dist-types/index.d.ts": "2bc89958815c9491f5c45755f474e8883e3a6d3e24aea7155e4ee7d4d448ace2", + "node_modules/@smithy/fetch-http-handler/dist-types/request-timeout.d.ts": "55f9b0b629552f4becf9575424f9270b27ac28e79c71f30d675905265c39c2c7", + "node_modules/@smithy/fetch-http-handler/dist-types/ts3.4/create-request.d.ts": "fb59e7a600ba6b926ce74d7f639576e47deb454b5f76277bc8625b29b56db6d2", + "node_modules/@smithy/fetch-http-handler/dist-types/ts3.4/fetch-http-handler.d.ts": "1918078ca570c03dbbb0ed5ec11ebf6d3b4e5b08dfa9851b78dae503eddd92b7", + "node_modules/@smithy/fetch-http-handler/dist-types/ts3.4/index.d.ts": "2c9f5448252e32c67e275982381ef354a4b53499bb9e77912b8ae67b3730baaf", + "node_modules/@smithy/fetch-http-handler/dist-types/ts3.4/request-timeout.d.ts": "27cfadfb59b76ef6b0041c424c49e14efe7d1ee1167b9858acd562beef391fa6", + "node_modules/@smithy/fetch-http-handler/package.json": "3319f9f00b71ae85d49473c1f5d5a0151c89547d7724753abe8d189471decffe", + "node_modules/@smithy/node-http-handler/CHANGELOG.md": "035f61ae4ca7e92f803c6bdf64486616eb0d2ed5e22999b046e3603e4ec6d82f", + "node_modules/@smithy/node-http-handler/LICENSE": "d82ceb8cbd004a2d4834085c557d55459f528e30268d95159ee1e4030a693a6e", + "node_modules/@smithy/node-http-handler/README.md": "9fa72ddc88fdb229909ec80982b51e85fc24fe08087cba2af0a1b38aeb18e6c2", + "node_modules/@smithy/node-http-handler/dist-cjs/index.js": "0ecc976029422fbbb832c64fd9d320f7bac410a04a21b9c3bd217c7877135e13", + "node_modules/@smithy/node-http-handler/dist-es/build-abort-error.js": "e6bcb563d4470e78fbaad1e1084bf8ab67806b07a43e396e2fe8bb667c0710cd", + "node_modules/@smithy/node-http-handler/dist-es/constants.js": "21c653f49f4dc5ddcacdd5b5e4595f4d39fe08f97eb7a2e038bea6fb3b9cc7bc", + "node_modules/@smithy/node-http-handler/dist-es/get-transformed-headers.js": "e2a40608d850c02084a739b75d873a0d63dfa78dc739de19ac31e9cf4489d575", + "node_modules/@smithy/node-http-handler/dist-es/http2/ClientHttp2SessionRef.js": "cf4fb145a584da942f56207366fa76b8c5d7f8c5e863c2f608bf002fb0908d03", + "node_modules/@smithy/node-http-handler/dist-es/index.js": "aaeaaeb75798cffb29c57591eec43ba13791d83f75e958b679db39a7c1498823", + "node_modules/@smithy/node-http-handler/dist-es/node-http-handler.js": "27855bbf9b16c456b68062c78ec51b8720729049c12ba78fc2cfe5e36eea920b", + "node_modules/@smithy/node-http-handler/dist-es/node-http2-connection-manager.js": "6e8e87085eae6b3fafe006f9a2fa88bff069a96f7d441ff0f111cf9735aae696", + "node_modules/@smithy/node-http-handler/dist-es/node-http2-connection-pool.js": "00e4162431d064a9cef5187d4d2eb40754a53a1af9a7193241962d1eb35623eb", + "node_modules/@smithy/node-http-handler/dist-es/node-http2-handler.js": "f668eeab71e6375b313ff2b3bfb3be96120f213faa0a7f452d7cfecde1840927", + "node_modules/@smithy/node-http-handler/dist-es/node-http2.js": "229c419101e0d5b597432d701f3d19d0b5836da9a91617fc02d6f291aa1d8a4c", + "node_modules/@smithy/node-http-handler/dist-es/node-https.js": "ddd232225649e0c62a1ace9fdc1705c596e178927100e1026ab30c90c314e56a", + "node_modules/@smithy/node-http-handler/dist-es/set-connection-timeout.js": "395995cf7c936369fdec4ebc6135e36e5de2ba9d30c7f8c9958c5b8cb2a589e4", + "node_modules/@smithy/node-http-handler/dist-es/set-request-timeout.js": "07c8473f0aae055f2b386528e0a9fe24b1896df8bb088c159e261aa7e04d370d", + "node_modules/@smithy/node-http-handler/dist-es/set-socket-keep-alive.js": "f1930ef38d52acd7ab7784395c120aeb3f0760a8bca9b8cc3fa5443082a386c3", + "node_modules/@smithy/node-http-handler/dist-es/set-socket-timeout.js": "2e606dd5564849ea73de8f88b75ae5a1d0427b59162a40a7afb91df7fb1d5cfb", + "node_modules/@smithy/node-http-handler/dist-es/timing.js": "eb957a865832ea8f38f769b9068f8d879e3ae29319119589ef7a238a58898a6a", + "node_modules/@smithy/node-http-handler/dist-es/write-request-body.js": "241fb8c7fd6407a458096e7225a529e1e33371bc7898646e5d9010789e0ff4a0", + "node_modules/@smithy/node-http-handler/dist-types/build-abort-error.d.ts": "ff5d7ed81ccff17efec6b214754230cf97d4f85c4ea768122c2e6e78c2d3cb51", + "node_modules/@smithy/node-http-handler/dist-types/constants.d.ts": "ed234d5cb004c40482359f68eb9dd8b8c7d8392c3141844ea5ff65440916899c", + "node_modules/@smithy/node-http-handler/dist-types/get-transformed-headers.d.ts": "58ff6ba2fb9f59a15d28dac0a3e9f17ca1d6d199694685061e0417c47aceebb0", + "node_modules/@smithy/node-http-handler/dist-types/http2/ClientHttp2SessionRef.d.ts": "df2ed6f1d576985ae1f5c5eb057084a60f804cc786d1032f26514995878c7573", + "node_modules/@smithy/node-http-handler/dist-types/index.d.ts": "57bc0d24f494bf17b616e8b3256cdaa4ac8753fbbe0086a773cc745c45ff3d6a", + "node_modules/@smithy/node-http-handler/dist-types/node-http-handler.d.ts": "696273055785fe2df209e6dc8519175e9d9563d44439d5b741600a9a051e581d", + "node_modules/@smithy/node-http-handler/dist-types/node-http2-connection-manager.d.ts": "11706b33ed7ee3176d67f55db59783532d40f5463466bd13180577f63b9b8999", + "node_modules/@smithy/node-http-handler/dist-types/node-http2-connection-pool.d.ts": "d8e14b42c9a3e896ad05fb94df3c76beabdd115c82d77c5b1ffbdf17f789cdf2", + "node_modules/@smithy/node-http-handler/dist-types/node-http2-handler.d.ts": "c0152bedc5b14a32a1b3a551035ef6753a71247dabfbf36011dff38fc0d4df12", + "node_modules/@smithy/node-http-handler/dist-types/node-http2.d.ts": "229c419101e0d5b597432d701f3d19d0b5836da9a91617fc02d6f291aa1d8a4c", + "node_modules/@smithy/node-http-handler/dist-types/node-https.d.ts": "ddd232225649e0c62a1ace9fdc1705c596e178927100e1026ab30c90c314e56a", + "node_modules/@smithy/node-http-handler/dist-types/set-connection-timeout.d.ts": "935537ddcc9138ff6ea99ebf8a8ad0fe83442dc95cfb7e48c8cb73d8192669bc", + "node_modules/@smithy/node-http-handler/dist-types/set-request-timeout.d.ts": "b4e1196ab4e8eb041a1b063f98b9e1c7a7cf60291616a20f4a604b4ef57cad0f", + "node_modules/@smithy/node-http-handler/dist-types/set-socket-keep-alive.d.ts": "806c474d530a6e15968c9f5e53d10acea0847aac076885ae50b7ad901900e61c", + "node_modules/@smithy/node-http-handler/dist-types/set-socket-timeout.d.ts": "e455387835aaf12d0fb7473148b9243e3635afcf336feba29d83acd3c225fe4e", + "node_modules/@smithy/node-http-handler/dist-types/timing.d.ts": "182f86f1b02a315d7cdf073db381234e1f2c533a3a04fdfd46c7129a96e2cc4b", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/build-abort-error.d.ts": "ef5c4f64771819a997d52d15e6898c3469a008dde93cdcd03f77239351587f81", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/constants.d.ts": "268541f5c8949a2be828dc2cd8535e25dbb0853f8c27e5cc8cda4dea33d4b3b3", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/get-transformed-headers.d.ts": "36f88cbefcd3bb766bc647a06d1528e5286475200fcee14cdb5e743d2de5478f", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/http2/ClientHttp2SessionRef.d.ts": "0985844912af167918915dd976872f14fc846bf963afbcf04a41cd902b64dc79", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/index.d.ts": "72e04b4d013c6c12546d669c1222850706d1365f4adb09d199eacdf551e4521f", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http-handler.d.ts": "9f4b2e1eaeb57b986486f230d5d30846ec1dc541967df10c6069650a1dc083b8", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http2-connection-manager.d.ts": "20b9a9a2a65cc8ce44dfaaaf13b3b7156ecb1d689cf6cbd467669f7e760620ef", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http2-connection-pool.d.ts": "afab59992b6c985071544f9c90e87590c59bdcb08dbe8f2c573d9fbc705422d8", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http2-handler.d.ts": "c1668cfd22d760645b86b58d851322d17f992270f8e5f99a67fd6fda59eb1e9a", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http2.d.ts": "6559e52c13060f78010d1301df689c42bb9f9e88e08a11d6b705e087887e36ce", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-https.d.ts": "76a089e8a8a42e0178af3c10fc46d767f4bb150393b9f476002879bd1ff49d86", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/set-connection-timeout.d.ts": "e166b97e9af6b9d0b507108eb617d4c5805e025f81177024f578f8818acc5ae8", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/set-request-timeout.d.ts": "7af7e6d895d0e9024718e160710abfd0396b28ded445a2f94e38dbce94945188", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/set-socket-keep-alive.d.ts": "3505543eae3343543ba6e4679efbb7bd6a83a52e45bcd99ed9ab44d8a228835f", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/set-socket-timeout.d.ts": "e92d2f825e428be5cc423543c8281d318970f29259173294fd2c54dbca81ac36", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/timing.d.ts": "97997d812650e385292fba388ccd9b6f5a1d342ba7ef45cfcbcd6edaaba292f6", + "node_modules/@smithy/node-http-handler/dist-types/ts3.4/write-request-body.d.ts": "fdc3661926993b74753c5a943c15da52e76a7837f6259d9b91a1daf7f71d1ece", + "node_modules/@smithy/node-http-handler/dist-types/write-request-body.d.ts": "3289ceb5969c0c8b6899157551c5feb1e70ef59f87dadb046eb8884245d8b198", + "node_modules/@smithy/node-http-handler/package.json": "e685e00a1e28371390ffab5711e5300b81743b7a7d5f5bf1a994e40197467221", + "node_modules/@smithy/signature-v4/CHANGELOG.md": "b88b97bb650f7609df2693ff95deccc6fbaeb97fec2b07781106b61a34d2cb71", + "node_modules/@smithy/signature-v4/LICENSE": "d82ceb8cbd004a2d4834085c557d55459f528e30268d95159ee1e4030a693a6e", + "node_modules/@smithy/signature-v4/README.md": "4652a43cf8e0af70f6b9144b1608e5a1f9d2c6eefe032c0f56e0b698d602fbd8", + "node_modules/@smithy/signature-v4/dist-cjs/index.js": "d547b51fb2b9e9494e0bc7286425320d7909eaab4007d32a87796a2160cd79a8", + "node_modules/@smithy/signature-v4/dist-es/HeaderFormatter.js": "666b1b32567c88cdd92cd9ef423d261fb320394b7052a28f18af0dc801caa62c", + "node_modules/@smithy/signature-v4/dist-es/SignatureV4.js": "634af3e93dc5f4b2bcac3189ae6a95bf1b16f5d94702fdd3811cafd6848b34e9", + "node_modules/@smithy/signature-v4/dist-es/SignatureV4Base.js": "e8199ea40ccf537e04e2cd0272e0b9d034e1bf706024ae51754575e215ec484e", + "node_modules/@smithy/signature-v4/dist-es/constants.js": "eb654133bea3a2bd0f975c69a9f0cde32af2ba2441f64e47f4378e8e6904aa99", + "node_modules/@smithy/signature-v4/dist-es/credentialDerivation.js": "2f1bb7c1784f0010a1070231326054523ac0cd0ffaf439e2c14913b6abb53167", + "node_modules/@smithy/signature-v4/dist-es/getCanonicalHeaders.js": "7e90f80ce64d45a4fa64539fdffec4215b444cbb002d33ea76df8740030af909", + "node_modules/@smithy/signature-v4/dist-es/getCanonicalQuery.js": "bcd82f0c8a78b0e880deaf0e9e5f94fe1b1bf7916dada4ad751991f7b523e788", + "node_modules/@smithy/signature-v4/dist-es/getPayloadHash.js": "8c87eecf7bc70029afdde80bea537dbbf2b5dbb6350bb5a0590c5d519a092241", + "node_modules/@smithy/signature-v4/dist-es/headerUtil.js": "7687b15db5477f2c1930d10042490e16bb604d77e99d1d8463dd605fe6975395", + "node_modules/@smithy/signature-v4/dist-es/index.js": "149bc8a4f00826217b6f72713cc504e67d94d5f9131928bb4eb79e89acbb5725", + "node_modules/@smithy/signature-v4/dist-es/moveHeadersToQuery.js": "1ba7a0adb7f4d141d78783fd9754a28fa892cc6bd608adbaace2cee26fccf426", + "node_modules/@smithy/signature-v4/dist-es/prepareRequest.js": "d304356b243f9556335a20c0eacfd3abfe8720e6442807a31a04b46f516db90c", + "node_modules/@smithy/signature-v4/dist-es/signature-v4a-container.js": "a617aa0b48b9979a2a860b30fa6ca5e7277c42f88bcc70d6de1cd1613c6b7ad2", + "node_modules/@smithy/signature-v4/dist-es/utilDate.js": "c31c249fe48a99f14e87e0e7a68d12254c925f7eb61713c539aebb847b67751c", + "node_modules/@smithy/signature-v4/dist-types/HeaderFormatter.d.ts": "418f04da84861ed43322499ba5bb77aeb64fdacc3ab34d4275583c527a099334", + "node_modules/@smithy/signature-v4/dist-types/SignatureV4.d.ts": "d14ca198f6cb072db02e0a8744c527b1d3723a03f2b3019cc7be5f226f9118de", + "node_modules/@smithy/signature-v4/dist-types/SignatureV4Base.d.ts": "5776c61de0f11da1c3cf8aafc3df524e8445201c96a7c5065a36dc74c2dc0ef6", + "node_modules/@smithy/signature-v4/dist-types/constants.d.ts": "7f0f90d0ffdd54875c464b940afaa0f711396f65392f20e9ffafc0af12ccbf14", + "node_modules/@smithy/signature-v4/dist-types/credentialDerivation.d.ts": "77ced89806322a43991a88a9bd267d6dc9e03fd207a65e879804fa760292a03b", + "node_modules/@smithy/signature-v4/dist-types/getCanonicalHeaders.d.ts": "483255952a9b6240575a67f7beb4768bd850999a32d44d2c6d0ae6dfcdafe35c", + "node_modules/@smithy/signature-v4/dist-types/getCanonicalQuery.d.ts": "a1957cc53ce2402d4dc5c51b7ccc76b30581ab67bea12a030a76300be67c51d8", + "node_modules/@smithy/signature-v4/dist-types/getPayloadHash.d.ts": "8149e534c91fc2bcb3bf59f7c1fab7584382abfc5348055e7f84d2552c3de987", + "node_modules/@smithy/signature-v4/dist-types/headerUtil.d.ts": "c8ff3a75cd1c990cbe56080b1d254695c989136c9521cb1252c739788fe55c83", + "node_modules/@smithy/signature-v4/dist-types/index.d.ts": "d5a8da319ac61f44be464a88e76866dc57ab24fa0ba4e9a1309ce965b61a83d6", + "node_modules/@smithy/signature-v4/dist-types/moveHeadersToQuery.d.ts": "c280ec77789efcf60ea1f6fd7159774422f588104dae9dfa438c9c921f5ab168", + "node_modules/@smithy/signature-v4/dist-types/prepareRequest.d.ts": "2826b3526af4f0e2c8f303e7a9a9a6bb8632e4a96fece2c787f2df286a696cea", + "node_modules/@smithy/signature-v4/dist-types/signature-v4a-container.d.ts": "832ccea70196d4235150be9baef887db9a6bb183722bfcd358931e2bc603e619", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/HeaderFormatter.d.ts": "918f5ecfd25e47303a926ad626792fa863bce5b9923329f797f4300abfe26bf2", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/SignatureV4.d.ts": "83ec5b983ad6ff73dcd1d2dfdac2f4949372e8c9cf5399efc14cee59137bc485", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/SignatureV4Base.d.ts": "9718168dbed54235ad22d6379548e8a974ce42bb40012096c37e6fcb3bf8822d", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/constants.d.ts": "b4bb5872490ff3bd6abfdbb360295b9d25db39a238a355e5e8c3d3aa124a5482", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/credentialDerivation.d.ts": "18eb3adcc47387d1aa22db3ba624ace4bd028ea57155964c4e05334aa343b01d", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/getCanonicalHeaders.d.ts": "71250ae0e3a784471b077e86fc10f5b0edf84a6384daab0e03cacb105d51c15b", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/getCanonicalQuery.d.ts": "35cad1d7c9ffd4692ff789e730de108cfc3f2b7ef7ad310e4988b034875356d6", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/getPayloadHash.d.ts": "d38b21ee750855821048ebdc5c9a22d739d0e4ac2d75e1e040f855ba83cbc889", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/headerUtil.d.ts": "90bbfb5fb4edb8ae1ddc35b70d2ea83376bb9bbaeb88c6355232e4b6a62cb674", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/index.d.ts": "129b7fa92c8e3364e0540a9b4ba0c062415b8898000c7f9dee97d7d0fda81ded", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/moveHeadersToQuery.d.ts": "85532116ee382a8dbe776677f34807300d5114732e8bea0f68455692801abc30", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/prepareRequest.d.ts": "c8dc9316519488e5e850a2f894ebb61c70c3d95d7b9f0a25e1727dc480b699a8", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/signature-v4a-container.d.ts": "fd93f45fa6c1a27b3cd24fa60ea18a7e44f064dc4cb3dbbb51d4f8ea6447036f", + "node_modules/@smithy/signature-v4/dist-types/ts3.4/utilDate.d.ts": "46667b9719d24a582a106d29769ebb1476a724b70c80ca7e10c5521389360896", + "node_modules/@smithy/signature-v4/dist-types/utilDate.d.ts": "dfb48fd47dcef59666aae6d556bed56508de570ec04a4b8db95cac0af7aec853", + "node_modules/@smithy/signature-v4/package.json": "311248fd3c1829d286f59813f53bb5d2805a57bc48d883849d78b39545ebe616", + "node_modules/@smithy/types/CHANGELOG.md": "591b7bb745397de0655f7f2e0d8280edec4cabaaa1486e6c07dc0a7de3e754c5", + "node_modules/@smithy/types/LICENSE": "07364a5f7d0177a59a05a0002187431f8f899320983c0dc049d72df015e8064c", + "node_modules/@smithy/types/README.md": "0955960a3d958a071673049fdb0df74f9b3c98d67d26dcb5bbe4eac4df3c7707", + "node_modules/@smithy/types/dist-cjs/index.js": "9d6dc721faba0c34fce3d8e1fa464f10446cb998529a1a774d9538a071e8c5ab", + "node_modules/@smithy/types/dist-es/abort-handler.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/abort.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/auth/HttpApiKeyAuth.js": "fd3030fff4b716e89c0943ddb3007fdd3bd8d8cc4cb23eb64423eb579af554a2", + "node_modules/@smithy/types/dist-es/auth/HttpAuthScheme.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/auth/HttpAuthSchemeProvider.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/auth/HttpSigner.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/auth/IdentityProviderConfig.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/auth/auth.js": "234f68e7a3770a5be04750f138ca25819b4bb199edb9edb5125146bb058af5da", + "node_modules/@smithy/types/dist-es/auth/index.js": "c4dea46632c998e8e833cd0087fb7722770aa97606508fb39c6b52862ec77ab3", + "node_modules/@smithy/types/dist-es/blob/blob-payload-input-types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/checksum.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/client.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/command.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/connection/config.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/connection/index.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/connection/manager.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/connection/pool.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/crypto.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/downlevel-ts3.4/transform/type-transform.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/encode.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/endpoint.js": "778576bbfca6ede0a052033214fee57ab5d19719da41ffc31f8c3513861643ea", + "node_modules/@smithy/types/dist-es/endpoints/EndpointParameterInstructions.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/endpoints/EndpointRuleObject.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/endpoints/ErrorRuleObject.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/endpoints/RuleSetObject.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/endpoints/TreeRuleObject.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/endpoints/index.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/endpoints/shared.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/eventStream.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/extensions/checksum.js": "7011618d2d354e319b592d6638d8f74f8cf83ade9a278716c9139a17184a6875", + "node_modules/@smithy/types/dist-es/extensions/defaultClientConfiguration.js": "cf9d8360db667ec16096bab7637e2df73417b2cadc8e610e21d3e6ba05b64d88", + "node_modules/@smithy/types/dist-es/extensions/defaultExtensionConfiguration.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/extensions/index.js": "4272d76ac9f923e8459b921c165fdcd389af49a254873e1301bdf2e7b737d7c0", + "node_modules/@smithy/types/dist-es/extensions/retry.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/externals-check/browser-externals-check.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/feature-ids.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/http.js": "6577a6264665c24a5c3ba5dfd429dcd1e9e8effafd01a3a24403bf46c903ebda", + "node_modules/@smithy/types/dist-es/http/httpHandlerInitialization.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/identity/apiKeyIdentity.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/identity/awsCredentialIdentity.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/identity/identity.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/identity/index.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/identity/tokenIdentity.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/index.js": "aaf24cff96c78f4914448cd789f397984b630e2447d0f3ab40c16bc20fb9b791", + "node_modules/@smithy/types/dist-es/logger.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/metrics.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/middleware.js": "bfda1be10e13adffd26eed0fcd011fb30cdfdd1eddc1f033bc141e40a2f735a9", + "node_modules/@smithy/types/dist-es/pagination.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/profile.js": "dc07bada1d339c032d2ae663f9a6dbf717af0d7e9aee8f12f782613ac4dfc6be", + "node_modules/@smithy/types/dist-es/response.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/retry.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/schema/schema-deprecated.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/schema/schema.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/schema/sentinels.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/schema/static-schemas.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/schema/traits.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/serde.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/shapes.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/signature.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/stream.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/streaming-payload/streaming-blob-common-types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/streaming-payload/streaming-blob-payload-input-types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/streaming-payload/streaming-blob-payload-output-types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/transfer.js": "6222aec3a84853cd23d81dce27cc3c40b458ca8cc35c6d0988b38bcf037c729f", + "node_modules/@smithy/types/dist-es/transform/client-method-transforms.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/transform/client-payload-blob-type-narrow.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/transform/exact.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/transform/mutable.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/transform/no-undefined.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/transform/type-transform.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/uri.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/util.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-es/waiter.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/@smithy/types/dist-types/abort-handler.d.ts": "b40885a4e39fb67eb251fb009bf990f3571ccf7279dccad26c2261b4e5c8ebcd", + "node_modules/@smithy/types/dist-types/abort.d.ts": "eb5ae2dfbd635f360032f4150f1119b72f174589e6cab3049f27bb5274c1b912", + "node_modules/@smithy/types/dist-types/auth/HttpApiKeyAuth.d.ts": "1c483cc60a58a0d4c9a068bdaa8d95933263e6017fbea33c9f99790cf870f0a8", + "node_modules/@smithy/types/dist-types/auth/HttpAuthScheme.d.ts": "247d6e003639b4106281694e58aa359613b4a102b02906c277e650269eaecede", + "node_modules/@smithy/types/dist-types/auth/HttpAuthSchemeProvider.d.ts": "fe37c7dc4acc6be457da7c271485fcd531f619d1e0bfb7df6a47d00fca76f19c", + "node_modules/@smithy/types/dist-types/auth/HttpSigner.d.ts": "3bdc578841f58bfd1087e14f81394ece5efd56b953362ef100bdd5bd179cd625", + "node_modules/@smithy/types/dist-types/auth/IdentityProviderConfig.d.ts": "2bc15addade46dc6480df2817c6761d84794c67819b81e9880ab5ce82afb1289", + "node_modules/@smithy/types/dist-types/auth/auth.d.ts": "1d788363783d8bc01d046e821aa2f674cde0c20af2999d2bbc034015368fbff4", + "node_modules/@smithy/types/dist-types/auth/index.d.ts": "0167df5f6e4939f82ce91fc941c843cfe0c77faaf565be50c34e5795b99f0a5f", + "node_modules/@smithy/types/dist-types/blob/blob-payload-input-types.d.ts": "bb977b21c99873e5b489c0fad5ee03b6010fd09f55b88edb8a207e60e29f8b4c", + "node_modules/@smithy/types/dist-types/checksum.d.ts": "8d8dc0f54a9ae72bdf67b3574144d639fd1951e08aa6424415022b3fa05544e3", + "node_modules/@smithy/types/dist-types/client.d.ts": "b310f4737336f11507a0ab14a3a936858334230974dda8bdbbcecb6e512ceb24", + "node_modules/@smithy/types/dist-types/command.d.ts": "7627a0fc528ac040ea1fb86a5cb3e66ba4de3c55947ee6a1aad89b46c2038efd", + "node_modules/@smithy/types/dist-types/connection/config.d.ts": "06921a4f3da17bed5d4bc6316658ce0ea7532658a5fc575a24aa07034c1b0d3d", + "node_modules/@smithy/types/dist-types/connection/index.d.ts": "5c16f17365c37b6376880cd0c441aa5b72eb3f3b2d2a9b293f0750c0188904be", + "node_modules/@smithy/types/dist-types/connection/manager.d.ts": "34c17533b08bd962570d7bdb838fcaf5bcf7b913c903bc9241b0696a635b8115", + "node_modules/@smithy/types/dist-types/connection/pool.d.ts": "1d567a058fe33c75604d2f973f5f10010131ab2b46cf5dddd2f7f5ee64928f07", + "node_modules/@smithy/types/dist-types/crypto.d.ts": "ce31b0fa39f2fd009c02acd675c575733839055905c2beca4a3915e938347f4b", + "node_modules/@smithy/types/dist-types/downlevel-ts3.4/transform/type-transform.d.ts": "497d6e449e9165bc5dc9a5aeab5787a3faf9a5e8b58b9176e45e72f2ddccfb9f", + "node_modules/@smithy/types/dist-types/encode.d.ts": "147734cfd0973548fb6ef75d1e7d2c0b56bb59aad72b280784e811d914dc47d6", + "node_modules/@smithy/types/dist-types/endpoint.d.ts": "c475aa6e8f0a20c76b5684658e0adaf7e1ba275a088ee6a5641e1f7fe9130b8a", + "node_modules/@smithy/types/dist-types/endpoints/EndpointParameterInstructions.d.ts": "4a34de405e3017bf9e153850386aacdf6d26bbcd623073d13ab3c42c2ae7314c", + "node_modules/@smithy/types/dist-types/endpoints/EndpointRuleObject.d.ts": "e399d54c1b272a400ed446ca35d5e43d6b820723c2e5727b188ebea261e7cc2e", + "node_modules/@smithy/types/dist-types/endpoints/ErrorRuleObject.d.ts": "123568587c36c9f2a75091d8cdf8f287193855ba5aa10797b4fc320c80920b7f", + "node_modules/@smithy/types/dist-types/endpoints/RuleSetObject.d.ts": "973551068756351486afe706b240eb4dc83678ab2d829a1c6b1a19871394fd5f", + "node_modules/@smithy/types/dist-types/endpoints/TreeRuleObject.d.ts": "6deffa531bdb8817b363505e88d957653d0c454f42c69e31588d00102cd1a076", + "node_modules/@smithy/types/dist-types/endpoints/index.d.ts": "49f5616d0ae02fa8860f83732bb39fd9a33b5933871d25c79c452f4b294d3cdb", + "node_modules/@smithy/types/dist-types/endpoints/shared.d.ts": "d2594d95d465026ebbee361f4819dc7b3146f4a8b42091ffb5dd90f9ceb345ab", + "node_modules/@smithy/types/dist-types/eventStream.d.ts": "63b3c76d46314470f92f89f8cfb6e016a055bfdf505b73f0950512b176fc776f", + "node_modules/@smithy/types/dist-types/extensions/checksum.d.ts": "9b7b0209a8841f5ffa60ccdfae26f7dc70ea4e7e446a603ef4732e84f1bb1b4f", + "node_modules/@smithy/types/dist-types/extensions/defaultClientConfiguration.d.ts": "bfc15f3582717affb1ad4cd6a2992f7cab76c313730b4367f3312a9348c294a0", + "node_modules/@smithy/types/dist-types/extensions/defaultExtensionConfiguration.d.ts": "3ea1b33c13157aa1750a7fb70ceb35730b92bf0224636b5f17f8ce0542fa5222", + "node_modules/@smithy/types/dist-types/extensions/index.d.ts": "501abe295927001f5c086e650b17237a7be93e6b658f93c306e5031da33e2d23", + "node_modules/@smithy/types/dist-types/extensions/retry.d.ts": "20cf19c8028a7b958e9c2000281d0f4c4cd12502fef7d63b088d44647cdd607b", + "node_modules/@smithy/types/dist-types/externals-check/browser-externals-check.d.ts": "7245e8f6453ff36dfdab1f448bfecafb4c0eb7e627a8552135eac69272888e02", + "node_modules/@smithy/types/dist-types/feature-ids.d.ts": "a42db31dacd0fa00d7b13608396ca4c9a5494ae794ad142e9fb4aa6597e5ca54", + "node_modules/@smithy/types/dist-types/http.d.ts": "a2a91d3575d79e42bd48c24377be9dd4e3eca0ab66ce0f49933ebdb06bcfd0c7", + "node_modules/@smithy/types/dist-types/http/httpHandlerInitialization.d.ts": "26f9efe374332596497ecb76dfc65ed25df7d4db0a08319fdb1cd6ace4a5c82f", + "node_modules/@smithy/types/dist-types/identity/apiKeyIdentity.d.ts": "a890cccdc380629c6cd9e9d92fff4ca69b9adddde84cc503296ada99429b5a3b", + "node_modules/@smithy/types/dist-types/identity/awsCredentialIdentity.d.ts": "168b6da36cf7b832173d7832e017bc6c6c7b4023bf6b2de293efb991b96bca44", + "node_modules/@smithy/types/dist-types/identity/identity.d.ts": "07863eea4f350458f803714350e43947f7f73d1d67a9ddf747017065d36b073a", + "node_modules/@smithy/types/dist-types/identity/index.d.ts": "12c1fbae21a0016a45615a3f5004866582a7642f0a48ef94d0e79c9ac3fc1871", + "node_modules/@smithy/types/dist-types/identity/tokenIdentity.d.ts": "05b39d7219bb2f55f865bca39a3772e1c0a396ea562967929d6b666560c85617", + "node_modules/@smithy/types/dist-types/index.d.ts": "e23990a57e4c6e7ba71d926b1a7c429ffc4da59254a33e4a5086bb784ae94b91", + "node_modules/@smithy/types/dist-types/logger.d.ts": "c7381606516c8b5725dd3df850263d6644f2df8d7f5e1c5956893b9afbc2f8bf", + "node_modules/@smithy/types/dist-types/metrics.d.ts": "87c3650ff7fca76b8eadb07ffeeff2fe4d903ae59b43895030ae70ce9bf4feef", + "node_modules/@smithy/types/dist-types/middleware.d.ts": "7879947214c05fa3789f364b1b56a813b9864ec4fb410b97d453ffc55fa6ccbc", + "node_modules/@smithy/types/dist-types/pagination.d.ts": "75e534cd013e641cf6f492167ed3e2a3569a4de54ca900d262f8d4fe7f224270", + "node_modules/@smithy/types/dist-types/profile.d.ts": "5ae003688265a1547bbcb344bf0e26cb994149ac2c032756718e9039302dfac8", + "node_modules/@smithy/types/dist-types/response.d.ts": "396c2c14fa408707235d761a965bd84ce3d4fc3117c3b9f1404d6987d98a30d6", + "node_modules/@smithy/types/dist-types/retry.d.ts": "87a91bc6063720dea9190f1926283368ab0ed67f4b6138ec76b02794a38fe574", + "node_modules/@smithy/types/dist-types/schema/schema-deprecated.d.ts": "eb6360635bc14b96a243bd5134e471f3ad26b0ecaf52d9d28621e443edb56e5c", + "node_modules/@smithy/types/dist-types/schema/schema.d.ts": "3dea56c1745af2c31af0c84ecc6082044dc14cfa4d7366251e5bf91693eecd8b", + "node_modules/@smithy/types/dist-types/schema/sentinels.d.ts": "7f9ea33f204fba8bfa37f84bec4255187439f1ae2b6409325cf965212282a84d", + "node_modules/@smithy/types/dist-types/schema/static-schemas.d.ts": "6921769648e4b83bb10e8fcf7011ea2d8f7de5d056daacf661648935a407376e", + "node_modules/@smithy/types/dist-types/schema/traits.d.ts": "dd21167f276d648aa8a6d0aacd796e205d822406a51420b7d7f5aa18a6d9d6d9", + "node_modules/@smithy/types/dist-types/serde.d.ts": "8be4e0787c5587f36669f9ee1da84e02e8419ddfedfbd4386d99307308cc70e5", + "node_modules/@smithy/types/dist-types/shapes.d.ts": "6e2b55943538468a63a7a627bd4f18eea7a917b9fbfea34cbdfed8d028137eda", + "node_modules/@smithy/types/dist-types/signature.d.ts": "7537944ecb74831ad1daa2280676c6399bdacb604f13ff9dbbab7da8fa8818e2", + "node_modules/@smithy/types/dist-types/stream.d.ts": "13975776e2d018a450ab5ef3dfe51bda565fac4842e119e7f8df57c46c1f4362", + "node_modules/@smithy/types/dist-types/streaming-payload/streaming-blob-common-types.d.ts": "3975b59c4131f8280c008a1df87d1ec209b25e2f5415be0ba2221761d4411fe0", + "node_modules/@smithy/types/dist-types/streaming-payload/streaming-blob-payload-input-types.d.ts": "1fa5ddc841b9a1b4d0240f28f676e07fce6ab79874903d115db4773ddabf3685", + "node_modules/@smithy/types/dist-types/streaming-payload/streaming-blob-payload-output-types.d.ts": "4577aa89575b73d4d335e17d9ca0b3c1455d00fe626dad648f90a9e4f0dc1d70", + "node_modules/@smithy/types/dist-types/transfer.d.ts": "eda0c3e4b54c8ab9cd128990455522df296de5986f4b2502a4f1fc2925cec8c6", + "node_modules/@smithy/types/dist-types/transform/client-method-transforms.d.ts": "a71bd1a65930f1a57f82dd3b674e5ea0d428d3dcf841d4da384f081418915f3b", + "node_modules/@smithy/types/dist-types/transform/client-payload-blob-type-narrow.d.ts": "9499e47767506b4774f2e58778e4cf54145a5b82d7a11dac3e58bb499daf028a", + "node_modules/@smithy/types/dist-types/transform/exact.d.ts": "2bb39eac4173f3db5dfb31fffdd4a97a75ed3fcffe184c93f03fe62fc5af5553", + "node_modules/@smithy/types/dist-types/transform/mutable.d.ts": "8175f51ec284200f7bd403cb353d578e49a719e80416c18e9a12ebf2c4021b2b", + "node_modules/@smithy/types/dist-types/transform/no-undefined.d.ts": "9871b1807440d67682ffa5381aaf8bcf79614d699c77f5d258ae221a233c14cc", + "node_modules/@smithy/types/dist-types/transform/type-transform.d.ts": "45cde71dc6212b64a86d01963c0cd260510526e7331466d9d182aaefd640e6be", + "node_modules/@smithy/types/dist-types/ts3.4/abort-handler.d.ts": "693d592ed1c8dd7e409bd2a18556bb131074caf4c7d2feac70fe4c8c76d47b0b", + "node_modules/@smithy/types/dist-types/ts3.4/abort.d.ts": "0206d6f3a247d4082deefc57462caa49086226eea296040aac85a67026a12ed7", + "node_modules/@smithy/types/dist-types/ts3.4/auth/HttpApiKeyAuth.d.ts": "ca16cdd8b65f6bcff4449e97c16a6ffecbc81ed37bd4a1c4cb36a63b3276c2ff", + "node_modules/@smithy/types/dist-types/ts3.4/auth/HttpAuthScheme.d.ts": "2ee5f92bf568dd3cc0353d797f47f08d2617bcc2561277e7f1928a38020faa92", + "node_modules/@smithy/types/dist-types/ts3.4/auth/HttpAuthSchemeProvider.d.ts": "cb9a846108b3c0fdd685758854b72ef4381ae215b1773e3ef305ddd880ef5dcd", + "node_modules/@smithy/types/dist-types/ts3.4/auth/HttpSigner.d.ts": "9baff384321bab913ae0d25ad93003d9809ddf33426e574a1bd68731280e3b56", + "node_modules/@smithy/types/dist-types/ts3.4/auth/IdentityProviderConfig.d.ts": "c79fee3c3dd8316e1f2cd404a7b87df122e622f4c841bd4dee35e71790635a85", + "node_modules/@smithy/types/dist-types/ts3.4/auth/auth.d.ts": "81eb36daf070c0cbe06d26a1a9427e8d426985bfb385c23c36acbcdfb9be8f14", + "node_modules/@smithy/types/dist-types/ts3.4/auth/index.d.ts": "24af1f584043b34e90f4820c4cf6710a10fffdde5f9ec48c130fe9abe49d1406", + "node_modules/@smithy/types/dist-types/ts3.4/blob/blob-payload-input-types.d.ts": "37db09dfab74aaa8466e89aafea8d15aba419be5a0b61e08914e54e4b457309e", + "node_modules/@smithy/types/dist-types/ts3.4/checksum.d.ts": "ff7b96a209f3c9b124b171b2260ff2d71e077d3e315964b75229d3d95b53aa04", + "node_modules/@smithy/types/dist-types/ts3.4/client.d.ts": "9cbffc1e51aefbc0e7b8fc912d2dd04104c36f70ad3b29551bbabb0d7783f377", + "node_modules/@smithy/types/dist-types/ts3.4/command.d.ts": "8ec3d5a7bc328d9f87646a5b96264dcebedafed6c861dc150a5f90163096d2fe", + "node_modules/@smithy/types/dist-types/ts3.4/connection/config.d.ts": "9b8c98a64dbbd12748a3f8d524e10fe266c1505589bddb86c3b1d58bd45c2209", + "node_modules/@smithy/types/dist-types/ts3.4/connection/index.d.ts": "2426221de20d87d273f6fb46c63b4773b7d8480246296a3cd73de036c0d1b8a6", + "node_modules/@smithy/types/dist-types/ts3.4/connection/manager.d.ts": "45afffbcb7dd370a9bab46ec7cc5d5ae757b68ff46e8ab0b0593f7849cc17b1c", + "node_modules/@smithy/types/dist-types/ts3.4/connection/pool.d.ts": "5e06b9d096c5b7540ba7333d312bac783a6e95e708b9a446d0c1721c8ee55147", + "node_modules/@smithy/types/dist-types/ts3.4/crypto.d.ts": "7f8af728365ab1aabbd1c89bfde89f6be276a6bf674cab31d52a1fb8ac5aab6c", + "node_modules/@smithy/types/dist-types/ts3.4/downlevel-ts3.4/transform/type-transform.d.ts": "7f8ffb83f0d97dc0a921bb02840435a4e55601d14d79e6407581a71713548434", + "node_modules/@smithy/types/dist-types/ts3.4/encode.d.ts": "989521d3db20a618b2e97e8411daf5486af023b012e63dc3a0c20230cb71af6a", + "node_modules/@smithy/types/dist-types/ts3.4/endpoint.d.ts": "26f4e8c857a565894f974c631ea4a6dcb8521c628971822a41882d0c8c2fc8a1", + "node_modules/@smithy/types/dist-types/ts3.4/endpoints/EndpointParameterInstructions.d.ts": "a7a54de87518d5af2745d67bb0654cf18dbc24b439b3e48911828b522023adcd", + "node_modules/@smithy/types/dist-types/ts3.4/endpoints/EndpointRuleObject.d.ts": "ce8ff376799e78db4016fba370e0491c736e3f10c29136740fd0585f18240b79", + "node_modules/@smithy/types/dist-types/ts3.4/endpoints/ErrorRuleObject.d.ts": "1d427364b5b855c843f1574aec790d5eddcae296187ed0b8854bbae768151687", + "node_modules/@smithy/types/dist-types/ts3.4/endpoints/RuleSetObject.d.ts": "8514a210c77a976a05477f00d2af9ebccd021e4d501181eda95f163d1427cd2b", + "node_modules/@smithy/types/dist-types/ts3.4/endpoints/TreeRuleObject.d.ts": "7829802c3df2ddf19e8ad082a89acb43758e8f8a273cae027e1fabf9e6644629", + "node_modules/@smithy/types/dist-types/ts3.4/endpoints/index.d.ts": "f1e982ed9d3a1f8f69140cd3137b39b91f48ed224385f06591e7b89d021b63fa", + "node_modules/@smithy/types/dist-types/ts3.4/endpoints/shared.d.ts": "4d7dcce8aac7e9e6c8dc4354c86eda6eec1ab1bdaf784da680afce05ec2af798", + "node_modules/@smithy/types/dist-types/ts3.4/eventStream.d.ts": "3dc46042d3fc13c2c738aa308d4b65a8f817462a359dae2b834b107ebea1636b", + "node_modules/@smithy/types/dist-types/ts3.4/extensions/checksum.d.ts": "c23d3488c224158762f1700e389f63c5f22435c11a4368f325bf495aab172481", + "node_modules/@smithy/types/dist-types/ts3.4/extensions/defaultClientConfiguration.d.ts": "591930f951d9c875ba61f12e4d28c19abe732d4a6e36bd31be6dc125339ffe5c", + "node_modules/@smithy/types/dist-types/ts3.4/extensions/defaultExtensionConfiguration.d.ts": "8b45b62cd2906c8a4ff9a496f97f7fc986c07ee90fa3abb6ecc6c09f88e11010", + "node_modules/@smithy/types/dist-types/ts3.4/extensions/index.d.ts": "84a7946d33f25b37af0dcb4885fb7a140c81130e947d8914549d0a3672972287", + "node_modules/@smithy/types/dist-types/ts3.4/extensions/retry.d.ts": "67d48523aed9bb88bb2a7639313573285cf910e50662c6c45f2af63029587cb8", + "node_modules/@smithy/types/dist-types/ts3.4/externals-check/browser-externals-check.d.ts": "ef4c5a752faf31e420f46062ed6d2b2f898762528a786d5de9fe93d3e44ab9bd", + "node_modules/@smithy/types/dist-types/ts3.4/feature-ids.d.ts": "525105076c2d56ac42078ace14e2fa70915a21725c94abf24e9e1209784ff2e1", + "node_modules/@smithy/types/dist-types/ts3.4/http.d.ts": "e08c0c92c0547771203dee773e480a6c10fd3e3503d93a3e3b236d6426633dea", + "node_modules/@smithy/types/dist-types/ts3.4/http/httpHandlerInitialization.d.ts": "b74f805602db66562d131104efe08a6007bfb1dbbf9ebcab904bd0936c2719e5", + "node_modules/@smithy/types/dist-types/ts3.4/identity/apiKeyIdentity.d.ts": "2f27d03bf7f58be4eb77d0379bd0c13083fa9266ffa36ef632449bec454e2f5d", + "node_modules/@smithy/types/dist-types/ts3.4/identity/awsCredentialIdentity.d.ts": "a8f97f7ff4a65ec51c22942ad1aa4f4440696996098b594d6dba919b5a114c7a", + "node_modules/@smithy/types/dist-types/ts3.4/identity/identity.d.ts": "506cfaf15b5c750fe89fdd3b1b5c4a0a3bfa61a02394e611d2db957209812c3e", + "node_modules/@smithy/types/dist-types/ts3.4/identity/index.d.ts": "dba1b923c2c0f29e0ab4ab90b66fc55e7ced2622a90eca446862bb5caf7fcd6b", + "node_modules/@smithy/types/dist-types/ts3.4/identity/tokenIdentity.d.ts": "595a248c8de518ad85f712d9a67009016a4e7d80cdbbdc803ac2f4ca2a36ef62", + "node_modules/@smithy/types/dist-types/ts3.4/index.d.ts": "95c9f5c4efa73d3730b0a280d612841d8b663972e97389efc760865c171449e2", + "node_modules/@smithy/types/dist-types/ts3.4/logger.d.ts": "7a6a78ced0da0dd2d9729c4baaba1065cc0d3095cc4c24b770be67b30820832c", + "node_modules/@smithy/types/dist-types/ts3.4/metrics.d.ts": "6098769a082e46480a902691bf19e0ddb6151daf5ecbee2d7b89ae02fa8da934", + "node_modules/@smithy/types/dist-types/ts3.4/middleware.d.ts": "69277a0b92ac93dc871d0f160aa8d7087a3b6fea2bb90f115d07e456bac17e11", + "node_modules/@smithy/types/dist-types/ts3.4/pagination.d.ts": "21e4240b0e64089f43333995fd1614f5afdd07c727170276a596e6e935e1ec18", + "node_modules/@smithy/types/dist-types/ts3.4/profile.d.ts": "9c93143d00286876a7770ac8af26def9f9760c86dc813b3604a61da1692b1225", + "node_modules/@smithy/types/dist-types/ts3.4/response.d.ts": "ea74ede14343e3a88f4e1a248115ca0c7a67722b16048d4a3cd4053e10ed735f", + "node_modules/@smithy/types/dist-types/ts3.4/retry.d.ts": "e80139f84a870d418b239179ebdaef67a8c37ab4cd2c5e18c9f569d7c26975b0", + "node_modules/@smithy/types/dist-types/ts3.4/schema/schema-deprecated.d.ts": "8f3bf50e67174613258453a6ecacc4cba0d535cda61e1f11740c5e330c860c62", + "node_modules/@smithy/types/dist-types/ts3.4/schema/schema.d.ts": "bdb0f18087b86deb40921da3228aae430603c341ca16a6466d4400dabc62dfc9", + "node_modules/@smithy/types/dist-types/ts3.4/schema/sentinels.d.ts": "dda63ff233f0210c06db4cabc201965831752afae88011005ba94ae408b65613", + "node_modules/@smithy/types/dist-types/ts3.4/schema/static-schemas.d.ts": "f8b9c3b5b9a917fe311584cb5a85338f0342e848ef15438feb31f8a602c0c745", + "node_modules/@smithy/types/dist-types/ts3.4/schema/traits.d.ts": "a708b34aa04c58f934f0dedc80a59a105d3760ebe0ad894ca344bf5d4e8e919f", + "node_modules/@smithy/types/dist-types/ts3.4/serde.d.ts": "e3b14ca95e3a63ff9e02a52ddbfa9a978fa18141a269166bb269b0562b222535", + "node_modules/@smithy/types/dist-types/ts3.4/shapes.d.ts": "157c94c7c511369820ad1261bcb57add7a22574543c3858c7bfa13ccf7ba5abf", + "node_modules/@smithy/types/dist-types/ts3.4/signature.d.ts": "d29ee570c55268f926f344857f764656de70a0423c578cb6de76b6a2adb7f451", + "node_modules/@smithy/types/dist-types/ts3.4/stream.d.ts": "e343ffab8d9e396663b1359c998bce9a80f90552d4544b5c910cc9324338e493", + "node_modules/@smithy/types/dist-types/ts3.4/streaming-payload/streaming-blob-common-types.d.ts": "1d5f4059cd6c013106b23cc6dac91bd3527afad016c8e221a3e8219873da1a88", + "node_modules/@smithy/types/dist-types/ts3.4/streaming-payload/streaming-blob-payload-input-types.d.ts": "af532c9d1a6556044f407b814f091725f80c5342c0178c123915463c2cb30315", + "node_modules/@smithy/types/dist-types/ts3.4/streaming-payload/streaming-blob-payload-output-types.d.ts": "4341f6966728cfd6cade6c7123f95cf0dda75873bc051b0487860a552344ec0a", + "node_modules/@smithy/types/dist-types/ts3.4/transfer.d.ts": "f167e3bf261d370ac3a706225971f331a06eba6267563d38b49d6927f8f39d26", + "node_modules/@smithy/types/dist-types/ts3.4/transform/client-method-transforms.d.ts": "9438b4324a69e41b2e338f80110dff3009fee9a913fc37e197d117862463c5dd", + "node_modules/@smithy/types/dist-types/ts3.4/transform/client-payload-blob-type-narrow.d.ts": "ab325b18a76df013c24c2667bb1af1601acba32c4f23eaf00affb4935a89777b", + "node_modules/@smithy/types/dist-types/ts3.4/transform/exact.d.ts": "f6eefbd1f285696788348eb8045bced8c1e5c49d9b7d7b9e99d2f6f50d7db8c3", + "node_modules/@smithy/types/dist-types/ts3.4/transform/mutable.d.ts": "b738a4e69f084b2c79b895503c8cdf23499506018d07b38f1b310c7d8bab2b94", + "node_modules/@smithy/types/dist-types/ts3.4/transform/no-undefined.d.ts": "03b36abf693997ded0bcd75f1259fefb9b9d7d398a4175e4a49a6430ffb614f6", + "node_modules/@smithy/types/dist-types/ts3.4/transform/type-transform.d.ts": "7f8ffb83f0d97dc0a921bb02840435a4e55601d14d79e6407581a71713548434", + "node_modules/@smithy/types/dist-types/ts3.4/uri.d.ts": "9eb4022ce29cd74edfcca184ff1d6f8c06a874b76539125bce283bc0f9523712", + "node_modules/@smithy/types/dist-types/ts3.4/util.d.ts": "5347e238d3825b954dcd7267dc444db0599d3ff236e842dc99a145582d58ac1f", + "node_modules/@smithy/types/dist-types/ts3.4/waiter.d.ts": "a0359d4cdca49d80e713981228966deca13a201e316e32324945a34d96734688", + "node_modules/@smithy/types/dist-types/uri.d.ts": "a8035a411d3b11d7f57bf0f1f2686cfda8f700a20d68821e32a0d6ebe5dbabf5", + "node_modules/@smithy/types/dist-types/util.d.ts": "1648cbd2f46b82fc3a6c612d17542b6a21ffaf0a4aae9ea9778ce9346bbdedee", + "node_modules/@smithy/types/dist-types/waiter.d.ts": "04d4c47854061cc5cefc3089f38e006375ae283c559ab2ce00763bca2e49516b", + "node_modules/@smithy/types/package.json": "3de7840fd63a03c33bb687fbdc4794884598402600b2ee02c68856e78e41bd52", + "node_modules/@types/duplexify/LICENSE": "c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383", + "node_modules/@types/duplexify/README.md": "bc02790f9d2c961e35a6d5a7db8e629ccb8735aef57880f717c7d3dfb4cb19b8", + "node_modules/@types/duplexify/index.d.ts": "28a9ce38fedd06dcb42547ea254869fe3d0cbe754385f058c59b0d38720c04a6", + "node_modules/@types/duplexify/package.json": "883a5ef96b45ee570985dfc11713686302088dd5a5643b02847d7b5d631c8a21", + "node_modules/@types/lru-cache/LICENSE": "c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383", + "node_modules/@types/lru-cache/README.md": "a49a2996de095bfa91c1bf6a1806945e31d60ab088dad5dc74696b505b38294d", + "node_modules/@types/lru-cache/index.d.ts": "6d727c1f6a7122c04e4f7c164c5e6f460c21ada618856894cdaa6ac25e95f38c", + "node_modules/@types/lru-cache/package.json": "6606e02b26ca594443b43fc787896bc000b1cc138026e1b63140578de91400d9", + "node_modules/@types/node/LICENSE": "c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383", + "node_modules/@types/node/README.md": "05c2b153709fe6e1c8b3e15a3c0e2654e29c5243c29146673857b0bd2e271ca5", + "node_modules/@types/node/assert.d.ts": "594ae90cacd813fa392ff80d2e0a8eff4e41f4a136329a940e321399dd8895b4", + "node_modules/@types/node/assert/strict.d.ts": "d1f333ade8ff35a409b4984e1f11956fe11c61855b0c7e9b59f0313e48b40c4a", + "node_modules/@types/node/async_hooks.d.ts": "0224aa4b3464895d69c413a640a19ac2166778a74eb5eb3b36b021c72d42b274", + "node_modules/@types/node/buffer.buffer.d.ts": "f9b68a1c3f8f45b4c199fa5b3ac09497230cb8f2f0cdcfebe5817b30b836a64a", + "node_modules/@types/node/buffer.d.ts": "575aa163c4c32e5b82e0e99b1f9f80806e1d78bfa259706e05139251c022277b", + "node_modules/@types/node/child_process.d.ts": "7fb21326854faaa31ef02c9d51629fb6acedacb9396e74bf22ff8409441ea9ae", + "node_modules/@types/node/cluster.d.ts": "2fbf504c4791f9d32cd766cfe6b605bcda63289b925401953a7900db9af85348", + "node_modules/@types/node/console.d.ts": "ed0fb633cae35948d9e144004299a4bdf1ab912667c787b7fbffcd6d8c7b92a2", + "node_modules/@types/node/constants.d.ts": "1678b04557dca52feab73cc67610918a7f5e25bfdba3e7fa081acd625d93106d", + "node_modules/@types/node/crypto.d.ts": "7db737262597ae2e35daa8cc1748f482cf4b0914e00a3d5a32cf6c4fc869a375", + "node_modules/@types/node/dgram.d.ts": "9c90f502816bd0179556bb875e86c944af3f6b995cde63199edbe4e0b1219aba", + "node_modules/@types/node/diagnostics_channel.d.ts": "97581dc7099ef3d844c83706d53a7ecf895c0e867fbf6b9dc035bb3080f677bd", + "node_modules/@types/node/dns.d.ts": "e494b286c8e27c52f8c7f945aa80ff223f2a3cd4f7a2619fa8991f1459e26f07", + "node_modules/@types/node/dns/promises.d.ts": "f54f5f9e10bbceba7515dfad0ffc89dffdb8d749d0604594c57a725cf6d79c20", + "node_modules/@types/node/domain.d.ts": "41d17e1ad9a002feb11c8cdd2777e5bbc0cdb1e3f595d237e4dded0b6949983b", + "node_modules/@types/node/events.d.ts": "8c7e618c2a91ea7f6b5cca272a295864e92c16413be8fc56a943e8c7d5320011", + "node_modules/@types/node/ffi.d.ts": "13f487e411f9657df0e76572e500c1209b8fe95ee3b4cc6e6e7913c15f56207d", + "node_modules/@types/node/fs.d.ts": "9d37b8a9678efbcdf38238b59ce8e6f7db70aba1a516f3a4a671a301dbacfb3d", + "node_modules/@types/node/fs/promises.d.ts": "a47a792b8cff71443be8dfb199f2b77e102a7bf9d4f90c18059f05d9af2d7413", + "node_modules/@types/node/globals.d.ts": "f53a7652392cf26ebbe4e29fd0672aa87c93bd6d0241289c13fab87b9ac35c8a", + "node_modules/@types/node/globals.typedarray.d.ts": "0ccdaa19852d25ecd84eec365c3bfa16e7859cadecf6e9ca6d0dbbbee439743f", + "node_modules/@types/node/http.d.ts": "ad50512520720d10294c629cb4e2df23891c3fcbeaca00fbfb9e6bbea65f3dd6", + "node_modules/@types/node/http2.d.ts": "189769511b45c93b23ba76069f7701d3b631cfbedc1eb94c7753486c78736f78", + "node_modules/@types/node/https.d.ts": "2c2bdaa1d8ead9f68628d6d9d250e46ee8e81aa4898b4769a36956ae15e060fe", + "node_modules/@types/node/index.d.ts": "c46f111af647c4134ae869653130610d62da321f90ebf19e186a3ea4bed79350", + "node_modules/@types/node/inspector.d.ts": "ba389fd3d7fba6670e97bc82eb12469dcb371d22f24e63859c1df5a083c823ed", + "node_modules/@types/node/inspector.generated.d.ts": "5ff4433a2deae4f85ab1377e90a7554ce6b47ae51c69a84ca30a6e22fae85834", + "node_modules/@types/node/inspector/promises.d.ts": "82b91e4e42e6c41bc7fc1b6c2dc5eba6a2ba98375eb1f210e6ff6bba2d54177e", + "node_modules/@types/node/module.d.ts": "97234c5303866576f913d0ccae7d58d6322d9e803c7bf1228a3fda46ab8087b2", + "node_modules/@types/node/net.d.ts": "961dec547748827cb6b45f6fdc66273f7bacd9c73103c6779f070f2fd07f23f9", + "node_modules/@types/node/os.d.ts": "73eeb740dacc45adc607a4f59f90fa9dbe2717d93d82f115f45f34e18f8d644a", + "node_modules/@types/node/package.json": "fc36627b9da6c853cfd8042f0592014a59f54296aa7be07acbbc01788613ba55", + "node_modules/@types/node/path.d.ts": "ec501101c2a96133a6c695f934c8f6642149cc728571b29cbb7b770984c1088e", + "node_modules/@types/node/path/posix.d.ts": "b214ebcf76c51b115453f69729ee8aa7b7f8eccdae2a922b568a45c2d7ff52f7", + "node_modules/@types/node/path/win32.d.ts": "429c9cdfa7d126255779efd7e6d9057ced2d69c81859bbab32073bad52e9ba76", + "node_modules/@types/node/perf_hooks.d.ts": "d251cda16f3f6a3e07f07c9e6e823ef6c583c63f653eb8777dafa749a78298e3", + "node_modules/@types/node/process.d.ts": "361939136b496a38b3e6b7be750bbb6998ef289bb8dd304c673138542ca16137", + "node_modules/@types/node/punycode.d.ts": "230763250f20449fa7b3c9273e1967adb0023dc890d4be1553faca658ee65971", + "node_modules/@types/node/querystring.d.ts": "c3e9078b60cb329d1221f5878e88cecfa3e74460550e605a58fcfb41a66029ff", + "node_modules/@types/node/quic.d.ts": "89e2568ebc2ec1e4149384b105e80e8bbf41eb4d711547f1121f4f628028de96", + "node_modules/@types/node/readline.d.ts": "441b9bb09013654aa3d050e68b06464d8959b473e85868249d9d18f692acd35b", + "node_modules/@types/node/readline/promises.d.ts": "bc18a1991ba681f03e13285fa1d7b99b03b67ee671b7bc936254467177543890", + "node_modules/@types/node/repl.d.ts": "55246f15e33ff1e2e9e679b25fa9790a48db55dc63d567fe25fac8b6a0efe911", + "node_modules/@types/node/sea.d.ts": "fa94bbf532b7af8f394b95fa310980d6e20bd2d4c871c6a6cb9f70f03750a44b", + "node_modules/@types/node/sqlite.d.ts": "925734f303b7f0cc111d42aeb21781d38a79e6d5ac05b2b1400cd1861a75d27b", + "node_modules/@types/node/stream.d.ts": "5b26c4dd672d88336bb929787c9bfb55119e80ba89ec6dc923da1c063892843e", + "node_modules/@types/node/stream/consumers.d.ts": "7fa2214bb0d64701bc6f9ce8cde2fd2ff8c571e0b23065fa04a8a5a6beb91511", + "node_modules/@types/node/stream/iter.d.ts": "7286e4e5f80c0b1ab16addbdbbc9dd47fbae1c42034189261177b84d745cde3b", + "node_modules/@types/node/stream/promises.d.ts": "f12624a4a8d042b68914eac1b0a16571fc1c523173fcdf2517c65d191bd5a86c", + "node_modules/@types/node/stream/web.d.ts": "cd2a656ba1d250c2002190b683e3826bf4362b6919450bc0f9bbf5612ebad837", + "node_modules/@types/node/string_decoder.d.ts": "841983e39bd4cbb463be385e92fda11057cab368bf27100a801c492f1d86cbaa", + "node_modules/@types/node/test.d.ts": "3e221ff0522e6ff95aa74a5211126aaf4d7e85ed8c8ff19290d591ad4bef988f", + "node_modules/@types/node/test/reporters.d.ts": "7da83f6429c6298ebf2be43d296b56ecfc94c9c28f5471b2f23b87545b914cea", + "node_modules/@types/node/timers.d.ts": "e4156ddb25aa0e3b5303d372f26957b36778f0f6bbd4326359269873295e3058", + "node_modules/@types/node/timers/promises.d.ts": "cc1b433a84cae05ddc5672d4823170af78606ad21ecef60dbc4570190cbf1357", + "node_modules/@types/node/tls.d.ts": "751048d4aa67e20e5ecd2692340ea0d19bae42a20cc651d6f56d095e411ce3ac", + "node_modules/@types/node/trace_events.d.ts": "7f78cfb2b343838612c192cb251746e3a7c62ac7675726a47e130d9b213f6580", + "node_modules/@types/node/ts5.6/buffer.buffer.d.ts": "697cf5ff22e25b78c1dcc108fb51838b335dbe8f2a024922bc93375d6c5d87a8", + "node_modules/@types/node/ts5.6/compatibility/float16array.d.ts": "394fda71d5d6bd00a372437dff510feab37b92f345861e592f956d6995e9c1ce", + "node_modules/@types/node/ts5.6/globals.typedarray.d.ts": "6f2442c0ca5e7fcb9d51ebbd7d43079844bcbfd947bb679b9419900745f871d5", + "node_modules/@types/node/ts5.6/index.d.ts": "c06cc00bc72dc896b4f1cceff8f1a52ea199ce23f8a0f7ccd93420e5906b17bd", + "node_modules/@types/node/ts5.7/compatibility/float16array.d.ts": "cebfeedf63623d54f7423d58cae1ec204c0d8d97a66d5fb17579e26a902085e1", + "node_modules/@types/node/ts5.7/index.d.ts": "62c781adc94b18f3f2982eb58a266cd48e118c3295adb7f1d51729215599c76b", + "node_modules/@types/node/tty.d.ts": "aed8f3e5c7e53462565eedf34a9bcb700393cb2c1ab0c768602b929c3ed3145d", + "node_modules/@types/node/url.d.ts": "8e2577e7262051fd3c5bd6ca2b2056d358ff8853565720f92455860824c25188", + "node_modules/@types/node/util.d.ts": "7ec8d7d483f7394f9da611b10d3a0e402f76ff5c991261e6ce43a15f81ca4258", + "node_modules/@types/node/util/types.d.ts": "bb9dbb4b2ad81e3e71ec5ba4314973718555b9d04ba2a17dfbf875efecb8e2c0", + "node_modules/@types/node/v8.d.ts": "9021dc5be8f3a87af5cede29221188a6497e2092b6889e0be212ef6b6b7153e9", + "node_modules/@types/node/vfs.d.ts": "4a8c8fd951d9fd9e0775f0aae8ed1f8b528204e24a2600bea3a1228ea83de52c", + "node_modules/@types/node/vm.d.ts": "5b9cc0c724a8ebc7a873ab8e73c20b2416cef04b9e9d7f95ef1fa0fdcd4e0ec8", + "node_modules/@types/node/wasi.d.ts": "99ab6d0d660ce4d21efb52288a39fd35bb3f556980ec5463b1ae8f304a3bbc85", + "node_modules/@types/node/web-globals/abortcontroller.d.ts": "e5e01375c9e124a83b52ee4b3244ed1a4d214a6cfb54ac73e164a823a4a7860a", + "node_modules/@types/node/web-globals/blob.d.ts": "f90ae2bbce1505e67f2f6502392e318f5714bae82d2d969185c4a6cecc8af2fc", + "node_modules/@types/node/web-globals/console.d.ts": "4b58e207b93a8f1c88bbf2a95ddc686ac83962b13830fe8ad3f404ffc7051fb4", + "node_modules/@types/node/web-globals/crypto.d.ts": "1fefabcb2b06736a66d2904074d56268753654805e829989a46a0161cd8412c5", + "node_modules/@types/node/web-globals/domexception.d.ts": "b00a630557d1622ad312633bdbfbdb9c6b7220d948dca9f899db30679f160074", + "node_modules/@types/node/web-globals/encoding.d.ts": "c18a99f01eb788d849ad032b31cafd49de0b19e083fe775370834c5675d7df8e", + "node_modules/@types/node/web-globals/events.d.ts": "5247874c2a23b9a62d178ae84f2db6a1d54e6c9a2e7e057e178cc5eea13757fc", + "node_modules/@types/node/web-globals/fetch.d.ts": "c3f5289820990ab66b70c7fb5b63cb674001009ff84b13de40619619a9c8175f", + "node_modules/@types/node/web-globals/importmeta.d.ts": "b3275d55fac10b799c9546804126239baf020d220136163f763b55a74e50e750", + "node_modules/@types/node/web-globals/messaging.d.ts": "fa68a0a3b7cb32c00e39ee3cd31f8f15b80cac97dce51b6ee7fc14a1e8deb30b", + "node_modules/@types/node/web-globals/navigator.d.ts": "1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123", + "node_modules/@types/node/web-globals/performance.d.ts": "6c36e755bced82df7fb6ce8169265d0a7bb046ab4e2cb6d0da0cb72b22033e89", + "node_modules/@types/node/web-globals/storage.d.ts": "e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1", + "node_modules/@types/node/web-globals/streams.d.ts": "279d0cc1a72879918854265c3405369aa2d4d23509b8694ea102995b298cc5e2", + "node_modules/@types/node/web-globals/timers.d.ts": "90e85f9bc549dfe2b5749b45fe734144e96cd5d04b38eae244028794e142a77e", + "node_modules/@types/node/web-globals/url.d.ts": "e0a5deeb610b2a50a6350bd23df6490036a1773a8a71d70f2f9549ab009e67ee", + "node_modules/@types/node/worker_threads.d.ts": "80a4b7023a2cc9f14d3f2e4635d6615ab3eff4b5b3630f62a8e73992fdd8cdb9", + "node_modules/@types/node/zlib.d.ts": "ec62d16f95636123624a69191a35b5df44b4b0a6428977e856c5739c0a787471", + "node_modules/@types/node/zlib/iter.d.ts": "afa1c49f8e559e413d57343339db857d2a8159435cf9cf7d4deb41718fff1b88", + "node_modules/asn1.js/.eslintrc.js": "f7f1c986945006df68014236796a1580bc2beebc57269668f29df7629630d47d", + "node_modules/asn1.js/LICENSE": "251ecdcdb2bafa7b106187a8cce96f322d0074c66e557c3c94cb49c769970408", + "node_modules/asn1.js/README.md": "6e462debebf95fecbf06c0888ffcf07b304df0c6d1ff09f5b36e70d7398d29db", + "node_modules/asn1.js/lib/asn1.js": "3c28dbbad70ade3a02eb723ec14e0bf1d8d2454c0e1df15b279a639dcd8d0921", + "node_modules/asn1.js/lib/asn1/api.js": "bb26a4a9dc4b4eb6498ba567d5edef88eed9f716f922d98f26abf9e9a516a960", + "node_modules/asn1.js/lib/asn1/base/buffer.js": "cd360f0a80cab827d3828bd0f62b4bdb9a1e720d8af7bb6ae1856ee10cbb2f95", + "node_modules/asn1.js/lib/asn1/base/index.js": "411b775d3e4098f6e1fbdd665160f246338ad8a64f6d998f54cae472fecc5d9f", + "node_modules/asn1.js/lib/asn1/base/node.js": "29408f47178558c942b814695c203415ed4345fb0e40fedd9a7cd779d0f68b86", + "node_modules/asn1.js/lib/asn1/base/reporter.js": "8519bddfc5de8a49a96f82a01ade31b1233301cf70b6ccdbaf3e0cc377bbfd01", + "node_modules/asn1.js/lib/asn1/constants/der.js": "a9186f2b120b3a4a6875a895a03b300a05e15a362695d5ddeb46967d7621552f", + "node_modules/asn1.js/lib/asn1/constants/index.js": "01aaefdd24bf1869347b14c35dc5d9b350a12eb3b80852e1b31477b2c52a0b18", + "node_modules/asn1.js/lib/asn1/decoders/der.js": "a8d8c8dc73b0b7732138d3e2a3099f877f2dc5ea22ed89f273976cd425ae7c08", + "node_modules/asn1.js/lib/asn1/decoders/index.js": "655474512dc950b76564d76814651cdb9518df98c799e3b53fe49cb6a48b2502", + "node_modules/asn1.js/lib/asn1/decoders/pem.js": "e33d58fc00591be586d5f1fe2e7ae7d26a718530ca9b496244178c0d240d7d7b", + "node_modules/asn1.js/lib/asn1/encoders/der.js": "ac3ce693c7b6e89708f6ab7c44de6b1b672a4eaf1a8bd192e55e4f16da1150de", + "node_modules/asn1.js/lib/asn1/encoders/index.js": "9ca424f5991c7590c1d81b5faf53d2f699b7812533e48e505c203839e762cef7", + "node_modules/asn1.js/lib/asn1/encoders/pem.js": "3e83d029ad6ce53f88851984f64d66c6fd0b1461db1cd54b315de503fd931b5e", + "node_modules/asn1.js/node_modules/bn.js/LICENSE": "445739f5b5eb63e5aeff5aeb0f35a45080a421615dce0d97f9939aeea498acdd", + "node_modules/asn1.js/node_modules/bn.js/README.md": "0fc89d35ff8bced5f11a412d0d6fc8858f5083282d053a52be82a107f8e1ebf6", + "node_modules/asn1.js/node_modules/bn.js/lib/bn.js": "6c67724e6d6ba2c42e098ffa680148dda95364afd565c2b1f72c966f26bd2cf2", + "node_modules/asn1.js/node_modules/bn.js/package.json": "34b1923a700a3a344515f0ea673cd36b22cdbfe9f68ef30dfc06dcdaf285ee0b", + "node_modules/asn1.js/node_modules/bn.js/util/genCombMulTo.js": "931b90cfd307f69888e148149f8b5736049089a90cf93bf267cadd5058cc1ebf", + "node_modules/asn1.js/node_modules/bn.js/util/genCombMulTo10.js": "a544f8f3bd04ccc08c6463d34d3b6bc796af20e10b6125ce7d60e144d714d294", + "node_modules/asn1.js/package.json": "a7f7950928493c0c6ee49d25cdea08c838fbd86deb40bc8ab3ef4782779bc0ee", + "node_modules/bn.js/LICENSE": "445739f5b5eb63e5aeff5aeb0f35a45080a421615dce0d97f9939aeea498acdd", + "node_modules/bn.js/README.md": "a49049bde43710e6478c56026c3bf4450c4aa11fdfead087bcbf3e6976a55e3c", + "node_modules/bn.js/lib/bn.js": "1b3516df7d559a221354db5cca56531d5c189b328abbf07dc97cdd6a0812983c", + "node_modules/bn.js/package.json": "7f158cf60aa9d9ac141c64a9e10f91b54d482faf44287c0829090863f2c6cefa", + "node_modules/bowser/LICENSE": "dfde54fedc270c2a305d9a9f993d93f706d34624455a2fd2d2a05ca34d8ae8c3", + "node_modules/bowser/README.md": "3a78f9f739669683862d8b996d68ea15c6bd030bb7369d96990adfcd1ec4a4ae", + "node_modules/bowser/bundled.js": "d9e7babb7849bd7c2aab5a56f484f39ff152d77f49c9226c5f3030833ca1566e", + "node_modules/bowser/es5.js": "fb1c1a555d9f830754a48122557f5d67e3216ab4ac1bb238190c504afefd65d2", + "node_modules/bowser/index.d.ts": "e4775e3c024060c1ec3b6df86810bd34fa8ab10e74e473a7952e7a7942cfc9de", + "node_modules/bowser/package.json": "b12b643998a1a7febff9933b5a05e53045c722ff4de13fab751391b2d346a974", + "node_modules/bowser/src/bowser.js": "c412675f822d7d7ff22f0563364c48d77f6bee4b020cde1a938866ea4ee354c2", + "node_modules/bowser/src/constants.js": "b123505cbc9a48596fd7fb00b26beffb59353e8a34ddffef0d84d148de235961", + "node_modules/bowser/src/parser-browsers.js": "9b0932a321386f87d729ac681dfac15c39e2593f772b5e75900e8ee3f0b2ae35", + "node_modules/bowser/src/parser-engines.js": "77e92334e4e20d1aa1f3046d571b64df971951b88787184185241d275bec5800", + "node_modules/bowser/src/parser-os.js": "ba15c933e9e5fcacbef30ce59390c9cf960b1999740d6d0be71f0a69ee6ec173", + "node_modules/bowser/src/parser-platforms.js": "ad75953b9d31c89b1513e305ef40ad285234f6ee42b6e603bee4701d26bf26ef", + "node_modules/bowser/src/parser.js": "ba58cda161bcdae55e091786ff8896a01872cd3a63047c61ad304b9e6d54255a", + "node_modules/bowser/src/utils.js": "87dc377180d48ef18257f9976e5d4a1334792f20da38bed04bf5c81dcec6888d", + "node_modules/duplexify/.travis.yml": "89911ac3a2ac145ba4d614b23df808aca4d9b6492ab1866bfbc240f6bd17c32a", + "node_modules/duplexify/LICENSE": "3fe8d55a98dbf260eace67c00cf9bc53edb46234e840098a0b93df3096b97fb6", + "node_modules/duplexify/README.md": "ebacd0e68864940b7350889536c5231c0d0f8519b66794c2af93c28cc9aebf1c", + "node_modules/duplexify/example.js": "27c0b0fbf208f7c8eaf570592852bd222ca9bdc776453bcacd5dd9146c7b02e1", + "node_modules/duplexify/index.js": "f4b5f98eb1a2cd1c5329afb133039d8a63ccd56d7180e813b273b6a62ac198f8", + "node_modules/duplexify/package.json": "83975d2cadc3d4f619cc02538d8c8be31a00a05930f543f2bdd9f8bd636d236e", + "node_modules/duplexify/test.js": "e52ad606af3930f2faaa79b61900432d7f421233223f55f3d5b666cc21e31367", + "node_modules/end-of-stream/LICENSE": "3fe8d55a98dbf260eace67c00cf9bc53edb46234e840098a0b93df3096b97fb6", + "node_modules/end-of-stream/README.md": "177b2368d761e70733e06bbc2ff58f59360e49643aa86b99bc3114ba7ee2fe0c", + "node_modules/end-of-stream/index.js": "fd2c4e9b9b363dce5971089ad21a45b68499dc5c7c3ee2aca6cffa9bc2f76d04", + "node_modules/end-of-stream/package.json": "da504d0bef5a5974590b62f58df0003b448eccde6b5fbeb044506b7e43368eb5", + "node_modules/inherits/LICENSE": "5ffe28e7ade7d8f10d85d5337a73fd793dac5c462fb9a28fbf8c5046c7fbca3b", + "node_modules/inherits/README.md": "b33c8be1ca837d9e4c9b0e04a550feacbc101556164d3cdbb875aed6ccbb8a29", + "node_modules/inherits/inherits.js": "bb380f32bef5feb18678f0f45f88073fed5d7a0069a309132cb2080cd553d5c7", + "node_modules/inherits/inherits_browser.js": "ad322a7b1dec60f3d2ebda2091816469efb55b567d241cf3cf0fa4c5a4afe500", + "node_modules/inherits/package.json": "be645800bc94fd8de29c8ae91690549b316cc437100108aeea7b2f347693cc80", + "node_modules/lru-cache/LICENSE": "4ec3d4c66cd87f5c8d8ad911b10f99bf27cb00cdfcff82621956e379186b016b", + "node_modules/lru-cache/README.md": "981a9b1f5cffb2d70e9e4f085d98d651ef8b83384b7f6c845a12fa941dd58b43", + "node_modules/lru-cache/index.js": "f527d2386a6266b9bf67d264194948cf741b12daa19fea19e3537414ee31f9a7", + "node_modules/lru-cache/package.json": "b06071b99bebe6cc7f7716f65ed4f36ff54aaf78d74ea0bec72f9029a8e2c215", + "node_modules/minimalistic-assert/LICENSE": "136fee1129ea96ce18b41c26a72f5d4ba196ee41bfd73b55d0b8509cdda148e9", + "node_modules/minimalistic-assert/index.js": "c62009724a28986193befd6b7746ea8f8a96fef1ac64df78fe3e4b05a8ffeeb2", + "node_modules/minimalistic-assert/package.json": "9a8e71d52eadcfae410d56953bf873e4089903001c872f964fc529fe906b8335", + "node_modules/minimalistic-assert/readme.md": "dad623dadc374fd700b74f3ee6f5ac316ba62a7073e6f9c80020589673753629", + "node_modules/mnemonist/CHANGELOG.md": "2b9c897dbc1031301b0d6b5b2b7b4ea2900a569ce5d54a98eb99e9749da40d44", + "node_modules/mnemonist/LICENSE.txt": "dfa5b838b877406f163d57ed736fd6d7f99ed442dfde2152c11d7b34dff3dd81", + "node_modules/mnemonist/README.md": "f4a86b650e64de5dc87f722eec0295d891c99f4e62f48517047db928996e9102", + "node_modules/mnemonist/bi-map.d.ts": "5c0a7a1d13891ffe4d2ce05eec148d7ba5898c5ad0cddceebf6d760d46aefd4d", + "node_modules/mnemonist/bi-map.js": "8ca00f439a6ba6236ea5682457b3b2004a8badb6827f0bdd48dddee38f689063", + "node_modules/mnemonist/bit-set.d.ts": "78bf5289d550431ee86eee1da373131d7524585bfa13861505a897f7e255d4b2", + "node_modules/mnemonist/bit-set.js": "84ad4721444bd12da8703bdb39a8d72762633d8c47f94bb76e4aaafbde53f0c4", + "node_modules/mnemonist/bit-vector.d.ts": "b2d4e2275f3fae5b51a91a95f2f3cca9ec0049fd02a1ac16f44422b1b794f599", + "node_modules/mnemonist/bit-vector.js": "e8d661ee596b3104f182337b79b246af2de083e047b3bf32cf07e33886b80a9f", + "node_modules/mnemonist/bk-tree.d.ts": "f852c7a3155dff6c6a3eb4a530318a8438ac26be1f06a2e2a97ed869f2b257d2", + "node_modules/mnemonist/bk-tree.js": "50ab88c3498ed2ea525a564573f25c07c793a8ffebf84fe3077a24fe4b070127", + "node_modules/mnemonist/bloom-filter.d.ts": "8dcc8fad41043c6cb998be75cb73737c97dc07323ede766353330bfac52a2465", + "node_modules/mnemonist/bloom-filter.js": "5aefa1efc466e31f9878404693e8d96ebe8f08461a8abda2def2b5240770bc50", + "node_modules/mnemonist/circular-buffer.d.ts": "59ce714991e8caeaa32adcf6a5940691994925760aaecc5d5d0c60520e54dbb3", + "node_modules/mnemonist/circular-buffer.js": "4766c99eede53372d89ea7395dd607da5aa6bce421331f599912eb87fb99667b", + "node_modules/mnemonist/critbit-tree-map.js": "a96d44b9c3af0c1c7e1167dd4ac3d2708b28e62fa9fd502ccc652e1550890321", + "node_modules/mnemonist/default-map.d.ts": "0a48a263f40c63d76cf3f0264ee7e80cee5bf1ba35d9d20a7113c2644cbdcb60", + "node_modules/mnemonist/default-map.js": "7ec38cc2aa3085d95ba86275a7df6537c9047b2b1e86d8b15ccd2769bfea4bf9", + "node_modules/mnemonist/default-weak-map.d.ts": "19540da60c08ebb434f269b0956ad591fca77371b421ecaec389a663c24f5ed9", + "node_modules/mnemonist/default-weak-map.js": "d4640e76047908b67a7fe9bc83beab8f3b6b054a6769153e2041e1c858375fc4", + "node_modules/mnemonist/fibonacci-heap.d.ts": "e0ea4908c3e1c597b7747931daf74451b9f8bf6c0580c701140b1c756e177250", + "node_modules/mnemonist/fibonacci-heap.js": "75a2426291ff6f151126842bd5d25ab58a790bc5fae8b64aad59f8819f41140a", + "node_modules/mnemonist/fixed-critbit-tree-map.js": "0741863ba39450cc4ab4e6f1364ffcb5e34922e164029b42edae17d20bcf84cf", + "node_modules/mnemonist/fixed-deque.d.ts": "98a01659e17e0fd3fa2f3e60d5d3462eefcfc1e69757c97128dd158c8d3aa179", + "node_modules/mnemonist/fixed-deque.js": "90a1fbaa28f96d93044fe249dfd8370b19c59127f12c1a523fff4055cd260f36", + "node_modules/mnemonist/fixed-reverse-heap.d.ts": "f2e3e7319a2cca8c002ca131a213c0b69a3f776cb666fb44174baf60806e93c0", + "node_modules/mnemonist/fixed-reverse-heap.js": "a131adb148c14eb663bec228bebd497acbb0a957de4e3a4bedb5c3d1a427a2c0", + "node_modules/mnemonist/fixed-stack.d.ts": "ddd7a000d9b9d7432ad1f75a8bbd519eda5a43f9d78cf7cd3e4ab8494de82d19", + "node_modules/mnemonist/fixed-stack.js": "0aecdd16784aab53bfe9bb37dc8fb0c522d9712470c16575ca9ca4edaca52999", + "node_modules/mnemonist/fuzzy-map.d.ts": "7d47230963402516f80d92baf7aa763d317b24ae05d0f12894e0df02eb652e95", + "node_modules/mnemonist/fuzzy-map.js": "065e44dd90e92c52260ecb0308fbaa3173fd7a234570890dce3ce1a11683ee4a", + "node_modules/mnemonist/fuzzy-multi-map.d.ts": "3c8f9dfde460135ef695cea7d6c73951105ed36d2aff45b3f2f506ce8624b0ba", + "node_modules/mnemonist/fuzzy-multi-map.js": "4437000bf30fc1216472148d7c35a1e93cecf93ce5b94da0cd27395ea66d0748", + "node_modules/mnemonist/hashed-array-tree.d.ts": "2a4a9bbefbfece5054fa69664206f66f39856896d3decb1c302304ea00ad59b1", + "node_modules/mnemonist/hashed-array-tree.js": "b0105aa9fad363c674af2c3bd8483e2d36ec80469a1cdf429108f3cce1d5bb5f", + "node_modules/mnemonist/heap.d.ts": "aee678e05471ea7be7bc2e10078f3205eafe1a7217080f0489a4360ae74fa02d", + "node_modules/mnemonist/heap.js": "4656f036d242fc8d550b5b12eedf986fcd3974ed08614820ccf08cda356ae161", + "node_modules/mnemonist/index.d.ts": "77a2b539fdc80356d932b75935d3143568140d371b46d2c78e1659f25ca42bdd", + "node_modules/mnemonist/index.js": "e8f72308afa84bf3a96385586ddd6ec99490a4bcb212af7b16f7159e8d0a5541", + "node_modules/mnemonist/inverted-index.d.ts": "574270e4cee01757ec9b7ec446b3fbe032f83742b8d8689e0e6548dab27c7e03", + "node_modules/mnemonist/inverted-index.js": "6ffd00605dcafa18d6c469e3d0290bcdc7be30516f94164bd7c363f0f58018f9", + "node_modules/mnemonist/kd-tree.d.ts": "9900bcf52f4764325ebdc33e6ec57372e17e85ccfb39ff4bd740461a8379af20", + "node_modules/mnemonist/kd-tree.js": "2d8be883c98a6056efe24943055f0c5aeb9213f6abfb10e91cf6762a7979db9b", + "node_modules/mnemonist/linked-list.d.ts": "491df8438471197f4b286e8dfdd66c7436909e61600e4d6608c5ac4d814f2589", + "node_modules/mnemonist/linked-list.js": "7e651dd70b9103d10d201bbf9b08ebe7786fcaece4650586397da073d7519462", + "node_modules/mnemonist/lru-cache.d.ts": "e22fe5283f65c62cfab32e9cb8986dd8792a9044bc5d7dc4f49ea6c3c1ea7cde", + "node_modules/mnemonist/lru-cache.js": "d9f8a9dd3200e1a41b7d45cc811a64163b48cd892852040e888e6bf05ef33140", + "node_modules/mnemonist/lru-map.d.ts": "c31116ec14a99506b840afbe5a54e41177811a39af2ef1b8bec4fef5b62a8e78", + "node_modules/mnemonist/lru-map.js": "15a20f083309c505c3d965a8b7769d043d61c012cf3e01a38c71e99121769d96", + "node_modules/mnemonist/multi-array.js": "81651b84c521c24729b5cb041f1f04edf438293a8ee5e12f7b4f6cab24978127", + "node_modules/mnemonist/multi-map.d.ts": "25a44e030adaf4e545f570128ab1463f422f7d83b1cc4c1467712e7e431cc73d", + "node_modules/mnemonist/multi-map.js": "3f2173a4bd1d45eab3597e7a965959665ca5e7b1d417108cd4fcea2b0464be99", + "node_modules/mnemonist/multi-set.d.ts": "1007bf10e4fc0d9945dc95bb8557ef31d92292a2f3c3fd3ae84223e39fce3878", + "node_modules/mnemonist/multi-set.js": "c6e8d4005f1ebb7aee5bffd02523920542d9a82832777d315b88aa5cd3b80899", + "node_modules/mnemonist/package.json": "ec6a27483b13c614fcaf903734b2d9083f14ed142425fb6e394b5f51f23052a8", + "node_modules/mnemonist/passjoin-index.d.ts": "0b5a08595eba24ca3de4893a16a5e515e55e0d4ebced55364973841db7980b15", + "node_modules/mnemonist/passjoin-index.js": "d77d3fa4ce85cb5c89d9d0b1955241da8b38554703fb2c3dd9a2fd388fb64672", + "node_modules/mnemonist/queue.d.ts": "2013c436fe32c9ceb496b28cc05ffed6bc3ad9ad0467ee180f11af3236d069f2", + "node_modules/mnemonist/queue.js": "5ac8f6f43cfa7f20ae9fced0737e442d6574041f80c6c3f1afddc08890b2329c", + "node_modules/mnemonist/semi-dynamic-trie.js": "1eb0151033e8cceffdeef456d0560db3dc09ce96101504bb7e44fea0e6690579", + "node_modules/mnemonist/set.d.ts": "01def9b71675e1f91e91017fee0535683c5568b5af365b94177b87a4d43c2065", + "node_modules/mnemonist/set.js": "1910ad3e50a9ca4ea79afa9ff7eaf24806605b9ecaa28af590d599d83bf3e714", + "node_modules/mnemonist/sort/insertion.d.ts": "d08b027a7d5d1a45d433c241d873b145b7acd6e13dabf0d2595c67c903888e94", + "node_modules/mnemonist/sort/insertion.js": "39c9dfdb3ce49797fc9792c826ee4d1cff92dd6f5e5a4cf9dc321be5ca8a4348", + "node_modules/mnemonist/sort/quick.d.ts": "d8a51098fff6400ecdddd053cb17d107752d54377bbf63a415c3f8ff33f338f9", + "node_modules/mnemonist/sort/quick.js": "4597e9265822656fdcd241dc2c56f5030281afbe1a7e52a53beea25c3ca05978", + "node_modules/mnemonist/sparse-map.d.ts": "1967bef5bbcaeda006f89525b00b9075c86718d667d1992a07a7dbcd0f2ee98a", + "node_modules/mnemonist/sparse-map.js": "4cf28f642a7f1c3f251f412e01be84b951e2893b98eb93aa166ec507d34df547", + "node_modules/mnemonist/sparse-queue-set.d.ts": "f77ab34aa3f0e860adeab6993f569d919a8fef301b6db43ed135ce535ab92c8a", + "node_modules/mnemonist/sparse-queue-set.js": "2cf6f6ec487a54ec512ef0ec2c4d6e85356fd1653b3e73189c1b9dc805d7b84d", + "node_modules/mnemonist/sparse-set.d.ts": "51c0033ffda0786abbf95f5275935a6d23f8148917e62db9529c320a2d0a0ec1", + "node_modules/mnemonist/sparse-set.js": "7fc1460fe19575c479321074d882ef961972d9dac3022abe84da002d8002d12c", + "node_modules/mnemonist/stack.d.ts": "133ee2dffa996baafb35167083ddb84a310a5f0e5aeb23de04e6196ae18795b3", + "node_modules/mnemonist/stack.js": "573520f7c43cfb0a05668ec55adf847f5d6c5c44e58cc5049528256b5f5635f2", + "node_modules/mnemonist/static-disjoint-set.d.ts": "5800bdb02cdcfd7ea6eb806f98df4b7742654d425b5e296120096ef2ddca440c", + "node_modules/mnemonist/static-disjoint-set.js": "cc5d70ebaca20781ca7c923081e1df3d1f1862fbc4e5795863dc627b342b72ba", + "node_modules/mnemonist/static-interval-tree.d.ts": "155c93382b2c7ed5f427f83daabbb03f4be1157fccd340507f1383654d4eeb5f", + "node_modules/mnemonist/static-interval-tree.js": "2a63597025b3a6a426ce78585ef74d61dced78f80711bdc4e87a0578fabbaa32", + "node_modules/mnemonist/suffix-array.d.ts": "79919e011350c1f2fea1552dc33a112582ed99760e9f76479a82deda72effbdb", + "node_modules/mnemonist/suffix-array.js": "36218ede8e278008664361b75f5f676109ac1aa9b3fd934887762e7927e7496d", + "node_modules/mnemonist/symspell.d.ts": "9ed1635ab1c01bbf1f8959b788a0edf021e8b0e84f9420364eed9f9f1101a380", + "node_modules/mnemonist/symspell.js": "a8972a2e34cbcce317c22c88534e20e6356908bd74fd46ffd13838fadac4d458", + "node_modules/mnemonist/trie-map.d.ts": "5fb5cf76fbab668255eda7c8cd0944752793fab7ebd141ccda1131e8ef2c48ac", + "node_modules/mnemonist/trie-map.js": "df3d87f9d7a98385e6b837dee2021ebc11ecd25f77fb09479d11d043b8e7e33e", + "node_modules/mnemonist/trie.d.ts": "946a596f5225c55cea08ea7299e934b894bb5cf434828a4d59af60f5d19bd5ba", + "node_modules/mnemonist/trie.js": "9a988c563d35b27f7b5599038eeda137fa44e77458a9f36c02bdd0d177170b44", + "node_modules/mnemonist/utils/binary-search.js": "0ed93c4c285af1e5af9ba248576659d1df43c17e538439994bd8cbdf13f053a3", + "node_modules/mnemonist/utils/bitwise.js": "4c40fcad33b2695b7c94be9136ce473f02ec6d93ecbfc4c41a820587f911d33c", + "node_modules/mnemonist/utils/comparators.js": "f3f33fef5ce9b1ffc254afbbdd22e3bdb347825d3a99b663919de6102cfde317", + "node_modules/mnemonist/utils/hash-tables.js": "07788a6ae5ad27b7c912f02d9c44d7223cab5e84b80244dc7e5586ce95e4e497", + "node_modules/mnemonist/utils/iterables.js": "c7b9bff000992a69ecbe5a1a2ede42935fc268d8ea231d723b0244bc9849c6e2", + "node_modules/mnemonist/utils/merge.js": "ae138459f5066431ebd2dfa54e6e34e888bc6f71e1762d1319d455edda39291e", + "node_modules/mnemonist/utils/murmurhash3.js": "faafb05b6514d86f56b6c86d6c67580fe06b19a2e953a4299849d0caf09e31ea", + "node_modules/mnemonist/utils/typed-arrays.js": "acf21410a20dcbb3d93ae3caf156f815641bd1465b7ac8c3a38124b895e0620d", + "node_modules/mnemonist/utils/types.d.ts": "4380c06c8d36e963c74636ccc3f5f18dca333f52224e8bd8d57175670b021211", + "node_modules/mnemonist/vector.d.ts": "c728cc7c6202713c66e568437ba0ba54d1a1154818808f4c1296150368156b4e", + "node_modules/mnemonist/vector.js": "45f38c96bab8b16560ade2a1fc82fa5a800363c0a9812e27f1d91cef0ef6c7f0", + "node_modules/mnemonist/vp-tree.d.ts": "68e29a589c44fda8cf9bddb3707779714776644a9e7f674342fc0382b2a2dcd6", + "node_modules/mnemonist/vp-tree.js": "f96c3ac863bc27b3c81d64d9b8d4bc89301531ded48ded9e28f1722fc5b6088a", + "node_modules/obliterator/LICENSE.txt": "9b8cd2a49c8df91e5f620ded7c58fcefd86ba617a14c0bf576f74b78f3be345a", + "node_modules/obliterator/README.md": "68ee8c58e941532a19bd3f914d0a21bc9dec2d2c824083e92846fefd51cc38f4", + "node_modules/obliterator/chain.d.ts": "13ab3266521e9e039a0add904ce536751b14e80b36e88a40dacc74d06b59a169", + "node_modules/obliterator/chain.js": "85c9bd0a04c10ef926c1083a3269b49bc6548974025a14318cc99053622f7688", + "node_modules/obliterator/combinations.d.ts": "81b19d1946370a2dbcbcdc49b9b713019327ffb0b49d9f5b9fc510b4db9b4115", + "node_modules/obliterator/combinations.js": "e00d12e1fb104191234f33807819a2be5287e143ca3162324e9faf4fe6105ecc", + "node_modules/obliterator/consume.d.ts": "6616649d2f8ddd804f96229d833b3e23350809ec24a3da9aa25da83ee31fae38", + "node_modules/obliterator/consume.js": "308f895396db1238f7d78ca8f56410fa0463565364c61cc7e60323f7d053ef83", + "node_modules/obliterator/filter.d.ts": "486550d9a23d3986ad52428e479e56e8caa6e47f0707dd9f3bb06aef5dbc1b35", + "node_modules/obliterator/filter.js": "04e60a48f3ef21da96131fcb4f544e490f9f33c9f792f81d21d97d5cb899b1b4", + "node_modules/obliterator/foreach.d.ts": "4a9ee497ac8ea314180e6149f5d2fc55857e698c21ef3e0c5e971efcce349388", + "node_modules/obliterator/foreach.js": "bd40cd9726c7519a41efbf78c915702247702004878b3703fbcefaa9d2d538ee", + "node_modules/obliterator/index.d.ts": "3c6a92b49d8ea8982fa79d140b74cdf191c116a5229788ffffdeb5d7d7693975", + "node_modules/obliterator/index.js": "f772ffd0db1373a188cbf5ca172a310304c7166400adc53c7022d5b2d915d1b2", + "node_modules/obliterator/iterator.d.ts": "5a7ab80e055d15a98435e3eac5a1dc6c8345503c8013fe6c7275bd1752c427a3", + "node_modules/obliterator/iterator.js": "29758c32e4426a82f5999563e09e69b078b244bb1893ece9d87fff61a2373e9c", + "node_modules/obliterator/map.d.ts": "8ce83c316a71502275d0e695a780799980e03b960c32f2344e22f1c294940114", + "node_modules/obliterator/map.js": "56ca4f60ce065bb87e0b67086a57993fbf4a3e5666a8b9cb03643e3fbc27ee39", + "node_modules/obliterator/match.d.ts": "c4966fd86ae90a73c1c14139ec64a28c97357e51cbfa23773edf4ca1c897f6f3", + "node_modules/obliterator/match.js": "8f99f3dedeadb2ce3dbb8ac2ab053a6998f32f405ccc79d480c107720c3cc9ea", + "node_modules/obliterator/package.json": "9ad53463bcc7cbf25ddd9dab36cb88c728fa1eae344991af9c4b1556b2cbb128", + "node_modules/obliterator/permutations.d.ts": "6fdb40ff9f13d99d4e5718f90a83ae17765448e1ed6f0e6a3ee068502dbea3bf", + "node_modules/obliterator/permutations.js": "e6f5dd8dc472f30d1ce370a354022df24ac5bd009e4b49e96c1cd338a7faf9b0", + "node_modules/obliterator/power-set.d.ts": "fd208589eac8bae371ee22fba2194208ed3ed37b7094b32b688183af43870b75", + "node_modules/obliterator/power-set.js": "85f27369c6703122d235929445768b25f1a591c310ccbbce2e9870442962e29b", + "node_modules/obliterator/range.d.ts": "30d58514f7bd5f4b3f188659ef8fc8fec5ebf9e89040631f0804c8a3f00a0a93", + "node_modules/obliterator/range.js": "3c1cda1f9228af9ef6c118b6e9f3a473d60bba2b8b42670d0dfffa0dc13b80ba", + "node_modules/obliterator/split.d.ts": "0a1d6c77342e626de8a024d634bff708bc582d24d62e2dcf9d72b416abcd2f6e", + "node_modules/obliterator/split.js": "86f7b933f5938bd8eb999418c3d30b727901705d62837bff73d5409668cb7c69", + "node_modules/obliterator/take-into.d.ts": "4a1b5c8e4583eb21b1760b7d1b64ae4041eabfcd0be0f6e8289c1a41b18d2b89", + "node_modules/obliterator/take-into.js": "bddecf4474995fb639372e3272d8e3fd6ada5b2998a7ad27014be8a74eec7388", + "node_modules/obliterator/take.d.ts": "c4487bf9eefaeb08c982e129cd052acd14aabc9f2335cdb1f9c1f626ed6a57b6", + "node_modules/obliterator/take.js": "1f2a131d7d53e8a7a30f3f3195197a878b7fd86d3122f783eb7090c3eb41e3a9", + "node_modules/once/LICENSE": "4ec3d4c66cd87f5c8d8ad911b10f99bf27cb00cdfcff82621956e379186b016b", + "node_modules/once/README.md": "43b6d72bb23aff98af3243ab7e8d23d9af339ec46cdbdc81ded537c9899fa650", + "node_modules/once/once.js": "3db73e347ecbdbafdf8d0db85145c877e133f5372e767360ef90c09f70ac5103", + "node_modules/once/package.json": "33840d74c14c94bfb75c76374765b635531b1eacb88d7f1f2f380c94d0ea1328", + "node_modules/readable-stream/CONTRIBUTING.md": "56e4e12a6934a2c4d36c7bf893f4d8aefa6c96f9ffcec357dfa6476e36c4f1f5", + "node_modules/readable-stream/GOVERNANCE.md": "7a521e462d1c6f3b599c44637fb337bbf969dda311510a87236ec539a415331d", + "node_modules/readable-stream/LICENSE": "ec62dc96da0099b87f4511736c87309335527fb7031639493e06c95728dc8c54", + "node_modules/readable-stream/README.md": "93e374225fa62e8b1423059092bd614b6fe837315f3c10688362f20dfe2c54a4", + "node_modules/readable-stream/errors-browser.js": "346033597378d23e59068d120d6257f7cd85ae88c40b1f85c3329cece0d119c4", + "node_modules/readable-stream/errors.js": "7028cca95b2f124345bd5b816e0d8184e7b7d208fe0aa76eb38df43e8644fd03", + "node_modules/readable-stream/experimentalWarning.js": "5f9beff594347ba6765b806ec36e25699be1407627e2ee91be5d609e40baacb8", + "node_modules/readable-stream/lib/_stream_duplex.js": "e2e55263df344f33d016a3e051de1a86e2206989a2162951e651e5557665954c", + "node_modules/readable-stream/lib/_stream_passthrough.js": "af6250df6a49a0148eed8b5f2e28b2f692c3ffa9df538a59dc716a4bd10901e2", + "node_modules/readable-stream/lib/_stream_readable.js": "9837de8cae91f9a80da75368855b45c3ada9a858983355fd873d8885cab1c5ab", + "node_modules/readable-stream/lib/_stream_transform.js": "a2f6e6d17a74dfa75bc7b34b50546ebcb76eeb2f4aac6d1090f80915aee20342", + "node_modules/readable-stream/lib/_stream_writable.js": "0f3f2accecd343dfae91d7daa72f3ad125b6f139d5883ff51362042d8cb65cf6", + "node_modules/readable-stream/lib/internal/streams/async_iterator.js": "56a7dde0d36ee9f55032df01e78229602c6afed6b6915362f33dc65bc0359972", + "node_modules/readable-stream/lib/internal/streams/buffer_list.js": "15b266db6ad5c6ee0309d13ed4ce137d8974d2fd236c1af7f3d602b7028a90df", + "node_modules/readable-stream/lib/internal/streams/destroy.js": "e1c1a185a2cc4bee2be5e7d33b5e294eb0fb55c1a47b61853a8c43e50cb822a4", + "node_modules/readable-stream/lib/internal/streams/end-of-stream.js": "2c35cabc9b279cb81e73d0d14859b2056ea10d9688d16a12199d2ca9904b93ae", + "node_modules/readable-stream/lib/internal/streams/from-browser.js": "4b4404c7bd6f66a2175cb7a29c60ce4395c055775ec45eaa35af4357656f604f", + "node_modules/readable-stream/lib/internal/streams/from.js": "b7b5815f476b47a23d094ecfa4f1c53beca69d4f5a42ed452d133b79a1bf28ae", + "node_modules/readable-stream/lib/internal/streams/pipeline.js": "6d51b72e1ae5ee0965f0f549e27ececdb4963605995ffad8b698d5359a9999c2", + "node_modules/readable-stream/lib/internal/streams/state.js": "bf9c5ce1ccde2131d474422bef258dc968d6d19ccac72f9b178e8246faf0c9ed", + "node_modules/readable-stream/lib/internal/streams/stream-browser.js": "72d46a15491627d8fb1489a47d03583cfe5c21902918016ab532b53e615e5a9a", + "node_modules/readable-stream/lib/internal/streams/stream.js": "1e7903927df33aadb3659ecce55266c9c851da65ce6c8b723a60a305c1c5422c", + "node_modules/readable-stream/package.json": "469ea81f64037d1f179bcd46412217903a2924dd2e7d7d9b728659b6f12c3e69", + "node_modules/readable-stream/readable-browser.js": "c4ff3ea62ef65a2c68ea721dcbd58b621150660facd02be95ebfc556c4dd123f", + "node_modules/readable-stream/readable.js": "2944f1d3c8c5d5c5e07e7c30d6cbef5fc37440b7c73de47aeb37fa8424f04bf1", + "node_modules/safe-buffer/LICENSE": "c7cc929b57080f4b9d0c6cf57669f0463fc5b39906344dfc8d3bc43426b30eac", + "node_modules/safe-buffer/README.md": "d301a850808775718ddb510d4bf2a922d9b0afa72894e67137daa20fbeafac05", + "node_modules/safe-buffer/index.d.ts": "5e379df3d61561c2ed7789b5995b9ba2143bbba21a905e2381e16efe7d1fa424", + "node_modules/safe-buffer/index.js": "184d5c4a83512e3f49d4d5b52f7073f601f933bad11f1fbb3ad364ca5e6478e1", + "node_modules/safe-buffer/package.json": "e4018fdbfe625433371f48274fee26f520bbe865e0d8c85bf3aa07424134b90c", + "node_modules/safer-buffer/LICENSE": "4bc935e71be198c67ddf3c2b5fddb195f6edc182bfc155a96a6db61b44b494b9", + "node_modules/safer-buffer/Porting-Buffer.md": "134523d05b39e177135c2a2070a426eb319657dfc77589e38ef7cf13cd50e730", + "node_modules/safer-buffer/Readme.md": "d4331b7c5af9c02161150daf9451ee6bc7a73ece3ec0312603fae09f46a817d8", + "node_modules/safer-buffer/dangerous.js": "1dc2900bcc8ffc375af0d2fc99a03ecad2dcbb85d278fbf26982e87707bea53f", + "node_modules/safer-buffer/package.json": "4247a3a63c45a6a7ced1557fe4dd9d2a163af4915d594b95e5867b69a53edb52", + "node_modules/safer-buffer/safer.js": "37f9716144105051ab302f6a0c30a52ff5de7269b9e1173ac22be798fc11ccad", + "node_modules/safer-buffer/tests.js": "511906fdf6a233c84a9216065d1701dbfd84f380fbd495ae1cf5a002370b9885", + "node_modules/stream-shift/.github/workflows/test.yml": "362333ff93e95bed34ff78de0ec39287c23ab29a57eb2af98b4602237ed92a68", + "node_modules/stream-shift/LICENSE": "6a134e51aa31496c15a741592fc5d782b2dbf8d8b6b8524e15c8520ae0cd6374", + "node_modules/stream-shift/README.md": "731ceeb99dc3c505655cf148b264b080e2e552f1c68c487a41a94fe4e1711aff", + "node_modules/stream-shift/index.js": "070674cf0f6891c07cf10841e740a0d88545c17aaec6ef2caedb7d8dc74fe6d9", + "node_modules/stream-shift/package.json": "841d13128a438ed997496e142856d686f1c3230fa7f954d787bc2a2a3ec1cdf6", + "node_modules/stream-shift/test.js": "32ed537c9510245047ee7fb3ae824fc4a09630cd4d048ae78d9a4b47a327ca9b", + "node_modules/string_decoder/LICENSE": "11f2aafb37d06b3ee5bdaf06e9811141d0da05263c316f3d627f45c20d43261b", + "node_modules/string_decoder/README.md": "64417fec74bed1e4160c792189c85e6f37f4232a179e2beb04f32d80df76c4cf", + "node_modules/string_decoder/lib/string_decoder.js": "f1d36d47b2c579063392c1a68963467f2d4f51a069af09eb068d974c63ee3b37", + "node_modules/string_decoder/package.json": "31984c85cd4f3df0aa75075acdec98c25961496897d1f46d04ed4a701f129c54", + "node_modules/tslib/CopyrightNotice.txt": "da16ddb65f8ca390998fb99223d0112498b56b45784d00afd77ff8ce1ac4de8b", + "node_modules/tslib/LICENSE.txt": "210b19e543130388c68654b7497e967119ce17145f66ab7d85688fbd70f08751", + "node_modules/tslib/README.md": "d2e199aa7eafd807ba6c3c31edaf06cc06712f30ef965687e22d315b9e8f0543", + "node_modules/tslib/SECURITY.md": "89c4e4b9ba7ec705482e2de05135d0626d33bf8c2c66119c65d7a306dd83459c", + "node_modules/tslib/modules/index.d.ts": "b8f34dd1757f68e03262b1ca3ddfa668a855b872f8bdd5224d6f993a7b37dc2c", + "node_modules/tslib/modules/index.js": "b3a4765da2bc019c79f2c2c12b36f860e946119d976897f1ce945caf227d0468", + "node_modules/tslib/modules/package.json": "f2a77d1fa2e7ee710b2835b2933d75d9c7f5dad4f1bab69e1679876e32b5e9eb", + "node_modules/tslib/package.json": "f02b4851df316f29bdfcd404aa1eb41e4e216b5859015f81fcd3e91caf8be931", + "node_modules/tslib/tslib.d.ts": "a6a5253138c5432c68a1510c70fe78a644fe2e632111ba778e1978010d6edfec", + "node_modules/tslib/tslib.es6.html": "759c26504a9ccbbfcd730269c9c5ef15512cf16ba617e61a92212365e57c9f5d", + "node_modules/tslib/tslib.es6.js": "480042d65f5abdacdcf9d9ade38f9d484455be82aa34bd472bf6ab87ea0e3620", + "node_modules/tslib/tslib.es6.mjs": "0929993220938091ede542d688b0fdb87dad99a7e77678e1983c32d18a503339", + "node_modules/tslib/tslib.html": "e14179f388a9690437165289d45ff7747f0f839538b5abec63d2dcfe21c2a5f4", + "node_modules/tslib/tslib.js": "8855865a058bc0a6df8f5db45347be041a2d6bbe1654216c51a805648c1b6e8a", + "node_modules/undici-types/LICENSE": "a6db8096b2707bc0102d256917d4d33f298ba36d8c3f25de067a2b5bb379db27", + "node_modules/undici-types/README.md": "c8cced6ec588c55b07e35733258cb7d6b63e3426ee7490aa9267aab4bda0fb20", + "node_modules/undici-types/agent.d.ts": "a70af845a2eb9dd6e2723e319e14ea7fb28b129ec1361c21509b49305448c323", + "node_modules/undici-types/api.d.ts": "8aee8b6d4f9f62cf3776cda1305fb18763e2aade7e13cea5bbe699112df85214", + "node_modules/undici-types/balanced-pool.d.ts": "4d327f7d72ad0918275cea3eee49a6a8dc8114ae1d5b7f3f5d0774de75f7439a", + "node_modules/undici-types/cache-interceptor.d.ts": "98498b101803bb3dde9f76a56e65c14b75db1cc8bec5f4db72be541570f74fc5", + "node_modules/undici-types/cache.d.ts": "c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605", + "node_modules/undici-types/client-stats.d.ts": "68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf", + "node_modules/undici-types/client.d.ts": "60e3ba4b2d24548107060e5d13bffafbbbaa53d001c07bbe40386deda569b5e8", + "node_modules/undici-types/connector.d.ts": "d3c85d4dde1a4918009e37426ee4a940cbbd4100e2e74d03e2de0d50a77bb488", + "node_modules/undici-types/content-type.d.ts": "2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6", + "node_modules/undici-types/cookies.d.ts": "013ff793053d33a15be4614efdd6c50247bbf0b26d6bc58ba338622a7441b543", + "node_modules/undici-types/diagnostics-channel.d.ts": "12950411eeab8563b349cb7959543d92d8d02c289ed893d78499a19becb5a8cc", + "node_modules/undici-types/dispatcher.d.ts": "25bd6ae1b97ea991433337310fc406cc7c9f271b67b7c79dd24eee384759f3d4", + "node_modules/undici-types/dispatcher1-wrapper.d.ts": "b53dc572d4f187904207ae1166652de47aab8eeb00c254d009cb226863076b56", + "node_modules/undici-types/env-http-proxy-agent.d.ts": "80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e", + "node_modules/undici-types/errors.d.ts": "a712be2c8cbe0154bd17d1d7bff503d7c04331e0745eb9eea3d303e0e5d9401e", + "node_modules/undici-types/eventsource.d.ts": "adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae", + "node_modules/undici-types/fetch.d.ts": "21d7fab00c101ddf754b392e11534e6b20c6bda269b2ccbcdf3453cbe21d8a39", + "node_modules/undici-types/formdata.d.ts": "615754924717c0b1e293e083b83503c0a872717ad5aa60ed7f1a699eb1b4ea5c", + "node_modules/undici-types/global-dispatcher.d.ts": "ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d", + "node_modules/undici-types/global-origin.d.ts": "36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59", + "node_modules/undici-types/h2c-client.d.ts": "e8a9dfa4c75ef6d25df8b40eaa9c31e0a69452aaf2ced4a3f4215dbdbaa876f4", + "node_modules/undici-types/handlers.d.ts": "4ca7fb8f6018ea836b1ad24f67d5e7c2cf7410af7462e0ccc90b012944f5d7ae", + "node_modules/undici-types/header.d.ts": "88809d6c1b9c78d04a133646a6feb926def05a8774c308c7c93bc32ee163d271", + "node_modules/undici-types/index.d.ts": "e99963ae1e3a48ca7a7958c02f3e88bb963eb7978c28b68ae6b8c9f03309d83c", + "node_modules/undici-types/interceptors.d.ts": "706abfbafe0ed5f685357375e89bbf48879549ea1772185d77d15f587c5c9400", + "node_modules/undici-types/mock-agent.d.ts": "9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0", + "node_modules/undici-types/mock-call-history.d.ts": "0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a", + "node_modules/undici-types/mock-client.d.ts": "40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca", + "node_modules/undici-types/mock-errors.d.ts": "40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b", + "node_modules/undici-types/mock-interceptor.d.ts": "57354d2f022ed64ede57b2e052306dd076a138b551abc7d30ef5e6718ce2f76a", + "node_modules/undici-types/mock-pool.d.ts": "b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3", + "node_modules/undici-types/package.json": "7f3c1a0498b1b7f74402f76d2958bf5a35fc89403036188dc5f8ede1b4e0638b", + "node_modules/undici-types/patch.d.ts": "addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d", + "node_modules/undici-types/pool-stats.d.ts": "914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60", + "node_modules/undici-types/pool.d.ts": "38004e6801340cb890afb8cb5a9fc8972297e7f88ab94026e4b0b3c61fb32f8a", + "node_modules/undici-types/proxy-agent.d.ts": "358f74dc35927dc88ea86dd201a2b98e701c281615e6b30a4c153eb0bb90ef71", + "node_modules/undici-types/readable.d.ts": "156a859e21ef3244d13afeeba4e49760a6afa035c149dda52f0c45ea8903b338", + "node_modules/undici-types/retry-agent.d.ts": "499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53", + "node_modules/undici-types/retry-handler.d.ts": "f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391", + "node_modules/undici-types/round-robin-pool.d.ts": "149f9a9e7f04e67afa0e2a49fc0ff421035c01d6b793cfcae7d2e9f6819431e2", + "node_modules/undici-types/snapshot-agent.d.ts": "f501234c5aeeeb5d7659412335227466aaacf30b952372d60afeb21c02c96348", + "node_modules/undici-types/socks5-proxy-agent.d.ts": "8b91ff5bb912be3ea213cbcf0075aace1f5d4ff249a0d227ed673868cb7bfabc", + "node_modules/undici-types/util.d.ts": "5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401", + "node_modules/undici-types/utility.d.ts": "cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c", + "node_modules/undici-types/webidl.d.ts": "9d8e1ed7d67b6fde015da61316157b18cb84518cccc95da7e5545c1ab74a955b", + "node_modules/undici-types/websocket.d.ts": "436d7b4543b340b0f3eef4310d524242e41369b9652aa9c70428767c4dcac455", + "node_modules/util-deprecate/History.md": "ab183ff93d075137547f9d96938185f2cd4b29ba1f4e28b29e6fbd573255968d", + "node_modules/util-deprecate/LICENSE": "0154425673db15cdfa80ecba2c9b1f1a867f7197a006764712849bfc3a93cbb7", + "node_modules/util-deprecate/README.md": "d30a5eca847bc6edc7b9723884def8942a66f89d12a5735dc22d1ee1216ff2ea", + "node_modules/util-deprecate/browser.js": "abaef53ebcdc98f3655331f834986a2d11b6a824f58d670369669246d1b7b69b", + "node_modules/util-deprecate/node.js": "9a86a29fa34a99b861e707345fb1d1e2e55a6c23edb8f992bed57cc607f42d8e", + "node_modules/util-deprecate/package.json": "7bba467f049074957e693fc06672848b040c38fa071b6eed8690f5fbe090a8b2", + "node_modules/uuid/LICENSE.md": "beaa6b04fb82e41dd2ad679e19e27953afb5999b1abbb455b6564e78ebfeb332", + "node_modules/uuid/README.md": "b2ba3cb5eb8ee5948c0892af69ac15f0baa39069000169fc4f50aceec1d56087", + "node_modules/uuid/dist/cjs-browser/index.d.ts": "badcc9d59770b91987e962f8e3ddfa1e06671b0e4c5e2738bbd002255cad3f38", + "node_modules/uuid/dist/cjs-browser/index.js": "22b5ab1089b5c33049fda7774213bc442dcbb455b083c4b6250d9eaa3d3dca0e", + "node_modules/uuid/dist/cjs-browser/max.d.ts": "6ada175c0c585e89569e8feb8ff6fc9fc443d7f9ca6340b456e0f94cbef559bf", + "node_modules/uuid/dist/cjs-browser/max.js": "d163efcf7f2dab3aac090e475d23b70f55e17d7d9857b62b2b5376b04a01d78c", + "node_modules/uuid/dist/cjs-browser/md5.d.ts": "3d0c4240e03f66fc182842870b015a1621c8e1a512d3cf9f54335427ac0b0f26", + "node_modules/uuid/dist/cjs-browser/md5.js": "8a6f91e5417fb15267144cdccc2cc2d2fde34f4c75ef189fb7003e3da47a2657", + "node_modules/uuid/dist/cjs-browser/native.d.ts": "70bcbc7c5e6c575eb4e289889c38915e2890bac342bc5b5200959e8cba158e7f", + "node_modules/uuid/dist/cjs-browser/native.js": "6fe54016411b3aa36f1c57df146e0177b2609dbac521375988cd1f5dd3c8d404", + "node_modules/uuid/dist/cjs-browser/nil.d.ts": "e56e4d95fad615c97eb0ae39c329a4cda9c0af178273a9173676cc9b14b58520", + "node_modules/uuid/dist/cjs-browser/nil.js": "6565dd5b2084d594da0ed0d37f31499b6cdc55ca73ba6c46dd8417efcb558932", + "node_modules/uuid/dist/cjs-browser/package.json": "dbf8353f77358bc12169b7bb7301e1978d5b503e002ee927229a8993672818fc", + "node_modules/uuid/dist/cjs-browser/parse.d.ts": "73e8dfd5e7d2abc18bdb5c5873e64dbdd1082408dd1921cad6ff7130d8339334", + "node_modules/uuid/dist/cjs-browser/parse.js": "59be9952d92d9939a08cd89d769336116716ea45148fb169010d9d00d0147d6f", + "node_modules/uuid/dist/cjs-browser/regex.d.ts": "f972c1a3ed268843b93ccca541c48a3265ced25031ce6bef2fe1f9de10c4d345", + "node_modules/uuid/dist/cjs-browser/regex.js": "2f77679506ef5d2770f7a5889fc724f9cb6ea265f2316654f8ba0d2fab200b67", + "node_modules/uuid/dist/cjs-browser/rng.d.ts": "43b8bf0182f6c141c27b92470c429aa455ea9613e7ff8ca6f157efb94ea16375", + "node_modules/uuid/dist/cjs-browser/rng.js": "f4e9b595f0c9d739ac6e6d0dddbaba19dfaf1ac468bbc8f6095c57c1cda34b57", + "node_modules/uuid/dist/cjs-browser/sha1.d.ts": "b3c0b2e75f331b8a2deaae04bd2695d59952cfe43e324a51e1e2ad41f74ce7c2", + "node_modules/uuid/dist/cjs-browser/sha1.js": "afb8823f62c6e55f592a415d3ab3f6e9997d5d38cbec8cfb077ae397e9c377da", + "node_modules/uuid/dist/cjs-browser/stringify.d.ts": "fc820b2f0c21501f51f79b58a21d3fa7ae5659fc1812784dbfbb72af147659ee", + "node_modules/uuid/dist/cjs-browser/stringify.js": "154caa4a1bef53d3bb336c0451bd42c87b38bed56bbd32c51ff09d25664251cb", + "node_modules/uuid/dist/cjs-browser/types.d.ts": "cff399d99c68e4fafdd5835d443a980622267a39ac6f3f59b9e3d60d60c4f133", + "node_modules/uuid/dist/cjs-browser/types.js": "d43aa81f5bc89faa359e0f97c814ba25155591ff078fbb9bfd40f8c7c9683230", + "node_modules/uuid/dist/cjs-browser/uuid-bin.d.ts": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/uuid/dist/cjs-browser/uuid-bin.js": "ceb9ce9ab227beb22625b874783f598f48ef70a117f306e2e7ba641c9237a316", + "node_modules/uuid/dist/cjs-browser/v1.d.ts": "4f041ef66167b5f9c73101e5fd8468774b09429932067926f9b2960cc3e4f99d", + "node_modules/uuid/dist/cjs-browser/v1.js": "ab103eb50addc408f0e773ee1e112e61ff10e8bf4fff54a1ce6d58c03cb6809d", + "node_modules/uuid/dist/cjs-browser/v1ToV6.d.ts": "31501b8fc4279e78f6a05ca35e365e73c0b0c57d06dbe8faecb10c7254ce7714", + "node_modules/uuid/dist/cjs-browser/v1ToV6.js": "104bb70bb256d18d3a25fb392462296f0440a0f3e0dd6d3d5f6600eddb312692", + "node_modules/uuid/dist/cjs-browser/v3.d.ts": "ff4e9db3eb1e95d7ba4b5765e4dc7f512b90fb3b588adfd5ca9b0d9d7a56a1ae", + "node_modules/uuid/dist/cjs-browser/v3.js": "8a2536068ae46c8de0bf92285ae2042907cc48036fe16221394960ff502d8a29", + "node_modules/uuid/dist/cjs-browser/v35.d.ts": "7bc76e7d4bbe3764abaf054aed3a622c5cdbac694e474050d71ce9d4ab93ea4b", + "node_modules/uuid/dist/cjs-browser/v35.js": "9691294997fec5c0ebefbabef0f81413932f23d49a89fb03d44ae98e8d27a155", + "node_modules/uuid/dist/cjs-browser/v4.d.ts": "f205fd03cd15ea054f7006b7ef8378ef29c315149da0726f4928d291e7dce7b9", + "node_modules/uuid/dist/cjs-browser/v4.js": "6b50a22ef711d3028fa7231543d10f44c7bc07cf55913e027f24aa153cc1ac5d", + "node_modules/uuid/dist/cjs-browser/v5.d.ts": "d683908557d53abeb1b94747e764b3bd6b6226273514b96a942340e9ce4b7be7", + "node_modules/uuid/dist/cjs-browser/v5.js": "3af0404ae46e84fb34b0e2ccedac1595f0a6f8f609572fb4304da377625ead6f", + "node_modules/uuid/dist/cjs-browser/v6.d.ts": "7c6d5704e2f236fddaf8dbe9131d998a4f5132609ef795b78c3b63f46317f88a", + "node_modules/uuid/dist/cjs-browser/v6.js": "eb3c940e0c2de0c7cc9fdb7487fff7d5b041cb8cf4c2a20bc115385cafc27d2f", + "node_modules/uuid/dist/cjs-browser/v6ToV1.d.ts": "d05bd4d28c12545827349b0ac3a79c50658d68147dad38d13e97e22353544496", + "node_modules/uuid/dist/cjs-browser/v6ToV1.js": "fde1e8251afa5b782b903bae7b17b410819c3e8421460b6fffeec399fc6cf086", + "node_modules/uuid/dist/cjs-browser/v7.d.ts": "b6436d90a5487d9b3c3916b939f68e43f7eaca4b0bb305d897d5124180a122b9", + "node_modules/uuid/dist/cjs-browser/v7.js": "e9f3c349fb16ad9fa90fd5de40edb492ea0ee02d003fe0719d198f4dfcbd992f", + "node_modules/uuid/dist/cjs-browser/validate.d.ts": "04ace6bedd6f59c30ea6df1f0f8d432c728c8bc5c5fd0c5c1c80242d3ab51977", + "node_modules/uuid/dist/cjs-browser/validate.js": "da5822aa41665cdf2ddd41dbef18fd9d78c48ec819c18a707a73c15c7f6254ea", + "node_modules/uuid/dist/cjs-browser/version.d.ts": "57a8a7772769c35ba7b4b1ba125f0812deec5c7102a0d04d9e15b1d22880c9e8", + "node_modules/uuid/dist/cjs-browser/version.js": "850710c3f67fa6177066ba792ddaa865a98c83516ebb3a34f4ad269a5f3c0346", + "node_modules/uuid/dist/cjs/index.d.ts": "badcc9d59770b91987e962f8e3ddfa1e06671b0e4c5e2738bbd002255cad3f38", + "node_modules/uuid/dist/cjs/index.js": "22b5ab1089b5c33049fda7774213bc442dcbb455b083c4b6250d9eaa3d3dca0e", + "node_modules/uuid/dist/cjs/max.d.ts": "6ada175c0c585e89569e8feb8ff6fc9fc443d7f9ca6340b456e0f94cbef559bf", + "node_modules/uuid/dist/cjs/max.js": "d163efcf7f2dab3aac090e475d23b70f55e17d7d9857b62b2b5376b04a01d78c", + "node_modules/uuid/dist/cjs/md5.d.ts": "6c5e48874d739800891173d98f8fa6d49762d46b412300cf78d875f20c19d4d1", + "node_modules/uuid/dist/cjs/md5.js": "c390019956471f76354d75b61701a4710e096377b89cadef354fe6ba0ce8bee2", + "node_modules/uuid/dist/cjs/native.d.ts": "8f8088eaf8cb36a3c0edeccbc8ba4032f2e36608b114a3e688d05d0bb9fe9167", + "node_modules/uuid/dist/cjs/native.js": "951f4974e6f2ddeac9e02133b73c742e106f895e7efeac3e32cb36f630f23450", + "node_modules/uuid/dist/cjs/nil.d.ts": "e56e4d95fad615c97eb0ae39c329a4cda9c0af178273a9173676cc9b14b58520", + "node_modules/uuid/dist/cjs/nil.js": "6565dd5b2084d594da0ed0d37f31499b6cdc55ca73ba6c46dd8417efcb558932", + "node_modules/uuid/dist/cjs/package.json": "dbf8353f77358bc12169b7bb7301e1978d5b503e002ee927229a8993672818fc", + "node_modules/uuid/dist/cjs/parse.d.ts": "73e8dfd5e7d2abc18bdb5c5873e64dbdd1082408dd1921cad6ff7130d8339334", + "node_modules/uuid/dist/cjs/parse.js": "59be9952d92d9939a08cd89d769336116716ea45148fb169010d9d00d0147d6f", + "node_modules/uuid/dist/cjs/regex.d.ts": "f972c1a3ed268843b93ccca541c48a3265ced25031ce6bef2fe1f9de10c4d345", + "node_modules/uuid/dist/cjs/regex.js": "2f77679506ef5d2770f7a5889fc724f9cb6ea265f2316654f8ba0d2fab200b67", + "node_modules/uuid/dist/cjs/rng.d.ts": "43b8bf0182f6c141c27b92470c429aa455ea9613e7ff8ca6f157efb94ea16375", + "node_modules/uuid/dist/cjs/rng.js": "8a1485ba1fe28d4d6ca5b41a30b522da73b3b9e566e8798de29a1fcd5e9d2ec2", + "node_modules/uuid/dist/cjs/sha1.d.ts": "e61542c6f4b0164a579a57d2bddc684806d79d206c25bf2b1f04a9dfa8839d4a", + "node_modules/uuid/dist/cjs/sha1.js": "82007a13729350dfeec44bdcbe299e5e9fd1ed7567e3d799f797bd84d0e5f4f7", + "node_modules/uuid/dist/cjs/stringify.d.ts": "fc820b2f0c21501f51f79b58a21d3fa7ae5659fc1812784dbfbb72af147659ee", + "node_modules/uuid/dist/cjs/stringify.js": "154caa4a1bef53d3bb336c0451bd42c87b38bed56bbd32c51ff09d25664251cb", + "node_modules/uuid/dist/cjs/types.d.ts": "cff399d99c68e4fafdd5835d443a980622267a39ac6f3f59b9e3d60d60c4f133", + "node_modules/uuid/dist/cjs/types.js": "d43aa81f5bc89faa359e0f97c814ba25155591ff078fbb9bfd40f8c7c9683230", + "node_modules/uuid/dist/cjs/uuid-bin.d.ts": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/uuid/dist/cjs/uuid-bin.js": "ceb9ce9ab227beb22625b874783f598f48ef70a117f306e2e7ba641c9237a316", + "node_modules/uuid/dist/cjs/v1.d.ts": "4f041ef66167b5f9c73101e5fd8468774b09429932067926f9b2960cc3e4f99d", + "node_modules/uuid/dist/cjs/v1.js": "ab103eb50addc408f0e773ee1e112e61ff10e8bf4fff54a1ce6d58c03cb6809d", + "node_modules/uuid/dist/cjs/v1ToV6.d.ts": "31501b8fc4279e78f6a05ca35e365e73c0b0c57d06dbe8faecb10c7254ce7714", + "node_modules/uuid/dist/cjs/v1ToV6.js": "104bb70bb256d18d3a25fb392462296f0440a0f3e0dd6d3d5f6600eddb312692", + "node_modules/uuid/dist/cjs/v3.d.ts": "ff4e9db3eb1e95d7ba4b5765e4dc7f512b90fb3b588adfd5ca9b0d9d7a56a1ae", + "node_modules/uuid/dist/cjs/v3.js": "8a2536068ae46c8de0bf92285ae2042907cc48036fe16221394960ff502d8a29", + "node_modules/uuid/dist/cjs/v35.d.ts": "7bc76e7d4bbe3764abaf054aed3a622c5cdbac694e474050d71ce9d4ab93ea4b", + "node_modules/uuid/dist/cjs/v35.js": "9691294997fec5c0ebefbabef0f81413932f23d49a89fb03d44ae98e8d27a155", + "node_modules/uuid/dist/cjs/v4.d.ts": "f205fd03cd15ea054f7006b7ef8378ef29c315149da0726f4928d291e7dce7b9", + "node_modules/uuid/dist/cjs/v4.js": "6b50a22ef711d3028fa7231543d10f44c7bc07cf55913e027f24aa153cc1ac5d", + "node_modules/uuid/dist/cjs/v5.d.ts": "d683908557d53abeb1b94747e764b3bd6b6226273514b96a942340e9ce4b7be7", + "node_modules/uuid/dist/cjs/v5.js": "3af0404ae46e84fb34b0e2ccedac1595f0a6f8f609572fb4304da377625ead6f", + "node_modules/uuid/dist/cjs/v6.d.ts": "7c6d5704e2f236fddaf8dbe9131d998a4f5132609ef795b78c3b63f46317f88a", + "node_modules/uuid/dist/cjs/v6.js": "eb3c940e0c2de0c7cc9fdb7487fff7d5b041cb8cf4c2a20bc115385cafc27d2f", + "node_modules/uuid/dist/cjs/v6ToV1.d.ts": "d05bd4d28c12545827349b0ac3a79c50658d68147dad38d13e97e22353544496", + "node_modules/uuid/dist/cjs/v6ToV1.js": "fde1e8251afa5b782b903bae7b17b410819c3e8421460b6fffeec399fc6cf086", + "node_modules/uuid/dist/cjs/v7.d.ts": "b6436d90a5487d9b3c3916b939f68e43f7eaca4b0bb305d897d5124180a122b9", + "node_modules/uuid/dist/cjs/v7.js": "e9f3c349fb16ad9fa90fd5de40edb492ea0ee02d003fe0719d198f4dfcbd992f", + "node_modules/uuid/dist/cjs/validate.d.ts": "04ace6bedd6f59c30ea6df1f0f8d432c728c8bc5c5fd0c5c1c80242d3ab51977", + "node_modules/uuid/dist/cjs/validate.js": "da5822aa41665cdf2ddd41dbef18fd9d78c48ec819c18a707a73c15c7f6254ea", + "node_modules/uuid/dist/cjs/version.d.ts": "57a8a7772769c35ba7b4b1ba125f0812deec5c7102a0d04d9e15b1d22880c9e8", + "node_modules/uuid/dist/cjs/version.js": "850710c3f67fa6177066ba792ddaa865a98c83516ebb3a34f4ad269a5f3c0346", + "node_modules/uuid/dist/esm-browser/index.d.ts": "badcc9d59770b91987e962f8e3ddfa1e06671b0e4c5e2738bbd002255cad3f38", + "node_modules/uuid/dist/esm-browser/index.js": "65e9fae8f22278e6c9ac5b016dbe1ead01b8014f66ce08a807741885d4bf01f1", + "node_modules/uuid/dist/esm-browser/max.d.ts": "6ada175c0c585e89569e8feb8ff6fc9fc443d7f9ca6340b456e0f94cbef559bf", + "node_modules/uuid/dist/esm-browser/max.js": "8f81a7b2a5f8704ea1b4e8f83d36659856fdf3d122b2bd16fe1b5c47c97b6ffa", + "node_modules/uuid/dist/esm-browser/md5.d.ts": "3d0c4240e03f66fc182842870b015a1621c8e1a512d3cf9f54335427ac0b0f26", + "node_modules/uuid/dist/esm-browser/md5.js": "15784bb25da507dd4645984534ff773c6181ab58eb7d71023f538318c957eca3", + "node_modules/uuid/dist/esm-browser/native.d.ts": "70bcbc7c5e6c575eb4e289889c38915e2890bac342bc5b5200959e8cba158e7f", + "node_modules/uuid/dist/esm-browser/native.js": "b6dfd1a72e81f1528c37a4ca8eb2e085d42c353dc098f610a18cd66358d8bb39", + "node_modules/uuid/dist/esm-browser/nil.d.ts": "e56e4d95fad615c97eb0ae39c329a4cda9c0af178273a9173676cc9b14b58520", + "node_modules/uuid/dist/esm-browser/nil.js": "a6f8ac293df0b75af2946e5190d8f00336b826bb1340b96d669a09beb8885ff8", + "node_modules/uuid/dist/esm-browser/parse.d.ts": "73e8dfd5e7d2abc18bdb5c5873e64dbdd1082408dd1921cad6ff7130d8339334", + "node_modules/uuid/dist/esm-browser/parse.js": "a383f50a691c05533a40bbb4a87f718daa050abbc96debc9715efdc74d8538b4", + "node_modules/uuid/dist/esm-browser/regex.d.ts": "f972c1a3ed268843b93ccca541c48a3265ced25031ce6bef2fe1f9de10c4d345", + "node_modules/uuid/dist/esm-browser/regex.js": "774bc4bc767a58458dfdd252430d96b71b3d380ac241b50df6cb757aaa1b4783", + "node_modules/uuid/dist/esm-browser/rng.d.ts": "43b8bf0182f6c141c27b92470c429aa455ea9613e7ff8ca6f157efb94ea16375", + "node_modules/uuid/dist/esm-browser/rng.js": "c77577812df80cdfd9401a90736a3f4fcf428bd85219ea0977d515949e2cfb18", + "node_modules/uuid/dist/esm-browser/sha1.d.ts": "b3c0b2e75f331b8a2deaae04bd2695d59952cfe43e324a51e1e2ad41f74ce7c2", + "node_modules/uuid/dist/esm-browser/sha1.js": "417afd0d7f9695f9b6facf84fbe17bd8e9cb1eed04893ecf1e0c241d13a622d2", + "node_modules/uuid/dist/esm-browser/stringify.d.ts": "fc820b2f0c21501f51f79b58a21d3fa7ae5659fc1812784dbfbb72af147659ee", + "node_modules/uuid/dist/esm-browser/stringify.js": "bb592b599f2b00be6ca9fb2ac2fb7350abf3a6e6e274201cf6865c8c15e52b6a", + "node_modules/uuid/dist/esm-browser/types.d.ts": "cff399d99c68e4fafdd5835d443a980622267a39ac6f3f59b9e3d60d60c4f133", + "node_modules/uuid/dist/esm-browser/types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/uuid/dist/esm-browser/uuid-bin.d.ts": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/uuid/dist/esm-browser/uuid-bin.js": "daa130ca926ffac9d538d47e82c7b9bbbd894006ab8a90dead255c21c58233cb", + "node_modules/uuid/dist/esm-browser/v1.d.ts": "4f041ef66167b5f9c73101e5fd8468774b09429932067926f9b2960cc3e4f99d", + "node_modules/uuid/dist/esm-browser/v1.js": "a3cd1c783a64f6e010af00593ac8e7e28c127a5e63266da4809b5687f1a081aa", + "node_modules/uuid/dist/esm-browser/v1ToV6.d.ts": "31501b8fc4279e78f6a05ca35e365e73c0b0c57d06dbe8faecb10c7254ce7714", + "node_modules/uuid/dist/esm-browser/v1ToV6.js": "f0a2d19ee0d4398607808fcfc3eebeb1ea7af08a0c17d71d42fc612f97241a3f", + "node_modules/uuid/dist/esm-browser/v3.d.ts": "ff4e9db3eb1e95d7ba4b5765e4dc7f512b90fb3b588adfd5ca9b0d9d7a56a1ae", + "node_modules/uuid/dist/esm-browser/v3.js": "3b65ad31501886af057e13aeec03afedc54f1f85cc8d332b0eb89800b11fbce4", + "node_modules/uuid/dist/esm-browser/v35.d.ts": "7bc76e7d4bbe3764abaf054aed3a622c5cdbac694e474050d71ce9d4ab93ea4b", + "node_modules/uuid/dist/esm-browser/v35.js": "cd9bdff5aca247c93c0c7bfa3bddbd022076aebb8759ca00004dc980f38901d8", + "node_modules/uuid/dist/esm-browser/v4.d.ts": "f205fd03cd15ea054f7006b7ef8378ef29c315149da0726f4928d291e7dce7b9", + "node_modules/uuid/dist/esm-browser/v4.js": "467ed3f76fe8065eb46dd027c3e5c78501df95cdb99ff6ada1701fa3de024a3e", + "node_modules/uuid/dist/esm-browser/v5.d.ts": "d683908557d53abeb1b94747e764b3bd6b6226273514b96a942340e9ce4b7be7", + "node_modules/uuid/dist/esm-browser/v5.js": "daaf09616ea3b145ccd6a1dfea190b65d546267a9916c5741e026d4290c61ae9", + "node_modules/uuid/dist/esm-browser/v6.d.ts": "7c6d5704e2f236fddaf8dbe9131d998a4f5132609ef795b78c3b63f46317f88a", + "node_modules/uuid/dist/esm-browser/v6.js": "32f4c875353e043cb8d790f2d63d46e7b23d592fa3d5d09ace6afcff1dd44e03", + "node_modules/uuid/dist/esm-browser/v6ToV1.d.ts": "d05bd4d28c12545827349b0ac3a79c50658d68147dad38d13e97e22353544496", + "node_modules/uuid/dist/esm-browser/v6ToV1.js": "78475a282beaea6e8c45e5f2586134929be2e21728f159fb8559df06d7288475", + "node_modules/uuid/dist/esm-browser/v7.d.ts": "b6436d90a5487d9b3c3916b939f68e43f7eaca4b0bb305d897d5124180a122b9", + "node_modules/uuid/dist/esm-browser/v7.js": "38208cc85d447b13d7fc9e75377d4f82e17ad982334786396623c57c1406a257", + "node_modules/uuid/dist/esm-browser/validate.d.ts": "04ace6bedd6f59c30ea6df1f0f8d432c728c8bc5c5fd0c5c1c80242d3ab51977", + "node_modules/uuid/dist/esm-browser/validate.js": "78ba6a91a3c2d219e3a6980d7704c0998e8152cabcaece0f5a04989025d0e60c", + "node_modules/uuid/dist/esm-browser/version.d.ts": "57a8a7772769c35ba7b4b1ba125f0812deec5c7102a0d04d9e15b1d22880c9e8", + "node_modules/uuid/dist/esm-browser/version.js": "5a43d6eb8496806fd5e152f92edda603dbb1f77108045edcf034f22c9e320ce8", + "node_modules/uuid/dist/esm/bin/uuid": "a9dc67d05c3b08842280064e7e65fab9de3f4257b1dfd88e6e51d5afd967bdf4", + "node_modules/uuid/dist/esm/index.d.ts": "badcc9d59770b91987e962f8e3ddfa1e06671b0e4c5e2738bbd002255cad3f38", + "node_modules/uuid/dist/esm/index.js": "65e9fae8f22278e6c9ac5b016dbe1ead01b8014f66ce08a807741885d4bf01f1", + "node_modules/uuid/dist/esm/max.d.ts": "6ada175c0c585e89569e8feb8ff6fc9fc443d7f9ca6340b456e0f94cbef559bf", + "node_modules/uuid/dist/esm/max.js": "8f81a7b2a5f8704ea1b4e8f83d36659856fdf3d122b2bd16fe1b5c47c97b6ffa", + "node_modules/uuid/dist/esm/md5.d.ts": "2286d2879906f2fe64438d039c9951f8709148b8bee35b485efe96783aaf38f6", + "node_modules/uuid/dist/esm/md5.js": "4638778ad2e2fb5b9d06d545adc24f1489789d9883fa1fb12ee50a06ee05e91c", + "node_modules/uuid/dist/esm/native.d.ts": "df16822806d73d5ec3570f50bd882a701d52042d99e29f2135a4d335a5df12fc", + "node_modules/uuid/dist/esm/native.js": "eae9051ed058565dfd3e6508405f7d3c280fb395dc77c71b15e251c1f9e6fad8", + "node_modules/uuid/dist/esm/nil.d.ts": "e56e4d95fad615c97eb0ae39c329a4cda9c0af178273a9173676cc9b14b58520", + "node_modules/uuid/dist/esm/nil.js": "a6f8ac293df0b75af2946e5190d8f00336b826bb1340b96d669a09beb8885ff8", + "node_modules/uuid/dist/esm/parse.d.ts": "73e8dfd5e7d2abc18bdb5c5873e64dbdd1082408dd1921cad6ff7130d8339334", + "node_modules/uuid/dist/esm/parse.js": "a383f50a691c05533a40bbb4a87f718daa050abbc96debc9715efdc74d8538b4", + "node_modules/uuid/dist/esm/regex.d.ts": "f972c1a3ed268843b93ccca541c48a3265ced25031ce6bef2fe1f9de10c4d345", + "node_modules/uuid/dist/esm/regex.js": "774bc4bc767a58458dfdd252430d96b71b3d380ac241b50df6cb757aaa1b4783", + "node_modules/uuid/dist/esm/rng.d.ts": "43b8bf0182f6c141c27b92470c429aa455ea9613e7ff8ca6f157efb94ea16375", + "node_modules/uuid/dist/esm/rng.js": "60ce4fc657149e0b5d3c2c06dce4c92338691247e71e06430a5543d91914579e", + "node_modules/uuid/dist/esm/sha1.d.ts": "95a32839b0653a5fcf429ee872b22af538416435d0cbe6b1b5de63bd14076e39", + "node_modules/uuid/dist/esm/sha1.js": "c0c5b3c9150a2ae9c2e3b2eaaa13309d9d37981f1111e00a53fa1ab3bb449cbe", + "node_modules/uuid/dist/esm/stringify.d.ts": "fc820b2f0c21501f51f79b58a21d3fa7ae5659fc1812784dbfbb72af147659ee", + "node_modules/uuid/dist/esm/stringify.js": "bb592b599f2b00be6ca9fb2ac2fb7350abf3a6e6e274201cf6865c8c15e52b6a", + "node_modules/uuid/dist/esm/types.d.ts": "cff399d99c68e4fafdd5835d443a980622267a39ac6f3f59b9e3d60d60c4f133", + "node_modules/uuid/dist/esm/types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/uuid/dist/esm/uuid-bin.d.ts": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "node_modules/uuid/dist/esm/uuid-bin.js": "daa130ca926ffac9d538d47e82c7b9bbbd894006ab8a90dead255c21c58233cb", + "node_modules/uuid/dist/esm/v1.d.ts": "4f041ef66167b5f9c73101e5fd8468774b09429932067926f9b2960cc3e4f99d", + "node_modules/uuid/dist/esm/v1.js": "a3cd1c783a64f6e010af00593ac8e7e28c127a5e63266da4809b5687f1a081aa", + "node_modules/uuid/dist/esm/v1ToV6.d.ts": "31501b8fc4279e78f6a05ca35e365e73c0b0c57d06dbe8faecb10c7254ce7714", + "node_modules/uuid/dist/esm/v1ToV6.js": "f0a2d19ee0d4398607808fcfc3eebeb1ea7af08a0c17d71d42fc612f97241a3f", + "node_modules/uuid/dist/esm/v3.d.ts": "ff4e9db3eb1e95d7ba4b5765e4dc7f512b90fb3b588adfd5ca9b0d9d7a56a1ae", + "node_modules/uuid/dist/esm/v3.js": "3b65ad31501886af057e13aeec03afedc54f1f85cc8d332b0eb89800b11fbce4", + "node_modules/uuid/dist/esm/v35.d.ts": "7bc76e7d4bbe3764abaf054aed3a622c5cdbac694e474050d71ce9d4ab93ea4b", + "node_modules/uuid/dist/esm/v35.js": "cd9bdff5aca247c93c0c7bfa3bddbd022076aebb8759ca00004dc980f38901d8", + "node_modules/uuid/dist/esm/v4.d.ts": "f205fd03cd15ea054f7006b7ef8378ef29c315149da0726f4928d291e7dce7b9", + "node_modules/uuid/dist/esm/v4.js": "467ed3f76fe8065eb46dd027c3e5c78501df95cdb99ff6ada1701fa3de024a3e", + "node_modules/uuid/dist/esm/v5.d.ts": "d683908557d53abeb1b94747e764b3bd6b6226273514b96a942340e9ce4b7be7", + "node_modules/uuid/dist/esm/v5.js": "daaf09616ea3b145ccd6a1dfea190b65d546267a9916c5741e026d4290c61ae9", + "node_modules/uuid/dist/esm/v6.d.ts": "7c6d5704e2f236fddaf8dbe9131d998a4f5132609ef795b78c3b63f46317f88a", + "node_modules/uuid/dist/esm/v6.js": "32f4c875353e043cb8d790f2d63d46e7b23d592fa3d5d09ace6afcff1dd44e03", + "node_modules/uuid/dist/esm/v6ToV1.d.ts": "d05bd4d28c12545827349b0ac3a79c50658d68147dad38d13e97e22353544496", + "node_modules/uuid/dist/esm/v6ToV1.js": "78475a282beaea6e8c45e5f2586134929be2e21728f159fb8559df06d7288475", + "node_modules/uuid/dist/esm/v7.d.ts": "b6436d90a5487d9b3c3916b939f68e43f7eaca4b0bb305d897d5124180a122b9", + "node_modules/uuid/dist/esm/v7.js": "38208cc85d447b13d7fc9e75377d4f82e17ad982334786396623c57c1406a257", + "node_modules/uuid/dist/esm/validate.d.ts": "04ace6bedd6f59c30ea6df1f0f8d432c728c8bc5c5fd0c5c1c80242d3ab51977", + "node_modules/uuid/dist/esm/validate.js": "78ba6a91a3c2d219e3a6980d7704c0998e8152cabcaece0f5a04989025d0e60c", + "node_modules/uuid/dist/esm/version.d.ts": "57a8a7772769c35ba7b4b1ba125f0812deec5c7102a0d04d9e15b1d22880c9e8", + "node_modules/uuid/dist/esm/version.js": "5a43d6eb8496806fd5e152f92edda603dbb1f77108045edcf034f22c9e320ce8", + "node_modules/uuid/package.json": "e5ebc4600db1bfcb5c00d8cc3430d869bdbe229c8d46b4bfac06ab2dfc180c39", + "node_modules/wrappy/LICENSE": "4ec3d4c66cd87f5c8d8ad911b10f99bf27cb00cdfcff82621956e379186b016b", + "node_modules/wrappy/README.md": "0d2537e86a9e5ae85d8e53910d779895179035c9daa35555874ec19f1225c224", + "node_modules/wrappy/package.json": "712ac9c4c3cef8bc79f42cf6075ff18551f4cb768f35158214fd643853ad7abc", + "node_modules/wrappy/wrappy.js": "153453c4d84a3c2f13589c37055a7daff61d48475dcce554f7eedad15fc7b3e5", + "node_modules/yallist/LICENSE": "4ec3d4c66cd87f5c8d8ad911b10f99bf27cb00cdfcff82621956e379186b016b", + "node_modules/yallist/README.md": "cf293896dc0c704e9ed655ebb43341cc1ac5d0b2214f78377bed4370b7c6dd0f", + "node_modules/yallist/iterator.js": "d02451fa396de7f9ec93cc6fb3b07aaa7be637acb3409a9ddebd1c2de9279c1a", + "node_modules/yallist/package.json": "c56b604bce814520105739e9559142ea9d4417454ebb933fd5687ca1d8d89bd5", + "node_modules/yallist/yallist.js": "69a10f726d26f8d804a3deaeac89f0106ddfa03d576d13971002fffc8f0e8a56", + "package.json": "1c39e6502a5accde70f7fc87df64d8d0c7c2068556d802242b5192584834d0c4" + }, + "sources": "3eff817759c3fdb894436e8784eb02c64fae8ea4c2b7920298010d15d0ea6c58" + }, + "define-auth-challenge": { + "files": { + "triggers.mjs": "a6804658073b5ab9c7b05a61a046c22abaa0818eb47f74ceb0d731480a7ef180" + }, + "sources": "ae794739623770c99a8650a5ab95e2dbcd6ce6a56065d2055534791571f465cf" + }, + "discard-sender": { + "files": { + "index.mjs": "614110a2c238347572e8621cd5cf4f8999bb7a5a651c03005eedce8898f0ee60" + }, + "sources": "5299579d0464e2dceef1b62cd10a7118339455c9c24cdf73e9612ae78cd8f5e1" + }, + "extend-api-key": { + "files": { + "index.mjs": "fbd4d6f65e9ada3a7de0b5b3defbb47f276a120e2a9ea286c6627f70476ebd61" + }, + "sources": "b714d3e5b1a584988737f7e8f5cc92bfac1fb9db66f29751058f90bc1eaa268f" + }, + "new-password-reset": { + "files": { + "index.mjs": "de5ec6b1e0bc80ea9461101fe0fca68b974bda67e27dd6ce4c9c64724f174f99" + }, + "sources": "d54f4801e05cf1039837d4ba20546acff11f90b85bfc89801b7d012d6cfa657b" + }, + "pre-sign-up": { + "files": { + "triggers.mjs": "a6804658073b5ab9c7b05a61a046c22abaa0818eb47f74ceb0d731480a7ef180" + }, + "sources": "ae794739623770c99a8650a5ab95e2dbcd6ce6a56065d2055534791571f465cf" + }, + "pre-sign-up-confirmable": { + "files": { + "triggers.mjs": "a6804658073b5ab9c7b05a61a046c22abaa0818eb47f74ceb0d731480a7ef180" + }, + "sources": "ae794739623770c99a8650a5ab95e2dbcd6ce6a56065d2055534791571f465cf" + }, + "verify-auth-challenge": { + "files": { + "triggers.mjs": "a6804658073b5ab9c7b05a61a046c22abaa0818eb47f74ceb0d731480a7ef180" + }, + "sources": "ae794739623770c99a8650a5ab95e2dbcd6ce6a56065d2055534791571f465cf" + } + } +} From 1d0b39aad7c795ee2a98743ca2950e09c8d6f79e Mon Sep 17 00:00:00 2001 From: Harsh <6162866+harsh62@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:13:48 -0400 Subject: [PATCH 57/57] fix(auth): stop repeating the parsed value in the MFA-type and auth-factor parsers' error line On the plugin's confirmSignIn the value is the user's answer, so an answer that is not a known MFA type or factor was logged at error level. Log the fixed text "Tried to initialize an unsupported MFA type" for every value instead. The log-transcript golden's scenario 7 line changes accordingly, and its pins in LogTranscriptSnapshotTests and check_fixture_lock.sh are updated. The Cognito client refuses an answer that is not a known type before the engine runs, so its line only printed values from Cognito or a saved record; two of its comments that described the old line are updated. --- .../Sources/Core/SessionCore+SignIn.swift | 2 +- .../Tests/UnitTests/ChallengeTests.swift | 6 +- .../EngineStaticSiteRoutingTests.swift | 25 ++++- .../Sources/Models/EngineAuthFactorType.swift | 3 +- .../Sources/Models/EngineMFAType.swift | 3 +- .../Models/AuthFactorTypeExtension.swift | 3 +- .../Models/MFATypeExtension.swift | 3 +- .../EngineFlowTypeForkTests.swift | 2 +- .../EngineMFATypeForkTests.swift | 2 +- .../EngineStaticLogSiteTests.swift | 94 ++++++++++++++++++- .../LogTranscriptSnapshotTests.swift | 7 +- .../TestResources/GoldenLogs/transcript.json | 2 +- docs/design/RELEASE-NOTES-PENDING.md | 2 + scripts/cognito-engine/check_fixture_lock.sh | 8 +- 14 files changed, 137 insertions(+), 25 deletions(-) diff --git a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+SignIn.swift b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+SignIn.swift index b845d7393d..52da2a272a 100644 --- a/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+SignIn.swift +++ b/AmplifyClients/AmplifyCognitoClient/Sources/Core/SessionCore+SignIn.swift @@ -211,7 +211,7 @@ extension SessionCore { } case .continueSignInWithMFASetupSelection(let allowed): // Checked here, so a wrong answer is refused with the step's choices, and never reaches the engine, whose - // MFA-type parser logs what it was given at error level. + // MFA-type parser would refuse it without naming the choices. let names = allowed.map(\.challengeResponse).sorted() guard names.contains(where: { response.caseInsensitiveCompare($0) == .orderedSame }) else { throw AuthClientError.validation( diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/ChallengeTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/ChallengeTests.swift index 634c42d8bb..1252444900 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/ChallengeTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/ChallengeTests.swift @@ -399,9 +399,9 @@ final class ChallengeTests: XCTestCase { XCTAssertEqual(engine.confirmSignInCalls.map(\.challengeResponse), ["email_otp"]) } - /// The live engine's MFA-type parser logs an answer it cannot parse, at error level ("Tried to initialize an - /// unsupported MFA type with value: …"). The check keeps a wrong setup selection from reaching it, so a code typed - /// into the wrong prompt is never logged. + /// A wrong MFA setup selection never reaches the live engine: the check refuses it before anything is sent, so a + /// code typed into the wrong prompt is never logged. (The engine's MFA-type parser logs a fixed text for a value it + /// cannot parse; 2.63.0's logged the value.) /// /// - Given: a client over the live engine, whose SRP sign-in stops on an MFA setup selection of TOTP or email, and /// a log sink diff --git a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/EngineStaticSiteRoutingTests.swift b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/EngineStaticSiteRoutingTests.swift index 7be48ad151..ccb6382d6e 100644 --- a/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/EngineStaticSiteRoutingTests.swift +++ b/AmplifyClients/AmplifyCognitoClient/Tests/UnitTests/EngineStaticSiteRoutingTests.swift @@ -57,7 +57,20 @@ final class EngineStaticSiteRoutingTests: XCTestCase { /// - each line arrived under the client's category: `AmplifyCognitoClient.KeychainStore`, /// `AmplifyCognitoClient.MFAType` and `AmplifyCognitoClient.AuthFactorType` /// - the registrant logs under `AmplifyCognitoClient.PlatformWebAuthnCredentials` + /// - no line holds a value the parsers were given func testClientPathsNeverReachTheGlobalRouter() async throws { + // The parsers log a fixed text, never the value, so each step's line is told apart by count. + let parserLine = "Tried to initialize an unsupported MFA type" + let parserCategories = ["AmplifyCognitoClient.MFAType", "AmplifyCognitoClient.AuthFactorType"] + var parserLinesSeen: [String: Int] = [:] + func newParserLineCategories() -> [String] { + parserCategories.filter { category in + let count = sink.lines(in: [category]).count(where: { $0.contains(parserLine) }) + defer { parserLinesSeen[category] = count } + return count > parserLinesSeen[category, default: 0] + } + } + let engine = try harness.engine() let payload = try await harness.signedInPayload(on: engine) XCTAssertFalse(sink.lines(in: ["AmplifyCognitoClient.KeychainStore"]).isEmpty, "the legacy keychain's lines") @@ -66,7 +79,7 @@ final class EngineStaticSiteRoutingTests: XCTestCase { GetUserOutput(preferredMfaSetting: "X", userAttributes: [], userMFASettingList: ["X"], username: "alice") } _ = try await engine.fetchMFAPreference(payload) - XCTAssertEqual(sink.categories(containing: "unsupported MFA type with value: X"), ["AmplifyCognitoClient.MFAType"]) + XCTAssertEqual(newParserLineCategories(), ["AmplifyCognitoClient.MFAType"]) // A saved session whose preferred first factor this build does not know, decoded as the client decodes // every payload. @@ -86,7 +99,7 @@ final class EngineStaticSiteRoutingTests: XCTestCase { XCTAssertTrue(known.contains(#""EMAIL_OTP""#)) let unknown = Data(known.replacingOccurrences(of: #""EMAIL_OTP""#, with: #""TELEKINESIS""#).utf8) XCTAssertThrowsError(try CredentialSlot.decode(unknown)) - XCTAssertEqual(sink.categories(containing: "value: TELEKINESIS"), ["AmplifyCognitoClient.AuthFactorType"]) + XCTAssertEqual(newParserLineCategories(), ["AmplifyCognitoClient.AuthFactorType"]) let factor = try XCTUnwrap(ChallengeRecord.State.fake( .confirmSignInWithTOTPCode, @@ -95,7 +108,7 @@ final class EngineStaticSiteRoutingTests: XCTestCase { let factorEngine = try harness.engine() let resumedFactor = await factorEngine.resumeSignIn(from: factor, epoch: 0) XCTAssertNil(resumedFactor) - XCTAssertEqual(sink.categories(containing: "value: TELEPATHY"), ["AmplifyCognitoClient.AuthFactorType"]) + XCTAssertEqual(newParserLineCategories(), ["AmplifyCognitoClient.AuthFactorType"]) guard case .challenge(var challenge) = ChallengeRecord.State.fake(.continueSignInWithMFASelection([.sms])) else { return XCTFail("no saved MFA selection") @@ -104,7 +117,7 @@ final class EngineStaticSiteRoutingTests: XCTestCase { let mfaEngine = try harness.engine() let resumedMFA = await mfaEngine.resumeSignIn(from: .challenge(challenge), epoch: 0) XCTAssertNil(resumedMFA) - XCTAssertEqual(sink.categories(containing: "value: TELEPHONE_MFA"), ["AmplifyCognitoClient.MFAType"]) + XCTAssertEqual(newParserLineCategories(), ["AmplifyCognitoClient.MFAType"]) #if os(iOS) || os(macOS) || os(visionOS) if #available(iOS 17.4, macOS 13.5, visionOS 1.0, *) { @@ -119,6 +132,10 @@ final class EngineStaticSiteRoutingTests: XCTestCase { } #endif + XCTAssertEqual(Set(sink.categories(containing: parserLine)), Set(parserCategories)) + for value in ["TELEKINESIS", "TELEPATHY", "TELEPHONE_MFA"] { + XCTAssertEqual(sink.categories(containing: value), [], "a line holds the parsed value \(value)") + } XCTAssertEqual(router.scopes, [], "no client line goes through the global router") XCTAssertEqual(sink.lines(in: ["MFAType", "AuthFactorType", "KeychainStore", "PlatformWebAuthnCredentials"]), []) } diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Models/EngineAuthFactorType.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Models/EngineAuthFactorType.swift index f15a6533b9..c30c326c33 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Models/EngineAuthFactorType.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Models/EngineAuthFactorType.swift @@ -78,7 +78,8 @@ package extension EngineAuthFactorType { return nil #endif default: - log.error("Tried to initialize an unsupported MFA type with value: \(rawValue)") + // The value is not logged: on the confirmSignIn path it is the user's answer. + log.error("Tried to initialize an unsupported MFA type") return nil } } diff --git a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Models/EngineMFAType.swift b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Models/EngineMFAType.swift index 0e052233c8..68a652a720 100644 --- a/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Models/EngineMFAType.swift +++ b/AmplifyClients/Internal/InternalAWSCognitoAuth/Sources/Models/EngineMFAType.swift @@ -45,7 +45,8 @@ package extension EngineMFAType { } else if rawValue.caseInsensitiveCompare("EMAIL_OTP") == .orderedSame { self = .email } else { - logger.scoped(Self.logScope).error("Tried to initialize an unsupported MFA type with value: \(rawValue)") + // The value is not logged: on the confirmSignIn path it is the user's answer. + logger.scoped(Self.logScope).error("Tried to initialize an unsupported MFA type") return nil } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AuthFactorTypeExtension.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AuthFactorTypeExtension.swift index 8292c630f8..91324d9454 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AuthFactorTypeExtension.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/AuthFactorTypeExtension.swift @@ -29,7 +29,8 @@ extension AuthFactorType: DefaultLogger { return nil #endif default: - Self.log.error("Tried to initialize an unsupported MFA type with value: \(rawValue)") + // The value is not logged: on the confirmSignIn path it is the user's answer. + Self.log.error("Tried to initialize an unsupported MFA type") return nil } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/MFATypeExtension.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/MFATypeExtension.swift index c4e49bb97e..e73d8807b1 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/MFATypeExtension.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/MFATypeExtension.swift @@ -19,7 +19,8 @@ extension MFAType: DefaultLogger { } else if rawValue.caseInsensitiveCompare("EMAIL_OTP") == .orderedSame { self = .email } else { - EngineLog.logger(.category("MFAType")).error("Tried to initialize an unsupported MFA type with value: \(rawValue)") + // The value is not logged: on the confirmSignIn path it is the user's answer. + EngineLog.logger(.category("MFAType")).error("Tried to initialize an unsupported MFA type") return nil } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/EngineFlowTypeForkTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/EngineFlowTypeForkTests.swift index e70477f414..aae35124b1 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/EngineFlowTypeForkTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/EngineFlowTypeForkTests.swift @@ -329,7 +329,7 @@ class EngineFlowTypeForkTests: XCTestCase { CapturingRouter.Entry( scope: .category("AuthFactorType"), level: .error, - message: "Tried to initialize an unsupported MFA type with value: X", + message: "Tried to initialize an unsupported MFA type", hasError: false ) ] diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/EngineMFATypeForkTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/EngineMFATypeForkTests.swift index 57db0a2b17..117d35654c 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/EngineMFATypeForkTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/EngineMFATypeForkTests.swift @@ -107,7 +107,7 @@ class EngineMFATypeForkTests: XCTestCase { .init( scope: .category("MFAType"), level: .error, - message: "Tried to initialize an unsupported MFA type with value: X", + message: "Tried to initialize an unsupported MFA type", hasError: false ) ]) diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/EngineStaticLogSiteTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/EngineStaticLogSiteTests.swift index 54f8e215da..0a7c722219 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/EngineStaticLogSiteTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/EngineFoundationTests/EngineStaticLogSiteTests.swift @@ -112,7 +112,7 @@ final class EngineStaticLogSiteTests: XCTestCase, @unchecked Sendable { XCTAssertNil(preference.enabled) assertLines(at: shapeB(MFAType.self), [ - ("error", "Tried to initialize an unsupported MFA type with value: X") + ("error", "Tried to initialize an unsupported MFA type") ]) } @@ -273,7 +273,7 @@ final class EngineStaticLogSiteTests: XCTestCase, @unchecked Sendable { ) _ = UserPoolSignInHelper.parseResponse(response, for: "user", signInMethod: .apiBased(.userSRP), logger: plugin) assertLines(at: shapeB(MFAType.self), [ - ("error", "Tried to initialize an unsupported MFA type with value: X") + ("error", "Tried to initialize an unsupported MFA type") ]) #if os(iOS) || os(macOS) || os(visionOS) @@ -298,7 +298,7 @@ final class EngineStaticLogSiteTests: XCTestCase, @unchecked Sendable { byApplying: answer ) assertLines(at: shapeB(AuthFactorType.self), [ - ("error", "Tried to initialize an unsupported MFA type with value: X") + ("error", "Tried to initialize an unsupported MFA type") ]) // `PlatformWebAuthnCredentials`: the delegate's logger, as `AssertWebAuthnCredentials` builds it. Its @@ -349,7 +349,7 @@ final class EngineStaticLogSiteTests: XCTestCase, @unchecked Sendable { XCTAssertThrowsError(try JSONDecoder().decode(AuthFlowType.self, from: json)) assertLines(at: shapeB(AuthFactorType.self), [ - ("error", "Tried to initialize an unsupported MFA type with value: X") + ("error", "Tried to initialize an unsupported MFA type") ]) } @@ -387,6 +387,92 @@ final class EngineStaticLogSiteTests: XCTestCase, @unchecked Sendable { ("verbose", "Received fetch auth session error - \(error)") ]) } + + // MARK: The parsers never log the value + + /// Test that the plugin's confirmSignIn never logs the user's answer through the parsers + /// + /// - Given: A confirmSignIn request whose answer is not an MFA type or an auth factor + /// - When: + /// - The confirmSignIn task validates it as an MFA selection and as a factor selection, as it does before + /// sending the answer + /// - Then: + /// - Both are refused with a validation error + /// - The `MFAType` and `AuthFactorType` lines are the fixed text, and no captured line holds the answer + /// + func testThePluginsConfirmSignInNeverLogsTheAnswer() { + let answer = "answer-not-a-type-4821" + let task = AWSAuthConfirmSignInTask( + AuthConfirmSignInRequest(challengeResponse: answer, options: .init()), + stateMachine: Defaults.makeDefaultAuthStateMachine(), + configuration: Defaults.makeDefaultAuthConfigData() + ) + + XCTAssertThrowsError(try task.validateRequestForMFASelection()) { error in + guard case AuthError.validation = error else { + return XCTFail("Should be a validation error, not \(error)") + } + } + XCTAssertThrowsError(try task.validateRequestForFactorSelection()) { error in + guard case AuthError.validation = error else { + return XCTFail("Should be a validation error, not \(error)") + } + } + + assertLines(at: shapeB(MFAType.self), [("error", "Tried to initialize an unsupported MFA type")]) + assertLines(at: shapeB(AuthFactorType.self), [("error", "Tried to initialize an unsupported MFA type")]) + XCTAssertFalse(capture.lines.contains { $0.message.contains(answer) }, "\(capture.lines.map(\.message))") + } + + /// Test that the engine's answer verification never logs the user's answer through the parsers + /// + /// - Given: A challenge awaiting an MFA setup selection, and one awaiting a first factor selection, with the + /// plugin's logger + /// - When: + /// - `VerifySignInChallenge` runs each with an answer that is neither an MFA type nor a factor + /// - Then: + /// - The `MFAType` and `AuthFactorType` lines are the fixed text, and no captured line holds the answer + /// + func testTheEnginesAnswerVerificationNeverLogsTheAnswer() async { + let answer = "answer-not-a-type-4821" + let environment = Defaults.makeDefaultAuthEnvironment(userPoolFactory: { + MockIdentityProvider(mockRespondToAuthChallengeResponse: { _ in + throw AWSCognitoIdentityProvider.InvalidParameterException(message: "rejected") + }) + }) + let challenge = RespondToAuthChallenge( + challenge: .selectChallenge, + availableChallenges: [], + username: "user", + session: "session", + parameters: [:] + ) + let answerData = ConfirmSignInEventData( + answer: answer, + attributes: [:], + metadata: nil, + friendlyDeviceName: nil, + presentationAnchor: nil + ) + let steps: [EngineSignInStep] = [ + .continueSignInWithMFASetupSelection([.totp, .email]), + .continueSignInWithFirstFactorSelection([.password, .emailOTP]) + ] + + for step in steps { + let action = VerifySignInChallenge( + challenge: challenge, + confirmSignEventData: answerData, + signInMethod: .apiBased(.userAuth(preferredFirstFactor: nil)), + currentSignInStep: step + ) + await action.execute(withDispatcher: MockDispatcher { _ in }, environment: environment) + } + + assertLines(at: shapeB(MFAType.self), [("error", "Tried to initialize an unsupported MFA type")]) + assertLines(at: shapeB(AuthFactorType.self), [("error", "Tried to initialize an unsupported MFA type")]) + XCTAssertFalse(capture.lines.contains { $0.message.contains(answer) }, "\(capture.lines.map(\.message))") + } } /// A global router that fails the test for every scope resolved through it. diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/GoldenTests/LogTranscriptSnapshotTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/GoldenTests/LogTranscriptSnapshotTests.swift index 2f12a30362..1b0162ccb3 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/GoldenTests/LogTranscriptSnapshotTests.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/GoldenTests/LogTranscriptSnapshotTests.swift @@ -24,8 +24,9 @@ import XCTest /// `awsCognitoAuthPluginTest` category, so every shape resolves as it does in an app. Each line is /// normalised (`LogTranscriptNormaliser`), and the scenario's lines are compared, as a sorted multiset, /// with `TestResources/GoldenLogs/transcript.json`, recorded before the engine extraction. It was re-pinned -/// once since, for a reviewed change: scenario 4's sign-out line masks the refresh token (`tr*****en`), where -/// the released plugin printed it in full. The file's note keeps its capture-time wording. +/// twice since, for reviewed changes: scenario 4's sign-out line masks the refresh token (`tr*****en`), where +/// the released plugin printed it in full; and scenario 7's parser line no longer prints the value it was given +/// (on the confirmSignIn path, the user's answer). The file's note keeps its capture-time wording. /// /// Why a sorted multiset: actions run on their own tasks, so the interleaving of lines from different /// tasks is not deterministic. Which lines are logged, how often, and at which scope and level is. @@ -40,7 +41,7 @@ final class LogTranscriptSnapshotTests: XCTestCase, @unchecked Sendable { /// SHA-256 of `transcript.json`. Regenerating the transcript changes it, so a regeneration also has to edit /// this line, in review. - static let pinnedTranscriptSHA256 = "6193c4562559fb8602ba4516c11ec533f8ee1e57cf65f851fe19afebed925afa" + static let pinnedTranscriptSHA256 = "5daa06cfb709db3ce7b2540a9af5566bdf24de306bd20ca7246b909a3596c796" static var fileURL: URL { GoldenFiles.directory("GoldenLogs").appendingPathComponent("transcript.json") diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestResources/GoldenLogs/transcript.json b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestResources/GoldenLogs/transcript.json index e5e94887d4..a1c187e428 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestResources/GoldenLogs/transcript.json +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestResources/GoldenLogs/transcript.json @@ -1002,7 +1002,7 @@ { "category" : "AuthFactorType", "level" : "error", - "message" : "Tried to initialize an unsupported MFA type with value: X", + "message" : "Tried to initialize an unsupported MFA type", "shape" : "category" } ] diff --git a/docs/design/RELEASE-NOTES-PENDING.md b/docs/design/RELEASE-NOTES-PENDING.md index 9c949e5c7e..713717cfdc 100644 --- a/docs/design/RELEASE-NOTES-PENDING.md +++ b/docs/design/RELEASE-NOTES-PENDING.md @@ -11,6 +11,7 @@ behaviour. | Change | Who sees it | Details | |---|---|---| | Security: `confirmSignIn`'s answer is no longer printed on the sign-in's verbose lines. While Cognito checks an answer, the sign-in's challenge state (`SignInChallengeState.verifying`) held it in full and printed it by reflection, so the plugin's verbose `Checking next step for:` line showed every answer `confirmSignIn` sent through a challenge: an SMS, email or TOTP code, a password after a `PASSWORD` or `PASSWORD_SRP` factor selection, a new password, a custom challenge's answer. In 2.63.0 each one was logged in full there. The state no longer holds the answer. The answer's own printed form, which the TOTP setup's verbose lines (`Checking next step for:`, `Auth state change`) and the `Sending event` line of an answer sent again after Cognito refused the saved device print, showed it masked (`S3*******21`, four characters of a six-digit code); it now shows ``, in `print`, `debugPrint`, interpolation and `dump` | Anyone reading or collecting verbose logs | — | +| Security: the MFA-type and auth-factor parsers' error line no longer repeats the value it was given. On `confirmSignIn` that value is the user's answer: an answer to an MFA selection, an MFA setup selection or a first-factor selection that is not a known type logged `Tried to initialize an unsupported MFA type with value: `. The line is now the fixed text `Tried to initialize an unsupported MFA type`, at the same level (error) and category (`MFAType`, `AuthFactorType`), for every value, including the ones Cognito or a saved session supplies | Anyone reading or collecting error-level logs | — | | Security: the TOTP setup's verified session is no longer printed. When a sign-in sets up TOTP, `VerifyTOTPSetup`'s verbose `Sending event` line printed the session `VerifySoftwareToken` returns, which answers the MFA setup challenge and finishes the sign-in, in full in 2.63.0. It now prints `respondToAuthChallenge("")`, and the event redacts the session in every printed form | Anyone reading or collecting verbose logs | — | | Security: a sign-up result no longer prints the auto sign-in session. The plugin's tasks log their result at info ("Successfully completed execution for … with result:"), and `AuthSignUpStep.completeAutoSignIn(Session)` had no description of its own, so in 2.63.0 `confirmSignUp`, and a `signUp` Cognito confirms at once (a passwordless sign-up), logged the session in full at info; with the username, the session signs the user in. `AuthSignUpStep` now conforms to `CustomStringConvertible`, `CustomDebugStringConvertible` and `CustomReflectable`, and prints `completeAutoSignIn("")` in `print`, `debugPrint`, interpolation and `dump`, the step and the `AuthSignUpResult` holding it alike; the other steps print as before. An additive API change; `case .completeAutoSignIn(let session)` still reads the session. An app's own `CustomStringConvertible`, `CustomDebugStringConvertible` or `CustomReflectable` conformance for `AuthSignUpStep` now gets a compiler warning, and Amplify's conformance is used instead: remove the app's own | Anyone reading or collecting info-level logs; apps that print or dump a sign-up result or step, or that declare their own description conformance for `AuthSignUpStep` | — | | Security: the sign-in's verbose lines no longer print the challenge session. The challenge a sign-in answers (the engine's `RespondToAuthChallenge`) had no description of its own, so every verbose line that printed it by reflection showed its session in full, and the username: the "Sending event" lines of the sign-in actions when Cognito answers with a challenge (SRP, custom auth, `USER_AUTH`, migrate auth, auto sign-in after sign-up, and each `confirmSignIn` answer), and the lines that print a sign-in state holding one. It now prints with the session as `` and its two usernames masked (`alice` as `al*ce`), as the sign-in data already did, in `print`, `debugPrint`, interpolation and `dump`. The other fields print as they did nested in an event or state in 2.63.0; `parameters`, Cognito's challenge parameters, still print in full, and can hold a username (`USERNAME` on a custom challenge). Printed on its own, the challenge now names its type in full (`InternalAWSCognitoAuth.RespondToAuthChallenge(…)`). The user's answer and the TOTP setup's session are the rows above | Anyone reading or collecting verbose logs | — | @@ -24,6 +25,7 @@ behaviour. | Change | Details | |---|---| +| Security: the MFA-type and auth-factor parsers' error line, which the client logs under `AmplifyCognitoClient.MFAType` and `AmplifyCognitoClient.AuthFactorType`, no longer repeats the value it was given: it is the fixed text `Tried to initialize an unsupported MFA type`, at the same level (error), where 2.63.0 logged `Tried to initialize an unsupported MFA type with value: `. The client never logged a user's answer there: it refuses an answer that is not a known type before the engine runs, so the line only printed a value from Cognito or a saved record | — | | Security: the engine's verbose lines no longer print a sign-in challenge's session: a sent challenge answer (`initiateTOTPSetup`, say) shows it as ``, beside the usernames the client already redacted. In 2.63.0 it was printed in full | — | | Security: the engine's verbose lines no longer print the TOTP setup's verified session: `VerifyTOTPSetup`'s `Sending event` line, which printed the session `VerifySoftwareToken` returns in full in 2.63.0, shows `respondToAuthChallenge("")`. The `Sending event` line of an answer sent again after Cognito refused the saved device shows the answer as ``, where 2.63.0 masked it (`S3*******21`) | — | | A Cognito internal error (`InternalErrorException`, from either pool) and an error status the SDK does not model are `AuthClientError.service` with no code in every call: `USER_AUTH`, auto, device SRP and passkey sign-ins, `deleteUser`, a sign-out's revoke and global sign-out, and the passkey credential calls included. In 2.63.0 they were `AuthClientError.unknown`, and an internal error in those calls and sign-ins still suggested reporting a bug. A 5xx or an internal error suggests a retry, and any other status says the service refused the request with an error the client does not recognise (the description has the status and message). In a `.partial` sign-out, the revoke's or global sign-out's suggestion says instead that it will not be retried; the global sign-out's says the user's other sessions stay signed in, without the "unless the request reached the service" that only a transport failure's has. Code that matched `.unknown` for these must match `.service(nil, …)`. The credentials providers report both cases the same way, so a provider's `CredentialsError` and its `disposition` don't change. The Auth plugin keeps `AuthError.unknown` | [README](../../AmplifyClients/AmplifyCognitoClient/README.md) "Errors" | diff --git a/scripts/cognito-engine/check_fixture_lock.sh b/scripts/cognito-engine/check_fixture_lock.sh index fcd9d5d5ef..17fd5cc315 100755 --- a/scripts/cognito-engine/check_fixture_lock.sh +++ b/scripts/cognito-engine/check_fixture_lock.sh @@ -20,7 +20,8 @@ # release notes (RELEASE-NOTES-PENDING.md): # - GoldenStoredFormat/AWSMobileClient: `locked-keychain` (a failed legacy read keeps the legacy stores and # clears nothing) and `existing-session-present` (legacy data never overwrites a newer saved session); -# - GoldenLogs: scenario 4's sign-out line masks the refresh token (re-pinned below); +# - GoldenLogs: scenario 4's sign-out line masks the refresh token, and scenario 7's parser line no longer +# prints the value it was given (both re-pinned below); # - GoldenKeychainQueries (the released plugin has no keychain seam to record): sign-out and delete user also # clear the three AWSMobileClient services; the legacy migration reads the saved session first, never # writes over a saved login, keeps everything after a failed read, and clears the legacy services only @@ -63,11 +64,12 @@ BASE_REF="${2:-}" # from the access-group migration's destination; again when the plugin's deleting configuration change # also removed the old configuration's default-session items; # - GoldenConfiguration: the Gen1 and no-sign-in-redirect inputs added, every earlier file unchanged; -# - GoldenLogs: the sign-out line's refresh token masked (security review). +# - GoldenLogs: the sign-out line's refresh token masked (security review); again when the MFA-type and +# auth-factor parsers' error line stopped printing the value it was given (security review). LOCKED_TREES=( "AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestResources/GoldenStoredFormat 00adb7ec49b1c25ad64bea2e2e30bbd2d818ad1c" "AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestResources/GoldenErrors bd2e1e15435e04d3153beac98f8370729e8038d6" - "AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestResources/GoldenLogs 14aefeb712286ee38af02d883a50f3bcc774a610" + "AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestResources/GoldenLogs 7ef93ecd4b541aebfd5d32a78a385d62357d3065" "AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestResources/GoldenKeychainQueries 68c9aaedba0002aed68630be394c9fabb1caa03e" "AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TestResources/GoldenConfiguration 5f07437e77f8c64dfe7b3a83c598e4be7c2475b7" )