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,58 +0,0 @@
1
- require File.expand_path("spec_helper", File.dirname(__FILE__))
2
-
3
- describe 'Rodauth session expiration feature' do
4
- it "should expire sessions based on last activity and max lifetime checks" do
5
- inactivity = max_lifetime = 300
6
- expiration_default = true
7
- rodauth do
8
- enable :login, :session_expiration
9
- session_expiration_default{expiration_default}
10
- session_inactivity_timeout{inactivity}
11
- max_session_lifetime{max_lifetime}
12
- end
13
- roda do |r|
14
- rodauth.check_session_expiration
15
- r.rodauth
16
- r.get("remove-creation"){session.delete(rodauth.session_created_session_key); r.redirect '/'}
17
- r.get("set-creation"){session[rodauth.session_created_session_key] = Time.now.to_i - 100000; r.redirect '/'}
18
- r.root{view :content=>rodauth.logged_in? ? "Logged In" : "Not Logged"}
19
- end
20
-
21
- visit '/'
22
- page.body.must_include "Not Logged"
23
-
24
- login
25
- page.body.must_include "Logged In"
26
-
27
- inactivity = -1
28
- visit '/'
29
- page.title.must_equal 'Login'
30
- page.find('#error_flash').text.must_equal "This session has expired, please login again."
31
-
32
- login
33
- page.title.must_equal 'Login'
34
- page.find('#error_flash').text.must_equal "This session has expired, please login again."
35
-
36
- inactivity = 10
37
- login
38
- page.body.must_include "Logged In"
39
-
40
- visit '/set-creation'
41
- page.title.must_equal 'Login'
42
- page.find('#error_flash').text.must_equal "This session has expired, please login again."
43
-
44
- login
45
- page.body.must_include "Logged In"
46
-
47
- visit '/remove-creation'
48
- page.title.must_equal 'Login'
49
- page.find('#error_flash').text.must_equal "This session has expired, please login again."
50
-
51
- expiration_default = false
52
- login
53
- page.body.must_include "Logged In"
54
-
55
- visit '/remove-creation'
56
- page.body.must_include "Logged In"
57
- end
58
- end
@@ -1,127 +0,0 @@
1
- require File.expand_path("spec_helper", File.dirname(__FILE__))
2
-
3
- describe 'Rodauth single session feature' do
4
- it "should limit accounts to a single logged in session" do
5
- secret = nil
6
- allow_raw = true
7
- rodauth do
8
- enable :login, :logout, :single_session
9
- hmac_secret{secret}
10
- allow_raw_single_session_key?{allow_raw}
11
- end
12
- roda do |r|
13
- rodauth.check_single_session
14
- r.rodauth
15
- r.is("clear"){session.delete(rodauth.single_session_session_key); DB[:account_session_keys].delete; r.redirect '/'}
16
- r.root{view :content=>rodauth.logged_in? ? "Logged In" : "Not Logged"}
17
- end
18
-
19
- login
20
- page.body.must_include "Logged In"
21
-
22
- session1 = get_cookie('rack.session')
23
-
24
- logout
25
-
26
- visit '/'
27
- page.body.must_include "Not Logged"
28
-
29
- remove_cookie('rack.session')
30
- set_cookie('rack.session', session1)
31
- visit '/foo'
32
- page.current_path.must_equal '/'
33
- page.body.must_include "Not Logged"
34
- page.find('#error_flash').text.must_equal "This session has been logged out as another session has become active"
35
-
36
- login
37
- page.body.must_include "Logged In"
38
-
39
- session2 = get_cookie('rack.session')
40
- remove_cookie('rack.session')
41
- set_cookie('rack.session', session1)
42
- visit '/'
43
- page.body.must_include "Not Logged"
44
- page.find('#error_flash').text.must_equal "This session has been logged out as another session has become active"
45
-
46
- remove_cookie('rack.session')
47
- set_cookie('rack.session', session2)
48
- visit '/'
49
- page.body.must_include "Logged In"
50
-
51
- visit '/clear'
52
- page.current_path.must_equal '/'
53
- page.body.must_include "Logged In"
54
-
55
- secret = SecureRandom.random_bytes(32)
56
- visit '/'
57
- page.body.must_include "Logged In"
58
-
59
- allow_raw = false
60
- visit '/'
61
- page.body.must_include "Not Logged"
62
-
63
- login
64
- page.body.must_include "Logged In"
65
-
66
- allow_raw = true
67
- secret = SecureRandom.random_bytes(32)
68
- visit '/'
69
- page.body.must_include "Not Logged"
70
- end
71
-
72
- it "should limit accounts to a single logged in session" do
73
- rodauth do
74
- enable :login, :close_account, :single_session
75
- close_account_requires_password? false
76
- end
77
- roda do |r|
78
- rodauth.check_single_session
79
- r.rodauth
80
- r.root{view :content=>rodauth.logged_in? ? "Logged In" : "Not Logged"}
81
- end
82
-
83
- login
84
-
85
- DB[:account_session_keys].count.must_equal 1
86
- visit '/close-account'
87
- click_button 'Close Account'
88
- DB[:account_session_keys].count.must_equal 0
89
- end
90
-
91
- it "should limit accounts to a single logged in session when using jwt" do
92
- rodauth do
93
- enable :login, :logout, :single_session
94
- end
95
- roda(:jwt) do |r|
96
- rodauth.check_single_session
97
- r.rodauth
98
- r.post("clear"){rodauth.session.delete(:single_session_key); DB[:account_session_keys].delete; [3]}
99
- rodauth.logged_in? ? [1] : [2]
100
- end
101
-
102
- json_login
103
- authorization1 = @authorization
104
- json_logout
105
-
106
- json_request.must_equal [200, [2]]
107
- @authorization = authorization1
108
- json_request.must_equal [400, {'error'=>"This session has been logged out as another session has become active"}]
109
-
110
- json_login
111
- json_request.must_equal [200, [1]]
112
-
113
- authorization2 = @authorization
114
- @authorization = authorization1
115
- json_request.must_equal [400, {'error'=>"This session has been logged out as another session has become active"}]
116
-
117
- @authorization = authorization2
118
- json_request.must_equal [200, [1]]
119
-
120
- json_request('/clear').must_equal [200, [3]]
121
- json_request.must_equal [400, {'error'=>"This session has been logged out as another session has become active"}]
122
- json_request.must_equal [200, [2]]
123
-
124
- @authorization = authorization2
125
- json_request.must_equal [400, {'error'=>"This session has been logged out as another session has become active"}]
126
- end
127
- end
@@ -1,327 +0,0 @@
1
- $: << 'lib'
2
-
3
- if ENV['WARNING']
4
- require 'warning'
5
- Warning.ignore([:missing_ivar, :missing_gvar, :fixnum, :not_reached])
6
- #Warning.ignore(/warning: URI\.escape is obsolete\n\z/)
7
- Warning.ignore(:method_redefined, File.dirname(File.dirname(__FILE__)))
8
- end
9
-
10
- if ENV['COVERAGE']
11
- require 'coverage'
12
- require 'simplecov'
13
-
14
- def SimpleCov.rodauth_coverage(opts = {})
15
- start do
16
- add_filter "/spec/"
17
- add_group('Missing'){|src| src.covered_percent < 100}
18
- add_group('Covered'){|src| src.covered_percent == 100}
19
- yield self if block_given?
20
- end
21
- end
22
-
23
- ENV.delete('COVERAGE')
24
- SimpleCov.rodauth_coverage
25
- end
26
-
27
- require 'rubygems'
28
- require 'capybara'
29
- require 'capybara/dsl'
30
- require 'rack/test'
31
- require 'stringio'
32
- require 'securerandom'
33
-
34
- ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
35
- gem 'minitest'
36
- require 'minitest/global_expectations/autorun'
37
- require 'minitest/hooks/default'
38
-
39
- require 'roda'
40
- require 'sequel'
41
- require 'bcrypt'
42
- require 'mail'
43
- require 'logger'
44
- require 'tilt/string'
45
-
46
- db_url = ENV['RODAUTH_SPEC_DB'] || 'postgres:///?user=rodauth_test&password=rodauth_test'
47
- DB = Sequel.connect(db_url, :identifier_mangling=>false)
48
- DB.extension :freeze_datasets, :date_arithmetic
49
- puts "using #{DB.database_type}"
50
-
51
- #DB.loggers << Logger.new($stdout)
52
- if DB.adapter_scheme == :jdbc
53
- case DB.database_type
54
- when :postgres
55
- DB.add_named_conversion_proc(:citext){|s| s}
56
- when :sqlite
57
- DB.timezone = :utc
58
- Sequel.application_timezone = :local
59
- end
60
- end
61
-
62
- if ENV['RODAUTH_SPEC_MIGRATE']
63
- Sequel.extension :migration
64
- Sequel::Migrator.run(DB, 'spec/migrate_travis')
65
- end
66
-
67
- DB.freeze
68
-
69
- ENV['RACK_ENV'] = 'test'
70
-
71
- ::Mail.defaults do
72
- delivery_method :test
73
- end
74
-
75
- Base = Class.new(Roda)
76
- Base.opts[:check_dynamic_arity] = Base.opts[:check_arity] = :warn
77
- Base.plugin :flash
78
- Base.plugin :render, :layout_opts=>{:path=>'spec/views/layout.str'}
79
- Base.plugin(:not_found){raise "path #{request.path_info} not found"}
80
-
81
- if defined?(Roda::RodaVersionNumber) && Roda::RodaVersionNumber >= 30100
82
- if ENV['RODA_ROUTE_CSRF'] == '0'
83
- require 'roda/session_middleware'
84
- Base.opts[:sessions_convert_symbols] = true
85
- Base.use RodaSessionMiddleware, :secret=>SecureRandom.random_bytes(64), :key=>'rack.session'
86
- else
87
- ENV['RODA_ROUTE_CSRF'] ||= '1'
88
- Base.plugin :sessions, :secret=>SecureRandom.random_bytes(64), :key=>'rack.session'
89
- end
90
- else
91
- Base.use Rack::Session::Cookie, :secret => '0123456789'
92
- end
93
-
94
- unless defined?(Rack::Test::VERSION) && Rack::Test::VERSION >= '0.8'
95
- class Rack::Test::Cookie
96
- def path
97
- ([*(@options['path'] == "" ? "/" : @options['path'])].first.split(',').first || '/').strip
98
- end
99
- end
100
- end
101
-
102
- class Base
103
- attr_writer :title
104
- end
105
-
106
- JsonBase = Class.new(Roda)
107
- JsonBase.opts[:check_dynamic_arity] = JsonBase.opts[:check_arity] = :warn
108
- JsonBase.plugin(:not_found){raise "path #{request.path_info} not found"}
109
-
110
- class Minitest::HooksSpec
111
- include Rack::Test::Methods
112
- include Capybara::DSL
113
-
114
- case ENV['RODA_ROUTE_CSRF']
115
- when '1'
116
- USE_ROUTE_CSRF = true
117
- ROUTE_CSRF_OPTS = {}
118
- when '2'
119
- USE_ROUTE_CSRF = true
120
- ROUTE_CSRF_OPTS = {:require_request_specific_tokens=>false}
121
- else
122
- USE_ROUTE_CSRF = false
123
- end
124
-
125
- attr_reader :app
126
-
127
- def no_freeze!
128
- @no_freeze = true
129
- end
130
-
131
- def app=(app)
132
- @app = Capybara.app = app
133
- end
134
-
135
- def rodauth(&block)
136
- @rodauth_block = block
137
- end
138
-
139
- def rodauth_opts(type={})
140
- opts = type.is_a?(Hash) ? type : {}
141
- if USE_ROUTE_CSRF && !opts.has_key?(:csrf)
142
- opts[:csrf] = :route_csrf
143
- end
144
- opts
145
- end
146
-
147
- def roda(type=nil, &block)
148
- jwt_only = type == :jwt
149
- jwt = type == :jwt || type == :jwt_html
150
-
151
- app = Class.new(jwt_only ? JsonBase : Base)
152
- begin
153
- app.plugin :request_aref, :raise
154
- rescue LoadError
155
- end
156
- app.opts[:unsupported_block_result] = :raise
157
- app.opts[:unsupported_matcher] = :raise
158
- app.opts[:verbatim_string_matcher] = true
159
- rodauth_block = @rodauth_block
160
- opts = rodauth_opts(type)
161
-
162
- if jwt
163
- opts[:json] = jwt_only ? :only : true
164
- end
165
-
166
- app.plugin(:rodauth, opts) do
167
- title_instance_variable :@title
168
- if jwt
169
- enable :jwt
170
- jwt_secret '1'
171
- json_response_success_key 'success'
172
- json_response_custom_error_status? true
173
- end
174
- if ENV['RODAUTH_SEPARATE_SCHEMA']
175
- password_hash_table Sequel[:rodauth_test_password][:account_password_hashes]
176
- function_name do |name|
177
- "rodauth_test_password.#{name}"
178
- end
179
- end
180
- instance_exec(&rodauth_block)
181
- end
182
- if USE_ROUTE_CSRF && !jwt_only && opts[:csrf] != false
183
- app.plugin(:route_csrf, ROUTE_CSRF_OPTS)
184
- orig_block = block
185
- block = proc do |r|
186
- check_csrf!
187
- instance_exec(r, &orig_block)
188
- end
189
- end
190
- app.route(&block)
191
- app.precompile_rodauth_templates unless @no_precompile || jwt_only
192
- app.freeze unless @no_freeze
193
- self.app = app
194
- end
195
-
196
- def email_link(regexp, to='foo@example.com')
197
- msgs = Mail::TestMailer.deliveries
198
- msgs.length.must_equal 1
199
- msgs.first.to.first.must_equal to
200
-
201
- link = msgs.first.body.to_s.gsub(/ $/, '')[regexp]
202
- msgs.clear
203
- link.must_be_kind_of(String)
204
- link
205
- end
206
-
207
- def remove_cookie(key)
208
- page.driver.browser.rack_mock_session.cookie_jar.delete(key)
209
- end
210
-
211
- def get_cookie(key)
212
- page.driver.browser.rack_mock_session.cookie_jar[key]
213
- end
214
-
215
- def set_cookie(key, value)
216
- page.driver.browser.rack_mock_session.cookie_jar[key] = value
217
- end
218
-
219
- def json_request(path='/', params={})
220
- include_headers = params.delete(:include_headers)
221
- headers = params.delete(:headers)
222
-
223
- env = {"REQUEST_METHOD" => params.delete(:method) || "POST",
224
- "PATH_INFO" => path,
225
- "SCRIPT_NAME" => "",
226
- "CONTENT_TYPE" => params.delete(:content_type) || "application/json",
227
- "SERVER_NAME" => 'example.com',
228
- "rack.input"=>StringIO.new((params || {}).to_json),
229
- "rack.errors"=>$stderr
230
- }
231
-
232
- if @authorization
233
- env["HTTP_AUTHORIZATION"] = "Bearer: #{@authorization}"
234
- end
235
- if @cookie
236
- env["HTTP_COOKIE"] = @cookie
237
- end
238
-
239
- env.merge!(headers) if headers
240
-
241
- r = @app.call(env)
242
-
243
- if cookie = r[1]['Set-Cookie']
244
- if cookie.include?('expires=Thu, 01 Jan 1970 00:00:00 -0000')
245
- @cookie = nil
246
- else
247
- @cookie = cookie.split(';', 2)[0]
248
- end
249
- end
250
- if authorization = r[1]['Authorization']
251
- @authorization = authorization
252
- end
253
-
254
- if env["CONTENT_TYPE"] == "application/json"
255
- r[1]['Content-Type'].must_equal 'application/json'
256
- r[2] = JSON.parse("[#{r[2].join}]").first
257
- end
258
-
259
- r.delete_at(1) unless include_headers
260
- r
261
- end
262
-
263
- def json_login(opts={})
264
- res = json_request(opts[:path]||'/login', :login=>opts[:login]||'foo@example.com', :password=>opts[:pass]||'0123456789')
265
- res.must_equal [200, {"success"=>'You have been logged in'}] unless opts[:no_check]
266
- res
267
- end
268
-
269
- def jwt_refresh_login
270
- res = json_login({:no_check => true})
271
- jwt_refresh_validate_login(res)
272
- res
273
- end
274
-
275
- def jwt_refresh_validate_login(res)
276
- res.first.must_equal 200
277
- res.last.keys.sort.must_equal ['access_token', 'refresh_token', 'success']
278
- res.last['success'].must_equal 'You have been logged in'
279
- res
280
- end
281
-
282
- def jwt_refresh_validate(res)
283
- res.first.must_equal 200
284
- res.last.keys.sort.must_equal ['access_token', 'refresh_token']
285
- res
286
- end
287
-
288
- def json_logout
289
- json_request("/logout").must_equal [200, {"success"=>'You have been logged out'}]
290
- end
291
-
292
- def login(opts={})
293
- visit(opts[:path]||'/login') unless opts[:visit] == false
294
- fill_in 'Login', :with=>opts[:login]||'foo@example.com'
295
- fill_in 'Password', :with=>opts[:pass]||'0123456789'
296
- click_button 'Login'
297
- end
298
-
299
- def logout
300
- visit '/logout'
301
- click_button 'Logout'
302
- end
303
-
304
- around do |&block|
305
- DB.transaction(:rollback=>:always, :savepoint=>true, :auto_savepoint=>true){super(&block)}
306
- end
307
-
308
- around(:all) do |&block|
309
- DB.transaction(:rollback=>:always) do
310
- hash = BCrypt::Password.create('0123456789', :cost=>BCrypt::Engine::MIN_COST)
311
- table = ENV['RODAUTH_SEPARATE_SCHEMA'] ? Sequel[:rodauth_test_password][:account_password_hashes] : :account_password_hashes
312
- DB[table].insert(:id=>DB[:accounts].insert(:email=>'foo@example.com', :status_id=>2, :ph=>hash), :password_hash=>hash)
313
- super(&block)
314
- end
315
- end
316
-
317
- after do
318
- msgs = Mail::TestMailer.deliveries
319
- len = msgs.length
320
- msgs.clear
321
- len.must_equal 0
322
- Capybara.reset_sessions!
323
- Capybara.use_default_driver
324
- end
325
- end
326
-
327
-