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',
|
'key': 'dynamic-panel',
|
||||||
'title': 'Dynamic Panel',
|
'title': 'Dynamic Panel',
|
||||||
'source': self.plugin_static_file('sample_panel.js'),
|
'source': self.plugin_static_file('sample_panel.js'),
|
||||||
'icon': 'part',
|
'icon': 'ti:wave-saw-tool:outline',
|
||||||
'context': {
|
'context': {
|
||||||
'version': INVENTREE_SW_VERSION,
|
'version': INVENTREE_SW_VERSION,
|
||||||
'plugin_version': self.VERSION,
|
'plugin_version': self.VERSION,
|
||||||
@ -97,7 +97,7 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug
|
|||||||
'key': 'part-panel',
|
'key': 'part-panel',
|
||||||
'title': _('Part Panel'),
|
'title': _('Part Panel'),
|
||||||
'source': self.plugin_static_file('sample_panel.js:renderPartPanel'),
|
'source': self.plugin_static_file('sample_panel.js:renderPartPanel'),
|
||||||
'icon': 'part',
|
'icon': 'ti:package_outline',
|
||||||
'context': {'part_name': part.name if part else ''},
|
'context': {'part_name': part.name if part else ''},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ export enum ApiEndpoints {
|
|||||||
user_simple_login = 'email/generate/',
|
user_simple_login = 'email/generate/',
|
||||||
user_reset = 'auth/password/reset/',
|
user_reset = 'auth/password/reset/',
|
||||||
user_reset_set = 'auth/password/reset/confirm/',
|
user_reset_set = 'auth/password/reset/confirm/',
|
||||||
|
user_change_password = 'auth/password/change/',
|
||||||
user_sso = 'auth/social/',
|
user_sso = 'auth/social/',
|
||||||
user_sso_remove = 'auth/social/:id/disconnect/',
|
user_sso_remove = 'auth/social/:id/disconnect/',
|
||||||
user_emails = 'auth/emails/',
|
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 { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
|
|
||||||
export default function Set_Password() {
|
export default function ResetPassword() {
|
||||||
const simpleForm = useForm({ initialValues: { password: '' } });
|
const simpleForm = useForm({ initialValues: { password: '' } });
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
@ -3,15 +3,17 @@ import { Group, Stack, Table, Title } from '@mantine/core';
|
|||||||
import { IconKey, IconUser } from '@tabler/icons-react';
|
import { IconKey, IconUser } from '@tabler/icons-react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { YesNoUndefinedButton } from '../../../../components/buttons/YesNoButton';
|
import { YesNoUndefinedButton } from '../../../../components/buttons/YesNoButton';
|
||||||
import type { ApiFormFieldSet } from '../../../../components/forms/fields/ApiFormField';
|
import type { ApiFormFieldSet } from '../../../../components/forms/fields/ApiFormField';
|
||||||
import { ActionDropdown } from '../../../../components/items/ActionDropdown';
|
import { ActionDropdown } from '../../../../components/items/ActionDropdown';
|
||||||
import { ApiEndpoints } from '../../../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../../../enums/ApiEndpoints';
|
||||||
import { notYetImplemented } from '../../../../functions/notifications';
|
|
||||||
import { useEditApiFormModal } from '../../../../hooks/UseForm';
|
import { useEditApiFormModal } from '../../../../hooks/UseForm';
|
||||||
import { useUserState } from '../../../../states/UserState';
|
import { useUserState } from '../../../../states/UserState';
|
||||||
|
|
||||||
export function AccountDetailPanel() {
|
export function AccountDetailPanel() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [user, fetchUserState] = useUserState((state) => [
|
const [user, fetchUserState] = useUserState((state) => [
|
||||||
state.user,
|
state.user,
|
||||||
state.fetchUserState
|
state.fetchUserState
|
||||||
@ -51,10 +53,12 @@ export function AccountDetailPanel() {
|
|||||||
onClick: editUser.open
|
onClick: editUser.open
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t`Set Password`,
|
name: t`Change Password`,
|
||||||
icon: <IconKey />,
|
icon: <IconKey />,
|
||||||
tooltip: t`Set User Password`,
|
tooltip: t`Change User Password`,
|
||||||
onClick: notYetImplemented
|
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 Logout = Loadable(lazy(() => import('./pages/Auth/Logout')));
|
||||||
export const Logged_In = Loadable(lazy(() => import('./pages/Auth/Logged-In')));
|
export const Logged_In = Loadable(lazy(() => import('./pages/Auth/Logged-In')));
|
||||||
export const Reset = Loadable(lazy(() => import('./pages/Auth/Reset')));
|
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
|
// Routes
|
||||||
@ -168,7 +173,8 @@ export const routes = (
|
|||||||
<Route path='/logout' element={<Logout />} />,
|
<Route path='/logout' element={<Logout />} />,
|
||||||
<Route path='/logged-in' element={<Logged_In />} />
|
<Route path='/logged-in' element={<Logged_In />} />
|
||||||
<Route path='/reset-password' element={<Reset />} />
|
<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>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
|
@ -116,6 +116,8 @@ test('Parts - Allocations', async ({ page }) => {
|
|||||||
await page.getByText('5 / 109').waitFor();
|
await page.getByText('5 / 109').waitFor();
|
||||||
|
|
||||||
// Navigate to the "Allocations" tab
|
// Navigate to the "Allocations" tab
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
await page.getByRole('tab', { name: 'Allocations' }).click();
|
await page.getByRole('tab', { name: 'Allocations' }).click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Build Order Allocations' }).waitFor();
|
await page.getByRole('button', { name: 'Build Order Allocations' }).waitFor();
|
||||||
|
@ -95,3 +95,37 @@ test('Login - Failures', async ({ page }) => {
|
|||||||
|
|
||||||
await page.waitForTimeout(2500);
|
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