authenticate 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -4
  3. data/CHANGELOG.md +12 -0
  4. data/Gemfile +1 -0
  5. data/Gemfile.lock +21 -6
  6. data/app/controllers/authenticate/passwords_controller.rb +1 -1
  7. data/authenticate.gemspec +7 -3
  8. data/config/locales/authenticate.en.yml +1 -1
  9. data/gemfiles/rails42.gemfile +6 -1
  10. data/lib/authenticate/callbacks/brute_force.rb +3 -4
  11. data/lib/authenticate/configuration.rb +2 -2
  12. data/lib/authenticate/controller.rb +1 -2
  13. data/lib/authenticate/model/brute_force.rb +2 -2
  14. data/lib/authenticate/model/db_password.rb +2 -3
  15. data/lib/authenticate/model/email.rb +3 -6
  16. data/lib/authenticate/model/lifetimed.rb +1 -1
  17. data/lib/authenticate/model/password_reset.rb +1 -1
  18. data/lib/authenticate/model/timeoutable.rb +2 -2
  19. data/lib/authenticate/model/trackable.rb +1 -1
  20. data/lib/authenticate/model/username.rb +1 -1
  21. data/lib/authenticate/session.rb +0 -4
  22. data/lib/authenticate/user.rb +12 -0
  23. data/lib/authenticate/version.rb +1 -1
  24. data/spec/controllers/passwords_controller_spec.rb +119 -0
  25. data/spec/controllers/secured_controller_spec.rb +70 -0
  26. data/spec/controllers/sessions_controller_spec.rb +86 -0
  27. data/spec/controllers/users_controller_spec.rb +82 -0
  28. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  29. data/spec/dummy/app/controllers/welcome_controller.rb +4 -0
  30. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  31. data/spec/dummy/app/views/welcome/index.html.erb +4 -0
  32. data/spec/dummy/config/application.rb +2 -0
  33. data/spec/dummy/config/environments/production.rb +12 -0
  34. data/spec/dummy/config/initializers/authenticate.rb +4 -11
  35. data/spec/dummy/config/routes.rb +3 -0
  36. data/spec/dummy/db/test.sqlite3 +0 -0
  37. data/spec/factories/users.rb +2 -4
  38. data/spec/features/brute_force_spec.rb +49 -0
  39. data/spec/features/max_session_lifetime_spec.rb +30 -0
  40. data/spec/features/password_reset_spec.rb +69 -0
  41. data/spec/features/password_update_spec.rb +41 -0
  42. data/spec/features/sign_in_spec.rb +29 -0
  43. data/spec/features/sign_out_spec.rb +22 -0
  44. data/spec/features/sign_up_spec.rb +42 -0
  45. data/spec/features/timeoutable_spec.rb +30 -0
  46. data/spec/model/brute_force_spec.rb +26 -29
  47. data/spec/model/configuration_spec.rb +61 -0
  48. data/spec/model/db_password_spec.rb +8 -9
  49. data/spec/model/email_spec.rb +0 -1
  50. data/spec/model/lifetimed_spec.rb +6 -18
  51. data/spec/model/password_reset_spec.rb +2 -9
  52. data/spec/model/session_spec.rb +16 -23
  53. data/spec/model/timeoutable_spec.rb +8 -7
  54. data/spec/model/trackable_spec.rb +0 -1
  55. data/spec/model/user_spec.rb +1 -2
  56. data/spec/spec_helper.rb +33 -131
  57. data/spec/support/controllers/controller_helpers.rb +24 -0
  58. data/spec/support/features/feature_helpers.rb +36 -0
  59. metadata +80 -8
  60. data/spec/configuration_spec.rb +0 -60
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+ require 'support/controllers/controller_helpers'
3
+
4
+ # Matcher that asserts user was denied access.
5
+ RSpec::Matchers.define :deny_access do
6
+ match do |controller|
7
+ redirects_to_sign_in?(controller) && sets_flash?(controller)
8
+ end
9
+ def redirects_to_sign_in? controller
10
+ expect(controller).to redirect_to(controller.sign_in_url)
11
+ end
12
+ def sets_flash? controller
13
+ controller.flash[:notice].match /sign in to continue/
14
+ end
15
+ end
16
+
17
+
18
+ class SecuredAppsController < ActionController::Base
19
+ include Authenticate::Controller
20
+
21
+ before_action :require_authentication, only: :show
22
+
23
+ def new
24
+ head :ok
25
+ end
26
+
27
+ def show
28
+ head :ok
29
+ end
30
+ end
31
+
32
+
33
+ describe SecuredAppsController, type: :controller do
34
+ before do
35
+ Rails.application.routes.draw do
36
+ resource :secured_app, only: [:new, :show]
37
+ get '/sign_in' => 'authenticate/sessions#new', as: 'sign_in'
38
+ end
39
+ end
40
+
41
+ after do
42
+ Rails.application.reload_routes!
43
+ end
44
+
45
+ context 'with authenticated user' do
46
+ before { sign_in }
47
+
48
+ it 'allows access to new' do
49
+ get :new
50
+ expect(subject).to_not deny_access
51
+ end
52
+
53
+ it 'allows access to show' do
54
+ get :show
55
+ expect(subject).to_not deny_access
56
+ end
57
+ end
58
+
59
+ context 'with an unauthenticated visitor' do
60
+ it 'allows access to new' do
61
+ get :new
62
+ expect(subject).to_not deny_access
63
+ end
64
+
65
+ it 'denies access to show' do
66
+ get :show
67
+ expect(subject).to deny_access
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+ require 'support/controllers/controller_helpers'
3
+
4
+ describe Authenticate::SessionsController, type: :controller do
5
+ it { is_expected.to be_a Authenticate::Controller }
6
+
7
+ describe 'get to #new' do
8
+ context 'when user not signed in' do
9
+ before do
10
+ get :new
11
+ end
12
+ it { is_expected.to respond_with 200 }
13
+ it { is_expected.to render_template :new }
14
+ it { is_expected.not_to set_flash }
15
+ end
16
+
17
+ context 'when user is signed in' do
18
+ before do
19
+ sign_in
20
+ get :new
21
+ end
22
+
23
+ it { is_expected.not_to set_flash }
24
+ it { is_expected.to redirect_to(Authenticate.configuration.redirect_url) }
25
+ end
26
+ end
27
+
28
+ describe 'post to #create' do
29
+ context 'without password' do
30
+ it 'renders page with error' do
31
+ user = create(:user)
32
+ post :create, session: { email: user.email }
33
+ expect(response).to render_template :new
34
+ expect(flash[:notice]).to match(/Invalid id or password/)
35
+ end
36
+ end
37
+ context 'with good password' do
38
+ before do
39
+ @user = create(:user)
40
+ post :create, session:{ email: @user.email, password: @user.password }
41
+ end
42
+ it { is_expected.to respond_with 302 }
43
+
44
+ it { is_expected.to redirect_to Authenticate.configuration.redirect_url }
45
+
46
+ it 'sets user session token' do
47
+ @user.reload
48
+ expect(@user.session_token).to_not be_nil
49
+ end
50
+
51
+ it 'sets user session' do
52
+ expect(controller.current_user).to eq(@user)
53
+ end
54
+ end
55
+ end
56
+
57
+ describe 'delete to #destroy' do
58
+ context 'with a signed out user' do
59
+ before do
60
+ sign_out
61
+ get :destroy
62
+ end
63
+
64
+ it { is_expected.to redirect_to sign_in_url }
65
+ end
66
+ context 'with a session cookie' do
67
+ before do
68
+ @user = create(:user, session_token: 'old-session-token')
69
+ @request.cookies['authenticate_session_token'] = 'old-session-token'
70
+ get :destroy
71
+ end
72
+
73
+ it { is_expected.to redirect_to sign_in_url }
74
+
75
+ it 'reset the session token' do
76
+ @user.reload
77
+ expect(@user.session_token).to_not eq('old-session-token')
78
+ end
79
+
80
+ it 'unset the current user' do
81
+ expect(controller.current_user).to be_nil
82
+ end
83
+ end
84
+ end
85
+
86
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+ require 'support/controllers/controller_helpers'
3
+
4
+ describe Authenticate::UsersController, type: :controller do
5
+ it { is_expected.to be_a Authenticate::Controller }
6
+
7
+ describe 'get to #new' do
8
+ context 'not signed in' do
9
+ it 'renders form' do
10
+ get :new
11
+ expect(response).to be_success
12
+ expect(response).to render_template(:new)
13
+ end
14
+ it 'defaults email field to the value provided in the query string' do
15
+ get :new, user: { email: 'dude@example.com' }
16
+ expect(assigns(:user).email).to eq 'dude@example.com'
17
+ expect(response).to be_success
18
+ expect(response).to render_template(:new)
19
+ end
20
+ end
21
+ context 'signed in' do
22
+ it 'redirects user to the redirect_url' do
23
+ sign_in
24
+ get :new
25
+ expect(response).to redirect_to Authenticate.configuration.redirect_url
26
+ end
27
+ end
28
+ end
29
+ describe 'post to #create' do
30
+ context 'not signed in' do
31
+ context 'with valid attributes' do
32
+ let(:user_attributes) { attributes_for(:user) }
33
+ subject { post :create, user: user_attributes }
34
+
35
+ it 'creates user' do
36
+ expect{ subject }.to change{ User.count }.by(1)
37
+ end
38
+
39
+ it 'assigned user' do
40
+ subject
41
+ expect(assigns(:user)).to be_present
42
+ end
43
+
44
+ it 'redirects to the redirect_url' do
45
+ subject
46
+ expect(response).to redirect_to Authenticate.configuration.redirect_url
47
+ end
48
+ end
49
+
50
+ context 'with valid attributes and a session return cookie' do
51
+ before do
52
+ @request.cookies[:authenticate_return_to] = '/url_in_the_session'
53
+ end
54
+ let(:user_attributes) { attributes_for(:user) }
55
+ subject { post :create, user: user_attributes }
56
+
57
+ it 'creates user' do
58
+ expect{ subject }.to change{ User.count }.by(1)
59
+ end
60
+
61
+ it 'assigned user' do
62
+ subject
63
+ expect(assigns(:user)).to be_present
64
+ end
65
+
66
+ it 'redirects to the redirect_url' do
67
+ subject
68
+ expect(response).to redirect_to '/url_in_the_session'
69
+ end
70
+ end
71
+
72
+ end
73
+ context 'signed in' do
74
+ it 'redirects to redirect_url' do
75
+ sign_in
76
+ post :create, user: {}
77
+ expect(response).to redirect_to Authenticate.configuration.redirect_url
78
+ end
79
+ end
80
+ end
81
+
82
+ end
@@ -1,5 +1,7 @@
1
1
  class ApplicationController < ActionController::Base
