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 826f2d3c..cecc9b49 100644 --- a/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java +++ b/android/src/main/java/io/invertase/firebase/auth/RNFirebaseAuth.java @@ -93,13 +93,11 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { FirebaseUser user = firebaseAuth.getCurrentUser(); WritableMap msgMap = Arguments.createMap(); if (user != null) { - msgMap.putBoolean("authenticated", true); msgMap.putString("appName", appName); // for js side distribution msgMap.putMap("user", firebaseUserToMap(user)); Utils.sendEvent(mReactContext, "auth_state_changed", msgMap); } else { msgMap.putString("appName", appName); // for js side distribution - msgMap.putBoolean("authenticated", false); Utils.sendEvent(mReactContext, "auth_state_changed", msgMap); } } @@ -180,11 +178,6 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } } - /** - * signOut - * - * @param promise - */ @ReactMethod public void signOut(String appName, Promise promise) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); @@ -199,13 +192,17 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } } - /** - * signInAnonymously - * - * @param promise - */ @ReactMethod public void signInAnonymously(String appName, final Promise promise) { + signInAnonymously(appName, promise, false); + } + + @ReactMethod + public void signInAnonymouslyAndRetrieveData(String appName, final Promise promise) { + signInAnonymously(appName, promise, true); + } + + public void signInAnonymously(String appName, final Promise promise, final boolean withData) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); @@ -215,7 +212,11 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { @Override public void onSuccess(AuthResult authResult) { Log.d(TAG, "signInAnonymously:onComplete:success"); - promiseWithUser(authResult.getUser(), promise); + if (withData) { + promiseWithAuthResult(authResult, promise); + } else { + promiseWithUser(authResult.getUser(), promise); + } } }) .addOnFailureListener(new OnFailureListener() { @@ -236,6 +237,15 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { */ @ReactMethod public void createUserWithEmailAndPassword(String appName, final String email, final String password, final Promise promise) { + createUserWithEmailAndPassword(appName, email, password, promise, false); + } + + @ReactMethod + public void createUserAndRetrieveDataWithEmailAndPassword(String appName, final String email, final String password, final Promise promise) { + createUserWithEmailAndPassword(appName, email, password, promise, true); + } + + public void createUserWithEmailAndPassword(String appName, final String email, final String password, final Promise promise, final boolean withData) { Log.d(TAG, "createUserWithEmailAndPassword"); FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); @@ -245,7 +255,11 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { @Override public void onSuccess(AuthResult authResult) { Log.d(TAG, "createUserWithEmailAndPassword:onComplete:success"); - promiseWithUser(authResult.getUser(), promise); + if (withData) { + promiseWithAuthResult(authResult, promise); + } else { + promiseWithUser(authResult.getUser(), promise); + } } }) .addOnFailureListener(new OnFailureListener() { @@ -266,6 +280,15 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { */ @ReactMethod public void signInWithEmailAndPassword(String appName, final String email, final String password, final Promise promise) { + signInWithEmailAndPassword(appName, email, password, promise, false); + } + + @ReactMethod + public void signInAndRetrieveDataWithEmailAndPassword(String appName, final String email, final String password, final Promise promise) { + signInWithEmailAndPassword(appName, email, password, promise, true); + } + + public void signInWithEmailAndPassword(String appName, final String email, final String password, final Promise promise, final boolean withData) { Log.d(TAG, "signInWithEmailAndPassword"); FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); @@ -275,7 +298,11 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { @Override public void onSuccess(AuthResult authResult) { Log.d(TAG, "signInWithEmailAndPassword:onComplete:success"); - promiseWithUser(authResult.getUser(), promise); + if (withData) { + promiseWithAuthResult(authResult, promise); + } else { + promiseWithUser(authResult.getUser(), promise); + } } }) .addOnFailureListener(new OnFailureListener() { @@ -288,14 +315,18 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } - /** - * signInWithCustomToken - * - * @param token - * @param promise - */ @ReactMethod public void signInWithCustomToken(String appName, final String token, final Promise promise) { + signInWithCustomToken(appName, token, promise, false); + } + + + @ReactMethod + public void signInAndRetrieveDataWithCustomToken(String appName, final String token, final Promise promise) { + signInWithCustomToken(appName, token, promise, true); + } + + public void signInWithCustomToken(String appName, final String token, final Promise promise, final boolean withData) { Log.d(TAG, "signInWithCustomToken"); FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); @@ -305,7 +336,11 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { @Override public void onSuccess(AuthResult authResult) { Log.d(TAG, "signInWithCustomToken:onComplete:success"); - promiseWithUser(authResult.getUser(), promise); + if (withData) { + promiseWithAuthResult(authResult, promise); + } else { + promiseWithUser(authResult.getUser(), promise); + } } }) .addOnFailureListener(new OnFailureListener() { @@ -583,16 +618,17 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } } - /** - * signInWithCredential - * - * @param provider - * @param authToken - * @param authSecret - * @param promise - */ @ReactMethod public void signInWithCredential(String appName, String provider, String authToken, String authSecret, final Promise promise) { + signInWithCredential(appName, provider, authToken, authSecret, promise, false); + } + + @ReactMethod + public void signInAndRetrieveDataWithCredential(String appName, String provider, String authToken, String authSecret, final Promise promise) { + signInWithCredential(appName, provider, authToken, authSecret, promise, true); + } + + public void signInWithCredential(String appName, String provider, String authToken, String authSecret, final Promise promise, final boolean withData) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); @@ -608,7 +644,11 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { Log.d(TAG, "signInWithCredential:onComplete:success"); - promiseWithUser(task.getResult().getUser(), promise); + if (withData) { + promiseWithAuthResult(task.getResult(), promise); + } else { + promiseWithUser(task.getResult().getUser(), promise); + } } else { Exception exception = task.getException(); Log.e(TAG, "signInWithCredential:onComplete:failure", exception); @@ -936,7 +976,7 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } /** - * link + * linkWithCredential * * @param provider * @param authToken @@ -945,6 +985,15 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { */ @ReactMethod public void linkWithCredential(String appName, String provider, String authToken, String authSecret, final Promise promise) { + link(appName, provider, authToken, authSecret, promise, false); + } + + @ReactMethod + public void linkAndRetrieveDataWithCredential(String appName, String provider, String authToken, String authSecret, final Promise promise) { + link(appName, provider, authToken, authSecret, promise, true); + } + + private void link(String appName, String provider, String authToken, String authSecret, final Promise promise, final boolean withData) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp); @@ -963,7 +1012,11 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { Log.d(TAG, "link:onComplete:success"); - promiseWithUser(task.getResult().getUser(), promise); + if (withData) { + promiseWithAuthResult(task.getResult(), promise); + } else { + promiseWithUser(task.getResult().getUser(), promise); + } } else { Exception exception = task.getException(); Log.e(TAG, "link:onComplete:failure", exception); @@ -977,13 +1030,6 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } } - /** - * unlink - * - * @param providerId - * @param promise - * @url https://firebase.google.com/docs/reference/android/com/google/firebase/auth/FirebaseUser.html#unlink(java.lang.String) - */ @ReactMethod public void unlink(final String appName, final String providerId, final Promise promise) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); @@ -1011,14 +1057,6 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } } - /** - * reauthenticateWithCredential - * - * @param provider - * @param authToken - * @param authSecret - * @param promise - */ @ReactMethod public void reauthenticateWithCredential(String appName, String provider, String authToken, String authSecret, final Promise promise) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); @@ -1055,11 +1093,6 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { /** * Returns an instance of AuthCredential for the specified provider - * - * @param provider - * @param authToken - * @param authSecret - * @return */ private AuthCredential getCredentialForProvider(String provider, String authToken, String authSecret) { switch (provider) { @@ -1089,11 +1122,6 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } } - /** - * getToken - * - * @param promise - */ @ReactMethod public void getToken(String appName, Boolean forceRefresh, final Promise promise) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); @@ -1122,12 +1150,6 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } } - /** - * fetchProvidersForEmail - * - * @param appName - * @param promise - */ @ReactMethod public void fetchProvidersForEmail(String appName, String email, final Promise promise) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); @@ -1160,11 +1182,6 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { }); } - /** - * Set the language code for the auth module - * @param appName - * @param code - */ @ReactMethod public void setLanguageCode(String appName, String code) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); @@ -1173,10 +1190,6 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { firebaseAuth.setLanguageCode(code); } - /** - * Use the device language - * @param appName - */ @ReactMethod public void useDeviceLanguage(String appName) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); @@ -1218,6 +1231,97 @@ class RNFirebaseAuth extends ReactContextBaseJavaModule { } } + /** + * promiseWithAuthResult + * + * @param authResult + * @param promise + */ + private void promiseWithAuthResult(AuthResult authResult, Promise promise) { + if (authResult != null && authResult.getUser() != null) { + WritableMap userMap = firebaseUserToMap(authResult.getUser()); + WritableMap authResultMap = Arguments.createMap(); + if (authResult.getAdditionalUserInfo() != null) { + WritableMap additionalUserInfoMap = Arguments.createMap(); + additionalUserInfoMap.putBoolean("isNewUser", authResult.getAdditionalUserInfo().isNewUser()); + if (authResult.getAdditionalUserInfo().getProfile() != null) { + WritableMap profileMap = mapToWritableMap(authResult.getAdditionalUserInfo().getProfile()); + additionalUserInfoMap.putMap("profile", profileMap); + } + if (authResult.getAdditionalUserInfo().getProviderId() != null) { + additionalUserInfoMap.putString("providerId", authResult.getAdditionalUserInfo().getProviderId()); + } + if (authResult.getAdditionalUserInfo().getUsername() != null) { + additionalUserInfoMap.putString("username", authResult.getAdditionalUserInfo().getUsername()); + } + authResultMap.putMap("additionalUserInfo", additionalUserInfoMap); + } + authResultMap.putMap("user", userMap); + promise.resolve(authResultMap); + } else { + promiseNoUser(promise, true); + } + } + + private WritableMap mapToWritableMap(Map map) { + WritableMap writableMap = Arguments.createMap(); + for (String key : map.keySet()) { + Object value = map.get(key); + if (value == null) { + writableMap.putNull(key); + } else if (value instanceof Boolean) { + writableMap.putBoolean(key, (Boolean) value); + } else if (value instanceof Integer) { + writableMap.putDouble(key, ((Integer) value).doubleValue()); + } else if (value instanceof Long) { + writableMap.putDouble(key, ((Long) value).doubleValue()); + } else if (value instanceof Double) { + writableMap.putDouble(key, (Double) value); + } else if (value instanceof Float) { + writableMap.putDouble(key, ((Float) value).doubleValue()); + } else if (value instanceof String) { + writableMap.putString(key, (String) value); + } else if (Map.class.isAssignableFrom(value.getClass())) { + writableMap.putMap(key, mapToWritableMap((Map) value)); + } else if (List.class.isAssignableFrom(value.getClass())) { + writableMap.putArray(key, listToWritableArray((List) value)); + } else { + Log.e(TAG, "mapToWritableMap: Cannot convert object of type " + value.getClass()); + } + } + + return writableMap; + } + + private WritableArray listToWritableArray(List list) { + WritableArray writableArray = Arguments.createArray(); + for (Object item : list) { + if (item == null) { + writableArray.pushNull(); + } else if (item instanceof Boolean) { + writableArray.pushBoolean((Boolean) item); + } else if (item instanceof Integer) { + writableArray.pushDouble(((Integer) item).doubleValue()); + } else if (item instanceof Long) { + writableArray.pushDouble(((Long) item).doubleValue()); + } else if (item instanceof Double) { + writableArray.pushDouble((Double) item); + } else if (item instanceof Float) { + writableArray.pushDouble(((Float) item).doubleValue()); + } else if (item instanceof String) { + writableArray.pushString((String) item); + } else if (Map.class.isAssignableFrom(item.getClass())) { + writableArray.pushMap(mapToWritableMap((Map) item)); + } else if (List.class.isAssignableFrom(item.getClass())) { + writableArray.pushArray(listToWritableArray((List) item)); + } else { + Log.e(TAG, "listToWritableArray: Cannot convert object of type " + item.getClass()); + } + } + return writableArray; + } + + /** * promiseRejectAuthException * diff --git a/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java b/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java index 7c452efa..d35e44d5 100644 --- a/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java +++ b/android/src/main/java/io/invertase/firebase/firestore/FirestoreSerialize.java @@ -197,7 +197,7 @@ public class FirestoreSerialize { } else if (Map.class.isAssignableFrom(value.getClass())) { typeMap.putString("type", "object"); typeMap.putMap("value", objectMapToWritable((Map) value)); - } else if (List.class.isAssignableFrom(value.getClass())) { + } else if (List.class.isAssignableFrom(value.getClass())) { typeMap.putString("type", "array"); List list = (List) value; Object[] array = list.toArray(new Object[list.size()]); @@ -215,7 +215,6 @@ public class FirestoreSerialize { typeMap.putString("type", "date"); typeMap.putDouble("value", ((Date) value).getTime()); } else { - // TODO: Changed to log an error rather than crash - is this correct? Log.e(TAG, "buildTypeMap: Cannot convert object of type " + value.getClass()); typeMap.putString("type", "null"); typeMap.putNull("value"); diff --git a/ios/RNFirebase/RNFirebaseEvents.h b/ios/RNFirebase/RNFirebaseEvents.h index 96626669..d75c875b 100644 --- a/ios/RNFirebase/RNFirebaseEvents.h +++ b/ios/RNFirebase/RNFirebaseEvents.h @@ -3,7 +3,7 @@ #import -static NSString *const AUTH_CHANGED_EVENT = @"auth_state_changed"; +static NSString *const AUTH_STATE_CHANGED_EVENT = @"auth_state_changed"; static NSString *const AUTH_ID_TOKEN_CHANGED_EVENT = @"auth_id_token_changed"; static NSString *const PHONE_AUTH_STATE_CHANGED_EVENT = @"phone_auth_state_changed"; diff --git a/ios/RNFirebase/auth/RNFirebaseAuth.m b/ios/RNFirebase/auth/RNFirebaseAuth.m index 56a2956d..70753bd9 100644 --- a/ios/RNFirebase/auth/RNFirebaseAuth.m +++ b/ios/RNFirebase/auth/RNFirebaseAuth.m @@ -29,9 +29,9 @@ RCT_EXPORT_METHOD(addAuthStateListener: if (![_authStateHandlers valueForKey:firApp.name]) { FIRAuthStateDidChangeListenerHandle newListenerHandle = [[FIRAuth authWithApp:firApp] addAuthStateDidChangeListener:^(FIRAuth *_Nonnull auth, FIRUser *_Nullable user) { if (user != nil) { - [RNFirebaseUtil sendJSEventWithAppName:self app:firApp name:AUTH_CHANGED_EVENT body:@{@"authenticated": @(true), @"user": [self firebaseUserToDict:user]}]; + [RNFirebaseUtil sendJSEventWithAppName:self app:firApp name:AUTH_STATE_CHANGED_EVENT body:@{@"user": [self firebaseUserToDict:user]}]; } else { - [RNFirebaseUtil sendJSEventWithAppName:self app:firApp name:AUTH_CHANGED_EVENT body:@{@"authenticated": @(false)}]; + [RNFirebaseUtil sendJSEventWithAppName:self app:firApp name:AUTH_STATE_CHANGED_EVENT body:@{}]; } }]; @@ -64,9 +64,9 @@ RCT_EXPORT_METHOD(addIdTokenListener: if (![_idTokenHandlers valueForKey:firApp.name]) { FIRIDTokenDidChangeListenerHandle newListenerHandle = [[FIRAuth authWithApp:firApp] addIDTokenDidChangeListener:^(FIRAuth * _Nonnull auth, FIRUser * _Nullable user) { if (user != nil) { - [RNFirebaseUtil sendJSEventWithAppName:self app:firApp name:AUTH_ID_TOKEN_CHANGED_EVENT body:@{@"authenticated": @(true), @"user": [self firebaseUserToDict:user]}]; + [RNFirebaseUtil sendJSEventWithAppName:self app:firApp name:AUTH_ID_TOKEN_CHANGED_EVENT body:@{@"user": [self firebaseUserToDict:user]}]; } else { - [RNFirebaseUtil sendJSEventWithAppName:self app:firApp name:AUTH_ID_TOKEN_CHANGED_EVENT body:@{@"authenticated": @(false)}]; + [RNFirebaseUtil sendJSEventWithAppName:self app:firApp name:AUTH_ID_TOKEN_CHANGED_EVENT body:@{}]; } }]; @@ -125,12 +125,9 @@ RCT_EXPORT_METHOD(signOut: @param RCTPromiseRejectBlock reject @return */ -RCT_EXPORT_METHOD(signInAnonymously: - (NSString *) appDisplayName - resolver: - (RCTPromiseResolveBlock) resolve - rejecter: - (RCTPromiseRejectBlock) reject) { +RCT_EXPORT_METHOD(signInAnonymously:(NSString *) appDisplayName + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; [[FIRAuth authWithApp:firApp] signInAnonymouslyWithCompletion:^(FIRUser *user, NSError *error) { @@ -140,7 +137,27 @@ RCT_EXPORT_METHOD(signInAnonymously: [self promiseWithUser:resolve rejecter:reject user:user]; } }]; +} +/** + signInAnonymouslyAndRetrieveData + + @param RCTPromiseResolveBlock resolve + @param RCTPromiseRejectBlock reject + @return + */ +RCT_EXPORT_METHOD(signInAnonymouslyAndRetrieveData:(NSString *) appDisplayName + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; + + [[FIRAuth authWithApp:firApp] signInAnonymouslyAndRetrieveDataWithCompletion:^(FIRAuthDataResult *authResult, NSError *error) { + if (error) { + [self promiseRejectAuthException:reject error:error]; + } else { + [self promiseWithAuthResult:resolve rejecter:reject authResult:authResult]; + } + }]; } /** @@ -152,16 +169,11 @@ RCT_EXPORT_METHOD(signInAnonymously: @param RCTPromiseRejectBlock reject @return return */ -RCT_EXPORT_METHOD(signInWithEmailAndPassword: - (NSString *) appDisplayName - email: - (NSString *) email - pass: - (NSString *) password - resolver: - (RCTPromiseResolveBlock) resolve - rejecter: - (RCTPromiseRejectBlock) reject) { +RCT_EXPORT_METHOD(signInWithEmailAndPassword:(NSString *) appDisplayName + email:(NSString *) email + pass:(NSString *) password + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; [[FIRAuth authWithApp:firApp] signInWithEmail:email password:password completion:^(FIRUser *user, NSError *error) { @@ -173,6 +185,31 @@ RCT_EXPORT_METHOD(signInWithEmailAndPassword: }]; } +/** + signInAndRetrieveDataWithEmailAndPassword + + @param NSString NSString email + @param NSString NSString password + @param RCTPromiseResolveBlock resolve + @param RCTPromiseRejectBlock reject + @return return + */ +RCT_EXPORT_METHOD(signInAndRetrieveDataWithEmailAndPassword:(NSString *) appDisplayName + email:(NSString *) email + pass:(NSString *) password + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; + + [[FIRAuth authWithApp:firApp] signInAndRetrieveDataWithEmail:email password:password completion:^(FIRAuthDataResult *authResult, NSError *error) { + if (error) { + [self promiseRejectAuthException:reject error:error]; + } else { + [self promiseWithAuthResult:resolve rejecter:reject authResult:authResult]; + } + }]; +} + /** createUserWithEmailAndPassword @@ -182,16 +219,11 @@ RCT_EXPORT_METHOD(signInWithEmailAndPassword: @param RCTPromiseRejectBlock reject @return return */ -RCT_EXPORT_METHOD(createUserWithEmailAndPassword: - (NSString *) appDisplayName - email: - (NSString *) email - pass: - (NSString *) password - resolver: - (RCTPromiseResolveBlock) resolve - rejecter: - (RCTPromiseRejectBlock) reject) { +RCT_EXPORT_METHOD(createUserWithEmailAndPassword:(NSString *) appDisplayName + email:(NSString *) email + pass:(NSString *) password + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; [[FIRAuth authWithApp:firApp] createUserWithEmail:email password:password completion:^(FIRUser *user, NSError *error) { @@ -203,6 +235,32 @@ RCT_EXPORT_METHOD(createUserWithEmailAndPassword: }]; } +/** + createUserAndRetrieveDataWithEmailAndPassword + + @param NSString NSString email + @param NSString NSString password + @param RCTPromiseResolveBlock resolve + @param RCTPromiseRejectBlock reject + @return return + */ +RCT_EXPORT_METHOD(createUserAndRetrieveDataWithEmailAndPassword:(NSString *) appDisplayName + email:(NSString *) email + pass:(NSString *) password + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; + + [[FIRAuth authWithApp:firApp] createUserAndRetrieveDataWithEmail:email password:password + completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { + if (error) { + [self promiseRejectAuthException:reject error:error]; + } else { + [self promiseWithAuthResult:resolve rejecter:reject authResult:authResult]; + } + }]; +} + /** deleteUser @@ -447,18 +505,12 @@ RCT_EXPORT_METHOD(getToken: @param RCTPromiseRejectBlock reject @return */ -RCT_EXPORT_METHOD(signInWithCredential: - (NSString *) appDisplayName - provider: - (NSString *) provider - token: - (NSString *) authToken - secret: - (NSString *) authSecret - resolver: - (RCTPromiseResolveBlock) resolve - rejecter: - (RCTPromiseRejectBlock) reject) { +RCT_EXPORT_METHOD(signInWithCredential:(NSString *) appDisplayName + provider:(NSString *) provider + token:(NSString *) authToken + secret:(NSString *) authSecret + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; FIRAuthCredential *credential = [self getCredentialForProvider:provider token:authToken secret:authSecret]; @@ -476,6 +528,39 @@ RCT_EXPORT_METHOD(signInWithCredential: }]; } +/** + signInAndRetrieveDataWithCredential + + @param NSString provider + @param NSString authToken + @param NSString authSecret + @param RCTPromiseResolveBlock resolve + @param RCTPromiseRejectBlock reject + @return + */ +RCT_EXPORT_METHOD(signInAndRetrieveDataWithCredential:(NSString *) appDisplayName + provider:(NSString *) provider + token:(NSString *) authToken + secret:(NSString *) authSecret + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; + + 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); + } + + [[FIRAuth authWithApp:firApp] signInAndRetrieveDataWithCredential:credential completion:^(FIRAuthDataResult *authResult, NSError *error) { + if (error) { + [self promiseRejectAuthException:reject error:error]; + } else { + [self promiseWithAuthResult:resolve rejecter:reject authResult:authResult]; + } + }]; +} + /** confirmPasswordReset @@ -612,22 +697,40 @@ RCT_EXPORT_METHOD(sendPasswordResetEmail:(NSString *) appDisplayName /** - signInWithCustomToken + signInAndRetrieveDataWithCustomToken @param RCTPromiseResolveBlock resolve @param RCTPromiseRejectBlock reject @return */ -RCT_EXPORT_METHOD(signInWithCustomToken: - (NSString *) appDisplayName - customToken: - (NSString *) customToken - resolver: - (RCTPromiseResolveBlock) resolve - rejecter: - (RCTPromiseRejectBlock) reject) { +RCT_EXPORT_METHOD(signInAndRetrieveDataWithCustomToken:(NSString *) appDisplayName + customToken:(NSString *) customToken + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; + [[FIRAuth authWithApp:firApp] signInAndRetrieveDataWithCustomToken:customToken completion:^(FIRAuthDataResult *authResult, NSError *error) { + if (error) { + [self promiseRejectAuthException:reject error:error]; + } else { + [self promiseWithAuthResult:resolve rejecter:reject authResult:authResult]; + } + }]; +} + +/** + signInWithCustomToken + + @param RCTPromiseResolveBlock resolve + @param RCTPromiseRejectBlock reject + @return + */ +RCT_EXPORT_METHOD(signInWithCustomToken:(NSString *) appDisplayName + customToken:(NSString *) customToken + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; + [[FIRAuth authWithApp:firApp] signInWithCustomToken:customToken completion:^(FIRUser *user, NSError *error) { if (error) { [self promiseRejectAuthException:reject error:error]; @@ -718,7 +821,7 @@ RCT_EXPORT_METHOD(_confirmVerificationCode:(NSString *) appDisplayName } /** - link - *insert zelda joke here* + linkWithCredential @param NSString provider @param NSString authToken @@ -727,20 +830,13 @@ RCT_EXPORT_METHOD(_confirmVerificationCode:(NSString *) appDisplayName @param RCTPromiseRejectBlock reject @return */ -RCT_EXPORT_METHOD(linkWithCredential: - (NSString *) appDisplayName - provider: - (NSString *) provider - authToken: - (NSString *) authToken - authSecret: - (NSString *) authSecret - resolver: - (RCTPromiseResolveBlock) resolve - rejecter: - (RCTPromiseRejectBlock) reject) { +RCT_EXPORT_METHOD(linkWithCredential:(NSString *) appDisplayName + provider:(NSString *) provider + authToken:(NSString *) authToken + authSecret:(NSString *) authSecret + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; - FIRAuthCredential *credential = [self getCredentialForProvider:provider token:authToken secret:authSecret]; if (credential == nil) { @@ -762,6 +858,44 @@ RCT_EXPORT_METHOD(linkWithCredential: } } +/** + linkAndRetrieveDataWithCredential + + @param NSString provider + @param NSString authToken + @param NSString authSecret + @param RCTPromiseResolveBlock resolve + @param RCTPromiseRejectBlock reject + @return + */ +RCT_EXPORT_METHOD(linkAndRetrieveDataWithCredential:(NSString *) appDisplayName + provider:(NSString *) provider + authToken:(NSString *) authToken + authSecret:(NSString *) authSecret + resolver:(RCTPromiseResolveBlock) resolve + rejecter:(RCTPromiseRejectBlock) reject) { + FIRApp *firApp = [RNFirebaseUtil getApp:appDisplayName]; + 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); + } + + FIRUser *user = [FIRAuth authWithApp:firApp].currentUser; + if (user) { + [user linkAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { + if (error) { + [self promiseRejectAuthException:reject error:error]; + } else { + [self promiseWithAuthResult:resolve rejecter:reject authResult:authResult]; + } + }]; + } else { + [self promiseNoUser:resolve rejecter:reject isError:YES]; + } +} + /** unlink @@ -1114,6 +1248,32 @@ RCT_EXPORT_METHOD(useDeviceLanguage: } +/** + Resolve or reject a promise based on FIRAuthResult value existance + + @param resolve RCTPromiseResolveBlock + @param reject RCTPromiseRejectBlock + @param authResult FIRAuthDataResult + */ +- (void)promiseWithAuthResult:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject authResult:(FIRAuthDataResult *)authResult { + if (authResult && authResult.user) { + NSDictionary *userDict = [self firebaseUserToDict:authResult.user]; + NSDictionary *authResultDict = @{ + @"additionalUserInfo": authResult.additionalUserInfo ? @{ + @"isNewUser": @(authResult.additionalUserInfo.isNewUser), + @"profile": authResult.additionalUserInfo.profile ? authResult.additionalUserInfo.profile : [NSNull null], + @"providerId": authResult.additionalUserInfo.providerID ? authResult.additionalUserInfo.providerID : [NSNull null], + @"username": authResult.additionalUserInfo.username ? authResult.additionalUserInfo.username : [NSNull null] + } : [NSNull null], + @"user": userDict + }; + resolve(authResultDict); + } else { + [self promiseNoUser:resolve rejecter:reject isError:YES]; + } + +} + /** Converts an array of FIRUserInfo instances into the correct format to match the web sdk @@ -1225,7 +1385,7 @@ RCT_EXPORT_METHOD(useDeviceLanguage: } - (NSArray *)supportedEvents { - return @[AUTH_CHANGED_EVENT, AUTH_ID_TOKEN_CHANGED_EVENT, PHONE_AUTH_STATE_CHANGED_EVENT]; + return @[AUTH_STATE_CHANGED_EVENT, AUTH_ID_TOKEN_CHANGED_EVENT, PHONE_AUTH_STATE_CHANGED_EVENT]; } + (BOOL)requiresMainQueueSetup diff --git a/lib/modules/auth/ConfirmationResult.js b/lib/modules/auth/ConfirmationResult.js index 340be9cb..fd76e69a 100644 --- a/lib/modules/auth/ConfirmationResult.js +++ b/lib/modules/auth/ConfirmationResult.js @@ -25,8 +25,10 @@ export default class ConfirmationResult { * @param verificationCode * @return {*} */ - confirm(verificationCode: string): Promise { - return this._auth._interceptUserValue(getNativeModule(this._auth)._confirmVerificationCode(verificationCode)); + confirm(verificationCode: string): Promise { + return getNativeModule(this._auth) + ._confirmVerificationCode(verificationCode) + .then(user => this._auth._setUser(user)); } get verificationId(): string | null { diff --git a/lib/modules/auth/User.js b/lib/modules/auth/User.js index 917984f4..6e685b35 100644 --- a/lib/modules/auth/User.js +++ b/lib/modules/auth/User.js @@ -6,20 +6,7 @@ import INTERNALS from '../../utils/internals'; import { getNativeModule } from '../../utils/native'; import type Auth from './'; -import type { ActionCodeSettings, AuthCredential, UserMetadata } from '../../types'; - -type NativeUser = { - displayName?: string, - email?: string, - emailVerified?: boolean, - isAnonymous?: boolean, - metadata: UserMetadata, - phoneNumber?: string, - photoURL?: string, - providerData: UserInfo[], - providerId: string, - uid: string, -} +import type { ActionCodeSettings, AuthCredential, NativeUser, UserCredential, UserMetadata } from './types'; type UserInfo = { displayName?: string, @@ -102,8 +89,11 @@ export default class User { * @return {Promise} */ delete(): Promise { - return this._auth - ._interceptUndefinedUserValue(getNativeModule(this._auth).delete()); + return getNativeModule(this._auth) + .delete() + .then(() => { + this._auth._setUser(); + }); } /** @@ -129,8 +119,20 @@ export default class User { * @param credential */ linkWithCredential(credential: AuthCredential): Promise { - return this._auth - ._interceptUserValue(getNativeModule(this._auth).linkWithCredential(credential.providerId, credential.token, credential.secret)); + console.warn('Deprecated firebase.User.prototype.linkWithCredential in favor of firebase.User.prototype.linkAndRetrieveDataWithCredential.'); + return getNativeModule(this._auth) + .linkWithCredential(credential.providerId, credential.token, credential.secret) + .then(user => this._auth._setUser(user)); + } + + /** + * + * @param credential + */ + linkAndRetrieveDataWithCredential(credential: AuthCredential): Promise { + return getNativeModule(this._auth) + .linkAndRetrieveDataWithCredential(credential.providerId, credential.token, credential.secret) + .then(userCredential => this._auth._setUserCredential(userCredential)); } /** @@ -138,8 +140,22 @@ export default class User { * @return {Promise} A promise resolved upon completion */ reauthenticateWithCredential(credential: AuthCredential): Promise { - return this._auth - ._interceptUndefinedUserValue(getNativeModule(this._auth).reauthenticateWithCredential(credential.providerId, credential.token, credential.secret)); + console.warn('Deprecated firebase.User.prototype.reauthenticateWithCredential in favor of firebase.User.prototype.reauthenticateAndRetrieveDataWithCredential.'); + return getNativeModule(this._auth) + .reauthenticateWithCredential(credential.providerId, credential.token, credential.secret) + .then((user) => { + this._auth._setUser(user); + }); + } + + /** + * Re-authenticate a user with a third-party authentication provider + * @return {Promise} A promise resolved upon completion + */ + reauthenticateAndRetrieveDataWithCredential(credential: AuthCredential): Promise { + return getNativeModule(this._auth) + .reauthenticateAndRetrieveDataWithCredential(credential.providerId, credential.token, credential.secret) + .then(userCredential => this._auth._setUserCredential(userCredential)); } /** @@ -147,16 +163,22 @@ export default class User { * @return {Promise} */ reload(): Promise { - return this._auth - ._interceptUndefinedUserValue(getNativeModule(this._auth).reload()); + return getNativeModule(this._auth) + .reload() + .then((user) => { + this._auth._setUser(user); + }); } /** * Send verification email to current user. */ sendEmailVerification(actionCodeSettings?: ActionCodeSettings): Promise { - return this._auth - ._interceptUndefinedUserValue(getNativeModule(this._auth).sendEmailVerification(actionCodeSettings)); + return getNativeModule(this._auth) + .sendEmailVerification(actionCodeSettings) + .then((user) => { + this._auth._setUser(user); + }); } toJSON(): Object { @@ -169,7 +191,9 @@ export default class User { * @return {Promise.|*} */ unlink(providerId: string): Promise { - return this._auth._interceptUserValue(getNativeModule(this._auth).unlink(providerId)); + return getNativeModule(this._auth) + .unlink(providerId) + .then(user => this._auth._setUser(user)); } /** @@ -179,8 +203,11 @@ export default class User { * @return {Promise} A promise resolved upon completion */ updateEmail(email: string): Promise { - return this._auth - ._interceptUndefinedUserValue(getNativeModule(this._auth).updateEmail(email)); + return getNativeModule(this._auth) + .updateEmail(email) + .then((user) => { + this._auth._setUser(user); + }); } /** @@ -189,8 +216,11 @@ export default class User { * @return {Promise} */ updatePassword(password: string): Promise { - return this._auth - ._interceptUndefinedUserValue(getNativeModule(this._auth).updatePassword(password)); + return getNativeModule(this._auth) + .updatePassword(password) + .then((user) => { + this._auth._setUser(user); + }); } /** @@ -199,18 +229,17 @@ export default class User { * @return {Promise} */ updateProfile(updates: UpdateProfile = {}): Promise { - return this._auth - ._interceptUndefinedUserValue(getNativeModule(this._auth).updateProfile(updates)); + return getNativeModule(this._auth) + .updateProfile(updates) + .then((user) => { + this._auth._setUser(user); + }); } /** * KNOWN UNSUPPORTED METHODS */ - linkAndRetrieveDataWithCredential() { - throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('User', 'linkAndRetrieveDataWithCredential')); - } - linkWithPhoneNumber() { throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_CLASS_METHOD('User', 'linkWithPhoneNumber')); } diff --git a/lib/modules/auth/index.js b/lib/modules/auth/index.js index 3c24e4de..a8d0cde8 100644 --- a/lib/modules/auth/index.js +++ b/lib/modules/auth/index.js @@ -20,13 +20,12 @@ import FacebookAuthProvider from './providers/FacebookAuthProvider'; import PhoneAuthListener from './PhoneAuthListener'; -import type { ActionCodeSettings, AuthCredential } from '../../types'; +import type { ActionCodeSettings, AuthCredential, NativeUser, NativeUserCredential, UserCredential } from './types'; import type App from '../core/firebase-app'; -type AuthResult = { - authenticated: boolean, - user: Object|null -} | null; +type AuthState = { + user?: NativeUser, +}; type ActionCodeInfo = { data: { @@ -45,7 +44,8 @@ export const MODULE_NAME = 'RNFirebaseAuth'; export const NAMESPACE = 'auth'; export default class Auth extends ModuleBase { - _authResult: AuthResult | null; + _authResult: boolean; + _languageCode: string; _user: User | null; constructor(app: App) { @@ -56,7 +56,7 @@ export default class Auth extends ModuleBase { namespace: NAMESPACE, }); this._user = null; - this._authResult = null; + this._authResult = false; this._languageCode = getNativeModule(this).APP_LANGUAGE[app._name] || getNativeModule(this).APP_LANGUAGE['[DEFAULT]']; SharedEventEmitter.addListener( @@ -94,10 +94,22 @@ export default class Auth extends ModuleBase { SharedEventEmitter.emit(eventKey, event.state); } - _setAuthState(auth: AuthResult) { - this._authResult = auth; - this._user = auth && auth.user ? new User(this, auth.user) : null; + _setUser(user: ?NativeUser): ?User { + this._authResult = true; + this._user = user ? new User(this, user) : null; SharedEventEmitter.emit(getAppEventName(this, 'onUserChanged'), this._user); + return this._user; + } + + _setUserCredential(userCredential: NativeUserCredential): UserCredential { + const user = new User(this, userCredential.user); + this._authResult = true; + this._user = user; + SharedEventEmitter.emit(getAppEventName(this, 'onUserChanged'), this._user); + return { + additionalUserInfo: userCredential.additionalUserInfo, + user, + }; } /** @@ -105,8 +117,8 @@ export default class Auth extends ModuleBase { * @param auth * @private */ - _onInternalAuthStateChanged(auth: AuthResult) { - this._setAuthState(auth); + _onInternalAuthStateChanged(auth: AuthState) { + this._setUser(auth.user); SharedEventEmitter.emit(getAppEventName(this, 'onAuthStateChanged'), this._user); } @@ -116,32 +128,11 @@ export default class Auth extends ModuleBase { * @param emit * @private */ - _onInternalIdTokenChanged(auth: AuthResult) { - this._setAuthState(auth); + _onInternalIdTokenChanged(auth: AuthState) { + this._setUser(auth.user); SharedEventEmitter.emit(getAppEventName(this, 'onIdTokenChanged'), this._user); } - /** - * Intercept all user actions and send their results to - * auth state change before resolving - * @param promise - * @returns {Promise.<*>} - * @private - */ - _interceptUserValue(promise: Promise): Promise { - return promise.then((result: AuthResult) => { - if (!result) this._setAuthState(null); - else if (result.user) this._setAuthState(result); - else if (result.uid) this._setAuthState({ authenticated: true, user: result }); - return this._user; - }); - } - - _interceptUndefinedUserValue(promise: Promise): Promise { - return this._interceptUserValue(promise) - .then(() => {}); - } - /* * WEB API */ @@ -211,7 +202,11 @@ export default class Auth extends ModuleBase { * @return {Promise} */ signOut(): Promise { - return this._interceptUndefinedUserValue(getNativeModule(this).signOut()); + return getNativeModule(this) + .signOut() + .then(() => { + this._setUser(); + }); } /** @@ -219,7 +214,19 @@ export default class Auth extends ModuleBase { * @return {Promise} A promise resolved upon completion */ signInAnonymously(): Promise { - return this._interceptUserValue(getNativeModule(this).signInAnonymously()); + return getNativeModule(this) + .signInAnonymously() + .then(user => this._setUser(user)); + } + + /** + * Sign a user in anonymously + * @return {Promise} A promise resolved upon completion + */ + signInAnonymouslyAndRetrieveData(): Promise { + return getNativeModule(this) + .signInAnonymouslyAndRetrieveData() + .then(userCredential => this._setUserCredential(userCredential)); } /** @@ -229,7 +236,21 @@ export default class Auth extends ModuleBase { * @return {Promise} A promise indicating the completion */ createUserWithEmailAndPassword(email: string, password: string): Promise { - return this._interceptUserValue(getNativeModule(this).createUserWithEmailAndPassword(email, password)); + return getNativeModule(this) + .createUserWithEmailAndPassword(email, password) + .then(user => this._setUser(user)); + } + + /** + * Create a user with the email/password functionality + * @param {string} email The user's email + * @param {string} password The user's password + * @return {Promise} A promise indicating the completion + */ + createUserAndRetrieveDataWithEmailAndPassword(email: string, password: string): Promise { + return getNativeModule(this) + .createUserAndRetrieveDataWithEmailAndPassword(email, password) + .then(userCredential => this._setUserCredential(userCredential)); } /** @@ -239,7 +260,21 @@ export default class Auth extends ModuleBase { * @return {Promise} A promise that is resolved upon completion */ signInWithEmailAndPassword(email: string, password: string): Promise { - return this._interceptUserValue(getNativeModule(this).signInWithEmailAndPassword(email, password)); + return getNativeModule(this) + .signInWithEmailAndPassword(email, password) + .then(user => this._setUser(user)); + } + + /** + * Sign a user in with email/password + * @param {string} email The user's email + * @param {string} password The user's password + * @return {Promise} A promise that is resolved upon completion + */ + signInAndRetrieveDataWithEmailAndPassword(email: string, password: string): Promise { + return getNativeModule(this) + .signInAndRetrieveDataWithEmailAndPassword(email, password) + .then(userCredential => this._setUserCredential(userCredential)); } /** @@ -248,7 +283,20 @@ export default class Auth extends ModuleBase { * @return {Promise} A promise resolved upon completion */ signInWithCustomToken(customToken: string): Promise { - return this._interceptUserValue(getNativeModule(this).signInWithCustomToken(customToken)); + return getNativeModule(this) + .signInWithCustomToken(customToken) + .then(user => this._setUser(user)); + } + + /** + * Sign the user in with a custom auth token + * @param {string} customToken A self-signed custom auth token. + * @return {Promise} A promise resolved upon completion + */ + signInAndRetrieveDataWithCustomToken(customToken: string): Promise { + return getNativeModule(this) + .signInAndRetrieveDataWithCustomToken(customToken) + .then(userCredential => this._setUserCredential(userCredential)); } /** @@ -256,11 +304,19 @@ export default class Auth extends ModuleBase { * @return {Promise} A promise resolved upon completion */ signInWithCredential(credential: AuthCredential): Promise { - return this._interceptUserValue( - getNativeModule(this).signInWithCredential( - credential.providerId, credential.token, credential.secret, - ), - ); + return getNativeModule(this) + .signInWithCredential(credential.providerId, credential.token, credential.secret) + .then(user => this._setUser(user)); + } + + /** + * Sign the user in with a third-party authentication provider + * @return {Promise} A promise resolved upon completion + */ + signInAndRetrieveDataWithCredential(credential: AuthCredential): Promise { + return getNativeModule(this) + .signInAndRetrieveDataWithCredential(credential.providerId, credential.token, credential.secret) + .then(userCredential => this._setUserCredential(userCredential)); } /** @@ -268,9 +324,11 @@ export default class Auth extends ModuleBase { * */ signInWithPhoneNumber(phoneNumber: string): Promise { - return getNativeModule(this).signInWithPhoneNumber(phoneNumber).then((result) => { - return new ConfirmationResult(this, result.verificationId); - }); + return getNativeModule(this) + .signInWithPhoneNumber(phoneNumber) + .then((result) => { + return new ConfirmationResult(this, result.verificationId); + }); } /** @@ -370,10 +428,6 @@ export default class Auth extends ModuleBase { throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD('auth', 'setPersistence')); } - signInAndRetrieveDataWithCredential() { - throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD('auth', 'signInAndRetrieveDataWithCredential')); - } - signInWithPopup() { throw new Error(INTERNALS.STRINGS.ERROR_UNSUPPORTED_MODULE_METHOD('auth', 'signInWithPopup')); } diff --git a/lib/modules/auth/providers/EmailAuthProvider.js b/lib/modules/auth/providers/EmailAuthProvider.js index a82e3ffc..0cdfe5ba 100644 --- a/lib/modules/auth/providers/EmailAuthProvider.js +++ b/lib/modules/auth/providers/EmailAuthProvider.js @@ -2,7 +2,7 @@ * @flow * EmailAuthProvider representation wrapper */ -import type { AuthCredential } from '../../../types'; +import type { AuthCredential } from '../types'; const providerId = 'password'; diff --git a/lib/modules/auth/providers/FacebookAuthProvider.js b/lib/modules/auth/providers/FacebookAuthProvider.js index b6e12ad4..fa1dfb35 100644 --- a/lib/modules/auth/providers/FacebookAuthProvider.js +++ b/lib/modules/auth/providers/FacebookAuthProvider.js @@ -2,7 +2,7 @@ * @flow * FacebookAuthProvider representation wrapper */ -import type { AuthCredential } from '../../../types'; +import type { AuthCredential } from '../types'; const providerId = 'facebook.com'; diff --git a/lib/modules/auth/providers/GithubAuthProvider.js b/lib/modules/auth/providers/GithubAuthProvider.js index aa89701f..46a5bef8 100644 --- a/lib/modules/auth/providers/GithubAuthProvider.js +++ b/lib/modules/auth/providers/GithubAuthProvider.js @@ -2,7 +2,7 @@ * @flow * GithubAuthProvider representation wrapper */ -import type { AuthCredential } from '../../../types'; +import type { AuthCredential } from '../types'; const providerId = 'github.com'; diff --git a/lib/modules/auth/providers/GoogleAuthProvider.js b/lib/modules/auth/providers/GoogleAuthProvider.js index a9fde23b..09bc5353 100644 --- a/lib/modules/auth/providers/GoogleAuthProvider.js +++ b/lib/modules/auth/providers/GoogleAuthProvider.js @@ -2,7 +2,7 @@ * @flow * EmailAuthProvider representation wrapper */ -import type { AuthCredential } from '../../../types'; +import type { AuthCredential } from '../types'; const providerId = 'google.com'; diff --git a/lib/modules/auth/providers/PhoneAuthProvider.js b/lib/modules/auth/providers/PhoneAuthProvider.js index fb2ce8cb..e5c25d90 100644 --- a/lib/modules/auth/providers/PhoneAuthProvider.js +++ b/lib/modules/auth/providers/PhoneAuthProvider.js @@ -2,7 +2,7 @@ * @flow * PhoneAuthProvider representation wrapper */ -import type { AuthCredential } from '../../../types'; +import type { AuthCredential } from '../types'; const providerId = 'phone'; diff --git a/lib/modules/auth/providers/TwitterAuthProvider.js b/lib/modules/auth/providers/TwitterAuthProvider.js index 0f2c6dad..8582ddaa 100644 --- a/lib/modules/auth/providers/TwitterAuthProvider.js +++ b/lib/modules/auth/providers/TwitterAuthProvider.js @@ -2,7 +2,7 @@ * @flow * TwitterAuthProvider representation wrapper */ -import type { AuthCredential } from '../../../types'; +import type { AuthCredential } from '../types'; const providerId = 'twitter.com'; diff --git a/lib/modules/auth/types.js b/lib/modules/auth/types.js new file mode 100644 index 00000000..a5fc3d22 --- /dev/null +++ b/lib/modules/auth/types.js @@ -0,0 +1,67 @@ +/** + * @flow + */ +import type User from './User'; + +export type ActionCodeSettings = { + android: { + installApp?: boolean, + minimumVersion?: string, + packageName: string, + }, + handleCodeInApp?: boolean, + iOS: { + bundleId?: string, + }, + url: string, +} + +type AdditionalUserInfo = { + isNewUser: boolean, + profile?: Object, + providerId: string, + username?: string, +} + +export type AuthCredential = { + providerId: string, + token: string, + secret: string +} + +export type UserCredential = {| + additionalUserInfo?: AdditionalUserInfo, + user: User, +|} + +export type UserInfo = { + displayName?: string, + email?: string, + phoneNumber?: string, + photoURL?: string, + providerId: string, + uid: string, +} + +export type UserMetadata = { + creationTime?: string, + lastSignInTime?: string, +} + +export type NativeUser = { + displayName?: string, + email?: string, + emailVerified?: boolean, + isAnonymous?: boolean, + metadata: UserMetadata, + phoneNumber?: string, + photoURL?: string, + providerData: UserInfo[], + providerId: string, + uid: string, +} + +export type NativeUserCredential = {| + additionalUserInfo?: AdditionalUserInfo, + user: NativeUser, +|} diff --git a/lib/types/index.js b/lib/types/index.js index d421ecfd..f410f40d 100644 --- a/lib/types/index.js +++ b/lib/types/index.js @@ -95,35 +95,11 @@ export type ConfigModule = { /* Auth types */ -export type AuthCredential = { - providerId: string, - token: string, - secret: string -} - export type AuthModule = { (): Auth, nativeModuleExists: boolean, } & AuthStatics; -export type ActionCodeSettings = { - android: { - installApp?: boolean, - minimumVersion?: string, - packageName: string, - }, - handleCodeInApp?: boolean, - iOS: { - bundleId?: string, - }, - url: string, -} - -export type UserMetadata = { - creationTime?: string, - lastSignInTime?: string, -} - /* Crash types */ export type CrashModule = { diff --git a/tests/ios/Podfile.lock b/tests/ios/Podfile.lock index 6b21774b..480652fa 100644 --- a/tests/ios/Podfile.lock +++ b/tests/ios/Podfile.lock @@ -1,59 +1,59 @@ PODS: - - BoringSSL (9.1): - - BoringSSL/Implementation (= 9.1) - - BoringSSL/Interface (= 9.1) - - BoringSSL/Implementation (9.1): - - BoringSSL/Interface (= 9.1) - - BoringSSL/Interface (9.1) + - BoringSSL (9.2): + - BoringSSL/Implementation (= 9.2) + - BoringSSL/Interface (= 9.2) + - BoringSSL/Implementation (9.2): + - BoringSSL/Interface (= 9.2) + - BoringSSL/Interface (9.2) - Crashlytics (3.9.3): - Fabric (~> 1.7.2) - Fabric (1.7.2) - - Firebase/AdMob (4.8.0): + - Firebase/AdMob (4.8.1): - Firebase/Core - Google-Mobile-Ads-SDK (= 7.27.0) - - Firebase/Auth (4.8.0): + - Firebase/Auth (4.8.1): - Firebase/Core - - FirebaseAuth (= 4.4.1) - - Firebase/Core (4.8.0): - - FirebaseAnalytics (= 4.0.5) - - FirebaseCore (= 4.0.13) - - Firebase/Crash (4.8.0): + - FirebaseAuth (= 4.4.2) + - Firebase/Core (4.8.1): + - FirebaseAnalytics (= 4.0.7) + - FirebaseCore (= 4.0.14) + - Firebase/Crash (4.8.1): - Firebase/Core - FirebaseCrash (= 2.0.2) - - Firebase/Database (4.8.0): + - Firebase/Database (4.8.1): - Firebase/Core - - FirebaseDatabase (= 4.1.3) - - Firebase/DynamicLinks (4.8.0): + - FirebaseDatabase (= 4.1.4) + - Firebase/DynamicLinks (4.8.1): - Firebase/Core - - FirebaseDynamicLinks (= 2.3.1) - - Firebase/Firestore (4.8.0): + - FirebaseDynamicLinks (= 2.3.2) + - Firebase/Firestore (4.8.1): - Firebase/Core - - FirebaseFirestore (= 0.9.4) - - Firebase/Messaging (4.8.0): + - FirebaseFirestore (= 0.10.0) + - Firebase/Messaging (4.8.1): - Firebase/Core - FirebaseMessaging (= 2.0.8) - - Firebase/Performance (4.8.0): + - Firebase/Performance (4.8.1): - Firebase/Core - - FirebasePerformance (= 1.1.0) - - Firebase/RemoteConfig (4.8.0): + - FirebasePerformance (= 1.1.1) + - Firebase/RemoteConfig (4.8.1): - Firebase/Core - - FirebaseRemoteConfig (= 2.1.0) - - Firebase/Storage (4.8.0): + - FirebaseRemoteConfig (= 2.1.1) + - Firebase/Storage (4.8.1): - Firebase/Core - - FirebaseStorage (= 2.1.1) + - FirebaseStorage (= 2.1.2) - FirebaseABTesting (1.0.0): - FirebaseCore (~> 4.0) - Protobuf (~> 3.1) - - FirebaseAnalytics (4.0.5): + - FirebaseAnalytics (4.0.7): - FirebaseCore (~> 4.0) - FirebaseInstanceID (~> 2.0) - GoogleToolboxForMac/NSData+zlib (~> 2.1) - nanopb (~> 0.3) - - FirebaseAuth (4.4.1): + - FirebaseAuth (4.4.2): - FirebaseAnalytics (~> 4.0) - GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1) - GTMSessionFetcher/Core (~> 1.1) - - FirebaseCore (4.0.13): + - FirebaseCore (4.0.14): - GoogleToolboxForMac/NSData+zlib (~> 2.1) - FirebaseCrash (2.0.2): - FirebaseAnalytics (~> 4.0) @@ -61,13 +61,13 @@ PODS: - GoogleToolboxForMac/Logger (~> 2.1) - GoogleToolboxForMac/NSData+zlib (~> 2.1) - Protobuf (~> 3.1) - - FirebaseDatabase (4.1.3): + - FirebaseDatabase (4.1.4): - FirebaseAnalytics (~> 4.0) - FirebaseCore (~> 4.0) - leveldb-library (~> 1.18) - - FirebaseDynamicLinks (2.3.1): + - FirebaseDynamicLinks (2.3.2): - FirebaseAnalytics (~> 4.0) - - FirebaseFirestore (0.9.4): + - FirebaseFirestore (0.10.0): - FirebaseAnalytics (~> 4.0) - FirebaseCore (~> 4.0) - gRPC-ProtoRPC (~> 1.0) @@ -81,7 +81,7 @@ PODS: - FirebaseInstanceID (~> 2.0) - GoogleToolboxForMac/Logger (~> 2.1) - Protobuf (~> 3.1) - - FirebasePerformance (1.1.0): + - FirebasePerformance (1.1.1): - FirebaseAnalytics (~> 4.0) - FirebaseInstanceID (~> 2.0) - FirebaseSwizzlingUtilities (~> 1.0) @@ -89,13 +89,14 @@ PODS: - GoogleToolboxForMac/NSData+zlib (~> 2.1) - GTMSessionFetcher/Core (~> 1.1) - Protobuf (~> 3.1) - - FirebaseRemoteConfig (2.1.0): + - FirebaseRemoteConfig (2.1.1): - FirebaseABTesting (~> 1.0) - FirebaseAnalytics (~> 4.0) + - FirebaseCore (~> 4.0) - FirebaseInstanceID (~> 2.0) - GoogleToolboxForMac/NSData+zlib (~> 2.1) - Protobuf (~> 3.1) - - FirebaseStorage (2.1.1): + - FirebaseStorage (2.1.2): - FirebaseAnalytics (~> 4.0) - FirebaseCore (~> 4.0) - GTMSessionFetcher/Core (~> 1.1) @@ -113,25 +114,25 @@ PODS: - GoogleToolboxForMac/Defines (= 2.1.3) - GoogleToolboxForMac/NSString+URLArguments (= 2.1.3) - GoogleToolboxForMac/NSString+URLArguments (2.1.3) - - gRPC (1.8.0): - - gRPC-RxLibrary (= 1.8.0) - - gRPC/Main (= 1.8.0) - - gRPC-Core (1.8.0): - - gRPC-Core/Implementation (= 1.8.0) - - gRPC-Core/Interface (= 1.8.0) - - gRPC-Core/Implementation (1.8.0): + - gRPC (1.8.4): + - gRPC-RxLibrary (= 1.8.4) + - gRPC/Main (= 1.8.4) + - gRPC-Core (1.8.4): + - gRPC-Core/Implementation (= 1.8.4) + - gRPC-Core/Interface (= 1.8.4) + - gRPC-Core/Implementation (1.8.4): - BoringSSL (~> 9.0) - - gRPC-Core/Interface (= 1.8.0) + - gRPC-Core/Interface (= 1.8.4) - nanopb (~> 0.3) - - gRPC-Core/Interface (1.8.0) - - gRPC-ProtoRPC (1.8.0): - - gRPC (= 1.8.0) - - gRPC-RxLibrary (= 1.8.0) + - gRPC-Core/Interface (1.8.4) + - gRPC-ProtoRPC (1.8.4): + - gRPC (= 1.8.4) + - gRPC-RxLibrary (= 1.8.4) - Protobuf (~> 3.0) - - gRPC-RxLibrary (1.8.0) - - gRPC/Main (1.8.0): - - gRPC-Core (= 1.8.0) - - gRPC-RxLibrary (= 1.8.0) + - gRPC-RxLibrary (1.8.4) + - gRPC/Main (1.8.4): + - gRPC-Core (= 1.8.4) + - gRPC-RxLibrary (= 1.8.4) - GTMSessionFetcher/Core (1.1.12) - leveldb-library (1.20) - nanopb (0.3.8): @@ -198,30 +199,30 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - BoringSSL: 84318770d120503ab1a6aaf1df777c5ca053697e + BoringSSL: f3d6b8ce199b9c450a8cfc14895d07a2627fc232 Crashlytics: dbb07d01876c171c5ccbdf7826410380189e452c Fabric: 9cd6a848efcf1b8b07497e0b6a2e7d336353ba15 - Firebase: 710decbbc6d9d48530e9a5dba3209740c3532e05 + Firebase: 2721056b8885eef90233b03f37be64358d35d262 FirebaseABTesting: d07d0ee833b842d5153549e4c7e2e2cb1c23a3f9 - FirebaseAnalytics: 5b02a63ead2c3f0259cfc7f15e053e440587ecf8 - FirebaseAuth: dc0dd403beca5b2b016aac89c2d0b8dad1c81926 - FirebaseCore: 3c02ec652db3d03fdc8bc6d9154af3e20d64b6f5 + FirebaseAnalytics: 617afa8c26b57a0c3f11361b248bc9e17bfd8dfd + FirebaseAuth: bd2738c5c1e92b108ba5f7f7335908097a4e50bb + FirebaseCore: 2e0b98fb2d64ca8140136beff15772bdd14d2dd7 FirebaseCrash: cded0fc566c03651aea606a101bc156085f333ca - FirebaseDatabase: 7088bfc4af2cc00231bb36e1404fc2d7509eb4dc - FirebaseDynamicLinks: b708fbc1e9bd77c2d992812736b206820e283203 - FirebaseFirestore: 0fb0301657759bdd7a4bc37a436e543a86b189ad + FirebaseDatabase: de4446507ccd3257fca37d16f40e1540324571fd + FirebaseDynamicLinks: 38b68641d24e78d0277a9205d988ce22875d5a25 + FirebaseFirestore: 713f0c555e7af5ac03d0fec0e2477c48857f4977 FirebaseInstanceID: 81df5805a08001e69138664bdd02c6719a9ac80f FirebaseMessaging: dfdcd307c2382290a1e297a81d0f18370f5b1bcd - FirebasePerformance: 3877d097c59956aa2d7a317dbae503c2b4e78459 - FirebaseRemoteConfig: 451fe8e9c43ac1e7a137ad2a42189bfc8c2c3ebc - FirebaseStorage: ab08d1c93a2feffa038fdd6693088b310ab1e6c6 + FirebasePerformance: 4e1f8091e400eaf88505234caef5718313653709 + FirebaseRemoteConfig: 3310f264fff78b6c2e78b24dcfc4c1b3d6766209 + FirebaseStorage: 181bb543d39ee3c53e0558de7ba86b1286a0427f FirebaseSwizzlingUtilities: f1c49a5a372ac852c853722a5891a0a5e2344a6c Google-Mobile-Ads-SDK: 83f7f890e638ce8f1debd440ea363338c9f6be3b GoogleToolboxForMac: 2501e2ad72a52eb3dfe7bd9aee7dad11b858bd20 - gRPC: cc3f797b97bf221a9e535e8e012a6f99b1d7f797 - gRPC-Core: 250fd60016c4e4c413279f4002679b0e14e7df26 - gRPC-ProtoRPC: 5b934b29a95b94b0f871d4fc361ff52269476765 - gRPC-RxLibrary: 7c57a1b2ff2f9cafb0face35bbfa0a326281295d + gRPC: 572520c17b794362388d5c95396329592a3c199b + gRPC-Core: af0d4f0a53735e335fccc815c50c0a03da695287 + gRPC-ProtoRPC: 6596fde8d27e0718d7de1de1dc99a951d832a809 + gRPC-RxLibrary: f6b1432a667c3354c7b345affed9886c0d4ff549 GTMSessionFetcher: ebaa1f79a5366922c1735f1566901f50beba23b7 leveldb-library: '08cba283675b7ed2d99629a4bc5fd052cd2bb6a5' nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3 diff --git a/tests/src/tests/auth/authTests.js b/tests/src/tests/auth/authTests.js index 5848db26..7d64b3b4 100644 --- a/tests/src/tests/auth/authTests.js +++ b/tests/src/tests/auth/authTests.js @@ -12,7 +12,7 @@ function randomString(length, chars) { } function authTests({ tryCatch, describe, it, firebase }) { - describe('Anonymous', () => { + describe('signInAnonymously', () => { it('it should sign in anonymously', () => { const successCb = (currentUser) => { currentUser.should.be.an.Object(); @@ -22,7 +22,7 @@ function authTests({ tryCatch, describe, it, firebase }) { currentUser.isAnonymous.should.equal(true); currentUser.providerId.should.equal('firebase'); - firebase.native.auth().currentUser.uid.should.be.a.String(); + currentUser.should.equal(firebase.native.auth().currentUser); return firebase.native.auth().signOut(); }; @@ -31,7 +31,29 @@ function authTests({ tryCatch, describe, it, firebase }) { }); }); - describe('Link', () => { + describe('signInAnonymouslyAndRetrieveData', () => { + it('it should sign in anonymously', () => { + const successCb = (currentUserCredential) => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.native.auth().currentUser); + + const additionalUserInfo = currentUserCredential.additionalUserInfo; + additionalUserInfo.should.be.an.Object(); + + return firebase.native.auth().signOut(); + }; + + return firebase.native.auth().signInAnonymouslyAndRetrieveData().then(successCb); + }); + }); + + describe('linkWithCredential', () => { it('it should link anonymous account <-> email account', () => { const random = randomString(12, '#aA'); const email = `${random}@${random}.com`; @@ -52,6 +74,7 @@ function authTests({ tryCatch, describe, it, firebase }) { .linkWithCredential(credential) .then((linkedUser) => { linkedUser.should.be.an.Object(); + linkedUser.should.equal(firebase.native.auth().currentUser); linkedUser.uid.should.be.a.String(); linkedUser.toJSON().should.be.an.Object(); // iOS and Android are inconsistent in returning lowercase / mixed case @@ -103,7 +126,86 @@ function authTests({ tryCatch, describe, it, firebase }) { }); }); - describe('Email - Login', () => { + describe('linkAndRetrieveDataWithCredential', () => { + it('it should link anonymous account <-> email account', () => { + const random = randomString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const successCb = (currentUser) => { + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + firebase.native.auth().currentUser.uid.should.be.a.String(); + + const credential = firebase.native.auth.EmailAuthProvider.credential(email, pass); + + return currentUser + .linkAndRetrieveDataWithCredential(credential) + .then((linkedUserCredential) => { + linkedUserCredential.should.be.an.Object(); + const linkedUser = linkedUserCredential.user; + linkedUser.should.be.an.Object(); + linkedUser.should.equal(firebase.native.auth().currentUser); + linkedUser.uid.should.be.a.String(); + linkedUser.toJSON().should.be.an.Object(); + // iOS and Android are inconsistent in returning lowercase / mixed case + linkedUser.toJSON().email.toLowerCase().should.eql(email.toLowerCase()); + linkedUser.isAnonymous.should.equal(false); + linkedUser.providerId.should.equal('firebase'); + const additionalUserInfo = linkedUserCredential.additionalUserInfo; + // TODO: iOS is incorrect, passes on Android + // additionalUserInfo.should.be.an.Object(); + // additionalUserInfo.isNewUser.should.equal(false); + return firebase.native.auth().signOut(); + }).catch((error) => { + return firebase.native.auth().signOut().then(() => { + return Promise.reject(error); + }); + }); + }; + + return firebase.native.auth().signInAnonymously().then(successCb); + }); + + it('it should error on link anon <-> email if email already exists', () => { + const email = 'test@test.com'; + const pass = 'test1234'; + + const successCb = (currentUser) => { + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + firebase.native.auth().currentUser.uid.should.be.a.String(); + + const credential = firebase.native.auth.EmailAuthProvider.credential(email, pass); + + return currentUser + .linkAndRetrieveDataWithCredential(credential) + .then(() => { + return firebase.native.auth().signOut().then(() => { + return Promise.reject(new Error('Did not error on link')); + }); + }).catch((error) => { + return firebase.native.auth().signOut().then(() => { + error.code.should.equal('auth/email-already-in-use'); + error.message.should.equal('The email address is already in use by another account.'); + return Promise.resolve(); + }); + }); + }; + + return firebase.native.auth().signInAnonymously().then(successCb); + }); + }); + + describe('signInWithEmailAndPassword', () => { it('it should login with email and password', () => { const email = 'test@test.com'; const pass = 'test1234'; @@ -115,8 +217,7 @@ function authTests({ tryCatch, describe, it, firebase }) { currentUser.toJSON().email.should.eql('test@test.com'); currentUser.isAnonymous.should.equal(false); currentUser.providerId.should.equal('firebase'); - - firebase.native.auth().currentUser.uid.should.be.a.String(); + currentUser.should.equal(firebase.native.auth().currentUser); return firebase.native.auth().signOut(); }; @@ -176,7 +277,225 @@ function authTests({ tryCatch, describe, it, firebase }) { }); }); - describe('Email - Create', () => { + describe('signInAndRetrieveDataWithEmailAndPassword', () => { + it('it should login with email and password', () => { + const email = 'test@test.com'; + const pass = 'test1234'; + + const successCb = (currentUserCredential) => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql('test@test.com'); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.native.auth().currentUser); + + const additionalUserInfo = currentUserCredential.additionalUserInfo; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(false); + + return firebase.native.auth().signOut(); + }; + + return firebase.native.auth().signInAndRetrieveDataWithEmailAndPassword(email, pass).then(successCb); + }); + + it('it should error on login if user is disabled', () => { + const email = 'disabled@account.com'; + const pass = 'test1234'; + + const successCb = () => { + return Promise.reject(new Error('Did not error.')); + }; + + const failureCb = (error) => { + error.code.should.equal('auth/user-disabled'); + error.message.should.equal('The user account has been disabled by an administrator.'); + return Promise.resolve(); + }; + + return firebase.native.auth().signInAndRetrieveDataWithEmailAndPassword(email, pass).then(successCb).catch(failureCb); + }); + + it('it should error on login if password incorrect', () => { + const email = 'test@test.com'; + const pass = 'test1234666'; + + const successCb = () => { + return Promise.reject(new Error('Did not error.')); + }; + + const failureCb = (error) => { + error.code.should.equal('auth/wrong-password'); + error.message.should.equal('The password is invalid or the user does not have a password.'); + return Promise.resolve(); + }; + + return firebase.native.auth().signInAndRetrieveDataWithEmailAndPassword(email, pass).then(successCb).catch(failureCb); + }); + + it('it should error on login if user not found', () => { + const email = 'randomSomeone@fourOhFour.com'; + const pass = 'test1234'; + + const successCb = () => { + return Promise.reject(new Error('Did not error.')); + }; + + const failureCb = (error) => { + error.code.should.equal('auth/user-not-found'); + error.message.should.equal('There is no user record corresponding to this identifier. The user may have been deleted.'); + return Promise.resolve(); + }; + + return firebase.native.auth().signInAndRetrieveDataWithEmailAndPassword(email, pass).then(successCb).catch(failureCb); + }); + }); + + describe('signInWithCredential', () => { + it('it should login with email and password', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential('test@test.com', 'test1234'); + + const successCb = (currentUser) => { + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql('test@test.com'); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.native.auth().currentUser); + + return firebase.native.auth().signOut(); + }; + + return firebase.native.auth().signInWithCredential(credential).then(successCb); + }); + + it('it should error on login if user is disabled', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential('disabled@account.com', 'test1234'); + + const successCb = () => { + return Promise.reject(new Error('Did not error.')); + }; + + const failureCb = (error) => { + error.code.should.equal('auth/user-disabled'); + error.message.should.equal('The user account has been disabled by an administrator.'); + return Promise.resolve(); + }; + + return firebase.native.auth().signInWithCredential(credential).then(successCb).catch(failureCb); + }); + + it('it should error on login if password incorrect', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential('test@test.com', 'test1234666'); + + const successCb = () => { + return Promise.reject(new Error('Did not error.')); + }; + + const failureCb = (error) => { + error.code.should.equal('auth/wrong-password'); + error.message.should.equal('The password is invalid or the user does not have a password.'); + return Promise.resolve(); + }; + + return firebase.native.auth().signInWithCredential(credential).then(successCb).catch(failureCb); + }); + + it('it should error on login if user not found', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential('randomSomeone@fourOhFour.com', 'test1234'); + + const successCb = () => { + return Promise.reject(new Error('Did not error.')); + }; + + const failureCb = (error) => { + error.code.should.equal('auth/user-not-found'); + error.message.should.equal('There is no user record corresponding to this identifier. The user may have been deleted.'); + return Promise.resolve(); + }; + + return firebase.native.auth().signInWithCredential(credential).then(successCb).catch(failureCb); + }); + }); + + describe('signInAndRetrieveDataWithCredential', () => { + it('it should login with email and password', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential('test@test.com', 'test1234'); + + const successCb = (currentUserCredential) => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql('test@test.com'); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.native.auth().currentUser); + + const additionalUserInfo = currentUserCredential.additionalUserInfo; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(false); + + return firebase.native.auth().signOut(); + }; + + return firebase.native.auth().signInAndRetrieveDataWithCredential(credential).then(successCb); + }); + + it('it should error on login if user is disabled', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential('disabled@account.com', 'test1234'); + + const successCb = () => { + return Promise.reject(new Error('Did not error.')); + }; + + const failureCb = (error) => { + error.code.should.equal('auth/user-disabled'); + error.message.should.equal('The user account has been disabled by an administrator.'); + return Promise.resolve(); + }; + + return firebase.native.auth().signInAndRetrieveDataWithCredential(credential).then(successCb).catch(failureCb); + }); + + it('it should error on login if password incorrect', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential('test@test.com', 'test1234666'); + + const successCb = () => { + return Promise.reject(new Error('Did not error.')); + }; + + const failureCb = (error) => { + error.code.should.equal('auth/wrong-password'); + error.message.should.equal('The password is invalid or the user does not have a password.'); + return Promise.resolve(); + }; + + return firebase.native.auth().signInAndRetrieveDataWithCredential(credential).then(successCb).catch(failureCb); + }); + + it('it should error on login if user not found', () => { + const credential = firebase.native.auth.EmailAuthProvider.credential('randomSomeone@fourOhFour.com', 'test1234'); + + const successCb = () => { + return Promise.reject(new Error('Did not error.')); + }; + + const failureCb = (error) => { + error.code.should.equal('auth/user-not-found'); + error.message.should.equal('There is no user record corresponding to this identifier. The user may have been deleted.'); + return Promise.resolve(); + }; + + return firebase.native.auth().signInAndRetrieveDataWithCredential(credential).then(successCb).catch(failureCb); + }); + }); + + describe('createUserWithEmailAndPassword', () => { it('it should create a user with an email and password', () => { const random = randomString(12, '#aA'); const email = `${random}@${random}.com`; @@ -188,6 +507,7 @@ function authTests({ tryCatch, describe, it, firebase }) { newUser.emailVerified.should.equal(false); newUser.isAnonymous.should.equal(false); newUser.providerId.should.equal('firebase'); + newUser.should.equal(firebase.native.auth().currentUser); }; return firebase.native.auth().createUserWithEmailAndPassword(email, pass).then(successCb); @@ -247,7 +567,83 @@ function authTests({ tryCatch, describe, it, firebase }) { }); }); - describe('Email - Providers', () => { + describe('createUserAndRetrieveDataWithEmailAndPassword', () => { + it('it should create a user with an email and password', () => { + const random = randomString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const successCb = (newUserCredential) => { + const newUser = newUserCredential.user; + newUser.uid.should.be.a.String(); + newUser.email.should.equal(email.toLowerCase()); + newUser.emailVerified.should.equal(false); + newUser.isAnonymous.should.equal(false); + newUser.providerId.should.equal('firebase'); + newUser.should.equal(firebase.native.auth().currentUser); + const additionalUserInfo = newUserCredential.additionalUserInfo; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(true); + }; + + return firebase.native.auth().createUserAndRetrieveDataWithEmailAndPassword(email, pass).then(successCb); + }); + + it('it should error on create with invalid email', () => { + const random = randomString(12, '#aA'); + const email = `${random}${random}.com.boop.shoop`; + const pass = random; + + const successCb = () => { + return Promise.reject(new Error('Did not error.')); + }; + + const failureCb = (error) => { + error.code.should.equal('auth/invalid-email'); + error.message.should.equal('The email address is badly formatted.'); + return Promise.resolve(); + }; + + return firebase.native.auth().createUserAndRetrieveDataWithEmailAndPassword(email, pass).then(successCb).catch(failureCb); + }); + + it('it should error on create if email in use', () => { + const email = 'test@test.com'; + const pass = 'test123456789'; + + const successCb = () => { + return Promise.reject(new Error('Did not error.')); + }; + + const failureCb = (error) => { + error.code.should.equal('auth/email-already-in-use'); + error.message.should.equal('The email address is already in use by another account.'); + return Promise.resolve(); + }; + + return firebase.native.auth().createUserAndRetrieveDataWithEmailAndPassword(email, pass).then(successCb).catch(failureCb); + }); + + it('it should error on create if password weak', () => { + const email = 'testy@testy.com'; + const pass = '123'; + + const successCb = () => { + return Promise.reject(new Error('Did not error.')); + }; + + const failureCb = (error) => { + error.code.should.equal('auth/weak-password'); + // cannot test this message - it's different on the web client than ios/android return + // error.message.should.equal('The given password is invalid.'); + return Promise.resolve(); + }; + + return firebase.native.auth().createUserAndRetrieveDataWithEmailAndPassword(email, pass).then(successCb).catch(failureCb); + }); + }); + + describe('fetchProvidersForEmail', () => { it('it should return password provider for an email address', () => { return new Promise((resolve, reject) => { const successCb = tryCatch((providers) => {