Skip to content

Commit b2336f5

Browse files
authored
Support for skipping the nascar buttons screen when only using a single federated provider. (#584)
1 parent 9f2ccda commit b2336f5

14 files changed

+750
-247
lines changed

README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,6 @@ Here is how you would track the Auth state across all your pages:
323323

324324
FirebaseUI supports the following configuration parameters.
325325

326-
<table>
327-
<thead>
328-
<tr>
329326
<table>
330327
<thead>
331328
<tr>
@@ -390,7 +387,6 @@ FirebaseUI supports the following configuration parameters.
390387
</td>
391388
</tr>
392389
<tr>
393-
<tr>
394390
<td>signInFlow</td>
395391
<td>No</td>
396392
<td>
@@ -401,6 +397,17 @@ FirebaseUI supports the following configuration parameters.
401397
</td>
402398
</tr>
403399
<tr>
400+
<td>immediateFederatedRedirect</td>
401+
<td>No</td>
402+
<td>
403+
A boolean which determines whether to immediately redirect to the provider's
404+
site or instead show the default 'Sign in with Provider' button when there is
405+
only a single federated provider in <code>signInOptions</code>. In order for
406+
this option to take effect, the <code>signInOptions</code> must only hold a
407+
single federated provider (like 'google.com') and signInFlow must be set to
408+
'redirect'.
409+
</td>
410+
</tr>
404411
<tr>
405412
<td>signInOptions</td>
406413
<td>Yes</td>
@@ -411,7 +418,6 @@ FirebaseUI supports the following configuration parameters.
411418
</td>
412419
</tr>
413420
<tr>
414-
<tr>
415421
<td>signInSuccessUrl</td>
416422
<td>No</td>
417423
<td>
@@ -1282,6 +1288,12 @@ FirebaseUI is displayed.
12821288
},
12831289
firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID
12841290
],
1291+
// Set to true if you only have a single federated provider like
1292+
// firebase.auth.GoogleAuthProvider.PROVIDER_ID and you would like to
1293+
// immediately redirect to the provider's site instead of showing a
1294+
// 'Sign in with Provider' button first. In order for this to take
1295+
// effect, the signInFlow option must also be set to 'redirect'.
1296+
immediateFederatedRedirect: false,
12851297
// tosUrl and privacyPolicyUrl accept either url string or a callback
12861298
// function.
12871299
// Terms of service url/callback.

javascript/utils/idp.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,19 @@ firebaseui.auth.idp.isSaml_ = function(providerId) {
110110
};
111111

112112

113+
/**
114+
* Returns true if the provider is federated, that is either: a built-in OAuth
115+
* provider like Google, a SAML provider or an ODIC or generic OAuth provider.
116+
* @param {string} providerId
117+
* @return {boolean} Whether the provider is non-federated or not.
118+
*/
119+
firebaseui.auth.idp.isFederatedSignInMethod = function(providerId) {
120+
return !goog.array.contains(
121+
firebaseui.auth.idp.NonFederatedSignInMethods,
122+
providerId);
123+
};
124+
125+
113126
/**
114127
* Returns the provider by provider ID. If the provider ID is neither built-in
115128
* provider or SAML provder, it will be considered as generic OAuth provider.

javascript/utils/idp_test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ function tearDown() {
6464
}
6565

6666

67+
function testIsFederatedSignInMethod() {
68+
// OAuth, SAML and OIDC providers should all return true.
69+
assertTrue(firebaseui.auth.idp.isFederatedSignInMethod('google.com'));
70+
assertTrue(firebaseui.auth.idp.isFederatedSignInMethod('microsoft.com'));
71+
assertTrue(firebaseui.auth.idp.isFederatedSignInMethod('saml.provider'));
72+
assertTrue(firebaseui.auth.idp.isFederatedSignInMethod('oidc.provider'));
73+
assertTrue(firebaseui.auth.idp.isFederatedSignInMethod('anotherProvider'));
74+
// Non-federated providers should return false.
75+
assertFalse(firebaseui.auth.idp.isFederatedSignInMethod('emailLink'));
76+
assertFalse(firebaseui.auth.idp.isFederatedSignInMethod('password'));
77+
assertFalse(firebaseui.auth.idp.isFederatedSignInMethod('phone'));
78+
}
79+
80+
6781
function testGetAuthProvider() {
6882
// Built-in provider.
6983
var builtInProvider = firebaseui.auth.idp.getAuthProvider('google.com');

javascript/widgets/authui.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ goog.require('firebaseui.auth.widget.handler.handleEmailVerification');
5656
/** @suppress {extraRequire} */
5757
goog.require('firebaseui.auth.widget.handler.handleFederatedLinking');
5858
/** @suppress {extraRequire} */
59+
goog.require('firebaseui.auth.widget.handler.handleFederatedRedirect');
60+
/** @suppress {extraRequire} */
5961
goog.require('firebaseui.auth.widget.handler.handleFederatedSignIn');
6062
/** @suppress {extraRequire} */
6163
goog.require('firebaseui.auth.widget.handler.handlePasswordLinking');

