Skip to content

Commit b8a808f

Browse files
authored
Merge pull request #20 from open-craft/artur/redirect-on-tpa-unlinked-redwood
feat: redirect to custom URL when third-party auth account is unlinked
2 parents 8efb225 + 8bf29dd commit b8a808f

File tree

5 files changed

+65
-1
lines changed

5 files changed

+65
-1
lines changed

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ ORDER_HISTORY_URL=null
1313
REFRESH_ACCESS_TOKEN_ENDPOINT=null
1414
SEGMENT_KEY=''
1515
SITE_NAME=null
16+
TPA_UNLINKED_ACCOUNT_PROVISION_URL=''
1617
INFO_EMAIL=''
1718
# ***** Cookies *****
1819
USER_RETENTION_COOKIE_NAME=null

README.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ The authentication micro-frontend also requires the following additional variabl
119119
- 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 <docs/how_tos/modifying_base_container.rst>`_.
120120
- ``true`` | ``''`` (empty strings are falsy)
121121

122+
* - ``TPA_UNLINKED_ACCOUNT_PROVISION_URL``
123+
- 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.
124+
- ``http://example.com/signup`` | ``''``
125+
122126

123127
edX-specific Environment Variables
124128
==================================
@@ -219,4 +223,4 @@ Please see `LICENSE <https://github.com/openedx/frontend-app-authn/blob/master/L
219223
:target: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml
220224
:alt: Continuous Integration
221225
.. |semantic-release| image:: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
222-
:target: https://github.com/semantic-release/semantic-release
226+
:target: https://github.com/semantic-release/semantic-release

src/config/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const configuration = {
2222
SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null,
2323
TOS_AND_HONOR_CODE: process.env.TOS_AND_HONOR_CODE || null,
2424
TOS_LINK: process.env.TOS_LINK || null,
25+
TPA_UNLINKED_ACCOUNT_PROVISION_URL: process.env.TPA_UNLINKED_ACCOUNT_PROVISION_URL || null,
2526
// Base container images
2627
BANNER_IMAGE_LARGE: process.env.BANNER_IMAGE_LARGE || '',
2728
BANNER_IMAGE_MEDIUM: process.env.BANNER_IMAGE_MEDIUM || '',

src/login/LoginPage.jsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,18 @@ const LoginPage = (props) => {
176176

177177
const { provider, skipHintedLogin } = getTpaProvider(tpaHint, providers, secondaryProviders);
178178

179+
const unlinkedProvisionUrl = getConfig().TPA_UNLINKED_ACCOUNT_PROVISION_URL;
180+
181+
/**
182+
* When currentProvider exists and we are in a login page, it is
183+
* because the third-party authenticated account is not linked.
184+
* See also ThirdPartyAuthAlert.jsx.
185+
*/
186+
if (currentProvider && unlinkedProvisionUrl) {
187+
window.location.href = unlinkedProvisionUrl;
188+
return null;
189+
}
190+
179191
if (tpaHint) {
180192
if (thirdPartyAuthApiStatus === PENDING_STATE) {
181193
return <Skeleton height={36} />;

src/login/tests/LoginPage.test.jsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,4 +830,50 @@ describe('LoginPage', () => {
830830
expect(container.querySelector('input#emailOrUsername').value).toEqual('john_doe');
831831
expect(container.querySelector('input#password').value).toEqual('test-password');
832832
});
833+
834+
it('should not redirect to provisioning URL when not configured', () => {
835+
mergeConfig({
836+
TPA_UNLINKED_ACCOUNT_PROVISION_URL: '',
837+
});
838+
839+
store = mockStore({
840+
...initialState,
841+
commonComponents: {
842+
...initialState.commonComponents,
843+
thirdPartyAuthContext: {
844+
...initialState.commonComponents.thirdPartyAuthContext,
845+
currentProvider: ssoProvider.name,
846+
},
847+
},
848+
});
849+
850+
delete window.location;
851+
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE) };
852+
853+
render(reduxWrapper(<IntlLoginPage {...props} />));
854+
expect(window.location.href).toEqual(getConfig().BASE_URL.concat(LOGIN_PAGE));
855+
});
856+
857+
it('should redirect to provisioning URL on unlinked third-party auth account', () => {
858+
mergeConfig({
859+
TPA_UNLINKED_ACCOUNT_PROVISION_URL: 'http://example.com/signup',
860+
});
861+
862+
store = mockStore({
863+
...initialState,
864+
commonComponents: {
865+
...initialState.commonComponents,
866+
thirdPartyAuthContext: {
867+
...initialState.commonComponents.thirdPartyAuthContext,
868+
currentProvider: ssoProvider.name,
869+
},
870+
},
871+
});
872+
873+
delete window.location;
874+
window.location = { href: getConfig().BASE_URL.concat(LOGIN_PAGE) };
875+
876+
render(reduxWrapper(<IntlLoginPage {...props} />));
877+
expect(window.location.href).toEqual('http://example.com/signup');
878+
});
833879
});

0 commit comments

Comments
 (0)