From a272910119e79058284ed2ec2c7bdc89f73a68fa Mon Sep 17 00:00:00 2001 From: Joel Arvidsson Date: Mon, 10 Sep 2018 14:56:33 +0200 Subject: [PATCH] [auth] Support updatePhoneNumber --- .../firebase/auth/RNFirebaseAuth.java | 87 ++++++++++++++++--- ios/RNFirebase/auth/RNFirebaseAuth.m | 42 +++++++++ src/modules/auth/User.js | 27 ++++-- tests/e2e/auth/user.e2e.js | 12 --- 4 files changed, 137 insertions(+), 31 deletions(-) diff --git a/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java b/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java index 1c200ca6..3f044b45 100644 --- a/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java +++ b/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java @@ -711,6 +711,63 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } } + /** + * updatePhoneNumber + * + * @param provider + * @param authToken + * @param authSecret + * @param promise + */ + @ReactMethod + private void updatePhoneNumber( + String appName, + String provider, + String authToken, + String authSecret, + final Promise promise + ) { + FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); + final FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); + FirebaseUser user = firebaseAuth.getCurrentUser(); + + if(!provider.equals("phone")) { + promise.reject( + "auth/invalid-credential", + "The supplied auth credential does not have a phone provider." + ); + } + + PhoneAuthCredential credential = getPhoneAuthCredential(authToken, authSecret); + + if (credential == null) { + promise.reject( + "auth/invalid-credential", + "The supplied auth credential is malformed, has expired or is not currently supported." + ); + } else if (user == null) { + promiseNoUser(promise, false); + Log.e(TAG, "updatePhoneNumber:failure:noCurrentUser"); + } else { + Log.d(TAG, "updatePhoneNumber"); + user + .updatePhoneNumber(credential) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + Log.d(TAG, "updatePhoneNumber:onComplete:success"); + promiseWithUser(firebaseAuth.getCurrentUser(), promise); + } else { + Exception exception = task.getException(); + Log.e(TAG, "updatePhoneNumber:onComplete:failure", exception); + promiseRejectAuthException(promise, exception); + } + } + }); + } + } + /** * updateProfile * @@ -1452,16 +1509,7 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { case "oauth": return OAuthProvider.getCredential(provider, authToken, authSecret); case "phone": - // If the phone number is auto-verified quickly, then the verificationId can be null - // We cached the credential as part of the verifyPhoneNumber request to be re-used here - // if possible - if (authToken == null && mCredential != null) { - PhoneAuthCredential credential = mCredential; - // Reset the cached credential - mCredential = null; - return credential; - } - return PhoneAuthProvider.getCredential(authToken, authSecret); + return getPhoneAuthCredential(authToken, authSecret); case "password": // authToken = email // authSecret = password @@ -1475,6 +1523,25 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } } + /** + * Returns an instance of PhoneAuthCredential, potentially cached + */ + private PhoneAuthCredential getPhoneAuthCredential( + String authToken, + String authSecret + ) { + // If the phone number is auto-verified quickly, then the verificationId can be null + // We cached the credential as part of the verifyPhoneNumber request to be re-used here + // if possible + if (authToken == null && mCredential != null) { + PhoneAuthCredential credential = mCredential; + // Reset the cached credential + mCredential = null; + return credential; + } + return PhoneAuthProvider.getCredential(authToken, authSecret); + } + @ReactMethod public void getToken(String appName, Boolean forceRefresh, final Promise promise) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); diff --git a/ios/RNFirebase/auth/RNFirebaseAuth.m b/ios/RNFirebase/auth/RNFirebaseAuth.m index 4c0830e6..ec9656f8 100644 --- a/ios/RNFirebase/auth/RNFirebaseAuth.m +++ b/ios/RNFirebase/auth/RNFirebaseAuth.m @@ -445,6 +445,48 @@ RCT_EXPORT_METHOD(updatePassword: } } + +/** + updatePhoneNumber + + @param NSString password + @param NSString provider + @param NSString authToken + @param NSString authSecret + @param RCTPromiseResolveBlock resolve + @param RCTPromiseRejectBlock reject + @return + */ +RCT_EXPORT_METHOD(updatePhoneNumber:(NSString *) appDisplayName + provider:(NSString *) provider + token:(NSString *) authToken + secret:(NSString *) authSecret + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; + + FIRUser *user = [FIRAuth authWithApp:firApp].currentUser; + + if (user) { + FIRAuthCredential *credential = [self getCredentialForProvider:provider token:authToken secret:authSecret]; + + if (credential == nil) { + return reject(@"auth/invalid-credential", @"The supplied auth credential is malformed, has expired or is not currently supported.", nil); + } + + [user updatePhoneNumberCredential:credential completion:^(NSError *_Nullable error) { + if (error) { + [self promiseRejectAuthException:reject error:error]; + } else { + FIRUser *userAfterUpdate = [FIRAuth authWithApp:firApp].currentUser; + [self promiseWithUser:resolve rejecter:reject user:userAfterUpdate]; + } + }]; + } else { + [self promiseNoUser:resolve rejecter:reject isError:YES]; + } +} + /** updateProfile diff --git a/src/modules/auth/User.js b/src/modules/auth/User.js index 7cb30787..6f8c9408 100644 --- a/src/modules/auth/User.js +++ b/src/modules/auth/User.js @@ -250,6 +250,24 @@ export default class User { }); } + /** + * Update the current user's phone number + * + * @param {AuthCredential} credential Auth credential with the _new_ phone number + * @return {Promise} + */ + updatePhoneNumber(credential: AuthCredential): Promise { + return getNativeModule(this._auth) + .updatePhoneNumber( + credential.providerId, + credential.token, + credential.secret + ) + .then(user => { + this._auth._setUser(user); + }); + } + /** * Update the current user's profile * @param {Object} updates An object containing the keys listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile) @@ -318,15 +336,6 @@ export default class User { ); } - updatePhoneNumber() { - throw new Error( - INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD( - 'User', - 'updatePhoneNumber' - ) - ); - } - get refreshToken(): string { throw new Error( INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_PROPERTY('User', 'refreshToken') diff --git a/tests/e2e/auth/user.e2e.js b/tests/e2e/auth/user.e2e.js index f2994e02..3ff39919 100644 --- a/tests/e2e/auth/user.e2e.js +++ b/tests/e2e/auth/user.e2e.js @@ -461,18 +461,6 @@ describe('auth().currentUser', () => { }); }); - describe('updatePhoneNumber()', () => { - it('should throw an unsupported error', async () => { - await firebase.auth().signInAnonymouslyAndRetrieveData(); - (() => { - firebase.auth().currentUser.updatePhoneNumber(); - }).should.throw( - 'User.updatePhoneNumber() is unsupported by the native Firebase SDKs.' - ); - await firebase.auth().signOut(); - }); - }); - describe('refreshToken', () => { it('should throw an unsupported error', async () => { await firebase.auth().signInAnonymouslyAndRetrieveData();