Skip to content

Commit 3a1a820

Browse files
authored
feat: refresh jwt on navigate for admin register page (#1714)
1 parent 50158c8 commit 3a1a820

File tree

4 files changed

+73
-4
lines changed

4 files changed

+73
-4
lines changed

src/components/AdminRegisterPage/AdminRegisterPage.test.jsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ const mockEnterpriseCustomer = {
1818
uuid: 'dc3bfcf8-c61f-11ec-9d64-0242ac120002',
1919
};
2020

21+
const mockLoginRefreshResponse = {
22+
data: {
23+
access_token: 'mock-access-token',
24+
refresh_token: 'mock-refresh-token',
25+
},
26+
};
27+
2128
const mockNavigate = jest.fn();
2229
jest.mock('react-router-dom', () => ({
2330
...jest.requireActual('react-router-dom'),
@@ -66,6 +73,9 @@ describe('<AdminRegisterPage />', () => {
6673
roles,
6774
});
6875
isEnterpriseUser.mockReturnValue(false);
76+
LmsApiService.loginRefresh.mockImplementation(() => Promise.resolve({
77+
data: mockLoginRefreshResponse,
78+
}));
6979
LmsApiService.fetchEnterpriseBySlug.mockImplementation(() => Promise.resolve({
7080
data: mockEnterpriseCustomer,
7181
}));
@@ -81,6 +91,9 @@ describe('<AdminRegisterPage />', () => {
8191
roles: ['enterprise_admin:dc3bfcf8-c61f-11ec-9d64-0242ac120002'],
8292
});
8393
isEnterpriseUser.mockReturnValue(true);
94+
LmsApiService.loginRefresh.mockImplementation(() => Promise.resolve({
95+
data: mockLoginRefreshResponse,
96+
}));
8497
LmsApiService.fetchEnterpriseBySlug.mockImplementation(() => Promise.resolve({
8598
data: mockEnterpriseCustomer,
8699
}));

src/components/AdminRegisterPage/index.jsx

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,59 @@ import { logError } from '@edx/frontend-platform/logging';
44
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
55
import { LoginRedirect, getProxyLoginUrl } from '@edx/frontend-enterprise-logistration';
66
import { isEnterpriseUser, ENTERPRISE_ADMIN } from '@edx/frontend-enterprise-utils';
7+
import { v5 as uuidv5 } from 'uuid';
78

89
import EnterpriseAppSkeleton from '../EnterpriseApp/EnterpriseAppSkeleton';
910
import LmsApiService from '../../data/services/LmsApiService';
1011

12+
/**
13+
* AdminRegisterPage is a React component that manages the registration process for enterprise administrators.
14+
*
15+
* The component:
16+
* - Redirects unauthenticated users to the enterprise proxy login flow.
17+
* - For authenticated users:
18+
* - Checks if they have the `enterprise_admin` JWT role associated with the specified enterprise.
19+
* - Redirects users with the `enterprise_admin` role to the account activation page.
20+
* - Redirects other authenticated users to the proxy login URL to refresh their JWT cookie
21+
* and retrieve updated role assignments.
22+
* - Fetches enterprise information by slug and processes the authenticated enterprise admin state.
23+
* - Ensures that users visiting the register page for the first time will have the page reloaded
24+
* for proper initialization.
25+
*
26+
* Dependencies:
27+
* - `getAuthenticatedUser`: Retrieves the currently authenticated user.
28+
* - `useParams`: React Router hook used to extract route parameters.
29+
* - `useNavigate`: React Router hook for programmatically navigating to different routes.
30+
* - `LmsApiService.fetchEnterpriseBySlug`: Fetches enterprise details by their slug.
31+
* - `LmsApiService.loginRefresh`: Refreshes user login session and retrieves user details.
32+
* - `isEnterpriseUser`: Validates if a user has a specific role within an enterprise.
33+
* - `getProxyLoginUrl`: Constructs a URL for redirecting to the enterprise proxy login flow.
34+
* - `uuidv5`: Used to generate a unique identifier for storage purposes.
35+
* - `EnterpriseAppSkeleton`: Component displayed as a loading or placeholder state.
36+
* - `LoginRedirect`: Component to handle login redirection with a loading display.
37+
*
38+
* Side Effects:
39+
* - Utilizes `useEffect` to handle asynchronous data fetching, user authentication validation, and navigation.
40+
* - Stores a flag in `localStorage` to identify if a user is visiting the register page for the first time.
41+
* - Logs errors encountered during the asynchronous operations.
42+
*
43+
* Returns:
44+
* - If the user is not authenticated, renders the `LoginRedirect` component for managing redirection
45+
* to the proxy login flow.
46+
* - If the user is authenticated, renders the `EnterpriseAppSkeleton` as a placeholder or loading
47+
* state during processing.
48+
*/
1149
const AdminRegisterPage = () => {
1250
const user = getAuthenticatedUser();
1351
const { enterpriseSlug } = useParams();
1452
const navigate = useNavigate();
15-
1653
useEffect(() => {
1754
if (!user) {
1855
return;
1956
}
2057
const processEnterpriseAdmin = (enterpriseUUID) => {
21-
const isEnterpriseAdmin = isEnterpriseUser(user, ENTERPRISE_ADMIN, enterpriseUUID);
58+
const authenticatedUser = getAuthenticatedUser();
59+
const isEnterpriseAdmin = isEnterpriseUser(authenticatedUser, ENTERPRISE_ADMIN, enterpriseUUID);
2260
if (isEnterpriseAdmin) {
2361
// user is authenticated and has the ``enterprise_admin`` JWT role, so redirect user to
2462
// account activation page to ensure they verify their email address.
@@ -40,7 +78,18 @@ const AdminRegisterPage = () => {
4078
logError(error);
4179
}
4280
};
43-
getEnterpriseBySlug();
81+
// Force a fetch of a new JWT token and reload the page prior to any redirects.
82+
// Importantly, only reload the page on first visit, or else risk infinite reloads.
83+
LmsApiService.loginRefresh().then(data => {
84+
const obfuscatedId = uuidv5(String(data.userId), uuidv5.DNS);
85+
const storageKey = `first_visit_register_page_${obfuscatedId}`;
86+
if (!localStorage.getItem(storageKey)) {
87+
localStorage.setItem(storageKey, 'true');
88+
window.location.reload();
89+
}
90+
return data;
91+
}).catch(error => logError(error));
92+
getEnterpriseBySlug().catch(error => logError(error));
4493
}, [user, navigate, enterpriseSlug]);
4594

4695
if (!user) {

src/components/UserActivationPage/index.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ const UserActivationPage = () => {
7272
</>
7373
);
7474
}
75-
7675
// user data is hydrated with an unverified email address, so display a warning message since
7776
// they have not yet verified their email via the "Activate your account" flow, so we should
7877
// prevent access to the Admin Portal.

src/data/services/LmsApiService.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ class LmsApiService {
108108

109109
static enterpriseLearnerUrl = `${LmsApiService.baseUrl}/enterprise/api/v1/enterprise-learner/`;
110110

111+
static loginRefreshUrl = `${LmsApiService.baseUrl}/login_refresh`;
112+
111113
static async createEnterpriseGroup(
112114
{
113115
groupName,
@@ -638,6 +640,12 @@ class LmsApiService {
638640
response.data = camelCaseObject(response.data);
639641
return response;
640642
};
643+
644+
static loginRefresh = async () => {
645+
const url = LmsApiService.loginRefreshUrl;
646+
const response = await LmsApiService.apiClient().post(url);
647+
return camelCaseObject(response.data);
648+
};
641649
}
642650

643651
export default LmsApiService;

0 commit comments

Comments
 (0)