authenticate 0.5.0 → 0.6.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 +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
|