Skip to content

Commit c88625f

Browse files
authored
feat(auth): Add support for authType as runtime parameter (#1774)
1 parent 637743c commit c88625f

File tree

8 files changed

+262
-3
lines changed

8 files changed

+262
-3
lines changed

AmplifyPlugins/Auth/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ extension AWSCognitoAuthPlugin {
4949
userService: userService,
5050
deviceService: deviceService,
5151
hubEventHandler: hubEventHandler,
52+
authConfig: jsonValueConfiguration,
5253
queue: operationQueue)
5354
} catch let authError as AuthError {
5455
throw authError
@@ -147,7 +148,6 @@ extension AWSCognitoAuthPlugin {
147148
}
148149

149150
// MARK: Internal
150-
151151
/// Internal configure method to set the properties of the plugin
152152
///
153153
/// Called from the configure method which implements the Plugin protocol. Useful for testing by passing in mocks.
@@ -161,12 +161,14 @@ extension AWSCognitoAuthPlugin {
161161
userService: AuthUserServiceBehavior,
162162
deviceService: AuthDeviceServiceBehavior,
163163
hubEventHandler: AuthHubEventBehavior,
164+
authConfig: JSONValue = JSONValue.object([:]),
164165
queue: OperationQueue = OperationQueue()) {
165166
self.authenticationProvider = authenticationProvider
166167
self.authorizationProvider = authorizationProvider
167168
self.userService = userService
168169
self.deviceService = deviceService
169170
self.hubEventHandler = hubEventHandler
171+
configuration = authConfig
170172
self.queue = queue
171173
}
172174
}

