rodauth 1.22.0 → 1.23.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +12 -0
  3. data/README.rdoc +5 -3
  4. data/doc/email_base.rdoc +1 -0
  5. data/doc/release_notes/1.23.0.txt +32 -0
  6. data/lib/rodauth.rb +5 -2
  7. data/lib/rodauth/features/base.rb +8 -0
  8. data/lib/rodauth/features/change_password_notify.rb +1 -1
  9. data/lib/rodauth/features/create_account.rb +1 -1
  10. data/lib/rodauth/features/email_auth.rb +3 -4
  11. data/lib/rodauth/features/email_base.rb +7 -2
  12. data/lib/rodauth/features/lockout.rb +1 -1
  13. data/lib/rodauth/features/login.rb +6 -2
  14. data/lib/rodauth/features/otp.rb +6 -3
  15. data/lib/rodauth/features/password_expiration.rb +1 -1
  16. data/lib/rodauth/features/recovery_codes.rb +3 -3
  17. data/lib/rodauth/features/reset_password.rb +2 -2
  18. data/lib/rodauth/features/sms_codes.rb +5 -5
  19. data/lib/rodauth/features/verify_account.rb +2 -2
  20. data/lib/rodauth/features/verify_login_change.rb +1 -1
  21. data/lib/rodauth/version.rb +1 -1
  22. data/templates/email-auth-request-form.str +2 -2
  23. data/templates/reset-password-request.str +3 -3
  24. data/templates/unlock-account-request.str +3 -3
  25. data/templates/verify-account-resend.str +3 -3
  26. metadata +5 -43
  27. data/Rakefile +0 -179
  28. data/spec/account_expiration_spec.rb +0 -225
  29. data/spec/all.rb +0 -1
  30. data/spec/change_login_spec.rb +0 -156
  31. data/spec/change_password_notify_spec.rb +0 -33
  32. data/spec/change_password_spec.rb +0 -202
  33. data/spec/close_account_spec.rb +0 -162
  34. data/spec/confirm_password_spec.rb +0 -70
  35. data/spec/create_account_spec.rb +0 -127
  36. data/spec/disallow_common_passwords_spec.rb +0 -93
  37. data/spec/disallow_password_reuse_spec.rb +0 -179
  38. data/spec/email_auth_spec.rb +0 -285
  39. data/spec/http_basic_auth_spec.rb +0 -143
  40. data/spec/jwt_cors_spec.rb +0 -57
  41. data/spec/jwt_refresh_spec.rb +0 -256
  42. data/spec/jwt_spec.rb +0 -235
  43. data/spec/lockout_spec.rb +0 -250
  44. data/spec/login_spec.rb +0 -328
  45. data/spec/migrate/001_tables.rb +0 -184
  46. data/spec/migrate/002_account_password_hash_column.rb +0 -11
  47. data/spec/migrate_password/001_tables.rb +0 -73
  48. data/spec/migrate_travis/001_tables.rb +0 -141
  49. data/spec/password_complexity_spec.rb +0 -109
  50. data/spec/password_expiration_spec.rb +0 -244
  51. data/spec/password_grace_period_spec.rb +0 -93
  52. data/spec/remember_spec.rb +0 -451
  53. data/spec/reset_password_spec.rb +0 -229
  54. data/spec/rodauth_spec.rb +0 -343
  55. data/spec/session_expiration_spec.rb +0 -58
  56. data/spec/single_session_spec.rb +0 -127
  57. data/spec/spec_helper.rb +0 -327
  58. data/spec/two_factor_spec.rb +0 -1462
  59. data/spec/update_password_hash_spec.rb +0 -40
  60. data/spec/verify_account_grace_period_spec.rb +0 -171
  61. data/spec/verify_account_spec.rb +0 -240
  62. data/spec/verify_change_login_spec.rb +0 -46
  63. data/spec/verify_login_change_spec.rb +0 -232
  64. data/spec/views/layout-other.str +0 -11
  65. data/spec/views/layout.str +0 -11
  66. data/spec/views/login.str +0 -21