2
2
  include Authenticate::Controller
3
+ before_action :require_authentication
4
+
3
5
  # Prevent CSRF attacks by raising an exception.
4
6
  # For APIs, you may want to use :null_session instead.
5
7
  protect_from_forgery with: :exception
@@ -0,0 +1,4 @@
1
+ class WelcomeController < ApplicationController
2
+ def index
3
+ end
4
+ end
@@ -8,6 +8,20 @@
8
8
  </head>
9
9
  <body>
10
10
 
11
+ <div id="header">
12
+ <% if authenticated? -%>
13
+ <%= link_to t(".sign_out"), sign_out_path %>
14
+ <% else -%>
15
+ <%= link_to t(".sign_in"), sign_in_path %>
16
+ <% end -%>
17
+ </div>
18
+
19
+ <div id="flash">
20
+ <% flash.each do |key, value| -%>
21
+ <div id="flash_<%= key %>"><%=h value %></div>
22
+ <% end %>
23
+ </div>
24
+
11
25
  <%= yield %>
12
26
 
13
27
  </body>
@@ -0,0 +1,4 @@
1
+ <h1>Welcome#index</h1>
2
+ <p>Find me in app/views/welcome/index.html.erb</p>
3
+
4
+ <%= link_to "Sign out", sign_out_path %>
@@ -21,6 +21,8 @@ module Dummy
21
21
 
