rodauth 0.9.1 → 0.10.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: c5d5ec10b4cd33cbc64f23f42bfb75e096982e09
4
- data.tar.gz: b8793e5c0881340a056c8188cd80f6a6c94c5191
3
+ metadata.gz: 4562cb5ac9001ed624c4a17b266e248bda416bfa
4
+ data.tar.gz: 83b071943791302efef9980bef8d04629ea26d8a
5
5
  SHA512:
6
- metadata.gz: f87e70dba05f2bf05c1879c0b1343740998335dddb544b3ce81a33e3c6478549d4e902e22e3c3a4867f165c2e623435ab7922bf572b88cddaf4a6ea21dc1ce92
7
- data.tar.gz: 3f3ff270fb76ce23e70da11b87365c18522571e79b3c546262a4da960dbac3c5dafaa881a3c103683c2336dc683b93f19bd788d21268de88c00d1f3d442b8e2e
6
+ metadata.gz: 46012c509f3e3fe2df2c27c56d7f0ecb6f950c6f7227faa056f8578f940908d20cd2d7757c5a6c63a387f7da066216eba6ab7e1553fd70d1a92882a8d2a2ced4
7
+ data.tar.gz: 88d313c1a7739bd54ac588e25046e90eed284502ce119c69862e20cc49028bcee2999e8c0ce93874bec4fefcdb2d92bc1de4183359b395cd9e7a8df8af272bde
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ === 0.10.0 (2016-02-17)
2
+
3
+ * Retrieve salt from database and compute hash client side, instead of computing hash on server (jeremyevans)
4
+
1
5
  === 0.9.1 (2015-08-13)
2
6
 
3
7
  * Don't use csrf plugin automatically (jeremyevans)
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2015 Jeremy Evans
1
+ Copyright (c) 2015-2016 Jeremy Evans
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
data/README.rdoc CHANGED
@@ -34,20 +34,21 @@ Bugs :: http://github.com/jeremyevans/rodauth/issues
34
34
 
35
35
  Passwords are hashed using bcrypt, and the password hashes are
36
36
  kept in a separate table from the accounts table, with a foreign key
37
- referencing the accounts table. A PostgreSQL function is added to
38
- check the password for a given application account matches.
37
+ referencing the accounts table. Two PostgreSQL functions are added,
38
+ one to retrieve the salt for a password, and the other to check
39
+ if a given password hash matches the password hash for the user.
39
40
 
40
41
  A separate database account owns the table containing the password
41
42
  hashes, which the application database account cannot access.
42
43
  The application database account has the ability to execute the
43
- function to check the password, but not the ability to access the
44
- password hashes directly, making it much more difficult for an
45
- attacker to access the password hashes even if they are able to
46
- exploit an SQL injection or remote code execution vulnerability
47
- in the application. Even if an attacker was able to exploit a
48
- vulnerability in the application, they would only be to check if
49
- a specific password matches for a given user, which is the same
50
- access an attacker would have anyway if they just tried to login.
44
+ functions to get the salt and check the password hash, but not the
45
+ ability to access the password hashes directly, making it much more
46
+ difficult for an attacker to access the password hashes even if they
47
+ are able to exploit an SQL injection or remote code execution
48
+ vulnerability in the application. Even if an attacker was able to
49
+ exploit a vulnerability in the application, the only additional
50
+ information they would have is the salt for the password, which
51
+ is much less sensitive than the entire password hash.
51
52
 
52
53
  While the application database account is not be able to read
53
54
  password hashes, it is still be able to insert password hashes,
@@ -56,15 +57,15 @@ additional security is not that painful.
56
57
 
57
58
  The reason for extra security in regards to password hashes stems from
58
59
  the fact that people tend to reuse passwords, so a compromise of one
59
- site can result in account access on other sites, making password hash
60
- storage of critical importance even if the other data stored is not
61
- that important.
60
+ database containing password hashes can result in account access on
61
+ other sites, making password hash storage of critical importance even
62
+ if the other data stored is not that important.
62
63
 
