Skip to content

Commit 626c947

Browse files
author
Di Wu
authored
chore: kickoff release
2 parents fac13ac + 8bbf5c7 commit 626c947

File tree

7 files changed

+174
-54
lines changed

7 files changed

+174
-54
lines changed

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Models/MFAPreference.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ public enum MFAPreference {
2626
}
2727

2828
extension MFAPreference {
29-
30-
var smsSetting: CognitoIdentityProviderClientTypes.SMSMfaSettingsType? {
29+
30+
func smsSetting(isCurrentlyPreferred: Bool = false) -> CognitoIdentityProviderClientTypes.SMSMfaSettingsType {
3131
switch self {
3232
case .enabled:
33-
return .init(enabled: true)
33+
return .init(enabled: true, preferredMfa: isCurrentlyPreferred)
3434
case .preferred:
3535
return .init(enabled: true, preferredMfa: true)
3636
case .notPreferred:
@@ -39,11 +39,11 @@ extension MFAPreference {
3939
return .init(enabled: false)
4040
}
4141
}
42-
43-
var softwareTokenSetting: CognitoIdentityProviderClientTypes.SoftwareTokenMfaSettingsType? {
42+
43+
func softwareTokenSetting(isCurrentlyPreferred: Bool = false) -> CognitoIdentityProviderClientTypes.SoftwareTokenMfaSettingsType {
4444
switch self {
4545
case .enabled:
46-
return .init(enabled: true)
46+
return .init(enabled: true, preferredMfa: isCurrentlyPreferred)
4747
case .preferred:
4848
return .init(enabled: true, preferredMfa: true)
4949
case .notPreferred:

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/HostedUI/HostedUIASWebAuthenticationSession.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ class HostedUIASWebAuthenticationSession: NSObject, HostedUISessionBehavior {
3131
let queryItems = urlComponents?.queryItems ?? []
3232

3333
if let error = queryItems.first(where: { $0.name == "error" })?.value {
34-
callback(.failure(.serviceMessage(error)))
34+
let errorDescription = queryItems.first(
35+
where: { $0.name == "error_description" }
36+
)?.value?.trim() ?? ""
37+
let message = "\(error) \(errorDescription)"
38+
callback(.failure(.serviceMessage(message)))
3539
return
3640
}
3741
callback(.success(queryItems))

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Task/UpdateMFAPreferenceTask.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,12 @@ class UpdateMFAPreferenceTask: AuthUpdateMFAPreferenceTask, DefaultLogger {
6161

6262
func updateMFAPreference(with accessToken: String) async throws {
6363
let userPoolService = try userPoolFactory()
64+
let currentPreference = try await userPoolService.getUser(input: .init(accessToken: accessToken))
65+
let preferredMFAType = currentPreference.preferredMfaSetting.map(MFAType.init(rawValue:))
6466
let input = SetUserMFAPreferenceInput(
6567
accessToken: accessToken,
66-
smsMfaSettings: smsPreference?.smsSetting,
67-
softwareTokenMfaSettings: totpPreference?.softwareTokenSetting)
68+
smsMfaSettings: smsPreference?.smsSetting(isCurrentlyPreferred: preferredMFAType == .sms),
69+
softwareTokenMfaSettings: totpPreference?.softwareTokenSetting(isCurrentlyPreferred: preferredMFAType == .totp))
6870
_ = try await userPoolService.setUserMFAPreference(input: input)
6971
}
7072
}

AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/ClientBehaviorTests/MFA/UpdateMFAPreferenceTaskTests.swift

Lines changed: 95 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,18 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
3333
for smsPreference in allSMSPreferences {
3434
for totpPreference in allTOTPPreference {
3535
self.mockIdentityProvider = MockIdentityProvider(
36+
mockGetUserAttributeResponse: { request in
37+
return .init(
38+
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
39+
)
40+
},
3641
mockSetUserMFAPreferenceResponse: { request in
3742
XCTAssertEqual(
3843
request.smsMfaSettings,
39-
smsPreference.smsSetting)
44+
smsPreference.smsSetting())
4045
XCTAssertEqual(
4146
request.softwareTokenMfaSettings,
42-
totpPreference.softwareTokenSetting)
47+
totpPreference.softwareTokenSetting())
4348

4449
return .init()
4550
})
@@ -67,9 +72,17 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
6772
///
6873
func testUpdateMFAPreferenceWithInternalErrorException() async throws {
6974

70-
mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
71-
throw SetUserMFAPreferenceOutputError.unknown(.init(httpResponse: .init(body: .empty, statusCode: .ok)))
72-
})
75+
mockIdentityProvider = MockIdentityProvider(
76+
mockGetUserAttributeResponse: { request in
77+
return .init(
78+
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
79+
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
80+
)
81+
},
82+
mockSetUserMFAPreferenceResponse: { _ in
83+
throw SetUserMFAPreferenceOutputError.unknown(.init(httpResponse: .init(body: .empty, statusCode: .ok)))
84+
}
85+
)
7386

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

95-
mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
96-
throw SetUserMFAPreferenceOutputError.invalidParameterException(.init())
97-
})
108+
mockIdentityProvider = MockIdentityProvider(
109+
mockGetUserAttributeResponse: { request in
110+
return .init(
111+
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
112+
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
113+
)
114+
},
115+
mockSetUserMFAPreferenceResponse: { _ in
116+
throw SetUserMFAPreferenceOutputError.invalidParameterException(.init())
117+
}
118+
)
98119

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

124-
mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
125-
throw SetUserMFAPreferenceOutputError.notAuthorizedException(.init(message: "message"))
126-
})
145+
mockIdentityProvider = MockIdentityProvider(
146+
mockGetUserAttributeResponse: { request in
147+
return .init(
148+
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
149+
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
150+
)
151+
},
152+
mockSetUserMFAPreferenceResponse: { _ in
153+
throw SetUserMFAPreferenceOutputError.notAuthorizedException(.init(message: "message"))
154+
}
155+
)
127156

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

151-
mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
152-
throw SetUserMFAPreferenceOutputError.passwordResetRequiredException(.init())
153-
})
180+
mockIdentityProvider = MockIdentityProvider(
181+
mockGetUserAttributeResponse: { request in
182+
return .init(
183+
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
184+
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
185+
)
186+
},
187+
mockSetUserMFAPreferenceResponse: { _ in
188+
throw SetUserMFAPreferenceOutputError.passwordResetRequiredException(.init())
189+
}
190+
)
154191

