authenticate 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +21 -6
- data/app/controllers/authenticate/passwords_controller.rb +1 -1
- data/authenticate.gemspec +7 -3
- data/config/locales/authenticate.en.yml +1 -1
- data/gemfiles/rails42.gemfile +6 -1
- data/lib/authenticate/callbacks/brute_force.rb +3 -4
- data/lib/authenticate/configuration.rb +2 -2
- data/lib/authenticate/controller.rb +1 -2
- data/lib/authenticate/model/brute_force.rb +2 -2
- data/lib/authenticate/model/db_password.rb +2 -3
- data/lib/authenticate/model/email.rb +3 -6
- data/lib/authenticate/model/lifetimed.rb +1 -1
- data/lib/authenticate/model/password_reset.rb +1 -1
- data/lib/authenticate/model/timeoutable.rb +2 -2
- data/lib/authenticate/model/trackable.rb +1 -1
- data/lib/authenticate/model/username.rb +1 -1
- data/lib/authenticate/session.rb +0 -4
- data/lib/authenticate/user.rb +12 -0
- data/lib/authenticate/version.rb +1 -1
- data/spec/controllers/passwords_controller_spec.rb +119 -0
- data/spec/controllers/secured_controller_spec.rb +70 -0
- data/spec/controllers/sessions_controller_spec.rb +86 -0
- data/spec/controllers/users_controller_spec.rb +82 -0
- data/spec/dummy/app/controllers/application_controller.rb +2 -0
- data/spec/dummy/app/controllers/welcome_controller.rb +4 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/welcome/index.html.erb +4 -0
- data/spec/dummy/config/application.rb +2 -0
- data/spec/dummy/config/environments/production.rb +12 -0
- data/spec/dummy/config/initializers/authenticate.rb +4 -11
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/factories/users.rb +2 -4
- data/spec/features/brute_force_spec.rb +49 -0
- data/spec/features/max_session_lifetime_spec.rb +30 -0
- data/spec/features/password_reset_spec.rb +69 -0
- data/spec/features/password_update_spec.rb +41 -0
- data/spec/features/sign_in_spec.rb +29 -0
- data/spec/features/sign_out_spec.rb +22 -0
- data/spec/features/sign_up_spec.rb +42 -0
- data/spec/features/timeoutable_spec.rb +30 -0
- data/spec/model/brute_force_spec.rb +26 -29
- data/spec/model/configuration_spec.rb +61 -0
- data/spec/model/db_password_spec.rb +8 -9
- data/spec/model/email_spec.rb +0 -1
- data/spec/model/lifetimed_spec.rb +6 -18
- data/spec/model/password_reset_spec.rb +2 -9
- data/spec/model/session_spec.rb +16 -23
- data/spec/model/timeoutable_spec.rb +8 -7
- data/spec/model/trackable_spec.rb +0 -1
- data/spec/model/user_spec.rb +1 -2
- data/spec/spec_helper.rb +33 -131
- data/spec/support/controllers/controller_helpers.rb +24 -0
- data/spec/support/features/feature_helpers.rb +36 -0
- metadata +80 -8
- 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
|
@@ -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>
|
@@ -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 =
|
11
|
-
config.max_consecutive_bad_logins_allowed =
|
12
|
-
config.bad_login_lockout_period =
|
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
|
-
|
7
|
+
config.password_length = 8..128
|
15
8
|
end
|
data/spec/dummy/config/routes.rb
CHANGED
data/spec/dummy/db/test.sqlite3
CHANGED
Binary file
|
data/spec/factories/users.rb
CHANGED
@@ -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 :
|
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
|