Skip to content

Commit

Permalink
feat(oauth): added signIn and link for oauth
Browse files Browse the repository at this point in the history
  • Loading branch information
Aetherall committed Apr 7, 2023
1 parent 90a2589 commit 5acc802
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseException;
import com.google.firebase.FirebaseNetworkException;
Expand All @@ -56,6 +59,7 @@
import com.google.firebase.auth.MultiFactorResolver;
import com.google.firebase.auth.MultiFactorSession;
import com.google.firebase.auth.OAuthProvider;
import com.google.firebase.auth.OAuthCredential;
import com.google.firebase.auth.PhoneAuthCredential;
import com.google.firebase.auth.PhoneAuthOptions;
import com.google.firebase.auth.PhoneAuthProvider;
Expand Down Expand Up @@ -203,7 +207,6 @@ public void addIdTokenListener(final String appName) {

FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);

if (!mIdTokenListeners.containsKey(appName)) {
FirebaseAuth.IdTokenListener newIdTokenListener =
firebaseAuth1 -> {
Expand Down Expand Up @@ -839,6 +842,87 @@ private void signInWithCredential(
}
}

@ReactMethod
public void signInWithProvider(String appName, String providerId, @Nullable String email, Promise promise){
OAuthProvider.Builder provider = OAuthProvider.newBuilder(providerId);
if(email != null){
provider.addCustomParameter("login_hint", email);
}
Activity activity = getCurrentActivity();
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);

OnSuccessListener onSuccess = new OnSuccessListener<AuthResult>(){
@Override
public void onSuccess(AuthResult authResult) {
Log.d(TAG, "signInWithProvider:onComplete:success");
promiseWithAuthResult(authResult, promise);
}
};

OnFailureListener onFailure = new OnFailureListener(){
@Override
public void onFailure(@NonNull Exception e) {
Log.w(TAG, "signInWithProvider:onComplete:failure", e);
promiseRejectAuthException(promise, e);
}
};


Task<AuthResult> pendingResultTask = firebaseAuth.getPendingAuthResult();
if(pendingResultTask != null){
pendingResultTask
.addOnSuccessListener(onSuccess)
.addOnFailureListener(onFailure);
} else {
firebaseAuth
.startActivityForSignInWithProvider(activity, provider.build())
.addOnSuccessListener(onSuccess)
.addOnFailureListener(onFailure);
}
}

@ReactMethod
public void linkWithProvider(String appName, String providerId, @Nullable String email, Promise promise){
OAuthProvider.Builder provider = OAuthProvider.newBuilder(providerId);
if(email != null){
provider.addCustomParameter("login_hint", email);
}
Activity activity = getCurrentActivity();
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
FirebaseUser firebaseUser = firebaseAuth.getCurrentUser();

OnSuccessListener onSuccess = new OnSuccessListener<AuthResult>(){
@Override
public void onSuccess(AuthResult authResult) {
Log.d(TAG, "linkWithProvider:onComplete:success");
promiseWithAuthResult(authResult, promise);
}
};

OnFailureListener onFailure = new OnFailureListener(){
@Override
public void onFailure(@NonNull Exception e) {
Log.w(TAG, "linkInWithProvider:onComplete:failure", e);
promiseRejectAuthException(promise, e);
}
};


Task<AuthResult> pendingResultTask = firebaseAuth.getPendingAuthResult();
if(pendingResultTask != null){
pendingResultTask
.addOnSuccessListener(onSuccess)
.addOnFailureListener(onFailure);
} else {
firebaseUser
.startActivityForLinkWithProvider(activity, provider.build())
.addOnSuccessListener(onSuccess)
.addOnFailureListener(onFailure);
}
}

