rodauth 0.10.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +146 -0
  3. data/README.rdoc +644 -220
  4. data/Rakefile +99 -11
  5. data/doc/account_expiration.rdoc +55 -0
  6. data/doc/base.rdoc +104 -0
  7. data/doc/change_login.rdoc +29 -0
  8. data/doc/change_password.rdoc +26 -0
  9. data/doc/close_account.rdoc +31 -0
  10. data/doc/confirm_password.rdoc +22 -0
  11. data/doc/create_account.rdoc +34 -0
  12. data/doc/disallow_password_reuse.rdoc +37 -0
  13. data/doc/email_base.rdoc +19 -0
  14. data/doc/jwt.rdoc +35 -0
  15. data/doc/lockout.rdoc +83 -0
  16. data/doc/login.rdoc +27 -0
  17. data/doc/login_password_requirements_base.rdoc +50 -0
  18. data/doc/logout.rdoc +21 -0
  19. data/doc/otp.rdoc +100 -0
  20. data/doc/password_complexity.rdoc +50 -0
  21. data/doc/password_expiration.rdoc +52 -0
  22. data/doc/password_grace_period.rdoc +10 -0
  23. data/doc/recovery_codes.rdoc +60 -0
  24. data/doc/release_notes/1.0.0.txt +443 -0
  25. data/doc/remember.rdoc +82 -0
  26. data/doc/reset_password.rdoc +70 -0
  27. data/doc/session_expiration.rdoc +27 -0
  28. data/doc/single_session.rdoc +43 -0
  29. data/doc/sms_codes.rdoc +119 -0
  30. data/doc/two_factor_base.rdoc +27 -0
  31. data/doc/verify_account.rdoc +70 -0
  32. data/doc/verify_account_grace_period.rdoc +15 -0
  33. data/doc/verify_change_login.rdoc +9 -0
  34. data/lib/roda/plugins/rodauth.rb +3 -262
  35. data/lib/rodauth.rb +260 -0
  36. data/lib/rodauth/features/account_expiration.rb +108 -0
  37. data/lib/rodauth/features/base.rb +479 -0
  38. data/lib/rodauth/features/change_login.rb +77 -0
  39. data/lib/rodauth/features/change_password.rb +66 -0
  40. data/lib/rodauth/features/close_account.rb +82 -0
  41. data/lib/rodauth/features/confirm_password.rb +51 -0
  42. data/lib/rodauth/features/create_account.rb +128 -0
  43. data/lib/rodauth/features/disallow_password_reuse.rb +82 -0
  44. data/lib/rodauth/features/email_base.rb +63 -0
  45. data/lib/rodauth/features/jwt.rb +151 -0
  46. data/lib/rodauth/features/lockout.rb +262 -0
  47. data/lib/rodauth/features/login.rb +61 -0
  48. data/lib/rodauth/features/login_password_requirements_base.rb +123 -0
  49. data/lib/rodauth/features/logout.rb +37 -0
  50. data/lib/rodauth/features/otp.rb +338 -0
  51. data/lib/rodauth/features/password_complexity.rb +89 -0
  52. data/lib/rodauth/features/password_expiration.rb +111 -0
  53. data/lib/rodauth/features/password_grace_period.rb +46 -0
  54. data/lib/rodauth/features/recovery_codes.rb +240 -0
  55. data/lib/rodauth/features/remember.rb +200 -0
  56. data/lib/rodauth/features/reset_password.rb +207 -0
  57. data/lib/rodauth/features/session_expiration.rb +55 -0
  58. data/lib/rodauth/features/single_session.rb +87 -0
  59. data/lib/rodauth/features/sms_codes.rb +498 -0
  60. data/lib/rodauth/features/two_factor_base.rb +135 -0
  61. data/lib/rodauth/features/verify_account.rb +232 -0
  62. data/lib/rodauth/features/verify_account_grace_period.rb +76 -0
  63. data/lib/rodauth/features/verify_change_login.rb +20 -0
  64. data/lib/rodauth/migrations.rb +130 -0
  65. data/lib/rodauth/version.rb +9 -0
  66. data/spec/account_expiration_spec.rb +90 -0
  67. data/spec/all.rb +1 -0
  68. data/spec/change_login_spec.rb +149 -0
  69. data/spec/change_password_spec.rb +177 -0
  70. data/spec/close_account_spec.rb +162 -0
  71. data/spec/confirm_password_spec.rb +70 -0
  72. data/spec/create_account_spec.rb +127 -0
  73. data/spec/disallow_password_reuse_spec.rb +84 -0
  74. data/spec/lockout_spec.rb +228 -0
  75. data/spec/login_spec.rb +188 -0
  76. data/spec/migrate/001_tables.rb +103 -16
  77. data/spec/migrate/002_account_password_hash_column.rb +11 -0
  78. data/spec/migrate_password/001_tables.rb +60 -42
  79. data/spec/migrate_travis/001_tables.rb +116 -0
  80. data/spec/password_complexity_spec.rb +108 -0
  81. data/spec/password_expiration_spec.rb +243 -0
  82. data/spec/password_grace_period_spec.rb +93 -0
  83. data/spec/remember_spec.rb +424 -0
  84. data/spec/reset_password_spec.rb +185 -0
  85. data/spec/rodauth_spec.rb +57 -980
  86. data/spec/session_expiration_spec.rb +58 -0
  87. data/spec/single_session_spec.rb +107 -0
  88. data/spec/spec_helper.rb +202 -0
  89. data/spec/two_factor_spec.rb +1310 -0
  90. data/spec/verify_account_grace_period_spec.rb +135 -0
  91. data/spec/verify_account_spec.rb +142 -0
  92. data/spec/verify_change_login_spec.rb +46 -0
  93. data/spec/views/login.str +2 -2
  94. data/templates/add-recovery-codes.str +2 -0
  95. data/templates/button.str +5 -0
  96. data/templates/change-login.str +5 -18
  97. data/templates/change-password.str +6 -14
  98. data/templates/close-account.str +3 -6
  99. data/templates/confirm-password.str +4 -14
  100. data/templates/create-account.str +6 -30
  101. data/templates/login-confirm-field.str +6 -0
  102. data/templates/login-field.str +6 -0
  103. data/templates/login.str +5 -19
  104. data/templates/logout.str +2 -6
  105. data/templates/otp-auth-code-field.str +6 -0
  106. data/templates/otp-auth.str +8 -0
  107. data/templates/otp-disable.str +6 -0
  108. data/templates/otp-setup.str +21 -0
  109. data/templates/password-confirm-field.str +6 -0
  110. data/templates/password-field.str +6 -0
  111. data/templates/recovery-auth.str +12 -0
  112. data/templates/recovery-codes.str +6 -0
  113. data/templates/remember.str +8 -12
  114. data/templates/reset-password-request.str +2 -2
  115. data/templates/reset-password.str +4 -18
  116. data/templates/sms-auth.str +6 -0
  117. data/templates/sms-code-field.str +6 -0
  118. data/templates/sms-confirm.str +7 -0
  119. data/templates/sms-disable.str +7 -0
  120. data/templates/sms-request.str +5 -0
  121. data/templates/sms-setup.str +12 -0
  122. data/templates/unlock-account-request.str +3 -7
  123. data/templates/unlock-account.str +4 -7
  124. data/templates/verify-account-resend.str +2 -2
  125. data/templates/verify-account.str +2 -6
  126. metadata +191 -29
  127. data/lib/roda/plugins/rodauth/base.rb +0 -428
  128. data/lib/roda/plugins/rodauth/change_login.rb +0 -48
  129. data/lib/roda/plugins/rodauth/change_password.rb +0 -42
  130. data/lib/roda/plugins/rodauth/close_account.rb +0 -42
  131. data/lib/roda/plugins/rodauth/create_account.rb +0 -92
  132. data/lib/roda/plugins/rodauth/lockout.rb +0 -292
  133. data/lib/roda/plugins/rodauth/login.rb +0 -81
  134. data/lib/roda/plugins/rodauth/logout.rb +0 -36
  135. data/lib/roda/plugins/rodauth/remember.rb +0 -226
  136. data/lib/roda/plugins/rodauth/reset_password.rb +0 -205
  137. data/lib/roda/plugins/rodauth/verify_account.rb +0 -228
