Skip to content

Commit 833c7bb

Browse files
authored
Merge pull request #3938 from aws-amplify/3937
* fix(auth): fix credential decoding * update comments * update comment * add unit tests * test fix * failed build
2 parents 06207f4 + fb0eb55 commit 833c7bb

File tree

3 files changed

+205
-18
lines changed

3 files changed

+205
-18
lines changed

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/CredentialStorage/AWSCognitoAuthCredentialStore.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ extension AWSCognitoAuthCredentialStore: AmplifyAuthCredentialStoreBehavior {
133133
func retrieveCredential() throws -> AmplifyCredentials {
134134
let authCredentialStoreKey = generateSessionKey(for: authConfiguration)
135135
let authCredentialData = try keychain._getData(authCredentialStoreKey)
136-
let awsCredential: AmplifyCredentials = try decode(data: authCredentialData)
137-
return awsCredential
136+
let amplifyCredential: AmplifyCredentials = try decode(data: authCredentialData)
137+
return amplifyCredential
138138
}
139139

140140
func deleteCredential() throws {
@@ -191,15 +191,15 @@ private extension AWSCognitoAuthCredentialStore {
191191
do {
192192
return try JSONEncoder().encode(object)
193193
} catch {
194-
throw KeychainStoreError.codingError("Error occurred while encoding AWSCredentials", error)
194+
throw KeychainStoreError.codingError("Error occurred while encoding credentials", error)
195195
}
196196
}
197197

198198
func decode<T: Decodable>(data: Data) throws -> T {
199199
do {
200200
return try JSONDecoder().decode(T.self, from: data)
201201
} catch {
202-
throw KeychainStoreError.codingError("Error occurred while decoding AWSCredentials", error)
202+
throw KeychainStoreError.codingError("Error occurred while decoding credentials", error)
203203
}
204204
}
205205

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

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ public enum AuthFlowType {
3636

3737
internal init?(rawValue: String) {
3838
switch rawValue {
39-
case "CUSTOM_AUTH":
39+
case "CUSTOM_AUTH", "CUSTOM_AUTH_WITH_SRP":
4040
self = .customWithSRP
41+
case "CUSTOM_AUTH_WITHOUT_SRP":
42+
self = .customWithoutSRP
4143
case "USER_SRP_AUTH":
4244
self = .userSRP
4345
case "USER_PASSWORD_AUTH":
@@ -51,8 +53,10 @@ public enum AuthFlowType {
5153

5254
var rawValue: String {
5355
switch self {
54-
case .custom, .customWithSRP, .customWithoutSRP:
55-
return "CUSTOM_AUTH"
56+
case .custom, .customWithSRP:
57+
return "CUSTOM_AUTH_WITH_SRP"
58+
case .customWithoutSRP:
59+
return "CUSTOM_AUTH_WITHOUT_SRP"
5660
case .userSRP:
5761
return "USER_SRP_AUTH"
5862
case .userPassword:
@@ -62,6 +66,24 @@ public enum AuthFlowType {
6266
}
6367
}
6468

69+
// This initializer has been added to migrate credentials that were created in the pre-passwordless era
70+
internal static func legacyInit(rawValue: String) -> Self? {
71+
switch rawValue {
72+
case "userSRP":
73+
return .userSRP
74+
case "userPassword":
75+
return .userPassword
76+
case "custom":
77+
return .custom
78+
case "customWithSRP":
79+
return .customWithSRP
80+
case "customWithoutSRP":
81+
return .customWithoutSRP
82+
default:
83+
return nil
84+
}
85+
}
86+
6587
public static var userAuth: AuthFlowType {
6688
return .userAuth(preferredFirstFactor: nil)
6789
}
@@ -110,27 +132,49 @@ extension AuthFlowType: Codable {
110132

111133
// Decoding the enum
112134
public init(from decoder: Decoder) throws {
113-
let container = try decoder.container(keyedBy: CodingKeys.self)
135+
let container: KeyedDecodingContainer<CodingKeys>
136+
do {
137+
container = try decoder.container(keyedBy: CodingKeys.self)
138+
} catch DecodingError.typeMismatch {
139+
// The type mismatch has been added to handle a scenario where the user is migrating passwordless flows.
140+
// Passwordless flow added a new enum case with a associated type.
141+
// The association resulted in encoding structure changes that is different from the non-passwordless flows.
142+
// The structure change causes the type mismatch exception and this code block tries to retrieve the legacy structure and decode it.
143+
let legacyContainer = try decoder.singleValueContainer()
144+
let type = try legacyContainer.decode(String.self)
145+
guard let authFlowType = AuthFlowType.legacyInit(rawValue: type) else {
146+
throw DecodingError.dataCorruptedError(in: legacyContainer, debugDescription: "Invalid AuthFlowType value")
147+
}
148+
self = authFlowType
149+
return
150+
} catch {
151+
throw error
152+
}
114153

115-
// Decode the type (raw value)
116154
let type = try container.decode(String.self, forKey: .type)
117155

118156
// Initialize based on the type
119157
switch type {
120158
case "USER_SRP_AUTH":
121159
self = .userSRP
122-
case "CUSTOM_AUTH":
123-
// Depending on your needs, choose either `.custom`, `.customWithSRP`, or `.customWithoutSRP`
124-
// In this case, we'll default to `.custom`
125-
self = .custom
160+
case "CUSTOM_AUTH", "CUSTOM_AUTH_WITH_SRP":
161+
self = .customWithSRP
162+
case "CUSTOM_AUTH_WITHOUT_SRP":
163+
self = .customWithoutSRP
126164
case "USER_PASSWORD_AUTH":
127165
self = .userPassword
128166
case "USER_AUTH":
129-
let preferredFirstFactorString = try container.decode(String.self, forKey: .preferredFirstFactor)
130-
if let preferredFirstFactor = AuthFactorType(rawValue: preferredFirstFactorString) {
131-
self = .userAuth(preferredFirstFactor: preferredFirstFactor)
167+
if let preferredFirstFactorString = try container.decodeIfPresent(String.self, forKey: .preferredFirstFactor) {
168+
if let preferredFirstFactor = AuthFactorType(rawValue: preferredFirstFactorString) {
169+
self = .userAuth(preferredFirstFactor: preferredFirstFactor)
170+
} else {
171+
throw DecodingError.dataCorruptedError(
172+
forKey: .preferredFirstFactor,
173+
in: container,
174+
debugDescription: "Unable to decode preferredFirstFactor value")
175+
}
132176
} else {
133-
throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Unable to decode preferredFirstFactor value")
177+
self = .userAuth(preferredFirstFactor: nil)
134178
}
135179
default:
136180
throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Invalid AuthFlowType value")
@@ -152,5 +196,4 @@ extension AuthFlowType {
152196
return .userAuth
153197
}
154198
}
155-
156199
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
9+
import XCTest
10+
@testable import AWSCognitoAuthPlugin
11+
12+
class AuthFlowTypeTests: XCTestCase {
13+
14+
func testRawValue() {
15+
XCTAssertEqual(AuthFlowType.userSRP.rawValue, "USER_SRP_AUTH")
16+
XCTAssertEqual(AuthFlowType.customWithSRP.rawValue, "CUSTOM_AUTH_WITH_SRP")
17+
XCTAssertEqual(AuthFlowType.customWithoutSRP.rawValue, "CUSTOM_AUTH_WITHOUT_SRP")
18+
XCTAssertEqual(AuthFlowType.userPassword.rawValue, "USER_PASSWORD_AUTH")
19+
XCTAssertEqual(AuthFlowType.userAuth(preferredFirstFactor: nil).rawValue, "USER_AUTH")
20+
}
21+
22+
func testInitWithRawValue() {
23+
XCTAssertEqual(AuthFlowType(rawValue: "USER_SRP_AUTH"), .userSRP)
24+
XCTAssertEqual(AuthFlowType(rawValue: "CUSTOM_AUTH"), .customWithSRP)
25+
XCTAssertEqual(AuthFlowType(rawValue: "CUSTOM_AUTH_WITH_SRP"), .customWithSRP)
26+
XCTAssertEqual(AuthFlowType(rawValue: "CUSTOM_AUTH_WITHOUT_SRP"), .customWithoutSRP)
27+
XCTAssertEqual(AuthFlowType(rawValue: "USER_PASSWORD_AUTH"), .userPassword)
28+
XCTAssertEqual(AuthFlowType(rawValue: "USER_AUTH"), .userAuth(preferredFirstFactor: nil))
29+
XCTAssertNil(AuthFlowType(rawValue: "INVALID_AUTH"))
30+
}
31+
32+
func testDeprecatedCustom() {
33+
// This test is to ensure the deprecated case is still functional
34+
XCTAssertEqual(AuthFlowType.custom.rawValue, "CUSTOM_AUTH_WITH_SRP")
35+
}
36+
37+
func testEncoding() throws {
38+
let encoder = JSONEncoder()
39+
let userSRP = try encoder.encode(AuthFlowType.userSRP)
40+
XCTAssertEqual(String(data: userSRP, encoding: .utf8), "{\"type\":\"USER_SRP_AUTH\"}")
41+
42+
let customWithSRP = try encoder.encode(AuthFlowType.customWithSRP)
43+
XCTAssertEqual(String(data: customWithSRP, encoding: .utf8), "{\"type\":\"CUSTOM_AUTH_WITH_SRP\"}")
44+
45+
let customWithoutSRP = try encoder.encode(AuthFlowType.customWithoutSRP)
46+
XCTAssertEqual(String(data: customWithoutSRP, encoding: .utf8), "{\"type\":\"CUSTOM_AUTH_WITHOUT_SRP\"}")
47+
48+
let userPassword = try encoder.encode(AuthFlowType.userPassword)
49+
XCTAssertEqual(String(data: userPassword, encoding: .utf8), "{\"type\":\"USER_PASSWORD_AUTH\"}")
50+
51+
let userAuth = try encoder.encode(AuthFlowType.userAuth(preferredFirstFactor: nil))
52+
XCTAssertTrue(String(data: userAuth, encoding: .utf8)?.contains("\"preferredFirstFactor\":null") == true)
53+
XCTAssertTrue(String(data: userAuth, encoding: .utf8)?.contains("\"type\":\"USER_AUTH\"") == true)
54+
}
55+
56+
func testDecoding() throws {
57+
let decoder = JSONDecoder()
58+
let userSRP = try decoder.decode(AuthFlowType.self, from: "{\"type\":\"USER_SRP_AUTH\"}".data(using: .utf8)!)
59+
XCTAssertEqual(userSRP, .userSRP)
60+
61+
let customWithSRP = try decoder.decode(AuthFlowType.self, from: "{\"type\":\"CUSTOM_AUTH_WITH_SRP\"}".data(using: .utf8)!)
62+
XCTAssertEqual(customWithSRP, .customWithSRP)
63+
64+
let customWithoutSRP = try decoder.decode(AuthFlowType.self, from: "{\"type\":\"CUSTOM_AUTH_WITHOUT_SRP\"}".data(using: .utf8)!)
65+
XCTAssertEqual(customWithoutSRP, .customWithoutSRP)
66+
67+
let userPassword = try decoder.decode(AuthFlowType.self, from: "{\"type\":\"USER_PASSWORD_AUTH\"}".data(using: .utf8)!)
68+
XCTAssertEqual(userPassword, .userPassword)
69+
70+
let userAuth = try decoder.decode(AuthFlowType.self, from: "{\"type\":\"USER_AUTH\"}".data(using: .utf8)!)
71+
XCTAssertEqual(userAuth, .userAuth(preferredFirstFactor: nil))
72+
}
73+
74+
func testDecodingWithPreferredFirstFactor() throws {
75+
let decoder = JSONDecoder()
76+
let json = """
77+
{
78+
"type": "USER_AUTH",
79+
"preferredFirstFactor": "SMS_OTP"
80+
}
81+
""".data(using: .utf8)!
82+
let authFlowType = try decoder.decode(AuthFlowType.self, from: json)
83+
XCTAssertEqual(authFlowType, .userAuth(preferredFirstFactor: .smsOTP))
84+
}
85+
86+
func testDecodingLegacyStructure() throws {
87+
let decoder = JSONDecoder()
88+
var legacyJson = "\"userSRP\"".data(using: .utf8)!
89+
var authFlowType = try decoder.decode(AuthFlowType.self, from: legacyJson)
90+
XCTAssertEqual(authFlowType, .userSRP)
91+
92+
legacyJson = "\"userPassword\"".data(using: .utf8)!
93+
authFlowType = try decoder.decode(AuthFlowType.self, from: legacyJson)
94+
XCTAssertEqual(authFlowType, .userPassword)
95+
96+
legacyJson = "\"customWithSRP\"".data(using: .utf8)!
97+
authFlowType = try decoder.decode(AuthFlowType.self, from: legacyJson)
98+
XCTAssertEqual(authFlowType, .customWithSRP)
99+
100+
legacyJson = "\"customWithoutSRP\"".data(using: .utf8)!
101+
authFlowType = try decoder.decode(AuthFlowType.self, from: legacyJson)
102+
XCTAssertEqual(authFlowType, .customWithoutSRP)
103+
104+
legacyJson = "\"custom\"".data(using: .utf8)!
105+
authFlowType = try decoder.decode(AuthFlowType.self, from: legacyJson)
106+
XCTAssertEqual(authFlowType, .custom)
107+
}
108+
109+
func testDecodingInvalidType() {
110+
let decoder = JSONDecoder()
111+
let invalidJson = "{\"type\":\"INVALID_AUTH\"}".data(using: .utf8)!
112+
XCTAssertThrowsError(try decoder.decode(AuthFlowType.self, from: invalidJson)) { error in
113+
guard case DecodingError.dataCorrupted(let context) = error else {
114+
return XCTFail("Expected dataCorrupted error")
115+
}
116+
XCTAssertEqual(context.debugDescription, "Invalid AuthFlowType value")
117+
}
118+
}
119+
120+
func testDecodingInvalidPreferredFirstFactor() {
121+
let decoder = JSONDecoder()
122+
let invalidJson = """
123+
{
124+
"type": "USER_AUTH",
125+
"preferredFirstFactor": "INVALID_FACTOR"
126+
}
127+
""".data(using: .utf8)!
128+
XCTAssertThrowsError(try decoder.decode(AuthFlowType.self, from: invalidJson)) { error in
129+
guard case DecodingError.dataCorrupted(let context) = error else {
130+
return XCTFail("Expected dataCorrupted error")
131+
}
132+
XCTAssertEqual(context.debugDescription, "Unable to decode preferredFirstFactor value")
133+
}
134+
}
135+
136+
func testGetClientFlowType() {
137+
XCTAssertEqual(AuthFlowType.custom.getClientFlowType(), .customAuth)
138+
XCTAssertEqual(AuthFlowType.customWithSRP.getClientFlowType(), .customAuth)
139+
XCTAssertEqual(AuthFlowType.customWithoutSRP.getClientFlowType(), .customAuth)
140+
XCTAssertEqual(AuthFlowType.userSRP.getClientFlowType(), .userSrpAuth)
141+
XCTAssertEqual(AuthFlowType.userPassword.getClientFlowType(), .userPasswordAuth)
142+
XCTAssertEqual(AuthFlowType.userAuth(preferredFirstFactor: nil).getClientFlowType(), .userAuth)
143+
}
144+
}

0 commit comments

Comments
 (0)