authenticate 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +15 -8
- data/Appraisals +10 -0
- data/CHANGELOG.md +58 -26
- data/Rakefile +14 -0
- data/app/controllers/authenticate/passwords_controller.rb +14 -3
- data/authenticate.gemspec +6 -4
- data/bin/setup +15 -0
- data/gemfiles/4.2.gemfile +7 -0
- data/gemfiles/5.0.gemfile +8 -0
- data/lib/authenticate/configuration.rb +16 -1
- data/lib/authenticate/controller.rb +9 -11
- data/lib/authenticate/version.rb +1 -1
- data/lib/generators/authenticate/install/templates/authenticate.rb +3 -2
- data/spec/controllers/secured_controller_spec.rb +4 -4
- data/spec/dummy/config/application.rb +1 -1
- data/spec/dummy/config/environments/test.rb +3 -2
- data/spec/features/create_user_spec.rb +45 -0
- data/spec/features/new_user_form_spec.rb +26 -0
- data/spec/features/password_reset_spec.rb +3 -1
- data/spec/features/password_update_spec.rb +83 -11
- data/spec/features/sign_in_spec.rb +19 -0
- data/spec/features/sign_out_spec.rb +12 -1
- data/spec/model/password_reset_spec.rb +12 -10
- data/spec/requests/csrf_rotation_spec.rb +39 -0
- data/spec/requests/session_key_spec.rb +42 -0
- data/spec/spec_helper.rb +46 -4
- data/spec/support/features/feature_helpers.rb +5 -1
- data/spec/support/mailer.rb +6 -0
- metadata +50 -17
- data/gemfiles/rails42.gemfile +0 -17
- data/spec/controllers/passwords_controller_spec.rb +0 -117
- data/spec/controllers/sessions_controller_spec.rb +0 -86
- data/spec/controllers/users_controller_spec.rb +0 -82
@@ -46,24 +46,24 @@ describe SecuredAppsController, type: :controller do
|
|
46
46
|
before { sign_in }
|
47
47
|
|
48
48
|
it 'allows access to new' do
|
49
|
-
|
49
|
+
do_get :new
|
50
50
|
expect(subject).to_not deny_access
|
51
51
|
end
|
52
52
|
|
53
53
|
it 'allows access to show' do
|
54
|
-
|
54
|
+
do_get :show
|
55
55
|
expect(subject).to_not deny_access
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
59
|
context 'with an unauthenticated visitor' do
|
60
60
|
it 'allows access to new' do
|
61
|
-
|
61
|
+
do_get :new
|
62
62
|
expect(subject).to_not deny_access
|
63
63
|
end
|
64
64
|
|
65
65
|
it 'denies access to show' do
|
66
|
-
|
66
|
+
do_get :show
|
67
67
|
expect(subject).to deny_access
|
68
68
|
end
|
69
69
|
end
|
@@ -20,7 +20,7 @@ module Dummy
|
|
20
20
|
# config.i18n.default_locale = :de
|
21
21
|
|
22
22
|
# Do not swallow errors in after_commit/after_rollback callbacks.
|
23
|
-
config.active_record.raise_in_transactional_callbacks = true
|
23
|
+
# config.active_record.raise_in_transactional_callbacks = true
|
24
24
|
|
25
25
|
end
|
26
26
|
end
|
@@ -13,8 +13,8 @@ Rails.application.configure do
|
|
13
13
|
config.eager_load = false
|
14
14
|
|
15
15
|
# Configure static file server for tests with Cache-Control for performance.
|
16
|
-
config.serve_static_files = true
|
17
|
-
config.static_cache_control = 'public, max-age=3600'
|
16
|
+
# config.serve_static_files = true
|
17
|
+
# config.static_cache_control = 'public, max-age=3600'
|
18
18
|
|
19
19
|
# Show full error reports and disable caching.
|
20
20
|
config.consider_all_requests_local = true
|
@@ -30,6 +30,7 @@ Rails.application.configure do
|
|
30
30
|
# The :test delivery method accumulates sent emails in the
|
31
31
|
# ActionMailer::Base.deliveries array.
|
32
32
|
config.action_mailer.delivery_method = :test
|
33
|
+
config.action_mailer.perform_deliveries = true
|
33
34
|
|
34
35
|
# Randomize the order test cases are executed.
|
35
36
|
config.active_support.test_order = :random
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/features/feature_helpers'
|
3
|
+
|
4
|
+
feature 'create a user with valid attributes' do
|
5
|
+
|
6
|
+
# this doesn't belong as a feature test but it will catch regressions.
|
7
|
+
# consider moving to a request spec or... something.
|
8
|
+
scenario 'increases number of users' do
|
9
|
+
expect { create_user_with_valid_params }.to change { User.count }.by(1)
|
10
|
+
end
|
11
|
+
|
12
|
+
scenario 'signs in the user after creation' do
|
13
|
+
create_user_with_valid_params
|
14
|
+
expect_user_to_be_signed_in
|
15
|
+
end
|
16
|
+
|
17
|
+
scenario 'redirects to redirect_url' do
|
18
|
+
create_user_with_valid_params
|
19
|
+
expect_path_is_redirect_url
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
feature 'visit a protected url, then create user' do
|
24
|
+
scenario 'redirects to the protected url after user is created' do
|
25
|
+
visit '/welcome'
|
26
|
+
create_user_with_valid_params
|
27
|
+
expect(current_path).to eq '/welcome'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
feature 'create user after signed in' do
|
32
|
+
scenario 'cannot get to new user page' do
|
33
|
+
user = create(:user, email: 'test.user@example.com')
|
34
|
+
sign_in_with user.email, user.password
|
35
|
+
visit new_users_path
|
36
|
+
expect_path_is_redirect_url
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_user_with_valid_params(user_attrs = attributes_for(:user))
|
41
|
+
visit new_users_path
|
42
|
+
fill_in 'user_email', with: user_attrs[:email]
|
43
|
+
fill_in 'user_password', with: user_attrs[:password]
|
44
|
+
click_button 'Sign up'
|
45
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/features/feature_helpers'
|
3
|
+
|
4
|
+
feature 'visitor at new user form, not signed in' do
|
5
|
+
scenario 'visit with no arguments' do
|
6
|
+
visit new_users_path
|
7
|
+
expect(page).to have_current_path new_users_path
|
8
|
+
within 'h2' do
|
9
|
+
expect(page).to have_content /Sign up/i
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
scenario 'defaults email to value provided in query string' do
|
14
|
+
visit new_users_path(user: { email: 'dude@example.com' })
|
15
|
+
expect(page).to have_selector 'input[value="dude@example.com"]'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
feature 'visitor at new user form, already signed in' do
|
20
|
+
scenario 'redirects user to redirect_url' do
|
21
|
+
user = create(:user, email: 'test.user@example.com')
|
22
|
+
sign_in_with 'Test.USER@example.com', user.password
|
23
|
+
visit new_users_path
|
24
|
+
expect_path_is_redirect_url
|
25
|
+
end
|
26
|
+
end
|
@@ -10,6 +10,7 @@ feature 'visitor requests password reset' do
|
|
10
10
|
visit sign_in_path
|
11
11
|
click_link 'Forgot Password'
|
12
12
|
expect(current_path).to eq new_password_path
|
13
|
+
expect(page).to have_content I18n.t("passwords.new.description")
|
13
14
|
end
|
14
15
|
|
15
16
|
scenario 'uses valid email' do
|
@@ -21,11 +22,12 @@ feature 'visitor requests password reset' do
|
|
21
22
|
expect_password_reset_email_for user
|
22
23
|
end
|
23
24
|
|
24
|
-
scenario 'with
|
25
|
+
scenario 'with an unknown email' do
|
25
26
|
request_password_reset_for 'fake.email@example.com'
|
26
27
|
|
27
28
|
expect_password_change_request_success_message
|
28
29
|
expect_mailer_to_have_no_deliveries
|
30
|
+
expect(current_path).to eq sign_in_path
|
29
31
|
end
|
30
32
|
|
31
33
|
scenario 'with invalid email' do
|
@@ -1,39 +1,111 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'support/features/feature_helpers'
|
3
3
|
|
4
|
+
|
5
|
+
feature 'visit password edit screen' do
|
6
|
+
scenario 'with valid token in url, redirects to the edit page with the token removed from the url' do
|
7
|
+
user = create(:user, :with_password_reset_token_and_timestamp)
|
8
|
+
visit_password_reset_page_for(user)
|
9
|
+
expect(current_path).to eq edit_users_password_path(user)
|
10
|
+
expect(current_path).to_not have_content('token')
|
11
|
+
end
|
12
|
+
|
13
|
+
scenario 'with an invalid token in url, failure and prompt to request a password reset' do
|
14
|
+
user = create(:user, :with_password_reset_token_and_timestamp)
|
15
|
+
visit_password_reset_page_for(user, 'this is an invalid token')
|
16
|
+
expect_forbidden_failure
|
17
|
+
end
|
18
|
+
|
19
|
+
scenario 'with a valid token, but an expired timestamp' do
|
20
|
+
user = create(:user, :with_password_reset_token_and_timestamp, password_reset_sent_at: 20.years.ago)
|
21
|
+
visit_password_reset_page_for(user)
|
22
|
+
expect_token_expired_failure
|
23
|
+
end
|
24
|
+
|
25
|
+
scenario 'with a nil token' do
|
26
|
+
user = create(:user)
|
27
|
+
visit_password_reset_page_for(user, token: nil)
|
28
|
+
expect_forbidden_failure
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
4
33
|
feature 'visitor updates password' do
|
5
34
|
before(:each) do
|
6
35
|
@user = create(:user, :with_password_reset_token_and_timestamp)
|
7
36
|
end
|
8
37
|
|
9
|
-
scenario 'with
|
38
|
+
scenario 'with valid password, signs in user' do
|
10
39
|
update_password @user, 'newpassword'
|
11
|
-
|
12
40
|
expect_user_to_be_signed_in
|
13
41
|
end
|
14
42
|
|
15
|
-
scenario 'with a
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
expect_user_to_be_signed_out
|
43
|
+
scenario 'with a valid password, password is updated' do
|
44
|
+
old_pw = @user.encrypted_password
|
45
|
+
update_password @user, 'newpassword'
|
46
|
+
expect_password_was_updated(old_pw)
|
20
47
|
end
|
21
48
|
|
22
|
-
scenario 'signs in
|
49
|
+
scenario 'password change signs in user' do
|
23
50
|
update_password @user, 'newpassword'
|
24
|
-
|
25
51
|
sign_out
|
26
52
|
sign_in_with @user.email, 'newpassword'
|
27
53
|
expect_user_to_be_signed_in
|
28
54
|
end
|
55
|
+
|
56
|
+
scenario 'signs in, redirects user' do
|
57
|
+
update_password @user, 'newpassword'
|
58
|
+
expect_path_is_redirect_url
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
feature 'visitor updates password with invalid password' do
|
63
|
+
before(:each) do
|
64
|
+
@user = create(:user, :with_password_reset_token_and_timestamp)
|
65
|
+
end
|
66
|
+
|
67
|
+
scenario 'with a blank password, signs out user' do
|
68
|
+
update_password @user, ''
|
69
|
+
expect_invalid_password
|
70
|
+
expect_user_to_be_signed_out
|
71
|
+
end
|
72
|
+
|
73
|
+
scenario 'with a short password, flashes invalid password' do
|
74
|
+
update_password @user, 'short'
|
75
|
+
expect_invalid_password
|
76
|
+
expect_user_to_be_signed_out
|
77
|
+
end
|
29
78
|
end
|
30
79
|
|
80
|
+
|
31
81
|
def update_password(user, password)
|
32
82
|
visit_password_reset_page_for user
|
33
83
|
fill_in 'password_reset_password', with: password
|
34
84
|
click_button 'Save this password'
|
35
85
|
end
|
36
86
|
|
37
|
-
def visit_password_reset_page_for(user)
|
38
|
-
visit edit_users_password_path(id: user, token:
|
87
|
+
def visit_password_reset_page_for(user, token = user.password_reset_token)
|
88
|
+
visit edit_users_password_path(id: user, token: token)
|
89
|
+
end
|
90
|
+
|
91
|
+
def expect_invalid_password
|
92
|
+
expect(page).to have_content I18n.t('flashes.failure_after_update')
|
93
|
+
end
|
94
|
+
|
95
|
+
def expect_forbidden_failure
|
96
|
+
expect(page).to have_content I18n.t('passwords.new.description')
|
97
|
+
expect(page).to have_content I18n.t('flashes.failure_when_forbidden')
|
98
|
+
end
|
99
|
+
|
100
|
+
def expect_token_expired_failure
|
101
|
+
expect(page).to have_content 'Sign in'
|
102
|
+
expect(page).to have_content I18n.t('flashes.failure_token_expired')
|
103
|
+
end
|
104
|
+
|
105
|
+
# def expect_path_is_redirect_url
|
106
|
+
# expect(current_path).to eq(Authenticate.configuration.redirect_url)
|
107
|
+
# end
|
108
|
+
|
109
|
+
def expect_password_was_updated(old_password)
|
110
|
+
expect(@user.reload.encrypted_password).not_to eq old_password
|
39
111
|
end
|
@@ -27,3 +27,22 @@ feature 'visitor signs in' do
|
|
27
27
|
expect_user_to_be_signed_out
|
28
28
|
end
|
29
29
|
end
|
30
|
+
|
31
|
+
feature 'visitor goes to sign in page' do
|
32
|
+
scenario 'signed out user is not redirected' do
|
33
|
+
visit sign_in_path
|
34
|
+
expect_sign_in_path
|
35
|
+
end
|
36
|
+
|
37
|
+
scenario 'signed in user is redirected' do
|
38
|
+
user = create(:user)
|
39
|
+
sign_in_with user.email, user.password
|
40
|
+
visit sign_in_path
|
41
|
+
expect_path_is_redirect_url
|
42
|
+
expect_user_to_be_signed_in
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def expect_sign_in_path
|
47
|
+
expect(current_path).to eq sign_in_path
|
48
|
+
end
|
@@ -12,10 +12,21 @@ feature 'visitor signs out' do
|
|
12
12
|
expect_user_to_be_signed_out
|
13
13
|
end
|
14
14
|
|
15
|
-
scenario 'sign out
|
15
|
+
scenario 'sign out again' do
|
16
16
|
sign_in_with(@user.email, @user.password)
|
17
17
|
visit sign_out_path
|
18
18
|
visit sign_out_path
|
19
19
|
expect_user_to_be_signed_out
|
20
20
|
end
|
21
|
+
|
22
|
+
scenario 'redirects to sign in' do
|
23
|
+
sign_in_with(@user.email, @user.password)
|
24
|
+
visit sign_out_path
|
25
|
+
expect_sign_in_path
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def expect_sign_in_path
|
31
|
+
expect(current_path).to eq sign_in_path
|
21
32
|
end
|
@@ -40,7 +40,7 @@ describe Authenticate::Model::PasswordReset do
|
|
40
40
|
context '#update_password' do
|
41
41
|
subject { create(:user) }
|
42
42
|
|
43
|
-
context 'within time
|
43
|
+
context 'within time limit' do
|
44
44
|
before(:each) { subject.password_reset_sent_at = 1.minutes.ago }
|
45
45
|
|
46
46
|
it 'allows password update within time limit' do
|
@@ -57,19 +57,21 @@ describe Authenticate::Model::PasswordReset do
|
|
57
57
|
subject.update_password 'password2'
|
58
58
|
expect(subject.session_token).to_not eq(token)
|
59
59
|
end
|
60
|
-
|
61
|
-
it 'prevents update if token is nil'
|
62
60
|
end
|
63
61
|
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
context 'after time limit' do
|
63
|
+
it 'stops password update' do
|
64
|
+
subject.password_reset_sent_at = 6.minutes.ago
|
65
|
+
expect(subject.update_password('password2')).to be_falsey
|
66
|
+
end
|
67
67
|
end
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
69
|
+
context 'password_reset_sent_at is nil' do
|
70
|
+
it 'stops password update' do
|
71
|
+
subject.password_reset_sent_at = nil
|
72
|
+
subject.password_reset_token = 'notNilResetToken'
|
73
|
+
expect(subject.update_password('password2')).to be_falsey
|
74
|
+
end
|
73
75
|
end
|
74
76
|
end
|
75
77
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'CSRF rotation' do
|
4
|
+
around do |example|
|
5
|
+
ActionController::Base.allow_forgery_protection = true
|
6
|
+
example.run
|
7
|
+
ActionController::Base.allow_forgery_protection = false
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'Authenticate configuration is set to rotate CSRF token on sign in' do
|
11
|
+
describe 'sign in' do
|
12
|
+
before do
|
13
|
+
@user = create(:user, password: 'password')
|
14
|
+
end
|
15
|
+
it 'rotates the CSRF token' do
|
16
|
+
Authenticate.configure { |config| config.rotate_csrf_on_sign_in = true }
|
17
|
+
|
18
|
+
# go to sign in screen, generating csrf
|
19
|
+
get sign_in_path
|
20
|
+
original_token = csrf_token
|
21
|
+
|
22
|
+
# post a login
|
23
|
+
do_post session_path, params: { **session_params }
|
24
|
+
|
25
|
+
# expect that we now have a new csrf token
|
26
|
+
expect(csrf_token).not_to eq original_token
|
27
|
+
expect(csrf_token).to be_present
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def csrf_token
|
33
|
+
session[:_csrf_token]
|
34
|
+
end
|
35
|
+
|
36
|
+
def session_params
|
37
|
+
{ session: { email: @user.email, password: @user.password }, authenticity_token: csrf_token }
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'session key assignment' do
|
4
|
+
context 'user signs in' do
|
5
|
+
before do
|
6
|
+
@user = create(:user)
|
7
|
+
do_post session_path, params: { session: { email: @user.email, password: @user.password } }
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'sets user session token' do
|
11
|
+
@user.reload
|
12
|
+
expect(@user.session_token).to_not be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'sets session token in cookie' do
|
16
|
+
expect(cookies['authenticate_session_token']).to_not be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'sets current_user' do
|
20
|
+
expect(controller.current_user).to eq(@user)
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'user signs out' do
|
24
|
+
it 'rotates user session token' do
|
25
|
+
old_session = @user.session_token
|
26
|
+
do_get sign_out_path
|
27
|
+
@user.reload
|
28
|
+
expect(@user.session_token).to_not eq old_session
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'removes session cookie' do
|
32
|
+
do_get sign_out_path
|
33
|
+
expect(cookies['authenticate_session_token']).to eq ''
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'sets current_user to nil' do
|
37
|
+
do_get sign_out_path
|
38
|
+
expect(controller.current_user).to be_nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|