mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 20:16:44 +00:00
[PUI] Set password (#8770)
* Add <ChangePassword> page * Rename Set-Password to ResetPassword * Add unit testing * Ensure user is properly logged into page * Update playwright tests * Small tweaks
This commit is contained in:
parent
5499884553
commit
189f2303b8
@ -75,7 +75,7 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug
|
||||
'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 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug
|
||||
'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 ''},
|
||||
})
|
||||
|
||||
|
@ -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/',
|
||||
|
124
src/frontend/src/pages/Auth/ChangePassword.tsx
Normal file
124
src/frontend/src/pages/Auth/ChangePassword.tsx
Normal file
@ -0,0 +1,124 @@
|
||||
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 { ProtectedRoute } from '../../components/nav/Layout';
|
||||
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 (
|
||||
<LanguageContext>
|
||||
<ProtectedRoute>
|
||||
<Center mih='100vh'>
|
||||
<Container w='md' miw={425}>
|
||||
<Stack>
|
||||
<StylishText size='xl'>{t`Reset Password`}</StylishText>
|
||||
<Divider />
|
||||
{user.username() && (
|
||||
<Paper>
|
||||
<Group>
|
||||
<StylishText size='md'>{t`User`}</StylishText>
|
||||
<Text>{user.username()}</Text>
|
||||
</Group>
|
||||
</Paper>
|
||||
)}
|
||||
<Divider />
|
||||
<Stack gap='xs'>
|
||||
<PasswordInput
|
||||
required
|
||||
aria-label='input-password-1'
|
||||
label={t`New Password`}
|
||||
description={t`Enter your new password`}
|
||||
{...simpleForm.getInputProps('new_password1')}
|
||||
/>
|
||||
<PasswordInput
|
||||
required
|
||||
aria-label='input-password-2'
|
||||
label={t`Confirm New Password`}
|
||||
description={t`Confirm your new password`}
|
||||
{...simpleForm.getInputProps('new_password2')}
|
||||
/>
|
||||
</Stack>
|
||||
<Button type='submit' onClick={handleSet}>
|
||||
<Trans>Confirm</Trans>
|
||||
</Button>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Center>
|
||||
</ProtectedRoute>
|
||||
</LanguageContext>
|
||||
);
|
||||
}
|
@ -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();
|
@ -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: <IconKey />,
|
||||
tooltip: t`Set User Password`,
|
||||
onClick: notYetImplemented
|
||||
tooltip: t`Change User Password`,
|
||||
onClick: () => {
|
||||
navigate('/change-password');
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
@ -107,8 +107,13 @@ 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 Set_Password = Loadable(
|
||||
lazy(() => import('./pages/Auth/Set-Password'))
|
||||
|
||||
export const ChangePassword = Loadable(
|
||||
lazy(() => import('./pages/Auth/ChangePassword'))
|
||||
);
|
||||
|
||||
export const ResetPassword = Loadable(
|
||||
lazy(() => import('./pages/Auth/ResetPassword'))
|
||||
);
|
||||
|
||||
// Routes
|
||||
@ -168,7 +173,8 @@ export const routes = (
|
||||
<Route path='/logout' element={<Logout />} />,
|
||||
<Route path='/logged-in' element={<Logged_In />} />
|
||||
<Route path='/reset-password' element={<Reset />} />
|
||||
<Route path='/set-password' element={<Set_Password />} />
|
||||
<Route path='/set-password' element={<ResetPassword />} />
|
||||
<Route path='/change-password' element={<ChangePassword />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
|
@ -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();
|
||||
|
@ -95,3 +95,37 @@ test('Login - Failures', async ({ page }) => {
|
||||
|
||||
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('youshallnotpass');
|
||||
await page.getByLabel('input-password-2').fill('youshallnotpass');
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
|
||||
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);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user