Skip to content

Commit

Permalink
chore: kickoff release
Browse files Browse the repository at this point in the history
  • Loading branch information
Di Wu authored Aug 30, 2023
2 parents fac13ac + 8bbf5c7 commit 626c947
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ public enum MFAPreference {
}

extension MFAPreference {
var smsSetting: CognitoIdentityProviderClientTypes.SMSMfaSettingsType? {

func smsSetting(isCurrentlyPreferred: Bool = false) -> CognitoIdentityProviderClientTypes.SMSMfaSettingsType {
switch self {
case .enabled:
return .init(enabled: true)
return .init(enabled: true, preferredMfa: isCurrentlyPreferred)
case .preferred:
return .init(enabled: true, preferredMfa: true)
case .notPreferred:
Expand All @@ -39,11 +39,11 @@ extension MFAPreference {
return .init(enabled: false)
}
}
var softwareTokenSetting: CognitoIdentityProviderClientTypes.SoftwareTokenMfaSettingsType? {

func softwareTokenSetting(isCurrentlyPreferred: Bool = false) -> CognitoIdentityProviderClientTypes.SoftwareTokenMfaSettingsType {
switch self {
case .enabled:
return .init(enabled: true)
return .init(enabled: true, preferredMfa: isCurrentlyPreferred)
case .preferred:
return .init(enabled: true, preferredMfa: true)
case .notPreferred:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ class HostedUIASWebAuthenticationSession: NSObject, HostedUISessionBehavior {
let queryItems = urlComponents?.queryItems ?? []

if let error = queryItems.first(where: { $0.name == "error" })?.value {
callback(.failure(.serviceMessage(error)))
let errorDescription = queryItems.first(
where: { $0.name == "error_description" }
)?.value?.trim() ?? ""
let message = "\(error) \(errorDescription)"
callback(.failure(.serviceMessage(message)))
return
}
callback(.success(queryItems))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ class UpdateMFAPreferenceTask: AuthUpdateMFAPreferenceTask, DefaultLogger {

func updateMFAPreference(with accessToken: String) async throws {
let userPoolService = try userPoolFactory()
let currentPreference = try await userPoolService.getUser(input: .init(accessToken: accessToken))
let preferredMFAType = currentPreference.preferredMfaSetting.map(MFAType.init(rawValue:))
let input = SetUserMFAPreferenceInput(
accessToken: accessToken,
smsMfaSettings: smsPreference?.smsSetting,
softwareTokenMfaSettings: totpPreference?.softwareTokenSetting)
smsMfaSettings: smsPreference?.smsSetting(isCurrentlyPreferred: preferredMFAType == .sms),
softwareTokenMfaSettings: totpPreference?.softwareTokenSetting(isCurrentlyPreferred: preferredMFAType == .totp))
_ = try await userPoolService.setUserMFAPreference(input: input)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,18 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
for smsPreference in allSMSPreferences {
for totpPreference in allTOTPPreference {
self.mockIdentityProvider = MockIdentityProvider(
mockGetUserAttributeResponse: { request in
return .init(
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
)
},
mockSetUserMFAPreferenceResponse: { request in
XCTAssertEqual(
request.smsMfaSettings,
smsPreference.smsSetting)
smsPreference.smsSetting())
XCTAssertEqual(
request.softwareTokenMfaSettings,
totpPreference.softwareTokenSetting)
totpPreference.softwareTokenSetting())

return .init()
})
Expand Down Expand Up @@ -67,9 +72,17 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
///
func testUpdateMFAPreferenceWithInternalErrorException() async throws {

mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.unknown(.init(httpResponse: .init(body: .empty, statusCode: .ok)))
})
mockIdentityProvider = MockIdentityProvider(
mockGetUserAttributeResponse: { request in
return .init(
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
)
},
mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.unknown(.init(httpResponse: .init(body: .empty, statusCode: .ok)))
}
)

do {
_ = try await plugin.updateMFAPreference(sms: .enabled, totp: .enabled)
Expand All @@ -92,9 +105,17 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
///
func testUpdateMFAPreferenceWithInvalidParameterException() async throws {

mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.invalidParameterException(.init())
})
mockIdentityProvider = MockIdentityProvider(
mockGetUserAttributeResponse: { request in
return .init(
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
)
},
mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.invalidParameterException(.init())
}
)

do {
_ = try await plugin.updateMFAPreference(sms: .enabled, totp: .enabled)
Expand All @@ -121,9 +142,17 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
///
func testUpdateMFAPreferenceWithNotAuthorizedException() async throws {

mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.notAuthorizedException(.init(message: "message"))
})
mockIdentityProvider = MockIdentityProvider(
mockGetUserAttributeResponse: { request in
return .init(
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
)
},
mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.notAuthorizedException(.init(message: "message"))
}
)

do {
_ = try await plugin.updateMFAPreference(sms: .enabled, totp: .enabled)
Expand All @@ -148,9 +177,17 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
///
func testUpdateMFAPreferenceWithPasswordResetRequiredException() async throws {

mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.passwordResetRequiredException(.init())
})
mockIdentityProvider = MockIdentityProvider(
mockGetUserAttributeResponse: { request in
return .init(
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
)
},
mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.passwordResetRequiredException(.init())
}
)

do {
_ = try await plugin.updateMFAPreference(sms: .enabled, totp: .enabled)
Expand Down Expand Up @@ -179,9 +216,17 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
///
func testUpdateMFAPreferenceWithResourceNotFoundException() async throws {

mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.resourceNotFoundException(.init())
})
mockIdentityProvider = MockIdentityProvider(
mockGetUserAttributeResponse: { request in
return .init(
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
)
},
mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.resourceNotFoundException(.init())
}
)

do {
_ = try await plugin.updateMFAPreference(sms: .enabled, totp: .enabled)
Expand Down Expand Up @@ -210,9 +255,17 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
///
func testUpdateMFAPreferenceWithForbiddenException() async throws {

mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.forbiddenException(.init())
})
mockIdentityProvider = MockIdentityProvider(
mockGetUserAttributeResponse: { request in
return .init(
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
)
},
mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.forbiddenException(.init())
}
)

do {
_ = try await plugin.updateMFAPreference(sms: .enabled, totp: .enabled)
Expand All @@ -237,9 +290,17 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
///
func testUpdateMFAPreferenceWithUserNotConfirmedException() async throws {

mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.userNotConfirmedException(.init())
})
mockIdentityProvider = MockIdentityProvider(
mockGetUserAttributeResponse: { request in
return .init(
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
)
},
mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.userNotConfirmedException(.init())
}
)
do {
_ = try await plugin.updateMFAPreference(sms: .enabled, totp: .enabled)
XCTFail("Should return an error if the result from service is invalid")
Expand Down Expand Up @@ -267,9 +328,17 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
///
func testUpdateMFAPreferenceWithUserNotFoundException() async throws {

mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.userNotFoundException(.init())
})
mockIdentityProvider = MockIdentityProvider(
mockGetUserAttributeResponse: { request in
return .init(
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
)
},
mockSetUserMFAPreferenceResponse: { _ in
throw SetUserMFAPreferenceOutputError.userNotFoundException(.init())
}
)
do {
_ = try await plugin.updateMFAPreference(sms: .enabled, totp: .enabled)
XCTFail("Should return an error if the result from service is invalid")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,15 +258,22 @@ class AWSAuthHostedUISignInTests: XCTestCase {
}

@MainActor
func testTokenErrorResponse() async {
/// Given: A HostedUI response with `error` and `error_description` query parameters.
/// When: Invoking `signInWithWebUI`
/// Then: The caller should receive an `AuthError.service` where the `errorDescription`
/// is `"\(error) \(error_description)"`
func testTokenErrorResponse() async throws {
mockHostedUIResult = .success([
.init(name: "state", value: mockState),
.init(name: "code", value: mockProof)
])

let (errorMessage, errorDescription) = ("invalid_grant", "Some error")
mockTokenResult = [
"error": "invalid_grant",
"error_description": "Some error"] as [String: Any]
mockJson = try! JSONSerialization.data(withJSONObject: mockTokenResult)
"error": errorMessage,
"error_description": errorDescription
]
mockJson = try JSONSerialization.data(withJSONObject: mockTokenResult)
MockURLProtocol.requestHandler = { _ in
return (HTTPURLResponse(), self.mockJson)
}
Expand All @@ -276,13 +283,15 @@ class AWSAuthHostedUISignInTests: XCTestCase {
_ = try await plugin.signInWithWebUI(presentationAnchor: ASPresentationAnchor(), options: nil)
XCTFail("Should not succeed")
} catch {
guard case AuthError.service = error else {
guard case AuthError.service(let message, _, _) = error else {
XCTFail("Should not fail with error = \(error)")
return
}
let expectedErrorDescription = "\(errorMessage) \(errorDescription)"
XCTAssertEqual(expectedErrorDescription, message)
expectation.fulfill()
}
wait(for: [expectation], timeout: networkTimeout)
await fulfillment(of: [expectation], timeout: networkTimeout)
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,54 @@ class MFAPreferenceTests: AWSAuthBaseTest {
return
}
}

}

/// Test successful call to fetchMFAPreference and updateMFAPreference API for SMS and TOTP for already preferred MFA method
///
/// - Given: A newly signed up user in Cognito user pool
/// - When:
/// - I invoke fetchMFAPreference and updateMFAPreference API under various conditions
/// - Then:
/// - I should get valid fetchMFAPreference results corresponding to the updateMFAPreference
///
func testFetchAndUpdateMFAPreferenceForAlreadyPreferredMethod() async throws {
do {
try await signUpAndSignIn(phoneNumber: "+16135550116") // Fake number for testing

let authCognitoPlugin = try Amplify.Auth.getPlugin(
for: "awsCognitoAuthPlugin") as! AWSCognitoAuthPlugin

var fetchMFAResult = try await authCognitoPlugin.fetchMFAPreference()
XCTAssertNil(fetchMFAResult.enabled)
XCTAssertNil(fetchMFAResult.preferred)

let totpSetupDetails = try await Amplify.Auth.setUpTOTP()
let totpCode = TOTPHelper.generateTOTPCode(sharedSecret: totpSetupDetails.sharedSecret)
try await Amplify.Auth.verifyTOTPSetup(code: totpCode)

// Test SMS as preferred, TOTP as enabled
try await authCognitoPlugin.updateMFAPreference(
sms: .preferred,
totp: .enabled)

fetchMFAResult = try await authCognitoPlugin.fetchMFAPreference()
XCTAssertEqual(fetchMFAResult.enabled, [.sms, .totp])
XCTAssertEqual(fetchMFAResult.preferred, .sms)

// Verify marking enabled does not change preference
try await authCognitoPlugin.updateMFAPreference(
sms: .enabled,
totp: .enabled
)

fetchMFAResult = try await authCognitoPlugin.fetchMFAPreference()
XCTAssertEqual(fetchMFAResult.enabled, [.sms, .totp])
XCTAssertEqual(fetchMFAResult.preferred, .sms)

} catch {
XCTFail("API should succeed without any errors instead failed with \(error)")
}
}

}
14 changes: 1 addition & 13 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,7 @@

### Features

- **Auth**: Updating the Sign In resolver to add new cases (#3104)
- **Auth**: Remove options from API's (#3075)
- **Auth**: Removed deviceNotFound TODO which is not applicable for TOTP (#3074)
- **Auth**: Enable tests for watchOS and tvOS (#3073)
- **Auth**: Worked on review comments and cleaning up unit tests (#3071)
- **Auth**: Adding TOTP integration tests (#3047)
- **Auth**: Add unit tests for TOTP (#3046)
- **Auth**: Adding TOTP states, events, data models and resolvers (#3045)
- **Auth**: Adding TOTP state machine actions (#3044)
- **Auth**: Adding TOTP tasks and requests to AWSAuthCognitoPlugin (#3043)
- **Auth**: Adding TOTP service behaviour (#3042)
- **Auth**: Adding TOTP related models to AWSCognitoPlugin (#3041)
- **Auth**: Adding TOTP support in Amplify Auth category (#3040)
- **Auth**: Added TOTP support in Auth plugin (#3072)

## 2.15.5 (2023-08-28)

Expand Down

0 comments on commit 626c947

Please sign in to comment.