Skip to content

Commit

Permalink
fix: Microsoft cancelling Permission UI improvement (#775)
Browse files Browse the repository at this point in the history
  • Loading branch information
xil222 authored Nov 3, 2020
1 parent 39230c6 commit acbac90
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 228 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* Improve UI error messages when cancelling permissions on MicroSoft Work Account.
* Update ES6 firebase/app import.
43 changes: 26 additions & 17 deletions javascript/widgets/handler/callback.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,31 +52,37 @@ firebaseui.auth.widget.handler.handleCallback =
firebaseui.auth.widget.handler.handleCallbackResult_(app, component,
result);
}, function(error) {
// Normalize the error.
const normalizedError =
firebaseui.auth.widget.handler.common.normalizeError(error);
// A previous redirect operation was triggered and some error occurred.
// Test for need confirmation error and handle appropriately.
// For all other errors, display info bar and show sign in screen.
if (error &&
if (normalizedError &&
// Single out need confirmation error as email-already-in-use and
// credential-already-in-use will also return email and credential
// and need to be handled differently.
(error['code'] == 'auth/account-exists-with-different-credential' ||
error['code'] == 'auth/email-already-in-use') &&
error['email'] &&
error['credential']) {
(normalizedError['code'] ==
'auth/account-exists-with-different-credential' ||
normalizedError['code'] == 'auth/email-already-in-use') &&
normalizedError['email'] &&
normalizedError['credential']) {
// Save pending email credential.
firebaseui.auth.storage.setPendingEmailCredential(
new firebaseui.auth.PendingEmailCredential(
error['email'], error['credential']),
normalizedError['email'], normalizedError['credential']),
app.getAppId());
firebaseui.auth.widget.handler.handleCallbackLinking_(
app, component, error['email']);
} else if (error && error['code'] == 'auth/user-cancelled') {
app, component, normalizedError['email']);
} else if (normalizedError &&
normalizedError['code'] == 'auth/user-cancelled') {
// Should go back to the previous linking screen. A pending email
// should be present, otherwise there's an error.
var pendingCredential =
const pendingCredential =
firebaseui.auth.storage.getPendingEmailCredential(app.getAppId());
var message =
firebaseui.auth.widget.handler.common.getErrorMessage(error);
const message =
firebaseui.auth.widget.handler.common.getErrorMessage(
normalizedError);
// If there is a credential too, then the previous screen was federated
// linking so we process the error as a linking flow.
if (pendingCredential && pendingCredential.getCredential()) {
Expand All @@ -92,14 +98,17 @@ firebaseui.auth.widget.handler.handleCallback =
} else {
// Go to the sign-in page with info bar error.
firebaseui.auth.widget.handler.handleCallbackFailure_(
app, component, /** @type {!Error} */ (error));
app, component, /** @type {!Error} */ (normalizedError));
}
} else if (error && error['code'] == 'auth/credential-already-in-use') {
} else if (normalizedError &&
normalizedError['code'] == 'auth/credential-already-in-use') {
// Do nothing and keep callback UI while onUpgradeError catches and
// handles this error.
} else if (error &&
error['code'] == 'auth/operation-not-supported-in-this-environment' &&
firebaseui.auth.widget.handler.common.isPasswordProviderOnly(app)) {
} else if (normalizedError &&
normalizedError['code'] ==
'auth/operation-not-supported-in-this-environment' &&
firebaseui.auth.widget.handler.common.isPasswordProviderOnly(
app)) {
// Operation is not supported in this environment but only password
// provider is enabled. So allow this to proceed as a no redirect result.
// This will allow developers using password sign-in in Cordova to use
Expand All @@ -114,7 +123,7 @@ firebaseui.auth.widget.handler.handleCallback =
} else {
// Go to the sign-in page with info bar error.
firebaseui.auth.widget.handler.handleCallbackFailure_(
app, component, /** @type {!Error} */ (error));
app, component, /** @type {!Error} */ (normalizedError));
}
}));
};
Expand Down
27 changes: 27 additions & 0 deletions javascript/widgets/handler/callback_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1961,6 +1961,33 @@ function testHandleCallback_redirectError_userCancelled_noPendingCredential() {
}