@@ -1,328 +0,0 @@
1
- require File.expand_path("spec_helper", File.dirname(__FILE__))
2
-
3
- describe 'Rodauth login feature' do
4
- it "should handle logins and logouts" do
5
- rodauth{enable :login, :logout}
6
- roda do |r|
7
- r.rodauth
8
- next unless rodauth.logged_in?
9
- r.root{view :content=>"Logged In"}
10
- end
11
-
12
- visit '/login'
13
- page.title.must_equal 'Login'
14
-
15
- login(:login=>'foo@example2.com', :visit=>false)
16
- page.find('#error_flash').text.must_equal 'There was an error logging in'
17
- page.html.must_include("no matching login")
18
- page.all('[type=text]').first.value.must_equal 'foo@example2.com'
19
-
20
- login(:pass=>'012345678', :visit=>false)
21
- page.find('#error_flash').text.must_equal 'There was an error logging in'
22
- page.html.must_include("invalid password")
23
-
24
- fill_in 'Password', :with=>'0123456789'
25
- click_button 'Login'
26
- page.current_path.must_equal '/'
27
- page.find('#notice_flash').text.must_equal 'You have been logged in'
28
- page.html.must_include("Logged In")
29
-
30
- visit '/logout'
31
- page.title.must_equal 'Logout'
32
-
33
- click_button 'Logout'
34
- page.find('#notice_flash').text.must_equal 'You have been logged out'
35
- page.current_path.must_equal '/login'
36
- end
37
-
38
- it "should handle multi phase login (email first, then password)" do
39
- rodauth do
40
- enable :login, :logout
41
- use_multi_phase_login? true
42
- login_input_type 'email'
43
- input_field_label_suffix ' (Required)'
44
- input_field_error_class ' bad-input'
45
- input_field_error_message_class 'err-msg'
46
- mark_input_fields_as_required? true
47
- field_attributes do |field|
48
- if field == 'login'
49
- 'custom_field="custom_value"'
50
- else
51
- super(field)
52
- end
53
- end
54
- field_error_attributes do |field|
55
- if field == 'login'
56
- 'custom_error_field="custom_error_value"'
57
- else
58
- super(field)
59
- end
60
- end
61
- formatted_field_error do |field, error|
62
- if field == 'login'
63
- super(field, error)
64
- else
65
- "<span class='err-msg2'>1#{error}2</span>"
66
- end
67
- end
68
- end
69
- roda do |r|
70
- r.rodauth
71
- next unless rodauth.logged_in?
72
- r.root{view :content=>"Logged In"}
73
- end
74
-
75
- visit '/login'
76
- page.title.must_equal 'Login'
77
-
78
- page.find('[custom_field=custom_value]').value.must_equal ''
79
- page.all('[custom_error_field=custom_error_value]').must_be_empty
80
- page.all('input[type=password]').must_be_empty
81
- fill_in 'Login (Required)', :with=>'foo2@example.com'
82
- click_button 'Login'
83
- page.find('#error_flash').text.must_equal 'There was an error logging in'
84
- page.find('[custom_field=custom_value]').value.must_equal 'foo2@example.com'
85
- page.find('[custom_error_field=custom_error_value]').value.must_equal 'foo2@example.com'
86
- page.find('[type=email]').value.must_equal 'foo2@example.com'
87
- page.find('.bad-input').value.must_equal 'foo2@example.com'
88
- page.find('.err-msg').text.must_equal 'no matching login'
89
-
90
- page.all('input[type=password]').must_be_empty
91
- fill_in 'Login (Required)', :with=>'foo@example.com'
92
- click_button 'Login'
93
- page.find('#notice_flash').text.must_equal 'Login recognized, please enter your password'
94
-
95
- page.all('[custom_field=custom_value]').must_be_empty
96
- page.all('[custom_error_field=custom_error_value]').must_be_empty
97
- page.all('[aria-invalid=true]').must_be_empty
98
- page.all('[aria-describedby]').must_be_empty
99
- page.find('[required=required]').value.to_s.must_equal ''
100
- page.all('input[type=text]').must_be_empty
101
- fill_in 'Password (Required)', :with=>'012345678'
102
- click_button 'Login'
103
- page.find('#error_flash').text.must_equal 'There was an error logging in'
104
- page.find('[aria-invalid=true]').value.to_s.must_equal ''
105
- page.find('[aria-describedby=password_error_message]').value.to_s.must_equal ''
106
- page.all('[custom_error_field=custom_error_value]').must_be_empty
107
- page.find('.err-msg2').text.must_equal '1invalid password2'
108
-
109
- page.all('input[type=text]').must_be_empty
110
- fill_in 'Password (Required)', :with=>'0123456789'
111
- click_button 'Login'
112
- page.current_path.must_equal '/'
113
- page.find('#notice_flash').text.must_equal 'You have been logged in'
114
- page.html.must_include("Logged In")
115
-
116
- visit '/logout'
117
- page.title.must_equal 'Logout'
118
-
119
- click_button 'Logout'
120
- page.find('#notice_flash').text.must_equal 'You have been logged out'
121
- page.current_path.must_equal '/login'
122
- end
123
-
124
- it "should not allow login to unverified account" do
125
- rodauth do
126
- enable :login
127
- skip_status_checks? false
128
- end
129
- roda do |r|
130
- r.rodauth
131
- next unless rodauth.logged_in?
132
- r.root{view :content=>"Logged In"}
133
- end
134
-
135
- DB[:accounts].update(:status_id=>1)
136
- login
137
- page.find('#error_flash').text.must_equal 'There was an error logging in'
138
- page.html.must_include("unverified account, please verify account before logging in")
139
- end
140
-
141
- it "should handle overriding login action" do
142
- rodauth do
143
- enable :login
144
- end
145
- roda do |r|
146
- r.post 'login' do
147
- if r.params['login'] == 'apple' && r.params['password'] == 'banana'
148
- session['user_id'] = 'pear'
149
- r.redirect '/'
150
- end
151
- r.redirect '/login'
152
- end
153
- r.rodauth
154
- next unless session['user_id'] == 'pear'
155
- r.root{"Logged In"}
156
- end
157
-
158
- login(:login=>'appl', :pass=>'banana')
159
- page.html.wont_match(/Logged In/)
160
-
161
- login(:login=>'apple', :pass=>'banan', :visit=>false)
162
- page.html.wont_match(/Logged In/)
163
-
164
- login(:login=>'apple', :pass=>'banana', :visit=>false)
165
- page.current_path.must_equal '/'
166
- page.html.must_include("Logged In")
167
- end
168
-
169
- it "should handle overriding some login attributes" do
170
- rodauth do
171
- enable :login
172
- account_from_login do |login|
173
- DB[:accounts].first if login == 'apple'
174
- end
175
- password_match? do |password|
176
- password == 'banana'
177
- end
178
- update_session do
179
- session['user_id'] = 'pear'
180
- end
181
- no_matching_login_message "no user"
182
- invalid_password_message "bad password"
183
- end
184
- roda do |r|
185
- r.rodauth
186
- next unless session['user_id'] == 'pear'
187
- r.root{"Logged In"}
188
- end
189
-
190
- login(:login=>'appl', :pass=>'banana')
191
- page.html.must_include("no user")
192
-
193
- login(:login=>'apple', :pass=>'banan', :visit=>false)
194
- page.html.must_include("bad password")
195
-
196
- fill_in 'Password', :with=>'banana'
197
- click_button 'Login'
198
- page.current_path.must_equal '/'
199
- page.html.must_include("Logged In")
200
- end
201
-
202
- it "should handle a prefix and some other login options" do
203
- rodauth do
204
- enable :login, :logout
205
- prefix '/auth'
206
- session_key 'login_email'
207
- account_from_session{DB[:accounts].first(:email=>session_value)}
208
- account_session_value{account[:email]}
209
- login_param{param('lp')}
210
- login_additional_form_tags "<input type='hidden' name='lp' value='l' />"
211
- password_param 'p'
212
- login_redirect{"/foo/#{account[:email]}"}
213
- logout_redirect '/auth/lin'
214
- login_route 'lin'
215
- logout_route 'lout'
216
- end
217
- no_freeze!
218
- roda do |r|
219
- r.on 'auth' do
220
- r.rodauth
221
- end
222
- next unless session['login_email'] =~ /example/
223
- r.get('foo', :email){|e| "Logged In: #{e}"}
224
- end
225
- app.plugin :render, :views=>'spec/views', :engine=>'str'
226
-
227
- visit '/auth/lin?lp=l'
228
-
229
- login(:login=>'foo@example2.com', :visit=>false)
230
- page.html.must_include("no matching login")
231
-
232
- login(:pass=>'012345678', :visit=>false)
233
- page.html.must_include("invalid password")
234
-
235
- login(:visit=>false)
236
- page.current_path.must_equal '/foo/foo@example.com'
237
- page.html.must_include("Logged In: foo@example.com")
238
-
239
- visit '/auth/lout'
240
- click_button 'Logout'
241
- page.current_path.must_equal '/auth/lin'
242
- end
243
-
244
- it "should use correct redirect paths when using prefix" do
245
- rodauth do
246
- enable :login, :logout
247
- prefix '/auth'
248
- end
249
- roda do |r|
250
- r.on 'auth' do
251
- r.rodauth
252
- rodauth.require_login
253
- end
254
- rodauth.send("#{r.remaining_path[1..-1]}_redirect")
255
- end
256
-
257
- visit '/login'
258
- page.html.must_equal '/'
259
- visit '/logout'
260
- page.html.must_equal '/auth/login'
261
- visit '/require_login'
262
- page.html.must_equal '/auth/login'
263
-
264
- visit '/auth'
265
- page.current_path.must_equal '/auth/login'
266
- end
267
-
268
- it "should login and logout via jwt" do
269
- rodauth do
270
- enable :login, :logout
271
- json_response_custom_error_status? false
272
- jwt_secret{proc{super()}.must_raise ArgumentError; "1"}
273
- end
274
- roda(:jwt) do |r|
275
- r.rodauth
276
- response['Content-Type'] = 'application/json'
277
- rodauth.logged_in? ? '1' : '2'
278
- end
279
-
280
- json_request.must_equal [200, 2]
281
-
282
- res = json_request("/login", :login=>'foo@example2.com', :password=>'0123456789')
283
- res.must_equal [400, {'error'=>"There was an error logging in", "field-error"=>["login", "no matching login"]}]
284
-
285
- res = json_request("/login", :login=>'foo@example.com', :password=>'012345678')
286
- res.must_equal [400, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
287
-
288
- json_request("/login", :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
289
- json_request.must_equal [200, 1]
290
-
291
- json_request("/logout").must_equal [200, {"success"=>'You have been logged out'}]
292
- json_request.must_equal [200, 2]
293
- end
294
-
295
- it "should login and logout via jwt with custom error statuses" do
296
- rodauth do
297
- enable :login, :logout
298
- end
299
- roda(:jwt) do |r|
300
- r.rodauth
301
- response['Content-Type'] = 'application/json'
302
- r.post('foo') do
303
- rodauth.require_login
304
- '3'
305
- end
306
- rodauth.logged_in? ? '1' : '2'
307
- end
308
-
309
- json_request.must_equal [200, 2]
310
-
311
- res = json_request("/foo")
312
- res.must_equal [401, {"error"=>"Please login to continue"}]
313
-
314
- res = json_request("/login", :login=>'foo@example2.com', :password=>'0123456789')
315
- res.must_equal [401, {'error'=>"There was an error logging in", "field-error"=>["login", "no matching login"]}]
316
-
317
- res = json_request("/login", :login=>'foo@example.com', :password=>'012345678')
318
- res.must_equal [401, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
319
-
320
- json_request("/login", :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
321
- json_request.must_equal [200, 1]
322
-
323
- res = json_request("/foo").must_equal [200, 3]
324
-
325
- json_request("/logout").must_equal [200, {"success"=>'You have been logged out'}]
326
- json_request.must_equal [200, 2]
327
- end
328
- end
@@ -1,184 +0,0 @@
1
- Sequel.migration do
2
- up do
3
- extension :date_arithmetic
4
-
5
- # Used by the account verification and close account features
6
- create_table(:account_statuses) do
7
- Integer :id, :primary_key=>true
8
- String :name, :null=>false, :unique=>true
9
- end
10
- from(:account_statuses).import([:id, :name], [[1, 'Unverified'], [2, 'Verified'], [3, 'Closed']])
11
-
12
- db = self
13
- create_table(:accounts) do
14
- primary_key :id, :type=>:Bignum
15
- foreign_key :status_id, :account_statuses, :null=>false, :default=>1
16
- if db.database_type == :postgres
17
- citext :email, :null=>false
18
- constraint :valid_email, :email=>/^[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+$/
19
- index :email, :unique=>true, :where=>{:status_id=>[1, 2]}
20
- else
21
- String :email, :null=>false
22
- index :email, :unique=>true
23
- end
24
- end
25
-
26
- deadline_opts = proc do |days|
27
- if database_type == :mysql
28
- {:null=>false}
29
- else
30
- {:null=>false, :default=>Sequel.date_add(Sequel::CURRENT_TIMESTAMP, :days=>days)}
31
- end
32
- end
33
-
34
- # Used by the password reset feature
35
- create_table(:account_password_reset_keys) do
36
- foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
37
- String :key, :null=>false
38
- DateTime :deadline, deadline_opts[1]
39
- DateTime :email_last_sent, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
40
- end
41
-
42
- # Used by the refresh token feature
43
- create_table(:account_jwt_refresh_keys) do
44
- primary_key :id, :type=>:Bignum
45
- foreign_key :account_id, :accounts, :type=>:Bignum
46
- String :key, :null=>false
47
- DateTime :deadline, deadline_opts[1]
48
- end
49
-
50
- # Used by the account verification feature
51
- create_table(:account_verification_keys) do
52
- foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
53
- String :key, :null=>false
54
- DateTime :requested_at, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
55
- DateTime :email_last_sent, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
56
- end
57
-
58
- # Used by the verify login change feature
59
- create_table(:account_login_change_keys) do
60
- foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
61
- String :key, :null=>false
62
- String :login, :null=>false
63
- DateTime :deadline, deadline_opts[1]
64
- end
65
-
66
- # Used by the remember me feature
67
- create_table(:account_remember_keys) do
68
- foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
69
- String :key, :null=>false
70
- DateTime :deadline, deadline_opts[14]
71
- end
72
-
73
- # Used by the lockout feature
74
- create_table(:account_login_failures) do
75
- foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
76
- Integer :number, :null=>false, :default=>1
77
- end
78
- create_table(:account_lockouts) do
79
- foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
80
- String :key, :null=>false
81
- DateTime :deadline, deadline_opts[1]
82
- DateTime :email_last_sent
83
- end
84
-
85
- # Used by the email auth feature
86
- create_table(:account_email_auth_keys) do
87
- foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
88
- String :key, :null=>false
89
- DateTime :deadline, deadline_opts[1]
90
- DateTime :email_last_sent, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
91
- end
92
-
93
- # Used by the password expiration feature
94
- create_table(:account_password_change_times) do
95
- foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
96
- DateTime :changed_at, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
97
- end
98
-
99
- # Used by the account expiration feature
100
- create_table(:account_activity_times) do
101
- foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
102
- DateTime :last_activity_at, :null=>false
103
- DateTime :last_login_at, :null=>false
104
- DateTime :expired_at
105
- end
106
-
107
- # Used by the single session feature
108
- create_table(:account_session_keys) do
109
- foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
110
- String :key, :null=>false
111
- end
112
-
113
- # Used by the otp feature
114
- create_table(:account_otp_keys) do
115
- foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
116
- String :key, :null=>false
117
- Integer :num_failures, :null=>false, :default=>0
118
- Time :last_use, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
119
- end
120
-
121
- # Used by the recovery codes feature
122
- create_table(:account_recovery_codes) do
123
- foreign_key :id, :accounts, :type=>:Bignum
124
- String :code
125
- primary_key [:id, :code]
126
- end
127
-
128
- # Used by the sms codes feature
129
- create_table(:account_sms_codes) do
130
- foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
131
- String :phone_number, :null=>false
132
- Integer :num_failures
133
- String :code
134
- DateTime :code_issued_at, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
135
- end
136
-
137
- case database_type
138
- when :postgres
139
- user = get(Sequel.lit('current_user')) + '_password'
140
- run "GRANT REFERENCES ON accounts TO #{user}"
141
- when :mysql, :mssql
142
- user = if database_type == :mysql
143
- get(Sequel.lit('current_user')).sub(/_password@/, '@')
144
- else
145
- get(Sequel.function(:DB_NAME))
146
- end
147
- run "GRANT ALL ON account_statuses TO #{user}"
148
- run "GRANT ALL ON accounts TO #{user}"
149
- run "GRANT ALL ON account_password_reset_keys TO #{user}"
150
- run "GRANT ALL ON account_jwt_refresh_keys TO #{user}"
151
- run "GRANT ALL ON account_verification_keys TO #{user}"
152
- run "GRANT ALL ON account_login_change_keys TO #{user}"
153
- run "GRANT ALL ON account_remember_keys TO #{user}"
154
- run "GRANT ALL ON account_login_failures TO #{user}"
155
- run "GRANT ALL ON account_email_auth_keys TO #{user}"
156
- run "GRANT ALL ON account_lockouts TO #{user}"
157
- run "GRANT ALL ON account_password_change_times TO #{user}"
158
- run "GRANT ALL ON account_activity_times TO #{user}"
159
- run "GRANT ALL ON account_session_keys TO #{user}"
160
- run "GRANT ALL ON account_otp_keys TO #{user}"
161
- run "GRANT ALL ON account_recovery_codes TO #{user}"
162
- run "GRANT ALL ON account_sms_codes TO #{user}"
163
- end
164
- end
165
-
166
- down do
167
- drop_table(:account_sms_codes,
168
- :account_recovery_codes,
169
- :account_otp_keys,
170
- :account_session_keys,
171
- :account_activity_times,
172
- :account_password_change_times,
173
- :account_email_auth_keys,
174
- :account_lockouts,
175
- :account_login_failures,
176
- :account_remember_keys,
177
- :account_login_change_keys,
178
- :account_verification_keys,
179
- :account_jwt_refresh_keys,
180
- :account_password_reset_keys,
181
- :accounts,
182
- :account_statuses)
183
- end
184
- end