【问题标题】:Devise not confirming email on update设计在更新时不确认电子邮件
【发布时间】:2022-01-03 14:26:21
【问题描述】:

我正在使用设备进行身份验证。我正在覆盖设计令牌生成器,以便我可以使用 6 位代码并覆盖它,以便我可以支持手机号码确认。

如果用户使用电子邮件注册并且 OTP 是通过电子邮件发送的。注册似乎工作正常。用户使用电子邮件注册。发送 OTP 并在确认后确认用户。

但是当用户尝试更新电子邮件时。我正在使用相同的方法来发送确认码(就像在注册时一样),用户被保存在 unconfirmed_email 中。邮件通过电子邮件发送,但确认后不会将用户电子邮件从 unconfirmed_email 字段复制到电子邮件字段。

这可能是什么问题。

app/services/users/confirmation_code_sender.rb

# frozen_string_literal: true

module Users
  class ConfirmationCodeSender
    attr_reader :user

    def initialize(id:)
      @user = User.find(id)
    end

    # rubocop :disable Metrics/AbcSize
    def call
      generate_confirmation_token!

      if user.email?
        DeviseMailer.confirmation_instructions(
          user,
          user.confirmation_token,
          { to: user.unconfirmed_email || user.email }
        ).deliver_now
      else
        Telco::Web::Sms.send_text(recipient: user.unconfirmed_mobile || user.mobile_number, message: sms_text)
      end
    end
    # rubocop :enable Metrics/AbcSize

    private

    def generate_confirmation_token!
      user.confirmation_token = TokenGenerator.token(6)
      user.confirmation_sent_at = DateTime.current
      user.save!(validate: false)
    end

    def sms_text
      I18n.t('sms.confirmation_token', token: user.confirmation_token)
    end
  end
end

app/services/users/phone_or_email_updater.rb

# frozen_string_literal: true

module Users
  class PhoneOrEmailUpdater < BaseService
    def call
      authorize!(current_user, to: :user?)

      current_user.tap do |user|
        user.update!(unconfirmed_mobile: params[:unconfirmed_mobile], unconfirmed_email: params[:unconfirmed_email])
        ConfirmationCodeSender.new(id: user.id).call
      end
    end
  end
end

config/nitializers/confirmable.rb

# frozen_string_literal: true

# Overriding this model to support the confirmation for mobile number as well

module Devise
  module Models
    module Confirmable
      def confirm(args = {})
        pending_any_confirmation do
          return expired_error if confirmation_period_expired?

          self.confirmed_at = Time.now.utc
          saved = saved(args)
          after_confirmation if saved
          saved
        end
      end

      def saved(args)
        @saved ||= if pending_reconfirmation?
                     skip_reconfirmation!
                     save!(validate: true)
                   else
                     save!(validate: args[:ensure_valid] == true)
                   end
      end

      def pending_reconfirmation?
        if unconfirmed_email.present?
          self.email = unconfirmed_email
          self.unconfirmed_email = nil
          true
        elsif unconfirmed_mobile.present?
          self.mobile_number = unconfirmed_mobile
          self.unconfirmed_mobile = nil
          true
        else
          false
        end
      end

      private

      def expired_error
        errors.add(
          :email,
          :confirmation_period_expired,
          period: Devise::TimeInflector.time_ago_in_words(self.class.confirm_within.ago)
        )
        false
      end
    end
  end
end

移动更新似乎工作正常,但电子邮件没有更新。我正在使用 graphql 更新电子邮件

在控制台中我尝试使用.confirm,但它似乎无法正常工作,用户电子邮件未得到确认

【问题讨论】:

    标签: ruby-on-rails ruby devise devise-confirmable


    【解决方案1】:

    在您的pending_reconfirmation? 中,self.unconfirmed_email 被分配为nil。好像pending_reconfirmation? 只在saved 中被调用,然而它也被pending_any_confirmation 调用。

    https://github.com/heartcombo/devise/blob/8593801130f2df94a50863b5db535c272b00efe1/lib/devise/models/confirmable.rb#L238

    # Checks whether the record requires any confirmation.
    def pending_any_confirmation
      if (!confirmed? || pending_reconfirmation?)
        yield
      else
        self.errors.add(:email, :already_confirmed)
        false
      end
    end
    

    那么当pending_reconfirmation? 第二次在saved 中被调用时,pending_reconfirmation?将返回 false,因为 unconfirmed_email 为 nil。

    您最好不要在以? 结尾的方法内进行实际分配,这将是一个隐含的副作用。也许创建一个以! 结尾的新方法来更改属性的值。

    例如:

    module Devise
      module Models
        module Confirmable
          def confirm(args = {})
            pending_any_confirmation do
              return expired_error if confirmation_period_expired?
    
              self.confirmed_at = Time.now.utc
              saved = saved(args)
              after_confirmation if saved
              saved
            end
          end
    
          def saved(args)
            @saved ||= if pending_reconfirmation?
              reconfirm_email! if unconfirmed_email.present?
              reconfirm_mobile! if unconfirmed_mobile.present?
              skip_reconfirmation!
              save!(validate: true)
            else
              save!(validate: args[:ensure_valid] == true)
            end
          end
    
          def pending_reconfirmation?
            unconfirmed_email.present? || nconfirmed_mobile.present?
          end
    
          def reconfirm_email!
            self.email = unconfirmed_email
            self.unconfirmed_email = nil
          end
    
          def reconfirm_mobile!
            self.mobile_number = unconfirmed_mobile
            self.unconfirmed_mobile = nil
          end
    
          private
    
          def expired_error
            errors.add(
              :email,
              :confirmation_period_expired,
              period: Devise::TimeInflector.time_ago_in_words(self.class.confirm_within.ago)
            )
            false
          end
        end
      end
    end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-22
      • 1970-01-01
      • 1970-01-01
      • 2012-03-10
      • 2019-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多