rodauth 0.10.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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,188 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe 'Rodauth login feature' do
4
+ it "should handle logins and logouts" do
5
+ rodauth{enable :login, :logout}
6
+ roda do |r|
7
+ r.rodauth
8
+ next unless session[:account_id]
9
+ r.root{view :content=>"Logged In"}
10
+ end
11
+
12
+ visit '/login'
13
+ page.title.must_equal 'Login'
14
+
15
+ login(:login=>'foo@example2.com', :visit=>false)
16
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
17
+ page.html.must_include("no matching login")
18
+
19
+ login(:pass=>'012345678', :visit=>false)
20
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
21
+ page.html.must_include("invalid password")
22
+
23
+ fill_in 'Password', :with=>'0123456789'
24
+ click_button 'Login'
25
+ page.current_path.must_equal '/'
26
+ page.find('#notice_flash').text.must_equal 'You have been logged in'
27
+ page.html.must_include("Logged In")
28
+
29
+ visit '/logout'
30
+ page.title.must_equal 'Logout'
31
+
32
+ click_button 'Logout'
33
+ page.find('#notice_flash').text.must_equal 'You have been logged out'
34
+ page.current_path.must_equal '/login'
35
+ end
36
+
37
+ it "should not allow login to unverified account" do
38
+ rodauth do
39
+ enable :login
40
+ skip_status_checks? false
41
+ end
42
+ roda do |r|
43
+ r.rodauth
44
+ next unless session[:account_id]
45
+ r.root{view :content=>"Logged In"}
46
+ end
47
+
48
+ DB[:accounts].update(:status_id=>1)
49
+ login
50
+ page.find('#error_flash').text.must_equal 'There was an error logging in'
51
+ page.html.must_include("unverified account, please verify account before logging in")
52
+ end
53
+
54
+ it "should handle overriding login action" do
55
+ rodauth do
56
+ enable :login
57
+ end
58
+ roda do |r|
59
+ r.post 'login' do
60
+ if r['login'] == 'apple' && r['password'] == 'banana'
61
+ session[:user_id] = 'pear'
62
+ r.redirect '/'
63
+ end
64
+ r.redirect '/login'
65
+ end
66
+ r.rodauth
67
+ next unless session[:user_id] == 'pear'
68
+ r.root{"Logged In"}
69
+ end
70
+
71
+ login(:login=>'appl', :pass=>'banana')
72
+ page.html.wont_match(/Logged In/)
73
+
74
+ login(:login=>'apple', :pass=>'banan', :visit=>false)
75
+ page.html.wont_match(/Logged In/)
76
+
77
+ login(:login=>'apple', :pass=>'banana', :visit=>false)
78
+ page.current_path.must_equal '/'
79
+ page.html.must_include("Logged In")
80
+ end
81
+
82
+ it "should handle overriding some login attributes" do
83
+ rodauth do
84
+ enable :login
85
+ account_from_login do |login|
86
+ DB[:accounts].first if login == 'apple'
87
+ end
88
+ password_match? do |password|
89
+ password == 'banana'
90
+ end
91
+ update_session do
92
+ session[:user_id] = 'pear'
93
+ end
94
+ no_matching_login_message "no user"
95
+ invalid_password_message "bad password"
96
+ end
97
+ roda do |r|
98
+ r.rodauth
99
+ next unless session[:user_id] == 'pear'
100
+ r.root{"Logged In"}
101
+ end
102
+
103
+ login(:login=>'appl', :pass=>'banana')
104
+ page.html.must_include("no user")
105
+
106
+ login(:login=>'apple', :pass=>'banan', :visit=>false)
107
+ page.html.must_include("bad password")
108
+
109
+ fill_in 'Password', :with=>'banana'
110
+ click_button 'Login'
111
+ page.current_path.must_equal '/'
112
+ page.html.must_include("Logged In")
113
+ end
114
+
115
+ it "should handle a prefix and some other login options" do
116
+ rodauth do
117
+ enable :login, :logout
118
+ prefix 'auth'
119
+ session_key :login_email
120
+ account_from_session{DB[:accounts].first(:email=>session_value)}
121
+ account_session_value{account[:email]}
122
+ login_param{request['lp']}
123
+ login_additional_form_tags "<input type='hidden' name='lp' value='l' />"
124
+ password_param 'p'
125
+ login_redirect{"/foo/#{account[:email]}"}
126
+ logout_redirect '/auth/lin'
127
+ login_route 'lin'
128
+ logout_route 'lout'
129
+ end
130
+ no_freeze!
131
+ roda do |r|
132
+ r.on 'auth' do
133
+ r.rodauth
134
+ end
135
+ next unless session[:login_email] =~ /example/
136
+ r.get('foo/:email'){|e| "Logged In: #{e}"}
137
+ end
138
+ app.plugin :render, :views=>'spec/views', :engine=>'str'
139
+
140
+ visit '/auth/lin?lp=l'
141
+
142
+ login(:login=>'foo@example2.com', :visit=>false)
143
+ page.html.must_include("no matching login")
144
+
145
+ login(:pass=>'012345678', :visit=>false)
146
+ page.html.must_include("invalid password")
147
+
148
+ login(:visit=>false)
149
+ page.current_path.must_equal '/foo/foo@example.com'
150
+ page.html.must_include("Logged In: foo@example.com")
151
+
152
+ visit '/auth/lout'
153
+ click_button 'Logout'
154
+ page.current_path.must_equal '/auth/lin'
155
+ end
156
+
157
+ it "should login and logout via jwt" do
158
+ rodauth do
159
+ enable :login, :logout
160
+ jwt_secret{proc{super()}.must_raise ArgumentError; "1"}
161
+ end
162
+ roda(:jwt) do |r|
163
+ r.rodauth
164
+ rodauth.logged_in? ? '1' : '2'
165
+ end
166
+
167
+ json_request.must_equal [200, 2]
168
+
169
+ visit '/login'
170
+ page.status_code.must_equal 400
171
+ page.body.must_equal "Only JSON format requests are allowed"
172
+
173
+ res = json_request("/login", :method=>'GET')
174
+ res.must_equal [405, {'error'=>'non-POST method used in JSON API'}]
175
+
176
+ res = json_request("/login", :login=>'foo@example2.com', :password=>'0123456789')
177
+ res.must_equal [400, {'error'=>"There was an error logging in", "field-error"=>["login", "no matching login"]}]
178
+
179
+ res = json_request("/login", :login=>'foo@example.com', :password=>'012345678')
180
+ res.must_equal [400, {'error'=>"There was an error logging in", "field-error"=>["password", "invalid password"]}]
181
+
182
+ json_request("/login", :login=>'foo@example.com', :password=>'0123456789').must_equal [200, {"success"=>'You have been logged in'}]
183
+ json_request.must_equal [200, 1]
184
+
185
+ json_request("/logout").must_equal [200, {"success"=>'You have been logged out'}]
186
+ json_request.must_equal [200, 2]
187
+ end
188
+ end
@@ -1,5 +1,7 @@
1
1
  Sequel.migration do
