rodauth 0.10.0 → 1.0.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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +146 -0
  3. data/README.rdoc +644 -220
  4. data/Rakefile +99 -11
  5. data/doc/account_expiration.rdoc +55 -0
  6. data/doc/base.rdoc +104 -0
  7. data/doc/change_login.rdoc +29 -0
  8. data/doc/change_password.rdoc +26 -0
  9. data/doc/close_account.rdoc +31 -0
  10. data/doc/confirm_password.rdoc +22 -0
  11. data/doc/create_account.rdoc +34 -0
  12. data/doc/disallow_password_reuse.rdoc +37 -0
  13. data/doc/email_base.rdoc +19 -0
  14. data/doc/jwt.rdoc +35 -0
  15. data/doc/lockout.rdoc +83 -0
  16. data/doc/login.rdoc +27 -0
  17. data/doc/login_password_requirements_base.rdoc +50 -0
  18. data/doc/logout.rdoc +21 -0
  19. data/doc/otp.rdoc +100 -0
  20. data/doc/password_complexity.rdoc +50 -0
  21. data/doc/password_expiration.rdoc +52 -0
  22. data/doc/password_grace_period.rdoc +10 -0
  23. data/doc/recovery_codes.rdoc +60 -0
  24. data/doc/release_notes/1.0.0.txt +443 -0
  25. data/doc/remember.rdoc +82 -0
  26. data/doc/reset_password.rdoc +70 -0
  27. data/doc/session_expiration.rdoc +27 -0
  28. data/doc/single_session.rdoc +43 -0
  29. data/doc/sms_codes.rdoc +119 -0
  30. data/doc/two_factor_base.rdoc +27 -0
  31. data/doc/verify_account.rdoc +70 -0
  32. data/doc/verify_account_grace_period.rdoc +15 -0
  33. data/doc/verify_change_login.rdoc +9 -0
  34. data/lib/roda/plugins/rodauth.rb +3 -262
  35. data/lib/rodauth.rb +260 -0
  36. data/lib/rodauth/features/account_expiration.rb +108 -0
  37. data/lib/rodauth/features/base.rb +479 -0
  38. data/lib/rodauth/features/change_login.rb +77 -0
  39. data/lib/rodauth/features/change_password.rb +66 -0
  40. data/lib/rodauth/features/close_account.rb +82 -0
  41. data/lib/rodauth/features/confirm_password.rb +51 -0
  42. data/lib/rodauth/features/create_account.rb +128 -0
  43. data/lib/rodauth/features/disallow_password_reuse.rb +82 -0
  44. data/lib/rodauth/features/email_base.rb +63 -0
  45. data/lib/rodauth/features/jwt.rb +151 -0
  46. data/lib/rodauth/features/lockout.rb +262 -0
  47. data/lib/rodauth/features/login.rb +61 -0
  48. data/lib/rodauth/features/login_password_requirements_base.rb +123 -0
  49. data/lib/rodauth/features/logout.rb +37 -0
  50. data/lib/rodauth/features/otp.rb +338 -0
  51. data/lib/rodauth/features/password_complexity.rb +89 -0
  52. data/lib/rodauth/features/password_expiration.rb +111 -0
  53. data/lib/rodauth/features/password_grace_period.rb +46 -0
  54. data/lib/rodauth/features/recovery_codes.rb +240 -0
  55. data/lib/rodauth/features/remember.rb +200 -0
  56. data/lib/rodauth/features/reset_password.rb +207 -0
  57. data/lib/rodauth/features/session_expiration.rb +55 -0
  58. data/lib/rodauth/features/single_session.rb +87 -0
  59. data/lib/rodauth/features/sms_codes.rb +498 -0
  60. data/lib/rodauth/features/two_factor_base.rb +135 -0
  61. data/lib/rodauth/features/verify_account.rb +232 -0
  62. data/lib/rodauth/features/verify_account_grace_period.rb +76 -0
  63. data/lib/rodauth/features/verify_change_login.rb +20 -0
  64. data/lib/rodauth/migrations.rb +130 -0
  65. data/lib/rodauth/version.rb +9 -0
  66. data/spec/account_expiration_spec.rb +90 -0
  67. data/spec/all.rb +1 -0
  68. data/spec/change_login_spec.rb +149 -0
  69. data/spec/change_password_spec.rb +177 -0
  70. data/spec/close_account_spec.rb +162 -0
  71. data/spec/confirm_password_spec.rb +70 -0
  72. data/spec/create_account_spec.rb +127 -0
  73. data/spec/disallow_password_reuse_spec.rb +84 -0
  74. data/spec/lockout_spec.rb +228 -0
  75. data/spec/login_spec.rb +188 -0
  76. data/spec/migrate/001_tables.rb +103 -16
  77. data/spec/migrate/002_account_password_hash_column.rb +11 -0
  78. data/spec/migrate_password/001_tables.rb +60 -42
  79. data/spec/migrate_travis/001_tables.rb +116 -0
  80. data/spec/password_complexity_spec.rb +108 -0
  81. data/spec/password_expiration_spec.rb +243 -0
  82. data/spec/password_grace_period_spec.rb +93 -0
  83. data/spec/remember_spec.rb +424 -0
  84. data/spec/reset_password_spec.rb +185 -0
  85. data/spec/rodauth_spec.rb +57 -980
  86. data/spec/session_expiration_spec.rb +58 -0
  87. data/spec/single_session_spec.rb +107 -0
  88. data/spec/spec_helper.rb +202 -0
  89. data/spec/two_factor_spec.rb +1310 -0
  90. data/spec/verify_account_grace_period_spec.rb +135 -0
  91. data/spec/verify_account_spec.rb +142 -0
  92. data/spec/verify_change_login_spec.rb +46 -0
  93. data/spec/views/login.str +2 -2
  94. data/templates/add-recovery-codes.str +2 -0
  95. data/templates/button.str +5 -0
  96. data/templates/change-login.str +5 -18
  97. data/templates/change-password.str +6 -14
  98. data/templates/close-account.str +3 -6
  99. data/templates/confirm-password.str +4 -14
  100. data/templates/create-account.str +6 -30
  101. data/templates/login-confirm-field.str +6 -0
  102. data/templates/login-field.str +6 -0
  103. data/templates/login.str +5 -19
  104. data/templates/logout.str +2 -6
  105. data/templates/otp-auth-code-field.str +6 -0
  106. data/templates/otp-auth.str +8 -0
  107. data/templates/otp-disable.str +6 -0
  108. data/templates/otp-setup.str +21 -0
  109. data/templates/password-confirm-field.str +6 -0
  110. data/templates/password-field.str +6 -0
  111. data/templates/recovery-auth.str +12 -0
  112. data/templates/recovery-codes.str +6 -0
  113. data/templates/remember.str +8 -12
  114. data/templates/reset-password-request.str +2 -2
  115. data/templates/reset-password.str +4 -18
  116. data/templates/sms-auth.str +6 -0
  117. data/templates/sms-code-field.str +6 -0
  118. data/templates/sms-confirm.str +7 -0
  119. data/templates/sms-disable.str +7 -0
  120. data/templates/sms-request.str +5 -0
  121. data/templates/sms-setup.str +12 -0
  122. data/templates/unlock-account-request.str +3 -7
  123. data/templates/unlock-account.str +4 -7
  124. data/templates/verify-account-resend.str +2 -2
  125. data/templates/verify-account.str +2 -6
  126. metadata +191 -29
  127. data/lib/roda/plugins/rodauth/base.rb +0 -428
  128. data/lib/roda/plugins/rodauth/change_login.rb +0 -48
  129. data/lib/roda/plugins/rodauth/change_password.rb +0 -42
  130. data/lib/roda/plugins/rodauth/close_account.rb +0 -42
  131. data/lib/roda/plugins/rodauth/create_account.rb +0 -92
  132. data/lib/roda/plugins/rodauth/lockout.rb +0 -292
  133. data/lib/roda/plugins/rodauth/login.rb +0 -81
  134. data/lib/roda/plugins/rodauth/logout.rb +0 -36
  135. data/lib/roda/plugins/rodauth/remember.rb +0 -226
  136. data/lib/roda/plugins/rodauth/reset_password.rb +0 -205
  137. data/lib/roda/plugins/rodauth/verify_account.rb +0 -228
