Подготовить страницу sign_up с условием?

Я хочу задать вопрос пользователю и позволить ему зарегистрироваться только в том случае, если пользователь правильно ответит на мой вопрос. Я искал изобретать практические действия, но мой случай, похоже, не существует.

Есть ли идиоматический способ справиться с этой ситуацией?

Первой мыслью может быть использование javascript, но ответы хранятся в LDAP, и я ожидаю, что с ними будет легче справляться с рельсами.

Я также думал об отключении маршрута /users/sign_up, вызывать действие (devise/registration#new) вручную и отображать представление (devise/registration/new).

Другим способом, который я могу придумать, является запуск фононного демона, который будет собирать идентификатор сеанса, где пользователь правильно ответил на вопросы. При правильном ответе пользователь будет перенаправлен на общедоступную страницу регистрации, которая будет проверять идентификатор сеанса пользователя с помощью демона.

Ответы

Ответ 1

Чтобы улучшить безопасность предыдущих предложений, лучший из них, похоже, является ключевым, но он небезопасен (независимо от того, шифруются ли файлы 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

Дополнительные вещи, чтобы делать/играть с....

  • удалить успешно использованную запись Rails.cache при создании пользователя
  • играйте с настройками :expires_in, если хотите, вы обычно хотите, чтобы это было как можно короче и до тех пор, пока это необходимо:), но рельсы по умолчанию 15 минут довольно хороши.
  • есть более интересные способы обойти эту и подобные проблемы безопасности с помощью куки файлов, а именно вы можете создать объект server_session, который в основном совпадает с session, но сохраняет данные в Rails.cache со случайным сохраненным маркером в session, используемом для доступа к записи в кеш, таким же образом, как и здесь.
  • просто перейдите на сеансы на стороне сервера и не беспокойтесь о безопасности сеанса, но это означает более длительное время отклика из-за вашей поездки Rails.cache (redis, memcache, AR,...)
  • вместо OK в значение кеша вы можете сохранить хэш значений, если вам нужно больше данных, безопасно сохраненных на хосте, для работы с этим
  • ...

Ответ 2

Предполагая, что у вас есть данные 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 для руководства и внедрения комплексного подхода к защите вашего приложения.

Ответ 3

У меня немного другой подход.

  • Покажите вопрос, получите ответ и подтвердите его.
  • Установить зашифрованный сеанс.
  • Переопределите контроллер регистрации разработки таким образом, чтобы, даже если они сразу посетили URL-адрес и попытались зарегистрироваться, они не смогут

    #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
    

мои 2cents

Ответ 4

Итак... Может быть, это должно быть в module Validatable?

  • Сгенерируйте контроллер Validatables с помощью Инструменты
  • Настройте этот контроллер примерно так: (Код этого модуля Вы можете увидеть This)

...

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

Это не выполнено, но я надеюсь, что это поможет вам...

Ответ 5

Я думаю, что самый простой способ сделать это - изменить стандартный модуль разработки с пользовательским с 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, все поведение остается по умолчанию, кроме проверки функциональности ответов.

Относительно вашего вопроса об идиоматическом способе - этот подход был описан в официальной документации . Ваше приложение - ваша логика, все в порядке.

UPDATE:

В комментариях @bbozo указал на некоторые проблемы безопасности с этим и другими ответами. Чтобы сделать его более безопасным, вы можете добавить время истечения срока действия и установить некоторый случайный секретный токен (дополнительная информация в комментариях).