2
2
  up do
3
+ extension :date_arithmetic
4
+
3
5
  # Used by the account verification and close account features
4
6
  create_table(:account_statuses) do
5
7
  Integer :id, :primary_key=>true
@@ -7,39 +9,47 @@ Sequel.migration do
7
9
  end
8
10
  from(:account_statuses).import([:id, :name], [[1, 'Unverified'], [2, 'Verified'], [3, 'Closed']])
9
11
 
10
- # Used by the create account, account verification,
11
- # and close account features.
12
+ db = self
12
13
  create_table(:accounts) do
13
14
  primary_key :id, :type=>Bignum
14
15
  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]}
16
+ if db.database_type == :postgres
17
+ citext :email, :null=>false
18
+ constraint :valid_email, :email=>/^[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+$/
19
+ index :email, :unique=>true, :where=>{:status_id=>[1, 2]}
20
+ else
21
+ String :email, :null=>false
22
+ index :email, :unique=>true
23
+ end
24
+ end
19
25
 
20
- # Only for testing of account_password_hash_column, not recommended for new
21
- # applications
22
- String :ph
26
+ deadline_opts = proc do |days|
27
+ if database_type == :mysql
28
+ {:null=>false}
29
+ else
30
+ {:null=>false, :default=>Sequel.date_add(Sequel::CURRENT_TIMESTAMP, :days=>days)}
31
+ end
23
32
  end
24
33
 
25
34
  # Used by the password reset feature
26
35
  create_table(:account_password_reset_keys) do
27
36
  foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
28
37
  String :key, :null=>false
29
- DateTime :deadline, :null=>false, :default=>Sequel.lit("CURRENT_TIMESTAMP + '1 day'")
38
+ DateTime :deadline, deadline_opts[1]
30
39
  end
31
40
 
32
41
  # Used by the account verification feature
33
42
  create_table(:account_verification_keys) do
34
43
  foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
35
44
  String :key, :null=>false
45
+ DateTime :requested_at, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
36
46
  end
37
47
 
38
48
  # Used by the remember me feature
39
49
  create_table(:account_remember_keys) do
40
50
  foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
41
51
  String :key, :null=>false
42
- DateTime :deadline, :null=>false, :default=>Sequel.lit("CURRENT_TIMESTAMP + '2 weeks'")
52
+ DateTime :deadline, deadline_opts[14]
43
53
  end
44
54
 
45
55
  # Used by the lockout feature
@@ -50,15 +60,92 @@ Sequel.migration do
50
60
  create_table(:account_lockouts) do
51
61
  foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
52
62
  String :key, :null=>false
53
- DateTime :deadline, :null=>false, :default=>Sequel.lit("CURRENT_TIMESTAMP + '1 day'")
63
+ DateTime :deadline, deadline_opts[1]
64
+ end
65
+
66
+ # Used by the password expiration feature
67
+ create_table(:account_password_change_times) do
68
+ foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
69
+ DateTime :changed_at, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
54
70
  end
55
71
 
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}"
72
+ # Used by the account expiration feature
73
+ create_table(:account_activity_times) do
74
+ foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
75
+ DateTime :last_activity_at, :null=>false
76
+ DateTime :last_login_at, :null=>false
77
+ DateTime :expired_at
78
+ end
79
+
80
+ # Used by the single session feature
81
+ create_table(:account_session_keys) do
82
+ foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
83
+ String :key, :null=>false
84
+ end
85
+
86
+ # Used by the otp feature
87
+ create_table(:account_otp_keys) do
88
+ foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
89
+ String :key, :null=>false
90
+ Integer :num_failures, :null=>false, :default=>0
91
+ Time :last_use, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
92
+ end
93
+
94
+ # Used by the recovery codes feature
95
+ create_table(:account_recovery_codes) do
96
+ foreign_key :id, :accounts, :type=>Bignum
97
+ String :code
98
+ primary_key [:id, :code]
99
+ end
100
+
101
+ # Used by the sms codes feature
102
+ create_table(:account_sms_codes) do
103
+ foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
104
+ String :phone_number, :null=>false
105
+ Integer :num_failures
106
+ String :code
107
+ DateTime :code_issued_at, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
108
+ end
109
+
110
+ case database_type
111
+ when :postgres
112
+ user = get{Sequel.lit('current_user')} + '_password'
113
+ run "GRANT REFERENCES ON accounts TO #{user}"
114
+ when :mysql, :mssql
115
+ user = if database_type == :mysql
116
+ get{Sequel.lit('current_user')}.sub(/_password@/, '@')
117
+ else
118
+ get{DB_NAME{}}
119
+ end
120
+ run "GRANT ALL ON account_statuses TO #{user}"
121
+ run "GRANT ALL ON accounts TO #{user}"
122
+ run "GRANT ALL ON account_password_reset_keys TO #{user}"
123
+ run "GRANT ALL ON account_verification_keys TO #{user}"
124
+ run "GRANT ALL ON account_remember_keys TO #{user}"
125
+ run "GRANT ALL ON account_login_failures TO #{user}"
126
+ run "GRANT ALL ON account_lockouts TO #{user}"
127
+ run "GRANT ALL ON account_password_change_times TO #{user}"
128
+ run "GRANT ALL ON account_activity_times TO #{user}"
129
+ run "GRANT ALL ON account_session_keys TO #{user}"
130
+ run "GRANT ALL ON account_otp_keys TO #{user}"
131
+ run "GRANT ALL ON account_recovery_codes TO #{user}"
132
+ run "GRANT ALL ON account_sms_codes TO #{user}"
133
+ end
59
134
  end
