From 82205cc3aa9d539133af3dd042efb5a8d39d25f6 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 26 Dec 2024 00:24:37 +0000 Subject: [PATCH 1/6] Add page --- src/frontend/src/enums/ApiEndpoints.tsx | 1 + .../src/pages/Auth/ChangePassword.tsx | 119 ++++++++++++++++++ .../AccountSettings/AccountDetailPanel.tsx | 12 +- src/frontend/src/router.tsx | 6 + 4 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 src/frontend/src/pages/Auth/ChangePassword.tsx diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index 0206dcd55ff8..8c0ec8fe38e3 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -18,6 +18,7 @@ export enum ApiEndpoints { user_simple_login = 'email/generate/', user_reset = 'auth/password/reset/', user_reset_set = 'auth/password/reset/confirm/', + user_change_password = 'auth/password/change/', user_sso = 'auth/social/', user_sso_remove = 'auth/social/:id/disconnect/', user_emails = 'auth/emails/', diff --git a/src/frontend/src/pages/Auth/ChangePassword.tsx b/src/frontend/src/pages/Auth/ChangePassword.tsx new file mode 100644 index 000000000000..815a35a28716 --- /dev/null +++ b/src/frontend/src/pages/Auth/ChangePassword.tsx @@ -0,0 +1,119 @@ +import { Trans, t } from '@lingui/macro'; +import { + Button, + Center, + Container, + Divider, + Group, + Paper, + PasswordInput, + Stack, + Text +} from '@mantine/core'; +import { useForm } from '@mantine/form'; +import { notifications } from '@mantine/notifications'; +import { useNavigate } from 'react-router-dom'; + +import { api } from '../../App'; +import { StylishText } from '../../components/items/StylishText'; +import { LanguageContext } from '../../contexts/LanguageContext'; +import { ApiEndpoints } from '../../enums/ApiEndpoints'; +import { apiUrl } from '../../states/ApiState'; +import { useUserState } from '../../states/UserState'; + +export default function Set_Password() { + const simpleForm = useForm({ + initialValues: { + new_password1: '', + new_password2: '' + } + }); + + const user = useUserState(); + const navigate = useNavigate(); + + function passwordError(values: any) { + let message: any = + values?.new_password2 || + values?.new_password1 || + values?.error || + t`Password could not be changed`; + + // If message is array + if (!Array.isArray(message)) { + message = [message]; + } + + message.forEach((msg: string) => { + notifications.show({ + title: t`Error`, + message: msg, + color: 'red' + }); + }); + } + + function handleSet() { + // Set password with call to backend + api + .post(apiUrl(ApiEndpoints.user_change_password), { + new_password1: simpleForm.values.new_password1, + new_password2: simpleForm.values.new_password2 + }) + .then((val) => { + if (val.status === 200) { + notifications.show({ + title: t`Password Changed`, + message: t`The password was set successfully. You can now login with your new password`, + color: 'green', + autoClose: false + }); + navigate('/login'); + } else { + passwordError(val.data); + } + }) + .catch((err) => { + passwordError(err.response.data); + }); + } + + return ( + +
+ + + {t`Reset Password`} + + {user.username() && ( + + + {t`User`} + {user.username()} + + + )} + + + + + + + + +
+
+ ); +} diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/AccountDetailPanel.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/AccountDetailPanel.tsx index fcbda4e262a9..a124a0ac14de 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/AccountDetailPanel.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/AccountDetailPanel.tsx @@ -3,15 +3,17 @@ import { Group, Stack, Table, Title } from '@mantine/core'; import { IconKey, IconUser } from '@tabler/icons-react'; import { useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import { YesNoUndefinedButton } from '../../../../components/buttons/YesNoButton'; import type { ApiFormFieldSet } from '../../../../components/forms/fields/ApiFormField'; import { ActionDropdown } from '../../../../components/items/ActionDropdown'; import { ApiEndpoints } from '../../../../enums/ApiEndpoints'; -import { notYetImplemented } from '../../../../functions/notifications'; import { useEditApiFormModal } from '../../../../hooks/UseForm'; import { useUserState } from '../../../../states/UserState'; export function AccountDetailPanel() { + const navigate = useNavigate(); + const [user, fetchUserState] = useUserState((state) => [ state.user, state.fetchUserState @@ -51,10 +53,12 @@ export function AccountDetailPanel() { onClick: editUser.open }, { - name: t`Set Password`, + name: t`Change Password`, icon: , - tooltip: t`Set User Password`, - onClick: notYetImplemented + tooltip: t`Change User Password`, + onClick: () => { + navigate('/change-password'); + } } ]} /> diff --git a/src/frontend/src/router.tsx b/src/frontend/src/router.tsx index c444f9afeb25..0b0089b7cef3 100644 --- a/src/frontend/src/router.tsx +++ b/src/frontend/src/router.tsx @@ -107,6 +107,11 @@ export const Login = Loadable(lazy(() => import('./pages/Auth/Login'))); export const Logout = Loadable(lazy(() => import('./pages/Auth/Logout'))); export const Logged_In = Loadable(lazy(() => import('./pages/Auth/Logged-In'))); export const Reset = Loadable(lazy(() => import('./pages/Auth/Reset'))); + +export const ChangePassword = Loadable( + lazy(() => import('./pages/Auth/ChangePassword')) +); + export const Set_Password = Loadable( lazy(() => import('./pages/Auth/Set-Password')) ); @@ -169,6 +174,7 @@ export const routes = ( } /> } /> } /> + } /> ); From 40df66efa4c94ce99487af1f6d20c3bae86c6061 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 26 Dec 2024 00:25:41 +0000 Subject: [PATCH 2/6] Rename Set-Password to ResetPassword --- .../src/pages/Auth/{Set-Password.tsx => ResetPassword.tsx} | 2 +- src/frontend/src/router.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/frontend/src/pages/Auth/{Set-Password.tsx => ResetPassword.tsx} (98%) diff --git a/src/frontend/src/pages/Auth/Set-Password.tsx b/src/frontend/src/pages/Auth/ResetPassword.tsx similarity index 98% rename from src/frontend/src/pages/Auth/Set-Password.tsx rename to src/frontend/src/pages/Auth/ResetPassword.tsx index 31f0b4aa1952..ac1d5a136229 100644 --- a/src/frontend/src/pages/Auth/Set-Password.tsx +++ b/src/frontend/src/pages/Auth/ResetPassword.tsx @@ -17,7 +17,7 @@ import { LanguageContext } from '../../contexts/LanguageContext'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { apiUrl } from '../../states/ApiState'; -export default function Set_Password() { +export default function ResetPassword() { const simpleForm = useForm({ initialValues: { password: '' } }); const [searchParams] = useSearchParams(); const navigate = useNavigate(); diff --git a/src/frontend/src/router.tsx b/src/frontend/src/router.tsx index 0b0089b7cef3..1eff80d49d16 100644 --- a/src/frontend/src/router.tsx +++ b/src/frontend/src/router.tsx @@ -112,8 +112,8 @@ export const ChangePassword = Loadable( lazy(() => import('./pages/Auth/ChangePassword')) ); -export const Set_Password = Loadable( - lazy(() => import('./pages/Auth/Set-Password')) +export const ResetPassword = Loadable( + lazy(() => import('./pages/Auth/ResetPassword')) ); // Routes @@ -173,7 +173,7 @@ export const routes = ( } />, } /> } /> - } /> + } /> } /> From e0447567b0e5a69e26a19cc0ebede4f4886fa5b4 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 26 Dec 2024 11:40:11 +0000 Subject: [PATCH 3/6] Add unit testing --- .../src/pages/Auth/ChangePassword.tsx | 2 + src/frontend/tests/pui_login.spec.ts | 41 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/frontend/src/pages/Auth/ChangePassword.tsx b/src/frontend/src/pages/Auth/ChangePassword.tsx index 815a35a28716..7f7fb55e2ed3 100644 --- a/src/frontend/src/pages/Auth/ChangePassword.tsx +++ b/src/frontend/src/pages/Auth/ChangePassword.tsx @@ -97,12 +97,14 @@ export default function Set_Password() { { await page.waitForTimeout(2500); }); + +test('Login - Change Password', async ({ page }) => { + await doQuickLogin(page, 'noaccess', 'youshallnotpass'); + + // Navigate to the 'change password' page + await page.goto(`${baseUrl}/settings/user/account`); + await page.getByLabel('action-menu-user-actions').click(); + await page.getByLabel('action-menu-user-actions-change-password').click(); + + // First attempt with some errors + await page.getByLabel('input-password-1').fill('12345'); + await page.getByLabel('input-password-2').fill('54321'); + await page.getByRole('button', { name: 'Confirm' }).click(); + await page.getByText('The two password fields didn’t match').waitFor(); + + await page.getByLabel('input-password-2').fill('12345'); + await page.getByRole('button', { name: 'Confirm' }).click(); + + await page.getByText('This password is too short').waitFor(); + await page.getByText('This password is entirely numeric').waitFor(); + + await page.getByLabel('input-password-1').fill('yeshallnotpass'); + await page.getByLabel('input-password-2').fill('yeshallnotpass'); + await page.getByRole('button', { name: 'Confirm' }).click(); + + // Logout the user + await page.goto(`${baseUrl}/logout/`); + + await page.getByLabel('login-username').fill('noaccess'); + await page.getByLabel('login-password').fill('yeshallnotpass'); + await page.getByRole('button', { name: 'Log In' }).click(); + + // Change the password back to the original + await page.goto(`${baseUrl}/change-password`); + + await page.getByLabel('input-password-1').fill('youshallnotpass'); + await page.getByLabel('input-password-2').fill('youshallnotpass'); + await page.getByRole('button', { name: 'Confirm' }).click(); + + await page.waitForTimeout(2500); +}); From d5329a5185d43a2f65cf3ef6004a0d90b98f1161 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 26 Dec 2024 12:14:52 +0000 Subject: [PATCH 4/6] Ensure user is properly logged into page --- .../src/pages/Auth/ChangePassword.tsx | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/src/frontend/src/pages/Auth/ChangePassword.tsx b/src/frontend/src/pages/Auth/ChangePassword.tsx index 7f7fb55e2ed3..6b6a2b96cd3c 100644 --- a/src/frontend/src/pages/Auth/ChangePassword.tsx +++ b/src/frontend/src/pages/Auth/ChangePassword.tsx @@ -16,6 +16,7 @@ import { useNavigate } from 'react-router-dom'; import { api } from '../../App'; import { StylishText } from '../../components/items/StylishText'; +import { ProtectedRoute } from '../../components/nav/Layout'; import { LanguageContext } from '../../contexts/LanguageContext'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { apiUrl } from '../../states/ApiState'; @@ -80,42 +81,44 @@ export default function Set_Password() { return ( -
- - - {t`Reset Password`} - - {user.username() && ( - - - {t`User`} - {user.username()} - - - )} - - - - + +
+ + + {t`Reset Password`} + + {user.username() && ( + + + {t`User`} + {user.username()} + + + )} + + + + + + - - - -
+
+
+
); } From f816df7af356a6c54e5b98da0cc732ed34fcf95e Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 27 Dec 2024 09:11:10 +1100 Subject: [PATCH 5/6] Update playwright tests --- src/frontend/tests/pui_login.spec.ts | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/frontend/tests/pui_login.spec.ts b/src/frontend/tests/pui_login.spec.ts index ffdc49e042ef..11add3145930 100644 --- a/src/frontend/tests/pui_login.spec.ts +++ b/src/frontend/tests/pui_login.spec.ts @@ -116,23 +116,16 @@ test('Login - Change Password', async ({ page }) => { await page.getByText('This password is too short').waitFor(); await page.getByText('This password is entirely numeric').waitFor(); - await page.getByLabel('input-password-1').fill('yeshallnotpass'); - await page.getByLabel('input-password-2').fill('yeshallnotpass'); - await page.getByRole('button', { name: 'Confirm' }).click(); - - // Logout the user - await page.goto(`${baseUrl}/logout/`); - - await page.getByLabel('login-username').fill('noaccess'); - await page.getByLabel('login-password').fill('yeshallnotpass'); - await page.getByRole('button', { name: 'Log In' }).click(); - - // Change the password back to the original - await page.goto(`${baseUrl}/change-password`); - await page.getByLabel('input-password-1').fill('youshallnotpass'); await page.getByLabel('input-password-2').fill('youshallnotpass'); await page.getByRole('button', { name: 'Confirm' }).click(); - await page.waitForTimeout(2500); + await page.getByText('Password Changed').waitFor(); + await page.getByText('The password was set successfully').waitFor(); + + // Should have redirected to the index page + await page.waitForURL('**/platform/home**'); + await page.getByText('InvenTree Demo Server - Norman Nothington'); + + await page.waitForTimeout(1000); }); From 334570e5137a798f9a8e6dd33c8fd42e295f4ad4 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 27 Dec 2024 09:59:39 +1100 Subject: [PATCH 6/6] Small tweaks --- .../plugin/samples/integration/user_interface_sample.py | 4 ++-- src/frontend/tests/pages/pui_part.spec.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py b/src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py index 04d757c790b5..a31606b37469 100644 --- a/src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py +++ b/src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py @@ -75,7 +75,7 @@ def get_ui_panels(self, request, context, **kwargs): 'key': 'dynamic-panel', 'title': 'Dynamic Panel', 'source': self.plugin_static_file('sample_panel.js'), - 'icon': 'part', + 'icon': 'ti:wave-saw-tool:outline', 'context': { 'version': INVENTREE_SW_VERSION, 'plugin_version': self.VERSION, @@ -97,7 +97,7 @@ def get_ui_panels(self, request, context, **kwargs): 'key': 'part-panel', 'title': _('Part Panel'), 'source': self.plugin_static_file('sample_panel.js:renderPartPanel'), - 'icon': 'part', + 'icon': 'ti:package_outline', 'context': {'part_name': part.name if part else ''}, }) diff --git a/src/frontend/tests/pages/pui_part.spec.ts b/src/frontend/tests/pages/pui_part.spec.ts index 9f19892f0329..fc9fbd12cd55 100644 --- a/src/frontend/tests/pages/pui_part.spec.ts +++ b/src/frontend/tests/pages/pui_part.spec.ts @@ -116,6 +116,8 @@ test('Parts - Allocations', async ({ page }) => { await page.getByText('5 / 109').waitFor(); // Navigate to the "Allocations" tab + await page.waitForTimeout(500); + await page.getByRole('tab', { name: 'Allocations' }).click(); await page.getByRole('button', { name: 'Build Order Allocations' }).waitFor();