Consolidate "email OTP required?" logic
The following discussion from !210998 (merged) should be addressed:
-
@bdenkovych started a discussion: (+1 comment) suggestion (non-blocking): @nmalcolm I am wondering whether we can use this method within
- https://gitlab.com/gitlab-org/gitlab/-/blob/162e7f20f962af59c7ed763b0a75d33f1564d4c3/app/controllers/concerns/verifies_with_email.rb#L246-L256
- https://gitlab.com/gitlab-org/gitlab/-/blob/162e7f20f962af59c7ed763b0a75d33f1564d4c3/app/helpers/sessions_helper.rb#L37-L41
for consistency? That will also allow us easily identify all places within the application where Email OTP requirement is applicable.
User
Just checks if the FF is enabled and the date is in the past
def email_based_otp_required?
Feature.enabled?(:email_based_mfa, self) &&
!!email_otp_required_after&.past?
end
VerifiesWithEmail Controller Check
Allows future-dated, for graceful rollout. Rejects when hasn't signed in at least once.
# Checks whether email-based OTP is required for the current sign-in
# attempt.
#
# This feature uses a two-part rollout mechanism:
# - Feature Flag acts as a kill switch that can be quickly disabled
# via ChatOps
# - User enrollment is controlled by setting the attribute
# email_otp_required_after
#
# This allows us to halt or revert the rollout immediately while
# preserving per-user enrollment dates.
#
# Later, the Feature Flag will be changed to an ApplicationSetting so
# that self-managed administrators can turn this feature on after
# validating that they have mail delivery correctly configured.
def require_email_based_otp?(user)
return false unless Feature.enabled?(:email_based_mfa, user)
password_based_login? &&
# Skip on first log in (which occurs for most during account
# creation), to avoid double email verification with
# Devise::Confirmable
user.last_sign_in_at.present? &&
user.email_otp_required_after.present? &&
(user.email_otp_required_after <= Time.zone.now || in_email_otp_grace_period?(user))
end
Sessions Helper
Requires it to be in the past too, plus checks "not locked".
def fallback_to_email_otp_permitted?(user)
Feature.enabled?(:email_based_mfa, user) &&
user.email_otp_required_after&.past? &&
!treat_as_locked?(user)
end
Proposal
- Consolidate the common logic on the User model, and re-use it