rodauth 0.9.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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +3 -0
  3. data/MIT-LICENSE +18 -0
  4. data/README.rdoc +484 -0
  5. data/Rakefile +91 -0
  6. data/lib/roda/plugins/rodauth.rb +265 -0
  7. data/lib/roda/plugins/rodauth/base.rb +428 -0
  8. data/lib/roda/plugins/rodauth/change_login.rb +48 -0
  9. data/lib/roda/plugins/rodauth/change_password.rb +42 -0
  10. data/lib/roda/plugins/rodauth/close_account.rb +42 -0
  11. data/lib/roda/plugins/rodauth/create_account.rb +92 -0
  12. data/lib/roda/plugins/rodauth/lockout.rb +292 -0
  13. data/lib/roda/plugins/rodauth/login.rb +77 -0
  14. data/lib/roda/plugins/rodauth/logout.rb +36 -0
  15. data/lib/roda/plugins/rodauth/remember.rb +226 -0
  16. data/lib/roda/plugins/rodauth/reset_password.rb +205 -0
  17. data/lib/roda/plugins/rodauth/verify_account.rb +228 -0
  18. data/spec/migrate/001_tables.rb +64 -0
  19. data/spec/migrate_password/001_tables.rb +38 -0
  20. data/spec/rodauth_spec.rb +1114 -0
  21. data/spec/views/layout.str +11 -0
  22. data/spec/views/login.str +21 -0
  23. data/templates/change-login.str +22 -0
  24. data/templates/change-password.str +21 -0
  25. data/templates/close-account.str +9 -0
  26. data/templates/confirm-password.str +16 -0
  27. data/templates/create-account.str +33 -0
  28. data/templates/login.str +25 -0
  29. data/templates/logout.str +9 -0
  30. data/templates/remember.str +28 -0
  31. data/templates/reset-password-email.str +5 -0
  32. data/templates/reset-password-request.str +7 -0
  33. data/templates/reset-password.str +23 -0
  34. data/templates/unlock-account-email.str +5 -0
  35. data/templates/unlock-account-request.str +11 -0
  36. data/templates/unlock-account.str +11 -0
  37. data/templates/verify-account-email.str +4 -0
  38. data/templates/verify-account-resend.str +7 -0
  39. data/templates/verify-account.str +11 -0
  40. metadata +227 -0
