From e57fc7257e82a387e373d8e0ade7eda0ae744da9 Mon Sep 17 00:00:00 2001 From: Michael Law <1365977+lawmicha@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:16:23 -0400 Subject: [PATCH 1/2] feat(Auth): provide AuthConfiguration from plugin --- .../AWSCognitoAuthPlugin.swift | 4 +- .../CodeGen/Data/AuthConfiguration.swift | 7 +- .../Data/IdentityPoolConfigurationData.swift | 4 +- .../Data/PasswordProtectionSettings.swift | 29 +++++++ .../CodeGen/Data/SignUpAttributeType.swift | 25 ++++++ .../CodeGen/Data/UserAttribute.swift | 27 +++++++ .../Data/UserPoolConfigurationData.swift | 24 +++++- .../CodeGen/Data/VerificationMechanism.swift | 14 ++++ .../Support/Helpers/ConfigurationHelper.swift | 79 ++++++++++++++++++- .../SignIn/AWSAuthSignInOptionsTestCase.swift | 2 +- 10 files changed, 204 insertions(+), 11 deletions(-) create mode 100644 AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/PasswordProtectionSettings.swift create mode 100644 AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/SignUpAttributeType.swift create mode 100644 AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/UserAttribute.swift create mode 100644 AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/VerificationMechanism.swift diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin.swift index f32209ba9c..75774f1e21 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin.swift @@ -21,7 +21,8 @@ public final class AWSCognitoAuthPlugin: AWSCognitoAuthPluginBehavior { var queue: OperationQueue! /// Configuration for the auth plugin - var authConfiguration: AuthConfiguration! + @_spi(InternalAmplifyConfiguration) + internal(set) public var authConfiguration: AuthConfiguration! /// Handles different auth event send through hub var hubEventHandler: AuthHubEventBehavior! @@ -35,6 +36,7 @@ public final class AWSCognitoAuthPlugin: AWSCognitoAuthPluginBehavior { /// The user network preferences for timeout and retry let networkPreferences: AWSCognitoNetworkPreferences? + @available(*, deprecated, message: "Use `authConfiguration`") @_spi(InternalAmplifyConfiguration) internal(set) public var jsonConfiguration: JSONValue? diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/AuthConfiguration.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/AuthConfiguration.swift index ce3d08e37d..e2c7babe97 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/AuthConfiguration.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/AuthConfiguration.swift @@ -7,7 +7,8 @@ import Foundation -enum AuthConfiguration { +@_spi(InternalAmplifyConfiguration) +public enum AuthConfiguration { case userPools(UserPoolConfigurationData) case identityPools(IdentityPoolConfigurationData) case userPoolsAndIdentityPools(UserPoolConfigurationData, IdentityPoolConfigurationData) @@ -21,7 +22,7 @@ extension AuthConfiguration: Codable { case identityPools } - func encode(to encoder: Encoder) throws { + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { @@ -35,7 +36,7 @@ extension AuthConfiguration: Codable { } } - init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) let userConfigData = try? values.decode(UserPoolConfigurationData.self, forKey: .userPools) diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/IdentityPoolConfigurationData.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/IdentityPoolConfigurationData.swift index 11e2474e48..764b4234f7 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/IdentityPoolConfigurationData.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/IdentityPoolConfigurationData.swift @@ -7,7 +7,7 @@ import Foundation -struct IdentityPoolConfigurationData: Equatable { +public struct IdentityPoolConfigurationData: Equatable { let poolId: String let region: String @@ -25,7 +25,7 @@ extension IdentityPoolConfigurationData: CustomDebugDictionaryConvertible { } extension IdentityPoolConfigurationData: CustomDebugStringConvertible { - var debugDescription: String { + public var debugDescription: String { debugDictionary.debugDescription } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/PasswordProtectionSettings.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/PasswordProtectionSettings.swift new file mode 100644 index 0000000000..bc3d682225 --- /dev/null +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/PasswordProtectionSettings.swift @@ -0,0 +1,29 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +@_spi(InternalAmplifyConfiguration) +public struct PasswordProtectionSettings: Equatable, Codable { + public var minLength: Int? + public var characterPolicy: [PasswordCharacterPolicy] + + public init(minLength: Int?, + characterPolicy: [PasswordCharacterPolicy]) { + self.minLength = minLength + self.characterPolicy = characterPolicy + } +} + +@_spi(InternalAmplifyConfiguration) +public enum PasswordCharacterPolicy: String, Codable { + case lowercase = "REQUIRES_LOWERCASE" + case uppercase = "REQUIRES_UPPERCASE" + case numbers = "REQUIRES_NUMBERS" + case symbols = "REQUIRES_SYMBOLS" +} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/SignUpAttributeType.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/SignUpAttributeType.swift new file mode 100644 index 0000000000..ea29151ec7 --- /dev/null +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/SignUpAttributeType.swift @@ -0,0 +1,25 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +@_spi(InternalAmplifyConfiguration) +public enum SignUpAttributeType: String, Codable { + case address = "ADDRESS" + case birthDate = "BIRTHDATE" + case email = "EMAIL" + case familyName = "FAMILY_NAME" + case gender = "GENDER" + case givenName = "GIVEN_NAME" + case middleName = "MIDDLE_NAME" + case name = "NAME" + case nickname = "NICKNAME" + case phoneNumber = "PHONE_NUMBER" + case preferredUsername = "PREFERRED_USERNAME" + case profile = "PROFILE" + case website = "WEBSITE" +} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/UserAttribute.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/UserAttribute.swift new file mode 100644 index 0000000000..632c3a3ba4 --- /dev/null +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/UserAttribute.swift @@ -0,0 +1,27 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import Amplify + +@_spi(InternalAmplifyConfiguration) +public enum UsernameAttribute: String, Codable { + case username = "USERNAME" + case email = "EMAIL" + case phoneNumber = "PHONE_NUMBER" + + public init?(from authUserAttributeKey: AuthUserAttributeKey) { + switch authUserAttributeKey { + case .email: + self = .email + case .phoneNumber: + self = .phoneNumber + default: + return nil + } + } +} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/UserPoolConfigurationData.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/UserPoolConfigurationData.swift index 829edc362d..742d7dae93 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/UserPoolConfigurationData.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/UserPoolConfigurationData.swift @@ -7,7 +7,8 @@ import ClientRuntime -struct UserPoolConfigurationData: Equatable { +@_spi(InternalAmplifyConfiguration) +public struct UserPoolConfigurationData: Equatable { let poolId: String let clientId: String @@ -17,6 +18,14 @@ struct UserPoolConfigurationData: Equatable { let pinpointAppId: String? let hostedUIConfig: HostedUIConfigurationData? let authFlowType: AuthFlowType + @_spi(InternalAmplifyConfiguration) + public let passwordProtectionSettings: PasswordProtectionSettings? + @_spi(InternalAmplifyConfiguration) + public let usernameAttributes: [UsernameAttribute] + @_spi(InternalAmplifyConfiguration) + public let signUpAttributes: [SignUpAttributeType] + @_spi(InternalAmplifyConfiguration) + public let verificationMechanisms: [VerificationMechanism] init( poolId: String, @@ -26,7 +35,11 @@ struct UserPoolConfigurationData: Equatable { clientSecret: String? = nil, pinpointAppId: String? = nil, authFlowType: AuthFlowType = .userSRP, - hostedUIConfig: HostedUIConfigurationData? = nil + hostedUIConfig: HostedUIConfigurationData? = nil, + passwordProtectionSettings: PasswordProtectionSettings? = nil, + usernameAttributes: [UsernameAttribute] = [], + signUpAttributes: [SignUpAttributeType] = [], + verificationMechanisms: [VerificationMechanism] = [] ) { self.poolId = poolId self.clientId = clientId @@ -36,6 +49,10 @@ struct UserPoolConfigurationData: Equatable { self.pinpointAppId = pinpointAppId self.hostedUIConfig = hostedUIConfig self.authFlowType = authFlowType + self.passwordProtectionSettings = passwordProtectionSettings + self.usernameAttributes = usernameAttributes + self.signUpAttributes = signUpAttributes + self.verificationMechanisms = verificationMechanisms } /// Amazon Cognito user pool: cognito-idp..amazonaws.com/, @@ -62,7 +79,8 @@ extension UserPoolConfigurationData: CustomDebugDictionaryConvertible { } extension UserPoolConfigurationData: CustomDebugStringConvertible { - var debugDescription: String { + @_spi(InternalAmplifyConfiguration) + public var debugDescription: String { debugDictionary.debugDescription } } diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/VerificationMechanism.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/VerificationMechanism.swift new file mode 100644 index 0000000000..b7943bb23a --- /dev/null +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/VerificationMechanism.swift @@ -0,0 +1,14 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation + +@_spi(InternalAmplifyConfiguration) +public enum VerificationMechanism: String, Codable { + case email = "EMAIL" + case phoneNumber = "PHONE_NUMBER" +} diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/ConfigurationHelper.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/ConfigurationHelper.swift index 60deb125ab..24ebe31bc3 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/ConfigurationHelper.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/ConfigurationHelper.swift @@ -23,6 +23,7 @@ struct ConfigurationHelper { return nil } + // parse `pinpointId` var pinpointId: String? if case .string(let pinpointIdFromConfig) = cognitoUserPoolJSON.value(at: "PinpointAppId") { pinpointId = pinpointIdFromConfig @@ -44,6 +45,7 @@ struct ConfigurationHelper { return nil }() + // parse `authFlowType` var authFlowType: AuthFlowType if case .boolean(let isMigrationEnabled) = cognitoUserPoolJSON.value(at: "MigrationEnabled"), isMigrationEnabled == true { @@ -56,14 +58,84 @@ struct ConfigurationHelper { authFlowType = .userSRP } + // parse `clientSecret` var clientSecret: String? if case .string(let clientSecretFromConfig) = cognitoUserPoolJSON.value(at: "AppClientSecret") { clientSecret = clientSecretFromConfig } + // parse `hostedUIConfig` let hostedUIConfig = parseHostedConfiguration( configuration: config.value(at: "Auth.Default.OAuth")) + // parse `passwordProtectionSettings` + let cognitoConfiguration = config.value(at: "Auth.Default") + var passwordProtectionSettings: PasswordProtectionSettings? + if case .object(let passwordSettings) = cognitoConfiguration?.value(at: "passwordProtectionSettings") { + + // parse `minLength` + var minLength: Int? + if case .number(let value) = passwordSettings["passwordPolicyMinLength"] { + minLength = Int(value) + } else if case .string(let value) = passwordSettings["passwordPolicyMinLength"], + let intValue = Int(value) { + minLength = intValue + } + + // parse `characterPolicy` + var characterPolicy: [PasswordCharacterPolicy] = [] + if case .array(let characters) = passwordSettings["passwordPolicyCharacters"] { + characterPolicy = characters.compactMap { value in + guard case .string(let string) = value else { + return nil + } + + return .init(rawValue: string) + } + } + + passwordProtectionSettings = PasswordProtectionSettings( + minLength: minLength, + characterPolicy: characterPolicy + ) + } + + // parse `usernameAttributes` + var usernameAttributes: [UsernameAttribute] = [] + if case .array(let attributes) = cognitoConfiguration?["usernameAttributes"] { + usernameAttributes = attributes.compactMap { value in + guard case .string(let string) = value else { + return nil + } + + return .init(rawValue: string) + } + } + + // parse `signUpAttributes` + var signUpAttributes: [SignUpAttributeType] = [] + if case .array(let attributes) = cognitoConfiguration?["signupAttributes"] { + signUpAttributes = attributes.compactMap { value in + guard case .string(let string) = value else { + return nil + } + + return .init(rawValue: string) + } + } + + // parse `verificationMechanisms` + var verificationMechanisms: [VerificationMechanism] = [] + if case .array(let attributes) = cognitoConfiguration?["verificationMechanisms"] { + verificationMechanisms = attributes.compactMap { value in + guard case .string(let string) = value else { + return nil + } + + return .init(rawValue: string) + } + } + return UserPoolConfigurationData(poolId: poolId, clientId: appClientId, region: region, @@ -71,7 +143,12 @@ struct ConfigurationHelper { clientSecret: clientSecret, pinpointAppId: pinpointId, authFlowType: authFlowType, - hostedUIConfig: hostedUIConfig) + hostedUIConfig: hostedUIConfig, + passwordProtectionSettings: passwordProtectionSettings, + usernameAttributes: usernameAttributes, + signUpAttributes: signUpAttributes, + verificationMechanisms: verificationMechanisms) + } static func parseHostedConfiguration(configuration: JSONValue?) -> HostedUIConfigurationData? { diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthSignInOptionsTestCase.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthSignInOptionsTestCase.swift index 3968b20914..dcb6a1c4d3 100644 --- a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthSignInOptionsTestCase.swift +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/SignIn/AWSAuthSignInOptionsTestCase.swift @@ -8,7 +8,7 @@ import XCTest import AWSCognitoIdentity @testable import Amplify -@testable import AWSCognitoAuthPlugin +@_spi(InternalAmplifyConfiguration) @testable import AWSCognitoAuthPlugin import AWSCognitoIdentityProvider import ClientRuntime From cb997027d5ee24d6fea82a5cd58dfb4bd967a909 Mon Sep 17 00:00:00 2001 From: Michael Law <1365977+lawmicha@users.noreply.github.com> Date: Wed, 13 Mar 2024 17:14:39 -0400 Subject: [PATCH 2/2] add missing spi, UInt password minLength --- .../CodeGen/Data/IdentityPoolConfigurationData.swift | 1 + .../CodeGen/Data/PasswordProtectionSettings.swift | 6 +++--- .../Support/Helpers/ConfigurationHelper.swift | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/IdentityPoolConfigurationData.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/IdentityPoolConfigurationData.swift index 764b4234f7..1ee2ed59ca 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/IdentityPoolConfigurationData.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/IdentityPoolConfigurationData.swift @@ -7,6 +7,7 @@ import Foundation +@_spi(InternalAmplifyConfiguration) public struct IdentityPoolConfigurationData: Equatable { let poolId: String let region: String diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/PasswordProtectionSettings.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/PasswordProtectionSettings.swift index bc3d682225..489f608780 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/PasswordProtectionSettings.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/PasswordProtectionSettings.swift @@ -10,10 +10,10 @@ import Amplify @_spi(InternalAmplifyConfiguration) public struct PasswordProtectionSettings: Equatable, Codable { - public var minLength: Int? - public var characterPolicy: [PasswordCharacterPolicy] + public let minLength: UInt + public let characterPolicy: [PasswordCharacterPolicy] - public init(minLength: Int?, + public init(minLength: UInt, characterPolicy: [PasswordCharacterPolicy]) { self.minLength = minLength self.characterPolicy = characterPolicy diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/ConfigurationHelper.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/ConfigurationHelper.swift index 24ebe31bc3..53fe6b82d5 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/ConfigurationHelper.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Helpers/ConfigurationHelper.swift @@ -74,11 +74,11 @@ struct ConfigurationHelper { if case .object(let passwordSettings) = cognitoConfiguration?.value(at: "passwordProtectionSettings") { // parse `minLength` - var minLength: Int? + var minLength: UInt = 0 if case .number(let value) = passwordSettings["passwordPolicyMinLength"] { - minLength = Int(value) + minLength = UInt(value) } else if case .string(let value) = passwordSettings["passwordPolicyMinLength"], - let intValue = Int(value) { + let intValue = UInt(value) { minLength = intValue }