60
135
 
61
136
  down do
62
- drop_table(:account_lockouts, :account_login_failures, :account_remember_keys, :account_verification_keys, :account_password_reset_keys, :accounts, :account_statuses)
137
+ drop_table(:account_sms_codes,
138
+ :account_recovery_codes,
139
+ :account_otp_keys,
140
+ :account_session_keys,
141
+ :account_activity_times,
142
+ :account_password_change_times,
143
+ :account_lockouts,
144
+ :account_login_failures,
145
+ :account_remember_keys,
146
+ :account_verification_keys,
147
+ :account_password_reset_keys,
148
+ :accounts,
149
+ :account_statuses)
63
150
  end
64
151
  end
@@ -0,0 +1,11 @@
1
+ Sequel.migration do
2
+ up do
3
+ # Only for testing of account_password_hash_column, not recommended for new
4
+ # applications
5
+ add_column :accounts, :ph, String
6
+ end
7
+
8
+ down do
9
+ drop_column :accounts, :ph
10
+ end
11
+ end
@@ -1,55 +1,73 @@
1
+ require 'rodauth/migrations'
2
+
1
3
  Sequel.migration do
2
4
  up do
3
- # Used by the login and change password features
4
5
  create_table(:account_password_hashes) do
5
6
  foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
6
7
  String :password_hash, :null=>false
