Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(auth): handle missing or empty signUpOptions #627

Conversation

Jordan-Nelson
Copy link
Member

@Jordan-Nelson Jordan-Nelson commented Jun 10, 2021

Issue #, if available: #292

Description of changes:

  • Update FlutterSignUpRequest on iOS and Android to handle missing sign up options and missing user attributes
  • Update SignUpOptions and CognitoSignUpOptions to make userAttributes optional (was optional from perspective of the public API, but was treated as required)
  • Add integration tests for standard signUp w/ and w/o sign up options

For a standard username based auth (where email is a required attribute), if a consumer omits options, user attributes, or just the email attribute they will receive and InvalidParameterException.

For an email based auth (where email is the username) omitting any of these fields/values will not cause an error.

// currently throws unhelpful error in all scenarios
// will now throw a helpful error for username based auth
// will now work for email based auth
var res = await Amplify.Auth.signUp(
  username: <USERNAME/EMAIL>,
  password: <PASSWORD>,
);

// currently throws unhelpful error in all scenarios
// will now throw a helpful error for username based auth
// will now work for email based auth
var res = await Amplify.Auth.signUp(
  username: <USERNAME/EMAIL>,
  password: <PASSWORD>,
  options: CognitoSignUpOptions(),
);

// unchanged for username based auth (throws InvalidParamterException)
// unchaged for email based auth (works as expected)
Map<String, String> userAttributes = {};
var res = await Amplify.Auth.signUp(
  username: <USERNAME/EMAIL>,
  password: <PASSWORD>,
  options: CognitoSignUpOptions(userAttributes: userAttributes),
);

// unchaged in all scenarios (works as expected)
var res = await Amplify.Auth.signUp(
  username: <USERNAME/EMAIL>,
  password: <PASSWORD>,
  options: CognitoSignUpOptions(userAttributes: {'email': <EMAIL>}),
);

Test scenarios

Scenario Auth Method Platform Expectation Tested
sign up w/ email attribute Username iOS Sign up works
sign up w/ email attribute Username Android Sign up works
sign up w/ email attribute Email iOS Sign up works
sign up w/ email attribute Email Android Sign up works
sign up w/ no email attribute Username iOS InvalidParamterException
sign up w/ no email attribute Username Android InvalidParamterException
sign up w/ no email attribute Email iOS Sign up works
sign up w/ no email attribute Email Android Sign up works
sign up w/ no attributes Username iOS InvalidParamterException
sign up w/ no attributes Username Android InvalidParamterException
sign up w/ no attributes Email iOS Sign up works
sign up w/ no attributes Email Android Sign up works
sign up w/ no options Username iOS InvalidParamterException
sign up w/ no options Username Android InvalidParamterException
sign up w/ no options Email iOS Sign up works
sign up w/ no options Email Android Sign up works

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@haverchuck
Copy link
Member

haverchuck commented Jun 10, 2021

@Jordan-Nelson Since the null safety branch will eventually be merged into master, I wonder if it would make sense to just fix THAT branch - that way we won't have to do duplicate work and release multiple times?

@Jordan-Nelson
Copy link
Member Author

@haverchuck I was thinking that originally as well, but I don't think it will lead to much duplication or additional releases. The changes on the dart side were so minimal that I think bringing this into the NS branch should be pretty easy. And I don't think we have to do a release of the NS branch just for this since its an issue in the latest non NS release as well.

@Jordan-Nelson Jordan-Nelson marked this pull request as ready for review June 11, 2021 19:05
@Jordan-Nelson Jordan-Nelson requested a review from a team as a code owner June 11, 2021 19:05
@Jordan-Nelson
Copy link
Member Author

Since integration tests are not running in CI yet, here are screenshots of the tests passing on iOS and Android. Note that these were run with #637 pulled in.

Screen Shot 2021-06-14 at 10 26 37 AM

Screen Shot 2021-06-14 at 10 27 26 AM

@codecov-commenter
Copy link

codecov-commenter commented Jun 14, 2021

Codecov Report

Merging #627 (6d7ae24) into master (8d5b6df) will increase coverage by 0.04%.
The diff coverage is 80.88%.

@@            Coverage Diff             @@
##           master     #627      +/-   ##
==========================================
+ Coverage   69.36%   69.41%   +0.04%     
==========================================
  Files         262      262              
  Lines        8604     8631      +27     
  Branches      353      353              
==========================================
+ Hits         5968     5991      +23     
- Misses       2504     2508       +4     
  Partials      132      132              
Flag Coverage Δ
android-unit-tests 63.95% <64.28%> (+0.08%) ⬆️
flutter-unit-tests 60.91% <ø> (ø)
ios-unit-tests 75.19% <85.18%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...y_auth_cognito/ios/Classes/AuthCognitoBridge.swift 0.64% <0.00%> (+<0.01%) ⬆️
..._flutter/lib/categories/amplify_auth_category.dart 13.00% <ø> (ø)
...amplify_auth_cognito/types/FlutterSignUpRequest.kt 54.54% <64.28%> (+6.71%) ⬆️
...uth_cognito/ios/Classes/FlutterSignUpRequest.swift 75.00% <75.00%> (-25.00%) ⬇️
...le/ios/unit_tests/amplify_auth_cognito_tests.swift 96.06% <96.55%> (+<0.01%) ⬆️

