2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-04-15 07:48:51 +00:00

feat(frontend): improve comms around danger of staff users (#11659)

* docs: add more details around staff / superuser roles and their dangers

* make clear that staff users are dangerous

* make distinction clearer in API

* add error code and frontend warning about running with staff / admin user

* fix test

* bump api

* adapt banner warning

* make banner locally disableable

* add global option to disable elevated user alert
This commit is contained in:
Matthias Mair
2026-04-05 14:51:46 +02:00
committed by GitHub
parent d358001827
commit e91f306245
13 changed files with 65 additions and 32 deletions

View File

@@ -1,11 +1,14 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 469
INVENTREE_API_VERSION = 470
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v470 -> 2026-04-01 : https://github.com/inventree/InvenTree/pull/11659
- Renames "is_staff" field to "is_admin" and updates help texts accordingly to highlight current security boundaries
v469 -> 2026-03-31 : https://github.com/inventree/InvenTree/pull/11641
- Adds parameter support to the SalesOrderShipment model and API endpoints

View File

@@ -306,8 +306,8 @@ class ExtendedUserSerializer(UserSerializer):
)
is_staff = serializers.BooleanField(
label=_('Staff'),
help_text=_('Does this user have staff permissions'),
label=_('Administrator'),
help_text=_('Does this user have administrative permissions'),
required=False,
)

View File

@@ -37,9 +37,10 @@ class UserAPITests(InvenTreeAPITestCase):
fields['is_active']['help_text'], 'Is this user account active'
)
self.assertEqual(fields['is_staff']['label'], 'Staff')
self.assertEqual(fields['is_staff']['label'], 'Administrator')
self.assertEqual(
fields['is_staff']['help_text'], 'Does this user have staff permissions'
fields['is_staff']['help_text'],
'Does this user have administrative permissions',
)
def test_api_url(self):

View File

@@ -127,7 +127,7 @@ function HoverNameBadge(data: any, type: BadgeType) {
data?.image,
<>
{data.is_superuser && <Badge color='red'>{t`Superuser`}</Badge>}
{data.is_staff && <Badge color='blue'>{t`Staff`}</Badge>}
{data.is_staff && <Badge color='orange'>{t`Administrator`}</Badge>}
{data.email && t`Email: ` + data.email}
</>
];

View File

@@ -7,5 +7,7 @@ export const OnlyStaff = ({ children }: { children: any }) => {
const [user] = useUserState(useShallow((state) => [state.user]));
if (user?.is_staff) return children;
return <Trans>This information is only available for staff users</Trans>;
return (
<Trans>This information is only available for administrative users</Trans>
);
};

View File

