在条件前面添加条件?

时间:2015-12-25 15:26:39

标签: ruby-on-rails devise

我想问用户一个问题,如果用户正确回答了我的问题,请让他只注册 。我搜查了devise how-to acticles,但我的情况似乎并不存在。

有没有一种惯用的方法来处理这种情况?

第一个想法可能是使用javascript,但答案存储在LDAP中,我希望在rails中更容易处理。

我还在考虑禁用/users/sign_up路由,手动调用操作(devise/registration#new)并渲染视图(devise/registration/new)。

我能想到的另一种方法是运行一个后台守护进程,它将收集会话ID,用户可以正确回答问题。在正确答案中,用户将被重定向到公共可用的注册页面,该页面将使用守护程序检查用户的会话ID。

5 个答案:

答案 0 :(得分:6)

假设你已经签署了cookie数据(这是Rails 3中的默认值),你可以像你说的那样做并使用会话:

# app/controllers/preauth_controller.rb
def new
end

def create
  if params[:answer] == 'correct answer'
    session[:preauthorized] = true
    redirect_to sign_up_path
  end
  flash[:error] = 'Incorrect answer'
  render :new
end


# app/controllers/users_controller.rb
before_filter :verify_preauth, only: [:new, :create]

def verify_preauth
  redirect_to new_preauth_path unless session[:preauthorized]
end

如果cookie数据未签名,则preauthorized密钥可能会被客户端篡改,因此不应被信任。

如果您的网页在传输过程中使用HTTPS通过TLS进行加密,并且您没有任何XSS漏洞,那么这应该足以满足您的需求。如果您认为这是一段特别敏感的代码,那么您需要的不仅仅是StackOverflow用户的想法,而是指导并实施一种全面的方法来保护您的应用程序。

答案 1 :(得分:4)

我的方法有点不同。

  1. 显示问题,接收回复并进行验证。
  2. 设置加密会话。
  3. 用这个覆盖设计的注册控制器,这样即使他们直接访问网址并尝试注册,他们也无法

    #app/controllers/registration_controller.rb
    class RegistrationsController < Devise::RegistrationsController
     before_filter :check_answered_or_not,only:[:create,:new]
      def check_answered_or_not
       if not session[:answered]==true
          redirect_to question_path
       end
      end
      private
      def sign_up_params
        params.require(:user).permit(:name,:phone,:password,:password_confirmation,:email)
      end
      def account_update_params
         params.require(:user).permit(:name,:phone,:password,:password_confirmation,:email,:current_password)
     end
    end
    
  4. 我的2cents

答案 2 :(得分:4)

为了提高以前建议的安全性,最好的建议似乎是通过coreyward,但它是不安全的(无论cookie是否加密 - 请参阅我对OP的评论)

# app/controllers/preauth_controller.rb
def new
end

def create
  if params[:answer] == 'correct answer'
    # Create a secret value, the `token`, we will share it to the client
    # through the `session` and store it on the server in `Rails.cache`
    # (or you can use the database if you like)
    #
    # The fact that this token expires (by default) in 15 minutes is
    # a bonus, it will secure the system against later use of a stolen
    # cookie. The token is also secure against brute force attack
    @token = SecureRandom.base64
    session[:preauthorization_token] = @token
    Rails.cache.write("users/preauthorization_tokens/#{@token}", "OK")
    redirect_to sign_up_path
  else
    flash[:error] = 'Incorrect answer'
    render :new
  end
end


# app/controllers/users_controller.rb
before_filter :verify_preauth, only: [:new, :create]

def verify_preauth
  # When we approve preauthorization we confirm that the
  # `token` is known to the client, if the client knows the token
  # let him sign up, else make him go away
  token = session[:preauthorization_token]
  redirect_to new_preauth_path unless token and Rails.cache.read("users/preauthorization_tokens/#{token}") == "OK"
end

可选择的事情/玩....

  1. 在创建用户时删除成功使用的Rails.cache条目
  2. 如果你想要玩:expires_in设置,你通常希望它尽可能短,只要需要:)但轨道默认为15分钟是相当不错的
  3. 有更好的方法解决这个问题以及类似的Cookie安全问题 - 即您可以创建一个与server_session基本相同的session对象,但将数据存储在Rails.cache中使用存储在session中的随机可过期令牌,用于访问缓存条目的方式与我们在此处的方式非常相似
  4. 只需转到服务器端会话而不用担心会话安全性,但这意味着由于Rails.cache往返(redis,memcache,AR,...)而导致响应时间更长
  5. 而不是OK进入缓存值,您可以存储值的哈希值,如果您需要更多数据,安全地存储在主机上,可以解决这个问题
  6. ...

答案 3 :(得分:3)

所以......可能应该在module Validatable

  1. 使用此Tools
  2. 生成Validatables控制器
  3. 自定义此控制器,如下所示: (此模块的代码您可以看到This
  4. ...

    base.class_eval do
              validates_presence_of   :email, if: :email_required?
              validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
              validates_format_of     :email, with: email_regexp, allow_blank: true, if: :email_changed?
    
              validates_presence_of     :password, if: :password_required?
              validates_confirmation_of :password, if: :password_required?
              validates_length_of       :password, within: password_length, allow_blank: true
    
              validates_presence_of     :question, if: :question_required?
              validates_format_of       :question, with: answered_regexp, if: :answered_changed?          
            end
          end
    
    ...
      def email_required?
        true
      end
      def question_required?
        true
      end
    

    这不是解决方案,但我希望它可以帮助你...

答案 4 :(得分:1)

我认为最简单的方法是更改​​默认设计控制器,其中包含before_action的自定义控制器:

# routes.rb
devise_for :users, :controllers => {:registrations => "registrations"}

使用以下控制器实现:

# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
  before_action :check_answers, only: :new # :new action is responsible for :sign_up route

  private

  def check_answers
    unless session[:gave_answers]
      redirect_to ask_questions_path
      false
    end
  end
end 

设置这样的会话:

# somewhere in questions controller:
if answers_correct?
  session[:gave_answers] = true
  redirect_to new_registration_path
end

一旦此控制器继承自Devise::RegistrationsController,除检查答案功能外,所有行为都保持默认状态。

关于关于惯用方式的问题 - official documentation中描述了这种方法。你的应用 - 你的逻辑,没关系。

更新:

在评论中@bbozo指出了这个和其他答案的某些安全问题。为了更安全,您可以添加expiration time并设置一些随机密钥令牌(评论中的更多信息)。