2014/12/11 追記
新しくなった reCAPTCHA が良さそうだったので、それに対応した。
やりたいことは、Twitter や Facebook みたいに数回ログインに失敗したら、そのユーザーでログインするのに CAPTCHA 認証を通さないといけない機能。
devise とかいうクソ gem 1 を使ってしまったせいで苦労したので、書き留めておく。
なお、使う CAPTCHA は reCAPTCHA。
devise の Lockable の設定
Devise.setup do |config|
# ...
# ==> Configuration for :lockable
config.lock_strategy = :failed_attempts
config.unlock_strategy = :none
config.maximum_attempts = 2 # お好きな値に
#...
end
CAPTCHA の表示
アカウントロックされているかどうかを知るだけでも工夫がいる。
ログインの検証を行ってるのは、Devise::SessionsController#create
の warden.authenticate!
だが、warden.authenticate
に変えたところで、アカウントロックされているユーザーのモデルは取得できない。
最初は、warden の Strategy を拡張する方法や、custom failure を使う方法とか小難しいこと考えたけど、Devise::Strategies#validate
で fail!
に渡している認証失敗メッセージを確認することにした。
class SessionsController < Devise::SessionsController
helper_method :access_locked?
# アカウントロックでエラーになったか調べる
def access_locked?
# Devise::Models::Lockable#unauthenticated_message の戻り値が入ってる
warden.message == :locked
end
end
あとは、View に表示させる。
-# CAPTCHA を表示させたいところに追加
- if access_locked?
render "devise/sessions/recapture"
%script{:type => "text/javascript", :src => "//www.google.com/recaptcha/api.js", :defer => true, :async => true}
.g-recaptcha{:"data-sitekey" => your_site_key}
まあ自分で書かなくても、ruby-recaptcha なんかの gem を使ってもいいと思います。
CAPTCHA 認証とロック解除
warden の Strategy を拡張する。
require 'cgi'
require 'net/http'
require 'uri'
require 'devise/strategies/authenticatable'
module Devise
module Strategies
# Devise 標準の DatabaseAuthenticatable に、reCAPTCHA 認証の機能を追加。
class CaptchaAuthenticatable < Authenticatable
def authenticate!
resource = valid_password? && mapping.to.find_for_database_authentication(authentication_hash)
encrypted = false
if validate(resource){ encrypted = true; validate_resource(resource) }
resource.after_database_authentication
success!(resource)
end
mapping.to.new.password = password if !encrypted && Devise.paranoid
fail(:not_found_in_database) unless resource
end
# ロックされたユーザーの CAPTCHA 認証と、パスワードの検証を行う。
def validate_resource(resource)
if resource.access_locked?
return false unless validate_recaptcha
resource.unlock_access! # CAPTCHA 認証が通ったらロック解除
# パスワードまで合ってないとロック解除しないとか、
# いろんな要件があると思うが、このメソッド内に記述すればよい
end
resource.valid_password?(password)
end
protected
# CAPTCHA の検証を行います。
def validate_recaptcha
# CAPTCHA のパラメータがある場合のみ検証する
return false unless params.has_key?("g-recaptcha-response")
res = Net::HTTP.post_form(URI.parse("https://www.google.com/recaptcha/api/siteverify"),
{ :secret => your_secret_key,
:remoteip => CGI.escape(request.remote_ip),
:response => CGI.escape(params["g-recaptcha-response"]) })
results = JSON.parse(res.body)
Rails.logger.info("reCAPTCHA: #{results}")
results["success"]
end
end
end
end
Warden::Strategies.add(:captcha_authenticatable, Devise::Strategies::CaptchaAuthenticatable)
前述した ruby-recaptcha なんかを使ってるなら、validate_recaptcha
の代わりに、その gem で用意されたメソッドを使えばよい。
拡張した Strategy を使うように devise を設定する。
require 'devise/strategies/captcha_authenticatable'
Devise.setup do |config|
# ...
# ==> Warden configuration
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :captcha_authenticatable
end
#...
end
2013/12/26 追記
lib/devise/hooks/lockable.rb
でリセット処理を行っているので、下記処理は不要だった。
class User < ActiveRecord::Base
# override Devise::Models::DatabaseAuthenticatable#after_database_authentication
def after_database_authentication
# lib/devise/hooks/lockable.rb の処理と重複していたのでコメントアウト
#unlock_access! if failed_attempts > 0
end
end
以上。
1. 個人の主観が入っています。