【问题标题】:Twilio - phone number verification on Rails 5Twilio - Rails 5 上的电话号码验证
【发布时间】:2017-10-26 08:45:30
【问题描述】:

我已经通过 Twilio 进行了电话号码验证,但我找不到如何实现再次发送 pin 码的功能(如果用户没有收到)但也不超过 3 次的方法(所以用户不能一遍又一遍地发送代码)。另外,我的代码看起来有点反模式,所以请随意提出更好的实现。

当设计用户注册自己时,我派他创建一个Profile 那个belongs_to 用户。 Profile 保存所有用户信息(和电话号码)。这是表格:

<%= form_for @profile, remote: true do |f| %>

  <%= f.label 'Your name' %><br />
  <%= f.text_field :first_name, autofocus: true, class: 'form-control' %>

  <%= f.label 'Phone number' %><br />
  <%= f.text_field :phone, class: 'form-control' %>
  </br>
  <div id="hideAfterSubmit">
    <%= f.submit 'Save', class: 'btn btn-lg btn-primary btn-block' %>
  </div>

<% end %>

<div id="verify-pin">
<h3>Enter your PIN</h3>
<%= form_tag profiles_verify_path, remote: true do |f| %>
  <div class="form-group">
    <%= text_field_tag :pin %>
  </div>
  <%= submit_tag "Verify PIN", class: "btn btn-primary" %>
  <% end %>
</div>

<div id="status-box" class="alert alert-success">
  <p id="status-message">Status: Haven’t done anything yet</p>
</div> 

#verify-pin 和 #status-box 是 display: none。我通过回复create.js.erb 取消隐藏它们。

创建动作:

  def create
    if user_signed_in? && current_user.profile
      redirect_to profile_path(current_user), notice: 'Jūs jau sukūrėte paskyrą'
    else     
      @profile = Profile.new(profile_params)
      @phone_number = params[:profile][:phone]
      @profile.user_id = current_user.id

      SmsTool.generate_pin
      SmsTool.send_pin(phone_number: @phone_number)

      if @profile.save
        respond_to do |format|
          format.js 
        end
      else
        render :new 
      end
    end
  end  

因此,此时已创建、保存个人资料并生成 pin 码并将其发送到用户刚刚添加的电话号码。

短信工具:

def self.generate_pin
  @@pin = rand(0000..9999).to_s.rjust(4, "0")
  puts "#{@@pin}, Generated"
end    

def self.send_pin(phone_number:)
  @client.messages.create(
    from: ENV['TWILIO_PHONE_NUMBER'],
    to: "+370#{phone_number}",
    body: "Your pin is #{@@pin}"
  )
end   

def self.verify(entered_pin)
  puts "#{@@pin}, pin #{entered_pin} entered"
  if @@pin == entered_pin
    Current.user.profile.update(verified: true) 
  else
    return
  end
end

还有Profiles#verify:

  def verify
    SmsTool.verify(params[:pin])
    @profile = current_user.profile     

    respond_to do |format|
      format.js
    end

    if @profile.verified
      redirect_to root_path, notice: 'Account created'
    end           
  end

所以我不喜欢的是 SmsTool - 正如你所看到的,我使用类变量 - 找不到其他方法。我还创建了一个单独的 Current 模块,只是为了访问 Devise current_user object..:

module Current
  thread_mattr_accessor :user
end

应用控制器:

  around_action :set_current_user

  def set_current_user
    Current.user = current_user
    yield
  ensure
    # to address the thread variable leak issues in Puma/Thin webserver
    Current.user = nil
  end 

正如我上面提到的 - 我无法找到一种方法来实现再次发送 pin 码的功能(如果用户没有收到它)。

请 - 随意建议优雅的实现。

附言这是我最长的帖子了。很抱歉,但我认为需要所有信息才能向您展示。

更新:

所以重新发送 pin 很容易,我只是添加了:

  <div id="hiddenUnlessWrongPin">
    <%= button_to "Re-send pin", action: "send_pin_again" %>
  </div> 

和行动:

  def send_pin_again
    @phone_number = current_user.profile.phone
    SmsTool.generate_pin
    SmsTool.send_pin(phone_number: @phone_number)
  end 

但是如果用户已经发送了三个密码,我仍然不知道如何停止发送密码。我看到的唯一方法是在 db 中使用整数值创建新行,并在每次用户发送 pin 时增加它。这是唯一的方法吗?

