rodauth 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +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
|