@openmrs/esm-login-app 10.0.1-pre.5607 → 10.0.1-pre.5619

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-login-app",
3
- "version": "10.0.1-pre.5607",
3
+ "version": "10.0.1-pre.5619",
4
4
  "license": "MPL-2.0",
5
5
  "description": "The login microfrontend for the OpenMRS SPA",
6
6
  "browser": "dist/openmrs-esm-login-app.js",
@@ -59,7 +59,7 @@
59
59
  "@vitest/coverage-v8": "^4.1.2",
60
60
  "cross-env": "^10.1.0",
61
61
  "happy-dom": "^20.6.0",
62
- "openmrs": "10.0.1-pre.5607",
62
+ "openmrs": "10.0.1-pre.5619",
63
63
  "react": "^18.3.1",
64
64
  "react-dom": "^18.3.1",
65
65
  "react-i18next": "^16.0.0",
@@ -2,15 +2,7 @@ import { useEffect, useMemo } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import useSwrInfinite, { type SWRInfiniteResponse } from 'swr/infinite';
4
4
  import useSwrImmutable from 'swr/immutable';
5
- import {
6
- fhirBaseUrl,
7
- openmrsFetch,
8
- refetchCurrentUser,
9
- restBaseUrl,
10
- type FetchResponse,
11
- type Session,
12
- useDebounce,
13
- } from '@openmrs/esm-framework';
5
+ import { fhirBaseUrl, openmrsFetch, type FetchResponse, useDebounce } from '@openmrs/esm-framework';
14
6
  import type { LocationEntry, LocationResponse } from './types';
15
7
 
16
8
  // "swr/infinite" doesn't export InfiniteKeyedMutator directly
@@ -106,21 +98,6 @@ export function useLoginLocations(
106
98
  return memoizedLocations;
107
99
  }
108
100
 
