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

Account Linking Phase 1 #1185

Closed
wants to merge 14 commits into from
Closed

Conversation

samtstern
Copy link
Contributor

@samtstern samtstern commented Mar 13, 2018

See #1179 and #309

TODO:

  • Handle register email flow
  • Handle the 'welcome back' email flow
  • Handle IDP flows
  • Handle phone flow
  • Update docs
  • Add comprehensive tests

@samtstern samtstern changed the title Very basic linking [WIP] Account linking phase 1 Mar 13, 2018
Change-Id: I50b05d1e50865c9f2dc5e231569e237c631e0003
Change-Id: Icca4a88adb9423b7f8510c9e338285e68a7965e0
Change-Id: If381ab96faa6684a38de7316f6a825abe0f40625
Change-Id: Ib85147724ce25ef9e757e3d7a496cc3dea8f94c8
@samtstern
Copy link
Contributor Author

@SUPERCILEX am I correct interpreting #309 to allow linking between any pre-signed-in account and not just anonymous?

@samtstern
Copy link
Contributor Author

samtstern commented Mar 14, 2018

I am also a little confused how to handle email.

Let's say after CheckEmailFragment we know that the email already exists. Then we know that calling linkWithCredential will fail, so we are definitely going to have a merge conflict.

But, we want to make sure that in the merge conflict handler the AuthCredential object they get is always going to be valid ... and the only way to check if the email credential is valid is to attempt a sign-in which will destroy the Anonymous auth state.

Seems like in #309 you'd give the user a chance to read all data from the anon account before attempting the link, but not sure how it would work here.


Edit 1: after talking to @bojeil-google we need to create a temporary FirebaseApp to verify the credential.


Edit 2: Implemented working version in 5876159

Change-Id: I15adb6fab8306420fa7ca6a9a837e2260691369e
Change-Id: I51da641853cc581ea90aa2d31c2908bed3737e76
Change-Id: Id45100d620ca65ac8b7ca4311daea08987026386
Change-Id: I245ca7ee28e7d0b06bb7b691f44a3a51e1bca4dd
Change-Id: Ic253a2f5cac30e51baef2f33aa0cc88b00db95c6
Change-Id: Ibb5c3f19a8f63c59b7685728e4a92e84d6b7690d
@samtstern samtstern changed the title [WIP] Account linking phase 1 Account Linking Phase 1 Mar 15, 2018
@@ -0,0 +1,197 @@
package com.firebase.uidemo.auth;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Decided to do a new sample here since this is an "advanced" feature and I don't want to clutter the main sample.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ooof! You sure about this? In my PR, I just added a checkbox that said something like "Allow account linking" and a button to open the AuthUiActivity from the signed in one so devs could fully customize the linking experience (I noticed you hardcoded the providers and all that).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we were going your way with this feature (service, data callbacks, etc) then I'd agree with merging the samples. However in this case since you have to write extra code and really understand the merge-failure-upgrade flow I wanted to give it dedicated UI and keep the code separate so as not so confuse users of the 'basic' AuthUI flow with all the extra stuff.

If you try out the sample, you can see how it makes the various states extra clear.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Gotya, that totally makes sense so 👍.

