Skip to content

Commit fb6ce04

Browse files
authored
Merge branch 'main' into cve-fix-and-beta1-cleanup
2 parents 1fdee6d + 4bd4025 commit fb6ce04

File tree

4 files changed

+177
-7
lines changed

4 files changed

+177
-7
lines changed

public/apps/account/password-reset-panel.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import { FormRow } from '../configuration/utils/form-row';
3434
import { logout, updateNewPassword } from './utils';
3535
import { PASSWORD_INSTRUCTION } from '../apps-constants';
3636
import { constructErrorMessageAndLog } from '../error-utils';
37-
import { validateCurrentPassword } from '../../utils/login-utils';
37+
import {
38+
isResetButtonDisabled,
39+
validateCurrentPassword,
40+
} from '../../utils/password-reset-panel-utils';
3841
import { getDashboardsInfo } from '../../utils/dashboards-info-utils';
3942
import { PasswordStrengthBar } from '../configuration/utils/password-strength-bar';
4043

@@ -85,7 +88,8 @@ export function PasswordResetPanel(props: PasswordResetPanelProps) {
8588
await validateCurrentPassword(http, props.username, currentPassword);
8689
} catch (e) {
8790
setIsCurrentPasswordInvalid(true);
88-
setCurrentPasswordError([constructErrorMessageAndLog(e, 'Invalid current password.')]);
91+
setCurrentPasswordError([`Invalid current password. ${e.message}`]);
92+
return;
8993
}
9094

