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,235 +0,0 @@
1
- require File.expand_path("spec_helper", File.dirname(__FILE__))
2
-
3
- describe 'Rodauth login feature' do
4
- it "should not have jwt feature assume JWT token given during Basic/Digest authentication" do
5
- rodauth do
6
- enable :login, :logout
7
- end
8
- roda(:jwt) do |r|
9
- rodauth.require_authentication
10
- '1'
11
- end
12
-
13
- res = json_request("/", :headers=>{'HTTP_AUTHORIZATION'=>'Basic foo'})
14
- res.must_equal [401, {'error'=>'Please login to continue'}]
15
-
16
- res = json_request("/", :headers=>{'HTTP_AUTHORIZATION'=>'Digest foo'})
17
- res.must_equal [401, {'error'=>'Please login to continue'}]
18
- end
19
-
20
- it "should return error message if invalid JWT format used in request Authorization header" do
21
- rodauth do
22
- enable :login, :logout
23
- end
24
- roda(:jwt) do |r|
25
- r.rodauth
26
- rodauth.require_authentication
27
- '1'
28
- end
29
-
30
- res = json_request('/login', :include_headers=>true, :login=>'foo@example.com', :password=>'0123456789')
31
-
32
- res = json_request("/", :headers=>{'HTTP_AUTHORIZATION'=>res[1]['Authorization'][1..-1]})
33
- res.must_equal [400, {'error'=>'invalid JWT format or claim in Authorization header'}]
34
- end
35
-
36
- it "should use custom JSON error statuses even if the request isn't in JSON format if a JWT is in use" do
37
- rodauth do
38
- only_json? true
39
- end
40
- roda(:jwt) do |r|
41
- r.rodauth
42
- rodauth.require_authentication
43
- '1'
44
- end
45
-
46
- status, headers, body = json_request("/", :headers=>{'CONTENT_TYPE'=>'text/html'}, :include_headers=>true)
47
- status.must_equal 401
48
- headers['Content-Type'].must_equal 'application/json'
49
- JSON.parse(body.join).must_equal("error"=>"Please login to continue")
50
- end
51
-
52
- it "should require json request content type in only json mode for rodauth endpoints only" do
53
- oj = false
54
- rodauth do
55
- enable :login, :logout, :jwt
56
- jwt_secret '1'
57
- json_response_success_key 'success'
58
- only_json?{oj}
59
- end
60
- roda(:csrf=>false, :json=>true) do |r|
61
- r.rodauth
62
- rodauth.require_authentication
63
- '1'
64
- end
65
-
66
- res = json_request("/", :content_type=>'application/x-www-form-urlencoded', :include_headers=>true, :method=>'GET')
67
- res[1].delete('Set-Cookie')
68
- res.must_equal [302, {"Content-Type"=>'text/html', "Content-Length"=>'0', "Location"=>"/login",}, []]
69
-
70
- res = json_request("/", :content_type=>'application/vnd.api+json', :method=>'GET')
71
- res.must_equal [400, ['{"error":"Please login to continue"}']]
72
-
73
- oj = true
74
-
75
- res = json_request("/", :content_type=>'application/x-www-form-urlencoded', :method=>'GET')
76
- res.must_equal [400, ['{"error":"Please login to continue"}']]
77
-
78
- res = json_request("/", :method=>'GET')
79
- res.must_equal [400, {'error'=>'Please login to continue'}]
80
-
81
- res = json_request("/login", :content_type=>'application/x-www-form-urlencoded', :include_headers=>true, :method=>'GET')
82
- msg = "Only JSON format requests are allowed"
83
- res[1].delete('Set-Cookie')
84
- res.must_equal [400, {"Content-Type"=>'text/html', "Content-Length"=>msg.length.to_s}, [msg]]
85
-
86
- json_login
87
-
88
- res = json_request("/", :content_type=>'application/x-www-form-urlencoded', :include_headers=>true, :method=>'GET')
89
- res.must_equal [200, {"Content-Type"=>'text/html', "Content-Length"=>'1'}, ['1']]
90
- end
91
-
92
- it "should allow non-json requests if only_json? is false" do
93
- rodauth do
94
- enable :login, :logout, :jwt
95
- jwt_secret '1'
96
- only_json? false
97
- end
98
- roda(:jwt_html) do |r|
99
- r.rodauth
100
- rodauth.require_authentication
101
- view(:content=>'1')
102
- end
103
-
104
- login
105
- page.find('#notice_flash').text.must_equal 'You have been logged in'
106
- end
107
-
108
- it "should require POST for json requests" do
109
- rodauth do
110
- enable :login, :logout, :jwt
111
- jwt_secret '1'
112
- json_response_success_key 'success'
113
- end
114
- roda(:jwt) do |r|
115
- r.rodauth
116
- end
117
-
118
- res = json_request("/login", :method=>'GET')
119
- res.must_equal [405, {'error'=>'non-POST method used in JSON API'}]
120
- end
121
-
122
- it "should allow customizing JSON response bodies" do
123
- rodauth do
124
- enable :login, :logout, :jwt
125
- json_response_body do |hash|
126
- super('status'=>response.status, 'detail'=>hash)
127
- end
128
- end
129
- roda(:jwt) do |r|
130
- r.rodauth
131
- end
132
-
133
- res = json_request("/login", :method=>'GET')
134
- res.must_equal [405, {'status'=>405, 'detail'=>{'error'=>'non-POST method used in JSON API'}}]
135
- end
136
-
137
- it "should support valid_jwt? method for checking for valid JWT tokens" do
138
- rodauth do
139
- enable :login, :logout, :jwt
140
- jwt_secret '1'
141
- json_response_success_key 'success'
142
- end
143
- roda(:jwt) do |r|
144
- r.rodauth
145
- [rodauth.valid_jwt?.to_s]
146
- end
147
-
148
- res = json_request("/", :method=>'GET')
149
- res.must_equal [200, ['false']]
150
-
151
- res = json_request("/login", :method=>'GET')
152
- res.must_equal [405, {'error'=>'non-POST method used in JSON API'}]
153
-
154
- res = json_request("/", :method=>'GET')
155
- res.must_equal [200, ['true']]
156
- end
157
-
158
- it "should require Accept contain application/json if jwt_check_accept? is true and Accept is present" do
159
- rodauth do
160
- enable :login, :logout, :jwt
161
- jwt_secret '1'
162
- json_response_success_key 'success'
163
- jwt_check_accept? true
164
- end
165
- roda(:jwt) do |r|
166
- r.rodauth
167
- end
168
-
169
- res = json_request("/login", :headers=>{'HTTP_ACCEPT'=>'text/html'})
170
- res.must_equal [406, {'error'=>'Unsupported Accept header. Must accept "application/json" or compatible content type'}]
171
-
172
- json_request("/login", :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
173
- json_request("/login", :headers=>{'HTTP_ACCEPT'=>'*/*'}, :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
174
- json_request("/login", :headers=>{'HTTP_ACCEPT'=>'application/*'}, :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
175
- json_request("/login", :headers=>{'HTTP_ACCEPT'=>'application/vnd.api+json'}, :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
176
- end
177
-
178
- it "generates and verifies JWTs with claims" do
179
- invalid_jti = false
180
-
181
- rodauth do
182
- enable :login, :logout, :jwt
183
- jwt_secret '1'
184
- json_response_success_key 'success'
185
- jwt_session_key 'data'
186
- jwt_symbolize_deeply? true
187
- jwt_session_hash do
188
- h = super()
189
- h['data']['foo'] = {:bar=>[1]}
190
- h.merge(
191
- :aud => %w[Young Old],
192
- :exp => Time.now.to_i + 120,
193
- :iat => Time.now.to_i,
194
- :iss => "Foobar, Inc.",
195
- :jti => SecureRandom.hex(10),
196
- :nbf => Time.now.to_i - 30,
197
- :sub => session_value
198
- )
199
- end
200
- jwt_decode_opts(
201
- :aud => 'Old',
202
- :iss => "Foobar, Inc.",
203
- :leeway => 30,
204
- :verify_aud => true,
205
- :verify_expiration => true,
206
- :verify_iat => true,
207
- :verify_iss => true,
208
- :verify_jti => proc{|jti| invalid_jti ? false : !!jti},
209
- :verify_not_before => true
210
- )
211
- end
212
- roda(:jwt) do |r|
213
- r.rodauth
214
- r.post{rodauth.session[:foo][:bar]}
215
- end
216
-
217
- json_login.must_equal [200, {"success"=>'You have been logged in'}]
218
-
219
- payload = JWT.decode(@authorization, nil, false)[0]
220
- payload['sub'].must_equal payload['data']['account_id']
221
- payload['iat'].must_be_kind_of Integer
222
- payload['exp'].must_be_kind_of Integer
223
- payload['nbf'].must_be_kind_of Integer
224
- payload['iss'].must_equal "Foobar, Inc."
225
- payload['aud'].must_equal %w[Young Old]
226
- payload['jti'].must_match(/^[0-9a-f]{20}$/)
227
-
228
- json_request.must_equal [200, [1]]
229
-
230
- invalid_jti = true
231
- if RUBY_VERSION >= '1.9'
232
- json_login(:no_check=>true).must_equal [400, {"error"=>'invalid JWT format or claim in Authorization header'}]
233
- end
234
- end
235
- end
@@ -1,250 +0,0 @@
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
- lockouts = []
6
- rodauth do
7
- enable :lockout
8
- max_invalid_logins 2
9
- unlock_account_autologin? false
10
- after_account_lockout{lockouts << true}
11
- end
12
- roda do |r|
13
- r.rodauth
14
- r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
15
- end
16
-
17
- login(:pass=>'012345678910')
18
- page.find('#error_flash').text.must_equal 'There was an error logging in'
19
-
20
- login
21
- page.find('#notice_flash').text.must_equal 'You have been logged in'
22
- page.body.must_include("Logged In")
23
-
24
- remove_cookie('rack.session')
25
-
26
- visit '/login'
27
- fill_in 'Login', :with=>'foo@example.com'
28
- 2.times do
29
- fill_in 'Password', :with=>'012345678910'
30
- click_button 'Login'
31
- page.find('#error_flash').text.must_equal 'There was an error logging in'
32
- end
33
- lockouts.must_equal [true]
34
-
35
- fill_in 'Password', :with=>'012345678910'
36
- click_button 'Login'
37
- page.find('#error_flash').text.must_equal "This account is currently locked out and cannot be logged in to."
38
- page.body.must_include("This account is currently locked out")
39
- click_button 'Request Account Unlock'
40
- page.find('#notice_flash').text.must_equal 'An email has been sent to you with a link to unlock your account'
41
- link = email_link(/(\/unlock-account\?key=.+)$/)
42
-
43
- visit '/login'
44
- fill_in 'Login', :with=>'foo@example.com'
45
- fill_in 'Password', :with=>'012345678910'
46
- click_button 'Login'
47
- click_button 'Request Account Unlock'
48
- email_link(/(\/unlock-account\?key=.+)$/).must_equal link
49
-
50
- visit link[0...-1]
51
- page.find('#error_flash').text.must_equal "There was an error unlocking your account: invalid or expired unlock account key"
52
-
53
- visit link
54
- click_button 'Unlock Account'
55
- page.find('#notice_flash').text.must_equal 'Your account has been unlocked'
56
- page.body.must_include('Not Logged')
57
-
58
- login
59
- page.find('#notice_flash').text.must_equal 'You have been logged in'
60
- page.body.must_include("Logged In")
61
- end
62
-
63
- it "should support account lockouts with autologin and password required on unlock" do
64
- rodauth do
65
- enable :lockout
66
- unlock_account_requires_password? true
67
- account_lockouts_email_last_sent_column :email_last_sent
68
- end
69
- roda do |r|
70
- r.rodauth
71
- r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
72
- end
73
-
74
- visit '/login'
75
- fill_in 'Login', :with=>'foo@example.com'
76
- 100.times do
77
- fill_in 'Password', :with=>'012345678910'
78
- click_button 'Login'
79
- page.find('#error_flash').text.must_equal 'There was an error logging in'
80
- end
81
-
82
- fill_in 'Password', :with=>'012345678910'
83
- click_button 'Login'
84
- page.find('#error_flash').text.must_equal "This account is currently locked out and cannot be logged in to."
85
- page.body.must_include("This account is currently locked out")
86
- click_button 'Request Account Unlock'
87
- page.find('#notice_flash').text.must_equal 'An email has been sent to you with a link to unlock your account'
88
- link = email_link(/(\/unlock-account\?key=.+)$/)
89
-
90
- visit '/login'
91
- fill_in 'Login', :with=>'foo@example.com'
92
- fill_in 'Password', :with=>'012345678910'
93
- click_button 'Login'
94
- click_button 'Request Account Unlock'
95
- page.find('#error_flash').text.must_equal "An email has recently been sent to you with a link to unlock the account"
96
- Mail::TestMailer.deliveries.must_equal []
97
-
98
- visit link
99
- click_button 'Unlock Account'
100
-
101
- page.find('#error_flash').text.must_equal 'There was an error unlocking your account'
102
- page.body.must_include('invalid password')
103
- fill_in 'Password', :with=>'0123456789'
104
- click_button 'Unlock Account'
105
-
106
- page.find('#notice_flash').text.must_equal 'Your account has been unlocked'
107
- page.body.must_include("Logged In")
108
- end
109
-
110
- it "should autounlock after enough time" do
111
- rodauth do
112
- enable :lockout
113
- max_invalid_logins 2
114
- end
115
- roda do |r|
116
- r.rodauth
117
- r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
118
- end
119
-
120
- visit '/login'
121
- fill_in 'Login', :with=>'foo@example.com'
122
- 2.times do
123
- fill_in 'Password', :with=>'012345678910'
124
- click_button 'Login'
125
- page.find('#error_flash').text.must_equal 'There was an error logging in'
126
- end
127
- fill_in 'Password', :with=>'012345678910'
128
- click_button 'Login'
129
- page.find('#error_flash').text.must_equal "This account is currently locked out and cannot be logged in to."
130
- page.body.must_include("This account is currently locked out")
131
- DB[:account_lockouts].update(:deadline=>Date.today - 3)
132
-
133
- login
134
- page.find('#notice_flash').text.must_equal 'You have been logged in'
135
- page.body.must_include("Logged In")
136
- end
137
-
138
- it "should clear unlock token when closing account" do
139
- rodauth do
140
- enable :lockout, :close_account
141
- max_invalid_logins 2
142
- end
143
- roda do |r|
144
- r.get('b') do
145
- session[:account_id] = DB[:accounts].get(:id)
146
- 'b'
147
- end
148
- r.rodauth
149
- r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
150
- end
151
-
152
- visit '/login'
153
- fill_in 'Login', :with=>'foo@example.com'
154
- 3.times do
155
- fill_in 'Password', :with=>'012345678910'
156
- click_button 'Login'
157
- end
158
- DB[:account_lockouts].count.must_equal 1
159
-
160
- visit 'b'
161
-
162
- visit '/close-account'
163
- fill_in 'Password', :with=>'0123456789'
164
- click_button 'Close Account'
165
- DB[:account_lockouts].count.must_equal 0
166
- end
167
-
168
- it "should handle uniqueness errors raised when inserting unlock account token" do
169
- lockouts = []
170
- rodauth do
171
- enable :lockout
172
- max_invalid_logins 2
173
- after_account_lockout{lockouts << true}
174
- end
175
- roda do |r|
176
- def rodauth.raised_uniqueness_violation(*) super; true; end
177
- r.rodauth
178
- r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
179
- end
180
-
181
- visit '/login'
182
- fill_in 'Login', :with=>'foo@example.com'
183
- fill_in 'Password', :with=>'012345678910'
184
- click_button 'Login'
185
- page.find('#error_flash').text.must_equal 'There was an error logging in'
186
-
187
- fill_in 'Password', :with=>'012345678910'
188
- click_button 'Login'
189
- lockouts.must_equal [true]
190
- page.find('#error_flash').text.must_equal "This account is currently locked out and cannot be logged in to."
191
- page.body.must_include("This account is currently locked out")
192
- click_button 'Request Account Unlock'
193
- page.find('#notice_flash').text.must_equal 'An email has been sent to you with a link to unlock your account'
194
-
195
- link = email_link(/(\/unlock-account\?key=.+)$/)
196
- visit link
197
- click_button 'Unlock Account'
198
- page.find('#notice_flash').text.must_equal 'Your account has been unlocked'
199
- page.body.must_include("Logged In")
200
- end
201
-
202
- it "should support account lockouts via jwt" do
203
- rodauth do
204
- enable :logout, :lockout
205
- max_invalid_logins 2
206
- unlock_account_autologin? false
207
- unlock_account_email_body{unlock_account_email_link}
208
- end
209
- roda(:jwt) do |r|
210
- r.rodauth
211
- [rodauth.logged_in? ? "Logged In" : "Not Logged"]
212
- end
213
-
214
- res = json_request('/unlock-account-request', :login=>'foo@example.com')
215
- res.must_equal [401, {'error'=>"No matching login"}]
216
-
217
- res = json_login(:pass=>'1', :no_check=>true)
218
- res.must_equal [401, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
219
-
220
- json_login
221
- json_logout
222
-
223
- 2.times do
224
- res = json_login(:pass=>'1', :no_check=>true)
225
- res.must_equal [401, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
226
- end
227
-
228
- 2.times do
229
- res = json_login(:pass=>'1', :no_check=>true)
230
- res.must_equal [403, {'error'=>"This account is currently locked out and cannot be logged in to."}]
231
- end
232
-
233
- res = json_request('/unlock-account')
234
- res.must_equal [401, {'error'=>"There was an error unlocking your account: invalid or expired unlock account key"}]
235
-
236
- res = json_request('/unlock-account-request', :login=>'foo@example.com')
237
- res.must_equal [200, {'success'=>"An email has been sent to you with a link to unlock your account"}]
238
-
239
- link = email_link(/key=.+$/)
240
- res = json_request('/unlock-account', :key=>link[4...-1])
241
- res.must_equal [401, {'error'=>"There was an error unlocking your account: invalid or expired unlock account key"}]
242
-
243
- res = json_request('/unlock-account', :key=>link[4..-1])
244
- res.must_equal [200, {'success'=>"Your account has been unlocked"}]
245
-
246
- res = json_request.must_equal [200, ['Not Logged']]
247
-
248
- json_login
249
- end
250
- end