2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-04-06 11:31:04 +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

@@ -210,6 +210,12 @@ The environment in which the backup was taken does not match the current environ
This warning will not prevent you from restoring the backup but it is recommended to ensure the mentioned issues are resolved before restoring the backup to prevent issues with the restored instance.
#### INVE-W14
**Elevated privileges - Backend**
A user is logged in with elevated privileges. This might be a superuser or a administrator user. These types of users have elevated permissions and should not be used for regular usage.
Use separate accounts for administrative tasks and regular usage to reduce risk. Make sure to review the [permission documentation](../settings/permissions.md#dangerous-user-flags).
### INVE-I (InvenTree Information)
Information — These are not errors but information messages. They might point out potential issues or just provide information.

View File

@@ -535,6 +535,7 @@ Set the `INVENTREE_FRONTEND_SETTINGS` Environment variable to a JSON object or u
| `url_compatibility` | Support compatibility with "legacy" URLs? | `true` |
| `sentry_dsn` | Set a Sentry DSN url | *Not specified* |
| `mobile_mode` | Controls if InvenTree web UI can be used by mobile devices. There are 3 options: `default` - does not allow mobile devices; `allow-ignore` - shows a mobile device detected banner with a button to ignore this warning AT THE USERS OWN RISK; `allow-always` - skips the mobile check and allows mobile devices always (of course at the server admins OWN RISK) | `default` |
| `dangerous_hide_evelevated_alert` | Hides the elevated permissions alert in the UI. This is a dangerous option as using the UI with elevated permissions is against the threat model. | `false` |
E.g. to allow mobile devices to ignore the mobile check, use the following Environment variable:

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',