javascript/widgets/config.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ firebaseui.auth.widget.Config = function() {
5555
this.config_.define(
5656
'credentialHelper',
5757
firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM);
58+
/**
59+
* Determines whether to immediately redirect to the provider's site or
60+
* instead show the default 'Sign in with Provider' button when there
61+
* is only a single federated provider in signInOptions. In order for this
62+
* option to take effect, the signInOptions must only hold a single federated
63+
* provider (like 'google.com') and signInFlow must be set to 'redirect'.
64+
*/
65+
this.config_.define('immediateFederatedRedirect', false);
5866
this.config_.define('popupMode', false);
5967
this.config_.define('privacyPolicyUrl');
6068
/**
@@ -835,6 +843,27 @@ firebaseui.auth.widget.Config.prototype.getPopupMode = function() {
835843
};
836844

837845

846+
/**
847+
* Determines whether to show the 'nascar' sign-in buttons screen or
848+
* immediately redirect to the provider's site when there is only a single
849+
* federated provider in signInOptions. In order for this option to take
850+
* effect, the signInOptions must only hold a single federated provider (like
851+
* 'google.com') and signInFlow must be set to 'redirect'.
852+
* @return {boolean} Whether to skip the 'nascar' screen or not.
853+
*/
854+
firebaseui.auth.widget.Config.prototype.
855+
federatedProviderShouldImmediatelyRedirect = function() {
856+
var immediateFederatedRedirect = !!this.config_.get(
857+
'immediateFederatedRedirect');
858+
var providers = this.getProviders();
859+
var signInFlow = this.getSignInFlow();
860+
return immediateFederatedRedirect &&
861+
providers.length == 1 &&
862+
firebaseui.auth.idp.isFederatedSignInMethod(providers[0]) &&
863+
signInFlow == firebaseui.auth.widget.Config.SignInFlow.REDIRECT;
864+
};
865+
866+
838867
/**
839868
* @return {!firebaseui.auth.widget.Config.SignInFlow} The current sign-in
840869
* flow.

javascript/widgets/config_test.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ function setUp() {
5050
});
5151
firebase.auth = {
5252
GoogleAuthProvider: {PROVIDER_ID: 'google.com'},
53+
FacebookAuthProvider: {PROVIDER_ID: 'facebook.com'},
5354
EmailAuthProvider: {
5455
EMAIL_LINK_SIGN_IN_METHOD: 'emailLink',
5556
EMAIL_PASSWORD_SIGN_IN_METHOD: 'password',
@@ -107,6 +108,64 @@ function testGetRequiredWidgetUrl() {
107108
}
108109

109110

111+
function testFederatedProviderShouldImmediatelyRedirect() {
112+
// Returns true when immediateFederatedRedirect is set, there is
113+
// only one federated provider and the signInFlow is set to redirect.
114+
config.setConfig({
115+
'immediateFederatedRedirect': true,
116+
'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID],
117+
'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT
118+
});
119+
assertTrue(config.federatedProviderShouldImmediatelyRedirect());
120+
121+
// Returns false if the immediateFederatedRedirect option is false.
122+
config.setConfig({
123+
'immediateFederatedRedirect': false,
124+
'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID],
125+
'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT
126+
});
127+
assertFalse(config.federatedProviderShouldImmediatelyRedirect());
128+
129+
// Returns false if the provider is not a federated provider.
130+
config.setConfig({
131+
'immediateFederatedRedirect': true,
132+
'signInOptions': [firebase.auth.EmailAuthProvider.PROVIDER_ID],
133+
'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT
134+
});
135+
assertFalse(config.federatedProviderShouldImmediatelyRedirect());
136+
137+
// Returns false if there is more than one federated provider.
138+
config.setConfig({
139+
'immediateFederatedRedirect': true,
140+
'signInOptions': [
141+
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
142+
firebase.auth.FacebookAuthProvider.PROVIDER_ID
143+
],
144+
'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT
145+
});
146+
assertFalse(config.federatedProviderShouldImmediatelyRedirect());
147+
148+
// Returns false if there is more than one provider of any kind.
149+
config.setConfig({
150+
'immediateFederatedRedirect': true,
151+
'signInOptions': [
152+
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
153+
firebase.auth.EmailAuthProvider.PROVIDER_ID
154+
],
155+
'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT
156+
});
157+
assertFalse(config.federatedProviderShouldImmediatelyRedirect());
158+
159+
// Returns false if signInFlow is using a popup.
160+
config.setConfig({
161+
'immediateFederatedRedirect': true,
162+
'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID],
163+
'signInFlow': firebaseui.auth.widget.Config.SignInFlow.POPUP
164+
});
165+
assertFalse(config.federatedProviderShouldImmediatelyRedirect());
166+
}
167+
168+
110169
function testGetSignInFlow() {
111170
// Confirm default value for sign-in flow
112171
assertEquals(

javascript/widgets/dispatcher_test.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,56 @@ function testDispatchOperation_callback_noPendingRedirect() {
629629
}
630630

631631

632+
function testDispatchOperation_callback_canSkipNascarScreen() {
633+
// Checks to make sure that when immediateFederatedRedirect is true
634+
// and all the correct options are set, the 'nascar' sign-in screen will
635+
// be skipped.
636+
var element = goog.dom.createElement('div');
637+
setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.CALLBACK);
638+
// Simulate app not returning from redirect sign-in operation.
639+
firebaseui.auth.storage.removePendingRedirectStatus(app.getAppId());
640+
// In order for an immediate redirect to succeed all of the following
641+
// options must be set:
642+
app.setConfig({
643+
'immediateFederatedRedirect': true,
644+
'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID],
645+
'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT
646+
});
647+
firebaseui.auth.widget.dispatcher.dispatchOperation(app, element);
648+
// The federated redirect handler should trigger.
649+
assertHandlerInvoked(
650+
firebaseui.auth.widget.HandlerName.FEDERATED_REDIRECT,
651+
app,
652+
element);
653+
}
654+
655+
656+
function testDispatchOperation_callback_canShowNascarScreen() {
657+
// Checks to make sure that even if immediateFederatedRedirect is true
658+
// unless all the correct options are set, the 'nascar' sign-in screen will
659+
// not be skipped.
660+
var element = goog.dom.createElement('div');
661+
setModeAndUrlParams(firebaseui.auth.widget.Config.WidgetMode.CALLBACK);
662+
// Simulate app not returning from redirect sign-in operation.
663+
firebaseui.auth.storage.removePendingRedirectStatus(app.getAppId());
664+
// The immediate redirect should not be triggered (since there is more
665+
// than one federated provider and it is using a popup).
666+
app.setConfig({
667+
'immediateFederatedRedirect': true,
668+
'signInOptions': [
669+
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
670+
firebase.auth.FacebookAuthProvider.PROVIDER_ID],
671+
'signInFlow': firebaseui.auth.widget.Config.SignInFlow.POPUP
672+
});
673+
firebaseui.auth.widget.dispatcher.dispatchOperation(app, element);
674+
// The normal provider sign in handler 'nascar' screen should be rendered.
675+
assertHandlerInvoked(
676+
firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN,
677+
app,
678+
element);
679+
}
680+
681+
632682
function testDispatchOperation_revokeChangeEmail() {
633683
var element = goog.dom.createElement('div');
634684
setModeAndUrlParams(

javascript/widgets/handler/callback_test.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,59 @@ function testHandleCallback_redirectUser_pendingCredential_error() {
11171117
}
11181118

11191119

1120+
function testHandleCallback_redirectUser_alwaysShowNascarScreenOnError() {
1121+
// Tests that the 'nascar' sign-in page won't be skipped when there is
1122+
// an error message. This is tested by simulating an error in linking
1123+
// after returning from a regular sign in operation with pending
1124+
// credentials.
1125+
asyncTestCase.waitForSignals(1);
1126+
app.setConfig({
1127+
'immediateFederatedRedirect': true,
1128+
'signInOptions': [firebase.auth.GoogleAuthProvider.PROVIDER_ID],
1129+
'signInFlow': firebaseui.auth.widget.Config.SignInFlow.REDIRECT
1130+
});
1131+
var cred = firebaseui.auth.idp.getAuthCredential({
1132+
'providerId': 'google.com',
1133+
'accessToken': 'ACCESS_TOKEN'
1134+
});
1135+
var pendingEmailCred = new firebaseui.auth.PendingEmailCredential(
1136+
federatedAccount.getEmail(), cred);
1137+
// Simulate previous linking required (pending credentials should be saved).
1138+
firebaseui.auth.storage.setPendingEmailCredential(
1139+
pendingEmailCred, app.getAppId());
1140+
// Callback rendered.
1141+
firebaseui.auth.widget.handler.handleCallback(app, container);
1142+
assertCallbackPage();
1143+
// User should be signed in at this point.
1144+
testAuth.setUser({
1145+
'email': federatedAccount.getEmail(),
1146+
'displayName': federatedAccount.getDisplayName()
1147+
});
1148+
// Attempting to get redirect result. Resolve with success.
1149+
testAuth.assertGetRedirectResult(
1150+
[],
1151+
{
1152+
'user': testAuth.currentUser,
1153+
'credential': null
1154+
});
1155+
testAuth.process().then(function() {
1156+
// Linking should be triggered with pending credential.
1157+
// Simulate an error here.
1158+
testAuth.currentUser.assertLinkAndRetrieveDataWithCredential(
1159+
[cred], null, internalError);
1160+
return testAuth.process();
1161+
}).then(function() {
1162+
// Redirect to the provider's sign-in page. This should not skip the
1163+
// 'nascar' screen since there is an error message.
1164+
assertProviderSignInPage();
1165+
// Show error in info bar.
1166+
assertInfoBarMessage(
1167+
firebaseui.auth.widget.handler.common.getErrorMessage(internalError));
1168+
asyncTestCase.signal();
1169+
});
1170+
}
1171+
1172+
11201173
function testHandleCallback_signedInUser_pendingCred_error_popup() {
11211174
// Test sign in operation with pending credentials requiring linking in popup
11221175
// flow. Simulate error in linking.

javascript/widgets/handler/common.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,21 @@ firebaseui.auth.widget.handler.common.federatedSignIn = function(
976976
firebaseui.auth.log.error('signInWithRedirect: ' + error['code']);
977977
var errorMessage = firebaseui.auth.widget.handler.common.getErrorMessage(
978978
error);
979-
component.showInfoBar(errorMessage);
979+
// If the page was previously blank because the 'nascar' screen is being
980+
// skipped, then the provider sign-in 'nascar' screen needs to be shown
981+
// along with the error message. Otherwise, the error message can simply
982+
// be added to the info bar.
983+
if (component.getPageId() == 'blank' &&
984+
app.getConfig().federatedProviderShouldImmediatelyRedirect()) {
985+
component.dispose();
986+
firebaseui.auth.widget.handler.handle(
987+
firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN,
988+
app,
989+
container,
990+
errorMessage);
991+
} else {
992+
component.showInfoBar(errorMessage);
993+
}
980994
};
981995
// Error handler for signInWithPopup and getRedirectResult on Cordova.
982996
var signInResultErrorCallback = function(error) {
@@ -1399,8 +1413,18 @@ firebaseui.auth.widget.handler.common.handleSignInStart = function(
13991413
// which would trigger an info bar message.
14001414
firebaseui.auth.widget.handler.handle(
14011415
firebaseui.auth.widget.HandlerName.PHONE_SIGN_IN_START, app, container);
1416+
} else if (app &&
1417+
app.getConfig().federatedProviderShouldImmediatelyRedirect() &&
1418+
!opt_infoBarMessage) {
1419+
// If there is an info bar message available, the 'nascar' screen cannot be
1420+
// skipped since the message or error must be shown to the user.
1421+
firebaseui.auth.widget.handler.handle(
1422+
firebaseui.auth.widget.HandlerName.FEDERATED_REDIRECT,
1423+
app,
1424+
container);
14021425
} else {
1403-
// For all other cases, show the provider sign-in screen.
1426+
// For all other cases, show the provider sign-in screen along with any
1427+
// info bar messages that need to be shown to the user.
14041428
firebaseui.auth.widget.handler.handle(
14051429
firebaseui.auth.widget.HandlerName.PROVIDER_SIGN_IN,
14061430
app,

0 commit comments

Comments
 (0)