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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f15b58f396d567f884cc2aaf40794e07110e2091
4
- data.tar.gz: 69a0eadaf802e8c82793c9c43084b0e4f442c8f7
3
+ metadata.gz: 888e341c90edf3af686821f3d64998da9fcdd5cb
4
+ data.tar.gz: 5f50386e3657dce0c24b81465a2e54ad8c81221a
5
5
  SHA512:
6
- metadata.gz: 7bcc3c3455093d1a1a21c358f99c4440a1a09ead7d34087a541490ab4e73acf1b7bea97ce0bbc2675392fbbae96b70ed9099ab63182355a647830d1877a143b5
7
- data.tar.gz: 03b37be1351549b1e8b5c6a8e3c102a26c946501b11282081ce92213c9ab169077edf1a75b920589a3a4d1d905732d6cb6ac91197f181305f9002791ca109f35
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 for a given account. The +ph+ account
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
- === Create database accounts
203
+ === Using non-default schema
170
204
 
171
- If you are currently running your application using the database superuser
172
- account, the first thing you need to do is to create the +app+ database
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
- You should also create the +ph+ database account which will handle access
177
- to the password hashes.
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
- Example for PostgreSQL:
214
+ You'll need to modify the code to load the extension to specify the schema:
180
215
 
181
- createuser -U postgres ${DATABASE_NAME}
182
- createuser -U postgres ${DATABASE_NAME}_password
216
+ psql -U postgres -c "CREATE EXTENSION citext SCHEMA ${DATABASE_NAME}" ${DATABASE_NAME}
183
217
 
184
- Note that if the database superuser account owns all of the items in the
185
- database, you'll need to change the ownership to the database account you
186
- just created. See https://gist.github.com/jeremyevans/8483320
187
- for a way to do that.
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{Sequel.lit('current_user')} + '_password'
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{Sequel.lit('current_user')}.sub(/_password@/, '@')
416
+ get(Sequel.lit('current_user')).sub(/_password@/, '@')
360
417
  else
361
- get{DB_NAME{}}
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{Sequel.lit('current_user')}.sub(/_password\z/, '')
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{Sequel.lit('current_user')}.sub(/_password@/, '@')
421
- db_name = get{database{}}
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{DB_NAME{}}
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{Sequel.lit('current_user')}.sub(/_password\z/, '')
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{Sequel.lit('current_user')}.sub(/_password@/, '@')
454
- db_name = get{database{}}
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{DB_NAME{}}
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
- require 'koala'
829
- plugin :rodauth do
885
+ require 'koala'
886
+ plugin :rodauth do
830
887
  enable :login, :logout, :jwt
831
888
 
832
889
  require_bcrypt? false
@@ -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
@@ -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 throw_basic_auth_error(*args)
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
- ds.where(Sequel::CURRENT_TIMESTAMP > reset_password_deadline_column).delete
150
- if ds.empty?
151
- if e = raised_uniqueness_violation{ds.insert(reset_password_key_insert_hash)}
152
- # If inserting into the reset password table causes a violation, we can pull the
153
- # existing reset password key from the table, or reraise.
154
- raise e unless @reset_password_key_value = get_password_reset_key(account_id)
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).get(reset_password_key_column)
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
@@ -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 = public, pg_temp;
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 = public, pg_temp;
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 rodauth_get_salt(int8)"
109
- db.run "DROP FUNCTION rodauth_valid_password_hash(int8, text)"
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 rodauth_get_salt"
112
- db.run "DROP FUNCTION rodauth_valid_password_hash"
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
- 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
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
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- VERSION = '1.11.0'.freeze
4
+ VERSION = '1.12.0'.freeze
5
5
 
6
6
  def self.version
7
7
  VERSION
@@ -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[:account_previous_password_hashes].get{count(:id)}.must_equal 7
51
+ DB[table].get{count(:id)}.must_equal 7
47
52
  visit '/close-account'
48
53
  click_button 'Close Account'
49
- DB[:account_previous_password_hashes].get{count(:id)}.must_equal 0
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
@@ -117,13 +117,13 @@ Sequel.migration do
117
117
 
118
118
  case database_type
119
119
  when :postgres
120
- user = get{Sequel.lit('current_user')} + '_password'
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{Sequel.lit('current_user')}.sub(/_password@/, '@')
124
+ get(Sequel.lit('current_user')).sub(/_password@/, '@')
125
125
  else
126
- get{DB_NAME{}}
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{Sequel.lit('current_user')}.sub(/_password\z/, '')
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{Sequel.lit('current_user')}.sub(/_password@/, '@')
22
- db_name = get{database{}}
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{DB_NAME{}}
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{Sequel.lit('current_user')}.sub(/_password\z/, '')
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{Sequel.lit('current_user')}.sub(/_password@/, '@')
55
- db_name = get{database{}}
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{DB_NAME{}}
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}"
@@ -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[:accs].select(:id))
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 'accs1'
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
- DB[:account_password_hashes].insert(:id=>DB[:accounts].insert(:email=>'foo@example.com', :status_id=>2, :ph=>hash), :password_hash=>hash)
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.11.0
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-04-24 00:00:00.000000000 Z
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.11
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