feat: add MFA disable (self-service + admin reset) endpoints

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-26 09:40:57 +00:00
parent 661f1ce552
commit fc609401c3
3 changed files with 57 additions and 0 deletions

View file

@ -170,6 +170,34 @@ def update_user(
return user return user
@router.delete("/users/{user_id}/mfa")
def admin_reset_mfa(
user_id: int,
request: Request,
admin: User = Depends(require_admin),
db: Session = Depends(get_db),
):
"""Admin: reset MFA on a user's account."""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
user.mfa_secret = None
user.mfa_enabled = False
db.commit()
log_action(
db,
user_id=admin.id,
action="mfa_reset",
entity_type="user",
entity_id=user.id,
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)
return {"detail": "MFA reset"}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Invitations # Invitations
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View file

@ -13,6 +13,7 @@ from app.models.user import User
from app.schemas.auth import ( from app.schemas.auth import (
ChangePasswordRequest, ChangePasswordRequest,
LoginRequest, LoginRequest,
MFADisableRequest,
MFASetupResponse, MFASetupResponse,
MFAVerifyRequest, MFAVerifyRequest,
RefreshRequest, RefreshRequest,
@ -25,6 +26,7 @@ from app.services.auth_service import (
authenticate_user, authenticate_user,
change_password, change_password,
create_tokens, create_tokens,
disable_mfa,
refresh_access_token, refresh_access_token,
register_user, register_user,
revoke_refresh_token, revoke_refresh_token,
@ -139,6 +141,17 @@ def mfa_verify(
return {"detail": "MFA enabled"} return {"detail": "MFA enabled"}
@router.delete("/mfa")
def mfa_disable(
data: MFADisableRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Disable MFA on the authenticated user's account (requires password)."""
disable_mfa(db, current_user, data.password)
return {"detail": "MFA disabled"}
@router.post("/change-password") @router.post("/change-password")
def change_password_endpoint( def change_password_endpoint(
data: ChangePasswordRequest, data: ChangePasswordRequest,

View file

@ -319,3 +319,19 @@ def update_profile(
db.commit() db.commit()
db.refresh(user) db.refresh(user)
return user return user
# ---------------------------------------------------------------------------
# MFA disable
# ---------------------------------------------------------------------------
def disable_mfa(db: Session, user: User, password: str) -> None:
"""Disable MFA on the user's account after verifying their password."""
if not verify_password(password, user.password_hash):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Incorrect password",
)
user.mfa_secret = None
user.mfa_enabled = False
db.commit()