diff --git a/app/controllers/devise/two_factor_authentication_controller.rb b/app/controllers/devise/two_factor_authentication_controller.rb index 7d756a0..5f5b4b4 100644 --- a/app/controllers/devise/two_factor_authentication_controller.rb +++ b/app/controllers/devise/two_factor_authentication_controller.rb @@ -11,6 +11,7 @@ class Devise::TwoFactorAuthenticationController < DeviseController if resource.authenticate_otp(params[:code]) warden.session(resource_name)[:need_two_factor_authentication] = false sign_in resource_name, resource, :bypass => true + set_flash_message :notice, :success redirect_to stored_location_for(resource_name) || :root resource.update_attribute(:second_factor_attempts_count, 0) else diff --git a/config/locales/en.yml b/config/locales/en.yml index ab459a0..9db4c28 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,4 +1,5 @@ en: devise: two_factor_authentication: + success: "Two factor authentication successful." attempt_failed: "Attempt failed." diff --git a/spec/features/two_factor_authenticatable_spec.rb b/spec/features/two_factor_authenticatable_spec.rb new file mode 100644 index 0000000..b16f6fb --- /dev/null +++ b/spec/features/two_factor_authenticatable_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +feature "User of two factor authentication" do + + scenario "must be logged in" do + visit user_two_factor_authentication_path + + page.should have_content("Welcome Home") + end + + context "when logged in" do + let(:user) { create_user } + + background do + login_as user + end + + scenario "can fill in TFA code" do + visit user_two_factor_authentication_path + + page.should have_content("Enter your personal code") + + fill_in "code", with: user.otp_code + click_button "Submit" + + within(".flash.notice") do + expect(page).to have_content("Two factor authentication successful.") + end + end + + scenario "is redirected to TFA when path requires authentication" do + visit dashboard_path + + expect(page).to_not have_content("Your Personal Dashboard") + + fill_in "code", with: user.otp_code + click_button "Submit" + + expect(page).to have_content("Your Personal Dashboard") + end + end +end diff --git a/spec/rails_app/app/controllers/home_controller.rb b/spec/rails_app/app/controllers/home_controller.rb index 95f2992..740af84 100644 --- a/spec/rails_app/app/controllers/home_controller.rb +++ b/spec/rails_app/app/controllers/home_controller.rb @@ -1,4 +1,16 @@ class HomeController < ApplicationController + prepend_before_filter :store_location, only: :dashboard + before_filter :authenticate_user!, only: :dashboard + def index end + + def dashboard + end + + private + + def store_location + store_location_for(:user, dashboard_path) + end end diff --git a/spec/rails_app/app/models/user.rb b/spec/rails_app/app/models/user.rb index 743d176..b7b937f 100644 --- a/spec/rails_app/app/models/user.rb +++ b/spec/rails_app/app/models/user.rb @@ -4,4 +4,8 @@ class User < ActiveRecord::Base :two_factor_authenticatable has_one_time_password + + def send_two_factor_authentication_code + # No op + end end diff --git a/spec/rails_app/app/views/home/dashboard.html.erb b/spec/rails_app/app/views/home/dashboard.html.erb new file mode 100644 index 0000000..d48f903 --- /dev/null +++ b/spec/rails_app/app/views/home/dashboard.html.erb @@ -0,0 +1,5 @@ +

Your Personal Dashboard

+ +

Your email is <%= current_user.email %>

+ +

You will only be able to see this page after successfully completing two factor authentication

diff --git a/spec/rails_app/app/views/home/index.html.erb b/spec/rails_app/app/views/home/index.html.erb index 2085730..43a267a 100644 --- a/spec/rails_app/app/views/home/index.html.erb +++ b/spec/rails_app/app/views/home/index.html.erb @@ -1,2 +1,3 @@ -

Home#index

+

Welcome Home

+

Find me in app/views/home/index.html.erb

diff --git a/spec/rails_app/app/views/layouts/application.html.erb b/spec/rails_app/app/views/layouts/application.html.erb index 7bc3a49..8d56308 100644 --- a/spec/rails_app/app/views/layouts/application.html.erb +++ b/spec/rails_app/app/views/layouts/application.html.erb @@ -7,8 +7,8 @@ <%= csrf_meta_tags %> -