7
8
  end
9
+ Rodauth.create_database_authentication_functions(self)
10
+ case database_type
11
+ when :postgres
12
+ user = get{Sequel.lit('current_user')}.sub(/_password\z/, '')
13
+ run "REVOKE ALL ON account_password_hashes FROM public"
14
+ run "REVOKE ALL ON FUNCTION rodauth_get_salt(int8) FROM public"
15
+ run "REVOKE ALL ON FUNCTION rodauth_valid_password_hash(int8, text) FROM public"
16
+ run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
17
+ run "GRANT SELECT(id) ON account_password_hashes TO #{user}"
18
+ run "GRANT EXECUTE ON FUNCTION rodauth_get_salt(int8) TO #{user}"
19
+ run "GRANT EXECUTE ON FUNCTION rodauth_valid_password_hash(int8, text) TO #{user}"
20
+ when :mysql
21
+ user = get{Sequel.lit('current_user')}.sub(/_password@/, '@')
22
+ db_name = get{database{}}
23
+ run "GRANT EXECUTE ON #{db_name}.* TO #{user}"
24
+ run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
25
+ run "GRANT SELECT (id) ON account_password_hashes TO #{user}"
26
+ when :mssql
27
+ user = get{DB_NAME{}}
28
+ run "GRANT EXECUTE ON rodauth_get_salt TO #{user}"
29
+ run "GRANT EXECUTE ON rodauth_valid_password_hash TO #{user}"
30
+ run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
31
+ run "GRANT SELECT ON account_password_hashes(id) TO #{user}"
32
+ end
8
33
 
