From f62e71852299976dc4a412fb87d1ef7459e7c565 Mon Sep 17 00:00:00 2001 From: Elies Lou Date: Mon, 3 Apr 2023 16:52:42 +0200 Subject: [PATCH] feat(oauth): added signIn and link for oauth --- .../auth/ReactNativeFirebaseAuthModule.java | 68 ++++++++++++++++++- packages/auth/e2e/auth.e2e.js | 22 +++--- packages/auth/e2e/provider.e2e.js | 4 +- packages/auth/lib/User.js | 13 ++-- packages/auth/lib/index.d.ts | 56 ++++++++++++++- packages/auth/lib/index.js | 14 ++-- packages/auth/lib/providers/OAuthProvider.js | 10 ++- 7 files changed, 154 insertions(+), 33 deletions(-) diff --git a/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java b/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java index 62f928b05f3..f290fd2663f 100644 --- a/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java +++ b/packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java @@ -30,6 +30,9 @@ import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseException; import com.google.firebase.FirebaseNetworkException; @@ -56,6 +59,7 @@ import com.google.firebase.auth.MultiFactorResolver; import com.google.firebase.auth.MultiFactorSession; import com.google.firebase.auth.OAuthProvider; +import com.google.firebase.auth.OAuthCredential; import com.google.firebase.auth.PhoneAuthCredential; import com.google.firebase.auth.PhoneAuthOptions; import com.google.firebase.auth.PhoneAuthProvider; @@ -203,7 +207,6 @@ public void addIdTokenListener(final String appName) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); - if (!mIdTokenListeners.containsKey(appName)) { FirebaseAuth.IdTokenListener newIdTokenListener = firebaseAuth1 -> { @@ -838,6 +841,45 @@ private void signInWithCredential( }); } } + @ReactMethod + public void signInWithProvider(String appName, String providerId, @Nullable String email, Promise promise){ + OAuthProvider.Builder provider = OAuthProvider.newBuilder(providerId); + if(email != null){ + provider.addCustomParameter("login_hint", email); + } + Activity activity = getCurrentActivity(); + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); + + OnSuccessListener onSuccess = new OnSuccessListener(){ + @Override + public void onSuccess(AuthResult authResult) { + Log.d(TAG, "signInWithProvider:onComplete:success"); + promiseWithAuthResult(authResult, promise); + } + }; + + OnFailureListener onFailure = new OnFailureListener(){ + @Override + public void onFailure(@NonNull Exception e) { + Log.w(TAG, "signInWithProvider:onComplete:failure", e); + promiseRejectAuthException(promise, e); + } + }; + + + Task pendingResultTask = firebaseAuth.getPendingAuthResult(); + if(pendingResultTask != null){ + pendingResultTask + .addOnSuccessListener(onSuccess) + .addOnFailureListener(onFailure); + } else { + firebaseAuth + .startActivityForSignInWithProvider(activity, provider.build()) + .addOnSuccessListener(onSuccess) + .addOnFailureListener(onFailure); + } + } /** * signInWithPhoneNumber @@ -1866,6 +1908,30 @@ private void promiseWithAuthResult(AuthResult authResult, Promise promise) { WritableMap authResultMap = Arguments.createMap(); WritableMap userMap = firebaseUserToMap(authResult.getUser()); + if(authResult.getCredential() != null){ + if(authResult.getCredential() instanceof OAuthCredential){ + OAuthCredential creds = (OAuthCredential) authResult.getCredential(); + WritableMap credentialMap = Arguments.createMap(); + + credentialMap.putString("providerId", creds.getProvider()); + credentialMap.putString("signInMethod", creds.getSignInMethod()); + + if(creds.getIdToken() != null){ + credentialMap.putString("idToken", creds.getIdToken()); + } + + if(creds.getAccessToken() != null){ + credentialMap.putString("accessToken", creds.getAccessToken()); + } + + if(creds.getSecret() != null){ + credentialMap.putString("secret", creds.getSecret()); + } + + authResultMap.putMap("credential", credentialMap); + } + } + if (authResult.getAdditionalUserInfo() != null) { WritableMap additionalUserInfoMap = Arguments.createMap(); diff --git a/packages/auth/e2e/auth.e2e.js b/packages/auth/e2e/auth.e2e.js index 5c89134e8a7..602b62d32c6 100644 --- a/packages/auth/e2e/auth.e2e.js +++ b/packages/auth/e2e/auth.e2e.js @@ -912,12 +912,11 @@ describe('auth()', function () { }); describe('signInWithPopup', function () { - it('should throw an unsupported error', function () { - (() => { - firebase.auth().signInWithPopup(); - }).should.throw( - 'firebase.auth().signInWithPopup() is unsupported by the native Firebase SDKs.', - ); + it('should trigger the oauth flow', async function () { + await (async () => { + const provider = new firebase.auth.OAuthProvider('oidc.react.com'); + await firebase.auth().signInWithPopup(provider); + }).should.not.throw(); }); }); @@ -1025,12 +1024,11 @@ describe('auth()', function () { }); describe('signInWithRedirect()', function () { - it('should throw an unsupported error', function () { - (() => { - firebase.auth().signInWithRedirect(); - }).should.throw( - 'firebase.auth().signInWithRedirect() is unsupported by the native Firebase SDKs.', - ); + it('should trigger the oauth flow', async function () { + await (async () => { + const provider = new firebase.auth.OAuthProvider('oidc.react.com'); + await firebase.auth().signInWithRedirect(provider); + }).should.not.throw(); }); }); diff --git a/packages/auth/e2e/provider.e2e.js b/packages/auth/e2e/provider.e2e.js index 1475eb56759..36fea1ea290 100644 --- a/packages/auth/e2e/provider.e2e.js +++ b/packages/auth/e2e/provider.e2e.js @@ -149,9 +149,7 @@ describe('auth() -> Providers', function () { describe('OAuthProvider', function () { describe('constructor', function () { it('should throw an unsupported error', function () { - (() => new firebase.auth.OAuthProvider()).should.throw( - '`new OAuthProvider()` is not supported on the native Firebase SDKs.', - ); + (() => new firebase.auth.OAuthProvider('oidc.react.com')).should.not.throw(); }); }); diff --git a/packages/auth/lib/User.js b/packages/auth/lib/User.js index 8ac80ae49b9..30a68916ac7 100644 --- a/packages/auth/lib/User.js +++ b/packages/auth/lib/User.js @@ -310,16 +310,15 @@ export default class User { ); } - linkWithPopup() { - throw new Error( - 'firebase.auth.User.linkWithPopup() is unsupported by the native Firebase SDKs.', + async linkWithPopup(provider) { + return this._auth.native.signInWithProvider( + provider.providerId, + provider.customParameters?.login_hint, ); } - linkWithRedirect() { - throw new Error( - 'firebase.auth.User.linkWithRedirect() is unsupported by the native Firebase SDKs.', - ); + async linkWithRedirect(provider) { + return this.linkWithPopup(provider); } reauthenticateWithPhoneNumber() { diff --git a/packages/auth/lib/index.d.ts b/packages/auth/lib/index.d.ts index 30d2bebf970..ad08c452824 100644 --- a/packages/auth/lib/index.d.ts +++ b/packages/auth/lib/index.d.ts @@ -109,6 +109,11 @@ export namespace FirebaseAuthTypes { credential: (token: string | null, secret?: string) => AuthCredential; } + export type OAuthProvider = AuthProvider & { + new (providerId: string): OAuthProvider; + setCustomParameters: (customOAuthParameters: object) => void; + }; + /** * Interface that represents an Open ID Connect auth provider. Implemented by other providers. */ @@ -315,7 +320,7 @@ export namespace FirebaseAuthTypes { * firebase.auth.OAuthProvider; * ``` */ - OAuthProvider: AuthProvider; + OAuthProvider: OAuthProvider; /** * Custom Open ID connect auth provider implementation. * @@ -387,6 +392,12 @@ export namespace FirebaseAuthTypes { * Any additional user information assigned to the user. */ additionalUserInfo?: AdditionalUserInfo; + + /** + * The AuthCredential returned from the identity provider. + */ + credential: AuthCredential | null; + /** * Returns the {@link auth.User} interface of this credential. */ @@ -1198,6 +1209,46 @@ export namespace FirebaseAuthTypes { */ linkWithCredential(credential: AuthCredential): Promise; + /** + * Link the user with a 3rd party provider. + * + * #### Example + * + * ```js + * const oauthProvider = new firebase.auth.OAuthProvider('oidc.react.com') + * const authCredentials = await firebase.auth().currentUser.linkWithPopup(oauthProvider); + * ``` + * + * @error auth/provider-already-linked Thrown if the provider has already been linked to the user. This error is thrown even if this is not the same provider's account that is currently linked to the user. + * @error auth/invalid-credential Thrown if the provider's credential is not valid. This can happen if it has already expired when calling link, or if it used invalid token(s). See the Firebase documentation for your provider, and make sure you pass in the correct parameters to the credential method. + * @error auth/credential-already-in-use Thrown if the account corresponding to the credential already exists among your users, or is already linked to a Firebase User. + * @error auth/email-already-in-use Thrown if the email corresponding to the credential already exists among your users. + * @error auth/operation-not-allowed Thrown if you have not enabled the provider in the Firebase Console. Go to the Firebase Console for your project, in the Auth section and the Sign in Method tab and configure the provider. + * @throws on iOS {@link auth.NativeFirebaseAuthError}, on Android {@link auth.NativeFirebaseError} + * @param provider A created {@link auth.AuthProvider}. + */ + linkWithPopup(provider: AuthProvider): Promise; + + /** + * Link the user with a 3rd party provider. + * + * #### Example + * + * ```js + * const oauthProvider = new firebase.auth.OAuthProvider('oidc.react.com') + * const authCredentials = await firebase.auth().currentUser.linkWithPopup(oauthProvider); + * ``` + * + * @error auth/provider-already-linked Thrown if the provider has already been linked to the user. This error is thrown even if this is not the same provider's account that is currently linked to the user. + * @error auth/invalid-credential Thrown if the provider's credential is not valid. This can happen if it has already expired when calling link, or if it used invalid token(s). See the Firebase documentation for your provider, and make sure you pass in the correct parameters to the credential method. + * @error auth/credential-already-in-use Thrown if the account corresponding to the credential already exists among your users, or is already linked to a Firebase User. + * @error auth/email-already-in-use Thrown if the email corresponding to the credential already exists among your users. + * @error auth/operation-not-allowed Thrown if you have not enabled the provider in the Firebase Console. Go to the Firebase Console for your project, in the Auth section and the Sign in Method tab and configure the provider. + * @throws on iOS {@link auth.NativeFirebaseAuthError}, on Android {@link auth.NativeFirebaseError} + * @param provider A created {@link auth.AuthProvider}. + */ + linkWithRedirect(provider: Provider): Promise; + /** * Re-authenticate a user with a third-party authentication provider. * @@ -1708,6 +1759,9 @@ export namespace FirebaseAuthTypes { */ signInWithCredential(credential: AuthCredential): Promise; + signInWithPopup(provider: AuthProvider): Promise; + signInWithRedirect(provider: AuthProvider): Promise; + /** * Sends a password reset email to the given email address. * Unlike the web SDK, the email will contain a password reset link rather than a code. diff --git a/packages/auth/lib/index.js b/packages/auth/lib/index.js index fe6af99339b..fd083553c21 100644 --- a/packages/auth/lib/index.js +++ b/packages/auth/lib/index.js @@ -360,16 +360,16 @@ class FirebaseAuthModule extends FirebaseModule { throw new Error('firebase.auth().setPersistence() is unsupported by the native Firebase SDKs.'); } - signInWithPopup() { - throw new Error( - 'firebase.auth().signInWithPopup() is unsupported by the native Firebase SDKs.', - ); + signInWithPopup(provider) { + return this.native + .signInWithProvider(provider.providerId, provider.customParameters?.login_hint) + .then(userCredential => this._setUserCredential(userCredential)); } signInWithRedirect() { - throw new Error( - 'firebase.auth().signInWithRedirect() is unsupported by the native Firebase SDKs.', - ); + return this.native + .signInWithProvider(provider.providerId, provider.customParameters?.login_hint) + .then(userCredential => this._setUserCredential(userCredential)); } // firebase issue - https://github.com/invertase/react-native-firebase/pull/655#issuecomment-349904680 diff --git a/packages/auth/lib/providers/OAuthProvider.js b/packages/auth/lib/providers/OAuthProvider.js index 7ef56c624ca..837fbf338aa 100644 --- a/packages/auth/lib/providers/OAuthProvider.js +++ b/packages/auth/lib/providers/OAuthProvider.js @@ -18,8 +18,14 @@ const providerId = 'oauth'; export default class OAuthProvider { - constructor() { - throw new Error('`new OAuthProvider()` is not supported on the native Firebase SDKs.'); + constructor(providerId) { + this.providerId = providerId; + } + + customParameters = {}; + + setCustomParameters(customParameters) { + this.customParameters = customParameters; } static get PROVIDER_ID() {