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 +4 -4
- data/CHANGELOG +4 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +87 -27
- data/Rakefile +0 -1
- data/lib/roda/plugins/rodauth/login.rb +6 -2
- data/spec/migrate_password/001_tables.rb +24 -7
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4562cb5ac9001ed624c4a17b266e248bda416bfa
|
4
|
+
data.tar.gz: 83b071943791302efef9980bef8d04629ea26d8a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 46012c509f3e3fe2df2c27c56d7f0ecb6f950c6f7227faa056f8578f940908d20cd2d7757c5a6c63a387f7da066216eba6ab7e1553fd70d1a92882a8d2a2ced4
|
7
|
+
data.tar.gz: 88d313c1a7739bd54ac588e25046e90eed284502ce119c69862e20cc49028bcee2999e8c0ce93874bec4fefcdb2d92bc1de4183359b395cd9e7a8df8af272bde
|
data/CHANGELOG
CHANGED
data/MIT-LICENSE
CHANGED
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.
|
38
|
-
|
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
|
-
|
44
|
-
password hashes directly, making it much more
|
45
|
-
attacker to access the password hashes even if they
|
46
|
-
exploit an SQL injection or remote code execution
|
47
|
-
in the application. Even if an attacker was able to
|
48
|
-
vulnerability in the application,
|
49
|
-
|
50
|
-
|
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
|
-
|
60
|
-
storage of critical importance even
|
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
|
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,
|
116
|
-
|
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
|
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
|
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 =
|
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
|
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
|
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
|
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
|
-
|
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
|
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
|
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 =
|
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
|
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
|
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
|
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.
|
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:
|
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.
|
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
|