9
- # Function that returns salt for current password.
10
- run <<END
11
- CREATE OR REPLACE FUNCTION rodauth_get_salt(account_id int8) RETURNS text AS $$
12
- DECLARE salt text;
13
- BEGIN
14
- SELECT substr(password_hash, 0, 30) INTO salt
15
- FROM account_password_hashes
16
- WHERE account_id = id;
17
- RETURN salt;
18
- END;
19
- $$ LANGUAGE plpgsql
20
- SECURITY DEFINER
21
- SET search_path = public, pg_temp;
22
- END
23
-
24
- # Function that checks if password hash is valid for given user.
25
- run <<END
26
- CREATE OR REPLACE FUNCTION rodauth_valid_password_hash(account_id int8, hash text) RETURNS boolean AS $$
27
- DECLARE valid boolean;
28
- BEGIN
29
- SELECT password_hash = hash INTO valid
30
- FROM account_password_hashes
31
- WHERE account_id = id;
32
- RETURN valid;
33
- END;
34
- $$ LANGUAGE plpgsql
35
- SECURITY DEFINER
36
- SET search_path = public, pg_temp;
37
- END
34
+ # Used by the disallow_password_reuse feature
35
+ create_table(:account_previous_password_hashes) do
36
+ primary_key :id, :type=>Bignum
37
+ foreign_key :account_id, :accounts, :type=>Bignum
38
+ String :password_hash, :null=>false
39
+ end
40
+ Rodauth.create_database_previous_password_check_functions(self)
38
41
 
39
- # Restrict access to the password hash table
40
- app_user = get{Sequel.lit('current_user')}.sub(/_password\z/, '')
41
- run "REVOKE ALL ON account_password_hashes FROM public"
42
- run "REVOKE ALL ON FUNCTION rodauth_get_salt(int8) FROM public"
43
- run "REVOKE ALL ON FUNCTION rodauth_valid_password_hash(int8, text) FROM public"
44
- run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{app_user}"
45
- run "GRANT SELECT(id) ON account_password_hashes TO #{app_user}"
46
- run "GRANT EXECUTE ON FUNCTION rodauth_get_salt(int8) TO #{app_user}"
47
- run "GRANT EXECUTE ON FUNCTION rodauth_valid_password_hash(int8, text) TO #{app_user}"
42
+ case database_type
43
+ when :postgres
44
+ user = get{Sequel.lit('current_user')}.sub(/_password\z/, '')
45
+ run "REVOKE ALL ON account_previous_password_hashes FROM public"
46
+ run "REVOKE ALL ON FUNCTION rodauth_get_previous_salt(int8) FROM public"
47
+ run "REVOKE ALL ON FUNCTION rodauth_previous_password_hash_match(int8, text) FROM public"
48
+ run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
49
+ run "GRANT SELECT(id, account_id) ON account_previous_password_hashes TO #{user}"
50
+ run "GRANT USAGE ON account_previous_password_hashes_id_seq TO #{user}"
51
+ run "GRANT EXECUTE ON FUNCTION rodauth_get_previous_salt(int8) TO #{user}"
52
+ run "GRANT EXECUTE ON FUNCTION rodauth_previous_password_hash_match(int8, text) TO #{user}"
53
+ when :mysql
54
+ user = get{Sequel.lit('current_user')}.sub(/_password@/, '@')
55
+ db_name = get{database{}}
56
+ run "GRANT EXECUTE ON #{db_name}.* TO #{user}"
57
+ run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
58
+ run "GRANT SELECT (id, account_id) ON account_previous_password_hashes TO #{user}"
59
+ when :mssql
60
+ user = get{DB_NAME{}}
61
+ run "GRANT EXECUTE ON rodauth_get_previous_salt TO #{user}"
62
+ run "GRANT EXECUTE ON rodauth_previous_password_hash_match TO #{user}"
63
+ run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
64
+ run "GRANT SELECT ON account_previous_password_hashes(id, account_id) TO #{user}"
65
+ end
48
66
  end
49
67
 
50
68
  down do
51
- run "DROP FUNCTION rodauth_get_salt(int8)"
52
- run "DROP FUNCTION rodauth_valid_password_hash(int8, text)"
53
- drop_table(:account_password_hashes)
69
+ Rodauth.drop_database_previous_password_check_functions(self)
70
+ Rodauth.drop_database_authentication_functions(self)
71
+ drop_table(:account_previous_password_hashes, :account_password_hashes)
54
72
  end
55
73
  end