28
28
import com .google .firebase .auth .FacebookAuthProvider ;
29
29
import com .google .firebase .auth .FirebaseAuth ;
30
30
import com .google .firebase .auth .FirebaseAuthException ;
31
+ import com .google .firebase .auth .FirebaseAuthMultiFactorException ;
31
32
import com .google .firebase .auth .FirebaseAuthProvider ;
32
33
import com .google .firebase .auth .FirebaseUser ;
33
34
import com .google .firebase .auth .FirebaseUserMetadata ;
34
35
import com .google .firebase .auth .GetTokenResult ;
35
36
import com .google .firebase .auth .GithubAuthProvider ;
36
37
import com .google .firebase .auth .GoogleAuthProvider ;
38
+ import com .google .firebase .auth .MultiFactor ;
39
+ import com .google .firebase .auth .MultiFactorAssertion ;
40
+ import com .google .firebase .auth .MultiFactorInfo ;
41
+ import com .google .firebase .auth .MultiFactorResolver ;
42
+ import com .google .firebase .auth .MultiFactorSession ;
37
43
import com .google .firebase .auth .OAuthProvider ;
38
44
import com .google .firebase .auth .PhoneAuthCredential ;
39
45
import com .google .firebase .auth .PhoneAuthProvider ;
46
+ import com .google .firebase .auth .PhoneMultiFactorGenerator ;
47
+ import com .google .firebase .auth .PhoneMultiFactorInfo ;
40
48
import com .google .firebase .auth .SignInMethodQueryResult ;
41
49
import com .google .firebase .auth .TwitterAuthProvider ;
42
50
import com .google .firebase .auth .UserInfo ;
43
51
import com .google .firebase .auth .UserProfileChangeRequest ;
52
+ import com .google .firebase .internal .api .FirebaseNoSignedInUserException ;
44
53
import io .flutter .embedding .engine .plugins .FlutterPlugin ;
45
54
import io .flutter .embedding .engine .plugins .activity .ActivityAware ;
46
55
import io .flutter .embedding .engine .plugins .activity .ActivityPluginBinding ;
63
72
64
73
/** Flutter plugin for Firebase Auth. */
65
74
public class FlutterFirebaseAuthPlugin
66
- implements FlutterFirebasePlugin , MethodCallHandler , FlutterPlugin , ActivityAware {
75
+ implements FlutterFirebasePlugin ,
76
+ MethodCallHandler ,
77
+ FlutterPlugin ,
78
+ ActivityAware ,
79
+ GeneratedAndroidFirebaseAuth .MultiFactorUserHostApi ,
80
+ GeneratedAndroidFirebaseAuth .MultiFactoResolverHostApi {
67
81
68
82
private static final String METHOD_CHANNEL_NAME = "plugins.flutter.io/firebase_auth" ;
69
83
@@ -98,6 +112,8 @@ private void initInstance(BinaryMessenger messenger) {
98
112
registerPlugin (METHOD_CHANNEL_NAME , this );
99
113
channel = new MethodChannel (messenger , METHOD_CHANNEL_NAME );
100
114
channel .setMethodCallHandler (this );
115
+ GeneratedAndroidFirebaseAuth .MultiFactorUserHostApi .setup (messenger , this );
116
+ GeneratedAndroidFirebaseAuth .MultiFactoResolverHostApi .setup (messenger , this );
101
117
102
118
this .messenger = messenger ;
103
119
}
@@ -112,6 +128,8 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
112
128
channel .setMethodCallHandler (null );
113
129
channel = null ;
114
130
messenger = null ;
131
+ GeneratedAndroidFirebaseAuth .MultiFactorUserHostApi .setup (null , this );
132
+ GeneratedAndroidFirebaseAuth .MultiFactoResolverHostApi .setup (null , this );
115
133
116
134
removeEventListeners ();
117
135
}
@@ -159,6 +177,11 @@ private FirebaseUser getCurrentUser(Map<String, Object> arguments) {
159
177
return FirebaseAuth .getInstance (app ).getCurrentUser ();
160
178
}
161
179
180
+ private FirebaseUser getCurrentUser (String appName ) {
181
+ FirebaseApp app = FirebaseApp .getInstance (appName );
182
+ return FirebaseAuth .getInstance (app ).getCurrentUser ();
183
+ }
184
+
162
185
private AuthCredential getCredential (Map <String , Object > arguments )
163
186
throws FlutterFirebaseAuthPluginException {
164
187
@ SuppressWarnings ("unchecked" )
@@ -790,13 +813,79 @@ private Task<Map<String, Object>> signInWithEmailAndPassword(Map<String, Object>
790
813
791
814
taskCompletionSource .setResult (parseAuthResult (authResult ));
792
815
} catch (Exception e ) {
793
- taskCompletionSource .setException (e );
816
+ if (e .getCause () instanceof FirebaseAuthMultiFactorException ) {
817
+ final FirebaseAuthMultiFactorException multiFactorException =
818
+ (FirebaseAuthMultiFactorException ) e .getCause ();
819
+ Map <String , Object > output = new HashMap <>();
820
+
821
+ MultiFactorResolver multiFactorResolver = multiFactorException .getResolver ();
822
+ final List <MultiFactorInfo > hints = multiFactorResolver .getHints ();
823
+
824
+ final MultiFactorSession session = multiFactorResolver .getSession ();
825
+ final String sessionId = UUID .randomUUID ().toString ();
826
+ multiFactorSessionMap .put (sessionId , session );
827
+
828
+ final String resolverId = UUID .randomUUID ().toString ();
829
+ multiFactorResolverMap .put (resolverId , multiFactorResolver );
830
+
831
+ final List <Map <String , Object >> pigeonHints = multiFactorInfoToMap (hints );
832
+
833
+ output .put (Constants .APP_NAME , getAuth (arguments ).getApp ().getName ());
834
+
835
+ output .put (Constants .MULTI_FACTOR_HINTS , pigeonHints );
836
+
837
+ output .put (Constants .MULTI_FACTOR_SESSION_ID , sessionId );
838
+ output .put (Constants .MULTI_FACTOR_RESOLVER_ID , resolverId );
839
+
840
+ taskCompletionSource .setException (
841
+ new FlutterFirebaseAuthPluginException (
842
+ multiFactorException .getErrorCode (),
843
+ multiFactorException .getLocalizedMessage (),
844
+ output ));
845
+ } else {
846
+ taskCompletionSource .setException (e );
847
+ }
794
848
}
795
849
});
796
850
797
851
return taskCompletionSource .getTask ();
798
852
}
799
853
854
+ private List <GeneratedAndroidFirebaseAuth .PigeonMultiFactorInfo > multiFactorInfoToPigeon (
855
+ List <MultiFactorInfo > hints ) {
856
+ List <GeneratedAndroidFirebaseAuth .PigeonMultiFactorInfo > pigeonHints = new ArrayList <>();
857
+ for (MultiFactorInfo info : hints ) {
858
+ if (info instanceof PhoneMultiFactorInfo ) {
859
+ pigeonHints .add (
860
+ new GeneratedAndroidFirebaseAuth .PigeonMultiFactorInfo .Builder ()
861
+ .setPhoneNumber (((PhoneMultiFactorInfo ) info ).getPhoneNumber ())
862
+ .setDisplayName (info .getDisplayName ())
863
+ .setEnrollmentTimestamp ((double ) info .getEnrollmentTimestamp ())
864
+ .setUid (info .getUid ())
865
+ .setFactorId (info .getFactorId ())
866
+ .build ());
867
+
868
+ } else {
869
+ pigeonHints .add (
870
+ new GeneratedAndroidFirebaseAuth .PigeonMultiFactorInfo .Builder ()
871
+ .setDisplayName (info .getDisplayName ())
872
+ .setEnrollmentTimestamp ((double ) info .getEnrollmentTimestamp ())
873
+ .setUid (info .getUid ())
874
+ .setFactorId (info .getFactorId ())
875
+ .build ());
876
+ }
877
+ }
878
+ return pigeonHints ;
879
+ }
880
+
881
+ private List <Map <String , Object >> multiFactorInfoToMap (List <MultiFactorInfo > hints ) {
882
+ List <Map <String , Object >> pigeonHints = new ArrayList <>();
883
+ for (GeneratedAndroidFirebaseAuth .PigeonMultiFactorInfo info : multiFactorInfoToPigeon (hints )) {
884
+ pigeonHints .add (info .toMap ());
885
+ }
886
+ return pigeonHints ;
887
+ }
888
+
800
889
private Task <Map <String , Object >> signInWithEmailLink (Map <String , Object > arguments ) {
801
890
TaskCompletionSource <Map <String , Object >> taskCompletionSource = new TaskCompletionSource <>();
802
891
@@ -883,10 +972,36 @@ private Task<String> verifyPhoneNumber(Map<String, Object> arguments) {
883
972
String eventChannelName =
884
973
METHOD_CHANNEL_NAME + "/phone/" + UUID .randomUUID ().toString ();
885
974
EventChannel channel = new EventChannel (messenger , eventChannelName );
975
+
976
+ final String multiFactorSessionId =
977
+ (String ) arguments .get (Constants .MULTI_FACTOR_SESSION_ID );
978
+ MultiFactorSession multiFactorSession = null ;
979
+
980
+ if (multiFactorSessionId != null ) {
981
+ multiFactorSession = multiFactorSessionMap .get (multiFactorSessionId );
982
+ }
983
+
984
+ final String multiFactorInfoId = (String ) arguments .get (Constants .MULTI_FACTOR_INFO );
985
+ PhoneMultiFactorInfo multiFactorInfo = null ;
986
+
987
+ if (multiFactorInfoId != null ) {
988
+ for (String resolverId : multiFactorResolverMap .keySet ()) {
989
+ for (MultiFactorInfo info : multiFactorResolverMap .get (resolverId ).getHints ()) {
990
+ if (info .getUid ().equals (multiFactorInfoId )
991
+ && info instanceof PhoneMultiFactorInfo ) {
992
+ multiFactorInfo = (PhoneMultiFactorInfo ) info ;
993
+ break ;
994
+ }
995
+ }
996
+ }
997
+ }
998
+
886
999
PhoneNumberVerificationStreamHandler handler =
887
1000
new PhoneNumberVerificationStreamHandler (
888
1001
getActivity (),
889
1002
arguments ,
1003
+ multiFactorSession ,
1004
+ multiFactorInfo ,
890
1005
credential -> {
891
1006
int hashCode = credential .hashCode ();
892
1007
authCredentials .put (hashCode , credential );
@@ -1573,4 +1688,169 @@ private void removeEventListeners() {
1573
1688
}
1574
1689
streamHandlers .clear ();
1575
1690
}
1691
+
1692
+ // Map an app id to a map of user id to a MultiFactorUser object.
1693
+ private final Map <String , Map <String , MultiFactor >> multiFactorUserMap = new HashMap <>();
1694
+
1695
+ // Map an id to a MultiFactorSession object.
1696
+ private final Map <String , MultiFactorSession > multiFactorSessionMap = new HashMap <>();
1697
+
1698
+ // Map an id to a MultiFactorSession object.
1699
+ private final Map <String , MultiFactorResolver > multiFactorResolverMap = new HashMap <>();
1700
+
1701
+ private MultiFactor getAppMultiFactor (@ NonNull String appName )
1702
+ throws FirebaseNoSignedInUserException {
1703
+ final FirebaseUser currentUser = getCurrentUser (appName );
1704
+ if (currentUser == null ) {
1705
+ throw new FirebaseNoSignedInUserException ("No user is signed in" );
1706
+ }
1707
+ if (multiFactorUserMap .get (appName ) == null ) {
1708
+ multiFactorUserMap .put (appName , new HashMap <>());
1709
+ }
1710
+
1711
+ final Map <String , MultiFactor > appMultiFactorUser = multiFactorUserMap .get (appName );
1712
+ if (appMultiFactorUser .get (currentUser .getUid ()) == null ) {
1713
+ appMultiFactorUser .put (currentUser .getUid (), currentUser .getMultiFactor ());
1714
+ }
1715
+
1716
+ final MultiFactor multiFactor = appMultiFactorUser .get (currentUser .getUid ());
1717
+ return multiFactor ;
1718
+ }
1719
+
1720
+ @ Override
1721
+ public void enrollPhone (
1722
+ @ NonNull String appName ,
1723
+ @ NonNull GeneratedAndroidFirebaseAuth .PigeonPhoneMultiFactorAssertion assertion ,
1724
+ @ Nullable String displayName ,
1725
+ GeneratedAndroidFirebaseAuth .Result <Void > result ) {
1726
+ final MultiFactor multiFactor ;
1727
+ try {
1728
+ multiFactor = getAppMultiFactor (appName );
1729
+ } catch (FirebaseNoSignedInUserException e ) {
1730
+ result .error (e );
1731
+ return ;
1732
+ }
1733
+
1734
+ PhoneAuthCredential credential =
1735
+ PhoneAuthProvider .getCredential (
1736
+ assertion .getVerificationId (), assertion .getVerificationCode ());
1737
+
1738
+ MultiFactorAssertion multiFactorAssertion = PhoneMultiFactorGenerator .getAssertion (credential );
1739
+
1740
+ multiFactor
1741
+ .enroll (multiFactorAssertion , displayName )
1742
+ .addOnCompleteListener (
1743
+ task -> {
1744
+ if (task .isSuccessful ()) {
1745
+ result .success (null );
1746
+ } else {
1747
+ result .error (task .getException ());
1748
+ }
1749
+ });
1750
+ }
1751
+
1752
+ @ Override
1753
+ public void getSession (
1754
+ @ NonNull String appName ,
1755
+ GeneratedAndroidFirebaseAuth .Result <GeneratedAndroidFirebaseAuth .PigeonMultiFactorSession >
1756
+ result ) {
1757
+ final MultiFactor multiFactor ;
1758
+ try {
1759
+ multiFactor = getAppMultiFactor (appName );
1760
+ } catch (FirebaseNoSignedInUserException e ) {
1761
+ result .error (e );
1762
+ return ;
1763
+ }
1764
+
1765
+ multiFactor
1766
+ .getSession ()
1767
+ .addOnCompleteListener (
1768
+ task -> {
1769
+ if (task .isSuccessful ()) {
1770
+ final MultiFactorSession sessionResult = task .getResult ();
1771
+ final String id = UUID .randomUUID ().toString ();
1772
+ multiFactorSessionMap .put (id , sessionResult );
1773
+ result .success (
1774
+ new GeneratedAndroidFirebaseAuth .PigeonMultiFactorSession .Builder ()
1775
+ .setId (id )
1776
+ .build ());
1777
+ } else {
1778
+ Exception exception = task .getException ();
1779
+ result .error (exception );
1780
+ }
1781
+ });
1782
+ }
1783
+
1784
+ @ Override
1785
+ public void unenroll (
1786
+ @ NonNull String appName ,
1787
+ @ Nullable String factorUid ,
1788
+ GeneratedAndroidFirebaseAuth .Result <Void > result ) {
1789
+ final MultiFactor multiFactor ;
1790
+ try {
1791
+ multiFactor = getAppMultiFactor (appName );
1792
+ } catch (FirebaseNoSignedInUserException e ) {
1793
+ result .error (e );
1794
+ return ;
1795
+ }
1796
+
1797
+ multiFactor
1798
+ .unenroll (factorUid )
1799
+ .addOnCompleteListener (
1800
+ task -> {
1801
+ if (task .isSuccessful ()) {
1802
+ result .success (null );
1803
+ } else {
1804
+ result .error (task .getException ());
1805
+ }
1806
+ });
1807
+ }
1808
+
1809
+ @ Override
1810
+ public void getEnrolledFactors (
1811
+ @ NonNull String appName ,
1812
+ GeneratedAndroidFirebaseAuth .Result <List <GeneratedAndroidFirebaseAuth .PigeonMultiFactorInfo >>
1813
+ result ) {
1814
+ final MultiFactor multiFactor ;
1815
+ try {
1816
+ multiFactor = getAppMultiFactor (appName );
1817
+ } catch (FirebaseNoSignedInUserException e ) {
1818
+ result .error (e );
1819
+ return ;
1820
+ }
1821
+
1822
+ final List <MultiFactorInfo > factors = multiFactor .getEnrolledFactors ();
1823
+
1824
+ final List <GeneratedAndroidFirebaseAuth .PigeonMultiFactorInfo > resultFactors =
1825
+ multiFactorInfoToPigeon (factors );
1826
+
1827
+ result .success (resultFactors );
1828
+ }
1829
+
1830
+ @ Override
1831
+ public void resolveSignIn (
1832
+ @ NonNull String resolverId ,
1833
+ @ NonNull GeneratedAndroidFirebaseAuth .PigeonPhoneMultiFactorAssertion assertion ,
1834
+ GeneratedAndroidFirebaseAuth .Result <Map <String , Object >> result ) {
1835
+ final MultiFactorResolver resolver = multiFactorResolverMap .get (resolverId );
1836
+
1837
+ PhoneAuthCredential credential =
1838
+ PhoneAuthProvider .getCredential (
1839
+ assertion .getVerificationId (), assertion .getVerificationCode ());
1840
+
1841
+ MultiFactorAssertion multiFactorAssertion = PhoneMultiFactorGenerator .getAssertion (credential );
1842
+
1843
+ resolver
1844
+ .resolveSignIn (multiFactorAssertion )
1845
+ .addOnCompleteListener (
1846
+ task -> {
1847
+ if (task .isSuccessful ()) {
1848
+ final AuthResult authResult = task .getResult ();
1849
+ result .success (parseAuthResult (authResult ));
1850
+ } else {
1851
+ Exception exception = task .getException ();
1852
+ result .error (exception );
1853
+ }
1854
+ });
1855
+ }
1576
1856
}
0 commit comments