rodauth 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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