Skip to content

Commit c892d1f

Browse files
committed
add underlying cognito error for not authorized errors
1 parent ee6b2fa commit c892d1f

File tree

3 files changed

+121
-2
lines changed

3 files changed

+121
-2
lines changed

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AWSCognitoIdentity+AuthErrorConvertible.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ extension AWSCognitoIdentity.NotAuthorizedException: AuthErrorConvertible {
6161
var authError: AuthError {
6262
.notAuthorized(
6363
properties.message ?? fallbackDescription,
64-
AuthPluginErrorConstants.notAuthorizedError
64+
AuthPluginErrorConstants.notAuthorizedError,
65+
self
6566
)
6667
}
6768
}

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Service/ErrorMapping/AWSCongnitoIdentityProvider+AuthErrorConvertible.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ extension NotAuthorizedException: AuthErrorConvertible {
7272
var authError: AuthError {
7373
.notAuthorized(
7474
properties.message ?? fallbackDescription,
75-
AuthPluginErrorConstants.notAuthorizedError
75+
AuthPluginErrorConstants.notAuthorizedError,
76+
self
7677
)
7778
}
7879
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import AWSCognitoIdentityProvider
9+
import XCTest
10+
@testable import Amplify
11+
@testable import AWSCognitoAuthPlugin
12+
13+
/// Tests for GitHub issue #4089: [Auth] Underlying Error is always nil
14+
/// https://github.com/aws-amplify/amplify-swift/issues/4089
15+
///
16+
/// When a NotAuthorizedException occurs during sign-in, the underlying error
17+
/// should be preserved so developers can distinguish between different failure
18+
/// reasons (e.g., "Incorrect username or password" vs "Password attempts exceeded").
19+
class AWSAuthSignInNotAuthorizedErrorTests: BasePluginTest {
20+
21+
override var initialState: AuthState {
22+
AuthState.configured(.signedOut(.init(lastKnownUserName: nil)), .configured, .notStarted)
23+
}
24+
25+
/// Test that NotAuthorizedException preserves the underlying error
26+
///
27+
/// - Given: An auth plugin with mocked service that throws NotAuthorizedException
28+
///
29+
/// - When:
30+
/// - I invoke signIn with incorrect credentials
31+
/// - Then:
32+
/// - I should get a .notAuthorized error with the underlying NotAuthorizedException preserved
33+
///
34+
func testSignInNotAuthorizedErrorPreservesUnderlyingError() async {
35+
let expectedErrorMessage = "Incorrect username or password."
36+
37+
mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in
38+
throw AWSCognitoIdentityProvider.NotAuthorizedException(
39+
message: expectedErrorMessage
40+
)
41+
})
42+
43+
let options = AuthSignInRequest.Options()
44+
45+
do {
46+
let result = try await plugin.signIn(username: "username", password: "wrongpassword", options: options)
47+
XCTFail("Should not receive a success response \(result)")
48+
} catch let error as AuthError {
49+
guard case .notAuthorized(let errorDescription, _, let underlyingError) = error else {
50+
XCTFail("Should receive notAuthorized error instead got \(error)")
51+
return
52+
}
53+
54+
// Verify the error description contains the expected message
55+
XCTAssertEqual(errorDescription, expectedErrorMessage)
56+
57+
// Verify the underlying error is NOT nil (this is the fix for issue #4089)
58+
XCTAssertNotNil(underlyingError, "Underlying error should not be nil - this is the fix for issue #4089")
59+
60+
// Verify the underlying error is the original NotAuthorizedException
61+
XCTAssertTrue(
62+
underlyingError is AWSCognitoIdentityProvider.NotAuthorizedException,
63+
"Underlying error should be NotAuthorizedException"
64+
)
65+
} catch {
66+
XCTFail("Received unexpected error type: \(error)")
67+
}
68+
}
69+
70+
/// Test that different NotAuthorizedException messages are preserved
71+
///
72+
/// - Given: An auth plugin with mocked service that throws NotAuthorizedException
73+
/// with "Password attempts exceeded" message
74+
///
75+
/// - When:
76+
/// - I invoke signIn
77+
/// - Then:
78+
/// - I should get a .notAuthorized error with the specific message preserved
79+
///
80+
func testSignInPasswordAttemptsExceededPreservesUnderlyingError() async {
81+
let expectedErrorMessage = "Password attempts exceeded"
82+
83+
mockIdentityProvider = MockIdentityProvider(mockInitiateAuthResponse: { _ in
84+
throw AWSCognitoIdentityProvider.NotAuthorizedException(
85+
message: expectedErrorMessage
86+
)
87+
})
88+
89+
let options = AuthSignInRequest.Options()
90+
91+
do {
92+
let result = try await plugin.signIn(username: "username", password: "password", options: options)
93+
XCTFail("Should not receive a success response \(result)")
94+
} catch let error as AuthError {
95+
guard case .notAuthorized(let errorDescription, _, let underlyingError) = error else {
96+
XCTFail("Should receive notAuthorized error instead got \(error)")
97+
return
98+
}
99+
100+
// Verify the error description contains the expected message
101+
XCTAssertEqual(errorDescription, expectedErrorMessage)
102+
103+
// Verify the underlying error is preserved
104+
XCTAssertNotNil(underlyingError, "Underlying error should not be nil")
105+
106+
// Verify we can cast and inspect the underlying error
107+
if let notAuthorizedException = underlyingError as? AWSCognitoIdentityProvider.NotAuthorizedException {
108+
// The message is stored in properties.message
109+
XCTAssertEqual(notAuthorizedException.properties.message, expectedErrorMessage)
110+
} else {
111+
XCTFail("Should be able to cast underlying error to NotAuthorizedException")
112+
}
113+
} catch {
114+
XCTFail("Received unexpected error type: \(error)")
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)