@@ -0,0 +1,228 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ module Rodauth
4
+ VerifyAccount = Feature.define(:verify_account) do
5
+ depends :login, :create_account
6
+ route 'verify-account'
7
+ notice_flash "Your account has been verified"
8
+ view 'verify-account', 'Verify Account'
9
+ additional_form_tags
10
+ after
11
+ button 'Verify Account'
12
+ redirect
13
+
14
+ auth_value_methods(
15
+ :no_matching_verify_account_key_message,
16
+ :verify_account_autologin?,
17
+ :verify_account_email_subject,
18
+ :verify_account_email_sent_redirect,
19
+ :verify_account_email_sent_notice_flash,
20
+ :verify_account_id_column,
21
+ :verify_account_key_column,
22
+ :verify_account_key_param,
23
+ :verify_account_key_value,
24
+ :verify_account_table
25
+ )
26
+ auth_methods(
27
+ :account_from_verify_account_key,
28
+ :create_verify_account_key,
29
+ :create_verify_account_email,
30
+ :remove_verify_account_key,
31
+ :send_verify_account_email,
32
+ :verify_account,
33
+ :verify_account_email_body,
34
+ :verify_account_email_link,
35
+ :verify_account_key_insert_hash
36
+ )
37
+
38
+ get_block do |r, auth|
39
+ if key = r[auth.verify_account_key_param]
40
+ if auth._account_from_verify_account_key(key)
41
+ auth.verify_account_view
42
+ else
43
+ auth.set_redirect_error_flash auth.no_matching_verify_account_key_message
44
+ r.redirect auth.require_login_redirect
45
+ end
46
+ end
47
+ end
48
+
49
+ post_block do |r, auth|
50
+ if login = r[auth.login_param]
51
+ if auth._account_from_login(login.to_s) && !auth.open_account? && auth.verify_account_email_resend
52
+ auth.set_notice_flash auth.verify_account_email_sent_notice_flash
53
+ r.redirect auth.verify_account_email_sent_redirect
54
+ end
55
+ elsif key = r[auth.verify_account_key_param]
56
+ if auth._account_from_verify_account_key(key.to_s)
57
+ auth.transaction do
58
+ auth.verify_account
59
+ auth.remove_verify_account_key
60
+ auth.after_verify_account
61
+ end
62
+ if auth.verify_account_autologin?
63
+ auth.update_session
64
+ end
65
+ auth.set_notice_flash auth.verify_account_notice_flash
66
+ r.redirect(auth.verify_account_redirect)
67
+ end
68
+ end
69
+ end
70
+
71
+ def before_login_attempt
72
+ unless open_account?
73
+ set_error_flash attempt_to_login_to_unverified_account_notice_message
74
+ response.write resend_verify_account_view
75
+ request.halt
76
+ end
77
+ super
78
+ end
79
+
80
+ def generate_verify_account_key_value
81
+ @verify_account_key_value = random_key
82
+ end
83
+
84
+ def create_verify_account_key
85
+ ds = db[verify_account_table].where(verify_account_id_column=>account_id_value)
86
+ transaction do
87
+ ds.insert(verify_account_key_insert_hash) if ds.empty?
88
+ end
89
+ end
90
+
91
+ def verify_account_key_insert_hash
92
+ {verify_account_id_column=>account_id_value, verify_account_key_column=>verify_account_key_value}
93
+ end
94
+
95
+ def remove_verify_account_key
96
+ db[verify_account_table].where(verify_account_id_column=>account_id_value).delete
97
+ end
98
+
99
+ def verify_account
100
+ account.set(account_status_id=>account_open_status_value).save_changes(:raise_on_failure=>true)
101
+ end
102
+
103
+ def verify_account_resend_additional_form_tags
104
+ nil
105
+ end
106
+
107
+ def verify_account_resend_button
108
+ 'Send Verification Email Again'
109
+ end
110
+
111
+ def verify_account_email_resend
112
+ if @verify_account_key_value = db[verify_account_table].where(verify_account_id_column=>account_id_value).get(verify_account_key_column)
113
+ send_verify_account_email
114
+ true
115
+ end
116
+ end
117
+
118
+ def attempt_to_create_unverified_account_notice_message
119
+ "The account you tried to create is currently awaiting verification"
120
+ end
121
+
122
+ def attempt_to_login_to_unverified_account_notice_message
123
+ "The account you tried to login with is currently awaiting verification"
124
+ end
125
+
126
+ def resend_verify_account_view
127
+ view('verify-account-resend', 'Resend Verification Email')
128
+ end
129
+
130
+ def verify_account_email_sent_notice_flash
131
+ "An email has been sent to you with a link to verify your account"
132
+ end
133
+
134
+ def create_account_notice_flash
135
+ verify_account_email_sent_notice_flash
136
+ end
137
+
138
+ def after_create_account
139
+ generate_verify_account_key_value
140
+ create_verify_account_key
141
+ send_verify_account_email
142
+ end
143
+
144
+ def new_account(login)
145
+ if _account_from_login(login)
146
+ set_error_flash attempt_to_create_unverified_account_notice_message
147
+ response.write resend_verify_account_view
148
+ request.halt
149
+ end
150
+ super
151
+ end
152
+
153
+ def no_matching_verify_account_key_message
154
+ "invalid verify account key"
155
+ end
156
+
157
+ def _account_from_verify_account_key(key)
158
+ @account = account_from_verify_account_key(key)
159
+ end
160
+
161
+ def account_from_verify_account_key(key)
162
+ id, key = key.split('_', 2)
163
+ id_column = verify_account_id_column
164
+ ds = db[verify_account_table].
165
+ select(id_column).
166
+ where(id_column=>id, verify_account_key_column=>key)
167
+ @account = account_model.where(account_status_id=>account_unverified_status_value, account_id=>ds).first
168
+ end
169
+
170
+ def verify_account_email_sent_redirect
171
+ require_login_redirect
172
+ end
173
+
174
+ def verify_account_table
175
+ :account_verification_keys
176
+ end
177
+
178
+ def verify_account_id_column
179
+ :id
180
+ end
181
+
182
+ def verify_account_key_column
183
+ :key
184
+ end
185
+
186
+ def account_initial_status_value
187
+ account_unverified_status_value
188
+ end
189
+
190
+ attr_reader :verify_account_key_value
191
+
192
+ def create_verify_account_email
193
+ create_email(verify_account_email_subject, verify_account_email_body)
194
+ end
195
+
196
+ def send_verify_account_email
197
+ create_verify_account_email.deliver!
198
+ end
199
+
200
+ def verify_account_email_body
201
+ render('verify-account-email')
202
+ end
203
+
204
+ def verify_account_email_link
205
+ "#{request.base_url}#{prefix}/#{verify_account_route}?#{verify_account_key_param}=#{account_id_value}_#{verify_account_key_value}"
206
+ end
207
+
208
+ def verify_account_email_subject
209
+ 'Verify Account'
210
+ end
211
+
212
+ def verify_account_key_param
213
+ 'key'
214
+ end
215
+
216
+ def verify_account_autologin?
217
+ false
218
+ end
219
+
220
+ def after_close_account
221
+ super
222
+ db[verify_account_table].where(reset_password_id_column=>account_id_value).delete
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
228
+
@@ -0,0 +1,64 @@
1
+ Sequel.migration do
2
+ up do
3
+ # Used by the account verification and close account features
4
+ create_table(:account_statuses) do
5
+ Integer :id, :primary_key=>true
6
+ String :name, :null=>false, :unique=>true
7
+ end
8
+ from(:account_statuses).import([:id, :name], [[1, 'Unverified'], [2, 'Verified'], [3, 'Closed']])
9
+
10
+ # Used by the create account, account verification,
11
+ # and close account features.
12
+ create_table(:accounts) do
13
+ primary_key :id, :type=>Bignum
14
+ foreign_key :status_id, :account_statuses, :null=>false, :default=>1
15
+ citext :email, :null=>false
16
+
17
+ constraint :valid_email, :email=>/^[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+$/
18
+ index :email, :unique=>true, :where=>{:status_id=>[1, 2]}
19
+
20
+ # Only for testing of account_password_hash_column, not recommended for new
21
+ # applications
22
+ String :ph
23
+ end
24
+
25
+ # Used by the password reset feature
26
+ create_table(:account_password_reset_keys) do
27
+ foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
28
+ String :key, :null=>false
29
+ DateTime :deadline, :null=>false, :default=>Sequel.lit("CURRENT_TIMESTAMP + '1 day'")
30
+ end
31
+
32
+ # Used by the account verification feature
33
+ create_table(:account_verification_keys) do
34
+ foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
35
+ String :key, :null=>false
36
+ end
37
+
38
+ # Used by the remember me feature
39
+ create_table(:account_remember_keys) do
40
+ foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
41
+ String :key, :null=>false
42
+ DateTime :deadline, :null=>false, :default=>Sequel.lit("CURRENT_TIMESTAMP + '2 weeks'")
43
+ end
44
+
45
+ # Used by the lockout feature
46
+ create_table(:account_login_failures) do
47
+ foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
48
+ Integer :number, :null=>false, :default=>1
49
+ end
50
+ create_table(:account_lockouts) do
51
+ foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
52
+ String :key, :null=>false
53
+ DateTime :deadline, :null=>false, :default=>Sequel.lit("CURRENT_TIMESTAMP + '1 day'")
54
+ end
55
+
56
+ # Grant password user access to reference accounts
57
+ pw_user = get{Sequel.lit('current_user')} + '_password'
58
+ run "GRANT REFERENCES ON accounts TO #{pw_user}"
59
+ end
60
+
61
+ down do
62
+ drop_table(:account_lockouts, :account_login_failures, :account_remember_keys, :account_verification_keys, :account_password_reset_keys, :accounts, :account_statuses)
63
+ end
64
+ end
@@ -0,0 +1,38 @@
1
+ Sequel.migration do
2
+ up do
3
+ # Used by the login and change password features
4
+ create_table(:account_password_hashes) do
5
+ foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
6
+ String :password_hash, :null=>false
7
+ end
8
+
9
+ # Function used to check if a password is valid. Takes the related account id
10
+ # and unencrypted password, checks if password matches password hash.
11
+ run <<END
12
+ CREATE OR REPLACE FUNCTION account_valid_password(account_id int8, password text) RETURNS boolean AS $$
13
+ DECLARE valid boolean;
14
+ BEGIN
15
+ SELECT password_hash = crypt($2, password_hash) INTO valid
16
+ FROM account_password_hashes
17
+ WHERE account_id = id;
18
+ RETURN valid;
19
+ END;
20
+ $$ LANGUAGE plpgsql
21
+ SECURITY DEFINER
22
+ SET search_path = public, pg_temp;
23
+ END
24
+
25
+ # Restrict access to the password hash table
26
+ app_user = get{Sequel.lit('current_user')}.sub(/_password\z/, '')
27
+ run "REVOKE ALL ON account_password_hashes FROM public"
28
+ run "REVOKE ALL ON FUNCTION account_valid_password(int8, text) FROM public"
29
+ run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{app_user}"
30
+ run "GRANT SELECT(id) ON account_password_hashes TO #{app_user}"
31
+ run "GRANT EXECUTE ON FUNCTION account_valid_password(int8, text) TO #{app_user}"
32
+ end
33
+
34
+ down do
35
+ run "DROP FUNCTION account_valid_password(int8, text)"
36
+ drop_table(:account_password_hashes)
37
+ end
38
+ end
@@ -0,0 +1,1114 @@
1
+ $: << 'lib'
2
+
3
+ if ENV['COVERAGE']
4
+ require 'coverage'
5
+ require 'simplecov'
6
+
7
+ def SimpleCov.rodauth_coverage(opts = {})
8
+ start do
9
+ add_filter "/spec/"
10
+ add_group('Missing'){|src| src.covered_percent < 100}
11
+ add_group('Covered'){|src| src.covered_percent == 100}
12
+ yield self if block_given?
13
+ end
14
+ end
15
+
16
+ ENV.delete('COVERAGE')
17
+ SimpleCov.rodauth_coverage
18
+ end
19
+
20
+ require 'rubygems'
21
+ require 'capybara'
22
+ require 'capybara/dsl'
23
+ require 'rack/test'
24
+ gem 'minitest'
25
+ require 'minitest/autorun'
26
+ require 'minitest/hooks/default'
27
+
28
+ require 'roda'
29
+ require 'sequel'
30
+ require 'bcrypt'
31
+ require 'mail'
32
+ require 'logger'
33
+ require 'tilt/string'
34
+
35
+ DB = Sequel.postgres(:user=>'rodauth_test', :password=>'rodauth_test')
36
+ #DB.loggers << Logger.new($stdout)
37
+
38
+ ENV['RACK_ENV'] = 'test'
39
+
40
+ ::Mail.defaults do
41
+ delivery_method :test
42
+ end
43
+
44
+ class Account < Sequel::Model
45
+ plugin :validation_helpers
46
+
47
+ def validate
48
+ super
49
+ validates_unique(:email){|ds| ds.where(:status_id=>[1,2])} unless status_id == 3
50
+ end
51
+ end
52
+
53
+ Base = Class.new(Roda)
54
+ Base.plugin :render, :layout=>{:path=>'spec/views/layout.str'}
55
+ Base.plugin(:not_found){raise "path #{request.path_info} not found"}
56
+ Base.use Rack::Session::Cookie, :secret=>'0123456789'
57
+ class Base
58
+ attr_writer :title
59
+ end
60
+
61
+ class Minitest::HooksSpec
62
+ include Rack::Test::Methods
63
+ include Capybara::DSL
64
+
65
+ attr_reader :app
66
+
67
+ def no_freeze!
68
+ @no_freeze = true
69
+ end
70
+
71
+ def app=(app)
72
+ @app = Capybara.app = app
73
+ end
74
+
75
+ def rodauth(&block)
76
+ @rodauth_block = block
77
+ end
78
+
79
+ def roda(&block)
80
+ app = Class.new(Base)
81
+ rodauth_block = @rodauth_block
82
+ app.plugin(:rodauth) do
83
+ title_instance_variable :@title
84
+ instance_exec(&rodauth_block)
85
+ end
86
+ app.route(&block)
87
+ app.freeze unless @no_freeze
88
+ self.app = app
89
+ end
90
+
91
+ def email_link(regexp)
92
+ link = Mail::TestMailer.deliveries.first.body.to_s[regexp]
93
+ Mail::TestMailer.deliveries.clear
94
+ link.must_be_kind_of(String)
95
+ link
96
+ end
97
+
98
+ def remove_cookie(key)
99
+ page.driver.browser.rack_mock_session.cookie_jar.delete(key)
100
+ end
101
+
102
+ def get_cookie(key)
103
+ page.driver.browser.rack_mock_session.cookie_jar[key]
104
+ end
105
+
106
+ def set_cookie(key, value)
107
+ page.driver.browser.rack_mock_session.cookie_jar[key] = value
108
+ end
109
+
110
+ around do |&block|
111
+ DB.transaction(:rollback=>:always, :savepoint=>true, :auto_savepoint=>true){super(&block)}
112
+ end
113
+
114
+ around(:all) do |&block|
115
+ DB.transaction(:rollback=>:always){super(&block)}
116
+ end
117
+
118
+ after do
119
+ Capybara.reset_sessions!
120
+ Capybara.use_default_driver
121
+ end
122
+ end
123
+
124
+ describe 'Rodauth' do
125
+ before(:all) do
126
+ hash = BCrypt::Password.create('0123456789', :cost=>BCrypt::Engine::MIN_COST)
127
+ DB[:account_password_hashes].insert(:id=>Account.create(:email=>'foo@example.com', :status_id=>2, :ph=>hash).id, :password_hash=>hash)
128
+ end
129
+
130
+ it "should handle logins and logouts" do
131
+ rodauth{enable :login, :logout}
132
+ roda do |r|
133
+ r.rodauth
134
+ next unless session[:account_id]
135
+ r.root{view :content=>"Logged In"}
136
+ end
137
+
138
+ visit '/login'
139
+ page.title.must_equal 'Login'
140
+
141
+ fill_in 'Login', :with=>'foo@example2.com'
142
+ fill_in 'Password', :with=>'0123456789'
143
+ click_button 'Login'
144
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
145
+ page.html.must_match(/no matching login/)
146
+
147
+ fill_in 'Login', :with=>'foo@example.com'
148
+ fill_in 'Password', :with=>'012345678'
149
+ click_button 'Login'
150
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
151
+ page.html.must_match(/invalid password/)
152
+
153
+ fill_in 'Password', :with=>'0123456789'
154
+ click_button 'Login'
155
+ page.current_path.must_equal '/'
156
+ page.find('#notice_flash').text.must_equal 'You have been logged in'
157
+ page.html.must_match(/Logged In/)
158
+
159
+ visit '/logout'
160
+ page.title.must_equal 'Logout'
161
+
162
+ click_button 'Logout'
163
+ page.find('#notice_flash').text.must_equal 'You have been logged out'
164
+ page.current_path.must_equal '/login'
165
+ end
166
+
167
+ it "should not allow login to unverified account" do
168
+ rodauth{enable :login}
169
+ roda do |r|
170
+ r.rodauth
171
+ next unless session[:account_id]
172
+ r.root{view :content=>"Logged In"}
173
+ end
174
+
175
+ visit '/login'
176
+ page.title.must_equal 'Login'
177
+
178
+ Account.first.update(:status_id=>1)
179
+ fill_in 'Login', :with=>'foo@example.com'
180
+ fill_in 'Password', :with=>'0123456789'
181
+ click_button 'Login'
182
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
183
+ page.html.must_match(/unverified account, please verify account before logging in/)
184
+ end
185
+
186
+ it "should handle overriding login action" do
187
+ rodauth do
188
+ enable :login
189
+ login_post_block do |r, _|
190
+ if r['login'] == 'apple' && r['password'] == 'banana'
191
+ session[:user_id] = 'pear'
192
+ r.redirect '/'
193
+ end
194
+ r.redirect '/login'
195
+ end
196
+ end
197
+ roda do |r|
198
+ r.rodauth
199
+ next unless session[:user_id] == 'pear'
200
+ r.root{"Logged In"}
201
+ end
202
+
203
+ visit '/login'
204
+
205
+ fill_in 'Login', :with=>'appl'
206
+ fill_in 'Password', :with=>'banana'
207
+ click_button 'Login'
208
+ page.html.wont_match(/Logged In/)
209
+
210
+ fill_in 'Login', :with=>'apple'
211
+ fill_in 'Password', :with=>'banan'
212
+ click_button 'Login'
213
+ page.html.wont_match(/Logged In/)
214
+
215
+ fill_in 'Login', :with=>'apple'
216
+ fill_in 'Password', :with=>'banana'
217
+ click_button 'Login'
218
+ page.current_path.must_equal '/'
219
+ page.html.must_match(/Logged In/)
220
+ end
221
+
222
+ it "should handle overriding some login attributes" do
223
+ rodauth do
224
+ enable :login
225
+ account_from_login do |login|
226
+ Account.first if login == 'apple'
227
+ end
228
+ password_match? do |password|
229
+ password == 'banana'
230
+ end
231
+ update_session do
232
+ session[:user_id] = 'pear'
233
+ end
234
+ no_matching_login_message "no user"
235
+ invalid_password_message "bad password"
236
+ end
237
+ roda do |r|
238
+ r.rodauth
239
+ next unless session[:user_id] == 'pear'
240
+ r.root{"Logged In"}
241
+ end
242
+
243
+ visit '/login'
244
+
245
+ fill_in 'Login', :with=>'appl'
246
+ fill_in 'Password', :with=>'banana'
247
+ click_button 'Login'
248
+ page.html.must_match(/no user/)
249
+
250
+ fill_in 'Login', :with=>'apple'
251
+ fill_in 'Password', :with=>'banan'
252
+ click_button 'Login'
253
+ page.html.must_match(/bad password/)
254
+
255
+ fill_in 'Password', :with=>'banana'
256
+ click_button 'Login'
257
+ page.current_path.must_equal '/'
258
+ page.html.must_match(/Logged In/)
259
+ end
260
+
261
+ it "should handle a prefix and some other login options" do
262
+ rodauth do
263
+ enable :login, :logout
264
+ prefix 'auth'
265
+ session_key :login_email
266
+ account_from_session{Account.first(:email=>session_value)}
267
+ account_session_value{account.email}
268
+ login_param{request['lp']}
269
+ password_param 'p'
270
+ login_redirect{"/foo/#{account.email}"}
271
+ logout_redirect '/auth/lin'
272
+ login_route 'lin'
273
+ logout_route 'lout'
274
+ end
275
+ no_freeze!
276
+ roda do |r|
277
+ r.on 'auth' do
278
+ r.rodauth
279
+ end
280
+ next unless session[:login_email] =~ /example/
281
+ r.get('foo/:email'){|e| "Logged In: #{e}"}
282
+ end
283
+ app.plugin :render, :views=>'spec/views', :engine=>'str'
284
+
285
+ visit '/auth/lin?lp=l'
286
+
287
+ fill_in 'Login', :with=>'foo@example2.com'
288
+ fill_in 'Password', :with=>'0123456789'
289
+ click_button 'Login'
290
+ page.html.must_match(/no matching login/)
291
+
292
+ fill_in 'Login', :with=>'foo@example.com'
293
+ fill_in 'Password', :with=>'012345678'
294
+ click_button 'Login'
295
+ page.html.must_match(/invalid password/)
296
+
297
+ fill_in 'Login', :with=>'foo@example.com'
298
+ fill_in 'Password', :with=>'0123456789'
299
+ click_button 'Login'
300
+ page.current_path.must_equal '/foo/foo@example.com'
301
+ page.html.must_match(/Logged In: foo@example\.com/)
302
+
303
+ visit '/auth/lout'
304
+ click_button 'Logout'
305
+ page.current_path.must_equal '/auth/lin'
306
+ end
307
+
308
+ it "should support closing accounts" do
309
+ rodauth do
310
+ enable :login, :close_account
311
+ end
312
+ roda do |r|
313
+ r.rodauth
314
+ r.root{""}
315
+ end
316
+
317
+ visit '/login'
318
+ fill_in 'Login', :with=>'foo@example.com'
319
+ fill_in 'Password', :with=>'0123456789'
320
+ click_button 'Login'
321
+ page.current_path.must_equal '/'
322
+
323
+ visit '/close-account'
324
+ click_button 'Close Account'
325
+ page.current_path.must_equal '/'
326
+
327
+ Account.select_map(:status_id).must_equal [3]
328
+ end
329
+
330
+ it "should support closing accounts with overrides" do
331
+ rodauth do
332
+ enable :login, :close_account
333
+ close_account do
334
+ account.email = 'foo@bar.com'
335
+ super()
336
+ end
337
+ close_account_route 'close'
338
+ close_account_redirect '/login'
339
+ end
340
+ roda do |r|
341
+ r.rodauth
342
+ r.root{""}
343
+ end
344
+
345
+ visit '/login'
346
+ fill_in 'Login', :with=>'foo@example.com'
347
+ fill_in 'Password', :with=>'0123456789'
348
+ click_button 'Login'
349
+ page.current_path.must_equal '/'
350
+
351
+ visit '/close'
352
+ page.title.must_equal 'Close Account'
353
+ click_button 'Close Account'
354
+ page.find('#notice_flash').text.must_equal "Your account has been closed"
355
+ page.current_path.must_equal '/login'
356
+
357
+ Account.select_map(:status_id).must_equal [3]
358
+ Account.select_map(:email).must_equal ['foo@bar.com']
359
+ end
360
+
361
+ [false, true].each do |ph|
362
+ it "should support creating accounts #{'with account_password_hash_column' if ph}" do
363
+ rodauth do
364
+ enable :login, :create_account
365
+ account_password_hash_column :ph if ph
366
+ end
367
+ roda do |r|
368
+ r.rodauth
369
+ r.root{view :content=>""}
370
+ end
371
+
372
+ visit '/create-account'
373
+ fill_in 'Login', :with=>'foo@example.com'
374
+ fill_in 'Confirm Login', :with=>'foo@example.com'
375
+ fill_in 'Password', :with=>'0123456789'
376
+ fill_in 'Confirm Password', :with=>'0123456789'
377
+ click_button 'Create Account'
378
+ page.html.must_match(/is already taken/)
379
+ page.find('#error_flash').text.must_equal "There was an error creating your account"
380
+ page.current_path.must_equal '/create-account'
381
+
382
+ fill_in 'Login', :with=>'foo@example2.com'
383
+ fill_in 'Password', :with=>'0123456789'
384
+ fill_in 'Confirm Password', :with=>'0123456789'
385
+ click_button 'Create Account'
386
+ page.html.must_match(/logins do not match/)
387
+ page.find('#error_flash').text.must_equal "There was an error creating your account"
388
+ page.current_path.must_equal '/create-account'
389
+
390
+ fill_in 'Confirm Login', :with=>'foo@example2.com'
391
+ fill_in 'Password', :with=>'0123456789'
392
+ fill_in 'Confirm Password', :with=>'012345678'
393
+ click_button 'Create Account'
394
+ page.html.must_match(/passwords do not match/)
395
+ page.find('#error_flash').text.must_equal "There was an error creating your account"
396
+ page.current_path.must_equal '/create-account'
397
+
398
+ fill_in 'Password', :with=>'0123456789'
399
+ fill_in 'Confirm Password', :with=>'0123456789'
400
+ click_button 'Create Account'
401
+ page.find('#notice_flash').text.must_equal "Your account has been created"
402
+ page.current_path.must_equal '/'
403
+
404
+ visit '/login'
405
+ fill_in 'Login', :with=>'foo@example2.com'
406
+ fill_in 'Password', :with=>'0123456789'
407
+ click_button 'Login'
408
+ page.current_path.must_equal '/'
409
+ end
410
+
411
+ it "should support changing passwords for accounts #{'with account_password_hash_column' if ph}" do
412
+ rodauth do
413
+ enable :login, :logout, :change_password
414
+ account_password_hash_column :ph if ph
415
+ end
416
+ roda do |r|
417
+ r.rodauth
418
+ r.root{view :content=>""}
419
+ end
420
+
421
+ visit '/login'
422
+ fill_in 'Login', :with=>'foo@example.com'
423
+ fill_in 'Password', :with=>'0123456789'
424
+ click_button 'Login'
425
+ page.current_path.must_equal '/'
426
+
427
+ visit '/change-password'
428
+ page.title.must_equal 'Change Password'
429
+
430
+ fill_in 'Password', :with=>'0123456'
431
+ fill_in 'Confirm Password', :with=>'0123456789'
432
+ click_button 'Change Password'
433
+ page.html.must_match(/passwords do not match/)
434
+ page.find('#error_flash').text.must_equal "There was an error changing your password"
435
+ page.current_path.must_equal '/change-password'
436
+
437
+ fill_in 'Password', :with=>'0123456'
438
+ fill_in 'Confirm Password', :with=>'0123456'
439
+ click_button 'Change Password'
440
+ page.find('#notice_flash').text.must_equal "Your password has been changed"
441
+ page.current_path.must_equal '/'
442
+
443
+ visit '/logout'
444
+ click_button 'Logout'
445
+
446
+ visit '/login'
447
+ fill_in 'Login', :with=>'foo@example.com'
448
+ fill_in 'Password', :with=>'0123456789'
449
+ click_button 'Login'
450
+ page.html.must_match(/invalid password/)
451
+ page.current_path.must_equal '/login'
452
+
453
+ fill_in 'Password', :with=>'0123456'
454
+ click_button 'Login'
455
+ page.current_path.must_equal '/'
456
+ end
457
+ end
458
+
459
+ it "should support changing logins for accounts" do
460
+ Account.create(:email=>'foo2@example.com')
461
+
462
+ rodauth do
463
+ enable :login, :logout, :change_login
464
+ end
465
+ roda do |r|
466
+ r.rodauth
467
+ r.root{view :content=>""}
468
+ end
469
+
470
+ visit '/login'
471
+ fill_in 'Login', :with=>'foo@example.com'
472
+ fill_in 'Password', :with=>'0123456789'
473
+ click_button 'Login'
474
+ page.current_path.must_equal '/'
475
+
476
+ visit '/change-login'
477
+ page.title.must_equal 'Change Login'
478
+
479
+ fill_in 'Login', :with=>'foo@example.com'
480
+ fill_in 'Confirm Login', :with=>'foo2@example.com'
481
+ click_button 'Change Login'
482
+ page.find('#error_flash').text.must_equal "There was an error changing your login"
483
+ page.html.must_match(/logins do not match/)
484
+ page.current_path.must_equal '/change-login'
485
+
486
+ fill_in 'Login', :with=>'foo2@example.com'
487
+ click_button 'Change Login'
488
+ page.find('#error_flash').text.must_equal "There was an error changing your login"
489
+ page.html.must_match(/is already taken/)
490
+ page.current_path.must_equal '/change-login'
491
+
492
+ fill_in 'Login', :with=>'foo3@example.com'
493
+ fill_in 'Confirm Login', :with=>'foo3@example.com'
494
+ click_button 'Change Login'
495
+ page.find('#notice_flash').text.must_equal "Your login has been changed"
496
+ page.current_path.must_equal '/'
497
+
498
+ visit '/logout'
499
+ click_button 'Logout'
500
+
501
+ visit '/login'
502
+ fill_in 'Login', :with=>'foo3@example.com'
503
+ fill_in 'Password', :with=>'0123456789'
504
+ click_button 'Login'
505
+ page.current_path.must_equal '/'
506
+ end
507
+
508
+ it "should support setting requirements for passwords" do
509
+ rodauth do
510
+ enable :login, :create_account, :change_password
511
+ password_meets_requirements? do |password|
512
+ password =~ /banana/
513
+ end
514
+ end
515
+ roda do |r|
516
+ r.rodauth
517
+ r.root{view :content=>""}
518
+ end
519
+
520
+ visit '/create-account'
521
+ fill_in 'Login', :with=>'foo2@example.com'
522
+ fill_in 'Confirm Login', :with=>'foo2@example.com'
523
+ fill_in 'Password', :with=>'apple'
524
+ fill_in 'Confirm Password', :with=>'apple'
525
+ click_button 'Create Account'
526
+ page.html.must_match(/invalid password, does not meet requirements/)
527
+ page.find('#error_flash').text.must_equal "There was an error creating your account"
528
+ page.current_path.must_equal '/create-account'
529
+
530
+ fill_in 'Password', :with=>'banana'
531
+ fill_in 'Confirm Password', :with=>'banana'
532
+ click_button 'Create Account'
533
+
534
+ visit '/login'
535
+ fill_in 'Login', :with=>'foo2@example.com'
536
+ fill_in 'Password', :with=>'banana'
537
+ click_button 'Login'
538
+
539
+ visit '/change-password'
540
+ fill_in 'Password', :with=>'apple'
541
+ fill_in 'Confirm Password', :with=>'apple'
542
+ click_button 'Change Password'
543
+ page.html.must_match(/invalid password, does not meet requirements/)
544
+ page.find('#error_flash').text.must_equal "There was an error changing your password"
545
+ page.current_path.must_equal '/change-password'
546
+
547
+ fill_in 'Password', :with=>'my_banana_3'
548
+ fill_in 'Confirm Password', :with=>'my_banana_3'
549
+ click_button 'Change Password'
550
+ page.current_path.must_equal '/'
551
+ end
552
+
553
+ it "should support autologin after account creation" do
554
+ rodauth do
555
+ enable :login, :create_account
556
+ create_account_autologin? true
557
+ end
558
+ roda do |r|
559
+ r.rodauth
560
+ next unless session[:account_id]
561
+ r.root{view :content=>"Logged In: #{Account[session[:account_id]].email}"}
562
+ end
563
+
564
+ visit '/create-account'
565
+ fill_in 'Login', :with=>'foo2@example.com'
566
+ fill_in 'Confirm Login', :with=>'foo2@example.com'
567
+ fill_in 'Password', :with=>'apple2'
568
+ fill_in 'Confirm Password', :with=>'apple2'
569
+ click_button 'Create Account'
570
+ page.html.must_match(/Logged In: foo2@example\.com/)
571
+ end
572
+
573
+ it "should require login to perform certain actions" do
574
+ rodauth do
575
+ enable :login, :change_password, :change_login, :close_account
576
+ end
577
+ roda do |r|
578
+ r.rodauth
579
+
580
+ r.is "a" do
581
+ rodauth.require_login
582
+ end
583
+ end
584
+
585
+ visit '/change-password'
586
+ page.current_path.must_equal '/login'
587
+
588
+ visit '/change-login'
589
+ page.current_path.must_equal '/login'
590
+
591
+ visit '/close-account'
592
+ page.current_path.must_equal '/login'
593
+
594
+ visit '/a'
595
+ page.current_path.must_equal '/login'
596
+ end
597
+
598
+ it "should handle case where account is no longer valid during session" do
599
+ rodauth do
600
+ enable :login, :change_password
601
+ already_logged_in{request.redirect '/'}
602
+ end
603
+ roda do |r|
604
+ r.rodauth
605
+
606
+ r.root do
607
+ view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")
608
+ end
609
+ end
610
+
611
+ visit '/login'
612
+ fill_in 'Login', :with=>'foo@example.com'
613
+ fill_in 'Password', :with=>'0123456789'
614
+ click_button 'Login'
615
+ page.body.must_match(/Logged In/)
616
+
617
+ Account.first.update(:status_id=>3)
618
+ visit '/change-password'
619
+ page.current_path.must_equal '/login'
620
+ visit '/'
621
+ page.body.must_match(/Not Logged/)
622
+ end
623
+
624
+ it "should handle cases where you are already logged in on pages that don't expect a login" do
625
+ rodauth do
626
+ enable :login, :logout, :create_account, :reset_password, :verify_account
627
+ already_logged_in{request.redirect '/'}
628
+ end
629
+ roda do |r|
630
+ r.rodauth
631
+
632
+ r.root do
633
+ view :content=>''
634
+ end
635
+ end
636
+
637
+ visit '/login'
638
+ fill_in 'Login', :with=>'foo@example.com'
639
+ fill_in 'Password', :with=>'0123456789'
640
+ click_button 'Login'
641
+
642
+ visit '/login'
643
+ page.current_path.must_equal '/'
644
+
645
+ visit '/create-account'
646
+ page.current_path.must_equal '/'
647
+
648
+ visit '/reset-password'
649
+ page.current_path.must_equal '/'
650
+
651
+ visit '/verify-account'
652
+ page.current_path.must_equal '/'
653
+
654
+ visit '/logout'
655
+ page.current_path.must_equal '/logout'
656
+ end
657
+
658
+ it "should support resetting passwords for accounts" do
659
+ rodauth do
660
+ enable :login, :reset_password
661
+ end
662
+ roda do |r|
663
+ r.rodauth
664
+ r.root{view :content=>""}
665
+ end
666
+
667
+ visit '/login'
668
+ fill_in 'Login', :with=>'foo@example2.com'
669
+ fill_in 'Password', :with=>'01234567'
670
+ click_button 'Login'
671
+ page.html.wont_match(/notice_flash/)
672
+
673
+ fill_in 'Login', :with=>'foo@example.com'
674
+ fill_in 'Password', :with=>'01234567'
675
+ click_button 'Login'
676
+
677
+ click_button 'Request Password Reset'
678
+ page.find('#notice_flash').text.must_equal "An email has been sent to you with a link to reset the password for your account"
679
+ page.current_path.must_equal '/'
680
+
681
+ link = email_link(/(\/reset-password\?key=.+)$/)
682
+ visit link[0...-1]
683
+ page.find('#error_flash').text.must_equal "invalid password reset key"
684
+
685
+ visit link
686
+ page.title.must_equal 'Reset Password'
687
+
688
+ fill_in 'Password', :with=>'0123456'
689
+ fill_in 'Confirm Password', :with=>'0123456789'
690
+ click_button 'Reset Password'
691
+ page.html.must_match(/passwords do not match/)
692
+ page.find('#error_flash').text.must_equal "There was an error resetting your password"
693
+ page.current_path.must_equal '/reset-password'
694
+
695
+ fill_in 'Password', :with=>'012'
696
+ fill_in 'Confirm Password', :with=>'012'
697
+ click_button 'Reset Password'
698
+ page.html.must_match(/invalid password, does not meet requirements/)
699
+ page.find('#error_flash').text.must_equal "There was an error resetting your password"
700
+ page.current_path.must_equal '/reset-password'
701
+
702
+ fill_in 'Password', :with=>'0123456'
703
+ fill_in 'Confirm Password', :with=>'0123456'
704
+ click_button 'Reset Password'
705
+ page.find('#notice_flash').text.must_equal "Your password has been reset"
706
+ page.current_path.must_equal '/'
707
+
708
+ visit '/login'
709
+ fill_in 'Login', :with=>'foo@example.com'
710
+ fill_in 'Password', :with=>'0123456'
711
+ click_button 'Login'
712
+ page.current_path.must_equal '/'
713
+ end
714
+
715
+ it "should support autologin when resetting passwords for accounts" do
716
+ rodauth do
717
+ enable :login, :reset_password
718
+ reset_password_autologin? true
719
+ end
720
+ roda do |r|
721
+ r.rodauth
722
+ r.root{view :content=>rodauth.logged_in? ? "Logged In" : "Not Logged"}
723
+ end
724
+
725
+ visit '/login'
726
+ fill_in 'Login', :with=>'foo@example.com'
727
+ fill_in 'Password', :with=>'01234567'
728
+ click_button 'Login'
729
+
730
+ click_button 'Request Password Reset'
731
+ link = email_link(/(\/reset-password\?key=.+)$/)
732
+ visit link
733
+ fill_in 'Password', :with=>'0123456'
734
+ fill_in 'Confirm Password', :with=>'0123456'
735
+ click_button 'Reset Password'
736
+ page.find('#notice_flash').text.must_equal "Your password has been reset"
737
+ page.body.must_match(/Logged In/)
738
+ end
739
+
740
+ it "should support verifying accounts" do
741
+ rodauth do
742
+ enable :login, :create_account, :verify_account
743
+ end
744
+ roda do |r|
745
+ r.rodauth
746
+ r.root{view :content=>""}
747
+ end
748
+
749
+ visit '/create-account'
750
+ fill_in 'Login', :with=>'foo@example2.com'
751
+ fill_in 'Confirm Login', :with=>'foo@example2.com'
752
+ fill_in 'Password', :with=>'0123456789'
753
+ fill_in 'Confirm Password', :with=>'0123456789'
754
+ click_button 'Create Account'
755
+ page.find('#notice_flash').text.must_equal "An email has been sent to you with a link to verify your account"
756
+ page.current_path.must_equal '/'
757
+
758
+ link = email_link(/(\/verify-account\?key=.+)$/)
759
+ visit '/login'
760
+ fill_in 'Login', :with=>'foo@example2.com'
761
+ fill_in 'Password', :with=>'0123456789'
762
+ click_button 'Login'
763
+ page.find('#error_flash').text.must_equal 'The account you tried to login with is currently awaiting verification'
764
+ page.html.must_match(/If you no longer have the email to verify the account, you can request that it be resent to you/)
765
+ click_button 'Send Verification Email Again'
766
+ page.current_path.must_equal '/login'
767
+
768
+ email_link(/(\/verify-account\?key=.+)$/).must_equal link
769
+ visit '/create-account'
770
+ fill_in 'Login', :with=>'foo@example2.com'
771
+ click_button 'Create Account'
772
+ click_button 'Send Verification Email Again'
773
+ page.find('#notice_flash').text.must_equal "An email has been sent to you with a link to verify your account"
774
+ page.current_path.must_equal '/login'
775
+
776
+ link = email_link(/(\/verify-account\?key=.+)$/)
777
+ visit link[0...-1]
778
+ page.find('#error_flash').text.must_equal "invalid verify account key"
779
+
780
+ visit link
781
+ click_button 'Verify Account'
782
+ page.find('#notice_flash').text.must_equal "Your account has been verified"
783
+ page.current_path.must_equal '/'
784
+
785
+ visit '/login'
786
+ fill_in 'Login', :with=>'foo@example2.com'
787
+ fill_in 'Password', :with=>'0123456789'
788
+ click_button 'Login'
789
+ page.find('#notice_flash').text.must_equal 'You have been logged in'
790
+ page.current_path.must_equal '/'
791
+ end
792
+
793
+ it "should support autologin when verifying accounts" do
794
+ rodauth do
795
+ enable :login, :create_account, :verify_account
796
+ verify_account_autologin? true
797
+ end
798
+ roda do |r|
799
+ r.rodauth
800
+ r.root{view :content=>rodauth.logged_in? ? "Logged In" : "Not Logged"}
801
+ end
802
+
803
+ visit '/create-account'
804
+ fill_in 'Login', :with=>'foo@example2.com'
805
+ fill_in 'Confirm Login', :with=>'foo@example2.com'
806
+ fill_in 'Password', :with=>'0123456789'
807
+ fill_in 'Confirm Password', :with=>'0123456789'
808
+ click_button 'Create Account'
809
+ page.find('#notice_flash').text.must_equal "An email has been sent to you with a link to verify your account"
810
+ page.current_path.must_equal '/'
811
+
812
+ link = email_link(/(\/verify-account\?key=.+)$/)
813
+ visit link
814
+ click_button 'Verify Account'
815
+ page.find('#notice_flash').text.must_equal "Your account has been verified"
816
+ page.body.must_match /Logged In/
817
+ end
818
+
819
+ it "should support login via remember token" do
820
+ rodauth do
821
+ enable :login, :remember
822
+ end
823
+ roda do |r|
824
+ r.rodauth
825
+ r.get 'load' do
826
+ rodauth.load_memory
827
+ r.redirect '/'
828
+ end
829
+ r.root{rodauth.logged_in? ? "Logged In#{session[:remembered]}" : "Not Logged In"}
830
+ end
831
+
832
+ visit '/login'
833
+ fill_in 'Login', :with=>'foo@example.com'
834
+ fill_in 'Password', :with=>'0123456789'
835
+ click_button 'Login'
836
+ page.body.must_equal 'Logged In'
837
+
838
+ visit '/remember'
839
+ choose 'Remember Me'
840
+ click_button 'Change Remember Setting'
841
+ page.body.must_equal 'Logged In'
842
+
843
+ remove_cookie('rack.session')
844
+ visit '/'
845
+ page.body.must_equal 'Not Logged In'
846
+
847
+ visit '/load'
848
+ page.body.must_equal 'Logged Intrue'
849
+
850
+ key = get_cookie('_remember')
851
+ visit '/remember'
852
+ choose 'Forget Me'
853
+ click_button 'Change Remember Setting'
854
+ page.body.must_equal 'Logged Intrue'
855
+
856
+ remove_cookie('rack.session')
857
+ visit '/'
858
+ page.body.must_equal 'Not Logged In'
859
+
860
+ visit '/load'
861
+ page.body.must_equal 'Not Logged In'
862
+
863
+ set_cookie('_remember', key)
864
+ visit '/load'
865
+ page.body.must_equal 'Logged Intrue'
866
+
867
+ visit '/remember'
868
+ choose 'Disable Remember Me'
869
+ click_button 'Change Remember Setting'
870
+ page.body.must_equal 'Logged Intrue'
871
+
872
+ remove_cookie('rack.session')
873
+ visit '/'
874
+ page.body.must_equal 'Not Logged In'
875
+
876
+ set_cookie('_remember', key)
877
+ visit '/load'
878
+ page.body.must_equal 'Not Logged In'
879
+ end
880
+
881
+ it "should forget remember token when explicitly logging out" do
882
+ rodauth do
883
+ enable :login, :logout, :remember
884
+ end
885
+ roda do |r|
886
+ r.rodauth
887
+ r.get 'load' do
888
+ rodauth.load_memory
889
+ r.redirect '/'
890
+ end
891
+ r.root{rodauth.logged_in? ? "Logged In#{session[:remembered]}" : "Not Logged In"}
892
+ end
893
+
894
+ visit '/login'
895
+ fill_in 'Login', :with=>'foo@example.com'
896
+ fill_in 'Password', :with=>'0123456789'
897
+ click_button 'Login'
898
+ page.body.must_equal 'Logged In'
899
+
900
+ visit '/remember'
901
+ choose 'Remember Me'
902
+ click_button 'Change Remember Setting'
903
+ page.body.must_equal 'Logged In'
904
+
905
+ visit '/logout'
906
+ click_button 'Logout'
907
+
908
+ visit '/'
909
+ page.body.must_equal 'Not Logged In'
910
+
911
+ visit '/load'
912
+ page.body.must_equal 'Not Logged In'
913
+ end
914
+
915
+ it "should support clearing remembered flag" do
916
+ rodauth do
917
+ enable :login, :remember
918
+ end
919
+ roda do |r|
920
+ r.rodauth
921
+ r.get 'load' do
922
+ rodauth.load_memory
923
+ r.redirect '/'
924
+ end
925
+ r.root{rodauth.logged_in? ? "Logged In#{session[:remembered]}" : "Not Logged In"}
926
+ end
927
+
928
+ visit '/login'
929
+ fill_in 'Login', :with=>'foo@example.com'
930
+ fill_in 'Password', :with=>'0123456789'
931
+ click_button 'Login'
932
+ page.body.must_equal 'Logged In'
933
+
934
+ visit '/remember'
935
+ choose 'Remember Me'
936
+ click_button 'Change Remember Setting'
937
+ page.body.must_equal 'Logged In'
938
+
939
+ remove_cookie('rack.session')
940
+ visit '/'
941
+ page.body.must_equal 'Not Logged In'
942
+
943
+ visit '/load'
944
+ page.body.must_equal 'Logged Intrue'
945
+
946
+ visit '/remember?confirm=t'
947
+ fill_in 'Password', :with=>'012345678'
948
+ click_button 'Confirm Password'
949
+ page.html.must_match(/invalid password/)
950
+
951
+ fill_in 'Password', :with=>'0123456789'
952
+ click_button 'Confirm Password'
953
+ page.body.must_equal 'Logged In'
954
+ end
955
+
956
+ it "should support extending remember token" do
957
+ rodauth do
958
+ enable :login, :remember
959
+ extend_remember_deadline? true
960
+ end
961
+ roda do |r|
962
+ r.rodauth
963
+ r.get 'load' do
964
+ rodauth.load_memory
965
+ r.redirect '/'
966
+ end
967
+ r.root{rodauth.logged_in? ? "Logged In#{session[:remembered]}" : "Not Logged In"}
968
+ end
969
+
970
+ visit '/login'
971
+ fill_in 'Login', :with=>'foo@example.com'
972
+ fill_in 'Password', :with=>'0123456789'
973
+ click_button 'Login'
974
+
975
+ visit '/remember'
976
+ choose 'Remember Me'
977
+ click_button 'Change Remember Setting'
978
+
979
+ remove_cookie('rack.session')
980
+ visit '/'
981
+ page.body.must_equal 'Not Logged In'
982
+
983
+ visit '/load'
984
+ page.body.must_equal 'Logged Intrue'
985
+ end
986
+
987
+ it "should support account lockouts" do
988
+ rodauth do
989
+ enable :lockout
990
+ max_invalid_logins 2
991
+ end
992
+ roda do |r|
993
+ r.rodauth
994
+ r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
995
+ end
996
+
997
+ visit '/login'
998
+ fill_in 'Login', :with=>'foo@example.com'
999
+ fill_in 'Password', :with=>'012345678910'
1000
+ click_button 'Login'
1001
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
1002
+
1003
+ fill_in 'Login', :with=>'foo@example.com'
1004
+ fill_in 'Password', :with=>'0123456789'
1005
+ click_button 'Login'
1006
+ page.find('#notice_flash').text.must_equal 'You have been logged in'
1007
+ page.body.must_match(/Logged In/)
1008
+
1009
+ remove_cookie('rack.session')
1010
+
1011
+ visit '/login'
1012
+ fill_in 'Login', :with=>'foo@example.com'
1013
+ 3.times do
1014
+ fill_in 'Password', :with=>'012345678910'
1015
+ click_button 'Login'
1016
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
1017
+ end
1018
+ page.body.must_match(/This account is currently locked out/)
1019
+ click_button 'Request Account Unlock'
1020
+ page.find('#notice_flash').text.must_equal 'An email has been sent to you with a link to unlock your account'
1021
+
1022
+ link = email_link(/(\/unlock-account\?key=.+)$/)
1023
+ visit link[0...-1]
1024
+ page.find('#error_flash').text.must_equal 'No matching unlock account key'
1025
+
1026
+ visit link
1027
+ click_button 'Unlock Account'
1028
+ page.find('#notice_flash').text.must_equal 'Your account has been unlocked'
1029
+
1030
+ visit '/login'
1031
+ fill_in 'Login', :with=>'foo@example.com'
1032
+ fill_in 'Password', :with=>'0123456789'
1033
+ click_button 'Login'
1034
+ page.find('#notice_flash').text.must_equal 'You have been logged in'
1035
+ page.body.must_match(/Logged In/)
1036
+ end
1037
+
1038
+ it "should support autologin when unlocking account" do
1039
+ rodauth do
1040
+ enable :lockout
1041
+ unlock_account_autologin? true
1042
+ end
1043
+ roda do |r|
1044
+ r.rodauth
1045
+ r.root{view :content=>(rodauth.logged_in? ? "Logged In" : "Not Logged")}
1046
+ end
1047
+
1048
+ visit '/login'
1049
+ fill_in 'Login', :with=>'foo@example.com'
1050
+ 101.times do |i|
1051
+ fill_in 'Password', :with=>'012345678910'
1052
+ click_button 'Login'
1053
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
1054
+ end
1055
+ page.body.must_match(/This account is currently locked out/)
1056
+ click_button 'Request Account Unlock'
1057
+ page.find('#notice_flash').text.must_equal 'An email has been sent to you with a link to unlock your account'
1058
+
1059
+ link = email_link(/(\/unlock-account\?key=.+)$/)
1060
+ visit link
1061
+ click_button 'Unlock Account'
1062
+ page.body.must_match(/Logged In/)
1063
+ end
1064
+
1065
+ it "should support verifying accounts" do
1066
+ rodauth do
1067
+ enable :login
1068
+ end
1069
+ roda do |r|
1070
+ "#{rodauth.features.first.inspect}#{rodauth.session_value.inspect}"
1071
+ end
1072
+
1073
+ visit '/'
1074
+ page.body.must_equal ':loginnil'
1075
+ end
1076
+
1077
+ it "should support multiple rodauth configurations in an app" do
1078
+ app = Class.new(Base)
1079
+ app.plugin(:rodauth) do
1080
+ enable :login
1081
+ end
1082
+ app.plugin(:rodauth, :name=>:r2) do
1083
+ enable :logout
1084
+ end
1085
+ app.route do |r|
1086
+ r.on 'r1' do
1087
+ r.rodauth
1088
+ 'r1'
1089
+ end
1090
+ r.on 'r2' do
1091
+ r.rodauth(:r2)
1092
+ 'r2'
1093
+ end
1094
+ rodauth.session_value.inspect
1095
+ end
1096
+ app.freeze
1097
+ self.app = app
1098
+
1099
+ visit '/r1/login'
1100
+ fill_in 'Login', :with=>'foo@example.com'
1101
+ fill_in 'Password', :with=>'0123456789'
1102
+ click_button 'Login'
1103
+ page.body.must_equal Account.first.id.to_s
1104
+
1105
+ visit '/r2/logout'
1106
+ click_button 'Logout'
1107
+ page.body.must_equal 'nil'
1108
+
1109
+ visit '/r1/logout'
1110
+ page.body.must_equal 'r1'
1111
+ visit '/r2/login'
1112
+ page.body.must_equal 'r2'
1113
+ end
1114
+ end