Skip to content

Commit 5acc802

Browse files
committed
feat(oauth): added signIn and link for oauth
1 parent 90a2589 commit 5acc802

File tree

7 files changed

+199
-34
lines changed

7 files changed

+199
-34
lines changed

packages/auth/android/src/main/java/io/invertase/firebase/auth/ReactNativeFirebaseAuthModule.java

+112-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
import com.facebook.react.bridge.WritableArray;
3131
import com.facebook.react.bridge.WritableMap;
3232
import com.google.android.gms.tasks.OnCompleteListener;
33+
import com.google.android.gms.tasks.OnFailureListener;
34+
import com.google.android.gms.tasks.OnSuccessListener;
35+
import com.google.android.gms.tasks.Task;
3336
import com.google.firebase.FirebaseApp;
3437
import com.google.firebase.FirebaseException;
3538
import com.google.firebase.FirebaseNetworkException;
@@ -56,6 +59,7 @@
5659
import com.google.firebase.auth.MultiFactorResolver;
5760
import com.google.firebase.auth.MultiFactorSession;
5861
import com.google.firebase.auth.OAuthProvider;
62+
import com.google.firebase.auth.OAuthCredential;
5963
import com.google.firebase.auth.PhoneAuthCredential;
6064
import com.google.firebase.auth.PhoneAuthOptions;
6165
import com.google.firebase.auth.PhoneAuthProvider;
@@ -203,7 +207,6 @@ public void addIdTokenListener(final String appName) {
203207

204208
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
205209
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
206-
207210
if (!mIdTokenListeners.containsKey(appName)) {
208211
FirebaseAuth.IdTokenListener newIdTokenListener =
209212
firebaseAuth1 -> {
@@ -839,6 +842,87 @@ private void signInWithCredential(
839842
}
840843
}
841844

845+
@ReactMethod
846+
public void signInWithProvider(String appName, String providerId, @Nullable String email, Promise promise){
847+
OAuthProvider.Builder provider = OAuthProvider.newBuilder(providerId);
848+
if(email != null){
849+
provider.addCustomParameter("login_hint", email);
850+
}
851+
Activity activity = getCurrentActivity();
852+
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
853+
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
854+
855+
OnSuccessListener onSuccess = new OnSuccessListener<AuthResult>(){
856+
@Override
857+
public void onSuccess(AuthResult authResult) {
858+
Log.d(TAG, "signInWithProvider:onComplete:success");
859+
promiseWithAuthResult(authResult, promise);
860+
}
861+
};
862+
863+
OnFailureListener onFailure = new OnFailureListener(){
864+
@Override
865+
public void onFailure(@NonNull Exception e) {
866+
Log.w(TAG, "signInWithProvider:onComplete:failure", e);
867+
promiseRejectAuthException(promise, e);
868+
}
869+
};
870+
871+
872+
Task<AuthResult> pendingResultTask = firebaseAuth.getPendingAuthResult();
873+
if(pendingResultTask != null){
874+
pendingResultTask
875+
.addOnSuccessListener(onSuccess)
876+
.addOnFailureListener(onFailure);
877+
} else {
878+
firebaseAuth
879+
.startActivityForSignInWithProvider(activity, provider.build())
880+
.addOnSuccessListener(onSuccess)
881+
.addOnFailureListener(onFailure);
882+
}
883+
}
884+
885+
@ReactMethod
886+
public void linkWithProvider(String appName, String providerId, @Nullable String email, Promise promise){
887+
OAuthProvider.Builder provider = OAuthProvider.newBuilder(providerId);
888+
if(email != null){
889+
provider.addCustomParameter("login_hint", email);
890+
}
891+
Activity activity = getCurrentActivity();
892+
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
893+
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
894+
FirebaseUser firebaseUser = firebaseAuth.getCurrentUser();
895+
896+
OnSuccessListener onSuccess = new OnSuccessListener<AuthResult>(){
897+
@Override
898+
public void onSuccess(AuthResult authResult) {
899+
Log.d(TAG, "linkWithProvider:onComplete:success");
900+
promiseWithAuthResult(authResult, promise);
901+
}
902+
};
903+
904+
OnFailureListener onFailure = new OnFailureListener(){
905+
@Override
906+
public void onFailure(@NonNull Exception e) {
907+
Log.w(TAG, "linkInWithProvider:onComplete:failure", e);
908+
promiseRejectAuthException(promise, e);
909+
}
910+
};
911+
912+
913+
Task<AuthResult> pendingResultTask = firebaseAuth.getPendingAuthResult();
914+
if(pendingResultTask != null){
915+
pendingResultTask
916+
.addOnSuccessListener(onSuccess)
917+
.addOnFailureListener(onFailure);
918+
} else {
919+
firebaseUser
920+
.startActivityForLinkWithProvider(activity, provider.build())
921+
.addOnSuccessListener(onSuccess)
922+
.addOnFailureListener(onFailure);
923+
}
924+
}
925+
842926
/**
843927
* signInWithPhoneNumber
844928
*
@@ -1866,6 +1950,30 @@ private void promiseWithAuthResult(AuthResult authResult, Promise promise) {
18661950
WritableMap authResultMap = Arguments.createMap();
18671951
WritableMap userMap = firebaseUserToMap(authResult.getUser());
18681952

1953+
if(authResult.getCredential() != null){
1954+
if(authResult.getCredential() instanceof OAuthCredential){
1955+
OAuthCredential creds = (OAuthCredential) authResult.getCredential();
1956+
WritableMap credentialMap = Arguments.createMap();
1957+
1958+
credentialMap.putString("providerId", creds.getProvider());
1959+
credentialMap.putString("signInMethod", creds.getSignInMethod());
1960+
1961+
if(creds.getIdToken() != null){
1962+
credentialMap.putString("idToken", creds.getIdToken());
1963+
}
1964+
1965+
if(creds.getAccessToken() != null){
1966+
credentialMap.putString("accessToken", creds.getAccessToken());
1967+
}
1968+
1969+
if(creds.getSecret() != null){
1970+
credentialMap.putString("secret", creds.getSecret());
1971+
}
1972+
1973+
authResultMap.putMap("credential", credentialMap);
1974+
}
1975+
}
1976+
18691977
if (authResult.getAdditionalUserInfo() != null) {
18701978
WritableMap additionalUserInfoMap = Arguments.createMap();
18711979

@@ -1906,6 +2014,7 @@ private void promiseWithAuthResult(AuthResult authResult, Promise promise) {
19062014
*/
19072015
private void promiseRejectAuthException(Promise promise, Exception exception) {
19082016
WritableMap error = getJSError(exception);
2017+
19092018
final String sessionId = error.getString("sessionId");
19102019
final MultiFactorResolver multiFactorResolver = mCachedResolvers.get(sessionId);
19112020
WritableMap resolverAsMap = Arguments.createMap();
@@ -1927,6 +2036,8 @@ private WritableMap getJSError(Exception exception) {
19272036
String message = exception.getMessage();
19282037
String invalidEmail = "The email address is badly formatted.";
19292038

2039+
System.out.print(exception);
2040+
19302041
try {
19312042
FirebaseAuthException authException = (FirebaseAuthException) exception;
19322043
code = authException.getErrorCode();

packages/auth/e2e/auth.e2e.js

+10-12
Original file line numberDiff line numberDiff line change
@@ -912,12 +912,11 @@ describe('auth()', function () {
912912
});
913913

914914
describe('signInWithPopup', function () {
915-
it('should throw an unsupported error', function () {
916-
(() => {
917-
firebase.auth().signInWithPopup();
918-
}).should.throw(
919-
'firebase.auth().signInWithPopup() is unsupported by the native Firebase SDKs.',
920-
);
915+
it('should trigger the oauth flow', async function () {
916+
await (async () => {
917+
const provider = new firebase.auth.OAuthProvider('oidc.react.com');
918+
await firebase.auth().signInWithPopup(provider);
919+
}).should.not.throw();
921920
});
922921
});
923922

@@ -1025,12 +1024,11 @@ describe('auth()', function () {
10251024
});
10261025

10271026
describe('signInWithRedirect()', function () {
1028-
it('should throw an unsupported error', function () {
1029-
(() => {
1030-
firebase.auth().signInWithRedirect();
1031-
}).should.throw(
1032-
'firebase.auth().signInWithRedirect() is unsupported by the native Firebase SDKs.',
1033-
);
1027+
it('should trigger the oauth flow', async function () {
1028+
await (async () => {
1029+
const provider = new firebase.auth.OAuthProvider('oidc.react.com');
1030+
await firebase.auth().signInWithRedirect(provider);
1031+
}).should.not.throw();
10341032
});
10351033
});
10361034

packages/auth/e2e/provider.e2e.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,7 @@ describe('auth() -> Providers', function () {
149149
describe('OAuthProvider', function () {
150150
describe('constructor', function () {
151151
it('should throw an unsupported error', function () {
152-
(() => new firebase.auth.OAuthProvider()).should.throw(
153-
'`new OAuthProvider()` is not supported on the native Firebase SDKs.',
154-
);
152+
(() => new firebase.auth.OAuthProvider('oidc.react.com')).should.not.throw();
155153
});
156154
});
157155

packages/auth/lib/User.js

+6-8
Original file line numberDiff line numberDiff line change
@@ -310,16 +310,14 @@ export default class User {
310310
);
311311
}
312312

313-
linkWithPopup() {
314-
throw new Error(
315-
'firebase.auth.User.linkWithPopup() is unsupported by the native Firebase SDKs.',
316-
);
313+
linkWithPopup(provider) {
314+
return this._auth.native
315+
.linkWithProvider(provider.providerId, provider.customParameters?.login_hint)
316+
.then(userCredential => this._auth._setUserCredential(userCredential));
317317
}
318318

319-
linkWithRedirect() {
320-
throw new Error(
321-
'firebase.auth.User.linkWithRedirect() is unsupported by the native Firebase SDKs.',
322-
);
319+
async linkWithRedirect(provider) {
320+
return this.linkWithPopup(provider);
323321
}
324322

325323
reauthenticateWithPhoneNumber() {

packages/auth/lib/index.d.ts

+55-1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ export namespace FirebaseAuthTypes {
109109
credential: (token: string | null, secret?: string) => AuthCredential;
110110
}
111111

112+
export type OAuthProvider = AuthProvider & {
113+
new (providerId: string): OAuthProvider;
114+
setCustomParameters: (customOAuthParameters: object) => void;
115+
};
116+
112117
/**
113118
* Interface that represents an Open ID Connect auth provider. Implemented by other providers.
114119
*/
@@ -315,7 +320,7 @@ export namespace FirebaseAuthTypes {
315320
* firebase.auth.OAuthProvider;
316321
* ```
317322
*/
318-
OAuthProvider: AuthProvider;
323+
OAuthProvider: OAuthProvider;
319324
/**
320325
* Custom Open ID connect auth provider implementation.
321326
*
@@ -387,6 +392,12 @@ export namespace FirebaseAuthTypes {
387392
* Any additional user information assigned to the user.
388393
*/
389394
additionalUserInfo?: AdditionalUserInfo;
395+
396+
/**
397+
* The AuthCredential returned from the identity provider.
398+
*/
399+
credential: AuthCredential | null;
400+
390401
/**
391402
* Returns the {@link auth.User} interface of this credential.
392403
*/
@@ -1198,6 +1209,46 @@ export namespace FirebaseAuthTypes {
11981209
*/
11991210
linkWithCredential(credential: AuthCredential): Promise<UserCredential>;
12001211

1212+
/**
1213+
* Link the user with a 3rd party provider.
1214+
*
1215+
* #### Example
1216+
*
1217+
* ```js
1218+
* const oauthProvider = new firebase.auth.OAuthProvider('oidc.react.com')
1219+
* const authCredentials = await firebase.auth().currentUser.linkWithPopup(oauthProvider);
1220+
* ```
1221+
*
1222+
* @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.
1223+
* @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.
1224+
* @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.
1225+
* @error auth/email-already-in-use Thrown if the email corresponding to the credential already exists among your users.
1226+
* @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.
1227+
* @throws on iOS {@link auth.NativeFirebaseAuthError}, on Android {@link auth.NativeFirebaseError}
1228+
* @param provider A created {@link auth.AuthProvider}.
1229+
*/
1230+
linkWithPopup(provider: AuthProvider): Promise<UserCredential>;
1231+
1232+
/**
1233+
* Link the user with a 3rd party provider.
1234+
*
1235+
* #### Example
1236+
*
1237+
* ```js
1238+
* const oauthProvider = new firebase.auth.OAuthProvider('oidc.react.com')
1239+
* const authCredentials = await firebase.auth().currentUser.linkWithPopup(oauthProvider);
1240+
* ```
1241+
*
1242+
* @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.
1243+
* @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.
1244+
* @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.
1245+
* @error auth/email-already-in-use Thrown if the email corresponding to the credential already exists among your users.
1246+
* @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.
1247+
* @throws on iOS {@link auth.NativeFirebaseAuthError}, on Android {@link auth.NativeFirebaseError}
1248+
* @param provider A created {@link auth.AuthProvider}.
1249+
*/
1250+
linkWithRedirect(provider: Provider): Promise<UserCredential>;
1251+
12011252
/**
12021253
* Re-authenticate a user with a third-party authentication provider.
12031254
*
@@ -1708,6 +1759,9 @@ export namespace FirebaseAuthTypes {
17081759
*/
17091760
signInWithCredential(credential: AuthCredential): Promise<UserCredential>;
17101761

1762+
signInWithPopup(provider: AuthProvider): Promise<UserCredential>;
1763+
signInWithRedirect(provider: AuthProvider): Promise<UserCredential>;
1764+
17111765
/**
17121766
* Sends a password reset email to the given email address.
17131767
* Unlike the web SDK, the email will contain a password reset link rather than a code.

packages/auth/lib/index.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -360,16 +360,16 @@ class FirebaseAuthModule extends FirebaseModule {
360360
throw new Error('firebase.auth().setPersistence() is unsupported by the native Firebase SDKs.');
361361
}
362362

363-
signInWithPopup() {
364-
throw new Error(
365-
'firebase.auth().signInWithPopup() is unsupported by the native Firebase SDKs.',
366-
);
363+
signInWithPopup(provider) {
364+
return this.native
365+
.signInWithProvider(provider.providerId, provider.customParameters?.login_hint)
366+
.then(userCredential => this._setUserCredential(userCredential));
367367
}
368368

369369
signInWithRedirect() {
370-
throw new Error(
371-
'firebase.auth().signInWithRedirect() is unsupported by the native Firebase SDKs.',
372-
);
370+
return this.native
371+
.signInWithProvider(provider.providerId, provider.customParameters?.login_hint)
372+
.then(userCredential => this._setUserCredential(userCredential));
373373
}
374374

375375
// firebase issue - https://github.com/invertase/react-native-firebase/pull/655#issuecomment-349904680

packages/auth/lib/providers/OAuthProvider.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,14 @@
1818
const providerId = 'oauth';
1919

2020
export default class OAuthProvider {
21-
constructor() {
22-
throw new Error('`new OAuthProvider()` is not supported on the native Firebase SDKs.');
21+
constructor(providerId) {
22+
this.providerId = providerId;
23+
}
24+
25+
customParameters = {};
26+
27+
setCustomParameters(customParameters) {
28+
this.customParameters = customParameters;
2329
}
2430

2531
static get PROVIDER_ID() {

0 commit comments

Comments
 (0)