22
22
  # Do not swallow errors in after_commit/after_rollback callbacks.
23
23
  config.active_record.raise_in_transactional_callbacks = true
24
+
24
25
  end
25
26
  end
26
27
 
28
+ Rails.application.routes.default_url_options[:host] = 'localhost:3000'
@@ -64,6 +64,17 @@ Rails.application.configure do
64
64
  # Set this to true and configure the email server for immediate delivery to raise delivery errors.
65
65
  # config.action_mailer.raise_delivery_errors = false
66
66
 
67
+
68
+
69
+
70
+ # Tell Action Mailer not to deliver emails to the real world.
71
+ # The :test delivery method accumulates sent emails in the
72
+ # ActionMailer::Base.deliveries array.
73
+ config.action_mailer.delivery_method = :test
74
+
75
+
76
+
77
+
67
78
  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
68
79
  # the I18n.default_locale when a translation cannot be found).
69
80
  config.i18n.fallbacks = true
@@ -77,3 +88,4 @@ Rails.application.configure do
77
88
  # Do not dump schema after migrations.
78
89
  config.active_record.dump_schema_after_migration = false
79
90
  end
91
+
@@ -1,15 +1,8 @@
1
1
  Authenticate.configure do |config|
2
- # config.user_model = 'User'
3
- # config.cookie_name = 'authenticate_session_token'
4
- # config.cookie_expiration = { 1.month.from_now.utc }
5
- # config.cookie_domain = nil
6
- # config.cookie_path = '/'
7
- # config.secure_cookie = false # set to true in production https environments
8
- # config.http_only = false # set to true if you can
9
2
  config.timeout_in = 45.minutes
