diff --git a/.env b/.env index 9d2c9658aa..729defc1bc 100644 --- a/.env +++ b/.env @@ -13,6 +13,7 @@ ORDER_HISTORY_URL=null REFRESH_ACCESS_TOKEN_ENDPOINT=null SEGMENT_KEY='' SITE_NAME=null +TPA_UNLINKED_ACCOUNT_PROVISION_URL='' INFO_EMAIL='' # ***** Cookies ***** USER_RETENTION_COOKIE_NAME=null diff --git a/README.rst b/README.rst index e2edba9259..71ca543a94 100644 --- a/README.rst +++ b/README.rst @@ -119,6 +119,10 @@ The authentication micro-frontend also requires the following additional variabl - Enables the image layout feature within the authn. When set to True, this feature allows the inclusion of images in the base container layout. For more details on configuring this feature, please refer to the `Modifying base container `_. - ``true`` | ``''`` (empty strings are falsy) + * - ``TPA_UNLINKED_ACCOUNT_PROVISION_URL`` + - URL to redirect to when the identity provided by third-party authentication is not yet linked to a platform account. This allows for redirecting to a custom sign-up flow handled by an external service to create the linked account. An empty string (the default) disables this feature. + - ``http://example.com/signup`` | ``''`` + edX-specific Environment Variables ================================== @@ -219,4 +223,4 @@ Please see `LICENSE { const { provider, skipHintedLogin } = getTpaProvider(tpaHint, providers, secondaryProviders); + const unlinkedProvisionUrl = getConfig().TPA_UNLINKED_ACCOUNT_PROVISION_URL; + + /** + * When currentProvider exists and we are in a login page, it is + * because the third-party authenticated account is not linked. + * See also ThirdPartyAuthAlert.jsx. + */ + if (currentProvider && unlinkedProvisionUrl) { + window.location.href = unlinkedProvisionUrl; + return null; + } + if (tpaHint) { if (thirdPartyAuthApiStatus === PENDING_STATE) { return ; diff --git a/src/login/tests/LoginPage.test.jsx b/src/login/tests/LoginPage.test.jsx index 9c337bf25e..891df9a92c 100644 --- a/src/login/tests/LoginPage.test.jsx +++ b/src/login/tests/LoginPage.test.jsx @@ -830,4 +830,50 @@ describe('LoginPage', () => { expect(container.querySelector('input#emailOrUsername').value).toEqual('john_doe'); expect(container.querySelector('input#password').value).toEqual('test-password'); }); + + it('should not redirect to provisioning URL when not configured', () => { + mergeConfig({ + TPA_UNLINKED_ACCOUNT_PROVISION_URL: '', + }); + + store = mockStore({ + ...initialState, + commonComponents: { + ...initialState.commonComponents, + thirdPartyAuthContext: { + ...initialState.commonComponents.thirdPartyAuthContext, + currentProvider: ssoProvider.name, + }, + }, + }); + + delete window.location; + window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE) }; + + render(reduxWrapper()); + expect(window.location.href).toEqual(getConfig().BASE_URL.concat(LOGIN_PAGE)); + }); + + it('should redirect to provisioning URL on unlinked third-party auth account', () => { + mergeConfig({ + TPA_UNLINKED_ACCOUNT_PROVISION_URL: 'http://example.com/signup', + }); + + store = mockStore({ + ...initialState, + commonComponents: { + ...initialState.commonComponents, + thirdPartyAuthContext: { + ...initialState.commonComponents.thirdPartyAuthContext, + currentProvider: ssoProvider.name, + }, + }, + }); + + delete window.location; + window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE) }; + + render(reduxWrapper()); + expect(window.location.href).toEqual('http://example.com/signup'); + }); });