Skip to content

Commit

Permalink
Fix double failed attempts and add error code for invalid otp
Browse files Browse the repository at this point in the history
  • Loading branch information
pdkproitf committed Aug 28, 2018
1 parent f60492b commit c70edc4
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 28 deletions.
40 changes: 26 additions & 14 deletions lib/devise_two_factor/strategies/two_factor_authenticatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,37 @@ module Strategies
class TwoFactorAuthenticatable < Devise::Strategies::DatabaseAuthenticatable

def authenticate!
resource = mapping.to.find_for_database_authentication(authentication_hash)
# We authenticate in two cases:
# 1. The password and the OTP are correct
# 2. The password is correct, and OTP is not required for login
# We check the OTP, then defer to DatabaseAuthenticatable
if validate(resource) { validate_otp(resource) }
super
resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
hashed = false

unless resource && validate(resource){ hashed = true; resource.valid_password?(password) }
mapping.to.new.password = password if !hashed && Devise.paranoid
return fail!(:not_found_in_database)
end

if resource.otp_required_for_login
return fail(:invalid_otp) if skip?

unless validate(resource){ valid_otp?(resource) }
return fail!(:invalid_otp)
end
end

fail(:not_found_in_database) unless resource
remember_me(resource)
resource.after_database_authentication
success!(resource)

mapping.to.new.password = password if !hashed && Devise.paranoid
end

private

# We want to cascade to the next strategy if this one fails,
# but database authenticatable automatically halts on a bad password
@halted = false if @result == :failure
def skip?
params[scope].key?('otp_backup')
end

def validate_otp(resource)
return true unless resource.otp_required_for_login
return if params[scope]['otp_attempt'].nil?
def valid_otp?(resource)
return false if params[scope]['otp_attempt'].nil?
resource.validate_and_consume_otp!(params[scope]['otp_attempt'])
end
end
Expand Down
24 changes: 10 additions & 14 deletions lib/devise_two_factor/strategies/two_factor_backupable.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
module Devise
module Strategies
class TwoFactorBackupable < Devise::Strategies::DatabaseAuthenticatable
class TwoFactorBackupable < Devise::Strategies::TwoFactorAuthenticatable

def authenticate!
resource = mapping.to.find_for_database_authentication(authentication_hash)
private

if validate(resource) { resource.invalidate_otp_backup_code!(params[scope]['otp_attempt']) }
# Devise fails to authenticate invalidated resources, but if we've
# gotten here, the object changed (Since we deleted a recovery code)
resource.save!
super
end

fail(:not_found_in_database) unless resource
def skip?
params[scope].key?('otp_attempt')
end

# We want to cascade to the next strategy if this one fails,
# but database authenticatable automatically halts on a bad password
@halted = false if @result == :failure
def valid_otp_backup?(resource)
return false if params[scope]['otp_backup'].nil?
resource.invalidate_otp_backup_code!(params[scope]['otp_backup']) && resource.save
end

alias_method :valid_otp?, :valid_otp_backup?
end
end
end
Expand Down

0 comments on commit c70edc4

Please sign in to comment.