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