@Jordan-Nelson Jordan-Nelson force-pushed the fix/209-handle-missing-signUpOptions branch 2 times, most recently from 456990d to f3450e5 Compare July 6, 2021 20:47
@@ -13,13 +13,12 @@
* permissions and limitations under the License.
*/



class SignUpOptions {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only actual changes in the file are lines 19 and 20. The rest are auto formatting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(the change seems reasonable, it just isn't related to the PR)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah my bad! Sorry I misread the diff.

Copy link
Member

@cshfang cshfang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had some suggestions but

  1. I don't see anything functionally wrong
  2. Many suggestions are nitpicking on existing code

So lgtm overall 🚀

var authUserAttributes: MutableList<AuthUserAttribute> = mutableListOf();
var validationData = rawOptions["validationData"] as? MutableMap<String, String>;
private fun formatOptions(rawOptions: HashMap<String, *>?): AWSCognitoAuthSignUpOptions {
val options = AWSCognitoAuthSignUpOptions.builder()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Is it convention to do this? Imo it's clearer to name this variable optionsBuilder unless the entire build chain is part of the assignment.

private fun formatOptions(rawOptions: HashMap<String, *>?): AWSCognitoAuthSignUpOptions {
val options = AWSCognitoAuthSignUpOptions.builder()
if (rawOptions != null) {
val authUserAttributes: MutableList<AuthUserAttribute> = mutableListOf()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val authUserAttributes: MutableList<AuthUserAttribute> = mutableListOf()

Comment on lines 39 to 43
attributeData.forEach { (key, value) ->
val attribute = createAuthUserAttribute(key, value)
authUserAttributes.add(attribute)
}
options.userAttributes(authUserAttributes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: More concise and arguably clearer with added benefit of not creating a list at all if attributeData is null

Suggested change
attributeData.forEach { (key, value) ->
val attribute = createAuthUserAttribute(key, value)
authUserAttributes.add(attribute)
}
options.userAttributes(authUserAttributes)
val authUserAttributes: List<AuthUserAttribute> = attributeData.map { (key, value) ->
createAuthUserAttribute(key, value)
}
options.userAttributes(authUserAttributes)

Comment on lines +21 to +22
import 'utils/mock_data.dart';
import 'utils/setup_utils.dart';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤩

Comment on lines 29 to 31
if (options != null) {
pendingRequest['options'] = options!.serializeAsMap();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we remove nulls on line 32, could we just keep line 29 the way it was but will the null-aware access?

'options': options?.serializeAsMap()

Comment on lines 32 to 45
private func createOptions(options: Dictionary<String, Any>?) -> AuthSignUpRequest.Options? {
if (options == nil) {
return nil
}
var formattedAttributes: Array<AuthUserAttribute> = Array()
if (options!["userAttributes"] is Dictionary<String, String>) {
let rawAttributes: Dictionary<String, Any> = options!["userAttributes"] as! Dictionary<String, String>
for attr in rawAttributes {
formattedAttributes.append(createAuthUserAttribute(key: attr.key, value: attr.value as! String))
}
}
return AuthSignUpRequest.Options(userAttributes: formattedAttributes)

}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for the initial guard clause to just be

if (!(options?["userAttributes"] is Dictionary<String, String>)) {
    return nil
}
// rest of function without if condition

It would reduce some of the nesting and improve readability if so. I think it'd also make it possible to just assign a mapped array directly to formattedAttributes similar to my kotlin suggestion above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I am going to make this update. I think I was mimicking what I had done on the Android side. However, on Android the nested if statements has a purpose since options can contain userAttributes and/or validationData. On iOS we aren't doing anything with the validationData. That seems like it might be a bug.

@haverchuck It doesn't seem like there is any way to include "validationData" in the sign up request in amplify-ios. Amplify-flutter seems to just be ignoring that value on iOS. On Android validationData is part of the AWSCognitoAuthSignUpOptions that gets included in the request. Is that a known platform discrepancy?

Copy link
Member

@haverchuck haverchuck Jul 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jordan-Nelson Yeah - this is something we need to fix. It is related to the clientmetadata work that Hui and myself have implemented piecemeal. In this case, android and ios should be able to handle both validationData and clientMetadata attributes.

This doesn't need to be part of this PR.

Comment on lines 48 to 53
let rawAttributes: Dictionary<String, Any> = options["userAttributes"] as! Dictionary<String, String>
var formattedAttributes: Array<AuthUserAttribute> = Array()
for attr in rawAttributes {
formattedAttributes.append(createAuthUserAttribute(key: attr.key, value: attr.value as! String))
}
return formattedAttributes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Could simplify this I think

Suggested change
let rawAttributes: Dictionary<String, Any> = options["userAttributes"] as! Dictionary<String, String>
var formattedAttributes: Array<AuthUserAttribute> = Array()
for attr in rawAttributes {
formattedAttributes.append(createAuthUserAttribute(key: attr.key, value: attr.value as! String))
}
return formattedAttributes
let rawAttributes: Dictionary<String, Any> = options["userAttributes"] as! Dictionary<String, String>
return rawAttributes.map { attr in
createAuthUserAttribute(key: attr.key, value: attr.value as! String)
}

Copy link
Member

@cshfang cshfang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of cleanup nits but lgtm

@Jordan-Nelson Jordan-Nelson merged commit abc22aa into aws-amplify:main Jul 9, 2021
@Jordan-Nelson Jordan-Nelson deleted the fix/209-handle-missing-signUpOptions branch November 11, 2021 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants