Added Ability to Reset MFA for Users

This commit is contained in:
2025-10-09 11:12:08 -06:00
parent 19f2197c90
commit 28b5ce5eea
2 changed files with 58 additions and 4 deletions

View File

@@ -89,6 +89,8 @@ export default function UserManagement({ isAdmin = false }) {
const [warnMessage, setWarnMessage] = useState("");
const [me, setMe] = useState(null);
const [mfaBusyUser, setMfaBusyUser] = useState(null);
const [resetMfaOpen, setResetMfaOpen] = useState(false);
const [resetMfaTarget, setResetMfaTarget] = useState(null);
// Columns and filters
const columns = useMemo(() => ([
@@ -243,6 +245,43 @@ export default function UserManagement({ isAdmin = false }) {
}
};
const openResetMfa = (user) => {
if (!user) return;
setResetMfaTarget(user);
setResetMfaOpen(true);
};
const doResetMfa = async () => {
const user = resetMfaTarget;
setResetMfaOpen(false);
setResetMfaTarget(null);
if (!user) return;
const username = user.username;
const keepEnabled = Boolean(user.mfa_enabled);
setMfaBusyUser(username);
try {
const resp = await fetch(`/api/users/${encodeURIComponent(username)}/mfa`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ enabled: keepEnabled, reset_secret: true })
});
const data = await resp.json();
if (!resp.ok) {
setWarnMessage(data?.error || "Failed to reset MFA for this user.");
setWarnOpen(true);
return;
}
await fetchUsers();
} catch (err) {
console.error(err);
setWarnMessage("Failed to reset MFA for this user.");
setWarnOpen(true);
} finally {
setMfaBusyUser(null);
}
};
const toggleMfa = async (user, enabled) => {
if (!user) return;
const previous = Boolean(user.mfa_enabled);
@@ -509,6 +548,9 @@ export default function UserManagement({ isAdmin = false }) {
>
Change Role
</MenuItem>
<MenuItem onClick={() => { const u = menuUser; closeMenu(); openResetMfa(u); }}>
Reset MFA
</MenuItem>
</Menu>
<Dialog open={resetOpen} onClose={() => setResetOpen(false)} PaperProps={{ sx: { bgcolor: "#121212", color: "#fff" } }}>
@@ -621,6 +663,12 @@ export default function UserManagement({ isAdmin = false }) {
onCancel={() => setConfirmChangeRoleOpen(false)}
onConfirm={doChangeRole}
/>
<ConfirmDeleteDialog
open={resetMfaOpen}
message={resetMfaTarget ? `Reset MFA enrollment for '${resetMfaTarget.username}'? This clears their existing authenticator.` : ""}
onCancel={() => { setResetMfaOpen(false); setResetMfaTarget(null); }}
onConfirm={doResetMfa}
/>
<ConfirmDeleteDialog
open={warnOpen}
message={warnMessage}

View File

@@ -1503,10 +1503,16 @@ def api_users_toggle_mfa(username):
cur = conn.cursor()
now = _now_ts()
if enabled:
cur.execute(
"UPDATE users SET mfa_enabled=1, updated_at=? WHERE LOWER(username)=LOWER(?)",
(now, username)
)
if reset_secret:
cur.execute(
"UPDATE users SET mfa_enabled=1, mfa_secret=NULL, updated_at=? WHERE LOWER(username)=LOWER(?)",
(now, username)
)
else:
cur.execute(
"UPDATE users SET mfa_enabled=1, updated_at=? WHERE LOWER(username)=LOWER(?)",
(now, username)
)
else:
if reset_secret:
cur.execute(