@@ -0,0 +1,162 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe 'Rodauth close_account feature' do
4
+ it "should support closing accounts when passwords are not required" do
5
+ rodauth do
6
+ enable :login, :close_account
7
+ close_account_requires_password? false
8
+ end
9
+ roda do |r|
10
+ r.rodauth
11
+ r.root{view(:content=>"")}
12
+ end
13
+
14
+ login
15
+ page.current_path.must_equal '/'
16
+
17
+ visit '/close-account'
18
+ click_button 'Close Account'
19
+ page.current_path.must_equal '/'
20
+
21
+ DB[:accounts].select_map(:status_id).must_equal [3]
22
+ end
23
+
24
+ it "should update account information when closing accounts" do
25
+ statuses = nil
26
+ rodauth do
27
+ enable :login, :close_account
28
+ close_account_requires_password? false
29
+ after_close_account{statuses = [account[:status_id], account_ds.get(:status_id)]}
30
+ end
31
+ roda do |r|
32
+ r.rodauth
33
+ r.root{view(:content=>"")}
34
+ end
35
+
36
+ login
37
+ visit '/close-account'
38
+ click_button 'Close Account'
39
+ statuses[0].must_equal 3
40
+ statuses[1].must_equal 3
41
+ end
42
+
43
+ it "should delete accounts when skip_status_checks? is true" do
44
+ rodauth do
45
+ enable :login, :close_account
46
+ close_account_requires_password? false
47
+ skip_status_checks? true
48
+ end
49
+ roda do |r|
50
+ r.rodauth
51
+ r.root{view(:content=>"")}
52
+ end
53
+
54
+ login
55
+ page.current_path.must_equal '/'
56
+
57
+ visit '/close-account'
58
+ click_button 'Close Account'
59
+ page.current_path.must_equal '/'
60
+
61
+ DB[:accounts].count.must_equal 0
62
+ end
63
+
64
+ it "should support closing accounts when passwords are required" do
65
+ rodauth do
66
+ enable :login, :close_account
67
+ end
68
+ roda do |r|
69
+ r.rodauth
70
+ r.root{view(:content=>"")}
71
+ end
72
+
73
+ login
74
+ page.current_path.must_equal '/'
75
+
76
+ visit '/close-account'
77
+ fill_in 'Password', :with=>'012345678'
78
+ click_button 'Close Account'
79
+ page.find('#error_flash').text.must_equal "There was an error closing your account"
80
+ page.html.must_include("invalid password")
81
+ DB[:accounts].select_map(:status_id).must_equal [2]
82
+
83
+ fill_in 'Password', :with=>'0123456789'
84
+ click_button 'Close Account'
85
+ page.find('#notice_flash').text.must_equal "Your account has been closed"
86
+ page.current_path.must_equal '/'
87
+
88
+ DB[:accounts].select_map(:status_id).must_equal [3]
89
+ end
90
+
91
+ it "should support closing accounts with overrides" do
92
+ rodauth do
93
+ enable :login, :close_account
94
+ close_account do
95
+ account_ds.update(:email => 'foo@bar.com', :status_id=>3)
96
+ end
97
+ close_account_route 'close'
98
+ close_account_redirect '/login'
99
+ end
100
+ roda do |r|
101
+ r.rodauth
102
+ r.root{""}
103
+ end
104
+
105
+ login
106
+ page.current_path.must_equal '/'
107
+
108
+ visit '/close'
109
+ page.title.must_equal 'Close Account'
110
+ fill_in 'Password', :with=>'0123456789'
111
+ click_button 'Close Account'
112
+ page.find('#notice_flash').text.must_equal "Your account has been closed"
113
+ page.current_path.must_equal '/login'
114
+
115
+ DB[:accounts].select_map(:status_id).must_equal [3]
116
+ DB[:accounts].select_map(:email).must_equal ['foo@bar.com']
117
+ end
118
+
119
+ it "should close accounts when account_password_hash_column is set" do
120
+ rodauth do
121
+ enable :create_account, :close_account
122
+ close_account_requires_password? false
123
+ account_password_hash_column :ph
124
+ end
125
+ roda do |r|
126
+ r.rodauth
127
+ r.root{view(:content=>"")}
128
+ end
129
+
130
+ visit '/create-account'
131
+ fill_in 'Login', :with=>'foo2@example.com'
132
+ fill_in 'Confirm Login', :with=>'foo2@example.com'
133
+ fill_in 'Password', :with=>'apple2'
134
+ fill_in 'Confirm Password', :with=>'apple2'
135
+ click_button 'Create Account'
136
+
137
+ visit '/close-account'
138
+ click_button 'Close Account'
139
+ page.current_path.must_equal '/'
140
+
141
+ DB[:accounts].reverse(:id).get(:status_id).must_equal 3
142
+ end
143
+
144
+ it "should support closing accounts via jwt" do
145
+ rodauth do
146
+ enable :login, :close_account
147
+ end
148
+ roda(:jwt) do |r|
149
+ r.rodauth
150
+ end
151
+
152
+ json_login
153
+
154
+ res = json_request('/close-account', :password=>'0123456')
155
+ res.must_equal [400, {'error'=>"There was an error closing your account", "field-error"=>["password", "invalid password"]}]
156
+ DB[:accounts].select_map(:status_id).must_equal [2]
157
+
158
+ res = json_request('/close-account', :password=>'0123456789')
159
+ res.must_equal [200, {'success'=>"Your account has been closed"}]
160
+ DB[:accounts].select_map(:status_id).must_equal [3]
161
+ end
162
+ end
@@ -0,0 +1,70 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe 'Rodauth confirm password feature' do
4
+ it "should support confirming passwords" do
5
+ rodauth do
6
+ enable :login, :change_login, :confirm_password, :password_grace_period
7
+ before_change_login_route do
8
+ unless password_recently_entered?
9
+ session[:confirm_password_redirect] = request.path_info
10
+ redirect '/confirm-password'
11
+ end
12
+ end
13
+ end
14
+ roda do |r|
15
+ r.rodauth
16
+ r.get("reset"){session[:last_password_entry] = Time.now.to_i - 400; "a"}
17
+ view :content=>""
18
+ end
19
+
20
+ login
21
+
22
+ visit '/change-login'
23
+ page.title.must_equal 'Change Login'
24
+
25
+ visit '/reset'
26
+ page.body.must_equal 'a'
27
+
28
+ visit '/change-login'
29
+ page.title.must_equal 'Confirm Password'
30
+ fill_in 'Password', :with=>'012345678'
31
+ click_button 'Confirm Password'
32
+ page.find('#error_flash').text.must_equal "There was an error confirming your password"
33
+ page.html.must_include("invalid password")
34
+
35
+ fill_in 'Password', :with=>'0123456789'
36
+ click_button 'Confirm Password'
37
+ page.find('#notice_flash').text.must_equal "Your password has been confirmed"
38
+
39
+ fill_in 'Login', :with=>'foo3@example.com'
40
+ fill_in 'Confirm Login', :with=>'foo3@example.com'
41
+ click_button 'Change Login'
42
+ page.find('#notice_flash').text.must_equal "Your login has been changed"
43
+ end
44
+
45
+ it "should support confirming passwords via jwt" do
46
+ rodauth do
47
+ enable :login, :change_password, :confirm_password, :password_grace_period
48
+ end
49
+ roda(:jwt) do |r|
50
+ r.rodauth
51
+ r.post("reset"){rodauth.send(:set_session_value, :last_password_entry, Time.now.to_i - 400); [1]}
52
+ end
53
+
54
+ json_login
55
+
56
+ res = json_request('/change-password', "new-password"=>'0123456', "password-confirm"=>'0123456')
57
+ res.must_equal [200, {'success'=>"Your password has been changed"}]
58
+
59
+ json_request('/reset').must_equal [200, [1]]
60
+
61
+ res = json_request('/change-password', "new-password"=>'01234567', "password-confirm"=>'01234567')
62
+ res.must_equal [400, {"field-error"=>["password", "invalid password"], "error"=>"There was an error changing your password"}]
63
+
64
+ res = json_request('/confirm-password', "password"=>'0123456')
65
+ res.must_equal [200, {'success'=>"Your password has been confirmed"}]
66
+
67
+ res = json_request('/change-password', "new-password"=>'01234567', "password-confirm"=>'01234567')
68
+ res.must_equal [200, {'success'=>"Your password has been changed"}]
69
+ end
70
+ end
@@ -0,0 +1,127 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe 'Rodauth create_account feature' do
4
+ [false, true].each do |ph|
5
+ it "should support creating accounts #{'with account_password_hash_column' if ph}" do
6
+ rodauth do
7
+ enable :login, :create_account
8
+ account_password_hash_column :ph if ph
9
+ create_account_autologin? false
10
+ end
11
+ roda do |r|
12
+ r.rodauth
13
+ r.root{view :content=>""}
14
+ end
15
+
16
+ visit '/create-account'
17
+ fill_in 'Login', :with=>'foo@example.com'
18
+ fill_in 'Confirm Login', :with=>'foo@example.com'
19
+ fill_in 'Password', :with=>'0123456789'
20
+ fill_in 'Confirm Password', :with=>'0123456789'
21
+ click_button 'Create Account'
22
+ page.html.must_include("invalid login, already an account with this login")
23
+ page.find('#error_flash').text.must_equal "There was an error creating your account"
24
+ page.current_path.must_equal '/create-account'
25
+
26
+ fill_in 'Login', :with=>'foobar'
27
+ fill_in 'Confirm Login', :with=>'foobar'
28
+ fill_in 'Password', :with=>'0123456789'
29
+ fill_in 'Confirm Password', :with=>'0123456789'
30
+ click_button 'Create Account'
31
+ page.html.must_include("invalid login, not a valid email address")
32
+ page.find('#error_flash').text.must_equal "There was an error creating your account"
33
+ page.current_path.must_equal '/create-account'
34
+
35
+ fill_in 'Login', :with=>'foo@example2.com'
36
+ fill_in 'Password', :with=>'0123456789'
37
+ fill_in 'Confirm Password', :with=>'0123456789'
38
+ click_button 'Create Account'
39
+ page.html.must_include("logins do not match")
40
+ page.find('#error_flash').text.must_equal "There was an error creating your account"
41
+ page.current_path.must_equal '/create-account'
42
+
43
+ fill_in 'Confirm Login', :with=>'foo@example2.com'
44
+ fill_in 'Password', :with=>'0123456789'
45
+ fill_in 'Confirm Password', :with=>'012345678'
46
+ click_button 'Create Account'
47
+ page.html.must_include("passwords do not match")
48
+ page.find('#error_flash').text.must_equal "There was an error creating your account"
49
+ page.current_path.must_equal '/create-account'
50
+
51
+ fill_in 'Password', :with=>'0123456789'
52
+ fill_in 'Confirm Password', :with=>'0123456789'
53
+ click_button 'Create Account'
54
+ page.find('#notice_flash').text.must_equal "Your account has been created"
55
+ page.current_path.must_equal '/'
56
+
57
+ login(:login=>'foo@example2.com')
58
+ page.current_path.must_equal '/'
59
+ end
60
+ end
61
+
62
+ it "should support creating accounts without login/password confirmation" do
63
+ rodauth do
64
+ enable :login, :create_account
65
+ require_login_confirmation? false
66
+ require_password_confirmation? false
67
+ create_account_autologin? false
68
+ end
69
+ roda do |r|
70
+ r.rodauth
71
+ r.root{view :content=>""}
72
+ end
73
+
74
+ visit '/create-account'
75
+ fill_in 'Login', :with=>'foo@example2.com'
76
+ fill_in 'Password', :with=>'0123456789'
77
+ click_button 'Create Account'
78
+ page.find('#notice_flash').text.must_equal "Your account has been created"
79
+ end
80
+
81
+ it "should support autologin after account creation" do
82
+ rodauth do
83
+ enable :create_account
84
+ end
85
+ roda do |r|
86
+ r.rodauth
87
+ next unless session[:account_id]
88
+ r.root{view :content=>"Logged In: #{DB[:accounts].where(:id=>session[:account_id]).get(:email)}"}
89
+ end
90
+
91
+ visit '/create-account'
92
+ fill_in 'Login', :with=>'foo2@example.com'
93
+ fill_in 'Confirm Login', :with=>'foo2@example.com'
94
+ fill_in 'Password', :with=>'apple2'
95
+ fill_in 'Confirm Password', :with=>'apple2'
96
+ click_button 'Create Account'
97
+ page.html.must_include("Logged In: foo2@example.com")
98
+ end
99
+
100
+ it "should support creating accounts via jwt" do
101
+ rodauth do
102
+ enable :login, :create_account
103
+ after_create_account{json_response[:account_id] = account_id}
104
+ create_account_autologin? false
105
+ end
106
+ roda(:jwt) do |r|
107
+ r.rodauth
108
+ end
109
+
110
+ res = json_request('/create-account', :login=>'foo@example.com', "login-confirm"=>'foo@example.com', :password=>'0123456789', "password-confirm"=>'0123456789')
111
+ res.must_equal [400, {'error'=>"There was an error creating your account", "field-error"=>["login", "invalid login, already an account with this login"]}]
112
+
113
+ res = json_request('/create-account', :login=>'foobar', "login-confirm"=>'foobar', :password=>'0123456789', "password-confirm"=>'0123456789')
114
+ res.must_equal [400, {'error'=>"There was an error creating your account", "field-error"=>["login", "invalid login, not a valid email address"]}]
115
+
116
+ res = json_request('/create-account', :login=>'foo@example2.com', "login-confirm"=>'foobar', :password=>'0123456789', "password-confirm"=>'0123456789')
117
+ res.must_equal [400, {'error'=>"There was an error creating your account", "field-error"=>["login", "logins do not match"]}]
118
+
119
+ res = json_request('/create-account', :login=>'foo@example2.com', "login-confirm"=>'foo@example2.com', :password=>'012345678', "password-confirm"=>'0123456789')
120
+ res.must_equal [400, {'error'=>"There was an error creating your account", "field-error"=>["password", "passwords do not match"]}]
121
+
122
+ res = json_request('/create-account', :login=>'foo@example2.com', "login-confirm"=>'foo@example2.com', :password=>'0123456789', "password-confirm"=>'0123456789')
123
+ res.must_equal [200, {'success'=>"Your account has been created", 'account_id'=>DB[:accounts].max(:id)}]
124
+
125
+ json_login(:login=>'foo@example2.com')
126
+ end
127
+ end
@@ -0,0 +1,84 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe 'Rodauth disallow_password_reuse feature' do
4
+ it "should disallow reuse of passwords" do
5
+ rodauth do
6
+ enable :login, :change_password, :disallow_password_reuse, :close_account
7
+ change_password_requires_password? false
8
+ close_account_requires_password? false
9
+ end
10
+ roda do |r|
11
+ r.rodauth
12
+ r.root{view :content=>""}
13
+ end
14
+
15
+ login
16
+ page.current_path.must_equal '/'
17
+
18
+ 8.times do |i|
19
+ visit '/change-password'
20
+ fill_in 'New Password', :with=>"password#{i}"
21
+ fill_in 'Confirm Password', :with=>"password#{i}"
22
+ click_button 'Change Password'
23
+ page.find('#notice_flash').text.must_equal "Your password has been changed"
24
+ end
25
+
26
+ visit '/change-password'
27
+
28
+ (1..6).each do |i|
29
+ fill_in 'New Password', :with=>"password#{i}"
30
+ fill_in 'Confirm Password', :with=>"password#{i}"
31
+ click_button 'Change Password'
32
+ page.html.must_include("invalid password, does not meet requirements (same as previous password)")
33
+ page.find('#error_flash').text.must_equal "There was an error changing your password"
34
+ end
35
+
36
+ fill_in 'New Password', :with=>"password7"
37
+ fill_in 'Confirm Password', :with=>"password7"
38
+ click_button 'Change Password'
39
+ page.html.must_include("invalid password, same as current password")
40
+
41
+ fill_in 'New Password', :with=>'password0'
42
+ fill_in 'Confirm Password', :with=>'password0'
43
+ click_button 'Change Password'
44
+ page.find('#notice_flash').text.must_equal "Your password has been changed"
45
+
46
+ DB[:account_previous_password_hashes].get{count(:id)}.must_equal 7
47
+ visit '/close-account'
48
+ click_button 'Close Account'
49
+ DB[:account_previous_password_hashes].get{count(:id)}.must_equal 0
50
+ end
51
+
52
+ it "should handle create account when account_password_hash_column is true" do
53
+ rodauth do
54
+ enable :login, :create_account, :change_password, :disallow_password_reuse
55
+ account_password_hash_column :ph
56
+ change_password_requires_password? false
57
+ end
58
+ roda do |r|
59
+ r.rodauth
60
+ r.root{view :content=>""}
61
+ end
62
+
63
+ visit '/create-account'
64
+ fill_in 'Login', :with=>'bar@example.com'
65
+ fill_in 'Confirm Login', :with=>'bar@example.com'
66
+ fill_in 'Password', :with=>'0123456789'
67
+ fill_in 'Confirm Password', :with=>'0123456789'
68
+ click_button 'Create Account'
69
+ page.current_path.must_equal '/'
70
+ page.find('#notice_flash').text.must_equal "Your account has been created"
71
+
72
+ visit '/change-password'
73
+ fill_in 'New Password', :with=>"012345678"
74
+ fill_in 'Confirm Password', :with=>"012345678"
75
+ click_button 'Change Password'
76
+ page.find('#notice_flash').text.must_equal "Your password has been changed"
77
+
78
+ visit '/change-password'
79
+ fill_in 'New Password', :with=>"0123456789"
80
+ fill_in 'Confirm Password', :with=>"0123456789"
81
+ click_button 'Change Password'
82
+ page.html.must_include("invalid password, does not meet requirements (same as previous password)")
83
+ end
84
+ end
@@ -0,0 +1,228 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe 'Rodauth lockout feature' do
4
+ it "should support account lockouts without autologin on unlock" do
5
+ rodauth do
6
+ enable :lockout
7
+ max_invalid_logins 2
8
+ unlock_account_autologin? false
9
+ end
10
+ roda do |r|
11
+ r.rodauth
12
+ r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
13
+ end
14
+
15
+ login(:pass=>'012345678910')
16
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
17
+
18
+ login
19
+ page.find('#notice_flash').text.must_equal 'You have been logged in'
20
+ page.body.must_include("Logged In")
21
+
22
+ remove_cookie('rack.session')
23
+
24
+ visit '/login'
25
+ fill_in 'Login', :with=>'foo@example.com'
26
+ 2.times do
27
+ fill_in 'Password', :with=>'012345678910'
28
+ click_button 'Login'
29
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
30
+ end
31
+
32
+ fill_in 'Password', :with=>'012345678910'
33
+ click_button 'Login'
34
+ page.find('#error_flash').text.must_equal "This account is currently locked out and cannot be logged in to."
35
+ page.body.must_include("This account is currently locked out")
36
+ click_button 'Request Account Unlock'
37
+ page.find('#notice_flash').text.must_equal 'An email has been sent to you with a link to unlock your account'
38
+
39
+ link = email_link(/(\/unlock-account\?key=.+)$/)
40
+ visit link[0...-1]
41
+ page.find('#error_flash').text.must_equal 'No matching unlock account key'
42
+
43
+ visit link
44
+ click_button 'Unlock Account'
45
+ page.find('#notice_flash').text.must_equal 'Your account has been unlocked'
46
+ page.body.must_include('Not Logged')
47
+
48
+ login
49
+ page.find('#notice_flash').text.must_equal 'You have been logged in'
50
+ page.body.must_include("Logged In")
51
+ end
52
+
53
+ it "should support account lockouts with autologin and password required on unlock" do
54
+ rodauth do
55
+ enable :lockout
56
+ unlock_account_requires_password? true
57
+ end
58
+ roda do |r|
59
+ r.rodauth
60
+ r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
61
+ end
62
+
63
+ visit '/login'
64
+ fill_in 'Login', :with=>'foo@example.com'
65
+ 100.times do
66
+ fill_in 'Password', :with=>'012345678910'
67
+ click_button 'Login'
68
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
69
+ end
70
+
71
+ fill_in 'Password', :with=>'012345678910'
72
+ click_button 'Login'
73
+ page.find('#error_flash').text.must_equal "This account is currently locked out and cannot be logged in to."
74
+ page.body.must_include("This account is currently locked out")
75
+ click_button 'Request Account Unlock'
76
+ page.find('#notice_flash').text.must_equal 'An email has been sent to you with a link to unlock your account'
77
+
78
+ link = email_link(/(\/unlock-account\?key=.+)$/)
79
+ visit link
80
+ click_button 'Unlock Account'
81
+
82
+ page.find('#error_flash').text.must_equal 'There was an error unlocking your account'
83
+ page.body.must_include('invalid password')
84
+ fill_in 'Password', :with=>'0123456789'
85
+ click_button 'Unlock Account'
86
+
87
+ page.find('#notice_flash').text.must_equal 'Your account has been unlocked'
88
+ page.body.must_include("Logged In")
89
+ end
90
+
91
+ it "should autounlock after enough time" do
92
+ rodauth do
93
+ enable :lockout
94
+ max_invalid_logins 2
95
+ end
96
+ roda do |r|
97
+ r.rodauth
98
+ r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
99
+ end
100
+
101
+ visit '/login'
102
+ fill_in 'Login', :with=>'foo@example.com'
103
+ 2.times do
104
+ fill_in 'Password', :with=>'012345678910'
105
+ click_button 'Login'
106
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
107
+ end
108
+ fill_in 'Password', :with=>'012345678910'
109
+ click_button 'Login'
110
+ page.find('#error_flash').text.must_equal "This account is currently locked out and cannot be logged in to."
111
+ page.body.must_include("This account is currently locked out")
112
+ DB[:account_lockouts].update(:deadline=>Date.today - 3)
113
+
114
+ login
115
+ page.find('#notice_flash').text.must_equal 'You have been logged in'
116
+ page.body.must_include("Logged In")
117
+ end
118
+
119
+ it "should clear unlock token when closing account" do
120
+ rodauth do
121
+ enable :lockout, :close_account
122
+ max_invalid_logins 2
123
+ end
124
+ roda do |r|
125
+ r.get('b') do
126
+ session[:account_id] = DB[:accounts].get(:id)
127
+ 'b'
128
+ end
129
+ r.rodauth
130
+ r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
131
+ end
132
+
133
+ visit '/login'
134
+ fill_in 'Login', :with=>'foo@example.com'
135
+ 3.times do
136
+ fill_in 'Password', :with=>'012345678910'
137
+ click_button 'Login'
138
+ end
139
+ DB[:account_lockouts].count.must_equal 1
140
+
141
+ visit 'b'
142
+
143
+ visit '/close-account'
144
+ fill_in 'Password', :with=>'0123456789'
145
+ click_button 'Close Account'
146
+ DB[:account_lockouts].count.must_equal 0
147
+ end
148
+
149
+ it "should handle uniqueness errors raised when inserting unlock account token" do
150
+ rodauth do
151
+ enable :lockout
152
+ max_invalid_logins 2
153
+ end
154
+ roda do |r|
155
+ def rodauth.raised_uniqueness_violation(*) super; true; end
156
+ r.rodauth
157
+ r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
158
+ end
159
+
160
+ visit '/login'
161
+ fill_in 'Login', :with=>'foo@example.com'
162
+ fill_in 'Password', :with=>'012345678910'
163
+ click_button 'Login'
164
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
165
+
166
+ fill_in 'Password', :with=>'012345678910'
167
+ click_button 'Login'
168
+ page.find('#error_flash').text.must_equal "This account is currently locked out and cannot be logged in to."
169
+ page.body.must_include("This account is currently locked out")
170
+ click_button 'Request Account Unlock'
171
+ page.find('#notice_flash').text.must_equal 'An email has been sent to you with a link to unlock your account'
172
+
173
+ link = email_link(/(\/unlock-account\?key=.+)$/)
174
+ visit link
175
+ click_button 'Unlock Account'
176
+ page.find('#notice_flash').text.must_equal 'Your account has been unlocked'
177
+ page.body.must_include("Logged In")
178
+ end
179
+
180
+ it "should support account lockouts via jwt" do
181
+ rodauth do
182
+ enable :logout, :lockout
183
+ max_invalid_logins 2
184
+ unlock_account_autologin? false
185
+ unlock_account_email_body{unlock_account_email_link}
186
+ end
187
+ roda(:jwt) do |r|
188
+ r.rodauth
189
+ [rodauth.logged_in? ? "Logged In" : "Not Logged"]
190
+ end
191
+
192
+ res = json_request('/unlock-account-request', :login=>'foo@example.com')
193
+ res.must_equal [400, {'error'=>"no matching login"}]
194
+
195
+ res = json_login(:pass=>'1', :no_check=>true)
196
+ res.must_equal [400, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
197
+
198
+ json_login
199
+ json_logout
200
+
201
+ 2.times do
202
+ res = json_login(:pass=>'1', :no_check=>true)
203
+ res.must_equal [400, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
204
+ end
205
+
206
+ 2.times do
207
+ res = json_login(:pass=>'1', :no_check=>true)
208
+ res.must_equal [400, {'error'=>"This account is currently locked out and cannot be logged in to."}]
209
+ end
210
+
211
+ res = json_request('/unlock-account')
212
+ res.must_equal [400, {'error'=>"No matching unlock account key"}]
213
+
214
+ res = json_request('/unlock-account-request', :login=>'foo@example.com')
215
+ res.must_equal [200, {'success'=>"An email has been sent to you with a link to unlock your account"}]
216
+
217
+ link = email_link(/key=.+$/)
218
+ res = json_request('/unlock-account', :key=>link[4...-1])
219
+ res.must_equal [400, {'error'=>"No matching unlock account key"}]
220
+
221
+ res = json_request('/unlock-account', :key=>link[4..-1])
222
+ res.must_equal [200, {'success'=>"Your account has been unlocked"}]
223
+
224
+ res = json_request.must_equal [200, ['Not Logged']]
225
+
226
+ json_login
227
+ end
228
+ end