@@ -8,8 +8,7 @@ import {
Group,
Space,
Stack,
Table,
Text
Table
} from '@mantine/core';
import type { ContextModalProps } from '@mantine/modals';
import { useQuery } from '@tanstack/react-query';
@@ -20,11 +19,11 @@ import { useShallow } from 'zustand/react/shallow';
import { api } from '../../App';
import { generateUrl } from '../../functions/urls';
import { useServerApiState } from '../../states/ServerApiState';
import { useUserState } from '../../states/UserState';
import { CopyButton } from '../buttons/CopyButton';
import { StylishText } from '../items/StylishText';
import type { JSX } from 'react';
import { OnlyStaff } from '../items/OnlyStaff';
type AboutLookupRef = {
ref: string;
@@ -42,15 +41,11 @@ export function AboutInvenTreeModal({
modalBody: string;
}>
>) {
const [user] = useUserState(useShallow((state) => [state.user]));
if (!user?.is_staff)
return (
<Text>
<Trans>This information is only available for staff users</Trans>
</Text>
);
return <AboutContent context={context} id={id} innerProps={innerProps} />;
return (
<OnlyStaff>
<AboutContent context={context} id={id} innerProps={innerProps} />
</OnlyStaff>
);
}
const AboutContent = ({

View File

@@ -1,5 +1,6 @@
import {
ActionIcon,
Alert,
Container,
Group,
Indicator,
@@ -39,7 +40,7 @@ import {
import { useUserState } from '../../states/UserState';
import { ScanButton } from '../buttons/ScanButton';
import { SpotlightButton } from '../buttons/SpotlightButton';
import { Alerts } from './Alerts';
import { Alerts, errorCodeLink } from './Alerts';
import { MainMenu } from './MainMenu';
import { NavHoverMenu } from './NavHoverMenu';
import { NavigationDrawer } from './NavigationDrawer';
@@ -53,11 +54,12 @@ export function Header() {
const [server] = useServerApiState(useShallow((state) => [state.server]));
const [navDrawerOpened, { open: openNavDrawer, close: closeNavDrawer }] =
useDisclosure(navigationOpen);
const [
searchDrawerOpened,
{ open: openSearchDrawer, close: closeSearchDrawer }
] = useDisclosure(false);
const [elevatedAlertClosed, setElevatedAlertClosed] =
useState<boolean>(false);
useHotkeys([
[
@@ -79,7 +81,7 @@ export function Header() {
{ open: openNotificationDrawer, close: closeNotificationDrawer }
] = useDisclosure(false);
const { isLoggedIn } = useUserState();
const { isLoggedIn, user } = useUserState();
const [notificationCount, setNotificationCount] = useState<number>(0);
const globalSettings = useGlobalSettingsState();
const userSettings = useUserSettingsState();
@@ -128,6 +130,14 @@ export function Header() {
else closeNavDrawer();
}, [navigationOpen]);
const showElevated = useMemo(
() =>
(user?.is_staff || user?.is_superuser || false) &&
!elevatedAlertClosed &&
!window.INVENTREE_SETTINGS.dangerous_hide_evelevated_alert,
[user, elevatedAlertClosed]
);
const headerStyle: any = useMemo(() => {
const sticky: boolean = userSettings.isSet('STICKY_HEADER', true);
@@ -200,6 +210,19 @@ export function Header() {
</Group>
</Group>
</Container>
{showElevated && user && (
<Alert
color={user.is_superuser ? 'red' : 'orange'}
title={user.is_superuser ? t`Superuser Mode` : t`Administrator Mode`}
withCloseButton
onClose={() => setElevatedAlertClosed(true)}
>
<Text>
{t`The current user has elevated privileges and should not be used for regular usage.`}
{errorCodeLink('INVE-W14')}
</Text>
</Alert>
)}
</div>
);
}

View File

@@ -36,6 +36,7 @@ declare global {
sentry_dsn?: string;
environment?: string;
mobile_mode?: 'default' | 'allow-ignore' | 'allow-always';
dangerous_hide_evelevated_alert?: boolean;
};
react: typeof React;
React: typeof React;

View File

@@ -69,7 +69,7 @@ export function AccountDetailPanel() {
value: <YesNoUndefinedButton value={user?.profile?.active} />
},
{
label: t`Staff Access`,
label: t`Administrator`,
value: <YesNoUndefinedButton value={user?.is_staff} />
},
{

View File

@@ -84,14 +84,14 @@ export default function UserDetail() {
{
type: 'boolean',
name: 'is_staff',
label: t`Staff`,
icon: 'info'
label: t`Administrator`,
icon: 'warning'
},
{
type: 'boolean',
name: 'is_superuser',
label: t`Superuser`,
icon: 'info'
icon: 'warning'
},
{
type: 'text',
@@ -197,13 +197,13 @@ export default function UserDetail() {
? []
: [
instance.is_staff && (
<Badge key='is_staff' color='blue'>{t`Staff`}</Badge>
<Badge key='is_staff' color='orange'>{t`Administrator`}</Badge>
),
instance.is_superuser && (
<Badge key='is_superuser' color='red'>{t`Superuser`}</Badge>
),
!instance.is_staff && !instance.is_superuser && (
<Badge key='is_normal' color='yellow'>{t`Basic user`}</Badge>
<Badge key='is_normal' color='yellow'>{t`Normal user`}</Badge>
),
instance.is_active ? (
<Badge key='is_active' color='green'>{t`Active`}</Badge>

View File

@@ -180,7 +180,7 @@ export function UserDrawer({
disabled: isCurrentUser
},
is_staff: {
label: t`Is Staff`,
label: t`Is Administrator`,
description: t`Designates whether the user can log into the django admin site.`,
disabled: isCurrentUser
},
@@ -281,7 +281,8 @@ export function UserTable({
}
},
BooleanColumn({
accessor: 'is_staff'
accessor: 'is_staff',
title: t`Administrator`
}),
BooleanColumn({
accessor: 'is_superuser'
@@ -402,8 +403,8 @@ export function UserTable({
},
{
name: 'is_staff',
label: t`Staff`,
description: t`Show staff users`
label: t`Administrator`,
description: t`Show administrators`
},
{
name: 'is_superuser',