[Fix] UI - Virtual Keys: restrict Edit Settings button to key owners

Non-owner Internal Users could see and interact with the "Edit Settings"
button in the key Settings tab for keys they don't own. The button was
gated by `rolesWithWriteAccess.includes(userRole)` (role-only check)
instead of `canModifyKey` (ownership-aware), unlike the Regenerate and
Delete buttons which already used the correct check.

Replace the condition with `canModifyKey` so the Edit Settings button
follows the same proxy-admin / team-admin / key-owner logic as the
other action buttons. Add tests covering all permission paths.
This commit is contained in:
yuneng-jiang
2026-02-23 23:06:27 -08:00
parent 36f7722b0f
commit c119adb6dc
2 changed files with 90 additions and 20 deletions
@@ -363,31 +363,101 @@ describe("KeyInfoView", () => {
});
it("should show edit button in settings tab when user has write access", async () => {
vi.mocked(useAuthorized).mockReturnValue({
...baseUseAuthorizedMock,
userRole: "Admin",
describe("'Edit Settings' button visibility in the Settings tab", () => {
const renderAndOpenSettingsTab = async (keyData = MOCK_KEY_DATA) => {
render(
<KeyInfoView
keyData={keyData}
onClose={() => {}}
keyId="test-key-id"
onKeyDataUpdate={() => {}}
teams={[]}
/>,
);
await waitFor(() => {
expect(screen.getByRole("tab", { name: /settings/i })).toBeInTheDocument();
});
await userEvent.click(screen.getByRole("tab", { name: /settings/i }));
};
it("should show the Edit Settings button when the user is a proxy admin for a key they do not own", async () => {
vi.mocked(useAuthorized).mockReturnValue({
...baseUseAuthorizedMock,
userId: "proxy-admin-user-id",
userRole: "proxy_admin",
});
await renderAndOpenSettingsTab({ ...MOCK_KEY_DATA, user_id: "someone-else-id" });
expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument();
});
render(
<KeyInfoView
keyData={MOCK_KEY_DATA}
onClose={() => { }}
keyId={"test-key-id"}
onKeyDataUpdate={() => { }}
teams={[]}
/>,
);
it("should show the Edit Settings button when the user is the key owner", async () => {
vi.mocked(useAuthorized).mockReturnValue({
...baseUseAuthorizedMock,
userId: "owner-user-id",
userRole: "Internal User",
});
await waitFor(() => {
const settingsTab = screen.getByRole("tab", { name: /settings/i });
expect(settingsTab).toBeInTheDocument();
await renderAndOpenSettingsTab({ ...MOCK_KEY_DATA, user_id: "owner-user-id" });
expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument();
});
const settingsTab = screen.getByRole("tab", { name: /settings/i });
await userEvent.click(settingsTab);
it("should not show the Edit Settings button when an Internal User does not own the key", async () => {
vi.mocked(useAuthorized).mockReturnValue({
...baseUseAuthorizedMock,
userId: "non-owner-user-id",
userRole: "Internal User",
});
await renderAndOpenSettingsTab({ ...MOCK_KEY_DATA, user_id: "owner-user-id" });
expect(screen.queryByRole("button", { name: /edit settings/i })).not.toBeInTheDocument();
});
it("should not show the Edit Settings button when the user is an Internal Viewer even if they own the key", async () => {
vi.mocked(useAuthorized).mockReturnValue({
...baseUseAuthorizedMock,
userId: "owner-user-id",
userRole: "Internal Viewer",
});
await renderAndOpenSettingsTab({ ...MOCK_KEY_DATA, user_id: "owner-user-id" });
expect(screen.queryByRole("button", { name: /edit settings/i })).not.toBeInTheDocument();
});
it("should show the Edit Settings button when the user is a team admin for the key's team", async () => {
const teamId = "test-team-id";
const teamAdminUserId = "team-admin-user";
vi.mocked(useTeams).mockReturnValue({
teams: [
{
team_id: teamId,
team_alias: "Test Team",
models: [],
max_budget: null,
budget_duration: null,
tpm_limit: null,
rpm_limit: null,
organization_id: "org-1",
created_at: "2025-01-01T00:00:00Z",
keys: [],
members_with_roles: [{ user_id: teamAdminUserId, role: "admin" }],
spend: 0,
},
],
setTeams: vi.fn(),
});
vi.mocked(useAuthorized).mockReturnValue({
...baseUseAuthorizedMock,
userId: teamAdminUserId,
userRole: "user",
});
await renderAndOpenSettingsTab({ ...MOCK_KEY_DATA, team_id: teamId, user_id: "other-user-id" });
await waitFor(() => {
expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument();
});
});
@@ -595,7 +595,7 @@ export default function KeyInfoView({
<Card className="overflow-y-auto max-h-[65vh]">
<div className="flex justify-between items-center mb-4">
<Title>Key Settings</Title>
{!isEditing && userRole && rolesWithWriteAccess.includes(userRole) && (
{!isEditing && canModifyKey && (
<Button onClick={() => setIsEditing(true)}>Edit Settings</Button>
)}
</div>