【问题讨论】:

  • 要重新发送 pin 我刚刚创建了一个单独的操作,但它太干了,看起来有点乱。所以仍然希望有好的实现。否则将在几个小时内删除
  • 我发现的一件事是您在调用@profile.save 之前先调用SmsTool.send_pin(phone_number: @phone_number),这意味着即使记录无效也会发送密码。
  • 哦,对了,谢谢@max。

标签: ruby-on-rails devise twilio ruby-on-rails-5


【解决方案1】:

一个很好的起点是查看处理电子邮件确认的 Devise::Confirmable 模块。我真正喜欢它的是它将确认建模为一个普通的旧资源。

我会尝试类似的方法,但使用单独的模型,因为它可以很容易地添加基于时间的限制。

class User < ApplicationRecord
  has_one :profile
  has_many :activations, through: :profiles
end

class Profile < ApplicationRecord
  belongs_to :user
  has_many :activations
end

# columns: 
# - pin [int or string]
# - profile_id [int] - foreign_key
# - confirmed_at [datetime]
class Activation < ApplicationRecord
  belongs_to :profile
  has_one :user, through: :profile
  delegate :phone_number, to: :profile
  authenticate :resend_limit, if: :new_record?
  authenticate :valid_pin, unless: :new_record?
  attr_accessor :response_pin
  after_initialize :set_random_pin!, if: :new_record?

  def set_random_pin!
    self.pin = rand(0000..9999).to_s.rjust(4, "0")
  end

  def resend_limit
    if self.profile.activations.where(created_at: (1.day.ago..Time.now)).count >= 3
      errors.add(:base, 'You have reached the maximum allow number of reminders!')
    end
  end

  def valid_pin
    unless response_pin.present? && response_pin == pin
      errors.add(:response_pin, 'Incorrect pin number')
    end
  end

  def send_sms!
    // @todo add logic to send sms
  end
end

随意想出一个更好的名字。此外,这允许您使用普通的旧 Rails 验证来处理逻辑。

然后您可以像任何其他资源一样对其进行 CRUD:

devise_scope :user do
  resources :activations, only: [:new, :create, :edit, :update]
end

class ActivationsController < ApplicationController
  before_action :authenticate_user!
  before_action :set_profile
  before_action :set_activation, only: [:edit, :update]

  # Form to resend a pin notification.
  # GET /users/activations/new
  def new
    @activation = @profile.phone_authentication.new
  end

  # POST /users/activations/new
  def create
    @activation = @profile.phone_authentication.new
    if @activation.save
      @activation.send_sms!
      redirect_to edit_user_phone_activations_path(@activation)
    else
      render :new
    end
  end

  # Renders form where user enters the activation code
  # GET /users/activations/:id/edit
  def edit
  end

  # confirms the users entered the correct pin number.
  # PATCH /users/activations/:id
  def update
    if @activation.update(update_params)
      # cleans up
      @profile.activations.where.not(id: @activation.id).destroy_all
      redirect_to profile_path(@profile), success: 'Your account was activated'
    else
      render :edit
    end
  end

  private 

    def update_params
      params.require(:activation)
            .permit(:response_pin)
            .merge(confirmed_at: Time.now)

    end

    def set_profile
      @profile = current_user.profile
    end

    def set_activation
      @profile.activations.find(params[:id])
    end
end

app/views/activations/new.html.erb:

<%= form_for(@activation) do |f| %>
  <%= f.submit("Send activation to #{@activation.phone_number}") %>
<% end %>

No activation SMS? <%= link_ to "Resend", new_user_activation_path %>

app/views/activations/edit.html.erb:

<%= form_for(@activation) do |f| %>
  <%= f.text_field :response_pin %>
  <%= f.submit("Confirm") %>
<% end %>

【讨论】:

  • 这段代码只是一个如何完成的草图。它完全未经测试,很可能充满错误。
  • 谢谢,这看起来比我的好。我会调查的。一个问题 - 为什么你创建带有感叹号的方法? send_sms!
  • 在 ruby​​ 约定中,您使用 ! 作为警告其他开发人员该方法会改变(更改)对象或可能引发错误的一种警告。
  • 您还可以添加验证,以便激活仅在创建后的有限时间内有效。
  • 嗯,谢谢。想知道为什么我从来不知道它。是的,我可能会进行限时激活。感谢您的建议,我很感激!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-24
  • 1970-01-01
  • 2017-12-24
  • 2012-10-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多