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,143 +0,0 @@
1
- require File.expand_path("spec_helper", File.dirname(__FILE__))
2
-
3
- describe "Rodauth http basic auth feature" do
4
- def basic_auth_visit(opts={})
5
- page.driver.browser.basic_authorize(opts.fetch(:username,"foo@example.com"), opts.fetch(:password, "0123456789"))
6
- visit(opts.fetch(:path, '/'))
7
- end
8
-
9
- def authorization_header(opts={})
10
- ["#{opts.delete(:username)||'foo@example.com'}:#{opts.delete(:password)||'0123456789'}"].pack("m*")
11
- end
12
-
13
- def basic_auth_json_request(opts={})
14
- auth = opts.delete(:auth) || authorization_header(opts)
15
- path = opts.delete(:path) || '/'
16
- json_request(path, opts.merge(:headers => {"HTTP_AUTHORIZATION" => "Basic #{auth}"}, :method=>'GET'))
17
- end
18
-
19
- def newline_basic_auth_json_request(opts={})
20
- auth = opts.delete(:auth) || authorization_header(opts)
21
- auth.chomp!
22
- basic_auth_json_request(opts.merge(:auth => auth))
23
- end
24
-
25
- describe "on page visit" do
26
- before do
27
- rodauth do
28
- enable :http_basic_auth
29
- end
30
- roda do |r|
31
- r.rodauth
32
- r.root{view :content=>(rodauth.logged_in? ? "Logged In" : 'Not Logged')}
33
- end
34
- end
35
-
36
- it "handles logins" do
37
- basic_auth_visit
38
- page.text.must_include "Logged In"
39
- end
40
-
41
- it "keeps the user logged in" do
42
- visit '/'
43
- page.text.must_include "Not Logged"
44
-
45
- basic_auth_visit
46
- page.text.must_include "Logged In"
47
-
48
- visit '/'
49
- page.text.must_include "Logged In"
50
- end
51
-
52
- it "fails when no login is found" do
53
- basic_auth_visit(:username => "foo2@example.com")
54
- page.text.must_include "Not Logged"
55
- page.response_headers.keys.must_include("WWW-Authenticate")
56
- end
57
-
58
- it "fails when passowrd does not match" do
59
- basic_auth_visit(:password => "1111111111")
60
- page.text.must_include "Not Logged"
61
- page.response_headers.keys.must_include("WWW-Authenticate")
62
- end
63
- end
64
-
65
- it "requires authentication if require_http_basic_auth is true" do
66
- rodauth do
67
- enable :http_basic_auth
68
- require_http_basic_auth true
69
- end
70
- roda do |r|
71
- rodauth.require_authentication
72
- r.root{view :content=>(rodauth.logged_in? ? "Logged In" : 'Not Logged')}
73
- end
74
-
75
- visit '/'
76
- page.status_code.must_equal 401
77
- page.response_headers.keys.must_include("WWW-Authenticate")
78
-
79
- basic_auth_visit
80
- page.text.must_include "Logged In"
81
- end
82
-
83
- it "works with standard authentication" do
84
- rodauth do
85
- enable :login, :http_basic_auth
86
- end
87
- roda do |r|
88
- r.rodauth
89
- r.root{view :content=>(rodauth.logged_in? ? "Logged In" : 'Not Logged')}
90
- end
91
-
92
- login
93
- page.text.must_include "Logged In"
94
- end
95
-
96
- it "does not allow login to unverified account" do
97
- rodauth do
98
- enable :http_basic_auth
99
- skip_status_checks? false
100
- end
101
- roda do |r|
102
- r.rodauth
103
- r.root{view :content=>(rodauth.logged_in? ? "Logged In" : 'Not Logged')}
104
- end
105
- DB[:accounts].update(:status_id=>1)
106
-
107
- basic_auth_visit
108
- page.text.must_include "Not Logged"
109
- page.response_headers.keys.must_include("WWW-Authenticate")
110
- end
111
-
112
- it "should login via jwt" do
113
- rodauth do
114
- enable :http_basic_auth
115
- end
116
- roda(:jwt) do |r|
117
- r.rodauth
118
- response['Content-Type'] = 'application/json'
119
- rodauth.require_authentication
120
- {"success"=>'You have been logged in'}
121
- end
122
-
123
- @authorization = nil
124
- res = basic_auth_json_request(:auth=>'.')
125
- res.must_equal [401, {'error'=>"Please login to continue"}]
126
-
127
- @authorization = nil
128
- res = basic_auth_json_request(:username=>'foo@example2.com')
129
- res.must_equal [401, {'error'=>"Please login to continue", "field-error"=>["login", "no matching login"]}]
130
-
131
- @authorization = nil
132
- res = basic_auth_json_request(:password=>'012345678')
133
- res.must_equal [401, {'error'=>"Please login to continue", "field-error"=>["password", "invalid password"]}]
134
-
135
- @authorization = nil
136
- res = newline_basic_auth_json_request
137
- res.must_equal [200, {"success"=>'You have been logged in'}]
138
-
139
- @authorization = nil
140
- res = basic_auth_json_request
141
- res.must_equal [200, {"success"=>'You have been logged in'}]
142
- end
143
- end
@@ -1,57 +0,0 @@
1
- require File.expand_path("spec_helper", File.dirname(__FILE__))
2
-
3
- describe 'Rodauth jwt_cors feature' do
4
- it "should support CORS logins if allowed" do
5
- origin = false
6
- rodauth do
7
- enable :login, :jwt_cors
8
- jwt_secret '1'
9
- json_response_success_key 'success'
10
- jwt_cors_allow_origin{origin}
11
- end
12
- roda(:csrf=>false, :json=>true) do |r|
13
- r.rodauth
14
- rodauth.require_authentication
15
- response['Content-Type'] = 'application/json'
16
- '1'
17
- end
18
-
19
- # CORS Preflight Request
20
- preflight_request = {
21
- :method=>'OPTIONS',
22
- :headers=>{
23
- "HTTP_ACCESS_CONTROL_REQUEST_METHOD"=>"POST",
24
- "HTTP_ORIGIN"=>"https://foo.example.com",
25
- "HTTP_ACCESS_CONTROL_REQUEST_HEADERS"=>"content-type",
26
- "CONTENT_TYPE"=>' application/json'
27
- }
28
- }
29
-
30
- res = json_request("/login", preflight_request.dup)
31
- res.must_equal [405, ["{\"error\":\"non-POST method used in JSON API\"}"]]
32
-
33
- origin = Object.new
34
- res = json_request("/login", preflight_request.dup)
35
- res.must_equal [405, ["{\"error\":\"non-POST method used in JSON API\"}"]]
36
-
37
- ["https://foo.example.com", ["https://foo.example.com"], %r{https://foo.example.com}, true].each do |orig|
38
- origin = orig
39
-
40
- res = json_request("/login", preflight_request.merge(:include_headers=>true))
41
- res[0].must_equal 204
42
- res[1]['Access-Control-Allow-Origin'].must_equal "https://foo.example.com"
43
- res[1]['Access-Control-Allow-Methods'].must_equal "POST"
44
- res[1]['Access-Control-Allow-Headers'].must_equal "Content-Type, Authorization, Accept"
45
- res[1]['Access-Control-Max-Age'].must_equal "86400"
46
- res[2].must_equal []
47
-
48
- res = json_request("/login", :login=>'foo@example.com', :password=>'0123456789', :headers=>{"HTTP_ORIGIN"=>"https://foo.example.com"}, :include_headers=>true)
49
- res[0].must_equal 200
50
- res[1]['Access-Control-Allow-Origin'].must_equal "https://foo.example.com"
51
- res[1]['Access-Control-Expose-Headers'].must_equal "Authorization"
52
- res[2].must_equal("success"=>"You have been logged in")
53
-
54
- json_request("/foo").must_equal [200, 1]
55
- end
56
- end
57
- end
@@ -1,256 +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 refresh feature assume JWT token given during Basic/Digest authentication" do
5
- rodauth do
6
- enable :login, :logout, :jwt_refresh
7
- end
8
- roda(:jwt) do |r|
9
- rodauth.require_authentication
10
- '1'
11
- end
12
-
13
- res = json_request("/jwt-refresh", :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 require json request content type in only json mode for rodauth endpoints only" do
21
- oj = false
22
- rodauth do
23
- enable :login, :logout, :jwt_refresh
24
- jwt_secret '1'
25
- json_response_success_key 'success'
26
- only_json?{oj}
27
- end
28
- roda(:csrf=>false, :json=>true) do |r|
29
- r.rodauth
30
- rodauth.require_authentication
31
- '1'
32
- end
33
-
34
- res = json_request("/", :content_type=>'application/x-www-form-urlencoded', :include_headers=>true, :method=>'GET')
35
- res[1].delete('Set-Cookie')
36
- res.must_equal [302, {"Content-Type"=>'text/html', "Content-Length"=>'0', "Location"=>"/login",}, []]
37
-
38
- res = json_request("/", :content_type=>'application/vnd.api+json', :method=>'GET')
39
- res.must_equal [400, ['{"error":"Please login to continue"}']]
40
-
41
- oj = true
42
-
43
- res = json_request("/", :content_type=>'application/x-www-form-urlencoded', :method=>'GET')
44
- res.must_equal [400, ['{"error":"Please login to continue"}']]
45
-
46
- res = json_request("/", :method=>'GET')
47
- res.must_equal [400, {'error'=>'Please login to continue'}]
48
-
49
- res = json_request("/login", :content_type=>'application/x-www-form-urlencoded', :include_headers=>true, :method=>'GET')
50
- msg = "Only JSON format requests are allowed"
51
- res[1].delete('Set-Cookie')
52
- res.must_equal [400, {"Content-Type"=>'text/html', "Content-Length"=>msg.length.to_s}, [msg]]
53
-
54
- jwt_refresh_login
55
-
56
- res = json_request("/", :content_type=>'application/x-www-form-urlencoded', :include_headers=>true, :method=>'GET')
57
- # res.must_equal [200, {"Content-Type"=>'text/html', "Content-Length"=>'1'}, ['1']]
58
- end
59
-
60
- it "should allow non-json requests if only_json? is false" do
61
- rodauth do
62
- enable :login, :logout, :jwt_refresh
63
- jwt_secret '1'
64
- only_json? false
65
- end
66
- roda(:jwt_html) do |r|
67
- r.rodauth
68
- rodauth.require_authentication
69
- view(:content=>'1')
70
- end
71
-
72
- login
73
- page.find('#notice_flash').text.must_equal 'You have been logged in'
74
- end
75
-
76
- it "should require POST for json requests" do
77
- rodauth do
78
- enable :login, :logout, :jwt_refresh
79
- jwt_secret '1'
80
- json_response_success_key 'success'
81
- end
82
- roda(:jwt) do |r|
83
- r.rodauth
84
- end
85
-
86
- res = json_request("/login", :method=>'GET')
87
- res.must_equal [405, {'error'=>'non-POST method used in JSON API'}]
88
- end
89
-
90
- it "should require Accept contain application/json if jwt_check_accept? is true and Accept is present" do
91
- rodauth do
92
- enable :login, :logout, :jwt_refresh
93
- jwt_secret '1'
94
- json_response_success_key 'success'
95
- jwt_check_accept? true
96
- end
97
- roda(:jwt) do |r|
98
- r.rodauth
99
- end
100
-
101
- res = json_request("/login", :headers=>{'HTTP_ACCEPT'=>'text/html'})
102
- res.must_equal [406, {'error'=>'Unsupported Accept header. Must accept "application/json" or compatible content type'}]
103
-
104
- jwt_refresh_validate_login(json_request("/login", :login=>'foo@example.com', :password=>'0123456789'))
105
- jwt_refresh_validate_login(json_request("/login", :headers=>{'HTTP_ACCEPT'=>'*/*'}, :login=>'foo@example.com', :password=>'0123456789'))
106
- jwt_refresh_validate_login(json_request("/login", :headers=>{'HTTP_ACCEPT'=>'application/*'}, :login=>'foo@example.com', :password=>'0123456789'))
107
- jwt_refresh_validate_login(json_request("/login", :headers=>{'HTTP_ACCEPT'=>'application/vnd.api+json'}, :login=>'foo@example.com', :password=>'0123456789'))
108
- end
109
-
110
- it "should clear jwt refresh token when closing account" do
111
- rodauth do
112
- enable :login, :jwt_refresh, :close_account
113
- jwt_secret '1'
114
- end
115
- roda(:jwt) do |r|
116
- r.rodauth
117
- rodauth.require_authentication
118
- response['Content-Type'] = 'application/json'
119
- {'hello' => 'world'}.to_json
120
- end
121
-
122
- jwt_refresh_login
123
-
124
- DB[:account_jwt_refresh_keys].count.must_equal 1
125
- res = json_request('/close-account', :password=>'0123456789')
126
- res[1].delete('access_token').must_be_kind_of(String)
127
- res.must_equal [200, {'success'=>"Your account has been closed"}]
128
- DB[:account_jwt_refresh_keys].count.must_equal 0
129
- end
130
-
131
-
132
- it "should set refresh tokens when creating accounts when using autologin" do
133
- rodauth do
134
- enable :login, :create_account, :jwt_refresh
135
- after_create_account{json_response[:account_id] = account_id}
136
- create_account_autologin? true
137
- end
138
- roda(:jwt) do |r|
139
- r.rodauth
140
- rodauth.require_authentication
141
- response['Content-Type'] = 'application/json'
142
- {'hello' => 'world'}.to_json
143
- end
144
-
145
- res = json_request('/create-account', :login=>'foo@example2.com', "login-confirm"=>'foo@example2.com', :password=>'0123456789', "password-confirm"=>'0123456789')
146
- refresh_token = res.last.delete('refresh_token')
147
- @authorization = res.last.delete('access_token')
148
- res.must_equal [200, {'success'=>"Your account has been created", 'account_id'=>DB[:accounts].max(:id)}]
149
-
150
- res = json_request("/")
151
- res.must_equal [200, {'hello'=>'world'}]
152
-
153
- # We can refresh our token
154
- res = json_request("/jwt-refresh", :refresh_token=>refresh_token)
155
- jwt_refresh_validate(res)
156
- @authorization = res.last.delete('access_token')
157
-
158
- # Which we can use to access protected resources
159
- res = json_request("/")
160
- res.must_equal [200, {'hello'=>'world'}]
161
- end
162
-
163
- [false, true].each do |hs|
164
- it "generates and refreshes Refresh Tokens #{'with hmac_secret' if hs}" do
165
- initial_secret = secret = SecureRandom.random_bytes(32) if hs
166
- rodauth do
167
- enable :login, :logout, :jwt_refresh
168
- hmac_secret{secret} if hs
169
- jwt_secret '1'
170
- end
171
- roda(:jwt) do |r|
172
- r.rodauth
173
- rodauth.require_authentication
174
- response['Content-Type'] = 'application/json'
175
- {'hello' => 'world'}.to_json
176
- end
177
- res = json_request("/")
178
- res.must_equal [401, {'error'=>'Please login to continue'}]
179
-
180
- # We can login
181
- res = jwt_refresh_login
182
- refresh_token = res.last['refresh_token']
183
-
184
- # Which gives us an access token which grants us access to protected resources
185
- @authorization = res.last['access_token']
186
- res = json_request("/")
187
- res.must_equal [200, {'hello'=>'world'}]
188
-
189
- # We can refresh our token
190
- res = json_request("/jwt-refresh", :refresh_token=>refresh_token)
191
- jwt_refresh_validate(res)
192
- second_refresh_token = res.last['refresh_token']
193
-
194
- # Which we can use to access protected resources
195
- @authorization = res.last['access_token']
196
- res = json_request("/")
197
- res.must_equal [200, {'hello'=>'world'}]
198
-
199
- # Subsequent refresh token is valid
200
- res = json_request("/jwt-refresh", :refresh_token=>second_refresh_token)
201
- jwt_refresh_validate(res)
202
- third_refresh_token = res.last['refresh_token']
203
-
204
- # First refresh token is now no longer valid
205
- res = json_request("/jwt-refresh", :refresh_token=>refresh_token)
206
- res.must_equal [400, {"error"=>"invalid JWT refresh token"}]
207
-
208
- # Third refresh token is valid
209
- res = json_request("/jwt-refresh", :refresh_token=>third_refresh_token)
210
- jwt_refresh_validate(res)
211
- fourth_refresh_token = res.last['refresh_token']
212
-
213
- # And still gives us a valid access token
214
- @authorization = res.last['access_token']
215
- res = json_request("/")
216
- res.must_equal [200, {'hello'=>'world'}]
217
-
218
- if hs
219
- # Refresh secret doesn't work if hmac_secret changed
220
- secret = SecureRandom.random_bytes(32)
221
- res = json_request("/jwt-refresh", :refresh_token=>fourth_refresh_token)
222
- res.first.must_equal 400
223
- res.must_equal [400, {'error'=>'invalid JWT refresh token'}]
224
-
225
- # Refresh secret works if hmac_secret changed back
226
- secret = initial_secret
227
- res = json_request("/jwt-refresh", :refresh_token=>fourth_refresh_token)
228
- jwt_refresh_validate(res)
229
-
230
- # And still gives us a valid access token
231
- @authorization = res.last['access_token']
232
- res = json_request("/")
233
- res.must_equal [200, {'hello'=>'world'}]
234
- end
235
- end
236
- end
237
-
238
- it "should not return access_token for failed login attempt" do
239
- rodauth do
240
- enable :login, :create_account, :jwt_refresh
241
- after_create_account{json_response[:account_id] = account_id}
242
- create_account_autologin? true
243
- end
244
- roda(:jwt) do |r|
245
- r.rodauth
246
- rodauth.require_authentication
247
- response['Content-Type'] = 'application/json'
248
- {'hello' => 'world'}.to_json
249
- end
250
-
251
- json_request('/create-account', :login=>'foo@example2.com', "login-confirm"=>'foo@example2.com', :password=>'0123456789', "password-confirm"=>'0123456789')
252
-
253
- res = json_request('/login', :login=>'foo@example2.com', :password=>'123123')
254
- res.must_equal [401, {"field-error"=>['password', 'invalid password'], "error"=>"There was an error logging in"}]
255
- end
256
- end