63
64
  If you are storing other important information in your database, you
64
65
  should consider using a similar approach in other areas (or all areas)
65
66
  of your application.
66
67
 
67
- Rodauth can still be used if you are using a more convential approach
68
+ Rodauth can still be used if you are using a more conventional approach
68
69
  of storing the password hash in a column in the same table, with
69
70
  a single configuration setting.
70
71
 
@@ -104,16 +105,14 @@ storage to the same level as other common authentication solutions.
104
105
  === Load extensions
105
106
 
106
107
  If you want to use the login features for Rodauth, you need to load the
107
- pgcrypto extension with the database superuser account, and load the
108
108
  citext extension if you want to support case insensitive logins.
109
109
 
110
110
  Example:
111
111
 
112
- echo "CREATE EXTENSION pgcrypto" | psql -U postgres $database_name
113
112
  echo "CREATE EXTENSION citext" | psql -U postgres $database_name
114
113
 
115
- Note that on Heroku, both of these extensions can be loaded using a
116
- standard database account.
114
+ Note that on Heroku, this extension can be loaded using a standard database
115
+ account.
117
116
 
118
117
  === Create database accounts
119
118
 
@@ -216,13 +215,27 @@ Second migration, run using the secondary database account:
216
215
  String :password_hash, :null=>false
217
216
  end
218
217
 
219
- # Function used to check if a password is valid. Takes the related account id
220
- # and unencrypted password, checks if password matches password hash.
218
+ # Function that returns salt for current password.
221
219
  run <<END
222
- CREATE OR REPLACE FUNCTION account_valid_password(account_id int8, password text) RETURNS boolean AS $$
220
+ CREATE OR REPLACE FUNCTION rodauth_get_salt(account_id int8) RETURNS text AS $$
221
+ DECLARE salt text;
222
+ BEGIN
223
+ SELECT substr(password_hash, 0, 30) INTO salt
224
+ FROM account_password_hashes
225
+ WHERE account_id = id;
226
+ RETURN salt;
227
+ END;
228
+ $$ LANGUAGE plpgsql
229
+ SECURITY DEFINER
230
+ SET search_path = public, pg_temp;
231
+ END
232
+
233
+ # Function that checks if password hash is valid for given user.
234
+ run <<END
235
+ CREATE OR REPLACE FUNCTION rodauth_valid_password_hash(account_id int8, hash text) RETURNS boolean AS $$
223
236
  DECLARE valid boolean;
224
237
  BEGIN
225
- SELECT password_hash = crypt($2, password_hash) INTO valid
238
+ SELECT password_hash = hash INTO valid
226
239
  FROM account_password_hashes
227
240
  WHERE account_id = id;
228
241
  RETURN valid;
@@ -235,14 +248,17 @@ Second migration, run using the secondary database account:
235
248
  # Restrict access to the password hash table
236
249
  app_user = get{Sequel.lit('current_user')}.sub(/_password\z/, '')
237
250
  run "REVOKE ALL ON account_password_hashes FROM public"
238
- run "REVOKE ALL ON FUNCTION account_valid_password(int8, text) FROM public"
251
+ run "REVOKE ALL ON FUNCTION rodauth_get_salt(int8) FROM public"
252
+ run "REVOKE ALL ON FUNCTION rodauth_valid_password_hash(int8, text) FROM public"
239
253
  run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{app_user}"
240
254
  run "GRANT SELECT(id) ON account_password_hashes TO #{app_user}"
241
- run "GRANT EXECUTE ON FUNCTION account_valid_password(int8, text) TO #{app_user}"
255
+ run "GRANT EXECUTE ON FUNCTION rodauth_get_salt(int8) TO #{app_user}"
256
+ run "GRANT EXECUTE ON FUNCTION rodauth_valid_password_hash(int8, text) TO #{app_user}"
242
257
  end
243
258
 
244
259
  down do