AmplifyPlugins/Auth/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ final public class AWSCognitoAuthPlugin: AuthCategoryPlugin {
3737
/// Handles different auth event send through hub
3838
var hubEventHandler: AuthHubEventBehavior!
3939

40+
/// Auth configuration used during initialization
41+
var configuration: JSONValue!
42+
4043
/// The unique key of the plugin within the auth category.
4144
public var key: PluginKey {
4245
return "awsCognitoAuthPlugin"

AmplifyPlugins/Auth/AWSCognitoAuthPlugin/ClientBehavior/AWSCognitoAuthPlugin+ClientBehavior.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ extension AWSCognitoAuthPlugin {
5959
password: password,
6060
options: options)
6161
let signInOperation = AWSAuthSignInOperation(request,
62+
configuration: configuration,
6263
authenticationProvider: authenticationProvider,
6364
resultListener: listener)
6465
queue.addOperation(signInOperation)

AmplifyPlugins/Auth/AWSCognitoAuthPlugin/Dependency/AuthenticationProviderAdapter+SignIn.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ typealias SigInResultCompletion = (Result<AuthSignInResult, AuthError>) -> Void
1616

1717
extension AuthenticationProviderAdapter {
1818

19+
static let cognitoClientKey = "CognitoUserPoolKey"
20+
1921
func signIn(request: AuthSignInRequest,
2022
completionHandler: @escaping SigInResultCompletion) {
2123

@@ -27,6 +29,12 @@ extension AuthenticationProviderAdapter {
2729
let password = request.password ?? ""
2830

2931
let clientMetaData = (request.options.pluginOptions as? AWSAuthSignInOptions)?.metadata ?? [:]
32+
let authType = (request.options.pluginOptions as? AWSAuthSignInOptions)?.authFlowType
33+
34+
if authType != .unknown,
35+
let client = AWSCognitoIdentityUserPool.init(forKey: Self.cognitoClientKey) {
36+
client.isCustomAuth = (authType == .custom)
37+
}
3038

3139
awsMobileClient.signIn(username: username,
3240
password: password,

AmplifyPlugins/Auth/AWSCognitoAuthPlugin/Models/Options/AWSAuthSignInOptions.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,26 @@ import Foundation
99

1010
public struct AWSAuthSignInOptions {
1111

12+
public let authFlowType: AuthFlowType
13+
1214
public let validationData: [String: String]?
1315

1416
public let metadata: [String: String]?
1517

16-
public init(validationData: [String: String]? = nil, metadata: [String: String]? = nil) {
18+
public init(validationData: [String: String]? = nil,
19+
metadata: [String: String]? = nil,
20+
authFlowType: AuthFlowType = .unknown) {
1721
self.validationData = validationData
1822
self.metadata = metadata
23+
self.authFlowType = authFlowType
1924
}
2025
}
26+
27+
public enum AuthFlowType {
28+
29+
case userSRP
30+
31+
case custom
32+
33+
case unknown
34+
}

AmplifyPlugins/Auth/AWSCognitoAuthPlugin/Operations/AWSAuthSignInOperation.swift

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ public class AWSAuthSignInOperation: AmplifyOperation<
1515
>, AuthSignInOperation {
1616

1717
let authenticationProvider: AuthenticationProviderBehavior
18+
let configuration: JSONValue
1819

1920
init(_ request: AuthSignInRequest,
21+
configuration: JSONValue,
2022
authenticationProvider: AuthenticationProviderBehavior,
2123
resultListener: ResultListener?) {
2224

2325
self.authenticationProvider = authenticationProvider
26+
self.configuration = configuration
2427
super.init(categoryType: .auth,
2528
eventName: HubPayload.EventName.Auth.signInAPI,
2629
request: request,
@@ -39,7 +42,25 @@ public class AWSAuthSignInOperation: AmplifyOperation<
3942
return
4043
}
4144

42-
authenticationProvider.signIn(request: request) {[weak self] result in
45+
// Get the authflowType and apply that to the existing request.
46+
guard let authFlowType = authFlowType(from: request) else {
47+
let error = AuthError.configuration(
48+
"Not a valid auth flow type",
49+
"AWSCognitoAuthPlugin currently supports only SRP and Custom auth")
50+
dispatch(error)
51+
finish()
52+
return
53+
}
54+
55+
let currentOptions = request.options.pluginOptions as? AWSAuthSignInOptions
56+
let options = AWSAuthSignInOptions(validationData: (currentOptions)?.validationData,
57+
metadata: (currentOptions)?.metadata,
58+
authFlowType: authFlowType)
59+
60+
let signInrequest = AuthSignInRequest(username: request.username,
61+
password: request.password,
62+
options: .init(pluginOptions: options))
63+
authenticationProvider.signIn(request: signInrequest) {[weak self] result in
4364
guard let self = self else { return }
4465

4566
defer {
@@ -68,4 +89,30 @@ public class AWSAuthSignInOperation: AmplifyOperation<
6889
let asyncEvent = AWSAuthSignInOperation.OperationResult.failure(error)
6990
dispatch(result: asyncEvent)
7091
}
92+
93+
// Determine the auth flow type to use for the signIn flow.
94+
//
95+
// First we check if authflow type is passed as a parameter in the signIn api. If not we
96+
// determine the authflow type from the configuration. If there is no configured authflowType
97+
// we will take srp as the default type.
98+
private func authFlowType(from request: AuthSignInRequest) -> AuthFlowType? {
99+
100+
if let authType = (request.options.pluginOptions as? AWSAuthSignInOptions)?.authFlowType,
101+
authType != .unknown {
102+
return authType
103+
}
104+
105+
let authTypeKeyPath = "Auth.Default.authenticationFlowType"
106+
guard case .string(let authTypeString) = configuration.value(at: authTypeKeyPath) else {
107+
return .userSRP
108+
}
109+
110+
switch authTypeString {
111+
case "CUSTOM_AUTH": return .custom
112+
case "USER_SRP_AUTH": return .userSRP
113+
default:
114+
return nil
115+
}
116+
117+
}
71118
}

AmplifyPlugins/Auth/AWSCognitoAuthPluginIntegrationTests/AuthSignInTests/AuthUsernamePasswordSignInTests.swift

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,150 @@ class AuthUsernamePasswordSignInTests: AWSAuthBaseTest {
6060
wait(for: [operationExpectation], timeout: networkTimeout)
6161
}
6262

63+
/// Test successful signIn of a valid user with authflowType
64+
///
65+
/// - Given: A user registered in Cognito user pool
66+
/// - When:
67+
/// - I invoke Amplify.Auth.signIn with the username and password
68+
/// - Then:
69+
/// - I should get a completed signIn flow.
70+
///
71+
func testSuccessfulSignInWithSRPFlow() {
72+
73+
let username = "integTest\(UUID().uuidString)"
74+
let password = "P123@\(UUID().uuidString)"
75+
76+
let signUpExpectation = expectation(description: "SignUp operation should complete")
77+
AuthSignInHelper.signUpUser(username: username, password: password,
78+
email: email) { didSucceed, error in
79+
signUpExpectation.fulfill()
80+
XCTAssertTrue(didSucceed, "Signup operation failed - \(String(describing: error))")
81+
}
82+
wait(for: [signUpExpectation], timeout: networkTimeout)
83+
84+
let operationExpectation = expectation(description: "Operation should complete")
85+
let option = AWSAuthSignInOptions(authFlowType: .userSRP)
86+
let operation = Amplify.Auth.signIn(username: username,
87+
password: password,
88+
options: .init(pluginOptions: option)) { result in
89+
defer {
90+
operationExpectation.fulfill()
91+
}
92+
switch result {
93+
case .success(let signInResult):
94+
XCTAssertTrue(signInResult.isSignedIn, "SignIn should be complete")
95+
case .failure(let error):
96+
XCTFail("SignIn with a valid username/password should not fail \(error)")
97+
}
98+
}
99+
XCTAssertNotNil(operation, "SignIn operation should not be nil")
100+
wait(for: [operationExpectation], timeout: networkTimeout)
101+
}
102+
103+
/// Test signIn error in invalid authflowtype
104+
///
105+
/// - Given: A user registered in Cognito user pool
106+
/// - When:
107+
/// - I invoke Amplify.Auth.signIn with a authflow not configured in user pool
108+
/// - Then:
109+
/// - I should get a completed signIn flow with an error
110+
///
111+
func testSuccessfulSignInWithCustomAuth() {
112+
113+
let username = "integTest\(UUID().uuidString)"
114+
let password = "P123@\(UUID().uuidString)"
115+
116+
let signUpExpectation = expectation(description: "SignUp operation should complete")
117+
AuthSignInHelper.signUpUser(username: username, password: password,
118+
email: email) { didSucceed, error in
119+
signUpExpectation.fulfill()
120+
XCTAssertTrue(didSucceed, "Signup operation failed - \(String(describing: error))")
121+
}
122+
wait(for: [signUpExpectation], timeout: networkTimeout)
123+
124+
let operationExpectation = expectation(description: "Operation should complete")
125+
let option = AWSAuthSignInOptions(authFlowType: .custom)
126+
let operation = Amplify.Auth.signIn(username: username,
127+
password: password,
128+
options: .init(pluginOptions: option)) { result in
129+
defer {
130+
operationExpectation.fulfill()
131+
}
132+
switch result {
133+
case .success:
134+
XCTFail("SignIn with invalid auth flow should not succeed")
135+
case .failure(let error):
136+
guard case .service = error else {
137+
XCTFail("Should return service error")
138+
return
139+
}
140+
}
141+
}
142+
XCTAssertNotNil(operation, "SignIn operation should not be nil")
143+
wait(for: [operationExpectation], timeout: networkTimeout)
144+
}
145+
146+
/// Test signIn error in invalid authflowtype
147+
///
148+
/// - Given: A user registered in Cognito user pool
149+
/// - When:
150+
/// - I invoke Amplify.Auth.signIn with a authflow not configured in user pool
151+
/// - Then:
152+
/// - I should get a completed signIn flow with an error
153+
///
154+
func testRuntimeAuthFlowSwitch() {
155+
156+
let username = "integTest\(UUID().uuidString)"
157+
let password = "P123@\(UUID().uuidString)"
158+
159+
let signUpExpectation = expectation(description: "SignUp operation should complete")
160+
AuthSignInHelper.signUpUser(username: username, password: password,
161+
email: email) { didSucceed, error in
162+
signUpExpectation.fulfill()
163+
XCTAssertTrue(didSucceed, "Signup operation failed - \(String(describing: error))")
164+
}
165+
wait(for: [signUpExpectation], timeout: networkTimeout)
166+
167+
let operationExpectation = expectation(description: "Operation should complete")
168+
let option = AWSAuthSignInOptions(authFlowType: .custom)
169+
let operation = Amplify.Auth.signIn(username: username,
170+
password: password,
171+
options: .init(pluginOptions: option)) { result in
172+
defer {
173+
operationExpectation.fulfill()
174+
}
175+
switch result {
176+
case .success:
177+
XCTFail("SignIn with invalid auth flow should not succeed")
178+
case .failure(let error):
179+
guard case .service = error else {
180+
XCTFail("Should return service error")
181+
return
182+
}
183+
}
184+
}
185+
XCTAssertNotNil(operation, "SignIn operation should not be nil")
186+
wait(for: [operationExpectation], timeout: networkTimeout)
187+
188+
let srpOperationExpectation = expectation(description: "Operation should complete")
189+
let srpOption = AWSAuthSignInOptions(authFlowType: .userSRP)
190+
let srpOperation = Amplify.Auth.signIn(username: username,
191+
password: password,
192+
options: .init(pluginOptions: srpOption)) { result in
193+
defer {
194+
srpOperationExpectation.fulfill()
195+
}
196+
switch result {
197+
case .success(let signInResult):
198+
XCTAssertTrue(signInResult.isSignedIn, "SignIn should be complete")
199+
case .failure(let error):
200+
XCTFail("SignIn with a valid username/password should not fail \(error)")
201+
}
202+
}
203+
XCTAssertNotNil(srpOperation, "SignIn operation should not be nil")
204+
wait(for: [srpOperationExpectation], timeout: networkTimeout)
205+
}
206+
63207
/// Test successful signIn of a valid user
64208
/// Internally, Two Cognito APIs will be called, Cognito's `InitiateAuth` and `RespondToAuthChallenge` API.
65209
///

AmplifyPlugins/Auth/AWSCognitoAuthPluginTests/AuthenticationProviderTests/AuthenticationProviderSigninTests.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,46 @@ class AuthenticationProviderSigninTests: BaseAuthenticationProviderTest {
5555
wait(for: [resultExpectation], timeout: apiTimeout)
5656
}
5757

58+
/// Test a signIn with valid inputs and authflow type
59+
///
60+
/// - Given: Given an auth plugin with mocked service.
61+
///
62+
/// - When:
63+
/// - I invoke signIn with valid values
64+
/// - Then:
65+
/// - I should get a .done response
66+
///
67+
func testSuccessfulSignInWithAuthFlow() {
68+
69+
let mockSigninResult = SignInResult(signInState: .signedIn)
70+
mockAWSMobileClient?.signInMockResult = .success(mockSigninResult)
71+
72+
let pluginOptions = AWSAuthSignInOptions(validationData: ["somekey": "somevalue"],
73+
metadata: ["somekey": "somevalue"],
74+
authFlowType: .userSRP)
75+
let options = AuthSignInRequest.Options(pluginOptions: pluginOptions)
76+
77+
let resultExpectation = expectation(description: "Should receive a result")
78+
_ = plugin.signIn(username: "username", password: "password", options: options) { result in
79+
defer {
80+
resultExpectation.fulfill()
81+
}
82+
switch result {
83+
case .success(let signinResult):
84+
guard case .done = signinResult.nextStep else {
85+
XCTFail("Result should be .done for next step")
86+
return
87+
}
88+
XCTAssertTrue(signinResult.isSignedIn, "Signin result should be complete")
89+
XCTAssertFalse(self.mockUserDefault.isPrivateSessionPreferred(),
90+
"Prefer private session userdefaults should not be set.")
91+
case .failure(let error):
92+
XCTFail("Received failure with error \(error)")
93+
}
94+
}
95+
wait(for: [resultExpectation], timeout: apiTimeout)
96+
}
97+
5898
/// Test a signIn with empty username
5999
///
60100
/// - Given: Given an auth plugin with mocked service.

0 commit comments

Comments
 (0)