authenticate 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|