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.
- checksums.yaml +7 -0
- data/CHANGELOG +3 -0
- data/MIT-LICENSE +18 -0
- data/README.rdoc +484 -0
- data/Rakefile +91 -0
- data/lib/roda/plugins/rodauth.rb +265 -0
- data/lib/roda/plugins/rodauth/base.rb +428 -0
- data/lib/roda/plugins/rodauth/change_login.rb +48 -0
- data/lib/roda/plugins/rodauth/change_password.rb +42 -0
- data/lib/roda/plugins/rodauth/close_account.rb +42 -0
- data/lib/roda/plugins/rodauth/create_account.rb +92 -0
- data/lib/roda/plugins/rodauth/lockout.rb +292 -0
- data/lib/roda/plugins/rodauth/login.rb +77 -0
- data/lib/roda/plugins/rodauth/logout.rb +36 -0
- data/lib/roda/plugins/rodauth/remember.rb +226 -0
- data/lib/roda/plugins/rodauth/reset_password.rb +205 -0
- data/lib/roda/plugins/rodauth/verify_account.rb +228 -0
- data/spec/migrate/001_tables.rb +64 -0
- data/spec/migrate_password/001_tables.rb +38 -0
- data/spec/rodauth_spec.rb +1114 -0
- data/spec/views/layout.str +11 -0
- data/spec/views/login.str +21 -0
- data/templates/change-login.str +22 -0
- data/templates/change-password.str +21 -0
- data/templates/close-account.str +9 -0
- data/templates/confirm-password.str +16 -0
- data/templates/create-account.str +33 -0
- data/templates/login.str +25 -0
- data/templates/logout.str +9 -0
- data/templates/remember.str +28 -0
- data/templates/reset-password-email.str +5 -0
- data/templates/reset-password-request.str +7 -0
- data/templates/reset-password.str +23 -0
- data/templates/unlock-account-email.str +5 -0
- data/templates/unlock-account-request.str +11 -0
- data/templates/unlock-account.str +11 -0
- data/templates/verify-account-email.str +4 -0
- data/templates/verify-account-resend.str +7 -0
- data/templates/verify-account.str +11 -0
- 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
|