mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-25 15:25:01 +03:00
fix(auth): add node-admin token scope for panel updates (#6666)
* feat(auth): add node-admin API token scope Keep ordinary node-sync credentials unable to self-update a remote panel while allowing explicitly elevated node administrators to inherit synchronization access and invoke updatePanel. * style(docs): format API scope table * fix(auth): preserve mTLS node-sync semantics Allow only an explicit node-admin bearer to elevate a verified mTLS request. Existing admin, monitor, and node-sync bearer values continue to use the historical node-sync identity on mTLS connections. * chore(ci): rerun flaky checks --------- Co-authored-by: libmur-dev <333915961+libmur-dev@users.noreply.github.com>
This commit is contained in:
@@ -49,6 +49,17 @@
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.api-token-name-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.api-token-name-line .ant-tag {
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
|
||||
.api-token-name {
|
||||
font-weight: 600;
|
||||
font-size: 13.5px;
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Empty, Form, Input, Modal, Space, Spin, Switch, Tabs, message } from 'antd';
|
||||
import {
|
||||
Button,
|
||||
Empty,
|
||||
Form,
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
Space,
|
||||
Spin,
|
||||
Switch,
|
||||
Tabs,
|
||||
Tag,
|
||||
message,
|
||||
} from 'antd';
|
||||
import { ApiOutlined, SafetyOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { ClipboardManager, HttpUtil, IntlUtil, RandomUtil } from '@/utils';
|
||||
import type { AllSetting } from '@/models/setting';
|
||||
@@ -16,12 +29,14 @@ interface ApiMsg<T = unknown> {
|
||||
obj?: T;
|
||||
}
|
||||
|
||||
type ApiTokenScope = 'admin' | 'monitor' | 'node-sync' | 'node-admin';
|
||||
|
||||
interface ApiTokenRow {
|
||||
id: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
createdAt: number;
|
||||
scope: 'admin' | 'monitor' | 'node-sync';
|
||||
scope: ApiTokenScope;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
@@ -76,6 +91,7 @@ export default function SecurityTab({ allSetting, updateSetting, saveSetting }:
|
||||
const [apiTokensLoading, setApiTokensLoading] = useState(true);
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [createName, setCreateName] = useState('');
|
||||
const [createScope, setCreateScope] = useState<ApiTokenScope>('admin');
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [createdToken, setCreatedToken] = useState<{ name: string; token: string } | null>(null);
|
||||
|
||||
@@ -157,6 +173,7 @@ export default function SecurityTab({ allSetting, updateSetting, saveSetting }:
|
||||
|
||||
function openCreateModal() {
|
||||
setCreateName('');
|
||||
setCreateScope('admin');
|
||||
setCreateOpen(true);
|
||||
}
|
||||
|
||||
@@ -168,9 +185,10 @@ export default function SecurityTab({ allSetting, updateSetting, saveSetting }:
|
||||
}
|
||||
setCreating(true);
|
||||
try {
|
||||
const msg = (await HttpUtil.post('/panel/api/setting/apiTokens/create', { name })) as ApiMsg<{
|
||||
token?: string;
|
||||
}>;
|
||||
const msg = (await HttpUtil.post('/panel/api/setting/apiTokens/create', {
|
||||
name,
|
||||
scope: createScope,
|
||||
})) as ApiMsg<{ token?: string }>;
|
||||
if (msg?.success) {
|
||||
setCreateOpen(false);
|
||||
await loadApiTokens();
|
||||
@@ -217,6 +235,29 @@ export default function SecurityTab({ allSetting, updateSetting, saveSetting }:
|
||||
return IntlUtil.formatDate(apiTokenCreatedAtMilliseconds(ts));
|
||||
}
|
||||
|
||||
const scopeMeta: Record<ApiTokenScope, { label: string; description: string; color: string }> = {
|
||||
admin: {
|
||||
label: t('pages.settings.security.apiTokenScopeAdmin'),
|
||||
description: t('pages.settings.security.apiTokenScopeAdminDesc'),
|
||||
color: 'red',
|
||||
},
|
||||
monitor: {
|
||||
label: t('pages.settings.security.apiTokenScopeMonitor'),
|
||||
description: t('pages.settings.security.apiTokenScopeMonitorDesc'),
|
||||
color: 'blue',
|
||||
},
|
||||
'node-sync': {
|
||||
label: t('pages.settings.security.apiTokenScopeNodeSync'),
|
||||
description: t('pages.settings.security.apiTokenScopeNodeSyncDesc'),
|
||||
color: 'gold',
|
||||
},
|
||||
'node-admin': {
|
||||
label: t('pages.settings.security.apiTokenScopeNodeAdmin'),
|
||||
description: t('pages.settings.security.apiTokenScopeNodeAdminDesc'),
|
||||
color: 'purple',
|
||||
},
|
||||
};
|
||||
|
||||
function toggleTwoFactor() {
|
||||
if (!allSetting.twoFactorEnable) {
|
||||
const newToken = RandomUtil.randomBase32String();
|
||||
@@ -345,7 +386,12 @@ export default function SecurityTab({ allSetting, updateSetting, saveSetting }:
|
||||
<div key={row.id} className={`api-token-row${row.enabled ? '' : ' disabled'}`}>
|
||||
<div className="api-token-row-head">
|
||||
<div className="api-token-name-wrap">
|
||||
<span className="api-token-name">{row.name}</span>
|
||||
<div className="api-token-name-line">
|
||||
<span className="api-token-name">{row.name}</span>
|
||||
<Tag color={scopeMeta[row.scope].color}>
|
||||
{scopeMeta[row.scope].label}
|
||||
</Tag>
|
||||
</div>
|
||||
<span className="api-token-created">
|
||||
{formatTokenDate(row.createdAt)}
|
||||
</span>
|
||||
@@ -396,6 +442,21 @@ export default function SecurityTab({ allSetting, updateSetting, saveSetting }:
|
||||
onPressEnter={confirmCreateToken}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('pages.settings.security.apiTokenScope')}
|
||||
extra={scopeMeta[createScope].description}
|
||||
required
|
||||
>
|
||||
<Select
|
||||
aria-label={t('pages.settings.security.apiTokenScope')}
|
||||
value={createScope}
|
||||
options={(Object.keys(scopeMeta) as ApiTokenScope[]).map((scope) => ({
|
||||
value: scope,
|
||||
label: scopeMeta[scope].label,
|
||||
}))}
|
||||
onChange={setCreateScope}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user