Reactivate deactivated users signing in with 2FA enforced

What does this MR do and why?

Skips the check_two_factor_requirement authorization gate on the sign-in action (POST /users/sign_in), in addition to the sign-out action. One line in app/controllers/sessions_controller.rb:

- skip_before_action :check_two_factor_requirement, only: [:destroy]
+ skip_before_action :check_two_factor_requirement, only: [:create, :destroy]

Why

The gate is a post-authentication check, but it ran as a before_action on sign-in itself. It reads current_user, which authenticates via params as a side effect, then redirects users without 2FA to the enrollment page — halting the chain before Devise's create body runs.

Reactivation lives in that body, so a deactivated user never got reactivated, and the enrollment page's active_user_check signed them straight back out. A correct password returned them to the sign-in page with "Your account has been deactivated by your administrator", every time.

Enforcement is unchanged: the redirect happens on the next request instead, and every other controller still runs the gate.

Behaviour change

create's body now runs for 2FA-enforced users who haven't enrolled, so these stop being skipped:

  • sign-in audit events and AuthenticationEvent rows
  • unknown sign-in emails
  • pending invitation acceptance
  • password-reset token invalidation
  • user_session_logins_total

In my opinion, these should always have fired - those users were already authenticated, with sign_in_count and current_sign_in_at recorded. Self-managed admins will see new audit events, and some users will get unknown sign-in emails they weren't getting before.

References

Screenshots or screen recordings

Before After
image image

How to set up and validate locally

  1. On master, open the Rails console and create a deactivated user in a group that enforces 2FA:

    password = 'quiet-harbor-92-Kx'
    
    user = User.new(
      username: 'repro486718',
      name: 'Repro 486718',
      email: 'repro486718@example.com',
      password: password,
      password_confirmation: password,
      organization: Organizations::Organization.default_organization
    )
    user.assign_personal_namespace(Organizations::Organization.default_organization)
    user.skip_confirmation!
    user.save!
    
    group = Group.new(
      name: 'Repro 486718 Group',
      path: 'repro486718-group',
      require_two_factor_authentication: true,
      two_factor_grace_period: 0,
      organization: Organizations::Organization.default_organization
    )
    group.save!
    group.add_developer(user)
    
    user.update_columns(
      require_two_factor_authentication_from_group: true,
      otp_grace_period_started_at: 1.year.ago
    )
    
    user.deactivate!

    Enforcement is on a group rather than instance-wide so it doesn't drag your own admin account into 2FA enrollment. The grace period is zeroed and backdated so there's no "Configure it later" link. require_two_factor_authentication_from_group is set directly because the worker that maintains it skips deactivated members.

  2. In a private window, sign in at /users/sign_in as repro486718 / quiet-harbor-92-Kx. You land back on the sign-in page with the deactivation message. Try again — same result.

  3. Confirm the account was never reactivated:

    User.find_by_username('repro486718').state # => "deactivated"
  4. Check out this branch and restart Rails:

    git checkout fix/reactivate-deactivated-users-with-2fa-enforced
    gdk restart rails-web
  5. Sign in again in a fresh private window. You now land on /-/profile/two_factor_auth with the enrollment form.

  6. Confirm the account was reactivated:

    User.find_by_username('repro486718').state # => "active"
  7. Clean up:

    User.find_by_username('repro486718').destroy
    Group.find_by_path('repro486718-group').destroy

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Anton Smith

Merge request reports

Loading
Loading