155192
do {
156193
_ = try await plugin.updateMFAPreference(sms: .enabled, totp: .enabled)
@@ -179,9 +216,17 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
179216
///
180217
func testUpdateMFAPreferenceWithResourceNotFoundException() async throws {
181218

182-
mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
183-
throw SetUserMFAPreferenceOutputError.resourceNotFoundException(.init())
184-
})
219+
mockIdentityProvider = MockIdentityProvider(
220+
mockGetUserAttributeResponse: { request in
221+
return .init(
222+
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
223+
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
224+
)
225+
},
226+
mockSetUserMFAPreferenceResponse: { _ in
227+
throw SetUserMFAPreferenceOutputError.resourceNotFoundException(.init())
228+
}
229+
)
185230

186231
do {
187232
_ = try await plugin.updateMFAPreference(sms: .enabled, totp: .enabled)
@@ -210,9 +255,17 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
210255
///
211256
func testUpdateMFAPreferenceWithForbiddenException() async throws {
212257

213-
mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
214-
throw SetUserMFAPreferenceOutputError.forbiddenException(.init())
215-
})
258+
mockIdentityProvider = MockIdentityProvider(
259+
mockGetUserAttributeResponse: { request in
260+
return .init(
261+
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
262+
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
263+
)
264+
},
265+
mockSetUserMFAPreferenceResponse: { _ in
266+
throw SetUserMFAPreferenceOutputError.forbiddenException(.init())
267+
}
268+
)
216269

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