function testHandleCallback_redirectError_consentRequired_invalidCredential() {
asyncTestCase.waitForSignals(1);
// Attempting to get redirect result. Special case error message equals
// consent_required. Reject with the user cancelled error.
const invalidCredentialError = {
'code': 'auth/invalid-credential',
'message': 'error=consent_required',
};
const expectedError = {
'code': 'auth/user-cancelled',
};
testAuth.assertGetRedirectResult([], null, invalidCredentialError);
// Callback rendered.
firebaseui.auth.widget.handler.handleCallback(app, container);
assertCallbackPage();
testAuth.process().then(function() {
// Redirects to the federated sign-in page.
assertProviderSignInPage();
// Confirm expected error shown in info bar.
assertInfoBarMessage(
firebaseui.auth.widget.handler.common.getErrorMessage(
expectedError));
asyncTestCase.signal();
});
}


function testHandleCallback_nullUser() {
// Test when no previous sign-in with redirect is detected and provider sign
// in page is rendered.
Expand Down
31 changes: 28 additions & 3 deletions javascript/widgets/handler/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,27 @@ goog.require('goog.array');
*/
firebaseui.auth.OAuthResponse;

/**
* Normalizes the error. This is useful for mapping certain errors to different
* errors.
* When no mapping is needed, the same error is returned.
* This is currently used to map 'auth/invalid-credential' code to
* 'auth/user-cancelled' when users do not grant access permission to
* Microsoft work account.
* @param {*} error The original error.
* @return {*} The normalized error.
* @package
*/
firebaseui.auth.widget.handler.common.normalizeError =
function(error) {
if (error['code'] === 'auth/invalid-credential' &&
error['message'] &&
error['message'].indexOf('error=consent_required') !== -1) {
return {code: 'auth/user-cancelled'};
}
return error;
};

/**
* Sets the user as signed in with Auth result. Signs in on external Auth
* instance if not already signed in and then invokes
Expand Down Expand Up @@ -513,7 +534,10 @@ firebaseui.auth.widget.handler.common.federatedSignIn = function(
if (error['name'] && error['name'] == 'cancel') {
return;
}
switch (error['code']) {
// Normalize the error.
const normalizedError =
firebaseui.auth.widget.handler.common.normalizeError(error);
switch (normalizedError['code']) {
case 'auth/popup-blocked':
// Popup blocked, switch to redirect flow as fallback.
processRedirect();
Expand All @@ -533,7 +557,8 @@ firebaseui.auth.widget.handler.common.federatedSignIn = function(
// For no action errors like network error, just display in info
// bar in current component. A second attempt could still work.
component.showInfoBar(
firebaseui.auth.widget.handler.common.getErrorMessage(error));
firebaseui.auth.widget.handler.common.getErrorMessage(
normalizedError));
break;
default:
// Either linking required errors or errors that are
Expand All @@ -543,7 +568,7 @@ firebaseui.auth.widget.handler.common.federatedSignIn = function(
firebaseui.auth.widget.HandlerName.CALLBACK,
app,
container,
goog.Promise.reject(error));
goog.Promise.reject(normalizedError));
break;
}
};
Expand Down
36 changes: 36 additions & 0 deletions javascript/widgets/handler/federatedsignin_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,42 @@ function testHandleFederatedSignIn_popup_userCancelled() {
}


function testHandleFederatedSignIn_popup_userCancelled_consentRequired() {
// Test federated sign in with popup when user denies permissions on
// Microsoft work account.
app.updateConfig('signInFlow', 'popup');
// Since Microsoft's signInOptions include a loginHintKey definition,
// a login_hint should be set in the customParameters.
const expectedProvider =
getExpectedProviderWithCustomParameters('microsoft.com',
{'login_hint': '[email protected]'});
firebaseui.auth.widget.handler.handleFederatedSignIn(
app, container, '[email protected]', 'microsoft.com');
assertFederatedLinkingPage();
submitForm();
// When microsoft.com consent is rejected, auth/invalid-credential is thrown.
// This will get normalized to auth/user-cancelled.
const invalidCredentialError = {
'code': 'auth/invalid-credential',
'message': 'error=consent_required',
};
const expectedError = {
'code': 'auth/user-cancelled',
};
testAuth.assertSignInWithPopup(
[expectedProvider],
null,
invalidCredentialError);
return testAuth.process().then(function() {
// Remain on same page and display the error in info bar.
assertFederatedLinkingPage();
// Show error in info bar.
assertInfoBarMessage(
firebaseui.auth.widget.handler.common.getErrorMessage(expectedError));
});
}


function testHandleFederatedSignIn_popup_popupBlockedError() {
// Test federated sign in with popup when popup blocked.
app.updateConfig('signInFlow', 'popup');
Expand Down
Loading

0 comments on commit acbac90

Please sign in to comment.