245
- run "DROP FUNCTION account_valid_password(int8, text)"
260
+ run "DROP FUNCTION rodauth_get_salt(int8)"
261
+ run "DROP FUNCTION rodauth_valid_password_hash(int8, text)"
246
262
  drop_table(:account_password_hashes)
247
263
  end
248
264
  end
@@ -344,8 +360,7 @@ route is handled:
344
360
  end
345
361
 
346
362
  By allowing every configuration method to take a block, Rodauth
347
- should be flexible enough to integrate into most legacy
348
- authentication systems.
363
+ should be flexible enough to integrate into most legacy systems.
349
364
 
350
365
  === Feature Documentation
351
366
 
@@ -466,6 +481,51 @@ use the following basic structure
466
481
  end
467
482
  end
468
483
 
484
+ == Upgrading from 0.9.x
485
+
486
+ To upgrade from 0.9.x to the current version, if you were using
487
+ the account_valid_password database function, you need to drop
488
+ it and add the two database functions listed in the migration
489
+ section above. You can add the following code to a migration to
490
+ accomplish that:
491
+
492
+ run "DROP FUNCTION account_valid_password(int8, text);"
493
+
494
+ run <<END
495
+ CREATE OR REPLACE FUNCTION rodauth_get_salt(account_id int8) RETURNS text AS $$
496
+ DECLARE salt text;
497
+ BEGIN
498
+ SELECT substr(password_hash, 0, 30) INTO salt
499
+ FROM account_password_hashes
500
+ WHERE account_id = id;
501
+ RETURN salt;
502
+ END;
503
+ $$ LANGUAGE plpgsql
504
+ SECURITY DEFINER
505
+ SET search_path = public, pg_temp;
506
+ END
507
+
508
+ run <<END
509
+ CREATE OR REPLACE FUNCTION rodauth_valid_password_hash(account_id int8, hash text) RETURNS boolean AS $$
510
+ DECLARE valid boolean;
511
+ BEGIN
512
+ SELECT password_hash = hash INTO valid
513
+ FROM account_password_hashes
514
+ WHERE account_id = id;
515
+ RETURN valid;
516
+ END;
517
+ $$ LANGUAGE plpgsql
518
+ SECURITY DEFINER
519
+ SET search_path = public, pg_temp;
520
+ END
521
+
522
+ # Restrict access to the password hash table
523
+ app_user = get{Sequel.lit('current_user')}.sub(/_password\z/, '')
524
+ run "REVOKE ALL ON FUNCTION rodauth_get_salt(int8) FROM public"
525
+ run "REVOKE ALL ON FUNCTION rodauth_valid_password_hash(int8, text) FROM public"
526
+ run "GRANT EXECUTE ON FUNCTION rodauth_get_salt(int8) TO #{app_user}"
527
+ run "GRANT EXECUTE ON FUNCTION rodauth_valid_password_hash(int8, text) TO #{app_user}"
528
+
469
529
  == Possible Future Directions
470
530
 
471
531
  * OmniAuth support. This is not something I plan to work on myself,
data/Rakefile CHANGED
@@ -71,7 +71,6 @@ task :db_setup do
71
71
  sh 'echo "CREATE USER rodauth_test PASSWORD \'rodauth_test\'" | psql -U postgres'
72
72
  sh 'echo "CREATE USER rodauth_test_password PASSWORD \'rodauth_test\'" | psql -U postgres'
73
73
  sh 'createdb -U postgres -O rodauth_test rodauth_test'
74
- sh 'echo "CREATE EXTENSION pgcrypto" | psql -U postgres rodauth_test'
75
74
  sh 'echo "CREATE EXTENSION citext" | psql -U postgres rodauth_test'
76
75
  require 'sequel'
77
76
  Sequel.extension :migration
@@ -64,11 +64,15 @@ class Roda
64
64
  end
65
65
 
66
66
  def password_match?(password)
67
+ require 'bcrypt'
67
68
  if account_password_hash_column
68
- require 'bcrypt'
69
69
  BCrypt::Password.new(account.send(account_password_hash_column)) == password
