Skip to content

feat(mfa)!: enable MFA by default and make it optional#682

Merged
lakhansamani merged 1 commit into
mainfrom
feat/mfa-default-on
Jul 12, 2026
Merged

feat(mfa)!: enable MFA by default and make it optional#682
lakhansamani merged 1 commit into
mainfrom
feat/mfa-default-on

Conversation

@lakhansamani

Copy link
Copy Markdown
Contributor

Summary

Makes MFA on by default and optional per user, and fixes the config contradiction behind the "all MFA services are disabled" error (issue class S1685).

Previously --enforce-mfa defaulted to true while --enable-mfa / --enable-totp-login / --enable-email-otp / --enable-sms-otp all defaulted to false. Enforcement flagged users' IsMultiFactorAuthEnabled=true at signup, but login never actually challenged because the global + per-method flags were off — MFA was "enforced" yet non-functional, and re-enabling after a disable threw "cannot enable multi factor authentication as all mfa services are disabled".

What changed

Flags (breaking):

Removed Replaced by Default
--enable-totp-login --disable-totp-login TOTP on
--enable-email-otp --disable-email-otp on when SMTP configured
--enable-sms-otp --disable-sms-otp on when Twilio configured
--enable-mfa (removed — derived)
--enforce-mfa (was true) --enforce-mfa now false

Behavior:

  • MFA methods are enabled by default; operators opt out with --disable-*.
  • Email/SMS OTP only take effect when their provider is configured — login already ANDs the service-enabled flags; meta now reflects it.
  • EnableMFA is derived (Config.Finalize()) = TOTP || (emailOTP && emailSvc) || (smsOTP && smsSvc), so MFA can never read "enabled" while every method is unavailable.
  • Optional by default: users may skip enrollment at signup (IsMultiFactorAuthEnabled=false) and enable later via profile edit — no new DB field; the existing bool is the record.
  • --enforce-mfa forces it when set (unchanged signup enforcement logic, only the default flips).

Wiring: derivation runs in a root-command PersistentPreRun so authorizer and authorizer mcp stay consistent (the inline service-availability derivation moved into Config.Finalize()).

Auth-gating logic is untouched

login.go / verify_email.go / resend_otp.go / meta.go still read the positive Enable* fields — only how those fields are populated changed. Zero changes to the challenge logic.

Testing

  • New unit test TestFinalizeMFADerivation — full availability truth table (TOTP-only, provider-gated email/SMS, all-disabled).
  • Integration suite (TestMFA*, TestSignup*, TestLogin*, TestUpdateProfile*, TestUpdateUser*) passes on SQLite.
  • Runtime-verified end to end: default config → /meta reports is_multi_factor_auth_enabled: true (via TOTP); --disable-totp-login with no providers → false.
  • gofmt clean, golangci-lint 0 issues, go vet clean.

Migration

Existing deployments passing the removed --enable-* flags must switch to the --disable-* inverse, and any relying on the implicit --enforce-mfa=true default must now pass it explicitly. Documented in CHANGELOG under Unreleased. Acceptable for the 2.4.0-rc line.

MFA methods are now on by default and opted out via --disable-totp-login,
--disable-email-otp, --disable-sms-otp; the old --enable-* / --enable-mfa
flags are removed. Availability is derived from the enabled methods in
Config.Finalize() (wired via the root PersistentPreRun so `mcp` is
consistent), fixing the state where MFA read "enabled" while every method
was unavailable. Email/SMS OTP only count when their provider is
configured. --enforce-mfa now defaults to false: MFA is optional and
skippable, enable later via profile edit; set --enforce-mfa to require it.

BREAKING CHANGE: --enable-totp-login, --enable-mfa, --enable-email-otp,
--enable-sms-otp removed; --enforce-mfa default flips true -> false.
@lakhansamani lakhansamani force-pushed the feat/mfa-default-on branch from fd405c3 to 4b16d48 Compare July 12, 2026 05:02
@lakhansamani lakhansamani merged commit d63fe15 into main Jul 12, 2026
4 checks passed
lakhansamani added a commit that referenced this pull request Jul 12, 2026
#682 removed the --enable-* MFA flags in favor of default-on methods
opted out via --disable-*, and made EnableMFA derived. Update the
admin/self-service error messages, dashboard tooltip, and schema/comment
wording to reference the current flag surface instead of the removed
--enable-mfa/--enable-totp-login/--enable-email-otp/--enable-sms-otp.
Regenerate GraphQL for the reworded schema description.

No behavior change: isMFAServiceAvailable still gates on the same derived
availability condition.
@lakhansamani lakhansamani deleted the feat/mfa-default-on branch July 12, 2026 06:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant