rodauth 1.11.0 → 1.12.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 +4 -4
- data/CHANGELOG +14 -0
- data/README.rdoc +85 -28
- data/doc/http_basic_auth.rdoc +1 -0
- data/doc/lockout.rdoc +2 -1
- data/doc/release_notes/1.12.0.txt +61 -0
- data/doc/remember.rdoc +2 -1
- data/doc/reset_password.rdoc +2 -1
- data/lib/rodauth/features/http_basic_auth.rb +15 -1
- data/lib/rodauth/features/reset_password.rb +9 -11
- data/lib/rodauth/migrations.rb +17 -18
- data/lib/rodauth/version.rb +1 -1
- data/spec/disallow_password_reuse_spec.rb +10 -2
- data/spec/http_basic_auth_spec.rb +18 -0
- data/spec/migrate/001_tables.rb +3 -3
- data/spec/migrate_password/001_tables.rb +8 -8
- data/spec/reset_password_spec.rb +9 -0
- data/spec/rodauth_spec.rb +8 -2
- data/spec/spec_helper.rb +8 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 888e341c90edf3af686821f3d64998da9fcdd5cb
|
4
|
+
data.tar.gz: 5f50386e3657dce0c24b81465a2e54ad8c81221a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2315ff44b30666ac9291217ba2c9fa2ecb928b0b6ac0919d4a6debc70ec093c1b42f1866191024ee00f0d086c9103b996b173e36a092b84eb56ea9017a7baea0
|
7
|
+
data.tar.gz: c432df2a344582f4aef84b7512fa4f5a5a72058dc9a8528ee315e95e21044bff3d46ac29052cc1a9968e5dd298370e447df31f31a19519880b5da200df680eb9
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
=== 1.12.0 (2017-10-03)
|
2
|
+
|
3
|
+
* [SECURITY] Clear expired password reset key for account before retrieving password reset key (chanks, jeremyevans) (#43)
|
4
|
+
|
5
|
+
* Update migrations to work with Sequel 5 (jeremyevans)
|
6
|
+
|
7
|
+
* Add require_http_basic_auth configuration method to http_basic_auth feature (jeremyevans) (#41)
|
8
|
+
|
9
|
+
* Support passing :search_path option to Rodauth.create_database_authentication_functions when using PostgreSQL (jeremyevans)
|
10
|
+
|
11
|
+
* Support passing options to Rodauth.{create,drop}_database_previous_password_check_functions (jeremyevans)
|
12
|
+
|
13
|
+
* Support passing options to Rodauth.drop_database_authentication_functions (jeremyevans)
|
14
|
+
|
1
15
|
=== 1.11.0 (2017-04-24)
|
2
16
|
|
3
17
|
* Add login_required_error_status, and use it in the jwt feature when custom error statuses are allowed (jeremyevans)
|
data/README.rdoc
CHANGED
@@ -93,7 +93,7 @@ account does not have access to read the password hashes. The other
|
|
93
93
|
account handles password hashes and is referred to as the +ph+
|
94
94
|
account. The +ph+ account sets up the database functions that can
|
95
95
|
retrieve the salt for a given account's password, and check if a
|
96
|
-
password hash matches for
|
96
|
+
password hash matches for a given account. The +ph+ account
|
97
97
|
sets these functions up so that the +app+ account can execute the
|
98
98
|
functions using the +ph+ account's permissions. This allows the
|
99
99
|
+app+ account to check passwords without having access to read
|
@@ -151,6 +151,40 @@ Heroku, it just won't have the same security benefits. That's not to say
|
|
151
151
|
it is insecure, just that it drops the security level for password hash
|
152
152
|
storage to the same level as other common authentication solutions.
|
153
153
|
|
154
|
+
=== Create database accounts
|
155
|
+
|
156
|
+
If you are currently running your application using the database superuser
|
157
|
+
account, the first thing you need to do is to create the +app+ database
|
158
|
+
account. It's often best to name this account the same as the
|
159
|
+
database name.
|
160
|
+
|
161
|
+
You should also create the +ph+ database account which will handle access
|
162
|
+
to the password hashes.
|
163
|
+
|
164
|
+
Example for PostgreSQL:
|
165
|
+
|
166
|
+
createuser -U postgres ${DATABASE_NAME}
|
167
|
+
createuser -U postgres ${DATABASE_NAME}_password
|
168
|
+
|
169
|
+
Note that if the database superuser account owns all of the items in the
|
170
|
+
database, you'll need to change the ownership to the database account you
|
171
|
+
just created. See https://gist.github.com/jeremyevans/8483320
|
172
|
+
for a way to do that.
|
173
|
+
|
174
|
+
=== Create database
|
175
|
+
|
176
|
+
In general, the +app+ account is the owner of the database, since it will
|
177
|
+
own most of the tables:
|
178
|
+
|
179
|
+
createdb -U postgres -O ${DATABASE_NAME} ${DATABASE_NAME}
|
180
|
+
|
181
|
+
Note that this is not the most secure way to develop applications. For
|
182
|
+
maximum security, you would want to use a separate database account as
|
183
|
+
the owner of the tables, have the +app+ account not be the
|
184
|
+
owner of any tables, and specifically grant the +app+ account only the
|
185
|
+
minimum access it needs to work correctly. Doing that is beyond the
|
186
|
+
scope of Rodauth, though.
|
187
|
+
|
154
188
|
=== Load extensions
|
155
189
|
|
156
190
|
If you want to use the login features for Rodauth, you need to load the
|
@@ -166,25 +200,48 @@ bad idea), you don't need to use the PostgreSQL citext extension. Just
|
|
166
200
|
remember to modify the migration below to use +String+ instead of +citext+
|
167
201
|
for the email in that case.
|
168
202
|
|
169
|
-
===
|
203
|
+
=== Using non-default schema
|
170
204
|
|
171
|
-
|
172
|
-
|
173
|
-
account. It's often best to name this account the same as the
|
174
|
-
database name.
|
205
|
+
PostgreSQL sets up new tables in the public schema by default.
|
206
|
+
If you would like to use separate schemas per user, you can do:
|
175
207
|
|
176
|
-
|
177
|
-
|
208
|
+
psql -U postgres -c "DROP SCHEMA public;" ${DATABASE_NAME}
|
209
|
+
psql -U postgres -c "CREATE SCHEMA ${DATABASE_NAME} AUTHORIZATION ${DATABASE_NAME};" ${DATABASE_NAME}
|
210
|
+
psql -U postgres -c "CREATE SCHEMA ${DATABASE_NAME}_password AUTHORIZATION ${DATABASE_NAME}_password;" ${DATABASE_NAME}
|
211
|
+
psql -U postgres -c "GRANT USAGE ON SCHEMA ${DATABASE_NAME} TO ${DATABASE_NAME}_password;" ${DATABASE_NAME}
|
212
|
+
psql -U postgres -c "GRANT USAGE ON SCHEMA ${DATABASE_NAME}_password TO ${DATABASE_NAME};" ${DATABASE_NAME}
|
178
213
|
|
179
|
-
|
214
|
+
You'll need to modify the code to load the extension to specify the schema:
|
180
215
|
|
181
|
-
|
182
|
-
createuser -U postgres ${DATABASE_NAME}_password
|
216
|
+
psql -U postgres -c "CREATE EXTENSION citext SCHEMA ${DATABASE_NAME}" ${DATABASE_NAME}
|
183
217
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
218
|
+
When running the migration for the +ph+ user you'll need to modify a couple
|
219
|
+
things for the schema changes:
|
220
|
+
|
221
|
+
create_table(:account_password_hashes) do
|
222
|
+
foreign_key :id, Sequel[:${DATABASE_NAME}][:accounts], :primary_key=>true, :type=>:Bignum
|
223
|
+
String :password_hash, :null=>false
|
224
|
+
end
|
225
|
+
Rodauth.create_database_authentication_functions(self, :table_name=>"${DATABASE_NAME}_password.account_password_hashes")
|
226
|
+
|
227
|
+
# if using the disallow_password_reuse feature:
|
228
|
+
create_table(:account_previous_password_hashes) do
|
229
|
+
primary_key :id, :type=>:Bignum
|
230
|
+
foreign_key :account_id, Sequel[:${DATABASE_NAME}][:accounts], :type=>:Bignum
|
231
|
+
String :password_hash, :null=>false
|
232
|
+
end
|
233
|
+
Rodauth.create_database_previous_password_check_functions(self, :table_name=>"${DATABASE_NAME}_password.account_previous_password_hashes")
|
234
|
+
|
235
|
+
You'll also need to use the following Rodauth configuration methods so that the
|
236
|
+
app account calls functions in a separate schema:
|
237
|
+
|
238
|
+
function_name do |name|
|
239
|
+
"${DATABASE_NAME}_password.#{name}"
|
240
|
+
end
|
241
|
+
password_hash_table Sequel[:${DATABASE_NAME}_password][:account_password_hashes]
|
242
|
+
|
243
|
+
# if using the disallow_password_reuse feature:
|
244
|
+
previous_password_hash_table Sequel[:${DATABASE_NAME}_password][:account_previous_password_hashes]
|
188
245
|
|
189
246
|
== MySQL Database Setup
|
190
247
|
|
@@ -352,13 +409,13 @@ versions of Sequel, switch the :Bignum symbols to Bignum constants.
|
|
352
409
|
|
353
410
|
case database_type
|
354
411
|
when :postgres
|
355
|
-
user = get
|
412
|
+
user = get(Sequel.lit('current_user')) + '_password'
|
356
413
|
run "GRANT REFERENCES ON accounts TO #{user}"
|
357
414
|
when :mysql, :mssql
|
358
415
|
user = if database_type == :mysql
|
359
|
-
get
|
416
|
+
get(Sequel.lit('current_user')).sub(/_password@/, '@')
|
360
417
|
else
|
361
|
-
get
|
418
|
+
get(Sequel.function(:DB_NAME))
|
362
419
|
end
|
363
420
|
run "GRANT ALL ON account_statuses TO #{user}"
|
364
421
|
run "GRANT ALL ON accounts TO #{user}"
|
@@ -408,7 +465,7 @@ Second migration, run using the +ph+ account:
|
|
408
465
|
Rodauth.create_database_authentication_functions(self)
|
409
466
|
case database_type
|
410
467
|
when :postgres
|
411
|
-
user = get
|
468
|
+
user = get(Sequel.lit('current_user')).sub(/_password\z/, '')
|
412
469
|
run "REVOKE ALL ON account_password_hashes FROM public"
|
413
470
|
run "REVOKE ALL ON FUNCTION rodauth_get_salt(int8) FROM public"
|
414
471
|
run "REVOKE ALL ON FUNCTION rodauth_valid_password_hash(int8, text) FROM public"
|
@@ -417,13 +474,13 @@ Second migration, run using the +ph+ account:
|
|
417
474
|
run "GRANT EXECUTE ON FUNCTION rodauth_get_salt(int8) TO #{user}"
|
418
475
|
run "GRANT EXECUTE ON FUNCTION rodauth_valid_password_hash(int8, text) TO #{user}"
|
419
476
|
when :mysql
|
420
|
-
user = get
|
421
|
-
db_name = get
|
477
|
+
user = get(Sequel.lit('current_user')).sub(/_password@/, '@')
|
478
|
+
db_name = get(Sequel.function(:database))
|
422
479
|
run "GRANT EXECUTE ON #{db_name}.* TO #{user}"
|
423
480
|
run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
|
424
481
|
run "GRANT SELECT (id) ON account_password_hashes TO #{user}"
|
425
482
|
when :mssql
|
426
|
-
user = get
|
483
|
+
user = get(Sequel.function(:DB_NAME))
|
427
484
|
run "GRANT EXECUTE ON rodauth_get_salt TO #{user}"
|
428
485
|
run "GRANT EXECUTE ON rodauth_valid_password_hash TO #{user}"
|
429
486
|
run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
|
@@ -440,7 +497,7 @@ Second migration, run using the +ph+ account:
|
|
440
497
|
|
441
498
|
case database_type
|
442
499
|
when :postgres
|
443
|
-
user = get
|
500
|
+
user = get(Sequel.lit('current_user')).sub(/_password\z/, '')
|
444
501
|
run "REVOKE ALL ON account_previous_password_hashes FROM public"
|
445
502
|
run "REVOKE ALL ON FUNCTION rodauth_get_previous_salt(int8) FROM public"
|
446
503
|
run "REVOKE ALL ON FUNCTION rodauth_previous_password_hash_match(int8, text) FROM public"
|
@@ -450,13 +507,13 @@ Second migration, run using the +ph+ account:
|
|
450
507
|
run "GRANT EXECUTE ON FUNCTION rodauth_get_previous_salt(int8) TO #{user}"
|
451
508
|
run "GRANT EXECUTE ON FUNCTION rodauth_previous_password_hash_match(int8, text) TO #{user}"
|
452
509
|
when :mysql
|
453
|
-
user = get
|
454
|
-
db_name = get
|
510
|
+
user = get(Sequel.lit('current_user')).sub(/_password@/, '@')
|
511
|
+
db_name = get(Sequel.function(:database))
|
455
512
|
run "GRANT EXECUTE ON #{db_name}.* TO #{user}"
|
456
513
|
run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
|
457
514
|
run "GRANT SELECT (id, account_id) ON account_previous_password_hashes TO #{user}"
|
458
515
|
when :mssql
|
459
|
-
user = get
|
516
|
+
user = get(Sequel.function(:DB_NAME))
|
460
517
|
run "GRANT EXECUTE ON rodauth_get_previous_salt TO #{user}"
|
461
518
|
run "GRANT EXECUTE ON rodauth_previous_password_hash_match TO #{user}"
|
462
519
|
run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
|
@@ -825,8 +882,8 @@ to +/login+ with an +access_token+ parameter that is set to the user's
|
|
825
882
|
Facebook OAuth access token.
|
826
883
|
|
827
884
|
|
828
|
-
|
829
|
-
|
885
|
+
require 'koala'
|
886
|
+
plugin :rodauth do
|
830
887
|
enable :login, :logout, :jwt
|
831
888
|
|
832
889
|
require_bcrypt? false
|
data/doc/http_basic_auth.rdoc
CHANGED
@@ -6,3 +6,4 @@ described in RFC 1945.
|
|
6
6
|
== Auth Value Methods
|
7
7
|
|
8
8
|
http_basic_auth_realm :: The realm to return in the WWW-Authenticate header.
|
9
|
+
require_http_basic_auth :: If true, when +rodauth.require_authentication+ is used, return a 401 status if basic auth has not been provided, instead of redirecting to the login page. False by default.
|
data/doc/lockout.rdoc
CHANGED
@@ -15,7 +15,8 @@ account_lockouts_deadline_column :: The deadline column in the account lockouts
|
|
15
15
|
table, containing how long the account is
|
16
16
|
locked out until.
|
17
17
|
account_lockouts_deadline_interval :: The amount of time for which to lock out accounts,
|
18
|
-
1 day by default.
|
18
|
+
1 day by default. Only used if set_deadline_values?
|
19
|
+
is true.
|
19
20
|
account_lockouts_key_column :: The unlock key column in the account lockouts table.
|
20
21
|
account_lockouts_table :: The table containing account lockout information.
|
21
22
|
account_login_failures_id_column :: The id column in the account login failures table,
|
@@ -0,0 +1,61 @@
|
|
1
|
+
= Security Fix
|
2
|
+
|
3
|
+
* The password reset key deadline was previously ignored when
|
4
|
+
checking for a password reset key. This allowed expired keys to
|
5
|
+
be used. This problem exists in all previous versions.
|
6
|
+
|
7
|
+
The root cause of this issue is that support for deadline checking
|
8
|
+
was not previously implemented. In previous versions, the deadline
|
9
|
+
was only used to remove old keys when creating a new key.
|
10
|
+
|
11
|
+
Rodauth only allows a single password reset key per account, and
|
12
|
+
deletes password reset keys when passwords are reset. So if the
|
13
|
+
user had subsequently generated a different password reset key, or
|
14
|
+
had already used the password reset key to reset the password,
|
15
|
+
then they would not be vulnerable. The most likely situation
|
16
|
+
where there exists a vulnerability due to this issue is:
|
17
|
+
|
18
|
+
* A user requests a password reset.
|
19
|
+
* They do not reset their password or request another
|
20
|
+
password reset.
|
21
|
+
* The password reset key deadline expires.
|
22
|
+
* An attacker gets access to their archived email containing
|
23
|
+
the password reset link, which they use to reset the
|
24
|
+
password for the account.
|
25
|
+
|
26
|
+
Reporting Details:
|
27
|
+
|
28
|
+
* Initially reported on 10/3/2017
|
29
|
+
* Fixed in repository on 10/3/2017
|
30
|
+
* Version 1.12.0 released with fix on 10/3/2017
|
31
|
+
|
32
|
+
Thanks to Chris Hanks for discovering and reporting this issue
|
33
|
+
and supplying an initial fix.
|
34
|
+
|
35
|
+
= New Features
|
36
|
+
|
37
|
+
* The http_basic_auth feature now supports a
|
38
|
+
require_http_basic_auth configuration method. When set to true,
|
39
|
+
if authentication is required and the request is not already
|
40
|
+
authenticated, they will get a 401 response instead of a
|
41
|
+
redirect to the login page.
|
42
|
+
|
43
|
+
* All of the following Rodauth migration methods now support an
|
44
|
+
options hash:
|
45
|
+
|
46
|
+
* Rodauth.drop_database_authentication_functions
|
47
|
+
* Rodauth.create_database_previous_password_check_functions
|
48
|
+
* Rodauth.drop_database_previous_password_check_functions
|
49
|
+
|
50
|
+
These options allow you to customize the get_salt_name and
|
51
|
+
valid_hash_name database functions, as well as set the the table
|
52
|
+
for the previous_password_check_functions.
|
53
|
+
|
54
|
+
* A :search_path option is now supported when using the following
|
55
|
+
Rodauth migration methods on PostgreSQL:
|
56
|
+
|
57
|
+
* Rodauth.create_database_authentication_functions
|
58
|
+
* Rodauth.drop_database_authentication_functions
|
59
|
+
|
60
|
+
This sets the search_path to use inside the function. For
|
61
|
+
backwards compatibility, it defaults to 'public, pg_temp'.
|
data/doc/remember.rdoc
CHANGED
@@ -20,7 +20,8 @@ remember_deadline_column :: The column name in the remember keys table storing
|
|
20
20
|
the deadline after which the token will be
|
21
21
|
ignored.
|
22
22
|
remember_deadline_interval :: The amount of time for which to remember accounts,
|
23
|
-
14 days by default.
|
23
|
+
14 days by default. Only used if set_deadline_values?
|
24
|
+
is true.
|
24
25
|
remember_disable_label :: The label for disabling remembering.
|
25
26
|
remember_disable_param_value :: The parameter value for disabling remembering.
|
26
27
|
remember_error_flash :: The flash error to show if there is an error changing a
|
data/doc/reset_password.rdoc
CHANGED
@@ -19,7 +19,8 @@ reset_password_button :: The text to use for the reset password button.
|
|
19
19
|
reset_password_deadline_column :: The column name in the reset password keys table storing
|
20
20
|
the deadline after which the token will be ignored.
|
21
21
|
reset_password_deadline_interval :: The amount of time for which to allow users to
|
22
|
-
reset their passwords, 1 day by default.
|
22
|
+
reset their passwords, 1 day by default. Only used if
|
23
|
+
set_deadline_values? is true.
|
23
24
|
reset_password_email_sent_notice_flash :: The flash notice to show after a reset
|
24
25
|
password email has been sent.
|
25
26
|
reset_password_email_sent_redirect :: Where to redirect after sending a reset
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module Rodauth
|
4
4
|
Feature.define(:http_basic_auth, :HttpBasicAuth) do
|
5
5
|
auth_value_method :http_basic_auth_realm, "protected"
|
6
|
+
auth_value_method :require_http_basic_auth, false
|
6
7
|
|
7
8
|
def session
|
8
9
|
return @session if defined?(@session)
|
@@ -41,9 +42,22 @@ module Rodauth
|
|
41
42
|
|
42
43
|
private
|
43
44
|
|
44
|
-
def
|
45
|
+
def require_login
|
46
|
+
if !logged_in? && require_http_basic_auth
|
47
|
+
set_http_basic_auth_error_response
|
48
|
+
request.halt
|
49
|
+
end
|
50
|
+
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_http_basic_auth_error_response
|
45
55
|
response.status = 401
|
46
56
|
response.headers["WWW-Authenticate"] = "Basic realm=\"#{http_basic_auth_realm}\""
|
57
|
+
end
|
58
|
+
|
59
|
+
def throw_basic_auth_error(*args)
|
60
|
+
set_http_basic_auth_error_response
|
47
61
|
throw_error(*args)
|
48
62
|
end
|
49
63
|
end
|
@@ -144,17 +144,13 @@ module Rodauth
|
|
144
144
|
end
|
145
145
|
|
146
146
|
def create_reset_password_key
|
147
|
-
ds = password_reset_ds
|
148
147
|
transaction do
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
end
|
156
|
-
else
|
157
|
-
@reset_password_key_value = get_password_reset_key(account_id)
|
148
|
+
if reset_password_key_value = get_password_reset_key(account_id)
|
149
|
+
@reset_password_key_value = reset_password_key_value
|
150
|
+
elsif e = raised_uniqueness_violation{password_reset_ds.insert(reset_password_key_insert_hash)}
|
151
|
+
# If inserting into the reset password table causes a violation, we can pull the
|
152
|
+
# existing reset password key from the table, or reraise.
|
153
|
+
raise e unless @reset_password_key_value = get_password_reset_key(account_id)
|
158
154
|
end
|
159
155
|
end
|
160
156
|
end
|
@@ -176,7 +172,9 @@ module Rodauth
|
|
176
172
|
end
|
177
173
|
|
178
174
|
def get_password_reset_key(id)
|
179
|
-
password_reset_ds(id)
|
175
|
+
ds = password_reset_ds(id)
|
176
|
+
ds.where(Sequel::CURRENT_TIMESTAMP > reset_password_deadline_column).delete
|
177
|
+
ds.get(reset_password_key_column)
|
180
178
|
end
|
181
179
|
|
182
180
|
def login_form_footer
|
data/lib/rodauth/migrations.rb
CHANGED
@@ -5,8 +5,11 @@ module Rodauth
|
|
5
5
|
table_name = opts[:table_name] || :account_password_hashes
|
6
6
|
get_salt_name = opts[:get_salt_name] || :rodauth_get_salt
|
7
7
|
valid_hash_name = opts[:valid_hash_name] || :rodauth_valid_password_hash
|
8
|
+
|
8
9
|
case db.database_type
|
9
10
|
when :postgres
|
11
|
+
search_path = opts[:search_path] || 'public, pg_temp'
|
12
|
+
|
10
13
|
db.run <<END
|
11
14
|
CREATE OR REPLACE FUNCTION #{get_salt_name}(acct_id int8) RETURNS text AS $$
|
12
15
|
DECLARE salt text;
|
@@ -18,7 +21,7 @@ RETURN salt;
|
|
18
21
|
END;
|
19
22
|
$$ LANGUAGE plpgsql
|
20
23
|
SECURITY DEFINER
|
21
|
-
SET search_path =
|
24
|
+
SET search_path = #{search_path};
|
22
25
|
END
|
23
26
|
|
24
27
|
db.run <<END
|
@@ -32,7 +35,7 @@ RETURN valid;
|
|
32
35
|
END;
|
33
36
|
$$ LANGUAGE plpgsql
|
34
37
|
SECURITY DEFINER
|
35
|
-
SET search_path =
|
38
|
+
SET search_path = #{search_path};
|
36
39
|
END
|
37
40
|
when :mysql
|
38
41
|
db.run <<END
|
@@ -102,29 +105,25 @@ END
|
|
102
105
|
end
|
103
106
|
end
|
104
107
|
|
105
|
-
def self.drop_database_authentication_functions(db)
|
108
|
+
def self.drop_database_authentication_functions(db, opts={})
|
109
|
+
get_salt_name = opts[:get_salt_name] || :rodauth_get_salt
|
110
|
+
valid_hash_name = opts[:valid_hash_name] || :rodauth_valid_password_hash
|
111
|
+
|
106
112
|
case db.database_type
|
107
113
|
when :postgres
|
108
|
-
db.run "DROP FUNCTION
|
109
|
-
db.run "DROP FUNCTION
|
114
|
+
db.run "DROP FUNCTION #{get_salt_name}(int8)"
|
115
|
+
db.run "DROP FUNCTION #{valid_hash_name}(int8, text)"
|
110
116
|
when :mysql, :mssql
|
111
|
-
db.run "DROP FUNCTION
|
112
|
-
db.run "DROP FUNCTION
|
117
|
+
db.run "DROP FUNCTION #{get_salt_name}"
|
118
|
+
db.run "DROP FUNCTION #{valid_hash_name}"
|
113
119
|
end
|
114
120
|
end
|
115
121
|
|
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)
|
122
|
+
def self.create_database_previous_password_check_functions(db, opts={})
|
123
|
+
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}.merge(opts))
|
118
124
|
end
|
119
125
|
|
120
|
-
def self.drop_database_previous_password_check_functions(db)
|
121
|
-
|
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
|
126
|
+
def self.drop_database_previous_password_check_functions(db, opts={})
|
127
|
+
drop_database_authentication_functions(db, {:get_salt_name=>:rodauth_get_previous_salt, :valid_hash_name=>:rodauth_previous_password_hash_match}.merge(opts))
|
129
128
|
end
|
130
129
|
end
|
data/lib/rodauth/version.rb
CHANGED
@@ -2,8 +2,13 @@ require File.expand_path("spec_helper", File.dirname(__FILE__))
|
|
2
2
|
|
3
3
|
describe 'Rodauth disallow_password_reuse feature' do
|
4
4
|
it "should disallow reuse of passwords" do
|
5
|
+
table = :account_previous_password_hashes
|
5
6
|
rodauth do
|
6
7
|
enable :login, :change_password, :disallow_password_reuse, :close_account
|
8
|
+
if ENV['RODAUTH_SEPARATE_SCHEMA']
|
9
|
+
table = Sequel[:rodauth_test_password][:account_previous_password_hashes]
|
10
|
+
previous_password_hash_table table
|
11
|
+
end
|
7
12
|
change_password_requires_password? false
|
8
13
|
close_account_requires_password? false
|
9
14
|
end
|
@@ -43,15 +48,18 @@ describe 'Rodauth disallow_password_reuse feature' do
|
|
43
48
|
click_button 'Change Password'
|
44
49
|
page.find('#notice_flash').text.must_equal "Your password has been changed"
|
45
50
|
|
46
|
-
DB[
|
51
|
+
DB[table].get{count(:id)}.must_equal 7
|
47
52
|
visit '/close-account'
|
48
53
|
click_button 'Close Account'
|
49
|
-
DB[
|
54
|
+
DB[table].get{count(:id)}.must_equal 0
|
50
55
|
end
|
51
56
|
|
52
57
|
it "should handle create account when account_password_hash_column is true" do
|
53
58
|
rodauth do
|
54
59
|
enable :login, :create_account, :change_password, :disallow_password_reuse
|
60
|
+
if ENV['RODAUTH_SEPARATE_SCHEMA']
|
61
|
+
previous_password_hash_table Sequel[:rodauth_test_password][:account_previous_password_hashes]
|
62
|
+
end
|
55
63
|
account_password_hash_column :ph
|
56
64
|
change_password_requires_password? false
|
57
65
|
end
|
@@ -62,6 +62,24 @@ describe "Rodauth http basic auth feature" do
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
+
it "requires authentication if require_http_basic_auth is true" do
|
66
|
+
rodauth do
|
67
|
+
enable :http_basic_auth
|
68
|
+
require_http_basic_auth true
|
69
|
+
end
|
70
|
+
roda do |r|
|
71
|
+
rodauth.require_authentication
|
72
|
+
r.root{view :content=>(rodauth.logged_in? ? "Logged In" : 'Not Logged')}
|
73
|
+
end
|
74
|
+
|
75
|
+
visit '/'
|
76
|
+
page.status_code.must_equal 401
|
77
|
+
page.response_headers.keys.must_include("WWW-Authenticate")
|
78
|
+
|
79
|
+
basic_auth_visit
|
80
|
+
page.text.must_include "Logged In"
|
81
|
+
end
|
82
|
+
|
65
83
|
it "works with standard authentication" do
|
66
84
|
rodauth do
|
67
85
|
enable :login, :http_basic_auth
|
data/spec/migrate/001_tables.rb
CHANGED
@@ -117,13 +117,13 @@ Sequel.migration do
|
|
117
117
|
|
118
118
|
case database_type
|
119
119
|
when :postgres
|
120
|
-
user = get
|
120
|
+
user = get(Sequel.lit('current_user')) + '_password'
|
121
121
|
run "GRANT REFERENCES ON accounts TO #{user}"
|
122
122
|
when :mysql, :mssql
|
123
123
|
user = if database_type == :mysql
|
124
|
-
get
|
124
|
+
get(Sequel.lit('current_user')).sub(/_password@/, '@')
|
125
125
|
else
|
126
|
-
get
|
126
|
+
get(Sequel.function(:DB_NAME))
|
127
127
|
end
|
128
128
|
run "GRANT ALL ON account_statuses TO #{user}"
|
129
129
|
run "GRANT ALL ON accounts TO #{user}"
|
@@ -9,7 +9,7 @@ Sequel.migration do
|
|
9
9
|
Rodauth.create_database_authentication_functions(self)
|
10
10
|
case database_type
|
11
11
|
when :postgres
|
12
|
-
user = get
|
12
|
+
user = get(Sequel.lit('current_user')).sub(/_password\z/, '')
|
13
13
|
run "REVOKE ALL ON account_password_hashes FROM public"
|
14
14
|
run "REVOKE ALL ON FUNCTION rodauth_get_salt(int8) FROM public"
|
15
15
|
run "REVOKE ALL ON FUNCTION rodauth_valid_password_hash(int8, text) FROM public"
|
@@ -18,13 +18,13 @@ Sequel.migration do
|
|
18
18
|
run "GRANT EXECUTE ON FUNCTION rodauth_get_salt(int8) TO #{user}"
|
19
19
|
run "GRANT EXECUTE ON FUNCTION rodauth_valid_password_hash(int8, text) TO #{user}"
|
20
20
|
when :mysql
|
21
|
-
user = get
|
22
|
-
db_name = get
|
21
|
+
user = get(Sequel.lit('current_user')).sub(/_password@/, '@')
|
22
|
+
db_name = get(Sequel.function(:database))
|
23
23
|
run "GRANT EXECUTE ON #{db_name}.* TO #{user}"
|
24
24
|
run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
|
25
25
|
run "GRANT SELECT (id) ON account_password_hashes TO #{user}"
|
26
26
|
when :mssql
|
27
|
-
user = get
|
27
|
+
user = get(Sequel.function(:DB_NAME))
|
28
28
|
run "GRANT EXECUTE ON rodauth_get_salt TO #{user}"
|
29
29
|
run "GRANT EXECUTE ON rodauth_valid_password_hash TO #{user}"
|
30
30
|
run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
|
@@ -41,7 +41,7 @@ Sequel.migration do
|
|
41
41
|
|
42
42
|
case database_type
|
43
43
|
when :postgres
|
44
|
-
user = get
|
44
|
+
user = get(Sequel.lit('current_user')).sub(/_password\z/, '')
|
45
45
|
run "REVOKE ALL ON account_previous_password_hashes FROM public"
|
46
46
|
run "REVOKE ALL ON FUNCTION rodauth_get_previous_salt(int8) FROM public"
|
47
47
|
run "REVOKE ALL ON FUNCTION rodauth_previous_password_hash_match(int8, text) FROM public"
|
@@ -51,13 +51,13 @@ Sequel.migration do
|
|
51
51
|
run "GRANT EXECUTE ON FUNCTION rodauth_get_previous_salt(int8) TO #{user}"
|
52
52
|
run "GRANT EXECUTE ON FUNCTION rodauth_previous_password_hash_match(int8, text) TO #{user}"
|
53
53
|
when :mysql
|
54
|
-
user = get
|
55
|
-
db_name = get
|
54
|
+
user = get(Sequel.lit('current_user')).sub(/_password@/, '@')
|
55
|
+
db_name = get(Sequel.function(:database))
|
56
56
|
run "GRANT EXECUTE ON #{db_name}.* TO #{user}"
|
57
57
|
run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
|
58
58
|
run "GRANT SELECT (id, account_id) ON account_previous_password_hashes TO #{user}"
|
59
59
|
when :mssql
|
60
|
-
user = get
|
60
|
+
user = get(Sequel.function(:DB_NAME))
|
61
61
|
run "GRANT EXECUTE ON rodauth_get_previous_salt TO #{user}"
|
62
62
|
run "GRANT EXECUTE ON rodauth_previous_password_hash_match TO #{user}"
|
63
63
|
run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
|
data/spec/reset_password_spec.rb
CHANGED
@@ -66,6 +66,15 @@ describe 'Rodauth reset_password feature' do
|
|
66
66
|
|
67
67
|
login(:pass=>'0123456')
|
68
68
|
page.current_path.must_equal '/'
|
69
|
+
|
70
|
+
login(:pass=>'bad')
|
71
|
+
click_link "Forgot Password?"
|
72
|
+
fill_in "Login", :with=>"foo@example.com"
|
73
|
+
click_button "Request Password Reset"
|
74
|
+
DB[:account_password_reset_keys].update(:deadline => Time.now - 60).must_equal 1
|
75
|
+
link = email_link(/(\/reset-password\?key=.+)$/)
|
76
|
+
visit link
|
77
|
+
page.find('#error_flash').text.must_equal "invalid password reset key"
|
69
78
|
end
|
70
79
|
|
71
80
|
it "should support resetting passwords for accounts without confirmation" do
|
data/spec/rodauth_spec.rb
CHANGED
@@ -152,6 +152,12 @@ describe 'Rodauth' do
|
|
152
152
|
app = Class.new(Base)
|
153
153
|
app.plugin(:rodauth) do
|
154
154
|
enable :login
|
155
|
+
if ENV['RODAUTH_SEPARATE_SCHEMA']
|
156
|
+
password_hash_table Sequel[:rodauth_test_password][:account_password_hashes]
|
157
|
+
function_name do |name|
|
158
|
+
"rodauth_test_password.#{name}"
|
159
|
+
end
|
160
|
+
end
|
155
161
|
end
|
156
162
|
app.plugin(:rodauth, :name=>:r2) do
|
157
163
|
enable :logout
|
@@ -188,14 +194,14 @@ describe 'Rodauth' do
|
|
188
194
|
rodauth do
|
189
195
|
enable :login
|
190
196
|
(class << self; self end).send(:define_method, :warn){|msg| warning = msg}
|
191
|
-
account_model Sequel::Model(DB[:
|
197
|
+
account_model Sequel::Model(DB[:accounts].select(:id))
|
192
198
|
end
|
193
199
|
roda do |r|
|
194
200
|
"#{rodauth.accounts_table}#{rodauth.account_select.length}"
|
195
201
|
end
|
196
202
|
|
197
203
|
visit '/'
|
198
|
-
page.body.must_equal '
|
204
|
+
page.body.must_equal 'accounts1'
|
199
205
|
warning.must_equal "account_model is deprecated, use db and accounts_table settings"
|
200
206
|
end
|
201
207
|
|
data/spec/spec_helper.rb
CHANGED
@@ -121,6 +121,12 @@ class Minitest::HooksSpec
|
|
121
121
|
json_response_success_key 'success'
|
122
122
|
json_response_custom_error_status? true
|
123
123
|
end
|
124
|
+
if ENV['RODAUTH_SEPARATE_SCHEMA']
|
125
|
+
password_hash_table Sequel[:rodauth_test_password][:account_password_hashes]
|
126
|
+
function_name do |name|
|
127
|
+
"rodauth_test_password.#{name}"
|
128
|
+
end
|
129
|
+
end
|
124
130
|
instance_exec(&rodauth_block)
|
125
131
|
end
|
126
132
|
app.route(&block)
|
@@ -220,7 +226,8 @@ class Minitest::HooksSpec
|
|
220
226
|
around(:all) do |&block|
|
221
227
|
DB.transaction(:rollback=>:always) do
|
222
228
|
hash = BCrypt::Password.create('0123456789', :cost=>BCrypt::Engine::MIN_COST)
|
223
|
-
|
229
|
+
table = ENV['RODAUTH_SEPARATE_SCHEMA'] ? Sequel[:rodauth_test_password][:account_password_hashes] : :account_password_hashes
|
230
|
+
DB[table].insert(:id=>DB[:accounts].insert(:email=>'foo@example.com', :status_id=>2, :ph=>hash), :password_hash=>hash)
|
224
231
|
super(&block)
|
225
232
|
end
|
226
233
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -240,6 +240,7 @@ extra_rdoc_files:
|
|
240
240
|
- doc/release_notes/1.9.0.txt
|
241
241
|
- doc/release_notes/1.10.0.txt
|
242
242
|
- doc/release_notes/1.11.0.txt
|
243
|
+
- doc/release_notes/1.12.0.txt
|
243
244
|
files:
|
244
245
|
- CHANGELOG
|
245
246
|
- MIT-LICENSE
|
@@ -270,6 +271,7 @@ files:
|
|
270
271
|
- doc/release_notes/1.1.0.txt
|
271
272
|
- doc/release_notes/1.10.0.txt
|
272
273
|
- doc/release_notes/1.11.0.txt
|
274
|
+
- doc/release_notes/1.12.0.txt
|
273
275
|
- doc/release_notes/1.2.0.txt
|
274
276
|
- doc/release_notes/1.3.0.txt
|
275
277
|
- doc/release_notes/1.4.0.txt
|
@@ -422,7 +424,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
422
424
|
version: '0'
|
423
425
|
requirements: []
|
424
426
|
rubyforge_project:
|
425
|
-
rubygems_version: 2.6.
|
427
|
+
rubygems_version: 2.6.13
|
426
428
|
signing_key:
|
427
429
|
specification_version: 4
|
428
430
|
summary: Authentication and Account Management Framework for Rack Applications
|