109
- export async function performLogin(username: string, password: string): Promise<{ data: Session }> {
110
- const abortController = new AbortController();
111
- const token = window.btoa(`${username}:${password}`);
112
- const url = `${restBaseUrl}/session`;
113
-
114
- return openmrsFetch(url, {
115
- headers: {
116
- Authorization: `Basic ${token}`,
117
- },
118
- signal: abortController.signal,
119
- }).then((res) => {
120
- refetchCurrentUser();
121
- return res;
122
- });
123
- }
124
101
  export function useValidateLocationUuid(userPreferredLocationUuid: string) {
125
102
  const url = userPreferredLocationUuid ? `${fhirBaseUrl}/Location?_id=${userPreferredLocationUuid}` : null;
126
103
  const { data, error, isLoading } = useSwrImmutable<FetchResponse<LocationResponse>>(url, openmrsFetch, {
@@ -1,19 +1,10 @@
1
1
  import { mutate } from 'swr';
2
- import { clearCurrentUser, openmrsFetch, refetchCurrentUser, restBaseUrl } from '@openmrs/esm-framework';
2
+ import { logout } from '@openmrs/esm-framework/src/internal';
3
3
 
4
4
  export async function performLogout() {
5
- await openmrsFetch(`${restBaseUrl}/session`, {
6
- method: 'DELETE',
7
- });
5
+ await logout();
8
6
 
9
7
  // clear the SWR cache on logout, do not revalidate
10
8
  // taken from the SWR docs
11
9
  mutate(() => true, undefined, { revalidate: false });
12
-
13
- clearCurrentUser();
14
- try {
15
- await refetchCurrentUser();
16
- } catch (_) {
17
- // do nothing, silence the user-visible error
18
- }
19
10
  }
@@ -2,33 +2,23 @@ import React from 'react';
2
2
  import { mutate } from 'swr';
3
3
  import { beforeEach, describe, expect, it, vi } from 'vitest';
4
4
  import { render, waitFor } from '@testing-library/react';
5
- import {
6
- type FetchResponse,
7
- type Session,
8
- clearCurrentUser,
9
- navigate,
10
- openmrsFetch,
11
- restBaseUrl,
12
- setUserLanguage,
13
- useConfig,
14
- useSession,
15
- } from '@openmrs/esm-framework';
5
+ import { type Session, navigate, setUserLanguage, useConfig, useSession } from '@openmrs/esm-framework';
6
+ import { logout } from '@openmrs/esm-framework/src/internal';
16
7
  import RedirectLogout from './redirect-logout.component';
17
8
 
18
9
  vi.mock('swr', () => ({
19
10
  mutate: vi.fn(),
20
11
  }));
21
12
 
22
- const mockClearCurrentUser = vi.mocked(clearCurrentUser);
13
+ const mockLogout = vi.mocked(logout);
23
14
  const mockNavigate = vi.mocked(navigate);
24
- const mockOpenmrsFetch = vi.mocked(openmrsFetch);
25
15
  const mockSetUserLanguage = vi.mocked(setUserLanguage);
26
16
  const mockUseConfig = vi.mocked(useConfig);
27
17
  const mockUseSession = vi.mocked(useSession);
28
18
 
29
19
  describe('RedirectLogout', () => {
30
20
  beforeEach(() => {
31
- mockOpenmrsFetch.mockResolvedValue({} as FetchResponse<unknown>);
21
+ mockLogout.mockResolvedValue(undefined);
32
22
 
33
23
  mockUseSession.mockReturnValue({
34
24
  authenticated: true,
@@ -47,13 +37,10 @@ describe('RedirectLogout', () => {
47
37
  it('should redirect to login page upon logout', async () => {
48
38
  render(<RedirectLogout />);
49
39
 
50
- expect(mockOpenmrsFetch).toHaveBeenCalledWith(`${restBaseUrl}/session`, {
51
- method: 'DELETE',
52
- });
40
+ expect(mockLogout).toHaveBeenCalled();
53
41
 
54
42
  await waitFor(() => expect(mutate).toHaveBeenCalled());
55
43
 
56
- expect(mockClearCurrentUser).toHaveBeenCalled();
57
44
  expect(mockSetUserLanguage).toHaveBeenCalledWith({
58
45
  locale: 'km',
59
46
  authenticated: false,
@@ -71,13 +58,10 @@ describe('RedirectLogout', () => {
71
58
 
72
59
  render(<RedirectLogout />);
73
60
 
74
- expect(mockOpenmrsFetch).toHaveBeenCalledWith(`${restBaseUrl}/session`, {
75
- method: 'DELETE',
76
- });
61
+ expect(mockLogout).toHaveBeenCalled();
77
62
 
78
63
  await waitFor(() => expect(mutate).toHaveBeenCalled());
79
64
 
80
- expect(mockClearCurrentUser).toHaveBeenCalled();
81
65
  expect(mockSetUserLanguage).toHaveBeenCalledWith({
82
66
  locale: 'km',
83
67
  authenticated: false,
@@ -98,7 +82,7 @@ describe('RedirectLogout', () => {
98
82
 
99
83
  it('should handle logout failure gracefully', async () => {
100
84
  const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
101
- mockOpenmrsFetch.mockRejectedValue(new Error('Logout failed'));
85
+ mockLogout.mockRejectedValue(new Error('Logout failed'));
102
86
 
103
87
  render(<RedirectLogout />);
104
88
 
@@ -4,13 +4,12 @@ import { useTranslation } from 'react-i18next';
4
4
  import { Button, Checkbox } from '@carbon/react';
5
5
  import {
6
6
  useConfig,
7
- openmrsFetch,
8
- refetchCurrentUser,
9
7
  navigate as openmrsNavigate,
10
8
  ArrowRightIcon,
11
9
  OpenmrsFetchError,
12
10
  ArrowLeftIcon,
13
11
  } from '@openmrs/esm-framework';
12
+ import { verifyTotpCode } from '@openmrs/esm-framework/src/internal';
14
13
  import type { ConfigSchema } from '../../config-schema';
15
14
  import VerificationCodeInput from './verification-code-input.component';
16
15
  import LoginPageWrapper from '../../login-page-wrapper/login-page-wrapper.component';
@@ -33,20 +32,10 @@ const TotpVerificationChallengePage: React.FC = () => {
33
32
  setIsVerifying(true);
34
33
  setVerificationError('');
35
34
 
36
- const sessionUrl = rememberDevice ? '/ws/rest/v1/session?rememberMe=true' : '/ws/rest/v1/session';
35
+ const session = await verifyTotpCode(code, rememberDevice);
37
36
 
38
- const response = await openmrsFetch(sessionUrl, {
39
- method: 'GET',
40
- headers: {
41
- 'X-Totp-Code': code,
42
- },
43
- });
44
-
45
- if (response.data && response.data.authenticated) {
46
- const sessionStore = await refetchCurrentUser();
47
- const session = sessionStore?.session;
48
-
49
- if (session?.sessionLocation) {
37
+ if (session?.authenticated) {
38
+ if (session.sessionLocation) {
50
39
  let to = loginLinks?.loginSuccess || '/home';
51
40
  const referrer = location?.state?.referrer || sessionStorage.getItem('loginReferrer');
52
41
 
@@ -3,14 +3,8 @@ import { beforeEach, describe, vi, it, expect } from 'vitest';
3
3
  import userEvent from '@testing-library/user-event';
4
4
  import { render, screen } from '@testing-library/react';
5
5
  import { MemoryRouter } from 'react-router-dom';
6
- import {
7
- useConfig,
8
- openmrsFetch,
9
- type FetchResponse,
10
- OpenmrsFetchError,
11
- refetchCurrentUser,
12
- navigate as openmrsNavigate,
13
- } from '@openmrs/esm-framework';
6
+ import { useConfig, type Session, navigate as openmrsNavigate } from '@openmrs/esm-framework';
7
+ import { verifyTotpCode } from '@openmrs/esm-framework/src/internal';
14
8
  import type { ConfigSchema } from '../../config-schema';
15
9
  import TotpVerificationChallengePage from './totp-verification-challenge-page.component';
16
10
 
@@ -20,8 +14,7 @@ vi.mock('@openmrs/esm-framework', async () => {
20
14
  ...actual,
21
15
  useConfig: vi.fn(),
22
16
  interpolateUrl: vi.fn(),
23
- openmrsFetch: vi.fn(),
24
- refetchCurrentUser: vi.fn(),
17
+ verifyTotpCode: vi.fn(),
25
18
  navigate: vi.fn(),
26
19
  };
27
20
  });
@@ -67,10 +60,8 @@ describe('TotpVerificationChallengePage', () => {
67
60
  expect(verifyButton).toBeDisabled();
68
61
  });
69
62
 
70
- it('should append rememberMe=true to the request only when the checkbox is checked', async () => {
71
- vi.mocked(openmrsFetch).mockResolvedValue({
72
- data: { authenticated: true },
73
- } as unknown as FetchResponse<unknown>);
63
+ it('should ask the server to remember the device only when the checkbox is checked', async () => {
64
+ vi.mocked(verifyTotpCode).mockResolvedValue({ authenticated: true } as Session);
74
65
 
75
66
  const { user } = setup();
76
67
  const inputs = screen.getAllByRole('textbox');
@@ -81,27 +72,21 @@ describe('TotpVerificationChallengePage', () => {
81
72
  const verifyButton = screen.getByRole('button', { name: /verify/i });
82
73
  const rememberMeCheckbox = screen.getByLabelText(/Remember this device/i);
83
74
  await user.click(verifyButton);
84
- expect(openmrsFetch).toHaveBeenCalledWith(
85
- '/ws/rest/v1/session?rememberMe=true',
86
- expect.objectContaining({ method: 'GET' }),
87
- );
75
+ expect(verifyTotpCode).toHaveBeenLastCalledWith('123456', true);
88
76
 
89
77
  await user.click(rememberMeCheckbox);
90
78
  expect(rememberMeCheckbox).not.toBeChecked();
91
79
 
92
80
  await user.click(verifyButton);
93
- expect(openmrsFetch).toHaveBeenCalledWith('/ws/rest/v1/session', expect.objectContaining({ method: 'GET' }));
81
+ expect(verifyTotpCode).toHaveBeenLastCalledWith('123456', false);
94
82
  });
95
83
 
96
84
  it('should use the original referrer from sessionStorage and clear it after successful verification', async () => {
97
85
  sessionStorage.setItem('loginReferrer', '/patient-chart');
98
- vi.mocked(openmrsFetch).mockResolvedValue({
99
- data: { authenticated: true },
100
- } as unknown as FetchResponse<unknown>);
101
-
102
- vi.mocked(refetchCurrentUser).mockResolvedValue({
103
- session: { sessionLocation: { uuid: 'location-123', display: 'Pharmacy' } },
104
- } as any);
86
+ vi.mocked(verifyTotpCode).mockResolvedValue({
87
+ authenticated: true,
88
+ sessionLocation: { uuid: 'location-123', display: 'Pharmacy' },
89
+ } as Session);
105
90
 
106
91
  const { user } = setup();
107
92
 
@@ -116,13 +101,10 @@ describe('TotpVerificationChallengePage', () => {
116
101
  });
117
102
 
118
103
  it('should verify successfully and navigate to home if the user has a session location', async () => {
119
- vi.mocked(openmrsFetch).mockResolvedValue({
120
- data: { authenticated: true },
121
- } as unknown as FetchResponse<unknown>);
122
-
123
- vi.mocked(refetchCurrentUser).mockResolvedValue({
124
- session: { sessionLocation: { uuid: 'location-123', display: 'Pharmacy' } },
125
- } as any);
104
+ vi.mocked(verifyTotpCode).mockResolvedValue({
105
+ authenticated: true,
106
+ sessionLocation: { uuid: 'location-123', display: 'Pharmacy' },
107
+ } as Session);
126
108
 
127
109
  const { user } = setup();
128
110
 
@@ -132,15 +114,21 @@ describe('TotpVerificationChallengePage', () => {
132
114
 
133
115
  const verifyButton = screen.getByRole('button', { name: /verify/i });
134
116
  await user.click(verifyButton);
135
- expect(openmrsFetch).toHaveBeenCalledWith(
136
- expect.stringContaining('/ws/rest/v1/session'),
137
- expect.objectContaining({
138
- method: 'GET',
139
- headers: { 'X-Totp-Code': '123456' },
140
- }),
141
- );
142
-
143
- expect(refetchCurrentUser).toHaveBeenCalled();
117
+ expect(verifyTotpCode).toHaveBeenCalledWith('123456', true);
144
118
  expect(openmrsNavigate).toHaveBeenCalledWith({ to: '/home' });
145
119
  });
120
+
121
+ it('should show an error and stay on the page when the code is not accepted', async () => {
122
+ vi.mocked(verifyTotpCode).mockResolvedValue({ authenticated: false } as Session);
123
+
124
+ const { user } = setup();
125
+
126
+ const inputs = screen.getAllByRole('textbox');
127
+ await user.click(inputs[0]);
128
+ await user.paste('123456');
129
+ await user.click(screen.getByRole('button', { name: /verify/i }));
130
+
131
+ expect(screen.getByText(/We could not verify that code/i)).toBeInTheDocument();
132
+ expect(openmrsNavigate).not.toHaveBeenCalled();
133
+ });
146
134
  });