9195
// update new password
@@ -184,7 +188,11 @@ export function PasswordResetPanel(props: PasswordResetPanelProps) {
184188
<EuiSmallButton
185189
data-test-subj="reset"
186190
fill
187-
disabled={isRepeatNewPasswordInvalid}
191+
disabled={isResetButtonDisabled(
192+
currentPassword,
193+
newPassword,
194+
isRepeatNewPasswordInvalid
195+
)}
188196
onClick={handleReset}
189197
>
190198
Reset

public/apps/account/test/password-reset-panel.test.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import { shallow } from 'enzyme';
1717
import React from 'react';
18-
import { validateCurrentPassword } from '../../../utils/login-utils';
18+
import { validateCurrentPassword } from '../../../utils/password-reset-panel-utils';
1919
import { PasswordResetPanel } from '../password-reset-panel';
2020
import { logout, updateNewPassword } from '../utils';
2121

@@ -24,7 +24,8 @@ jest.mock('../utils', () => ({
2424
updateNewPassword: jest.fn(),
2525
}));
2626

27-
jest.mock('../../../utils/login-utils', () => ({
27+
jest.mock('../../../utils/password-reset-panel-utils', () => ({
28+
isResetButtonDisabled: jest.fn(),
2829
validateCurrentPassword: jest.fn(),
2930
}));
3031

@@ -71,15 +72,16 @@ describe('Account menu - Password reset panel', () => {
7172

7273
it('invalid current password', (done) => {
7374
(validateCurrentPassword as jest.Mock).mockImplementationOnce(() => {
74-
throw new Error();
75+
throw new Error('Unauthorized');
7576
});
7677

7778
// Hide the error message
7879
jest.spyOn(console, 'error').mockImplementationOnce(() => {});
7980

8081
component.find('[data-test-subj="reset"]').simulate('click');
8182
process.nextTick(() => {
82-
expect(setState).toHaveBeenCalledWith(true);
83+
expect(setState).toHaveBeenNthCalledWith(1, true);
84+
expect(setState).toHaveBeenNthCalledWith(2, ['Invalid current password. Unauthorized']);
8385
expect(setState).toBeTruthy();
8486
done();
8587
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
import { HttpStart } from 'opensearch-dashboards/public';
17+
18+
export async function validateCurrentPassword(
19+
http: HttpStart,
20+
userName: string,
21+
currentPassword: string
22+
) {
23+
const url = http.basePath.prepend('/auth/login');
24+
const res = await fetch(url, {
25+
method: 'POST',
26+
credentials: 'include',
27+
headers: {
28+
'Content-Type': 'application/json',
29+
'osd-xsrf': 'true',
30+
},
31+
body: JSON.stringify({ username: userName, password: currentPassword }),
32+
});
33+
34+
if (!res.ok) {
35+
const data = await res.json();
36+
throw new Error(data.message);
37+
}
38+
}
39+
40+
export function isResetButtonDisabled(
41+
currentPassword: string,
42+
newPassword: string,
43+
isRepeatNewPasswordInvalid: boolean
44+
): boolean {
45+
return !currentPassword || !newPassword || isRepeatNewPasswordInvalid;
46+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
import { isResetButtonDisabled, validateCurrentPassword } from '../password-reset-panel-utils';
17+
import { HttpStart } from 'opensearch-dashboards/public';
18+
19+
describe('Test Password Reset Panel Utlis', () => {
20+
const mockUrl = 'http://localhost:5601/auth/login';
21+
let http: HttpStart;
22+
beforeEach(() => {
23+
http = {
24+
basePath: {
25+
prepend: jest.fn(() => mockUrl),
26+
},
27+
} as any;
28+
global.fetch = jest.fn();
29+
});
30+
31+
it('validate current password resolves when the server returns 2xx', async () => {
32+
(global.fetch as jest.Mock).mockResolvedValue({
33+
ok: true,
34+
});
35+
36+
await expect(validateCurrentPassword(http, 'user', 'password')).resolves.toBeUndefined();
37+
38+
expect(global.fetch).toHaveBeenCalledWith(
39+
mockUrl,
40+
expect.objectContaining({
41+
method: 'POST',
42+
credentials: 'include',
43+
headers: {
44+
'Content-Type': 'application/json',
45+
'osd-xsrf': 'true',
46+
},
47+
body: JSON.stringify({ username: 'user', password: 'password' }),
48+
})
49+
);
50+
});
51+
52+
it('validate current password rejects with the server message on 4xx', async () => {
53+
const serverBody = { message: 'Unauthorized' };
54+
(global.fetch as jest.Mock).mockResolvedValue({
55+
ok: false,
56+
json: jest.fn().mockResolvedValue(serverBody),
57+
status: 401,
58+
});
59+
60+
await expect(validateCurrentPassword(http, 'user', 'incorrectpassword')).rejects.toThrow(
61+
'Unauthorized'
62+
);
63+
});
64+
});
65+
66+
describe('Validates password reset button state based on input conditions', () => {
67+
const testScenarios = [
68+
{
69+
description: 'should be disabled when currentPassword is empty',
70+
currentPassword: '',
71+
newPassword: 'newpass',
72+
isRepeatNewPasswordInvalid: false,
73+
expected: true,
74+
},
75+
{
76+
description: 'should be disabled when newPassword is empty',
77+
currentPassword: 'current',
78+
newPassword: '',
79+
isRepeatNewPasswordInvalid: false,
80+
expected: true,
81+
},
82+
{
83+
description: 'should be disabled when isRepeatNewPasswordInvalid is true',
84+
currentPassword: 'current',
85+
newPassword: 'newpass',
86+
isRepeatNewPasswordInvalid: true,
87+
expected: true,
88+
},
89+
{
90+
description: 'should be enabled when all conditions are valid',
91+
currentPassword: 'current',
92+
newPassword: 'newpass',
93+
isRepeatNewPasswordInvalid: false,
94+
expected: false,
95+
},
96+
{
97+
description: 'should be disabled when all fields are invalid',
98+
currentPassword: '',
99+
newPassword: '',
100+
isRepeatNewPasswordInvalid: true,
101+
expected: true,
102+
},
103+
] as const;
104+
105+
it.each(testScenarios)('$description', async function ({
106+
currentPassword,
107+
newPassword,
108+
isRepeatNewPasswordInvalid,
109+
expected,
110+
}) {
111+
const result = isResetButtonDisabled(currentPassword, newPassword, isRepeatNewPasswordInvalid);
112+
expect(result).toBe(expected);
113+
});
114+
});

0 commit comments

Comments
 (0)