@@ -129,6 +129,7 @@ public void onSaveInstanceState(Bundle outState) {
@Override
public void onSuccess(@NonNull final IdpResponse response) {
AuthCredential credential = ProviderUtils.getAuthCredential(response);
// TODO: Probably need to allow linking here
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note to self: need to check out this piece.

protected void onUpgradeFailure(@NonNull FirebaseAuthUserCollisionException e) {
// In the case of Phone Auth the exception contains an updated credential we can
// user since the other credential is expired after one use.
AuthCredential credential = e.getUpdatedCredential() != null
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So this is a cool trick I learned: you get a collision exception from Phone Auth the exception gives you a try-again credential so you don't have to show the UI again.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ooooooh, fancy!

(In your comment, I think you meant use, not user. 😉 Everything starts looking like a user in Firebase Auth! 😆)

Change-Id: I7db94a0b5c5f7fc515278dcb82c2e51a1ac5da48
@samtstern samtstern added this to the 3.3.0 milestone Mar 15, 2018
@samtstern samtstern removed this from the 3.3.0 milestone Mar 23, 2018
@samtstern samtstern changed the title Account Linking Phase 1 [ON HOLD] Account Linking Phase 1 Mar 23, 2018
@samtstern
Copy link
Contributor Author

Removing this from 3.3.0 as #1189 dropped a nuclear merge bomb on this :-)

@SUPERCILEX
Copy link
Collaborator

Oh noes! 😞 I completely forgot about this PR, sorry man!

@samtstern
Copy link
Contributor Author

@SUPERCILEX haha no worries, I knew exactly what I was getting into.

Change-Id: Ibdf3e61f6dc1e1578640cb66562a217bf2fd74c0
Change-Id: Ib7956224986259b388b570d59c2f65b00251f8dc
Change-Id: I7eb7675c29c5d03c292499d9517d064bd3a02315
@samtstern samtstern changed the title [ON HOLD] Account Linking Phase 1 [Account Linking Phase 1 Mar 23, 2018
@samtstern samtstern changed the title [Account Linking Phase 1 Account Linking Phase 1 Mar 23, 2018
@samtstern
Copy link
Contributor Author

@SUPERCILEX I've got the merge conflicts squared away.

The two places I am not sure how to apply the linking logic are in SocialProviderResponseHandler and LinkingSocialProviderResponseHandler.

Mind taking a look?

@SUPERCILEX
Copy link
Collaborator

Will do! I just realized I forgot some loading stuff in the providers... so PR incoming for that first! 😊

Copy link
Collaborator

@SUPERCILEX SUPERCILEX left a comment

Choose a reason for hiding this comment

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

Alrighty, I'm starting off with some general comments.

The gist I'm getting is you're creating a temporary, fake firebase app to test whether or not a credential already exists, thus seeing if there's going to be a collision. While that's crazy cool and I learned something new, I think it's a bit over the top . (Show off! 😆) In my PR, I literally just added an if (e instanceof FirebaseAuthUserCollisionException) in the welcome back stuff and returned an error if it was the case. I'm probably missing something though...

So for the SocialProviderResponseHandler, we'll want to add this:
https://github.com/SUPERCILEX/FirebaseUI-Android/blob/460f4307089d3933e2517058758147d725eea567/auth/src/main/java/com/firebase/ui/auth/ui/idp/CredentialSignInHandler.java#L105-L127
Except instead of performing the service merge, you'll return the pending credential.

For the LinkingSocialProviderResponseHandler, that's where you simply add the if statement and return the pending credential if it fails.

PS: I've forgotten, is the service thing permanently out of the picture? Or why aren't we doing it here? I feel like we've already talked about this somewhere, so sorry for the repetition.

@@ -0,0 +1,197 @@
package com.firebase.uidemo.auth;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ooof! You sure about this? In my PR, I just added a checkbox that said something like "Allow account linking" and a button to open the AuthUiActivity from the signed in one so devs could fully customize the linking experience (I noticed you hardcoded the providers and all that).

@@ -61,25 +63,36 @@ public IdpResponse createFromParcel(Parcel in) {

private final FirebaseUiException mException;

private final AuthCredential mPendingCredential;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you move this above the exception?

if (mException != null ? !mException.equals(response.mException) : response.mException != null) {
return false;
}
if (mPendingCredential != null ? !mPendingCredential.equals(response.mPendingCredential) : response.mPendingCredential != null) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

These if statements are kinda confusing... Also, the AuthCredential doesn't implement equals properly so adding that will make the response never have an equal. I think if you just add this line it'll work out:

&& (mPendingCredential == null ? response.mPendingCredential == null : mPendingCredential.getProvider().equals(response.mPendingCredential.getProvider()))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be honest I just let IntelliJ write this, and since these things are now marked @Nullable this is what it came up with.

But good call on the AuthCredential part.

result = 31 * result + (mToken != null ? mToken.hashCode() : 0);
result = 31 * result + (mSecret != null ? mSecret.hashCode() : 0);
result = 31 * result + (mException != null ? mException.hashCode() : 0);
result = 31 * result + (mPendingCredential != null ? mPendingCredential.hashCode() : 0);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here, we should just hash code the provider:

result = 31 * result + (mPendingCredential == null ? 0 : mPendingCredential.getProvider().hashCode());

setResult(Resource.forSuccess(response));
} else {
// TODO: Will this correctly pass back anonymous errors?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, I'm pretty sure it will.

import com.firebase.ui.auth.util.data.ProviderUtils;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
Copy link
Collaborator

Choose a reason for hiding this comment

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

?. Probs refactor mistake

protected void onUpgradeFailure(@NonNull FirebaseAuthUserCollisionException e) {
// In the case of Phone Auth the exception contains an updated credential we can
// user since the other credential is expired after one use.
AuthCredential credential = e.getUpdatedCredential() != null
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ooooooh, fancy!

(In your comment, I think you meant use, not user. 😉 Everything starts looking like a user in Firebase Auth! 😆)


protected void onUpgradeFailure(@NonNull IdpResponse response) {}

protected abstract void onNonUpgradeFailure(@NonNull Exception e);
Copy link
Collaborator

Choose a reason for hiding this comment

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

How about calling it onUnknownFailure?

}
}

protected void onUpgradeFailure(@NonNull FirebaseAuthUserCollisionException e) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we make this final? I got confused about why you weren't calling super until I realized there was an overload. Or make it private and call it handleUpgradleFailure?

/**
* Uses type system to enforce the proper failure listener.
*/
public static class UpgradeTaskWrapper<T> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I like this idea, but it doesn't really scale. Or is the plan to force us to always add a failure listener first?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah that was the plan, to make sure that we always handle that error case. But you're right that it doesn't quite work, because in some places we have continuations.

@SUPERCILEX
Copy link
Collaborator

SUPERCILEX commented Mar 24, 2018

Huh, I guess embedded code snippets don't work on forks. 😭

@samtstern
Copy link
Contributor Author

@SUPERCILEX thanks for the comments.

The temporary FirebaseApp trick isn't to check for a collision, it's for when you know there's going to be a collision and want to make sure you're giving the user a valid AuthCredential. So you can check if the credential is valid but without messing up the current sign-in state, so they still have a chance to transfer data.

As for why we're not doing the Service thing: basically because in the API discussion for doing this feature cross-platform the only thing we could agree on in the short term was something minimal and correct like this, while leaving room to improve the developer experience in the future.

@SUPERCILEX
Copy link
Collaborator

Ohhhhhhhhh, I see. Thanks for clarifying! Re-reading your comments though, it does look like you're checking for a conflict. Maybe change them to say we're expecting a conflict, but want to ensure the credential is usable?

As for the service, thanks, I remember now.

@samtstern samtstern changed the base branch from version-3.3.0-dev to version-3.3.1-dev March 29, 2018 15:29
@samtstern samtstern changed the base branch from version-3.3.1-dev to version-3.4.0-dev April 16, 2018 16:59
@samtstern samtstern changed the base branch from version-3.4.0-dev to version-4.0.0-dev May 4, 2018 17:24
@samtstern
Copy link
Contributor Author

Closing this PR, it's very very conflicty and @lsirac is going to take over this task.

@samtstern samtstern closed this Jun 13, 2018
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.

None yet

2 participants