rodauth 0.10.0 → 1.0.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 +146 -0
- data/README.rdoc +644 -220
- data/Rakefile +99 -11
- data/doc/account_expiration.rdoc +55 -0
- data/doc/base.rdoc +104 -0
- data/doc/change_login.rdoc +29 -0
- data/doc/change_password.rdoc +26 -0
- data/doc/close_account.rdoc +31 -0
- data/doc/confirm_password.rdoc +22 -0
- data/doc/create_account.rdoc +34 -0
- data/doc/disallow_password_reuse.rdoc +37 -0
- data/doc/email_base.rdoc +19 -0
- data/doc/jwt.rdoc +35 -0
- data/doc/lockout.rdoc +83 -0
- data/doc/login.rdoc +27 -0
- data/doc/login_password_requirements_base.rdoc +50 -0
- data/doc/logout.rdoc +21 -0
- data/doc/otp.rdoc +100 -0
- data/doc/password_complexity.rdoc +50 -0
- data/doc/password_expiration.rdoc +52 -0
- data/doc/password_grace_period.rdoc +10 -0
- data/doc/recovery_codes.rdoc +60 -0
- data/doc/release_notes/1.0.0.txt +443 -0
- data/doc/remember.rdoc +82 -0
- data/doc/reset_password.rdoc +70 -0
- data/doc/session_expiration.rdoc +27 -0
- data/doc/single_session.rdoc +43 -0
- data/doc/sms_codes.rdoc +119 -0
- data/doc/two_factor_base.rdoc +27 -0
- data/doc/verify_account.rdoc +70 -0
- data/doc/verify_account_grace_period.rdoc +15 -0
- data/doc/verify_change_login.rdoc +9 -0
- data/lib/roda/plugins/rodauth.rb +3 -262
- data/lib/rodauth.rb +260 -0
- data/lib/rodauth/features/account_expiration.rb +108 -0
- data/lib/rodauth/features/base.rb +479 -0
- data/lib/rodauth/features/change_login.rb +77 -0
- data/lib/rodauth/features/change_password.rb +66 -0
- data/lib/rodauth/features/close_account.rb +82 -0
- data/lib/rodauth/features/confirm_password.rb +51 -0
- data/lib/rodauth/features/create_account.rb +128 -0
- data/lib/rodauth/features/disallow_password_reuse.rb +82 -0
- data/lib/rodauth/features/email_base.rb +63 -0
- data/lib/rodauth/features/jwt.rb +151 -0
- data/lib/rodauth/features/lockout.rb +262 -0
- data/lib/rodauth/features/login.rb +61 -0
- data/lib/rodauth/features/login_password_requirements_base.rb +123 -0
- data/lib/rodauth/features/logout.rb +37 -0
- data/lib/rodauth/features/otp.rb +338 -0
- data/lib/rodauth/features/password_complexity.rb +89 -0
- data/lib/rodauth/features/password_expiration.rb +111 -0
- data/lib/rodauth/features/password_grace_period.rb +46 -0
- data/lib/rodauth/features/recovery_codes.rb +240 -0
- data/lib/rodauth/features/remember.rb +200 -0
- data/lib/rodauth/features/reset_password.rb +207 -0
- data/lib/rodauth/features/session_expiration.rb +55 -0
- data/lib/rodauth/features/single_session.rb +87 -0
- data/lib/rodauth/features/sms_codes.rb +498 -0
- data/lib/rodauth/features/two_factor_base.rb +135 -0
- data/lib/rodauth/features/verify_account.rb +232 -0
- data/lib/rodauth/features/verify_account_grace_period.rb +76 -0
- data/lib/rodauth/features/verify_change_login.rb +20 -0
- data/lib/rodauth/migrations.rb +130 -0
- data/lib/rodauth/version.rb +9 -0
- data/spec/account_expiration_spec.rb +90 -0
- data/spec/all.rb +1 -0
- data/spec/change_login_spec.rb +149 -0
- data/spec/change_password_spec.rb +177 -0
- data/spec/close_account_spec.rb +162 -0
- data/spec/confirm_password_spec.rb +70 -0
- data/spec/create_account_spec.rb +127 -0
- data/spec/disallow_password_reuse_spec.rb +84 -0
- data/spec/lockout_spec.rb +228 -0
- data/spec/login_spec.rb +188 -0
- data/spec/migrate/001_tables.rb +103 -16
- data/spec/migrate/002_account_password_hash_column.rb +11 -0
- data/spec/migrate_password/001_tables.rb +60 -42
- data/spec/migrate_travis/001_tables.rb +116 -0
- data/spec/password_complexity_spec.rb +108 -0
- data/spec/password_expiration_spec.rb +243 -0
- data/spec/password_grace_period_spec.rb +93 -0
- data/spec/remember_spec.rb +424 -0
- data/spec/reset_password_spec.rb +185 -0
- data/spec/rodauth_spec.rb +57 -980
- data/spec/session_expiration_spec.rb +58 -0
- data/spec/single_session_spec.rb +107 -0
- data/spec/spec_helper.rb +202 -0
- data/spec/two_factor_spec.rb +1310 -0
- data/spec/verify_account_grace_period_spec.rb +135 -0
- data/spec/verify_account_spec.rb +142 -0
- data/spec/verify_change_login_spec.rb +46 -0
- data/spec/views/login.str +2 -2
- data/templates/add-recovery-codes.str +2 -0
- data/templates/button.str +5 -0
- data/templates/change-login.str +5 -18
- data/templates/change-password.str +6 -14
- data/templates/close-account.str +3 -6
- data/templates/confirm-password.str +4 -14
- data/templates/create-account.str +6 -30
- data/templates/login-confirm-field.str +6 -0
- data/templates/login-field.str +6 -0
- data/templates/login.str +5 -19
- data/templates/logout.str +2 -6
- data/templates/otp-auth-code-field.str +6 -0
- data/templates/otp-auth.str +8 -0
- data/templates/otp-disable.str +6 -0
- data/templates/otp-setup.str +21 -0
- data/templates/password-confirm-field.str +6 -0
- data/templates/password-field.str +6 -0
- data/templates/recovery-auth.str +12 -0
- data/templates/recovery-codes.str +6 -0
- data/templates/remember.str +8 -12
- data/templates/reset-password-request.str +2 -2
- data/templates/reset-password.str +4 -18
- data/templates/sms-auth.str +6 -0
- data/templates/sms-code-field.str +6 -0
- data/templates/sms-confirm.str +7 -0
- data/templates/sms-disable.str +7 -0
- data/templates/sms-request.str +5 -0
- data/templates/sms-setup.str +12 -0
- data/templates/unlock-account-request.str +3 -7
- data/templates/unlock-account.str +4 -7
- data/templates/verify-account-resend.str +2 -2
- data/templates/verify-account.str +2 -6
- metadata +191 -29
- data/lib/roda/plugins/rodauth/base.rb +0 -428
- data/lib/roda/plugins/rodauth/change_login.rb +0 -48
- data/lib/roda/plugins/rodauth/change_password.rb +0 -42
- data/lib/roda/plugins/rodauth/close_account.rb +0 -42
- data/lib/roda/plugins/rodauth/create_account.rb +0 -92
- data/lib/roda/plugins/rodauth/lockout.rb +0 -292
- data/lib/roda/plugins/rodauth/login.rb +0 -81
- data/lib/roda/plugins/rodauth/logout.rb +0 -36
- data/lib/roda/plugins/rodauth/remember.rb +0 -226
- data/lib/roda/plugins/rodauth/reset_password.rb +0 -205
- data/lib/roda/plugins/rodauth/verify_account.rb +0 -228
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9f7b621217958c77c148ee85c271e7e39548b02f
|
|
4
|
+
data.tar.gz: f4fa75ad99266693763134533d5f7f0dc90de8ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9ce46e8a0b1d1b5f5a2994d4ab3d70503fd5da9f968fa0dc1cbd938cd9ce2d327c8dd427fd09a4c8242b2dbdb8be4f428c40277008352021c91b97ad438689fb
|
|
7
|
+
data.tar.gz: 6535d95d9d3573f5c7190b990d448f983757d674a4df73662de2567ca197fcc63867773a86a4f3617edc26c4a2cd54f3bb741ba3153f503a8f69094c8a89a3d3
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,149 @@
|
|
|
1
|
+
=== HEAD
|
|
2
|
+
|
|
3
|
+
* Remove invalid remember cookies to prevent unnecessary future database checks (jeremyevans)
|
|
4
|
+
|
|
5
|
+
* Extend remember deadline in cookie in addition to database (jeremyevans)
|
|
6
|
+
|
|
7
|
+
* Make tokens work with string account ids (jeremyevans)
|
|
8
|
+
|
|
9
|
+
* Add verify_change_login feature for requiring account reverification on login changes (jeremyevans)
|
|
10
|
+
|
|
11
|
+
* Set correct cookie expiration in the remember feature (jeremyevans)
|
|
12
|
+
|
|
13
|
+
* Split confirm_password feature from remember feature (jeremyevans)
|
|
14
|
+
|
|
15
|
+
* Add verify_account_grace_period feature, for allowing logins into unverified accounts for a certain period after creation (jeremyevans)
|
|
16
|
+
|
|
17
|
+
* Move login/password requirements settings to login password requirements base feature (jeremyevans)
|
|
18
|
+
|
|
19
|
+
* Add session_expiration feature, expiring sessions based on inactivity and max lifetime checks (jeremyevans)
|
|
20
|
+
|
|
21
|
+
* Add password_grace_period feature, for not requiring password entry if password was recently entered (jeremyevans)
|
|
22
|
+
|
|
23
|
+
* Make create/verify account autologin true by default (jeremyevans)
|
|
24
|
+
|
|
25
|
+
* Optimize routing using a hash table, disallow per-request routes (jeremyevans)
|
|
26
|
+
|
|
27
|
+
* Add ability to turn off login/password confirmations (jeremyevans)
|
|
28
|
+
|
|
29
|
+
* Don't allow changing login to the same as the current login (jeremyevans)
|
|
30
|
+
|
|
31
|
+
* Only allow requesting account unlocks if the account is current locked out (jeremyevans)
|
|
32
|
+
|
|
33
|
+
* Use separate routes for unlock account/reset password/verify account requests (jeremyevans)
|
|
34
|
+
|
|
35
|
+
* Use separate routes for confirming passwords and changing remember settings (jeremyevans)
|
|
36
|
+
|
|
37
|
+
* Add JWT feature for JSON API support using JWT tokens (jeremyevans)
|
|
38
|
+
|
|
39
|
+
* Add account_select configuration option for setting which columns to select from accounts_table (jeremyevans)
|
|
40
|
+
|
|
41
|
+
* Execute get_block and post_block in the Rodauth::Auth instance scope (jeremyevans)
|
|
42
|
+
|
|
43
|
+
* Store field errors in the rodauth object instead of instance variables in the Roda scope (jeremyevans)
|
|
44
|
+
|
|
45
|
+
* Add rodauth.redirect to abstract redirection code (jeremyevans)
|
|
46
|
+
|
|
47
|
+
* Only use flash notices for successful requests, other requests that redirect now use an error flash (jeremyevans)
|
|
48
|
+
|
|
49
|
+
* The before_* configuration methods now run directly before making the related database changes (jeremyevans)
|
|
50
|
+
|
|
51
|
+
* Before hooks run before routes now use before_*_route instead of before_* configuration methods (jeremyevans)
|
|
52
|
+
|
|
53
|
+
* Add token_separator configuration method to replace the default of _ (jeremyevans)
|
|
54
|
+
|
|
55
|
+
* Rename account_id_value to account_id (jeremyevans)
|
|
56
|
+
|
|
57
|
+
* Rename account_id to account_id_column and account_session_id to account_session_column (jeremyevans)
|
|
58
|
+
|
|
59
|
+
* Make skip_status_checks? default to true unless loading verify_account or close_account features (jeremyevans)
|
|
60
|
+
|
|
61
|
+
* Replace account_model with accounts_table and db, removing use of Sequel models (jeremyevans)
|
|
62
|
+
|
|
63
|
+
* Extract shared email-related code into email_base feature (jeremyevans)
|
|
64
|
+
|
|
65
|
+
* Add auth_class_eval to configuration block for adding custom methods (jeremyevans)
|
|
66
|
+
|
|
67
|
+
* Add configuration_eval to feature definitions for adding custom configuration methods (jeremyevans)
|
|
68
|
+
|
|
69
|
+
* Allow close_account feature to optionally delete accounts (jeremyevans)
|
|
70
|
+
|
|
71
|
+
* Make close_account feature work when skipping status checks or when using account_password_hash_column (jeremyevans)
|
|
72
|
+
|
|
73
|
+
* Add sms_codes feature, for codes received via SMS that can be used if TOTP authentication is not available (jeremyevans)
|
|
74
|
+
|
|
75
|
+
* Attempt to handle unique constraint violations raised in race conditions where possible (jeremyevans)
|
|
76
|
+
|
|
77
|
+
* Add _before and _after internal methods, make ununderscored methods only for users (jeremyevans)
|
|
78
|
+
|
|
79
|
+
* Add single_session feature, for only allowing a single active session per account (jeremyevans)
|
|
80
|
+
|
|
81
|
+
* Add account_expiration feature, for disallowing access to accounts after an amount of time since last login/activity (jeremyevans)
|
|
82
|
+
|
|
83
|
+
* Check account status in rodauth.load_memory in remember plugin (jeremyevans)
|
|
84
|
+
|
|
85
|
+
* Use csrf plugin automatically, depend on Roda >=2.6.0 (jeremyevans)
|
|
86
|
+
|
|
87
|
+
* Make bcrypt and mail development dependencies instead of runtime dependencies in the gem (jeremyevans)
|
|
88
|
+
|
|
89
|
+
* Add password_expiration feature, requiring users to change their password after a given amount of time (jeremyevans)
|
|
90
|
+
|
|
91
|
+
* Add disallow_password_reuse feature, checking that a new password doesn't match previous passwords (jeremyevans)
|
|
92
|
+
|
|
93
|
+
* Add password_complexity feature, allowing more sophisticated password complexity checks (jeremyevans)
|
|
94
|
+
|
|
95
|
+
* Add rodauth.remember_param and .remember_confirm_param for overriding parameter names (jeremyevans)
|
|
96
|
+
|
|
97
|
+
* Check that new password is not the same as existing password in change password and reset password features (jeremyevans)
|
|
98
|
+
|
|
99
|
+
* Add rodauth.login_meets_requirements? for checking if a login is valid, by default a valid email address (jeremyevans)
|
|
100
|
+
|
|
101
|
+
* Allow unlock account to optionally require the user's current password (jeremyevans)
|
|
102
|
+
|
|
103
|
+
* Add support for running on Microsoft SQL Server with database functions for authentication (jeremyevans)
|
|
104
|
+
|
|
105
|
+
* Make change password, change login, and close account require the user's current password by default (jeremyevans)
|
|
106
|
+
|
|
107
|
+
* Add rodauth.csrf_tag to make it easy to replace the CSRF tag implementation (jeremyevans)
|
|
108
|
+
|
|
109
|
+
* Switch unlock_account_autologin? to be true by default (jeremyevans)
|
|
110
|
+
|
|
111
|
+
* Add rodauth.authenticated? and .require_authentication (jeremyevans)
|
|
112
|
+
|
|
113
|
+
* Add recovery_codes feature, for single use codes that can be used if TOTP authentication is not available (jeremyevans)
|
|
114
|
+
|
|
115
|
+
* Add otp feature, for 2 factor authentication via TOTP (jeremyevans)
|
|
116
|
+
|
|
117
|
+
* Add support for running on MySQL with database functions for authentication (jeremyevans)
|
|
118
|
+
|
|
119
|
+
* Add *_interval and set_deadline_values? methods for setting deadline intervals on a per-request basis (jeremyevans)
|
|
120
|
+
|
|
121
|
+
* Add remember_deadline_column method for overriding the column used for storing the deadline (jeremyevans)
|
|
122
|
+
|
|
123
|
+
* Add rodauth/migrations file for DRYing up the database function creation (jeremyevans)
|
|
124
|
+
|
|
125
|
+
* Add Rodauth.version for getting the version (jeremyevans)
|
|
126
|
+
|
|
127
|
+
* External features should now be requirable via rodauth/features/feature_name instead of roda/plugins/rodauth/feature_name (jeremyevans)
|
|
128
|
+
|
|
129
|
+
* Make Rodauth top level module instead of under Roda::RodaPlugins (jeremyevans)
|
|
130
|
+
|
|
131
|
+
* Require mail at configure time instead of run time if using a feature that sends email, use require_mail? false to disable (jeremyevans)
|
|
132
|
+
|
|
133
|
+
* Require bcrypt at configure time instead of run time, use require_bcrypt? false to disable (jeremyevans)
|
|
134
|
+
|
|
135
|
+
* Always require securerandom (jeremyevans)
|
|
136
|
+
|
|
137
|
+
* Make remember, password reset, and lockout features work on non-PostgreSQL databases (jeremyevans)
|
|
138
|
+
|
|
139
|
+
* Support authentication without database functions when password hashes are stored in separate table (jeremyevans)
|
|
140
|
+
|
|
141
|
+
* Remove overriding of route/get/post blocks (jeremyevans)
|
|
142
|
+
|
|
143
|
+
* Make lockout feature work on databases not supporting UPDATE RETURNING (jeremyevans)
|
|
144
|
+
|
|
145
|
+
* Add timing safe comparison of tokens (jeremyevans)
|
|
146
|
+
|
|
1
147
|
=== 0.10.0 (2016-02-17)
|
|
2
148
|
|
|
3
149
|
* Retrieve salt from database and compute hash client side, instead of computing hash on server (jeremyevans)
|
data/README.rdoc
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
= Rodauth
|
|
2
2
|
|
|
3
|
-
Rodauth is an authentication
|
|
3
|
+
Rodauth is an authentication and account management framework for
|
|
4
|
+
rack applications. It's built using Roda and Sequel, but it can
|
|
5
|
+
be used with other web frameworks, database libraries, and databases.
|
|
6
|
+
When used with PostgreSQL, MySQL, and Microsoft SQL Server in the
|
|
7
|
+
default configuration, it offers additional security for password
|
|
8
|
+
hashes by protecting access via database functions.
|
|
4
9
|
|
|
5
10
|
== Design Goals
|
|
6
11
|
|
|
@@ -18,57 +23,100 @@ Rodauth is an authentication framework using Roda, Sequel, and PostgreSQL.
|
|
|
18
23
|
* Create Account
|
|
19
24
|
* Close Account
|
|
20
25
|
* Verify Account
|
|
26
|
+
* Confirm Password
|
|
21
27
|
* Remember (Autologin via token)
|
|
22
28
|
* Lockout (Bruteforce protection)
|
|
29
|
+
* OTP (2 factor authentication via TOTP)
|
|
30
|
+
* Recovery Codes (2 factor authentication via backup codes)
|
|
31
|
+
* SMS Codes (2 factor authentication via SMS)
|
|
32
|
+
* Verify Change Login (Reverify accounts after login changes)
|
|
33
|
+
* Verify Account Grace Period (Don't require verification before login)
|
|
34
|
+
* Password Grace Period (Don't require password entry if recently entered)
|
|
35
|
+
* Password Complexity (More sophisticated checks)
|
|
36
|
+
* Disallow Password Reuse
|
|
37
|
+
* Password Expiration
|
|
38
|
+
* Account Expiration
|
|
39
|
+
* Session Expiration
|
|
40
|
+
* Single Session (Only one active session per account)
|
|
41
|
+
* JWT (JSON API support for all other features)
|
|
23
42
|
|
|
24
43
|
== Resources
|
|
25
44
|
|
|
26
|
-
|
|
45
|
+
Website :: http://rodauth.jeremyevans.net
|
|
27
46
|
Demo Site :: http://rodauth-demo.jeremyevans.net
|
|
28
47
|
Source :: http://github.com/jeremyevans/rodauth
|
|
29
48
|
Bugs :: http://github.com/jeremyevans/rodauth/issues
|
|
49
|
+
Google Group :: https://groups.google.com/forum/#!forum/rodauth
|
|
50
|
+
IRC :: irc://chat.freenode.net/#rodauth
|
|
51
|
+
|
|
52
|
+
== Dependencies
|
|
53
|
+
|
|
54
|
+
There are some dependencies that Rodauth uses by default, but are
|
|
55
|
+
development dependencies instead of runtime dependencies in the
|
|
56
|
+
gem as it is possible to run without them:
|
|
57
|
+
|
|
58
|
+
tilt, rack_csrf :: Used by all features unless in JSON API only mode.
|
|
59
|
+
bcrypt :: Used by default for password matching, can be skipped
|
|
60
|
+
if password_match? is overridden for custom authentication.
|
|
61
|
+
mail :: Used by default for mailing in the reset password, verify
|
|
62
|
+
account, and lockout features.
|
|
63
|
+
rotp, rqrcode :: Used by the otp feature
|
|
64
|
+
jwt :: Used by the jwt feature
|
|
30
65
|
|
|
31
66
|
== Security
|
|
32
67
|
|
|
33
|
-
===
|
|
68
|
+
=== Password Hash Access Via Database Functions
|
|
69
|
+
|
|
70
|
+
By default on PostgreSQL, MySQL, and Microsoft SQL Server, Rodauth
|
|
71
|
+
uses database functions to access password hashes, with the user
|
|
72
|
+
running the application unable to get direct access to password
|
|
73
|
+
hashes. This reduces the risk of an attacker being able to access
|
|
74
|
+
password hashes and use them to attack other sites.
|
|
75
|
+
|
|
76
|
+
The rest of this section describes this feature in more detail, but
|
|
77
|
+
note that Rodauth does not require this feature be used and works
|
|
78
|
+
correctly without it. There may be cases where you cannot use
|
|
79
|
+
this feature, such as when using a different database or when you
|
|
80
|
+
do not have full control over the database you are using.
|
|
34
81
|
|
|
35
82
|
Passwords are hashed using bcrypt, and the password hashes are
|
|
36
83
|
kept in a separate table from the accounts table, with a foreign key
|
|
37
|
-
referencing the accounts table. Two
|
|
84
|
+
referencing the accounts table. Two database functions are added,
|
|
38
85
|
one to retrieve the salt for a password, and the other to check
|
|
39
86
|
if a given password hash matches the password hash for the user.
|
|
40
87
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
While the
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
88
|
+
Two database accounts are used. The first is the account that the
|
|
89
|
+
application uses, which is referred to as the +app+ account. The +app+
|
|
90
|
+
account does not have access to read the password hashes. The other
|
|
91
|
+
account handles password hashes and is referred to as the +ph+
|
|
92
|
+
account. The +ph+ account sets up the database functions that can
|
|
93
|
+
retrieve the salt for a given account's password, and check if a
|
|
94
|
+
password hash matches for for a given account. The +ph+ account
|
|
95
|
+
sets these functions up so that the +app+ account can execute the
|
|
96
|
+
functions using the +ph+ account's permissions. This allows the
|
|
97
|
+
+app+ account to check passwords without having access to read
|
|
98
|
+
password hashes.
|
|
99
|
+
|
|
100
|
+
While the +app+ account is not be able to read password hashes, it
|
|
101
|
+
is still be able to insert password hashes, update passwords hashes,
|
|
102
|
+
and delete password hashes, so the additional security is not that
|
|
103
|
+
painful.
|
|
104
|
+
|
|
105
|
+
By disallowing the +app+ account access to the password hashes,
|
|
106
|
+
it is much more difficult for an attacker to access the password
|
|
107
|
+
hashes, even if they are able to exploit an SQL injection or remote
|
|
108
|
+
code execution vulnerability in the application.
|
|
57
109
|
|
|
58
110
|
The reason for extra security in regards to password hashes stems from
|
|
59
|
-
the fact that people tend to
|
|
60
|
-
database containing password hashes can result
|
|
61
|
-
other sites, making password hash storage of
|
|
62
|
-
if the other data stored is not that important.
|
|
111
|
+
the fact that people tend to choose poor passwords and reuse passwords,
|
|
112
|
+
so a compromise of one database containing password hashes can result
|
|
113
|
+
in account access on other sites, making password hash storage of
|
|
114
|
+
critical importance even if the other data stored is not that important.
|
|
63
115
|
|
|
64
|
-
If you are storing other
|
|
116
|
+
If you are storing other sensitive information in your database, you
|
|
65
117
|
should consider using a similar approach in other areas (or all areas)
|
|
66
118
|
of your application.
|
|
67
119
|
|
|
68
|
-
Rodauth can still be used if you are using a more conventional approach
|
|
69
|
-
of storing the password hash in a column in the same table, with
|
|
70
|
-
a single configuration setting.
|
|
71
|
-
|
|
72
120
|
=== Tokens
|
|
73
121
|
|
|
74
122
|
Account verification, password resets, remember, and lockout tokens
|
|
@@ -79,18 +127,17 @@ for a single account, instead of being able to bruteforce tokens for
|
|
|
79
127
|
all accounts at once (which would be possible if the token was just a
|
|
80
128
|
random string).
|
|
81
129
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
of requests in order to make bruteforcing easier.
|
|
130
|
+
Additionally, all comparisons of tokens use a timing-safe comparison
|
|
131
|
+
function to reduce the risk of timing attacks.
|
|
85
132
|
|
|
86
|
-
== Database Setup
|
|
133
|
+
== PostgreSQL Database Setup
|
|
87
134
|
|
|
88
|
-
In order to get full advantages of Rodauth's security design,
|
|
89
|
-
database accounts are involved:
|
|
135
|
+
In order to get full advantages of Rodauth's security design on PostgreSQL,
|
|
136
|
+
multiple database accounts are involved:
|
|
90
137
|
|
|
91
|
-
1
|
|
92
|
-
2
|
|
93
|
-
3
|
|
138
|
+
1. database superuser account (usually postgres)
|
|
139
|
+
2. +app+ account (same name as application)
|
|
140
|
+
3. +ph+ account (application name with +_password+ appended)
|
|
94
141
|
|
|
95
142
|
The database superuser account is used to load extensions related to the
|
|
96
143
|
database. The application should never be run using the database
|
|
@@ -109,42 +156,81 @@ citext extension if you want to support case insensitive logins.
|
|
|
109
156
|
|
|
110
157
|
Example:
|
|
111
158
|
|
|
112
|
-
|
|
159
|
+
psql -U postgres -c "CREATE EXTENSION citext" ${DATABASE_NAME}
|
|
113
160
|
|
|
114
161
|
Note that on Heroku, this extension can be loaded using a standard database
|
|
115
|
-
account.
|
|
162
|
+
account. If you don't want to support case sensitive logins, you don't
|
|
163
|
+
need to use the PostgreSQL citext extension. Just remember to modify the
|
|
164
|
+
migration below to use +String+ instead of +citext+ for the email.
|
|
116
165
|
|
|
117
166
|
=== Create database accounts
|
|
118
167
|
|
|
119
168
|
If you are currently running your application using the database superuser
|
|
120
|
-
account, the first thing you need to do is to create
|
|
121
|
-
|
|
169
|
+
account, the first thing you need to do is to create the +app+ database
|
|
170
|
+
account. It's often best to name this account the same as the
|
|
122
171
|
database name.
|
|
123
172
|
|
|
124
|
-
You should also create
|
|
125
|
-
|
|
173
|
+
You should also create the +ph+ database account which will handle access
|
|
174
|
+
to the password hashes.
|
|
126
175
|
|
|
127
|
-
Example:
|
|
176
|
+
Example for PostgreSQL:
|
|
128
177
|
|
|
129
|
-
createuser -U postgres $
|
|
130
|
-
createuser -U postgres $
|
|
178
|
+
createuser -U postgres ${DATABASE_NAME}
|
|
179
|
+
createuser -U postgres ${DATABASE_NAME}_password
|
|
131
180
|
|
|
132
181
|
Note that if the database superuser account owns all of the items in the
|
|
133
182
|
database, you'll need to change the ownership to the database account you
|
|
134
183
|
just created. See https://gist.github.com/jeremyevans/8483320
|
|
135
184
|
for a way to do that.
|
|
136
185
|
|
|
137
|
-
|
|
186
|
+
== MySQL Database Setup
|
|
187
|
+
|
|
188
|
+
MySQL does not have the concept of object owners, and MySQL's GRANT/REVOKE
|
|
189
|
+
support is much more limited than PostgreSQL's. When using MySQL, it is
|
|
190
|
+
recommended to GRANT the +ph+ account ALL privileges on the database,
|
|
191
|
+
including the ability to GRANT permissions to the +app+ account:
|
|
192
|
+
|
|
193
|
+
CREATE USER '${DATABASE_NAME}'@'localhost' IDENTIFIED BY '${PASSWORD}';
|
|
194
|
+
CREATE USER '${DATABASE_NAME}_password'@'localhost' IDENTIFIED BY '${OTHER_PASSWORD}';
|
|
195
|
+
GRANT ALL ON ${DATABASE_NAME}.* TO '${DATABASE_NAME}_password'@'localhost' WITH GRANT OPTION;
|
|
196
|
+
|
|
197
|
+
You should run all migrations as the +ph+ account, and GRANT specific access
|
|
198
|
+
to the +app+ account as needed.
|
|
199
|
+
|
|
200
|
+
Adding the database functions on MySQL may require setting the
|
|
201
|
+
<tt>log_bin_trust_function_creators=1</tt> setting in the MySQL configuration.
|
|
202
|
+
|
|
203
|
+
== Microsoft SQL Server Database Setup
|
|
204
|
+
|
|
205
|
+
Microsoft SQL Server has a concept of database owners, but similar to MySQL
|
|
206
|
+
usage it's recommended to use the +ph+ account as the superuser for the
|
|
207
|
+
database, and have it GRANT permissions to the +app+ account:
|
|
208
|
+
|
|
209
|
+
CREATE LOGIN rodauth_test WITH PASSWORD = 'rodauth_test';
|
|
210
|
+
CREATE LOGIN rodauth_test_password WITH PASSWORD = 'rodauth_test';
|
|
211
|
+
CREATE DATABASE rodauth_test;
|
|
212
|
+
USE rodauth_test;
|
|
213
|
+
CREATE USER rodauth_test FOR LOGIN rodauth_test;
|
|
214
|
+
GRANT CONNECT, EXECUTE TO rodauth_test;
|
|
215
|
+
EXECUTE sp_changedbowner 'rodauth_test_password';
|
|
216
|
+
|
|
217
|
+
You should run all migrations as the +ph+ account, and GRANT specific access
|
|
218
|
+
to the +app+ account as needed.
|
|
219
|
+
|
|
220
|
+
== Creating tables
|
|
138
221
|
|
|
139
222
|
Because two different database accounts are used, two different migrations
|
|
140
223
|
are required, one for each database account. Here are example migrations.
|
|
141
224
|
You can modify them to add support for additional columns, or remove tables
|
|
142
225
|
or columns related to features that you don't need.
|
|
143
226
|
|
|
144
|
-
First migration, run
|
|
227
|
+
First migration. On PostgreSQL, this should be run with the +app+ account,
|
|
228
|
+
on MySQL and Microsoft SQL Server this should be run with the +ph+ account.
|
|
145
229
|
|
|
146
230
|
Sequel.migration do
|
|
147
231
|
up do
|
|
232
|
+
extension :date_arithmetic
|
|
233
|
+
|
|
148
234
|
# Used by the account verification and close account features
|
|
149
235
|
create_table(:account_statuses) do
|
|
150
236
|
Integer :id, :primary_key=>true
|
|
@@ -152,35 +238,47 @@ First migration, run using the application database account:
|
|
|
152
238
|
end
|
|
153
239
|
from(:account_statuses).import([:id, :name], [[1, 'Unverified'], [2, 'Verified'], [3, 'Closed']])
|
|
154
240
|
|
|
155
|
-
|
|
156
|
-
# and close account features.
|
|
241
|
+
db = self
|
|
157
242
|
create_table(:accounts) do
|
|
158
243
|
primary_key :id, :type=>Bignum
|
|
159
244
|
foreign_key :status_id, :account_statuses, :null=>false, :default=>1
|
|
160
|
-
|
|
245
|
+
if db.database_type == :postgres
|
|
246
|
+
citext :email, :null=>false
|
|
247
|
+
constraint :valid_email, :email=>/^[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+$/
|
|
248
|
+
index :email, :unique=>true, :where=>{:status_id=>[1, 2]}
|
|
249
|
+
else
|
|
250
|
+
String :email, :null=>false
|
|
251
|
+
index :email, :unique=>true
|
|
252
|
+
end
|
|
253
|
+
end
|
|
161
254
|
|
|
162
|
-
|
|
163
|
-
|
|
255
|
+
deadline_opts = proc do |days|
|
|
256
|
+
if database_type == :mysql
|
|
257
|
+
{:null=>false}
|
|
258
|
+
else
|
|
259
|
+
{:null=>false, :default=>Sequel.date_add(Sequel::CURRENT_TIMESTAMP, :days=>days)}
|
|
260
|
+
end
|
|
164
261
|
end
|
|
165
262
|
|
|
166
263
|
# Used by the password reset feature
|
|
167
264
|
create_table(:account_password_reset_keys) do
|
|
168
265
|
foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
|
|
169
266
|
String :key, :null=>false
|
|
170
|
-
DateTime :deadline,
|
|
267
|
+
DateTime :deadline, deadline_opts[1]
|
|
171
268
|
end
|
|
172
269
|
|
|
173
270
|
# Used by the account verification feature
|
|
174
271
|
create_table(:account_verification_keys) do
|
|
175
272
|
foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
|
|
176
273
|
String :key, :null=>false
|
|
274
|
+
DateTime :requested_at, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
|
|
177
275
|
end
|
|
178
276
|
|
|
179
277
|
# Used by the remember me feature
|
|
180
278
|
create_table(:account_remember_keys) do
|
|
181
279
|
foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
|
|
182
280
|
String :key, :null=>false
|
|
183
|
-
DateTime :deadline,
|
|
281
|
+
DateTime :deadline, deadline_opts[14]
|
|
184
282
|
end
|
|
185
283
|
|
|
186
284
|
# Used by the lockout feature
|
|
@@ -191,81 +289,183 @@ First migration, run using the application database account:
|
|
|
191
289
|
create_table(:account_lockouts) do
|
|
192
290
|
foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
|
|
193
291
|
String :key, :null=>false
|
|
194
|
-
DateTime :deadline,
|
|
292
|
+
DateTime :deadline, deadline_opts[1]
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Used by the password expiration feature
|
|
296
|
+
create_table(:account_password_change_times) do
|
|
297
|
+
foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
|
|
298
|
+
DateTime :changed_at, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Used by the account expiration feature
|
|
302
|
+
create_table(:account_activity_times) do
|
|
303
|
+
foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
|
|
304
|
+
DateTime :last_activity_at, :null=>false
|
|
305
|
+
DateTime :last_login_at, :null=>false
|
|
306
|
+
DateTime :expired_at
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Used by the single session feature
|
|
310
|
+
create_table(:account_session_keys) do
|
|
311
|
+
foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
|
|
312
|
+
String :key, :null=>false
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Used by the otp feature
|
|
316
|
+
create_table(:account_otp_keys) do
|
|
317
|
+
foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
|
|
318
|
+
String :key, :null=>false
|
|
319
|
+
Integer :num_failures, :null=>false, :default=>0
|
|
320
|
+
Time :last_use, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
|
|
195
321
|
end
|
|
196
322
|
|
|
197
|
-
#
|
|
198
|
-
|
|
199
|
-
|
|
323
|
+
# Used by the recovery codes feature
|
|
324
|
+
create_table(:account_recovery_codes) do
|
|
325
|
+
foreign_key :id, :accounts, :type=>Bignum
|
|
326
|
+
String :code
|
|
327
|
+
primary_key [:id, :code]
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Used by the sms codes feature
|
|
331
|
+
create_table(:account_sms_codes) do
|
|
332
|
+
foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
|
|
333
|
+
String :phone_number, :null=>false
|
|
334
|
+
Integer :num_failures
|
|
335
|
+
String :code
|
|
336
|
+
DateTime :code_issued_at, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
case database_type
|
|
340
|
+
when :postgres
|
|
341
|
+
user = get{Sequel.lit('current_user')} + '_password'
|
|
342
|
+
run "GRANT REFERENCES ON accounts TO #{user}"
|
|
343
|
+
when :mysql, :mssql
|
|
344
|
+
user = if database_type == :mysql
|
|
345
|
+
get{Sequel.lit('current_user')}.sub(/_password@/, '@')
|
|
346
|
+
else
|
|
347
|
+
get{DB_NAME{}}
|
|
348
|
+
end
|
|
349
|
+
run "GRANT ALL ON account_statuses TO #{user}"
|
|
350
|
+
run "GRANT ALL ON accounts TO #{user}"
|
|
351
|
+
run "GRANT ALL ON account_password_reset_keys TO #{user}"
|
|
352
|
+
run "GRANT ALL ON account_verification_keys TO #{user}"
|
|
353
|
+
run "GRANT ALL ON account_remember_keys TO #{user}"
|
|
354
|
+
run "GRANT ALL ON account_login_failures TO #{user}"
|
|
355
|
+
run "GRANT ALL ON account_lockouts TO #{user}"
|
|
356
|
+
run "GRANT ALL ON account_password_change_times TO #{user}"
|
|
357
|
+
run "GRANT ALL ON account_activity_times TO #{user}"
|
|
358
|
+
run "GRANT ALL ON account_session_keys TO #{user}"
|
|
359
|
+
run "GRANT ALL ON account_otp_keys TO #{user}"
|
|
360
|
+
run "GRANT ALL ON account_recovery_codes TO #{user}"
|
|
361
|
+
run "GRANT ALL ON account_sms_codes TO #{user}"
|
|
362
|
+
end
|
|
200
363
|
end
|
|
201
364
|
|
|
202
365
|
down do
|
|
203
|
-
drop_table(:
|
|
204
|
-
|
|
366
|
+
drop_table(:account_sms_codes,
|
|
367
|
+
:account_recovery_codes,
|
|
368
|
+
:account_otp_keys,
|
|
369
|
+
:account_session_keys,
|
|
370
|
+
:account_activity_times,
|
|
371
|
+
:account_password_change_times,
|
|
372
|
+
:account_lockouts,
|
|
373
|
+
:account_login_failures,
|
|
374
|
+
:account_remember_keys,
|
|
375
|
+
:account_verification_keys,
|
|
376
|
+
:account_password_reset_keys,
|
|
377
|
+
:accounts,
|
|
378
|
+
:account_statuses)
|
|
205
379
|
end
|
|
206
380
|
end
|
|
207
381
|
|
|
208
|
-
Second migration, run using the
|
|
382
|
+
Second migration, run using the +ph+ account:
|
|
383
|
+
|
|
384
|
+
require 'rodauth/migrations'
|
|
209
385
|
|
|
210
386
|
Sequel.migration do
|
|
211
387
|
up do
|
|
212
|
-
# Used by the login and change password features
|
|
213
388
|
create_table(:account_password_hashes) do
|
|
214
389
|
foreign_key :id, :accounts, :primary_key=>true, :type=>Bignum
|
|
215
390
|
String :password_hash, :null=>false
|
|
216
391
|
end
|
|
392
|
+
Rodauth.create_database_authentication_functions(self)
|
|
393
|
+
case database_type
|
|
394
|
+
when :postgres
|
|
395
|
+
user = get{Sequel.lit('current_user')}.sub(/_password\z/, '')
|
|
396
|
+
run "REVOKE ALL ON account_password_hashes FROM public"
|
|
397
|
+
run "REVOKE ALL ON FUNCTION rodauth_get_salt(int8) FROM public"
|
|
398
|
+
run "REVOKE ALL ON FUNCTION rodauth_valid_password_hash(int8, text) FROM public"
|
|
399
|
+
run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
|
|
400
|
+
run "GRANT SELECT(id) ON account_password_hashes TO #{user}"
|
|
401
|
+
run "GRANT EXECUTE ON FUNCTION rodauth_get_salt(int8) TO #{user}"
|
|
402
|
+
run "GRANT EXECUTE ON FUNCTION rodauth_valid_password_hash(int8, text) TO #{user}"
|
|
403
|
+
when :mysql
|
|
404
|
+
user = get{Sequel.lit('current_user')}.sub(/_password@/, '@')
|
|
405
|
+
db_name = get{database{}}
|
|
406
|
+
run "GRANT EXECUTE ON #{db_name}.* TO #{user}"
|
|
407
|
+
run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
|
|
408
|
+
run "GRANT SELECT (id) ON account_password_hashes TO #{user}"
|
|
409
|
+
when :mssql
|
|
410
|
+
user = get{DB_NAME{}}
|
|
411
|
+
run "GRANT EXECUTE ON rodauth_get_salt TO #{user}"
|
|
412
|
+
run "GRANT EXECUTE ON rodauth_valid_password_hash TO #{user}"
|
|
413
|
+
run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
|
|
414
|
+
run "GRANT SELECT ON account_password_hashes(id) TO #{user}"
|
|
415
|
+
end
|
|
217
416
|
|
|
218
|
-
#
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
run "REVOKE ALL ON account_password_hashes 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"
|
|
253
|
-
run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{app_user}"
|
|
254
|
-
run "GRANT SELECT(id) ON account_password_hashes 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}"
|
|
417
|
+
# Used by the disallow_password_reuse feature
|
|
418
|
+
create_table(:account_previous_password_hashes) do
|
|
419
|
+
primary_key :id, :type=>Bignum
|
|
420
|
+
foreign_key :account_id, :accounts, :type=>Bignum
|
|
421
|
+
String :password_hash, :null=>false
|
|
422
|
+
end
|
|
423
|
+
Rodauth.create_database_previous_password_check_functions(self)
|
|
424
|
+
|
|
425
|
+
case database_type
|
|
426
|
+
when :postgres
|
|
427
|
+
user = get{Sequel.lit('current_user')}.sub(/_password\z/, '')
|
|
428
|
+
run "REVOKE ALL ON account_previous_password_hashes FROM public"
|
|
429
|
+
run "REVOKE ALL ON FUNCTION rodauth_get_previous_salt(int8) FROM public"
|
|
430
|
+
run "REVOKE ALL ON FUNCTION rodauth_previous_password_hash_match(int8, text) FROM public"
|
|
431
|
+
run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
|
|
432
|
+
run "GRANT SELECT(id, account_id) ON account_previous_password_hashes TO #{user}"
|
|
433
|
+
run "GRANT USAGE ON account_previous_password_hashes_id_seq TO #{user}"
|
|
434
|
+
run "GRANT EXECUTE ON FUNCTION rodauth_get_previous_salt(int8) TO #{user}"
|
|
435
|
+
run "GRANT EXECUTE ON FUNCTION rodauth_previous_password_hash_match(int8, text) TO #{user}"
|
|
436
|
+
when :mysql
|
|
437
|
+
user = get{Sequel.lit('current_user')}.sub(/_password@/, '@')
|
|
438
|
+
db_name = get{database{}}
|
|
439
|
+
run "GRANT EXECUTE ON #{db_name}.* TO #{user}"
|
|
440
|
+
run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
|
|
441
|
+
run "GRANT SELECT (id, account_id) ON account_previous_password_hashes TO #{user}"
|
|
442
|
+
when :mssql
|
|
443
|
+
user = get{DB_NAME{}}
|
|
444
|
+
run "GRANT EXECUTE ON rodauth_get_previous_salt TO #{user}"
|
|
445
|
+
run "GRANT EXECUTE ON rodauth_previous_password_hash_match TO #{user}"
|
|
446
|
+
run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
|
|
447
|
+
run "GRANT SELECT ON account_previous_password_hashes(id, account_id) TO #{user}"
|
|
448
|
+
end
|
|
257
449
|
end
|
|
258
450
|
|
|
259
451
|
down do
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
drop_table(:account_password_hashes)
|
|
452
|
+
Rodauth.drop_database_previous_password_check_functions(self)
|
|
453
|
+
Rodauth.drop_database_authentication_functions(self)
|
|
454
|
+
drop_table(:account_previous_password_hashes, :account_password_hashes)
|
|
263
455
|
end
|
|
264
456
|
end
|
|
265
457
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
458
|
+
To support multiple separate migration users, you can run the migration
|
|
459
|
+
for the password user using Sequel's migration API:
|
|
460
|
+
|
|
461
|
+
Sequel.extension :migration
|
|
462
|
+
Sequel.postgres('DATABASE_NAME', :user=>'PASSWORD_USER_NAME') do |db|
|
|
463
|
+
Sequel::Migrator.run(db, 'path/to/password_user/migrations', :table=>'schema_info_password')
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
If the database is not PostgreSQL, MySQL, or Microsoft SQL Server, or you
|
|
467
|
+
cannot use multiple user accounts, just combine the two migrations into a
|
|
468
|
+
single migration.
|
|
269
469
|
|
|
270
470
|
One thing to notice in the above migrations is that Rodauth uses additional
|
|
271
471
|
tables for additional features, instead of additional columns in a single
|
|
@@ -282,7 +482,7 @@ are loaded:
|
|
|
282
482
|
end
|
|
283
483
|
|
|
284
484
|
The block passed to the plugin call uses the Rodauth configuration DSL.
|
|
285
|
-
The one configuration method that should always be used is enable
|
|
485
|
+
The one configuration method that should always be used is +enable+,
|
|
286
486
|
which chooses which features you would like to load:
|
|
287
487
|
|
|
288
488
|
plugin :rodauth do
|
|
@@ -290,17 +490,18 @@ which chooses which features you would like to load:
|
|
|
290
490
|
end
|
|
291
491
|
|
|
292
492
|
Once features are loaded, you can use any of the configuration methods
|
|
293
|
-
supported by the features. There are
|
|
493
|
+
supported by the features. There are two types of configuration
|
|
294
494
|
methods. The first type are called auth methods, and they take a
|
|
295
|
-
block
|
|
296
|
-
block, you can call super if you want to get the default behavior
|
|
297
|
-
|
|
495
|
+
block which overrides the default method that Rodauth uses. Inside the
|
|
496
|
+
block, you can call super if you want to get the default behavior, though
|
|
497
|
+
you must provide explicit arguments to super. There is no need to
|
|
498
|
+
call super in before or after hooks, though. For example, if you want to
|
|
499
|
+
add additional logging when a user logs in:
|
|
298
500
|
|
|
299
501
|
plugin :rodauth do
|
|
300
502
|
enable :login, :logout
|
|
301
503
|
after_login do
|
|
302
|
-
|
|
303
|
-
super
|
|
504
|
+
LOGGER.info "#{account.email} logged in!"
|
|
304
505
|
end
|
|
305
506
|
end
|
|
306
507
|
|
|
@@ -320,8 +521,7 @@ So if you want to log the IP address for the user during login:
|
|
|
320
521
|
plugin :rodauth do
|
|
321
522
|
enable :login, :logout
|
|
322
523
|
after_login do
|
|
323
|
-
|
|
324
|
-
super
|
|
524
|
+
LOGGER.info "#{account.email} logged in from #{request.ip}"
|
|
325
525
|
end
|
|
326
526
|
end
|
|
327
527
|
|
|
@@ -329,33 +529,22 @@ The second type of configuration methods are called auth value
|
|
|
329
529
|
methods. They are similar to auth methods, but instead of just
|
|
330
530
|
accepting a block, they can optionally accept a single argument
|
|
331
531
|
without a block, which will be treated as a block that just returns
|
|
332
|
-
that value. For example, the
|
|
333
|
-
|
|
334
|
-
|
|
532
|
+
that value. For example, the accounts_table method sets the database
|
|
533
|
+
table storing accounts, so to override it, you can call the method
|
|
534
|
+
with a symbol for the table:
|
|
335
535
|
|
|
336
536
|
plugin :rodauth do
|
|
337
537
|
enable :login, :logout
|
|
338
|
-
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
itself, one for handling just the GET route, and one for handling just
|
|
344
|
-
the POST route. For the login feature, login_route_block would set
|
|
345
|
-
the routing block to use if the login route matches, login_get_block
|
|
346
|
-
would set the routing block to use if the login route matches and it
|
|
347
|
-
is a GET request, and login_post_block would set the routing block to
|
|
348
|
-
use if the login route matches and it is a POST request. As auth block
|
|
349
|
-
methods specify the route blocks, they are executed in the context
|
|
350
|
-
of the Roda instance, and are passed two arguments, the first being the
|
|
351
|
-
RodaRequest instance, and the second being the Rodauth::Auth instance.
|
|
352
|
-
For example, if you wanted to override how a POST request to the login
|
|
353
|
-
route is handled:
|
|
538
|
+
accounts_table :users
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
Note that all auth value methods can still take a block, allowing
|
|
542
|
+
overriding for all behavior, using any information from the request:
|
|
354
543
|
|
|
355
544
|
plugin :rodauth do
|
|
356
|
-
enable :login
|
|
357
|
-
|
|
358
|
-
|
|
545
|
+
enable :login, :logout
|
|
546
|
+
accounts_table do
|
|
547
|
+
request.ip.start_with?("192.168.1") ? :admins : :users
|
|
359
548
|
end
|
|
360
549
|
end
|
|
361
550
|
|
|
@@ -369,6 +558,9 @@ separate page per feature. If these links are not active, please
|
|
|
369
558
|
view the appropriate file in the doc directory.
|
|
370
559
|
|
|
371
560
|
* {Base}[rdoc-ref:doc/base.rdoc] (this feature is autoloaded)
|
|
561
|
+
* {Login Password Requirements Base}[rdoc-ref:doc/login_password_requirements_base.rdoc] (this feature is autoloaded by features that set logins/passwords)
|
|
562
|
+
* {Email Base}[rdoc-ref:doc/email_base.rdoc] (this feature is autoloaded by features that send email)
|
|
563
|
+
* {Two Factor Base}[rdoc-ref:doc/two_factor_base.rdoc] (this feature is autoloaded by 2 factor authentication features)
|
|
372
564
|
* {Login}[rdoc-ref:doc/login.rdoc]
|
|
373
565
|
* {Logout}[rdoc-ref:doc/logout.rdoc]
|
|
374
566
|
* {Change Password}[rdoc-ref:doc/change_password.rdoc]
|
|
@@ -377,13 +569,81 @@ view the appropriate file in the doc directory.
|
|
|
377
569
|
* {Create Account}[rdoc-ref:doc/create_account.rdoc]
|
|
378
570
|
* {Close Account}[rdoc-ref:doc/close_account.rdoc]
|
|
379
571
|
* {Verify Account}[rdoc-ref:doc/verify_account.rdoc]
|
|
572
|
+
* {Confirm Password}[rdoc-ref:doc/confirm_password.rdoc]
|
|
380
573
|
* {Remember}[rdoc-ref:doc/remember.rdoc]
|
|
381
574
|
* {Lockout}[rdoc-ref:doc/lockout.rdoc]
|
|
575
|
+
* {OTP}[rdoc-ref:doc/otp.rdoc]
|
|
576
|
+
* {Recovery Codes}[rdoc-ref:doc/recovery_codes.rdoc]
|
|
577
|
+
* {SMS Codes}[rdoc-ref:doc/sms_codes.rdoc]
|
|
578
|
+
* {Verify Change Login}[rdoc-ref:doc/verify_change_login.rdoc]
|
|
579
|
+
* {Verify Account Grace Period}[rdoc-ref:doc/verify_account_grace_period.rdoc]
|
|
580
|
+
* {Password Grace Period}[rdoc-ref:doc/password_grace_period.rdoc]
|
|
581
|
+
* {Password Complexity}[rdoc-ref:doc/password_complexity.rdoc]
|
|
582
|
+
* {Disallow Password Reuse}[rdoc-ref:doc/disallow_password_reuse.rdoc]
|
|
583
|
+
* {Password Expiration}[rdoc-ref:doc/password_expiration.rdoc]
|
|
584
|
+
* {Account Expiration}[rdoc-ref:doc/account_expiration.rdoc]
|
|
585
|
+
* {Session Expiration}[rdoc-ref:doc/session_expiration.rdoc]
|
|
586
|
+
* {Single Session}[rdoc-ref:doc/single_session.rdoc]
|
|
587
|
+
* {JWT}[rdoc-ref:doc/jwt.rdoc]
|
|
588
|
+
|
|
589
|
+
=== Calling Rodauth in the Routing Tree
|
|
590
|
+
|
|
591
|
+
In general, you will usually want to call rodauth early in your
|
|
592
|
+
route block:
|
|
593
|
+
|
|
594
|
+
route do |r|
|
|
595
|
+
r.rodauth
|
|
596
|
+
|
|
597
|
+
# ...
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
Note that will allow Rodauth to run, but it won't force people
|
|
601
|
+
to login or add any security to your site. If you want to force
|
|
602
|
+
all users to login, you need to redirect to them login page if
|
|
603
|
+
they are not already logged in:
|
|
604
|
+
|
|
605
|
+
route do |r|
|
|
606
|
+
r.rodauth
|
|
607
|
+
rodauth.require_authentication
|
|
608
|
+
|
|
609
|
+
# ...
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
If only certain parts of your site require logins, then you can
|
|
613
|
+
only redirect if they are not logged in certain branches of the
|
|
614
|
+
routing tree:
|
|
615
|
+
|
|
616
|
+
route do |r|
|
|
617
|
+
r.rodauth
|
|
618
|
+
|
|
619
|
+
r.on "admin" do
|
|
620
|
+
rodauth.require_authentication
|
|
621
|
+
|
|
622
|
+
# ...
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
# ...
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
In some cases you may want to have rodauth run inside a branch of
|
|
629
|
+
the routing tree, instead of in the root. You can do this by
|
|
630
|
+
setting a +:prefix+ when configuring Rodauth, and calling +r.rodauth+
|
|
631
|
+
inside a matching routing tree branch:
|
|
382
632
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
633
|
+
plugin :rodauth do
|
|
634
|
+
enable :login, :logout
|
|
635
|
+
prefix "auth"
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
route do |r|
|
|
639
|
+
r.on "auth" do
|
|
640
|
+
r.rodauth
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
rodauth.require_authentication
|
|
644
|
+
|
|
645
|
+
# ...
|
|
646
|
+
end
|
|
387
647
|
|
|
388
648
|
=== With Multiple Configurations
|
|
389
649
|
|
|
@@ -407,26 +667,79 @@ the name as an argument to use that configuration:
|
|
|
407
667
|
r.rodauth
|
|
408
668
|
end
|
|
409
669
|
|
|
410
|
-
=== With
|
|
670
|
+
=== With Password Hashes Inside the Accounts Table
|
|
411
671
|
|
|
412
|
-
You can use Rodauth
|
|
413
|
-
|
|
414
|
-
|
|
672
|
+
You can use Rodauth if you are storing password hashes in the same
|
|
673
|
+
table as the accounts. You just need to specify which column
|
|
674
|
+
stores the password hash:
|
|
415
675
|
|
|
416
676
|
plugin :rodauth do
|
|
417
677
|
account_password_hash_column :password_hash
|
|
418
678
|
end
|
|
419
679
|
|
|
420
|
-
When this option is set, Rodauth will
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
680
|
+
When this option is set, Rodauth will do the password hash check
|
|
681
|
+
in ruby.
|
|
682
|
+
|
|
683
|
+
=== When Using PostgreSQL/MySQL/Microsoft SQL Server without Database Functions
|
|
684
|
+
|
|
685
|
+
If you want to use Rodauth on PostgreSQL, MySQL, or Microsoft SQL Server
|
|
686
|
+
without using database functions for authentication, but still storing password
|
|
687
|
+
hashes in a separate table, you can do so:
|
|
688
|
+
|
|
689
|
+
plugin :rodauth do
|
|
690
|
+
use_database_authentication_functions? false
|
|
691
|
+
end
|
|
425
692
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
693
|
+
Conversely, if you implement the rodauth_get_salt and
|
|
694
|
+
rodauth_valid_password_hash functions on a database that isn't
|
|
695
|
+
PostgreSQL, MySQL, or Microsoft SQL Server, you can set this value to true.
|
|
696
|
+
|
|
697
|
+
=== With Custom Authentication (such as LDAP)
|
|
698
|
+
|
|
699
|
+
You can use Rodauth with other authentication types, by overriding
|
|
700
|
+
a single configuration setting. For example, if you have accounts
|
|
701
|
+
stored in the database, but authentication happens via LDAP, you
|
|
702
|
+
can use the +simple_ldap_authenticator+ library:
|
|
703
|
+
|
|
704
|
+
require 'simple_ldap_authenticator'
|
|
705
|
+
plugin :rodauth do
|
|
706
|
+
enable :login, :logout
|
|
707
|
+
require_bcrypt? false
|
|
708
|
+
password_match? do |password|
|
|
709
|
+
SimpleLdapAuthenticator.valid?(account.username, password)
|
|
710
|
+
end
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
If you aren't storing accounts in the database, but want to allow
|
|
714
|
+
any valid LDAP user to login, you can do something like this:
|
|
715
|
+
|
|
716
|
+
require 'simple_ldap_authenticator'
|
|
717
|
+
plugin :rodauth do
|
|
718
|
+
enable :login, :logout
|
|
719
|
+
|
|
720
|
+
# Don't require the bcrypt library, since using LDAP for auth
|
|
721
|
+
require_bcrypt? false
|
|
722
|
+
|
|
723
|
+
# Treat the login itself as the account
|
|
724
|
+
account_from_login{|l| l.to_s}
|
|
725
|
+
|
|
726
|
+
# Use the login provided as the session value
|
|
727
|
+
account_session_value{account}
|
|
728
|
+
|
|
729
|
+
# Store session value in :login key, since the :account_id
|
|
730
|
+
# default wouldn't make sense
|
|
731
|
+
session_key :login
|
|
732
|
+
|
|
733
|
+
password_match? do |password|
|
|
734
|
+
SimpleLdapAuthenticator.valid?(account, password)
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
Note that when using custom authentication, using some of Rodauth's
|
|
739
|
+
features such as change login and change password either would not
|
|
740
|
+
make sense or would require some additional custom configuration.
|
|
741
|
+
The login and logout features should work correctly with the examples
|
|
742
|
+
above, though.
|
|
430
743
|
|
|
431
744
|
=== With Other Web Frameworks
|
|
432
745
|
|
|
@@ -438,47 +751,192 @@ Rodauth:
|
|
|
438
751
|
|
|
439
752
|
class RodauthApp < Roda
|
|
440
753
|
plugin :middleware
|
|
441
|
-
plugin :rodauth
|
|
754
|
+
plugin :rodauth do
|
|
755
|
+
enable :login
|
|
756
|
+
end
|
|
757
|
+
|
|
442
758
|
route do |r|
|
|
443
759
|
r.rodauth
|
|
760
|
+
env['rodauth'] = rodauth
|
|
761
|
+
rodauth.require_authentication
|
|
444
762
|
end
|
|
445
763
|
end
|
|
446
764
|
|
|
447
765
|
use RodauthApp
|
|
448
766
|
|
|
767
|
+
Note that Rodauth expects the Roda app it is used in to provide a
|
|
768
|
+
layout. So if you are using Rodauth as middleware for another app,
|
|
769
|
+
if you don't have a +views/layout.erb+ file that Rodauth can use,
|
|
770
|
+
you should probably also add load Roda's +render+ plugin
|
|
771
|
+
with the appropriate settings that allow Rodauth to use the same
|
|
772
|
+
layout as the application.
|
|
773
|
+
|
|
774
|
+
By setting <tt>env['rodauth'] = rodauth</tt> in the route block
|
|
775
|
+
inside the middleware, you can easily provide a way for your
|
|
776
|
+
application to call Rodauth methods.
|
|
777
|
+
|
|
778
|
+
For an example of integrating Rodauth into a real application that
|
|
779
|
+
doesn't use Roda, see
|
|
780
|
+
{this example integrating Rodauth into Ginatra, a Sinatra-based git repository viewer}[https://github.com/jeremyevans/ginatra/commit/28108ebec96e8d42596ee55b01c3f7b50c155dd1].
|
|
781
|
+
|
|
782
|
+
=== Using 2 Factor Authentication
|
|
783
|
+
|
|
784
|
+
Rodauth ships with 2 factor authentication support via TOTP (Time-Based
|
|
785
|
+
One-Time Passwords, RFC 6238). There are a wide variety of ways in
|
|
786
|
+
which to integrate 2 factor authentication into your site with Rodauth,
|
|
787
|
+
based on the needs of the application.
|
|
788
|
+
|
|
789
|
+
The 2 factor authentication support is part of the OTP feature, which
|
|
790
|
+
needs to be enabled in addition to the login feature. In addition,
|
|
791
|
+
when implementing 2 factor authentication, you should generally
|
|
792
|
+
provide a backup 2nd factor if the primary second factor is not
|
|
793
|
+
available. Rodauth supports SMS codes and recovery codes as other
|
|
794
|
+
2nd factors.
|
|
795
|
+
|
|
796
|
+
If you want to support but not require 2 factor authentication:
|
|
797
|
+
|
|
798
|
+
plugin :rodauth do
|
|
799
|
+
enable :login, :logout, :otp, :recovery_codes, :sms_codes
|
|
800
|
+
end
|
|
801
|
+
route do |r|
|
|
802
|
+
r.rodauth
|
|
803
|
+
rodauth.require_authentication
|
|
804
|
+
|
|
805
|
+
# ...
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
If you want to force all users to use OTP authentication, requiring users
|
|
809
|
+
that don't currently have an account to set one up:
|
|
810
|
+
|
|
811
|
+
route do |r|
|
|
812
|
+
r.rodauth
|
|
813
|
+
rodauth.require_authentication
|
|
814
|
+
rodauth.require_two_factor_authentication_setup
|
|
815
|
+
|
|
816
|
+
# ...
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
Similarly to requiring authentication in general, it's possible to require
|
|
820
|
+
login authentication for most of the site, but require 2 factor
|
|
821
|
+
authentication only for particular branches:
|
|
822
|
+
|
|
823
|
+
route do |r|
|
|
824
|
+
r.rodauth
|
|
825
|
+
rodauth.require_login
|
|
826
|
+
|
|
827
|
+
r.on "admin" do
|
|
828
|
+
rodauth.require_two_factor_authenticated
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
# ...
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
== JSON API Support
|
|
835
|
+
|
|
836
|
+
To add support for handling JSON responses, you can pass the +:json+
|
|
837
|
+
option to the plugin, and enable the JWT feature in addition to
|
|
838
|
+
other features you plan to use:
|
|
839
|
+
|
|
840
|
+
plugin :rodauth, :json=>true do
|
|
841
|
+
enable :login, :logout, :jwt
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
If you do not want to load the HTML plugins that Rodauth usually loads
|
|
845
|
+
(render, csrf, flash, h), because you are building a JSON-only API,
|
|
846
|
+
pass <tt>:json => :only</tt>
|
|
847
|
+
|
|
848
|
+
plugin :rodauth, :json=>:only do
|
|
849
|
+
enable :login, :logout, :jwt
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
Note that by default, the features that send email depend on the
|
|
853
|
+
render plugin, so if using the <tt>:json=>:only</tt> option, you
|
|
854
|
+
either need to load the render plugin manually or you need to
|
|
855
|
+
use the necessary *_email_body configuration options to specify
|
|
856
|
+
the body of the emails.
|
|
857
|
+
|
|
858
|
+
The JWT feature enables JSON API support for all of the other features
|
|
859
|
+
that Rodauth ships with.
|
|
860
|
+
|
|
861
|
+
=== Adding Custom Methods to the +rodauth+ Object
|
|
862
|
+
|
|
863
|
+
Inside the configuration block, you can use +auth_class_eval+ to add
|
|
864
|
+
custom methods that will be callable on the +rodauth+ object.
|
|
865
|
+
|
|
866
|
+
plugin :rodauth do
|
|
867
|
+
enable :login
|
|
868
|
+
|
|
869
|
+
auth_class_eval do
|
|
870
|
+
def require_admin
|
|
871
|
+
request.redirect("/") unless account[:admin]
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
route do |r|
|
|
877
|
+
r.rodauth
|
|
878
|
+
|
|
879
|
+
r.on "admin"
|
|
880
|
+
rodauth.require_admin
|
|
881
|
+
end
|
|
882
|
+
end
|
|
883
|
+
|
|
449
884
|
=== Using External Features
|
|
450
885
|
|
|
451
886
|
The enable configuration method is able to load features external to
|
|
452
887
|
Rodauth. You need to place the external feature file where it can be
|
|
453
|
-
required via
|
|
888
|
+
required via rodauth/features/feature_name. That file should
|
|
454
889
|
use the following basic structure
|
|
455
890
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
# define the default behavior for the auth methods
|
|
477
|
-
# and auth value methods
|
|
478
|
-
# ...
|
|
891
|
+
module Rodauth
|
|
892
|
+
# :feature_name will be the argument given to enable to
|
|
893
|
+
# load the feature
|
|
894
|
+
FeatureName = Feature.define(:feature_name) do
|
|
895
|
+
# Shortcut for defining auth value methods with static values
|
|
896
|
+
auth_value_method :method_name, 1 # method_value
|
|
897
|
+
|
|
898
|
+
auth_value_methods # one argument per auth value method
|
|
899
|
+
|
|
900
|
+
auth_methods # one argument per auth method
|
|
901
|
+
|
|
902
|
+
route do |r|
|
|
903
|
+
# This block is taken for requests to the feature's route.
|
|
904
|
+
# This block is evaluated in the scope of the Rodauth::Auth instance.
|
|
905
|
+
# r is the Roda::RodaRequest instance for the request
|
|
906
|
+
|
|
907
|
+
r.get do
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
r.post do
|
|
479
911
|
end
|
|
480
912
|
end
|
|
913
|
+
|
|
914
|
+
configuration_eval do
|
|
915
|
+
# define additional configuration specific methods here, if any
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
# define the default behavior for the auth_methods
|
|
919
|
+
# and auth_value_methods
|
|
920
|
+
# ...
|
|
921
|
+
end
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
See the source code for the features that ship with Rodauth for an
|
|
925
|
+
example of how to construct features.
|
|
926
|
+
|
|
927
|
+
=== Overriding Route-Level Behavior
|
|
928
|
+
|
|
929
|
+
All of Rodauth's configuration methods change the behavior of the
|
|
930
|
+
Rodauth::Auth instance. However, in some cases you may want to
|
|
931
|
+
overriding handling at the routing layer. You can do this easily
|
|
932
|
+
by adding an appropriate route before calling +r.rodauth+:
|
|
933
|
+
|
|
934
|
+
route do |r|
|
|
935
|
+
r.post 'login' do
|
|
936
|
+
# Custom POST /login handling here
|
|
481
937
|
end
|
|
938
|
+
|
|
939
|
+
r.rodauth
|
|
482
940
|
end
|
|
483
941
|
|
|
484
942
|
== Upgrading from 0.9.x
|
|
@@ -489,47 +947,13 @@ it and add the two database functions listed in the migration
|
|
|
489
947
|
section above. You can add the following code to a migration to
|
|
490
948
|
accomplish that:
|
|
491
949
|
|
|
950
|
+
require 'rodauth/migrations'
|
|
492
951
|
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/, '')
|
|
952
|
+
Rodauth.create_database_authentication_functions(self)
|
|
524
953
|
run "REVOKE ALL ON FUNCTION rodauth_get_salt(int8) FROM public"
|
|
525
954
|
run "REVOKE ALL ON FUNCTION rodauth_valid_password_hash(int8, text) FROM public"
|
|
526
|
-
run "GRANT EXECUTE ON FUNCTION rodauth_get_salt(int8) TO
|
|
527
|
-
run "GRANT EXECUTE ON FUNCTION rodauth_valid_password_hash(int8, text) TO
|
|
528
|
-
|
|
529
|
-
== Possible Future Directions
|
|
530
|
-
|
|
531
|
-
* OmniAuth support. This is not something I plan to work on myself,
|
|
532
|
-
but I will consider patches that add it.
|
|
955
|
+
run "GRANT EXECUTE ON FUNCTION rodauth_get_salt(int8) TO ${DATABASE_NAME}"
|
|
956
|
+
run "GRANT EXECUTE ON FUNCTION rodauth_valid_password_hash(int8, text) TO ${DATABASE_NAME}"
|
|
533
957
|
|
|
534
958
|
== Similar Projects
|
|
535
959
|
|