🚫 fix: Hide Delete Account Button When ALLOW_ACCOUNT_DELETION Is Disabled (#12568)

* fix: hide Delete Account button when ALLOW_ACCOUNT_DELETION is false

* fix: add admin bypass, inline env read, and tests for allowAccountDeletion

- Show delete button for admin users even when ALLOW_ACCOUNT_DELETION=false,
  matching the canDeleteAccount middleware's ACCESS_ADMIN bypass
- Move env var read inline in buildSharedPayload() for per-request evaluation
- Add 4 frontend tests for Account conditional rendering
- Add 3 backend tests for allowAccountDeletion config field

* fix: use server-side ACCESS_ADMIN capability check instead of frontend role check

- Replace frontend SystemRoles.ADMIN check with server-side hasCapability()
  in the authenticated config route, matching canDeleteAccount middleware exactly
- Admin bypass now evaluates ACCESS_ADMIN capability per-user in GET /api/config,
  so users with the grant (regardless of role) see the button, and admins
  without the grant do not
- Add 3 authenticated backend tests: without capability, with capability,
  and skip-when-already-enabled
- Simplify frontend to pure config check (no role logic)
- Remove redundant jest-dom import; add inline env var comment

* test: add missing toHaveBeenCalled assertion in ACCESS_ADMIN test
This commit is contained in:
Danny Avila 2026-04-07 23:51:23 -04:00 committed by GitHub
parent 223065c411
commit d350c58633
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 164 additions and 4 deletions

View file

@ -0,0 +1,64 @@
import React from 'react';
import { SystemRoles } from 'librechat-data-provider';
import { render, screen } from '@testing-library/react';
import type { TUser } from 'librechat-data-provider';
import Account from './Account';
jest.mock('./DisplayUsernameMessages', () => () => <div data-testid="display-username" />);
jest.mock('./Avatar', () => () => <div data-testid="avatar" />);
jest.mock('./TwoFactorAuthentication', () => () => <div data-testid="two-factor" />);
jest.mock('./BackupCodesItem', () => () => <div data-testid="backup-codes" />);
jest.mock('./DeleteAccount', () => () => <div data-testid="delete-account" />);
const mockUseAuthContext = jest.fn();
const mockUseGetStartupConfig = jest.fn();
jest.mock('~/hooks', () => ({
useAuthContext: () => mockUseAuthContext(),
}));
jest.mock('~/data-provider', () => ({
useGetStartupConfig: () => mockUseGetStartupConfig(),
}));
const baseUser: TUser = {
id: 'user-123',
username: 'testuser',
email: 'test@example.com',
name: 'Test User',
avatar: '',
role: SystemRoles.USER,
provider: 'local',
createdAt: '2023-01-01T00:00:00.000Z',
updatedAt: '2023-01-01T00:00:00.000Z',
};
beforeEach(() => {
mockUseAuthContext.mockReturnValue({ user: baseUser });
mockUseGetStartupConfig.mockReturnValue({ data: { allowAccountDeletion: true } });
});
afterEach(() => {
jest.resetAllMocks();
});
describe('Account', () => {
describe('DeleteAccount visibility', () => {
it('renders DeleteAccount when allowAccountDeletion is true', () => {
render(<Account />);
expect(screen.getByTestId('delete-account')).toBeInTheDocument();
});
it('hides DeleteAccount when allowAccountDeletion is false', () => {
mockUseGetStartupConfig.mockReturnValue({ data: { allowAccountDeletion: false } });
render(<Account />);
expect(screen.queryByTestId('delete-account')).not.toBeInTheDocument();
});
it('shows DeleteAccount when startup config is still loading', () => {
mockUseGetStartupConfig.mockReturnValue({ data: undefined });
render(<Account />);
expect(screen.getByTestId('delete-account')).toBeInTheDocument();
});
});
});

View file

@ -4,10 +4,12 @@ import DeleteAccount from './DeleteAccount';
import Avatar from './Avatar';
import EnableTwoFactorItem from './TwoFactorAuthentication';
import BackupCodesItem from './BackupCodesItem';
import { useGetStartupConfig } from '~/data-provider';
import { useAuthContext } from '~/hooks';
function Account() {
const { user } = useAuthContext();
const { data: startupConfig } = useGetStartupConfig();
return (
<div className="flex flex-col gap-3 p-1 text-sm text-text-primary">
@ -29,9 +31,11 @@ function Account() {
)}
</>
)}
<div className="pb-3">
<DeleteAccount />
</div>
{startupConfig?.allowAccountDeletion !== false && (
<div className="pb-3">
<DeleteAccount />
</div>
)}
</div>
);
}