diff --git a/frontend/src/api.js b/frontend/src/api.js index 98c94322..915633f7 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -2904,6 +2904,33 @@ export default class API { } } + static async revokeApiKey({ user_id = null } = {}) { + try { + const body = {}; + if (user_id) { + body.user_id = user_id; + } + + const response = await request(`${host}/api/accounts/api-keys/revoke/`, { + method: 'POST', + body, + }); + + // If the backend returned an updated user, refresh the users store + try { + if (response && response.user) { + useUsersStore.getState().updateUser(response.user); + } + } catch (e) { + // ignore store update errors + } + + return response; + } catch (e) { + errorNotification('Failed to revoke API key', e); + } + } + static async updateUser(id, body) { try { const response = await request(`${host}/api/accounts/users/${id}/`, { diff --git a/frontend/src/components/forms/User.jsx b/frontend/src/components/forms/User.jsx index 825d4405..3f6259c0 100644 --- a/frontend/src/components/forms/User.jsx +++ b/frontend/src/components/forms/User.jsx @@ -15,8 +15,11 @@ import { Switch, Box, Tooltip, + Grid, + SimpleGrid, + useMantineTheme, } from '@mantine/core'; -import { RotateCcwKey, X } from 'lucide-react'; +import { RotateCcwKey, RotateCw, X } from 'lucide-react'; import { Copy, Key } from 'lucide-react'; import { useForm } from '@mantine/form'; import useChannelsStore from '../../store/channels'; @@ -35,6 +38,8 @@ const User = ({ user = null, isOpen, onClose }) => { const [generatedKey, setGeneratedKey] = useState(null); const [userAPIKey, setUserAPIKey] = useState(user?.api_key || null); + const theme = useMantineTheme(); + const form = useForm({ mode: 'uncontrolled', initialValues: { @@ -120,6 +125,7 @@ const User = ({ user = null, isOpen, onClose }) => { } form.reset(); + setUserAPIKey(null); onClose(); }; @@ -145,9 +151,7 @@ const User = ({ user = null, isOpen, onClose }) => { setEnableXC(true); } - if (user?.api_key) { - setUserAPIKey(user.api_key); - } + setUserAPIKey(user.api_key || null); } else { form.reset(); } @@ -194,6 +198,34 @@ const User = ({ user = null, isOpen, onClose }) => { } }; + const onRevokeKey = async () => { + if (!canGenerateKey) return; + + setGenerating(true); + try { + const payload = {}; + if (authUser.user_level == USER_LEVELS.ADMIN && user?.id) { + payload.user_id = user.id; + } + + const resp = await API.revokeApiKey(payload); + // backend returns { success: true } - clear local state + if (resp && resp.success) { + setGeneratedKey(null); + setUserAPIKey(null); + + // If we're revoking the current authenticated user's key, update auth store + if (user?.id && authUser?.id === user.id) { + setUser({ ...authUser, api_key: null }); + } + } + } catch (e) { + // API shows notifications + } finally { + setGenerating(false); + } + }; + return (
@@ -333,16 +365,32 @@ const User = ({ user = null, isOpen, onClose }) => { /> )} - + + + + {userAPIKey && ( + + )} + )}