From 095cfffe573eab4d5bd9f1c4b13d2c19edfe6408 Mon Sep 17 00:00:00 2001 From: James Kachel Date: Wed, 22 Jan 2025 14:35:57 -0600 Subject: [PATCH 1/4] Add support for round-tripping the user through Unified Ecommerce on login This will ensure they get signed into UE when they log into MITx Online, so they can then get to the cart/etc. This is feature flagged - ensure "enable_unified_ecommerce" is configured in PostHog to test this. Because this is checked before login, you won't be able to limit this to specific usernames. (You can still limit it somewhat, like to IP address.) Until MITx Online is set up to use Keycloak for authentication, this will necessarily require you to log into Keycloak too once you've logged into MITx Online. There's no requirement that you log in using the _same_ account. MITx Online will have no idea what your session in UE is. --- .secrets.baseline | 4 ++-- frontend/public/src/lib/auth.js | 18 +++++++++++++++++- frontend/public/src/lib/auth_test.js | 22 +++++++++++++++++++++- main/settings.py | 8 ++++++++ main/utils.py | 1 + 5 files changed, 49 insertions(+), 4 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index c1fd94de4b..3c62e25052 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -166,7 +166,7 @@ "filename": "frontend/public/src/lib/auth.js", "hashed_secret": "e3c328a97de7239b3f60eecda765a69535205744", "is_verified": false, - "line_number": 23 + "line_number": 25 } ], "frontend/public/src/lib/test_constants.js": [ @@ -240,5 +240,5 @@ } ] }, - "generated_at": "2024-11-05T12:32:12Z" + "generated_at": "2025-01-22T20:35:24Z" } diff --git a/frontend/public/src/lib/auth.js b/frontend/public/src/lib/auth.js index f6a629a811..9fe44e6a4a 100644 --- a/frontend/public/src/lib/auth.js +++ b/frontend/public/src/lib/auth.js @@ -1,8 +1,10 @@ // @flow +/* global SETTINGS: false */ import qs from "query-string" import { isEmpty, includes, has } from "ramda" import { routes } from "../lib/urls" +import { checkFeatureFlag } from "../lib/util" import type { RouterHistory } from "react-router" import type { AuthResponse, AuthStates } from "../flow/authTypes" @@ -77,6 +79,9 @@ export const handleAuthResponse = ( ) => { /* eslint-disable camelcase */ const { state, redirect_url, partial_token, errors, field_errors } = response + // This is pre-login so we won't be able to flag this out for individual users. + // The use of "anonymousUser" is historical - see usage in PR #2064 for instance + const sendThruEcommerce = checkFeatureFlag("enable_unified_ecommerce", "anonymousUser"); // If a specific handler function was passed in for this response state, invoke it if (has(state, handlers)) { @@ -84,7 +89,18 @@ export const handleAuthResponse = ( } if (state === STATE_SUCCESS) { - window.location = redirect_url || routes.dashboard + const end_location = redirect_url || routes.dashboard + if (SETTINGS.unified_ecommerce_url && sendThruEcommerce) { + const ecommerce = new URL(`${SETTINGS.unified_ecommerce_url}`) + + ecommerce.pathname = "establish_session/" + ecommerce.searchParams.set("next", end_location) + ecommerce.searchParams.set("system", "mitxonline") + + window.location = ecommerce.href + } else { + window.location = end_location + } } else if (state === STATE_LOGIN_PASSWORD) { history.push(routes.login.password) } else if (state === STATE_REGISTER_DETAILS) { diff --git a/frontend/public/src/lib/auth_test.js b/frontend/public/src/lib/auth_test.js index be488e0dd0..c8f2338905 100644 --- a/frontend/public/src/lib/auth_test.js +++ b/frontend/public/src/lib/auth_test.js @@ -11,6 +11,9 @@ import { import { routes } from "../lib/urls" import { makeRegisterAuthResponse } from "../factories/auth" + +const util = require("../lib/util") + describe("auth lib function", () => { it("generateLoginRedirectUrl should generate a url to redirect to after login", () => { window.location = "/protected/route?var=abc" @@ -47,5 +50,22 @@ describe("auth lib function", () => { sinon.assert.calledWith(handler, response) }) }) - }) + + ;[true, false].forEach(flagState => { + it(`redirects through Unified Ecommerce properly if the feature flag is ${flagState ? "enabled" : "disabled"}`, () => { + const response = makeRegisterAuthResponse({ state: "success", redirect_url: "/" }) + const handlers = [] + global.SETTINGS = { + unified_ecommerce_url: "http://ecommerce" + } + + sandbox.stub(util, 'checkFeatureFlag').returns(flagState) + + assert.equal(util.checkFeatureFlag("enable_unified_ecommerce", "anonymousUser"), flagState) + + handleAuthResponse(history, response, handlers) + + assert.equal(window.location.hostname, flagState ? "ecommerce" : "fake") + }) + })}) }) diff --git a/main/settings.py b/main/settings.py index 15a0871167..0087ea4973 100644 --- a/main/settings.py +++ b/main/settings.py @@ -1199,3 +1199,11 @@ default="", description="Hubspot Portal ID", ) + +# Unified Ecommerce integration + +UNIFIED_ECOMMERCE_URL = get_string( + name="UNIFIED_ECOMMERCE_URL", + default="http://ue.odl.local:9080/", + description="The base URL for Unified Ecommerce.", +) diff --git a/main/utils.py b/main/utils.py index d980259f25..6325efafd6 100644 --- a/main/utils.py +++ b/main/utils.py @@ -52,6 +52,7 @@ def get_js_settings(request: HttpRequest): # noqa: ARG001 "features": {}, "posthog_api_token": settings.POSTHOG_PROJECT_API_KEY, "posthog_api_host": settings.POSTHOG_API_HOST, + "unified_ecommerce_url": settings.UNIFIED_ECOMMERCE_URL, } From 80242f22ffadb2db60b3e47fdfa17d857069a656 Mon Sep 17 00:00:00 2001 From: James Kachel Date: Wed, 22 Jan 2025 15:06:20 -0600 Subject: [PATCH 2/4] lint fix --- frontend/public/src/lib/auth.js | 5 ++++- frontend/public/src/lib/auth_test.js | 21 ++++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/frontend/public/src/lib/auth.js b/frontend/public/src/lib/auth.js index 9fe44e6a4a..ad74433ed1 100644 --- a/frontend/public/src/lib/auth.js +++ b/frontend/public/src/lib/auth.js @@ -81,7 +81,10 @@ export const handleAuthResponse = ( const { state, redirect_url, partial_token, errors, field_errors } = response // This is pre-login so we won't be able to flag this out for individual users. // The use of "anonymousUser" is historical - see usage in PR #2064 for instance - const sendThruEcommerce = checkFeatureFlag("enable_unified_ecommerce", "anonymousUser"); + const sendThruEcommerce = checkFeatureFlag( + "enable_unified_ecommerce", + "anonymousUser" + ) // If a specific handler function was passed in for this response state, invoke it if (has(state, handlers)) { diff --git a/frontend/public/src/lib/auth_test.js b/frontend/public/src/lib/auth_test.js index c8f2338905..c0de0b28f7 100644 --- a/frontend/public/src/lib/auth_test.js +++ b/frontend/public/src/lib/auth_test.js @@ -11,7 +11,6 @@ import { import { routes } from "../lib/urls" import { makeRegisterAuthResponse } from "../factories/auth" - const util = require("../lib/util") describe("auth lib function", () => { @@ -50,22 +49,30 @@ describe("auth lib function", () => { sinon.assert.calledWith(handler, response) }) }) - ;[true, false].forEach(flagState => { - it(`redirects through Unified Ecommerce properly if the feature flag is ${flagState ? "enabled" : "disabled"}`, () => { - const response = makeRegisterAuthResponse({ state: "success", redirect_url: "/" }) + it(`redirects through Unified Ecommerce properly if the feature flag is ${ + flagState ? "enabled" : "disabled" + }`, () => { + const response = makeRegisterAuthResponse({ + state: "success", + redirect_url: "/" + }) const handlers = [] global.SETTINGS = { unified_ecommerce_url: "http://ecommerce" } - sandbox.stub(util, 'checkFeatureFlag').returns(flagState) + sandbox.stub(util, "checkFeatureFlag").returns(flagState) - assert.equal(util.checkFeatureFlag("enable_unified_ecommerce", "anonymousUser"), flagState) + assert.equal( + util.checkFeatureFlag("enable_unified_ecommerce", "anonymousUser"), + flagState + ) handleAuthResponse(history, response, handlers) assert.equal(window.location.hostname, flagState ? "ecommerce" : "fake") }) - })}) + }) + }) }) From 75cbc697972c4572b54591ff06f72c16ec689534 Mon Sep 17 00:00:00 2001 From: James Kachel Date: Wed, 22 Jan 2025 15:15:57 -0600 Subject: [PATCH 3/4] fix app.json, add new settings to test --- app.json | 4 ++++ main/utils_test.py | 1 + 2 files changed, 5 insertions(+) diff --git a/app.json b/app.json index 4c2bf7ca6a..4bc6c0d4da 100644 --- a/app.json +++ b/app.json @@ -612,6 +612,10 @@ "description": "Token to access the status API.", "required": false }, + "UNIFIED_ECOMMERCE_URL": { + "description": "The base URL for Unified Ecommerce.", + "required": false + }, "USE_X_FORWARDED_HOST": { "description": "Set HOST header to original domain accessed by user", "required": false diff --git a/main/utils_test.py b/main/utils_test.py index 00d4ac0b18..587278446d 100644 --- a/main/utils_test.py +++ b/main/utils_test.py @@ -51,6 +51,7 @@ def test_get_js_settings(settings, rf): "features": {}, "posthog_api_token": settings.POSTHOG_PROJECT_API_KEY, "posthog_api_host": settings.POSTHOG_API_HOST, + "unified_ecommerce_url": settings.UNIFIED_ECOMMERCE_URL, } From 7a79879e17f49b87dce6eee0499f3192038ac022 Mon Sep 17 00:00:00 2001 From: James Kachel Date: Wed, 22 Jan 2025 15:24:31 -0600 Subject: [PATCH 4/4] fix flow errors --- frontend/public/src/flow/declarations.js | 3 ++- frontend/public/src/lib/auth_test.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/public/src/flow/declarations.js b/frontend/public/src/flow/declarations.js index 62fb3ce0bc..0854c85fd5 100644 --- a/frontend/public/src/flow/declarations.js +++ b/frontend/public/src/flow/declarations.js @@ -21,7 +21,8 @@ declare type Settings = { digital_credentials: boolean, digital_credentials_supported_runs: Array, posthog_api_token: ?string, - posthog_api_host: ?string + posthog_api_host: ?string, + unified_ecommerce_url: ?string, } declare var SETTINGS: Settings diff --git a/frontend/public/src/lib/auth_test.js b/frontend/public/src/lib/auth_test.js index c0de0b28f7..f55d66327b 100644 --- a/frontend/public/src/lib/auth_test.js +++ b/frontend/public/src/lib/auth_test.js @@ -57,7 +57,7 @@ describe("auth lib function", () => { state: "success", redirect_url: "/" }) - const handlers = [] + const handlers = {} global.SETTINGS = { unified_ecommerce_url: "http://ecommerce" }