3572582dd2
1. According to the spec, either we have a single challenge with a number of `signRequests`, or a number of `signRequests`, each with it's own challenge. 2. Previously, we had both these - per-request challenges, as well as a single extra challenge. 3. This commit changes this so that the per-request challenges are removed, leaving only a single challenge, as per the v1.1 U2F API. 4. The existing implementation didn't work in Firefox, because the Firefox (extension) implementation is less flexible with regard to the inputs. 5. Fix teaspoon specs. 6. References: https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-javascript-api.html#h2_background
85 lines
2.7 KiB
Ruby
85 lines
2.7 KiB
Ruby
# == AuthenticatesWithTwoFactor
|
|
#
|
|
# Controller concern to handle two-factor authentication
|
|
#
|
|
# Upon inclusion, skips `require_no_authentication` on `:create`.
|
|
module AuthenticatesWithTwoFactor
|
|
extend ActiveSupport::Concern
|
|
|
|
included do
|
|
# This action comes from DeviseController, but because we call `sign_in`
|
|
# manually, not skipping this action would cause a "You are already signed
|
|
# in." error message to be shown upon successful login.
|
|
skip_before_action :require_no_authentication, only: [:create]
|
|
end
|
|
|
|
# Store the user's ID in the session for later retrieval and render the
|
|
# two factor code prompt
|
|
#
|
|
# The user must have been authenticated with a valid login and password
|
|
# before calling this method!
|
|
#
|
|
# user - User record
|
|
#
|
|
# Returns nil
|
|
def prompt_for_two_factor(user)
|
|
session[:otp_user_id] = user.id
|
|
setup_u2f_authentication(user)
|
|
render 'devise/sessions/two_factor'
|
|
end
|
|
|
|
def authenticate_with_two_factor
|
|
user = self.resource = find_user
|
|
|
|
if user_params[:otp_attempt].present? && session[:otp_user_id]
|
|
authenticate_with_two_factor_via_otp(user)
|
|
elsif user_params[:device_response].present? && session[:otp_user_id]
|
|
authenticate_with_two_factor_via_u2f(user)
|
|
elsif user && user.valid_password?(user_params[:password])
|
|
prompt_for_two_factor(user)
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def authenticate_with_two_factor_via_otp(user)
|
|
if valid_otp_attempt?(user)
|
|
# Remove any lingering user data from login
|
|
session.delete(:otp_user_id)
|
|
|
|
remember_me(user) if user_params[:remember_me] == '1'
|
|
sign_in(user)
|
|
else
|
|
flash.now[:alert] = 'Invalid two-factor code.'
|
|
render :two_factor
|
|
end
|
|
end
|
|
|
|
# Authenticate using the response from a U2F (universal 2nd factor) device
|
|
def authenticate_with_two_factor_via_u2f(user)
|
|
if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge])
|
|
# Remove any lingering user data from login
|
|
session.delete(:otp_user_id)
|
|
session.delete(:challenges)
|
|
|
|
sign_in(user)
|
|
else
|
|
flash.now[:alert] = 'Authentication via U2F device failed.'
|
|
prompt_for_two_factor(user)
|
|
end
|
|
end
|
|
|
|
# Setup in preparation of communication with a U2F (universal 2nd factor) device
|
|
# Actual communication is performed using a Javascript API
|
|
def setup_u2f_authentication(user)
|
|
key_handles = user.u2f_registrations.pluck(:key_handle)
|
|
u2f = U2F::U2F.new(u2f_app_id)
|
|
|
|
if key_handles.present?
|
|
sign_requests = u2f.authentication_requests(key_handles)
|
|
session[:challenge] ||= u2f.challenge
|
|
gon.push(u2f: { challenge: session[:challenge], app_id: u2f_app_id,
|
|
sign_requests: sign_requests })
|
|
end
|
|
end
|
|
end
|