<%= notice %>

-

<%= alert %>

+

<%= notice %>

+

<%= alert %>

<%= yield %> diff --git a/spec/rails_app/config/application.rb b/spec/rails_app/config/application.rb index f633485..8a1ebd2 100644 --- a/spec/rails_app/config/application.rb +++ b/spec/rails_app/config/application.rb @@ -55,6 +55,8 @@ module Dummy config.assets.version = '1.0' config.action_mailer.default_url_options = { host: 'localhost:3000' } + + config.i18n.enforce_available_locales = false end end diff --git a/spec/rails_app/config/routes.rb b/spec/rails_app/config/routes.rb index 4563c92..60cf37d 100644 --- a/spec/rails_app/config/routes.rb +++ b/spec/rails_app/config/routes.rb @@ -1,6 +1,8 @@ Dummy::Application.routes.draw do root to: "home#index" + match "/dashboard", to: "home#dashboard", as: :dashboard + devise_for :users # The priority is based upon order of creation: diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2c61751..07f08df 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,9 +1,7 @@ ENV["RAILS_ENV"] ||= "test" require File.expand_path("../rails_app/config/environment.rb", __FILE__) -require 'two_factor_authentication' - -Dir["#{Dir.pwd}/spec/support/**/*.rb"].each {|f| require f} +require 'rspec/rails' # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| @@ -11,9 +9,13 @@ RSpec.configure do |config| config.run_all_when_everything_filtered = true config.filter_run :focus + config.use_transactional_examples = true + # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = 'random' end + +Dir["#{Dir.pwd}/spec/support/**/*.rb"].each {|f| require f} diff --git a/spec/support/authenticated_model_helper.rb b/spec/support/authenticated_model_helper.rb index f2c6a6a..b469ea6 100644 --- a/spec/support/authenticated_model_helper.rb +++ b/spec/support/authenticated_model_helper.rb @@ -1,18 +1,48 @@ module AuthenticatedModelHelper - class UserWithOverrides < User + class POROUser + extend ActiveModel::Callbacks + include ActiveModel::Validations + include Devise::Models::TwoFactorAuthenticatable + define_model_callbacks :create + attr_accessor :otp_secret_key, :email, :second_factor_attempts_count + + has_one_time_password + end + + class UserWithOverrides < POROUser def send_two_factor_authentication_code "Code sent" end end def create_new_user - User.new + POROUser.new end def create_new_user_with_overrides UserWithOverrides.new end + def create_user(attributes={}) + User.create!(valid_attributes(attributes)) + end + + def valid_attributes(attributes={}) + { + email: generate_unique_email, + password: 'password', + password_confirmation: 'password' + }.merge(attributes) + end + + def generate_unique_email + @@email_count ||= 0 + @@email_count += 1 + "user#{@@email_count}@example.com" + end + end + +RSpec.configuration.send(:include, AuthenticatedModelHelper) diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb new file mode 100644 index 0000000..c112d19 --- /dev/null +++ b/spec/support/capybara.rb @@ -0,0 +1,9 @@ +require 'capybara/rspec' + +Capybara.app = Dummy::Application + +RSpec.configure do |config| + config.before(:each, :feature) do + + end +end diff --git a/spec/support/features_spec_helper.rb b/spec/support/features_spec_helper.rb new file mode 100644 index 0000000..72cc218 --- /dev/null +++ b/spec/support/features_spec_helper.rb @@ -0,0 +1,13 @@ +require 'warden' + +module FeaturesSpecHelper + def warden + request.env['warden'] + end +end + +RSpec.configure do |config| + config.include Warden::Test::Helpers, type: :feature + config.include FeaturesSpecHelper, type: :feature +end + diff --git a/two_factor_authentication.gemspec b/two_factor_authentication.gemspec index 2f1053f..8efe54b 100644 --- a/two_factor_authentication.gemspec +++ b/two_factor_authentication.gemspec @@ -29,7 +29,8 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'randexp' s.add_runtime_dependency 'rotp' - s.add_development_dependency 'rspec-rails' s.add_development_dependency 'bundler' s.add_development_dependency 'rake' + s.add_development_dependency 'rspec-rails' + s.add_development_dependency 'capybara' end