70
70
  else
71
- db.get{|db| db.account_valid_password(account.send(account_id), password)}
71
+ id = account.send(account_id)
72
+ if salt = db.get{rodauth_get_salt(id)}
73
+ hash = BCrypt::Engine.hash_secret(password, salt)
74
+ db.get{rodauth_valid_password_hash(id, hash)}
75
+ end
72
76
  end
73
77
  end
74
78
  end
@@ -6,13 +6,27 @@ Sequel.migration do
6
6
  String :password_hash, :null=>false
7
7
  end
8
8
 
9
- # Function used to check if a password is valid. Takes the related account id
10
- # and unencrypted password, checks if password matches password hash.
9
+ # Function that returns salt for current password.
11
10
  run <<END
12
- CREATE OR REPLACE FUNCTION account_valid_password(account_id int8, password text) RETURNS boolean AS $$
11
+ CREATE OR REPLACE FUNCTION rodauth_get_salt(account_id int8) RETURNS text AS $$
12
+ DECLARE salt text;
13
+ BEGIN
14
+ SELECT substr(password_hash, 0, 30) INTO salt
15
+ FROM account_password_hashes
16
+ WHERE account_id = id;
17
+ RETURN salt;
18
+ END;
19
+ $$ LANGUAGE plpgsql
20
+ SECURITY DEFINER
21
+ SET search_path = public, pg_temp;
22
+ END
23
+
24
+ # Function that checks if password hash is valid for given user.
25
+ run <<END
26
+ CREATE OR REPLACE FUNCTION rodauth_valid_password_hash(account_id int8, hash text) RETURNS boolean AS $$
13
27
  DECLARE valid boolean;
14
28
  BEGIN
15
- SELECT password_hash = crypt($2, password_hash) INTO valid
29
+ SELECT password_hash = hash INTO valid
16
30
  FROM account_password_hashes
17
31
  WHERE account_id = id;
18
32
  RETURN valid;
@@ -25,14 +39,17 @@ END
25
39
  # Restrict access to the password hash table
26
40
  app_user = get{Sequel.lit('current_user')}.sub(/_password\z/, '')
27
41
  run "REVOKE ALL ON account_password_hashes FROM public"
28
- run "REVOKE ALL ON FUNCTION account_valid_password(int8, text) FROM public"
42
+ run "REVOKE ALL ON FUNCTION rodauth_get_salt(int8) FROM public"
43
+ run "REVOKE ALL ON FUNCTION rodauth_valid_password_hash(int8, text) FROM public"
29
44
  run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{app_user}"
30
45
  run "GRANT SELECT(id) ON account_password_hashes TO #{app_user}"
31
- run "GRANT EXECUTE ON FUNCTION account_valid_password(int8, text) TO #{app_user}"
46
+ run "GRANT EXECUTE ON FUNCTION rodauth_get_salt(int8) TO #{app_user}"
47
+ run "GRANT EXECUTE ON FUNCTION rodauth_valid_password_hash(int8, text) TO #{app_user}"
32
48
  end
33
49
 
34
50
  down do
35
- run "DROP FUNCTION account_valid_password(int8, text)"
51
+ run "DROP FUNCTION rodauth_get_salt(int8)"
52
+ run "DROP FUNCTION rodauth_valid_password_hash(int8, text)"
36
53
  drop_table(:account_password_hashes)
37
54
  end
38
55
  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: 0.9.1
4
+ version: 0.10.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: 2015-08-13 00:00:00.000000000 Z
11
+ date: 2016-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -220,9 +220,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
220
220
  version: '0'
221
221
  requirements: []
222
222
  rubyforge_project:
223
- rubygems_version: 2.4.5
223
+ rubygems_version: 2.5.1
224
224
  signing_key:
225
225
  specification_version: 4
226
226
  summary: Authentication Framework for Roda/Sequel/PostgreSQL
227
227
  test_files: []
228
- has_rdoc: true