diff --git a/package-lock.json b/package-lock.json index 43b7de3db..7c4e84512 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "formik": "2.2.9", "history": "4.10.1", "iso-639-1": "2.1.4", + "jwt-decode": "3.1.2", "lodash.camelcase": "4.3.0", "lodash.capitalize": "4.2.1", "lodash.clonedeep": "4.5.0", diff --git a/package.json b/package.json index 1f61d052c..473b21d15 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "formik": "2.2.9", "history": "4.10.1", "iso-639-1": "2.1.4", + "jwt-decode": "3.1.2", "lodash.camelcase": "4.3.0", "lodash.capitalize": "4.2.1", "lodash.clonedeep": "4.5.0", diff --git a/src/components/enterprise-user-subsidy/data/hooks/hooks.js b/src/components/enterprise-user-subsidy/data/hooks/hooks.js index 9069ed3d9..e36ec8952 100644 --- a/src/components/enterprise-user-subsidy/data/hooks/hooks.js +++ b/src/components/enterprise-user-subsidy/data/hooks/hooks.js @@ -2,8 +2,11 @@ import { useState, useEffect, useReducer, useCallback, useMemo, } from 'react'; import { logError } from '@edx/frontend-platform/logging'; +import { getConfig } from '@edx/frontend-platform/config'; import { camelCaseObject } from '@edx/frontend-platform/utils'; import { useQuery } from '@tanstack/react-query'; +import jwtDecode from 'jwt-decode'; +import Cookies from 'universal-cookie'; import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils'; import { fetchCouponCodeAssignments } from '../../coupons'; @@ -104,6 +107,20 @@ export function useSubscriptionLicense({ enterpriseIdentityProvider: enterpriseConfig.identityProvider, }), [enterpriseConfig]); + const decodeJwtCookie = () => { + const cookies = new Cookies(); + const cookieValue = cookies.get(getConfig().ACCESS_TOKEN_COOKIE_NAME); + if (!cookieValue) { return null; } + try { + return jwtDecode(cookieValue); + } catch (e) { + const error = Object.create(e); + error.message = '[useSubscriptionLicense] Error decoding JWT token'; + error.customAttributes = { cookieValue }; + throw error; + } + }; + useEffect(() => { async function retrieveUserLicense() { let result = await fetchExistingUserLicense(enterpriseId); @@ -118,9 +135,11 @@ export function useSubscriptionLicense({ ]; const hasCustomerAgreementData = customerAgreementMetadata.every(item => !!item); - // Only request an auto-applied license if ther user is a learner of the enterprise. + const decodedJwt = decodeJwtCookie(); + + // Only request an auto-applied license if the user is a learner of the enterprise. // This is mainly to prevent edx operators from accidently getting a license. - const isEnterpriseLearner = !!user.roles.find(userRole => { + const isEnterpriseLearner = !!decodedJwt.roles.find(userRole => { const [role, enterprise] = userRole.split(':'); return role === 'enterprise_learner' && enterprise === enterpriseId; }); diff --git a/src/components/enterprise-user-subsidy/data/hooks/hooks.test.jsx b/src/components/enterprise-user-subsidy/data/hooks/hooks.test.jsx index 7972b3dc6..9d94d673f 100644 --- a/src/components/enterprise-user-subsidy/data/hooks/hooks.test.jsx +++ b/src/components/enterprise-user-subsidy/data/hooks/hooks.test.jsx @@ -2,6 +2,7 @@ import { renderHook } from '@testing-library/react-hooks'; import * as logging from '@edx/frontend-platform/logging'; import { camelCaseObject } from '@edx/frontend-platform/utils'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import Cookies from 'universal-cookie'; import { useCouponCodes, @@ -20,6 +21,8 @@ import { fetchCouponsOverview } from '../../coupons/data/service'; import { fetchCouponCodeAssignments } from '../../coupons'; import { LICENSE_STATUS } from '../constants'; +jest.mock('universal-cookie'); + jest.mock('../../data/service'); jest.mock('../../coupons/data/service'); jest.mock('../../coupons'); @@ -38,7 +41,7 @@ jest.mock('../../../../config', () => ({ const TEST_SUBSCRIPTION_UUID = 'test-subscription-uuid'; const TEST_LICENSE_UUID = 'test-license-uuid'; -const TEST_ENTERPRISE_UUID = 'test-enterprise-uuid'; +const TEST_ENTERPRISE_UUID = 'd0224477-4903-404a-b3c4-9edfce728d37'; const TEST_USER_ID = '35'; const TEST_ACTIVATION_KEY = 'test-activation-key'; @@ -79,8 +82,14 @@ const mockCustomerAgreement = { }; describe('useSubscriptionLicense', () => { + beforeEach(() => { + const mockJwt = 'eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTY5NzcyMDYyMywiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNjk3NzE3MDIzLCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJyaWNrIiwic2NvcGVzIjpbInVzZXJfaWQiLCJlbWFpbCIsInByb2ZpbGUiXSwidmVyc2lvbiI6IjEuMi4wIiwic3ViIjoiNTY0NDQ4NjIwMzMwNjJiMGQzZjhlYjIxMzljYTM2NGYiLCJmaWx0ZXJzIjpbInVzZXI6bWUiXSwiaXNfcmVzdHJpY3RlZCI6ZmFsc2UsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJ1c2VyX2lkIjo1MjcwODc4LCJlbWFpbCI6InJzYW5jaGV6QHNhbWx0ZXN0LmlkIiwibmFtZSI6IlRlc3QgUmljayIsImZhbWlseV9uYW1lIjoiU2FuY2hleiIsImdpdmVuX25hbWUiOiJSaWNrIiwiYWRtaW5pc3RyYXRvciI6ZmFsc2UsInN1cGVydXNlciI6ZmFsc2UsInJvbGVzIjpbImVudGVycHJpc2VfbGVhcm5lcjpkMDIyNDQ3Ny00OTAzLTQwNGEtYjNjNC05ZWRmY2U3MjhkMzciXX0'; + const mockGetCookie = jest.fn().mockReturnValue(mockJwt); + Cookies.mockReturnValue({ get: mockGetCookie }); + }); + afterEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); }); it('does nothing if customer agreement is still loading', async () => {