10
- config.max_session_lifetime = 5.minutes
11
- config.max_consecutive_bad_logins_allowed = 1
12
- config.bad_login_lockout_period = 2.minutes
3
+ config.max_session_lifetime = 20.minutes
4
+ config.max_consecutive_bad_logins_allowed = 2
5
+ config.bad_login_lockout_period = 10.minutes
13
6
  config.reset_password_within = 5.minutes
14
- # config.authentication_strategy = :email
7
+ config.password_length = 8..128
15
8
  end
@@ -1,4 +1,7 @@
1
1
  Rails.application.routes.draw do
2
+ resources :welcome, only: [:index]
3
+ root 'welcome#index'
4
+
2
5
  # The priority is based upon order of creation: first created -> highest priority.
3
6
  # See how all your routes lay out with "rake routes".
4
7
 
Binary file
@@ -1,5 +1,3 @@
1
- require 'authenticate/user'
2
-
3
1
  FactoryGirl.define do
4
2
  sequence :email do |n|
5
3
  "user#{n}@example.com"
@@ -7,7 +5,6 @@ FactoryGirl.define do
7
5
 
8
6
  factory :user do
9
7
  email
10
- # encrypted_password 'password'
11
8
  password 'password'
12
9
 
13
10
  trait :without_email do
@@ -23,8 +20,9 @@ FactoryGirl.define do
23
20
  session_token 'this_is_a_big_fake_long_token'
24
21
  end
25
22
 
26
- trait :with_forgotten_password do
23
+ trait :with_password_reset_token_and_timestamp do
27
24
  password_reset_token Authenticate::Token.new
25
+ password_reset_sent_at 10.seconds.ago
28
26
  end
29
27
  end
30
28
  end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'support/features/feature_helpers'
3
+
4
+ feature 'visitor has consecutive bad logins' do
5
+ before do
6
+ # puts Authenticate.configuration.max_consecutive_bad_logins_allowed.inspect
7
+ # puts Authenticate.configuration.bad_login_lockout_period.inspect
8
+ @user = create(:user)
9
+ end
10
+
11
+ scenario 'less than max bad logins does not lock account' do
12
+ sign_in_with @user.email, 'badpassword'
13
+ sign_in_with @user.email, 'badpassword'
14
+ sign_in_with @user.email, @user.password
15
+
16
+ expect_user_to_be_signed_in
17
+ end
18
+
19
+ scenario 'exceeds max bad logins and locks account' do
20
+ sign_in_with @user.email, 'badpassword'
21
+ sign_in_with @user.email, 'badpassword'
22
+ sign_in_with @user.email, 'badpassword'
23
+
24
+ expect_locked_account
25
+ expect_lockout_time_to_be_displayed
26
+ expect_user_to_be_signed_out
27
+ end
28
+
29
+ scenario 'user locks account, waits for lock to expire, logs in successfully' do
30
+ sign_in_with @user.email, 'badpassword'
31
+ sign_in_with @user.email, 'badpassword'
32
+ sign_in_with @user.email, 'badpassword'
33
+
34
+ Timecop.travel 50.minutes do
35
+ sign_in_with @user.email, @user.password
36
+ expect_user_to_be_signed_in
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+
43
+ def expect_locked_account
44
+ expect(page).to have_content 'Your account is locked'
45
+ end
46
+
47
+ def expect_lockout_time_to_be_displayed
48
+ expect(page).to have_content '10 minutes'
49
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+ require 'support/features/feature_helpers'
3
+
4
+ feature 'visitor has consecutive bad logins' do
5
+ before(:each) do
6
+ @user = create(:user)
7
+ end
8
+
9
+ scenario 'visitor logs in and subsequent click within lifetime' do
10
+ sign_in_with @user.email, @user.password
11
+ expect_user_to_be_signed_in
12
+
13
+ Timecop.travel 10.minutes do
14
+ visit root_path
15
+ expect_user_to_be_signed_in
16
+ end
17
+ end
18
+
19
+ scenario 'visitor logs in and lets session live too long' do
20
+ sign_in_with @user.email, @user.password
21
+ expect_user_to_be_signed_in
22
+
23
+ Timecop.travel 21.minutes do
24
+ visit root_path
25
+ expect(current_path).to eq sign_in_path
26
+ expect_user_to_be_signed_out
27
+ end
28
+ end
29
+
30
+ end