@@ -0,0 +1,130 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ def self.create_database_authentication_functions(db, opts={})
5
+ table_name = opts[:table_name] || :account_password_hashes
6
+ get_salt_name = opts[:get_salt_name] || :rodauth_get_salt
7
+ valid_hash_name = opts[:valid_hash_name] || :rodauth_valid_password_hash
8
+ case db.database_type
9
+ when :postgres
10
+ db.run <<END
11
+ CREATE OR REPLACE FUNCTION #{get_salt_name}(acct_id int8) RETURNS text AS $$
12
+ DECLARE salt text;
13
+ BEGIN
14
+ SELECT substr(password_hash, 0, 30) INTO salt
15
+ FROM #{table_name}
16
+ WHERE acct_id = id;
17
+ RETURN salt;
18
+ END;
19
+ $$ LANGUAGE plpgsql
20
+ SECURITY DEFINER
21
+ SET search_path = public, pg_temp;
22
+ END
23
+
24
+ db.run <<END
25
+ CREATE OR REPLACE FUNCTION #{valid_hash_name}(acct_id int8, hash text) RETURNS boolean AS $$
26
+ DECLARE valid boolean;
27
+ BEGIN
28
+ SELECT password_hash = hash INTO valid
29
+ FROM #{table_name}
30
+ WHERE acct_id = id;
31
+ RETURN valid;
32
+ END;
33
+ $$ LANGUAGE plpgsql
34
+ SECURITY DEFINER
35
+ SET search_path = public, pg_temp;
36
+ END
37
+ when :mysql
38
+ db.run <<END
39
+ CREATE FUNCTION #{get_salt_name}(acct_id int8) RETURNS varchar(255)
40
+ SQL SECURITY DEFINER
41
+ READS SQL DATA
42
+ BEGIN
43
+ DECLARE salt varchar(255);
44
+ DECLARE csr CURSOR FOR
45
+ SELECT substr(password_hash, 1, 30)
46
+ FROM #{table_name}
47
+ WHERE acct_id = id;
48
+ OPEN csr;
49
+ FETCH csr INTO salt;
50
+ CLOSE csr;
51
+ RETURN salt;
52
+ END;
53
+ END
54
+
55
+ db.run <<END
56
+ CREATE FUNCTION #{valid_hash_name}(acct_id int8, hash varchar(255)) RETURNS tinyint(1)
57
+ SQL SECURITY DEFINER
58
+ READS SQL DATA
59
+ BEGIN
60
+ DECLARE valid tinyint(1);
61
+ DECLARE csr CURSOR FOR
62
+ SELECT password_hash = hash
63
+ FROM #{table_name}
64
+ WHERE acct_id = id;
65
+ OPEN csr;
66
+ FETCH csr INTO valid;
67
+ CLOSE csr;
68
+ RETURN valid;
69
+ END;
70
+ END
71
+ when :mssql
72
+ db.run <<END
73
+ CREATE FUNCTION #{get_salt_name}(@account_id bigint) RETURNS nvarchar(255)
74
+ WITH EXECUTE AS OWNER
75
+ AS
76
+ BEGIN
77
+ DECLARE @salt nvarchar(255);
78
+ SELECT @salt = substring(password_hash, 0, 30)
79
+ FROM #{table_name}
80
+ WHERE id = @account_id;
81
+ RETURN @salt;
82
+ END;
83
+ END
84
+
85
+ db.run <<END
86
+ CREATE FUNCTION #{valid_hash_name}(@account_id bigint, @hash nvarchar(255)) RETURNS bit
87
+ WITH EXECUTE AS OWNER
88
+ AS
89
+ BEGIN
90
+ DECLARE @valid bit;
91
+ DECLARE @ph nvarchar(255);
92
+ SELECT @ph = password_hash
93
+ FROM #{table_name}
94
+ WHERE id = @account_id;
95
+ IF(@hash = @ph)
96
+ SET @valid = 1;
97
+ ELSE
98
+ SET @valid = 0
99
+ RETURN @valid;
100
+ END;
101
+ END
102
+ end
103
+ end
104
+
105
+ def self.drop_database_authentication_functions(db)
106
+ case db.database_type
107
+ when :postgres
108
+ db.run "DROP FUNCTION rodauth_get_salt(int8)"
109
+ db.run "DROP FUNCTION rodauth_valid_password_hash(int8, text)"
110
+ when :mysql, :mssql
111
+ db.run "DROP FUNCTION rodauth_get_salt"
112
+ db.run "DROP FUNCTION rodauth_valid_password_hash"
113
+ end
114
+ end
115
+
116
+ def self.create_database_previous_password_check_functions(db)
117
+ create_database_authentication_functions(db, :table_name=>:account_previous_password_hashes, :get_salt_name=>:rodauth_get_previous_salt, :valid_hash_name=>:rodauth_previous_password_hash_match)
118
+ end
119
+
120
+ def self.drop_database_previous_password_check_functions(db)
121
+ case db.database_type
122
+ when :postgres
123
+ db.run "DROP FUNCTION rodauth_get_previous_salt(int8)"
124
+ db.run "DROP FUNCTION rodauth_previous_password_hash_match(int8, text)"
125
+ when :mysql, :mssql
126
+ db.run "DROP FUNCTION rodauth_get_previous_salt"
127
+ db.run "DROP FUNCTION rodauth_previous_password_hash_match"
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,9 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ VERSION = '1.0.0'.freeze
5
+
6
+ def self.version
7
+ VERSION
8
+ end
9
+ end
@@ -0,0 +1,90 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe 'Rodauth account expiration feature' do
4
+ it "should force account expiration after x number of days" do
5
+ rodauth do
6
+ enable :login, :logout, :account_expiration
7
+ end
8
+ roda do |r|
9
+ r.rodauth
10
+ r.root{view :content=>rodauth.logged_in? ? "Logged In#{rodauth.last_account_login_at.strftime('%m%d%y')}" : "Not Logged"}
11
+ end
12
+
13
+ now = Time.now
14
+ 2.times do
15
+ login
16
+ page.body.must_include "Logged In#{now.strftime('%m%d%y')}"
17
+
18
+ logout
19
+ end
20
+
21
+ DB[:account_activity_times].update(:last_login_at => Time.now - 181*86400)
22
+
23
+ 2.times do
24
+ login
25
+ page.body.must_include 'Not Logged'
26
+ page.find('#error_flash').text.must_equal "You cannot log into this account as it has expired"
27
+ end
28
+ end
29
+
30
+ it "should use last activity time if configured" do
31
+ rodauth do
32
+ enable :login, :logout, :account_expiration
33
+ expire_account_on_last_activity? true
34
+ account_expiration_error_flash{"Account expired on #{account_expired_at.strftime('%m%d%y')}"}
35
+ end
36
+ roda do |r|
37
+ r.is("a"){view :content=>"Logged In#{rodauth.last_account_activity_at.strftime('%m%d%y')}"}
38
+ rodauth.update_last_activity
39
+ r.rodauth
40
+ r.root{view :content=>rodauth.logged_in? ? "Logged In#{rodauth.last_account_activity_at.strftime('%m%d%y')}" : 'Not Logged'}
41
+ end
42
+
43
+ now = Time.now
44
+ login
45
+ page.body.must_include "Logged In#{now.strftime('%m%d%y')}"
46
+
47
+ DB[:account_activity_times].count.must_equal 1
48
+ DB[:account_activity_times].delete
49
+
50
+ visit '/'
51
+ DB[:account_activity_times].count.must_equal 1
52
+
53
+ t1 = now - 179*86400
54
+ DB[:account_activity_times].update(:last_activity_at => t1)
55
+ visit '/a'
56
+ page.body.must_include "Logged In#{t1.strftime('%m%d%y')}"
57
+
58
+ logout
59
+
60
+ t2 = now - 181*86400
61
+ DB[:account_activity_times].update(:last_activity_at => t2).must_equal 1
62
+
63
+ login
64
+ page.body.must_include 'Not Logged'
65
+ page.find('#error_flash').text.must_equal "Account expired on #{now.strftime('%m%d%y')}"
66
+
67
+ DB[:account_activity_times].update(:expired_at=>t1).must_equal 1
68
+
69
+ login
70
+ page.body.must_include 'Not Logged'
71
+ page.find('#error_flash').text.must_equal "Account expired on #{t1.strftime('%m%d%y')}"
72
+ end
73
+
74
+ it "should remove account activity data when closing accounts" do
75
+ rodauth do
76
+ enable :login, :close_account, :account_expiration
77
+ close_account_requires_password? false
78
+ end
79
+ roda do |r|
80
+ r.rodauth
81
+ r.root{view :content=>rodauth.logged_in? ? "Logged In#{rodauth.last_account_login_at.strftime('%m%d%y')}" : "Not Logged"}
82
+ end
83
+
84
+ login
85
+ DB[:account_activity_times].count.must_equal 1
86
+ visit '/close-account'
87
+ click_button 'Close Account'
88
+ DB[:account_activity_times].count.must_equal 0
89
+ end
90
+ end
@@ -0,0 +1 @@
1
+ Dir['./spec/*_spec.rb'].each{|f| require f}
@@ -0,0 +1,149 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe 'Rodauth change_login feature' do
4
+ it "should support changing logins for accounts" do
5
+ DB[:accounts].insert(:email=>'foo2@example.com')
6
+ require_password = false
7
+ require_email = true
8
+
9
+ rodauth do
10
+ enable :login, :logout, :change_login
11
+ change_login_requires_password?{require_password}
12
+ require_email_address_logins?{require_email}
13
+ end
14
+ roda do |r|
15
+ r.rodauth
16
+ r.root{view :content=>""}
17
+ end
18
+
19
+ login
20
+ page.current_path.must_equal '/'
21
+
22
+ visit '/change-login'
23
+ page.title.must_equal 'Change Login'
24
+
25
+ fill_in 'Login', :with=>'foobar'
26
+ fill_in 'Confirm Login', :with=>'foobar'
27
+ click_button 'Change Login'
28
+ page.find('#error_flash').text.must_equal "There was an error changing your login"
29
+ page.html.must_include("invalid login, not a valid email address")
30
+ page.current_path.must_equal '/change-login'
31
+
32
+ require_email = false
33
+
34
+ fill_in 'Login', :with=>'fb'
35
+ fill_in 'Confirm Login', :with=>'fb'
36
+ click_button 'Change Login'
37
+ page.find('#error_flash').text.must_equal "There was an error changing your login"
38
+ page.html.must_include("invalid login, minimum 3 characters")
39
+ page.current_path.must_equal '/change-login'
40
+
41
+ fill_in 'Login', :with=>'foo@example.com'
42
+ fill_in 'Confirm Login', :with=>'foo2@example.com'
43
+ click_button 'Change Login'
44
+ page.find('#error_flash').text.must_equal "There was an error changing your login"
45
+ page.html.must_include("logins do not match")
46
+ page.current_path.must_equal '/change-login'
47
+
48
+ fill_in 'Login', :with=>'foo2@example.com'
49
+ click_button 'Change Login'
50
+ page.find('#error_flash').text.must_equal "There was an error changing your login"
51
+ page.html.must_include("invalid login, already an account with this login")
52
+ page.current_path.must_equal '/change-login'
53
+
54
+ fill_in 'Login', :with=>'foo@example.com'
55
+ fill_in 'Confirm Login', :with=>'foo@example.com'
56
+ click_button 'Change Login'
57
+ page.find('#error_flash').text.must_equal "There was an error changing your login"
58
+ page.html.must_include("invalid login, same as current login")
59
+ page.current_path.must_equal '/change-login'
60
+
61
+ fill_in 'Login', :with=>'foo3@example.com'
62
+ fill_in 'Confirm Login', :with=>'foo3@example.com'
63
+ click_button 'Change Login'
64
+ page.find('#notice_flash').text.must_equal "Your login has been changed"
65
+ page.current_path.must_equal '/'
66
+
67
+ logout
68
+ login(:login=>'foo3@example.com')
69
+ page.current_path.must_equal '/'
70
+
71
+ require_password = true
72
+ visit '/change-login'
73
+ fill_in 'Password', :with=>'012345678'
74
+ fill_in 'Login', :with=>'foo4@example.com'
75
+ fill_in 'Confirm Login', :with=>'foo4@example.com'
76
+ click_button 'Change Login'
77
+ page.find('#error_flash').text.must_equal "There was an error changing your login"
78
+ page.html.must_include("invalid password")
79
+ page.current_path.must_equal '/change-login'
80
+
81
+ fill_in 'Password', :with=>'0123456789'
82
+ click_button 'Change Login'
83
+ page.find('#notice_flash').text.must_equal "Your login has been changed"
84
+ page.current_path.must_equal '/'
85
+
86
+ logout
87
+ login(:login=>'foo4@example.com')
88
+ page.current_path.must_equal '/'
89
+ end
90
+
91
+ it "should support changing logins for accounts with login confirmation" do
92
+ rodauth do
93
+ enable :login, :change_login
94
+ change_login_requires_password? false
95
+ require_login_confirmation? false
96
+ end
97
+ roda do |r|
98
+ r.rodauth
99
+ r.root{view :content=>""}
100
+ end
101
+
102
+ login
103
+ visit '/change-login'
104
+ fill_in 'Login', :with=>'foo3@example.com'
105
+ click_button 'Change Login'
106
+ page.find('#notice_flash').text.must_equal "Your login has been changed"
107
+ end
108
+
109
+ it "should support changing logins via jwt" do
110
+ DB[:accounts].insert(:email=>'foo2@example.com')
111
+ require_password = false
112
+
113
+ rodauth do
114
+ enable :login, :logout, :change_login
115
+ change_login_requires_password?{require_password}
116
+ end
117
+ roda(:jwt) do |r|
118
+ r.rodauth
119
+ end
120
+
121
+ json_login
122
+
123
+ res = json_request('/change-login', :login=>'foobar', "login-confirm"=>'foobar')
124
+ res.must_equal [400, {'error'=>"There was an error changing your login", "field-error"=>["login", "invalid login, not a valid email address"]}]
125
+
126
+ res = json_request('/change-login', :login=>'foo@example.com', "login-confirm"=>'foo2@example.com')
127
+ res.must_equal [400, {'error'=>"There was an error changing your login", "field-error"=>["login", "logins do not match"]}]
128
+
129
+ res = json_request('/change-login', :login=>'foo2@example.com', "login-confirm"=>'foo2@example.com')
130
+ res.must_equal [400, {'error'=>"There was an error changing your login", "field-error"=>["login", "invalid login, already an account with this login"]}]
131
+
132
+ res = json_request('/change-login', :login=>'foo3@example.com', "login-confirm"=>'foo3@example.com')
133
+ res.must_equal [200, {'success'=>"Your login has been changed"}]
134
+
135
+ json_logout
136
+ json_login(:login=>'foo3@example.com')
137
+
138
+ require_password = true
139
+
140
+ res = json_request('/change-login', :login=>'foo4@example.com', "login-confirm"=>'foo4@example.com', :password=>'012345678')
141
+ res.must_equal [400, {'error'=>"There was an error changing your login", "field-error"=>["password", "invalid password"]}]
142
+
143
+ res = json_request('/change-login', :login=>'foo4@example.com', "login-confirm"=>'foo4@example.com', :password=>'0123456789')
144
+ res.must_equal [200, {'success'=>"Your login has been changed"}]
145
+
146
+ json_logout
147
+ json_login(:login=>'foo4@example.com')
148
+ end
149
+ end
@@ -0,0 +1,177 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe 'Rodauth change_password feature' do
4
+ [false, true].each do |ph|
5
+ it "should support changing passwords for accounts #{'with account_password_hash_column' if ph}" do
6
+ require_password = true
7
+ rodauth do
8
+ enable :login, :logout, :change_password
9
+ account_password_hash_column :ph if ph
10
+ change_password_requires_password?{require_password}
11
+ end
12
+ roda do |r|
13
+ r.rodauth
14
+ r.root{view :content=>""}
15
+ end
16
+
17
+ login
18
+ page.current_path.must_equal '/'
19
+
20
+ visit '/change-password'
21
+ page.title.must_equal 'Change Password'
22
+
23
+ fill_in 'Password', :with=>'0123456789'
24
+ fill_in 'New Password', :with=>'0123456'
25
+ fill_in 'Confirm Password', :with=>'0123456789'
26
+ click_button 'Change Password'
27
+ page.html.must_include("passwords do not match")
28
+ page.find('#error_flash').text.must_equal "There was an error changing your password"
29
+ page.current_path.must_equal '/change-password'
30
+
31
+ fill_in 'Password', :with=>'0123456'
32
+ fill_in 'New Password', :with=>'0123456'
33
+ fill_in 'Confirm Password', :with=>'0123456'
34
+ click_button 'Change Password'
35
+ page.find('#error_flash').text.must_equal "There was an error changing your password"
36
+ page.body.must_include 'invalid password'
37
+ page.current_path.must_equal '/change-password'
38
+
39
+ fill_in 'Password', :with=>'0123456789'
40
+ fill_in 'New Password', :with=>'0123456789'
41
+ fill_in 'Confirm Password', :with=>'0123456789'
42
+ click_button 'Change Password'
43
+ page.find('#error_flash').text.must_equal "There was an error changing your password"
44
+ page.body.must_include 'invalid password, same as current password'
45
+ page.current_path.must_equal '/change-password'
46
+
47
+ fill_in 'Password', :with=>'0123456789'
48
+ fill_in 'New Password', :with=>'0123456'
49
+ fill_in 'Confirm Password', :with=>'0123456'
50
+ click_button 'Change Password'
51
+ page.find('#notice_flash').text.must_equal "Your password has been changed"
52
+ page.current_path.must_equal '/'
53
+
54
+ logout
55
+ login
56
+ page.html.must_include("invalid password")
57
+ page.current_path.must_equal '/login'
58
+
59
+ fill_in 'Password', :with=>'0123456'
60
+ click_button 'Login'
61
+ page.current_path.must_equal '/'
62
+
63
+ require_password = false
64
+ visit '/change-password'
65
+ fill_in 'New Password', :with=>'012345678'
66
+ fill_in 'Confirm Password', :with=>'012345678'
67
+ click_button 'Change Password'
68
+ page.find('#notice_flash').text.must_equal "Your password has been changed"
69
+ page.current_path.must_equal '/'
70
+
71
+ login(:pass=>'012345678')
72
+ page.current_path.must_equal '/'
73
+ end
74
+ end
75
+
76
+ it "should support changing passwords for accounts without confirmation" do
77
+ rodauth do
78
+ enable :login, :change_password
79
+ modifications_require_password? false
80
+ require_password_confirmation? false
81
+ end
82
+ roda do |r|
83
+ r.rodauth
84
+ r.root{view :content=>""}
85
+ end
86
+
87
+ login
88
+ visit '/change-password'
89
+ fill_in 'New Password', :with=>'012345678'
90
+ click_button 'Change Password'
91
+ page.find('#notice_flash').text.must_equal "Your password has been changed"
92
+ end
93
+
94
+ it "should support setting requirements for passwords" do
95
+ rodauth do
96
+ enable :login, :create_account, :change_password
97
+ create_account_autologin? false
98
+ password_meets_requirements? do |password|
99
+ password =~ /banana/
100
+ end
101
+ end
102
+ roda do |r|
103
+ r.rodauth
104
+ r.root{view :content=>""}
105
+ end
106
+
107
+ visit '/create-account'
108
+ fill_in 'Login', :with=>'foo2@example.com'
109
+ fill_in 'Confirm Login', :with=>'foo2@example.com'
110
+ fill_in 'Password', :with=>'apple'
111
+ fill_in 'Confirm Password', :with=>'apple'
112
+ click_button 'Create Account'
113
+ page.html.must_include("invalid password, does not meet requirements")
114
+ page.find('#error_flash').text.must_equal "There was an error creating your account"
115
+ page.current_path.must_equal '/create-account'
116
+
117
+ fill_in 'Password', :with=>'banana'
118
+ fill_in 'Confirm Password', :with=>'banana'
119
+ click_button 'Create Account'
120
+
121
+ login(:login=>'foo2@example.com', :pass=>'banana')
122
+
123
+ visit '/change-password'
124
+ fill_in 'Password', :with=>'banana'
125
+ fill_in 'New Password', :with=>'apple'
126
+ fill_in 'Confirm Password', :with=>'apple'
127
+ click_button 'Change Password'
128
+ page.html.must_include("invalid password, does not meet requirements")
129
+ page.find('#error_flash').text.must_equal "There was an error changing your password"
130
+ page.current_path.must_equal '/change-password'
131
+
132
+ fill_in 'Password', :with=>'banana'
133
+ fill_in 'New Password', :with=>'my_banana_3'
134
+ fill_in 'Confirm Password', :with=>'my_banana_3'
135
+ click_button 'Change Password'
136
+ page.current_path.must_equal '/'
137
+ end
138
+
139
+ it "should support changing passwords for accounts via jwt" do
140
+ require_password = true
141
+ rodauth do
142
+ enable :login, :logout, :change_password
143
+ change_password_requires_password?{require_password}
144
+ end
145
+ roda(:jwt) do |r|
146
+ r.rodauth
147
+ end
148
+
149
+ json_login
150
+
151
+ res = json_request('/change-password', :password=>'0123456789', "new-password"=>'0123456', "password-confirm"=>'0123456789')
152
+ res.must_equal [400, {'error'=>"There was an error changing your password", "field-error"=>["new-password", "passwords do not match"]}]
153
+
154
+ res = json_request('/change-password', :password=>'0123456', "new-password"=>'0123456', "password-confirm"=>'0123456')
155
+ res.must_equal [400, {'error'=>"There was an error changing your password", "field-error"=>["password", "invalid password"]}]
156
+
157
+ res = json_request('/change-password', :password=>'0123456789', "new-password"=>'0123456789', "password-confirm"=>'0123456789')
158
+ res.must_equal [400, {'error'=>"There was an error changing your password", "field-error"=>["new-password", "invalid password, same as current password"]}]
159
+
160
+ res = json_request('/change-password', :password=>'0123456789', "new-password"=>'0123456', "password-confirm"=>'0123456')
161
+ res.must_equal [200, {'success'=>"Your password has been changed"}]
162
+
163
+ json_logout
164
+ res = json_login(:no_check=>true)
165
+ res.must_equal [400, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
166
+
167
+ json_login(:pass=>'0123456')
168
+
169
+ require_password = false
170
+
171
+ res = json_request('/change-password', "new-password"=>'012345678', "password-confirm"=>'012345678')
172
+ res.must_equal [200, {'success'=>"Your password has been changed"}]
173
+
174
+ json_logout
175
+ json_login(:pass=>'012345678')
176
+ end
177
+ end