diff --git a/Rakefile b/Rakefile
index 7723dfbb..9a575025 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,3 +1,9 @@
+begin
+ require 'bundler/setup'
+rescue LoadError
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
+end
+
require "bundler/gem_tasks"
APP_RAKEFILE = File.expand_path("../spec/rails_app/Rakefile", __FILE__)
diff --git a/lib/two_factor_authentication/controllers/helpers.rb b/lib/two_factor_authentication/controllers/helpers.rb
index 64e8377c..501f4d9a 100644
--- a/lib/two_factor_authentication/controllers/helpers.rb
+++ b/lib/two_factor_authentication/controllers/helpers.rb
@@ -20,14 +20,15 @@ def handle_two_factor_authentication
end
def handle_failed_second_factor(scope)
- if request.format.present?
- if request.format.html?
- session["#{scope}_return_to"] = request.original_fullpath if request.get?
- redirect_to two_factor_authentication_path_for(scope)
- elsif request.format.json?
- session["#{scope}_return_to"] = root_path(format: :html)
- render json: { redirect_to: two_factor_authentication_path_for(scope) }, status: :unauthorized
- end
+ if request.format&.html?
+ session["#{scope}_return_to"] = request.original_fullpath if request.get?
+ redirect_to two_factor_authentication_path_for(scope)
+ elsif request.format&.json?
+ session["#{scope}_return_to"] = root_path(format: :html)
+ render json: {
+ redirect_to: two_factor_authentication_path_for(scope),
+ authentication_type: send("current_#{scope}")&.direct_otp ? :otp : :totp
+ }, status: :unauthorized
else
head :unauthorized
end
diff --git a/lib/two_factor_authentication/models/two_factor_authenticatable.rb b/lib/two_factor_authentication/models/two_factor_authenticatable.rb
index 6d73a0fb..d23cae87 100644
--- a/lib/two_factor_authentication/models/two_factor_authenticatable.rb
+++ b/lib/two_factor_authentication/models/two_factor_authenticatable.rb
@@ -101,7 +101,7 @@ def generate_totp_secret
def create_direct_otp(options = {})
# Create a new random OTP and store it in the database
digits = options[:length] || self.class.direct_otp_length || 6
- update_attributes(
+ update(
direct_otp: random_base10(digits),
direct_otp_sent_at: Time.now.utc
)
@@ -122,7 +122,7 @@ def direct_otp_expired?
end
def clear_direct_otp
- update_attributes(direct_otp: nil, direct_otp_sent_at: nil)
+ update(direct_otp: nil, direct_otp_sent_at: nil)
end
end
diff --git a/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb b/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb
index 6fb4f505..6e58fd1d 100644
--- a/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb
+++ b/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb
@@ -138,7 +138,7 @@ def instance.send_two_factor_authentication_code(code)
it "returns uri with user's email" do
expect(instance.provisioning_uri).
- to match(%r{otpauth://totp/houdini@example.com\?secret=\w{32}})
+ to match(%r{otpauth://totp/houdini%40example.com\?secret=\w{32}})
end
it 'returns uri with issuer option' do
diff --git a/spec/rails_app/app/assets/config/manifest.js b/spec/rails_app/app/assets/config/manifest.js
new file mode 100644
index 00000000..a3d7d420
--- /dev/null
+++ b/spec/rails_app/app/assets/config/manifest.js
@@ -0,0 +1,2 @@
+//= link application.css
+//= link application.js
diff --git a/spec/rails_app/app/controllers/home_controller.rb b/spec/rails_app/app/controllers/home_controller.rb
index f55da102..7007d886 100644
--- a/spec/rails_app/app/controllers/home_controller.rb
+++ b/spec/rails_app/app/controllers/home_controller.rb
@@ -5,6 +5,15 @@ def index
end
def dashboard
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: {success: true}
+ end
+ format.xml do
+ render xml: ""
+ end
+ end
end
end
diff --git a/spec/rails_app/app/models/guest_user.rb b/spec/rails_app/app/models/guest_user.rb
index 8003624c..1222279a 100644
--- a/spec/rails_app/app/models/guest_user.rb
+++ b/spec/rails_app/app/models/guest_user.rb
@@ -7,7 +7,7 @@ class GuestUser
attr_accessor :direct_otp, :direct_otp_sent_at, :otp_secret_key, :email,
:second_factor_attempts_count, :totp_timestamp
- def update_attributes(attrs)
+ def update(attrs)
attrs.each do |key, value|
send(key.to_s + '=', value)
end
diff --git a/spec/requests/api_request_spec.rb b/spec/requests/api_request_spec.rb
new file mode 100644
index 00000000..c7b48ad8
--- /dev/null
+++ b/spec/requests/api_request_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe "API request", type: :request do
+ context "when logged in" do
+ let(:user) { create_user("encrypted", email: "foo@example.com", otp_secret_key: "6iispf5cjufa4vsm") }
+
+ before do
+ sign_in user
+ end
+
+ context "with json" do
+ context "with totp authentication" do
+ it "returns 401 when path requires authentication" do
+ get "/dashboard.json"
+ expect(response.response_code).to eq(401)
+ body = JSON.parse(response.body)
+ expect(body["redirect_to"]).to eq(user_two_factor_authentication_path)
+ expect(body["authentication_type"]).to eq("totp")
+ end
+ end
+
+ context "with direct otp authentication" do
+ it "returns 401 when path requires authentication" do
+ user.update!(direct_otp: true)
+ get "/dashboard.json"
+ expect(response.response_code).to eq(401)
+ body = JSON.parse(response.body)
+ expect(body["redirect_to"]).to eq(user_two_factor_authentication_path)
+ expect(body["authentication_type"]).to eq("otp")
+ end
+ end
+
+ context "after TFA" do
+ it "returns successfully" do
+ get "/dashboard.json"
+ expect(response.response_code).to eq(401)
+ expect(JSON.parse(response.body)["redirect_to"]).to eq(user_two_factor_authentication_path)
+ totp_code = ROTP::TOTP.new(user.otp_secret_key, digits: 6).at(Time.now)
+ put "/users/two_factor_authentication", params: { code: totp_code }
+ get "/dashboard.json"
+ expect(response.response_code).to eq(200)
+ body = JSON.parse(response.body)
+ expect(body["success"]).to eq(true)
+ end
+ end
+ end
+
+ context "with xml" do
+ it "returns 401 when path requires authentication" do
+ get "/dashboard.xml"
+ expect(response.response_code).to eq(401)
+ end
+
+ context "after TFA" do
+ it "returns successfully" do
+ totp_code = ROTP::TOTP.new(user.otp_secret_key, digits: 6).at(Time.now)
+ put "/users/two_factor_authentication", params: { code: totp_code }
+ get "/dashboard.xml"
+ expect(response.response_code).to eq(200)
+ expect(response.body).to eq("")
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 63704333..02ac6723 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -21,6 +21,8 @@
config.order = 'random'
config.after(:each) { Timecop.return }
+
+ config.include Devise::Test::IntegrationHelpers, type: :request
end
Dir["#{Dir.pwd}/spec/support/**/*.rb"].each {|f| require f}