/**
* signInWithPhoneNumber
*
Expand Down Expand Up @@ -1866,6 +1950,30 @@ private void promiseWithAuthResult(AuthResult authResult, Promise promise) {
WritableMap authResultMap = Arguments.createMap();
WritableMap userMap = firebaseUserToMap(authResult.getUser());

if(authResult.getCredential() != null){
if(authResult.getCredential() instanceof OAuthCredential){
OAuthCredential creds = (OAuthCredential) authResult.getCredential();
WritableMap credentialMap = Arguments.createMap();

credentialMap.putString("providerId", creds.getProvider());
credentialMap.putString("signInMethod", creds.getSignInMethod());

if(creds.getIdToken() != null){
credentialMap.putString("idToken", creds.getIdToken());
}

if(creds.getAccessToken() != null){
credentialMap.putString("accessToken", creds.getAccessToken());
}

if(creds.getSecret() != null){
credentialMap.putString("secret", creds.getSecret());
}

authResultMap.putMap("credential", credentialMap);
}
}

if (authResult.getAdditionalUserInfo() != null) {
WritableMap additionalUserInfoMap = Arguments.createMap();

Expand Down Expand Up @@ -1906,6 +2014,7 @@ private void promiseWithAuthResult(AuthResult authResult, Promise promise) {
*/
private void promiseRejectAuthException(Promise promise, Exception exception) {
WritableMap error = getJSError(exception);

final String sessionId = error.getString("sessionId");
final MultiFactorResolver multiFactorResolver = mCachedResolvers.get(sessionId);
WritableMap resolverAsMap = Arguments.createMap();
Expand All @@ -1927,6 +2036,8 @@ private WritableMap getJSError(Exception exception) {
String message = exception.getMessage();
String invalidEmail = "The email address is badly formatted.";

System.out.print(exception);

try {
FirebaseAuthException authException = (FirebaseAuthException) exception;
code = authException.getErrorCode();
Expand Down
22 changes: 10 additions & 12 deletions packages/auth/e2e/auth.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -912,12 +912,11 @@ describe('auth()', function () {
});

describe('signInWithPopup', function () {
it('should throw an unsupported error', function () {
(() => {
firebase.auth().signInWithPopup();
}).should.throw(
'firebase.auth().signInWithPopup() is unsupported by the native Firebase SDKs.',
);
it('should trigger the oauth flow', async function () {
await (async () => {
const provider = new firebase.auth.OAuthProvider('oidc.react.com');
await firebase.auth().signInWithPopup(provider);
}).should.not.throw();
});
});

Expand Down Expand Up @@ -1025,12 +1024,11 @@ describe('auth()', function () {
});

describe('signInWithRedirect()', function () {
it('should throw an unsupported error', function () {
(() => {
firebase.auth().signInWithRedirect();
}).should.throw(
'firebase.auth().signInWithRedirect() is unsupported by the native Firebase SDKs.',
);
it('should trigger the oauth flow', async function () {
await (async () => {
const provider = new firebase.auth.OAuthProvider('oidc.react.com');
await firebase.auth().signInWithRedirect(provider);
}).should.not.throw();
});
});

Expand Down
4 changes: 1 addition & 3 deletions packages/auth/e2e/provider.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,7 @@ describe('auth() -> Providers', function () {
describe('OAuthProvider', function () {
describe('constructor', function () {
it('should throw an unsupported error', function () {
(() => new firebase.auth.OAuthProvider()).should.throw(
'`new OAuthProvider()` is not supported on the native Firebase SDKs.',
);
(() => new firebase.auth.OAuthProvider('oidc.react.com')).should.not.throw();
});
});

Expand Down
14 changes: 6 additions & 8 deletions packages/auth/lib/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,16 +310,14 @@ export default class User {
);
}

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

linkWithRedirect() {
throw new Error(
'firebase.auth.User.linkWithRedirect() is unsupported by the native Firebase SDKs.',
);
async linkWithRedirect(provider) {
return this.linkWithPopup(provider);
}

reauthenticateWithPhoneNumber() {
Expand Down
56 changes: 55 additions & 1 deletion packages/auth/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ export namespace FirebaseAuthTypes {
credential: (token: string | null, secret?: string) => AuthCredential;
}

export type OAuthProvider = AuthProvider & {
new (providerId: string): OAuthProvider;
setCustomParameters: (customOAuthParameters: object) => void;
};

/**
* Interface that represents an Open ID Connect auth provider. Implemented by other providers.
*/
Expand Down Expand Up @@ -315,7 +320,7 @@ export namespace FirebaseAuthTypes {
* firebase.auth.OAuthProvider;
* ```
*/
OAuthProvider: AuthProvider;
OAuthProvider: OAuthProvider;
/**
* Custom Open ID connect auth provider implementation.
*
Expand Down Expand Up @@ -387,6 +392,12 @@ export namespace FirebaseAuthTypes {
* Any additional user information assigned to the user.
*/
additionalUserInfo?: AdditionalUserInfo;

/**
* The AuthCredential returned from the identity provider.
*/
credential: AuthCredential | null;

/**
* Returns the {@link auth.User} interface of this credential.
*/
Expand Down Expand Up @@ -1198,6 +1209,46 @@ export namespace FirebaseAuthTypes {
*/
linkWithCredential(credential: AuthCredential): Promise<UserCredential>;

/**
* Link the user with a 3rd party provider.
*
* #### Example
*
* ```js
* const oauthProvider = new firebase.auth.OAuthProvider('oidc.react.com')
* const authCredentials = await firebase.auth().currentUser.linkWithPopup(oauthProvider);
* ```
*
* @error auth/provider-already-linked Thrown if the provider has already been linked to the user. This error is thrown even if this is not the same provider's account that is currently linked to the user.
* @error auth/invalid-credential Thrown if the provider's credential is not valid. This can happen if it has already expired when calling link, or if it used invalid token(s). See the Firebase documentation for your provider, and make sure you pass in the correct parameters to the credential method.
* @error auth/credential-already-in-use Thrown if the account corresponding to the credential already exists among your users, or is already linked to a Firebase User.
* @error auth/email-already-in-use Thrown if the email corresponding to the credential already exists among your users.
* @error auth/operation-not-allowed Thrown if you have not enabled the provider in the Firebase Console. Go to the Firebase Console for your project, in the Auth section and the Sign in Method tab and configure the provider.
* @throws on iOS {@link auth.NativeFirebaseAuthError}, on Android {@link auth.NativeFirebaseError}
* @param provider A created {@link auth.AuthProvider}.
*/
linkWithPopup(provider: AuthProvider): Promise<UserCredential>;

/**
* Link the user with a 3rd party provider.
*
* #### Example
*
* ```js
* const oauthProvider = new firebase.auth.OAuthProvider('oidc.react.com')
* const authCredentials = await firebase.auth().currentUser.linkWithPopup(oauthProvider);
* ```
*
* @error auth/provider-already-linked Thrown if the provider has already been linked to the user. This error is thrown even if this is not the same provider's account that is currently linked to the user.
* @error auth/invalid-credential Thrown if the provider's credential is not valid. This can happen if it has already expired when calling link, or if it used invalid token(s). See the Firebase documentation for your provider, and make sure you pass in the correct parameters to the credential method.
* @error auth/credential-already-in-use Thrown if the account corresponding to the credential already exists among your users, or is already linked to a Firebase User.
* @error auth/email-already-in-use Thrown if the email corresponding to the credential already exists among your users.
* @error auth/operation-not-allowed Thrown if you have not enabled the provider in the Firebase Console. Go to the Firebase Console for your project, in the Auth section and the Sign in Method tab and configure the provider.
* @throws on iOS {@link auth.NativeFirebaseAuthError}, on Android {@link auth.NativeFirebaseError}
* @param provider A created {@link auth.AuthProvider}.
*/
linkWithRedirect(provider: Provider): Promise<UserCredential>;

/**
* Re-authenticate a user with a third-party authentication provider.
*
Expand Down Expand Up @@ -1708,6 +1759,9 @@ export namespace FirebaseAuthTypes {
*/
signInWithCredential(credential: AuthCredential): Promise<UserCredential>;

signInWithPopup(provider: AuthProvider): Promise<UserCredential>;
signInWithRedirect(provider: AuthProvider): Promise<UserCredential>;

/**
* Sends a password reset email to the given email address.
* Unlike the web SDK, the email will contain a password reset link rather than a code.
Expand Down
14 changes: 7 additions & 7 deletions packages/auth/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,16 +360,16 @@ class FirebaseAuthModule extends FirebaseModule {
throw new Error('firebase.auth().setPersistence() is unsupported by the native Firebase SDKs.');
}

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

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

// firebase issue - https://github.com/invertase/react-native-firebase/pull/655#issuecomment-349904680
Expand Down
10 changes: 8 additions & 2 deletions packages/auth/lib/providers/OAuthProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@
const providerId = 'oauth';

export default class OAuthProvider {
constructor() {
throw new Error('`new OAuthProvider()` is not supported on the native Firebase SDKs.');
constructor(providerId) {
this.providerId = providerId;
}

customParameters = {};

setCustomParameters(customParameters) {
this.customParameters = customParameters;
}

static get PROVIDER_ID() {
Expand Down

0 comments on commit 5acc802

Please sign in to comment.