mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-23 14: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:
@@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { AllSetting } from '@/models/setting';
|
||||
@@ -16,12 +16,16 @@ describe('API token creation date', () => {
|
||||
name: 'seconds-token',
|
||||
enabled: true,
|
||||
createdAt: 1782485394,
|
||||
scope: 'node-sync',
|
||||
expiresAt: 0,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'legacy-milliseconds-token',
|
||||
enabled: true,
|
||||
createdAt: 1782485394270,
|
||||
scope: 'node-admin',
|
||||
expiresAt: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -34,5 +38,46 @@ describe('API token creation date', () => {
|
||||
expect(await screen.findByText('seconds-token')).toBeTruthy();
|
||||
expect(screen.getByText('legacy-milliseconds-token')).toBeTruthy();
|
||||
expect(screen.getAllByText(/2026/)).toHaveLength(2);
|
||||
expect(screen.getByText('Node synchronization')).toBeTruthy();
|
||||
expect(screen.getByText('Node administrator')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('creates a token with the selected scope', async () => {
|
||||
vi.spyOn(HttpUtil, 'get').mockResolvedValue({ success: true, msg: '', obj: [] });
|
||||
const post = vi.spyOn(HttpUtil, 'post').mockResolvedValue({
|
||||
success: true,
|
||||
msg: '',
|
||||
obj: { token: 'created-token' },
|
||||
});
|
||||
|
||||
render(
|
||||
<SecurityTab allSetting={{} as AllSetting} updateSetting={vi.fn()} saveSetting={vi.fn()} />,
|
||||
);
|
||||
fireEvent.click(screen.getByRole('tab', { name: /API Token/ }));
|
||||
fireEvent.click(await screen.findByRole('button', { name: /New token/ }));
|
||||
fireEvent.change(screen.getByPlaceholderText('e.g. central-panel-a'), {
|
||||
target: { value: 'central-node' },
|
||||
});
|
||||
|
||||
const scope = screen.getByRole('combobox', { name: 'Scope' });
|
||||
fireEvent.mouseDown(scope.closest('.ant-select') ?? scope);
|
||||
const option = Array.from(document.querySelectorAll('.ant-select-item-option')).find(
|
||||
(item) => item.textContent === 'Node administrator',
|
||||
);
|
||||
if (!option) throw new Error('Node administrator option not found');
|
||||
fireEvent.click(option);
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Includes node synchronization permissions and can also start panel software updates.',
|
||||
),
|
||||
).toBeTruthy();
|
||||
|
||||
fireEvent.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Confirm' }));
|
||||
await waitFor(() =>
|
||||
expect(post).toHaveBeenCalledWith('/panel/api/setting/apiTokens/create', {
|
||||
name: 'central-node',
|
||||
scope: 'node-admin',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user