diff --git a/docs/docs/settings/error_codes.md b/docs/docs/settings/error_codes.md
index 59874a9402..bf388e7f2b 100644
--- a/docs/docs/settings/error_codes.md
+++ b/docs/docs/settings/error_codes.md
@@ -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.
diff --git a/docs/docs/start/config.md b/docs/docs/start/config.md
index 37688941f0..b25dfc1a98 100644
--- a/docs/docs/start/config.md
+++ b/docs/docs/start/config.md
@@ -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:
diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py
index 182c008bd8..354c46c20c 100644
--- a/src/backend/InvenTree/InvenTree/api_version.py
+++ b/src/backend/InvenTree/InvenTree/api_version.py
@@ -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
diff --git a/src/backend/InvenTree/users/serializers.py b/src/backend/InvenTree/users/serializers.py
index 0fdd481137..284b4611d3 100644
--- a/src/backend/InvenTree/users/serializers.py
+++ b/src/backend/InvenTree/users/serializers.py
@@ -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,
)
diff --git a/src/backend/InvenTree/users/test_api.py b/src/backend/InvenTree/users/test_api.py
index 473b70946b..54c4ddfa3a 100644
--- a/src/backend/InvenTree/users/test_api.py
+++ b/src/backend/InvenTree/users/test_api.py
@@ -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):
diff --git a/src/frontend/src/components/details/Details.tsx b/src/frontend/src/components/details/Details.tsx
index 2e61e7377a..9222814fa4 100644
--- a/src/frontend/src/components/details/Details.tsx
+++ b/src/frontend/src/components/details/Details.tsx
@@ -127,7 +127,7 @@ function HoverNameBadge(data: any, type: BadgeType) {
data?.image,
<>
{data.is_superuser && {t`Superuser`}}
- {data.is_staff && {t`Staff`}}
+ {data.is_staff && {t`Administrator`}}
{data.email && t`Email: ` + data.email}
>
];
diff --git a/src/frontend/src/components/items/OnlyStaff.tsx b/src/frontend/src/components/items/OnlyStaff.tsx
index 192e748d53..709d75c69d 100644
--- a/src/frontend/src/components/items/OnlyStaff.tsx
+++ b/src/frontend/src/components/items/OnlyStaff.tsx
@@ -7,5 +7,7 @@ export const OnlyStaff = ({ children }: { children: any }) => {
const [user] = useUserState(useShallow((state) => [state.user]));
if (user?.is_staff) return children;
- return This information is only available for staff users;
+ return (
+ This information is only available for administrative users
+ );
};
diff --git a/src/frontend/src/components/modals/AboutInvenTreeModal.tsx b/src/frontend/src/components/modals/AboutInvenTreeModal.tsx
index 56feea6e2c..dc478d56b7 100644
--- a/src/frontend/src/components/modals/AboutInvenTreeModal.tsx
+++ b/src/frontend/src/components/modals/AboutInvenTreeModal.tsx
@@ -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 (
-
- This information is only available for staff users
-
- );
- return ;
+ return (
+
+
+
+ );
}
const AboutContent = ({
diff --git a/src/frontend/src/components/nav/Header.tsx b/src/frontend/src/components/nav/Header.tsx
index 14725a6002..a70b87819b 100644
--- a/src/frontend/src/components/nav/Header.tsx
+++ b/src/frontend/src/components/nav/Header.tsx
@@ -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(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(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() {
+ {showElevated && user && (
+ setElevatedAlertClosed(true)}
+ >
+
+ {t`The current user has elevated privileges and should not be used for regular usage.`}
+ {errorCodeLink('INVE-W14')}
+
+
+ )}
);
}
diff --git a/src/frontend/src/main.tsx b/src/frontend/src/main.tsx
index d5fc90bd52..54b2ffc7da 100644
--- a/src/frontend/src/main.tsx
+++ b/src/frontend/src/main.tsx
@@ -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;
diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/AccountDetailPanel.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/AccountDetailPanel.tsx
index f879e77906..7d0ad44770 100644
--- a/src/frontend/src/pages/Index/Settings/AccountSettings/AccountDetailPanel.tsx
+++ b/src/frontend/src/pages/Index/Settings/AccountSettings/AccountDetailPanel.tsx
@@ -69,7 +69,7 @@ export function AccountDetailPanel() {
value:
},
{
- label: t`Staff Access`,
+ label: t`Administrator`,
value:
},
{
diff --git a/src/frontend/src/pages/core/UserDetail.tsx b/src/frontend/src/pages/core/UserDetail.tsx
index e7dd1940ca..457f19e5d6 100644
--- a/src/frontend/src/pages/core/UserDetail.tsx
+++ b/src/frontend/src/pages/core/UserDetail.tsx
@@ -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 && (
- {t`Staff`}
+ {t`Administrator`}
),
instance.is_superuser && (
{t`Superuser`}
),
!instance.is_staff && !instance.is_superuser && (
- {t`Basic user`}
+ {t`Normal user`}
),
instance.is_active ? (
{t`Active`}
diff --git a/src/frontend/src/tables/settings/UserTable.tsx b/src/frontend/src/tables/settings/UserTable.tsx
index 66bd277547..e70f106afe 100644
--- a/src/frontend/src/tables/settings/UserTable.tsx
+++ b/src/frontend/src/tables/settings/UserTable.tsx
@@ -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',