TechnitiumSoftware/DnsServer · warning · InvalidOperationException

Time-based one-time password (TOTP) is already disabled for

Error message

Time-based one-time password (TOTP) is already disabled for user: 

What it means

Thrown by User.DisableTOTP() when !_totpEnabled — i.e. TOTP was never enabled (or was already disabled). The library refuses to clear state that is already clear, preventing a misleading 'disabled' success on an account that had no 2FA. _totpKeyUri and _totpEnabled are left untouched on failure.

Source

Thrown at DnsServerCore/Auth/User.cs:319

            if (_totpEnabled)
                throw new InvalidOperationException("Time-based one-time password (TOTP) is already enabled for user: " + _username);

            Authenticator authenticator = new Authenticator(_totpKeyUri);

            if (!authenticator.IsTOTPValid(totp))
                throw new Exception("Invalid time-based one-time password (TOTP) was attempted for user: " + _username);

            _totpEnabled = true;
        }

        public void DisableTOTP()
        {
            if (_isSsoUser)
                throw new InvalidOperationException("Time-based one-time password (TOTP) feature is not available for SSO users.");

            if (!_totpEnabled)
                throw new InvalidOperationException("Time-based one-time password (TOTP) is already disabled for user: " + _username);

            _totpKeyUri = null;
            _totpEnabled = false;
        }

        public void LoggedInFrom(IPAddress remoteAddress)
        {
            if (remoteAddress.IsIPv4MappedToIPv6)
                remoteAddress = remoteAddress.MapToIPv4();

            _previousSessionLoggedOn = _recentSessionLoggedOn;
            _previousSessionRemoteAddress = _recentSessionRemoteAddress;

            _recentSessionLoggedOn = DateTime.UtcNow;
            _recentSessionRemoteAddress = remoteAddress;
        }

        public void AddToGroup(Group group)

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Check the user's current TOTP-enabled state before calling DisableTOTP and treat already-disabled as success.
  2. Make the disable endpoint idempotent: return OK if TOTP is already off.
  3. Refresh user state in the UI after a successful disable so the control reflects reality.

Example fix

// before
user.DisableTOTP();

// after
if (user.TotpEnabled)
    user.DisableTOTP();
Defensive patterns

Strategy: validation

Validate before calling

if (!user.TotpEnabled)
    return Ok("TOTP already disabled."); // idempotent
user.DisableTOTP();

Type guard

static bool NeedsDisableTotp(User user) => user.TotpEnabled;

Prevention

When it happens

Trigger: Calling DisableTOTP on a user who never enrolled, or calling it twice (the second call finds _totpEnabled already false).

Common situations: A 'disable 2FA' admin action run against users who never set it up; a retry after the first disable already succeeded; UI state out of sync with server state.

Related errors


AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13). Data as JSON: /api/errors/ee4aa8db768574df. Report an issue: GitHub.