240-
mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
241-
throw SetUserMFAPreferenceOutputError.userNotConfirmedException(.init())
242-
})
293+
mockIdentityProvider = MockIdentityProvider(
294+
mockGetUserAttributeResponse: { request in
295+
return .init(
296+
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
297+
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
298+
)
299+
},
300+
mockSetUserMFAPreferenceResponse: { _ in
301+
throw SetUserMFAPreferenceOutputError.userNotConfirmedException(.init())
302+
}
303+
)
243304
do {
244305
_ = try await plugin.updateMFAPreference(sms: .enabled, totp: .enabled)
245306
XCTFail("Should return an error if the result from service is invalid")
@@ -267,9 +328,17 @@ class UpdateMFAPreferenceTaskTests: BasePluginTest {
267328
///
268329
func testUpdateMFAPreferenceWithUserNotFoundException() async throws {
269330

270-
mockIdentityProvider = MockIdentityProvider(mockSetUserMFAPreferenceResponse: { _ in
271-
throw SetUserMFAPreferenceOutputError.userNotFoundException(.init())
272-
})
331+
mockIdentityProvider = MockIdentityProvider(
332+
mockGetUserAttributeResponse: { request in
333+
return .init(
334+
preferredMfaSetting: "SOFTWARE_TOKEN_MFA",
335+
userMFASettingList: ["SOFTWARE_TOKEN_MFA", "SMS_MFA"]
336+
)
337+
},
338+
mockSetUserMFAPreferenceResponse: { _ in
339+
throw SetUserMFAPreferenceOutputError.userNotFoundException(.init())
340+
}
341+
)
273342
do {
274343
_ = try await plugin.updateMFAPreference(sms: .enabled, totp: .enabled)
275344
XCTFail("Should return an error if the result from service is invalid")

AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/TaskTests/HostedUITests/AWSAuthHostedUISignInTests.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,15 +258,22 @@ class AWSAuthHostedUISignInTests: XCTestCase {
258258
}
259259

260260
@MainActor
261-
func testTokenErrorResponse() async {
261+
/// Given: A HostedUI response with `error` and `error_description` query parameters.
262+
/// When: Invoking `signInWithWebUI`
263+
/// Then: The caller should receive an `AuthError.service` where the `errorDescription`
264+
/// is `"\(error) \(error_description)"`
265+
func testTokenErrorResponse() async throws {
262266
mockHostedUIResult = .success([
263267
.init(name: "state", value: mockState),
264268
.init(name: "code", value: mockProof)
265269
])
270+
271+
let (errorMessage, errorDescription) = ("invalid_grant", "Some error")
266272
mockTokenResult = [
267-
"error": "invalid_grant",
268-
"error_description": "Some error"] as [String: Any]
269-
mockJson = try! JSONSerialization.data(withJSONObject: mockTokenResult)
273+
"error": errorMessage,
274+
"error_description": errorDescription
275+
]
276+
mockJson = try JSONSerialization.data(withJSONObject: mockTokenResult)
270277
MockURLProtocol.requestHandler = { _ in
271278
return (HTTPURLResponse(), self.mockJson)
272279
}
@@ -276,13 +283,15 @@ class AWSAuthHostedUISignInTests: XCTestCase {
276283
_ = try await plugin.signInWithWebUI(presentationAnchor: ASPresentationAnchor(), options: nil)
277284
XCTFail("Should not succeed")
278285
} catch {
279-
guard case AuthError.service = error else {
286+
guard case AuthError.service(let message, _, _) = error else {
280287
XCTFail("Should not fail with error = \(error)")
281288
return
282289
}
290+
let expectedErrorDescription = "\(errorMessage) \(errorDescription)"
291+
XCTAssertEqual(expectedErrorDescription, message)
283292
expectation.fulfill()
284293
}
285-
wait(for: [expectation], timeout: networkTimeout)
294+
await fulfillment(of: [expectation], timeout: networkTimeout)
286295
}
287296

288297

AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/MFATests/MFAPreferenceTests.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,54 @@ class MFAPreferenceTests: AWSAuthBaseTest {
306306
return
307307
}
308308
}
309+
310+
}
311+
312+
/// Test successful call to fetchMFAPreference and updateMFAPreference API for SMS and TOTP for already preferred MFA method
313+
///
314+
/// - Given: A newly signed up user in Cognito user pool
315+
/// - When:
316+
/// - I invoke fetchMFAPreference and updateMFAPreference API under various conditions
317+
/// - Then:
318+
/// - I should get valid fetchMFAPreference results corresponding to the updateMFAPreference
319+
///
320+
func testFetchAndUpdateMFAPreferenceForAlreadyPreferredMethod() async throws {
321+
do {
322+
try await signUpAndSignIn(phoneNumber: "+16135550116") // Fake number for testing
323+
324+
let authCognitoPlugin = try Amplify.Auth.getPlugin(
325+
for: "awsCognitoAuthPlugin") as! AWSCognitoAuthPlugin
326+
327+
var fetchMFAResult = try await authCognitoPlugin.fetchMFAPreference()
328+
XCTAssertNil(fetchMFAResult.enabled)
329+
XCTAssertNil(fetchMFAResult.preferred)
330+
331+
let totpSetupDetails = try await Amplify.Auth.setUpTOTP()
332+
let totpCode = TOTPHelper.generateTOTPCode(sharedSecret: totpSetupDetails.sharedSecret)
333+
try await Amplify.Auth.verifyTOTPSetup(code: totpCode)
334+
335+
// Test SMS as preferred, TOTP as enabled
336+
try await authCognitoPlugin.updateMFAPreference(
337+
sms: .preferred,
338+
totp: .enabled)
339+
340+
fetchMFAResult = try await authCognitoPlugin.fetchMFAPreference()
341+
XCTAssertEqual(fetchMFAResult.enabled, [.sms, .totp])
342+
XCTAssertEqual(fetchMFAResult.preferred, .sms)
343+
344+
// Verify marking enabled does not change preference
345+
try await authCognitoPlugin.updateMFAPreference(
346+
sms: .enabled,
347+
totp: .enabled
348+
)
349+
350+
fetchMFAResult = try await authCognitoPlugin.fetchMFAPreference()
351+
XCTAssertEqual(fetchMFAResult.enabled, [.sms, .totp])
352+
XCTAssertEqual(fetchMFAResult.preferred, .sms)
353+
354+
} catch {
355+
XCTFail("API should succeed without any errors instead failed with \(error)")
356+
}
309357
}
310358

311359
}

CHANGELOG.md

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,7 @@
44

55
### Features
66

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

219
## 2.15.5 (2023-08-28)
2210

0 commit comments

Comments
 (0)