From 119ffbbf2fb3595d2c56ef0736ecf607c9fc75c9 Mon Sep 17 00:00:00 2001 From: Ludy87 Date: Thu, 29 Jan 2026 18:51:26 +0100 Subject: [PATCH] Add admin controls for requiring MFA per user Introduces the ability for admins to require or make optional multi-factor authentication (MFA) for individual users in the PeopleSection UI. Updates the userManagementService with a new requireMfaByAdmin method and extends the User interface to include mfaRequired. The UI now displays MFA status and provides actions to set MFA as required or optional for users. --- .../config/configSections/PeopleSection.tsx | 587 +++++++++++------- .../services/userManagementService.ts | 13 + 2 files changed, 372 insertions(+), 228 deletions(-) diff --git a/frontend/src/proprietary/components/shared/config/configSections/PeopleSection.tsx b/frontend/src/proprietary/components/shared/config/configSections/PeopleSection.tsx index be65e40475..2db9292f13 100644 --- a/frontend/src/proprietary/components/shared/config/configSections/PeopleSection.tsx +++ b/frontend/src/proprietary/components/shared/config/configSections/PeopleSection.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect } from "react"; import { useTranslation } from 'react-i18next'; import { Stack, @@ -17,21 +17,21 @@ import { CloseButton, Avatar, Box, -} from '@mantine/core'; -import LocalIcon from '@app/components/shared/LocalIcon'; -import { alert } from '@app/components/toast'; -import { userManagementService, User } from '@app/services/userManagementService'; -import { teamService, Team } from '@app/services/teamService'; -import { Z_INDEX_OVER_CONFIG_MODAL } from '@app/styles/zIndex'; -import { useAppConfig } from '@app/contexts/AppConfigContext'; -import InviteMembersModal from '@app/components/shared/InviteMembersModal'; -import { useLoginRequired } from '@app/hooks/useLoginRequired'; -import LoginRequiredBanner from '@app/components/shared/config/LoginRequiredBanner'; -import { useNavigate } from 'react-router-dom'; -import UpdateSeatsButton from '@app/components/shared/UpdateSeatsButton'; -import { useLicense } from '@app/contexts/LicenseContext'; -import ChangeUserPasswordModal from '@app/components/shared/ChangeUserPasswordModal'; -import { useAuth } from '@app/auth/UseSession'; +} from "@mantine/core"; +import LocalIcon from "@app/components/shared/LocalIcon"; +import { alert } from "@app/components/toast"; +import { userManagementService, User } from "@app/services/userManagementService"; +import { teamService, Team } from "@app/services/teamService"; +import { Z_INDEX_OVER_CONFIG_MODAL } from "@app/styles/zIndex"; +import { useAppConfig } from "@app/contexts/AppConfigContext"; +import InviteMembersModal from "@app/components/shared/InviteMembersModal"; +import { useLoginRequired } from "@app/hooks/useLoginRequired"; +import LoginRequiredBanner from "@app/components/shared/config/LoginRequiredBanner"; +import { useNavigate } from "react-router-dom"; +import UpdateSeatsButton from "@app/components/shared/UpdateSeatsButton"; +import { useLicense } from "@app/contexts/LicenseContext"; +import ChangeUserPasswordModal from "@app/components/shared/ChangeUserPasswordModal"; +import { useAuth } from "@app/auth/UseSession"; export default function PeopleSection() { const { t } = useTranslation(); @@ -43,7 +43,7 @@ export default function PeopleSection() { const [users, setUsers] = useState([]); const [teams, setTeams] = useState([]); const [loading, setLoading] = useState(true); - const [searchQuery, setSearchQuery] = useState(''); + const [searchQuery, setSearchQuery] = useState(""); const [inviteModalOpened, setInviteModalOpened] = useState(false); const [editUserModalOpened, setEditUserModalOpened] = useState(false); const [changePasswordModalOpened, setChangePasswordModalOpened] = useState(false); @@ -67,52 +67,90 @@ export default function PeopleSection() { return; } if (hasNoSlots) { - navigate('/settings/adminPlan'); + navigate("/settings/adminPlan"); return; } setInviteModalOpened(true); }; const addMemberTooltip = !loginEnabled - ? t('workspace.people.loginRequired', 'Enable login mode first') + ? t("workspace.people.loginRequired", "Enable login mode first") : hasNoSlots - ? t('workspace.people.license.noSlotsAvailable', 'No user slots available') + ? t("workspace.people.license.noSlotsAvailable", "No user slots available") : null; const isCurrentUser = (user: User) => currentUser?.username === user.username; // Form state for edit user modal const [editForm, setEditForm] = useState({ - role: 'ROLE_USER', + role: "ROLE_USER", teamId: undefined as number | undefined, }); useEffect(() => { - fetchData(); + (async () => { + await fetchData(); + })(); }, []); useEffect(() => { if (config) { - console.log('[PeopleSection] Email invites enabled:', config.enableEmailInvites); + console.log("[PeopleSection] Email invites enabled:", config.enableEmailInvites); } }, [config]); + const handleClickButton = async (user: User) => { + // Warte auf fetchData Abschluss + + console.log( + "[PeopleSection] klick1", + user.mfaRequired, + users.find((u) => u.username === user.username), + ); + try { + if (!user.mfaRequired) { + await userManagementService.requireMfaByAdmin(user.username, true); + console.log("[PeopleSection] klick3"); + alert({ + alertType: "success", + title: t("workspace.people.mfa.adminEnableSuccess", "MFA enabled successfully for user"), + }); + } else { + await userManagementService.requireMfaByAdmin(user.username, false); + console.log("[PeopleSection] klick4"); + + alert({ + alertType: "success", + title: t("workspace.people.mfa.adminDisableSuccess", "MFA disabled successfully for user"), + }); + } + } catch (error: any) { + console.error("[PeopleSection] Failed to enable MFA for user:", error); + const errorMessage = + error.response?.data?.message || + error.response?.data?.error || + error.message || + t("workspace.people.mfa.adminEnableError", "Failed to enable MFA for user"); + alert({ alertType: "error", title: errorMessage }); + } + await fetchData(); + console.log("[PeopleSection] klick1", user.mfaRequired); + }; + const fetchData = async () => { try { setLoading(true); if (loginEnabled) { - const [adminData, teamsData] = await Promise.all([ - userManagementService.getUsers(), - teamService.getTeams(), - ]); + const [adminData, teamsData] = await Promise.all([userManagementService.getUsers(), teamService.getTeams()]); // Enrich users with session data - const enrichedUsers = adminData.users.map(user => ({ + const enrichedUsers = adminData.users.map((user) => ({ ...user, isActive: adminData.userSessions[user.username] || false, lastRequest: adminData.userLastRequest[user.username] || undefined, - mfaEnabled: adminData.userSettings?.[user.username]?.mfaEnabled === 'true', + mfaEnabled: adminData.userSettings?.[user.username]?.mfaEnabled === "true", + mfaRequired: adminData.userSettings?.[user.username]?.mfaRequired === "true", })); setUsers(enrichedUsers); @@ -133,57 +171,61 @@ export default function PeopleSection() { const exampleUsers: User[] = [ { id: 1, - username: 'admin', - email: 'admin@example.com', + username: "admin", + email: "admin@example.com", enabled: true, - roleName: 'ROLE_ADMIN', - rolesAsString: 'ROLE_ADMIN', - authenticationType: 'password', + roleName: "ROLE_ADMIN", + rolesAsString: "ROLE_ADMIN", + authenticationType: "password", isActive: true, lastRequest: Date.now(), - team: { id: 1, name: 'Engineering' } + team: { id: 1, name: "Engineering" }, + mfaRequired: true, }, { id: 2, - username: 'john.doe', - email: 'john.doe@example.com', + username: "john.doe", + email: "john.doe@example.com", enabled: true, - roleName: 'ROLE_USER', - rolesAsString: 'ROLE_USER', - authenticationType: 'password', + roleName: "ROLE_USER", + rolesAsString: "ROLE_USER", + authenticationType: "password", isActive: false, lastRequest: Date.now() - 86400000, - team: { id: 1, name: 'Engineering' } + team: { id: 1, name: "Engineering" }, + mfaRequired: true, }, { id: 3, - username: 'jane.smith', - email: 'jane.smith@example.com', + username: "jane.smith", + email: "jane.smith@example.com", enabled: true, - roleName: 'ROLE_USER', - rolesAsString: 'ROLE_USER', - authenticationType: 'oauth', + roleName: "ROLE_USER", + rolesAsString: "ROLE_USER", + authenticationType: "oauth", isActive: true, lastRequest: Date.now(), - team: { id: 2, name: 'Marketing' } + team: { id: 2, name: "Marketing" }, + mfaRequired: true, }, { id: 4, - username: 'bob.wilson', - email: 'bob.wilson@example.com', + username: "bob.wilson", + email: "bob.wilson@example.com", enabled: false, - roleName: 'ROLE_USER', - rolesAsString: 'ROLE_USER', - authenticationType: 'password', + roleName: "ROLE_USER", + rolesAsString: "ROLE_USER", + authenticationType: "password", isActive: false, lastRequest: Date.now() - 604800000, - team: undefined - } + team: undefined, + mfaRequired: false, + }, ]; const exampleTeams: Team[] = [ - { id: 1, name: 'Engineering', userCount: 3 }, - { id: 2, name: 'Marketing', userCount: 2 } + { id: 1, name: "Engineering", userCount: 3 }, + { id: 2, name: "Marketing", userCount: 2 }, ]; setUsers(exampleUsers); @@ -201,8 +243,8 @@ export default function PeopleSection() { }); } } catch (error) { - console.error('[PeopleSection] Failed to fetch people data:', error); - alert({ alertType: 'error', title: 'Failed to load people data' }); + console.error("[PeopleSection] Failed to fetch people data:", error); + alert({ alertType: "error", title: "Failed to load people data" }); } finally { setLoading(false); } @@ -218,16 +260,17 @@ export default function PeopleSection() { role: editForm.role, teamId: editForm.teamId, }); - alert({ alertType: 'success', title: t('workspace.people.editMember.success') }); + alert({ alertType: "success", title: t("workspace.people.editMember.success") }); closeEditModal(); - fetchData(); + await fetchData(); } catch (error: any) { - console.error('[PeopleSection] Failed to update user:', error); - const errorMessage = error.response?.data?.message || - error.response?.data?.error || - error.message || - t('workspace.people.editMember.error'); - alert({ alertType: 'error', title: errorMessage }); + console.error("[PeopleSection] Failed to update user:", error); + const errorMessage = + error.response?.data?.message || + error.response?.data?.error || + error.message || + t("workspace.people.editMember.error"); + alert({ alertType: "error", title: errorMessage }); } finally { setProcessing(false); } @@ -236,35 +279,61 @@ export default function PeopleSection() { const handleToggleEnabled = async (user: User) => { try { await userManagementService.toggleUserEnabled(user.username, !user.enabled); - alert({ alertType: 'success', title: t('workspace.people.toggleEnabled.success') }); - fetchData(); + alert({ alertType: "success", title: t("workspace.people.toggleEnabled.success") }); + await fetchData(); } catch (error: any) { - console.error('[PeopleSection] Failed to toggle user status:', error); - const errorMessage = error.response?.data?.message || - error.response?.data?.error || - error.message || - t('workspace.people.toggleEnabled.error'); - alert({ alertType: 'error', title: errorMessage }); + console.error("[PeopleSection] Failed to toggle user status:", error); + const errorMessage = + error.response?.data?.message || + error.response?.data?.error || + error.message || + t("workspace.people.toggleEnabled.error"); + alert({ alertType: "error", title: errorMessage }); } }; const handleDeleteUser = async (user: User) => { - const confirmMessage = t('workspace.people.confirmDelete', 'Are you sure you want to delete this user? This action cannot be undone.'); + const confirmMessage = t( + "workspace.people.confirmDelete", + "Are you sure you want to delete this user? This action cannot be undone.", + ); if (!window.confirm(`${confirmMessage}\n\nUser: ${user.username}`)) { return; } try { await userManagementService.deleteUser(user.username); - alert({ alertType: 'success', title: t('workspace.people.deleteUserSuccess', 'User deleted successfully') }); - fetchData(); + alert({ alertType: "success", title: t("workspace.people.deleteUserSuccess", "User deleted successfully") }); + await fetchData(); } catch (error: any) { - console.error('[PeopleSection] Failed to delete user:', error); - const errorMessage = error.response?.data?.message || - error.response?.data?.error || - error.message || - t('workspace.people.deleteUserError', 'Failed to delete user'); - alert({ alertType: 'error', title: errorMessage }); + console.error("[PeopleSection] Failed to delete user:", error); + const errorMessage = + error.response?.data?.message || + error.response?.data?.error || + error.message || + t("workspace.people.deleteUserError", "Failed to delete user"); + alert({ alertType: "error", title: errorMessage }); + } + }; + + const handleDisableMfaByAdmin = async (user: User) => { + try { + if (user.mfaRequired) { + await userManagementService.requireMfaByAdmin(user.username, false); + alert({ + alertType: "success", + title: t("workspace.people.mfa.adminDisableSuccess", "MFA disabled successfully for user"), + }); + } + await fetchData(); + } catch (error: any) { + console.error("[PeopleSection] Failed to disable MFA for user:", error); + const errorMessage = + error.response?.data?.message || + error.response?.data?.error || + error.message || + t("workspace.people.mfa.adminDisableError", "Failed to disable MFA for user"); + alert({ alertType: "error", title: errorMessage }); } }; @@ -291,27 +360,31 @@ export default function PeopleSection() { setEditUserModalOpened(false); setSelectedUser(null); setEditForm({ - role: 'ROLE_USER', + role: "ROLE_USER", teamId: undefined, }); }; - const filteredUsers = users.filter((user) => - user.username.toLowerCase().includes(searchQuery.toLowerCase()) - ); + const filteredUsers = users.filter((user) => user.username.toLowerCase().includes(searchQuery.toLowerCase())); const roleOptions = [ { - value: 'ROLE_ADMIN', - label: t('workspace.people.admin'), - description: t('workspace.people.roleDescriptions.admin', 'Can manage settings and invite members, with full administrative access.'), - icon: 'admin-panel-settings' + value: "ROLE_ADMIN", + label: t("workspace.people.admin"), + description: t( + "workspace.people.roleDescriptions.admin", + "Can manage settings and invite members, with full administrative access.", + ), + icon: "admin-panel-settings", }, { - value: 'ROLE_USER', - label: t('workspace.people.member'), - description: t('workspace.people.roleDescriptions.member', 'Can view and edit shared files, but cannot manage workspace settings or members.'), - icon: 'person' + value: "ROLE_USER", + label: t("workspace.people.member"), + description: t( + "workspace.people.roleDescriptions.member", + "Can view and edit shared files, but cannot manage workspace settings or members.", + ), + icon: "person", }, ]; @@ -319,8 +392,10 @@ export default function PeopleSection() { - {option.label} - + + {option.label} + + {option.description} @@ -337,7 +412,7 @@ export default function PeopleSection() { - {t('workspace.people.loading', 'Loading people...')} + {t("workspace.people.loading", "Loading people...")} ); @@ -348,34 +423,40 @@ export default function PeopleSection() {
- {t('workspace.people.title')} + {t("workspace.people.title")} - {t('workspace.people.description')} + {t("workspace.people.description")}
{/* License Information - Compact */} {licenseInfo && ( - + - {licenseInfo.totalUsers} - / - {licenseInfo.maxAllowedUsers} - {t('workspace.people.license.users', 'users')} + + {licenseInfo.totalUsers} + + + {" "} + /{" "} + + + {licenseInfo.maxAllowedUsers} + + + {" "} + {t("workspace.people.license.users", "users")} + {licenseInfo.availableSlots === 0 && ( - {t('workspace.people.license.noSlotsAvailable', 'No slots available')} + {t("workspace.people.license.noSlotsAvailable", "No slots available")} - )} @@ -384,25 +465,26 @@ export default function PeopleSection() { - {t('workspace.people.license.grandfatheredShort', '{{count}} grandfathered', { count: licenseInfo.grandfatheredUserCount })} + {t("workspace.people.license.grandfatheredShort", "{{count}} grandfathered", { + count: licenseInfo.grandfatheredUserCount, + })} )} {licenseInfo.premiumEnabled && licenseInfo.licenseMaxUsers > 0 && ( - +{licenseInfo.licenseMaxUsers} {t('workspace.people.license.fromLicense', 'from license')} + +{licenseInfo.licenseMaxUsers} {t("workspace.people.license.fromLicense", "from license")} )} {/* Enterprise Seat Management Button */} - {globalLicenseInfo?.licenseType === 'ENTERPRISE' && ( + {globalLicenseInfo?.licenseType === "ENTERPRISE" && ( <> - - + + • + + )} @@ -411,7 +493,7 @@ export default function PeopleSection() { {/* Header Actions */} } value={searchQuery} onChange={(e) => setSearchQuery(e.currentTarget.value)} @@ -428,7 +510,7 @@ export default function PeopleSection() { onClick={handleAddMembersClick} disabled={!loginEnabled || (licenseInfo ? licenseInfo.availableSlots === 0 : false)} > - {t('workspace.people.addMembers')} + {t("workspace.people.addMembers")} @@ -438,20 +520,25 @@ export default function PeopleSection() { horizontalSpacing="md" verticalSpacing="sm" withRowBorders - style={{ - '--table-border-color': 'var(--mantine-color-gray-3)', - } as React.CSSProperties} + style={ + { + "--table-border-color": "var(--mantine-color-gray-3)", + } as React.CSSProperties + } > - - - {t('workspace.people.user')} + + + {t("workspace.people.user")} - - {t('workspace.people.role')} + + {t("workspace.people.role")} - - {t('workspace.people.team')} + + {t("workspace.people.team")} + + + {t("workspace.people.mfaTitle", "MFA")} @@ -461,7 +548,7 @@ export default function PeopleSection() { - {t('workspace.people.noMembersFound')} + {t("workspace.people.noMembersFound")} @@ -469,28 +556,28 @@ export default function PeopleSection() { filteredUsers.map((user) => ( {user.username.charAt(0).toUpperCase()} @@ -505,9 +592,9 @@ export default function PeopleSection() { style={{ lineHeight: 1.3, opacity: user.enabled ? 1 : 0.6, - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", }} > {user.username} @@ -522,14 +609,10 @@ export default function PeopleSection() { - - {(user.rolesAsString || '').includes('ROLE_ADMIN') - ? t('workspace.people.admin', 'Admin') - : t('workspace.people.member', 'Member')} + + {(user.rolesAsString || "").includes("ROLE_ADMIN") + ? t("workspace.people.admin", "Admin") + : t("workspace.people.member", "Member")} @@ -539,9 +622,9 @@ export default function PeopleSection() { size="sm" maw={150} style={{ - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", }} > {user.team.name} @@ -551,36 +634,62 @@ export default function PeopleSection() { )} - - - {/* Info icon with tooltip */} - - Authentication: {user.authenticationType || 'Unknown'} - - Last Activity: {user.lastRequest && new Date(user.lastRequest).getFullYear() >= 1980 - ? new Date(user.lastRequest).toLocaleString() - :t('never', 'Never')} - - - } - multiline - w={220} - position="left" - withArrow - zIndex={Z_INDEX_OVER_CONFIG_MODAL + 10} - > - - - - + + {!user.mfaEnabled && user.mfaRequired ? ( + // shield icon when MFA is required + + + + ) : null} + {!user.mfaEnabled && !user.mfaRequired ? ( + // dash when MFA is not required + + + + ) : null} + {user.mfaEnabled && ( + // key icon when MFA is enabled + + + + )} + + + + {/* Info icon with tooltip */} + + + Authentication: {user.authenticationType || "Unknown"} + + + Last Activity:{" "} + {user.lastRequest && new Date(user.lastRequest).getFullYear() >= 1980 + ? new Date(user.lastRequest).toLocaleString() + : t("never", "Never")} + + + } + multiline + w={220} + position="left" + withArrow + zIndex={Z_INDEX_OVER_CONFIG_MODAL + 10} + > + + + + - {/* Actions menu */} - {!isCurrentUser(user) && ( + {/* Actions menu */} + {!isCurrentUser(user) && ( - + @@ -591,25 +700,31 @@ export default function PeopleSection() { onClick={() => openEditModal(user)} disabled={!loginEnabled} > - {t('workspace.people.editRole', 'Edit Role & Team')} + {t("workspace.people.editRole", "Edit Role & Team")} )} {!isCurrentUser(user) && ( - } - onClick={() => openChangePasswordModal(user)} - disabled={!loginEnabled} - > - {t('workspace.people.changePassword.action', 'Change password')} - + } + onClick={() => openChangePasswordModal(user)} + disabled={!loginEnabled} + > + {t("workspace.people.changePassword.action", "Change password")} + )} {!isCurrentUser(user) && ( : } + leftSection={ + user.enabled ? ( + + ) : ( + + ) + } onClick={() => handleToggleEnabled(user)} disabled={!loginEnabled} > - {user.enabled ? t('workspace.people.disable') : t('workspace.people.enable')} + {user.enabled ? t("workspace.people.disable") : t("workspace.people.enable")} )} {!isCurrentUser(user) && user.mfaEnabled && ( @@ -618,50 +733,66 @@ export default function PeopleSection() { } - onClick={async () => { - try { - await userManagementService.disableMfaByAdmin(user.username); - alert({ alertType: 'success', title: t('workspace.people.mfa.adminDisableSuccess', 'MFA disabled successfully for user') }); - } catch (error: any) { - console.error('[PeopleSection] Failed to disable MFA for user:', error); - const errorMessage = error.response?.data?.message || - error.response?.data?.error || - error.message || - t('workspace.people.mfa.adminDisableError', 'Failed to disable MFA for user'); - alert({ alertType: 'error', title: errorMessage }); - } - }} + onClick={async () => handleDisableMfaByAdmin(user)} disabled={!loginEnabled} > - {t('workspace.people.mfa.disableByAdmin', 'Disable MFA')} + {t("workspace.people.mfa.disableByAdmin", "Disable MFA")} + + + )} + {!isCurrentUser(user) && !user.mfaEnabled && ( + <> + + } + onClick={() => handleClickButton(user)} + disabled={!loginEnabled} + > + {user.mfaRequired + ? t("workspace.people.mfa.setOptional", "Set MFA Optional") + : t("workspace.people.mfa.setRequired", "Set MFA Require")} )} {!isCurrentUser(user) && ( <> - } onClick={() => handleDeleteUser(user)} disabled={!loginEnabled}> - {t('workspace.people.deleteUser')} + } + onClick={() => handleDeleteUser(user)} + disabled={!loginEnabled} + > + {t("workspace.people.deleteUser")} )} - )} - - - - )) - )} + )} + + {/* link to Account */} + {isCurrentUser(user) && ( + + navigate("/settings/account")} disabled={!loginEnabled}> + + + + )} + + + + )) + )} {/* Invite Members Modal (reusable) */} - setInviteModalOpened(false)} - onSuccess={fetchData} - /> + setInviteModalOpened(false)} onSuccess={fetchData} /> {/* Header with Icon */} - + - {t('workspace.people.editMember.title')} + {t("workspace.people.editMember.title")} - {t('workspace.people.editMember.editing')} {selectedUser?.username} + {t("workspace.people.editMember.editing")} {selectedUser?.username} setEditForm({ ...editForm, teamId: value ? parseInt(value) : undefined })} @@ -721,7 +852,7 @@ export default function PeopleSection() { comboboxProps={{ withinPortal: true, zIndex: Z_INDEX_OVER_CONFIG_MODAL }} /> diff --git a/frontend/src/proprietary/services/userManagementService.ts b/frontend/src/proprietary/services/userManagementService.ts index 8da746dd2d..791761f25d 100644 --- a/frontend/src/proprietary/services/userManagementService.ts +++ b/frontend/src/proprietary/services/userManagementService.ts @@ -1,6 +1,7 @@ import apiClient from '@app/services/apiClient'; export interface User { + mfaRequired: boolean; id: number; username: string; email?: string; @@ -40,6 +41,7 @@ export interface AdminSettingsData { premiumEnabled: boolean; mailEnabled: boolean; userSettings?: Record; + mfaRequired: boolean; } export interface CreateUserRequest { @@ -305,4 +307,15 @@ export const userManagementService = { await apiClient.post(`/api/v1/auth/mfa/disable/admin/${encodeURIComponent(username)}`, undefined); }, + /** + * Require MFA for a user (admin only) + */ + async requireMfaByAdmin(username: string, active: boolean): Promise { + if (!active) { + await apiClient.post(`/api/v1/auth/mfa/optional/admin/${encodeURIComponent(username)}`, undefined); + return; + } + await apiClient.post(`/api/v1/auth/mfa/require/admin/${encodeURIComponent(username)}`, undefined); + }, + };