rodauth 2.36.0 → 2.37.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rodauth/features/base.rb +15 -1
- data/lib/rodauth/features/change_login.rb +2 -2
- data/lib/rodauth/features/create_account.rb +2 -2
- data/lib/rodauth/features/email_auth.rb +1 -1
- data/lib/rodauth/features/internal_request.rb +4 -4
- data/lib/rodauth/features/json.rb +5 -0
- data/lib/rodauth/features/jwt.rb +5 -9
- data/lib/rodauth/features/lockout.rb +1 -1
- data/lib/rodauth/features/login.rb +1 -1
- data/lib/rodauth/features/login_password_requirements_base.rb +13 -0
- data/lib/rodauth/features/reset_password.rb +1 -1
- data/lib/rodauth/features/two_factor_base.rb +6 -13
- data/lib/rodauth/features/verify_account.rb +2 -2
- data/lib/rodauth/features/webauthn_autofill.rb +2 -1
- data/lib/rodauth/features/webauthn_login.rb +1 -1
- data/lib/rodauth/version.rb +1 -1
- data/lib/rodauth.rb +6 -2
- metadata +3 -258
- data/CHANGELOG +0 -521
- data/README.rdoc +0 -1555
- data/doc/account_expiration.rdoc +0 -41
- data/doc/active_sessions.rdoc +0 -56
- data/doc/argon2.rdoc +0 -54
- data/doc/audit_logging.rdoc +0 -44
- data/doc/base.rdoc +0 -123
- data/doc/change_login.rdoc +0 -25
- data/doc/change_password.rdoc +0 -26
- data/doc/change_password_notify.rdoc +0 -14
- data/doc/close_account.rdoc +0 -26
- data/doc/confirm_password.rdoc +0 -32
- data/doc/create_account.rdoc +0 -27
- data/doc/disallow_common_passwords.rdoc +0 -17
- data/doc/disallow_password_reuse.rdoc +0 -30
- data/doc/email_auth.rdoc +0 -55
- data/doc/email_base.rdoc +0 -18
- data/doc/error_reasons.rdoc +0 -77
- data/doc/guides/admin_activation.rdoc +0 -46
- data/doc/guides/already_authenticated.rdoc +0 -10
- data/doc/guides/alternative_login.rdoc +0 -46
- data/doc/guides/change_table_and_column_names.rdoc +0 -19
- data/doc/guides/create_account_programmatically.rdoc +0 -38
- data/doc/guides/delay_password.rdoc +0 -25
- data/doc/guides/email_only.rdoc +0 -16
- data/doc/guides/i18n.rdoc +0 -29
- data/doc/guides/internals.rdoc +0 -233
- data/doc/guides/links.rdoc +0 -12
- data/doc/guides/login_return.rdoc +0 -37
- data/doc/guides/migrate_password_hash_algorithm.rdoc +0 -15
- data/doc/guides/password_column.rdoc +0 -25
- data/doc/guides/password_confirmation.rdoc +0 -37
- data/doc/guides/password_requirements.rdoc +0 -43
- data/doc/guides/paths.rdoc +0 -51
- data/doc/guides/query_params.rdoc +0 -9
- data/doc/guides/redirects.rdoc +0 -17
- data/doc/guides/registration_field.rdoc +0 -68
- data/doc/guides/render_confirmation.rdoc +0 -17
- data/doc/guides/require_mfa.rdoc +0 -30
- data/doc/guides/reset_password_autologin.rdoc +0 -21
- data/doc/guides/share_configuration.rdoc +0 -34
- data/doc/guides/status_column.rdoc +0 -28
- data/doc/guides/totp_or_recovery.rdoc +0 -16
- data/doc/http_basic_auth.rdoc +0 -18
- data/doc/internal_request.rdoc +0 -539
- data/doc/json.rdoc +0 -56
- data/doc/jwt.rdoc +0 -52
- data/doc/jwt_cors.rdoc +0 -22
- data/doc/jwt_refresh.rdoc +0 -58
- data/doc/lockout.rdoc +0 -73
- data/doc/login.rdoc +0 -39
- data/doc/login_password_requirements_base.rdoc +0 -44
- data/doc/logout.rdoc +0 -22
- data/doc/otp.rdoc +0 -93
- data/doc/otp_lockout_email.rdoc +0 -30
- data/doc/otp_modify_email.rdoc +0 -19
- data/doc/otp_unlock.rdoc +0 -58
- data/doc/password_complexity.rdoc +0 -34
- data/doc/password_expiration.rdoc +0 -38
- data/doc/password_grace_period.rdoc +0 -24
- data/doc/password_pepper.rdoc +0 -52
- data/doc/path_class_methods.rdoc +0 -10
- data/doc/recovery_codes.rdoc +0 -61
- data/doc/release_notes/1.0.0.txt +0 -443
- data/doc/release_notes/1.1.0.txt +0 -8
- data/doc/release_notes/1.10.0.txt +0 -80
- data/doc/release_notes/1.11.0.txt +0 -32
- data/doc/release_notes/1.12.0.txt +0 -61
- data/doc/release_notes/1.13.0.txt +0 -34
- data/doc/release_notes/1.14.0.txt +0 -19
- data/doc/release_notes/1.15.0.txt +0 -21
- data/doc/release_notes/1.16.0.txt +0 -31
- data/doc/release_notes/1.17.0.txt +0 -23
- data/doc/release_notes/1.18.0.txt +0 -26
- data/doc/release_notes/1.19.0.txt +0 -116
- data/doc/release_notes/1.2.0.txt +0 -18
- data/doc/release_notes/1.20.0.txt +0 -175
- data/doc/release_notes/1.21.0.txt +0 -12
- data/doc/release_notes/1.22.0.txt +0 -11
- data/doc/release_notes/1.23.0.txt +0 -32
- data/doc/release_notes/1.3.0.txt +0 -21
- data/doc/release_notes/1.4.0.txt +0 -11
- data/doc/release_notes/1.5.0.txt +0 -74
- data/doc/release_notes/1.6.0.txt +0 -37
- data/doc/release_notes/1.7.0.txt +0 -6
- data/doc/release_notes/1.8.0.txt +0 -14
- data/doc/release_notes/1.9.0.txt +0 -15
- data/doc/release_notes/2.0.0.txt +0 -361
- data/doc/release_notes/2.1.0.txt +0 -31
- data/doc/release_notes/2.10.0.txt +0 -47
- data/doc/release_notes/2.11.0.txt +0 -31
- data/doc/release_notes/2.12.0.txt +0 -17
- data/doc/release_notes/2.13.0.txt +0 -19
- data/doc/release_notes/2.14.0.txt +0 -17
- data/doc/release_notes/2.15.0.txt +0 -48
- data/doc/release_notes/2.16.0.txt +0 -20
- data/doc/release_notes/2.17.0.txt +0 -10
- data/doc/release_notes/2.18.0.txt +0 -27
- data/doc/release_notes/2.19.0.txt +0 -61
- data/doc/release_notes/2.2.0.txt +0 -39
- data/doc/release_notes/2.20.0.txt +0 -10
- data/doc/release_notes/2.21.0.txt +0 -28
- data/doc/release_notes/2.22.0.txt +0 -43
- data/doc/release_notes/2.23.0.txt +0 -15
- data/doc/release_notes/2.24.0.txt +0 -15
- data/doc/release_notes/2.25.0.txt +0 -8
- data/doc/release_notes/2.26.0.txt +0 -45
- data/doc/release_notes/2.27.0.txt +0 -35
- data/doc/release_notes/2.28.0.txt +0 -16
- data/doc/release_notes/2.29.0.txt +0 -27
- data/doc/release_notes/2.3.0.txt +0 -37
- data/doc/release_notes/2.30.0.txt +0 -15
- data/doc/release_notes/2.31.0.txt +0 -47
- data/doc/release_notes/2.32.0.txt +0 -65
- data/doc/release_notes/2.33.0.txt +0 -18
- data/doc/release_notes/2.34.0.txt +0 -36
- data/doc/release_notes/2.35.0.txt +0 -22
- data/doc/release_notes/2.36.0.txt +0 -35
- data/doc/release_notes/2.4.0.txt +0 -22
- data/doc/release_notes/2.5.0.txt +0 -20
- data/doc/release_notes/2.6.0.txt +0 -37
- data/doc/release_notes/2.7.0.txt +0 -33
- data/doc/release_notes/2.8.0.txt +0 -20
- data/doc/release_notes/2.9.0.txt +0 -21
- data/doc/remember.rdoc +0 -79
- data/doc/reset_password.rdoc +0 -66
- data/doc/reset_password_notify.rdoc +0 -17
- data/doc/session_expiration.rdoc +0 -28
- data/doc/single_session.rdoc +0 -37
- data/doc/sms_codes.rdoc +0 -138
- data/doc/two_factor_base.rdoc +0 -70
- data/doc/update_password_hash.rdoc +0 -7
- data/doc/verify_account.rdoc +0 -67
- data/doc/verify_account_grace_period.rdoc +0 -19
- data/doc/verify_login_change.rdoc +0 -59
- data/doc/webauthn.rdoc +0 -118
- data/doc/webauthn_autofill.rdoc +0 -19
- data/doc/webauthn_login.rdoc +0 -16
- data/doc/webauthn_modify_email.rdoc +0 -19
- data/doc/webauthn_verify_account.rdoc +0 -9
data/README.rdoc
DELETED
@@ -1,1555 +0,0 @@
|
|
1
|
-
= Rodauth
|
2
|
-
|
3
|
-
Rodauth is Ruby's most advanced authentication framework, designed
|
4
|
-
to work in any rack application. It's built using Roda and Sequel,
|
5
|
-
but it can be used with other web frameworks, database libraries,
|
6
|
-
and databases.
|
7
|
-
|
8
|
-
When used with PostgreSQL, MySQL, and Microsoft SQL Server in the
|
9
|
-
default configuration, it offers additional security for password
|
10
|
-
hashes by protecting access via database functions.
|
11
|
-
|
12
|
-
Rodauth supports multiple multifactor authentication methods,
|
13
|
-
multiple passwordless authentication methods, and offers both an
|
14
|
-
HTML and JSON API for all supported features.
|
15
|
-
|
16
|
-
== Design Goals
|
17
|
-
|
18
|
-
* Security: Ship in a maximum security by default configuration
|
19
|
-
* Simplicity: Allow for easy configuration via a DSL
|
20
|
-
* Flexibility: Allow for easy overriding of any part of the framework
|
21
|
-
|
22
|
-
== Features
|
23
|
-
|
24
|
-
* Login
|
25
|
-
* Logout
|
26
|
-
* Change Password
|
27
|
-
* Change Login
|
28
|
-
* Reset Password
|
29
|
-
* Create Account
|
30
|
-
* Close Account
|
31
|
-
* Verify Account
|
32
|
-
* Confirm Password
|
33
|
-
* Remember (Autologin via token)
|
34
|
-
* Lockout (Bruteforce protection)
|
35
|
-
* Audit Logging
|
36
|
-
* Email Authentication (Passwordless login via email link)
|
37
|
-
* WebAuthn (Multifactor authentication via WebAuthn)
|
38
|
-
* WebAuthn Login (Passwordless login via WebAuthn)
|
39
|
-
* WebAuthn Verify Account (Passwordless WebAuthn Setup)
|
40
|
-
* WebAuthn Autofill (Autofill WebAuthn credentials on login)
|
41
|
-
* WebAuthn Modify Email (Email when WebAuthn authenticator aded or removed)
|
42
|
-
* OTP (Multifactor authentication via TOTP)
|
43
|
-
* OTP Modify Email (Email when TOTP authentication setup or disabled)
|
44
|
-
* OTP Unlock (Unlock TOTP authentication after lockout)
|
45
|
-
* OTP Lockout Email (Email when TOTP authentication locked out or unlocked)
|
46
|
-
* Recovery Codes (Multifactor authentication via backup codes)
|
47
|
-
* SMS Codes (Multifactor authentication via SMS)
|
48
|
-
* Verify Login Change (Verify new login before changing login)
|
49
|
-
* Verify Account Grace Period (Don't require verification before login)
|
50
|
-
* Password Grace Period (Don't require password entry if recently entered)
|
51
|
-
* Password Complexity (More sophisticated checks)
|
52
|
-
* Password Pepper
|
53
|
-
* Disallow Password Reuse
|
54
|
-
* Disallow Common Passwords
|
55
|
-
* Password Expiration
|
56
|
-
* Account Expiration
|
57
|
-
* Session Expiration
|
58
|
-
* Active Sessions (Prevent session reuse after logout, allow logout of all sessions)
|
59
|
-
* Single Session (Only one active session per account)
|
60
|
-
* JSON (JSON API support for all other features)
|
61
|
-
* JWT (JSON Web Token support for all other features)
|
62
|
-
* JWT Refresh (Access & Refresh Token)
|
63
|
-
* JWT CORS (Cross-Origin Resource Sharing)
|
64
|
-
* Update Password Hash (when hash cost changes)
|
65
|
-
* Argon2
|
66
|
-
* HTTP Basic Auth
|
67
|
-
* Change Password Notify
|
68
|
-
* Reset Password Notify
|
69
|
-
* Internal Request
|
70
|
-
* Path Class Methods
|
71
|
-
|
72
|
-
== Resources
|
73
|
-
|
74
|
-
Website :: http://rodauth.jeremyevans.net
|
75
|
-
Demo Site :: http://rodauth-demo.jeremyevans.net
|
76
|
-
Source :: http://github.com/jeremyevans/rodauth
|
77
|
-
Bugs :: http://github.com/jeremyevans/rodauth/issues
|
78
|
-
Discussion Forum (GitHub Discussions) :: https://github.com/jeremyevans/rodauth/discussions
|
79
|
-
Alternate Discussion Forum (Google Groups) :: https://groups.google.com/forum/#!forum/rodauth
|
80
|
-
|
81
|
-
== Dependencies
|
82
|
-
|
83
|
-
There are some dependencies that Rodauth uses depending on the
|
84
|
-
features in use. These are development dependencies instead of
|
85
|
-
runtime dependencies in the gem as it is possible to run without them:
|
86
|
-
|
87
|
-
tilt :: Used by all features unless in JSON API only mode or using
|
88
|
-
:render=>false plugin option.
|
89
|
-
rack_csrf :: Used for CSRF support if the <tt>csrf: :rack_csrf</tt> plugin
|
90
|
-
option is given (the default is to use Roda's route_csrf
|
91
|
-
plugin, as that allows for more secure request-specific
|
92
|
-
tokens).
|
93
|
-
bcrypt :: Used by default for password hashing, can be skipped
|
94
|
-
if password_match? is overridden for custom authentication.
|
95
|
-
argon2 :: Used by the argon2 feature as alternative to bcrypt for
|
96
|
-
password hashing.
|
97
|
-
mail :: Used by default for mailing in the reset_password, verify_account,
|
98
|
-
verify_login_change, change_password_notify, lockout, and
|
99
|
-
email_auth features.
|
100
|
-
rotp :: Used by the otp feature
|
101
|
-
rqrcode :: Used by the otp feature
|
102
|
-
jwt :: Used by the jwt feature
|
103
|
-
webauthn :: Used by the webauthn feature
|
104
|
-
|
105
|
-
You can use <tt>gem install --development rodauth</tt> to install
|
106
|
-
the development dependencies in order to run tests.
|
107
|
-
|
108
|
-
== Security
|
109
|
-
|
110
|
-
=== Password Hash Access Via Database Functions
|
111
|
-
|
112
|
-
By default on PostgreSQL, MySQL, and Microsoft SQL Server, Rodauth
|
113
|
-
uses database functions to access password hashes, with the user
|
114
|
-
running the application unable to get direct access to password
|
115
|
-
hashes. This reduces the risk of an attacker being able to access
|
116
|
-
password hashes and use them to attack other sites.
|
117
|
-
|
118
|
-
The rest of this section describes this feature in more detail, but
|
119
|
-
note that Rodauth does not require this feature be used and works
|
120
|
-
correctly without it. There may be cases where you cannot use
|
121
|
-
this feature, such as when using a different database or when you
|
122
|
-
do not have full control over the database you are using.
|
123
|
-
|
124
|
-
Passwords are hashed using bcrypt by default, and the password hashes are
|
125
|
-
kept in a separate table from the accounts table, with a foreign key
|
126
|
-
referencing the accounts table. Two database functions are added,
|
127
|
-
one to retrieve the salt for a password, and the other to check
|
128
|
-
if a given password hash matches the password hash for the user.
|
129
|
-
|
130
|
-
Two database accounts are used. The first is the account that the
|
131
|
-
application uses, which is referred to as the +app+ account. The +app+
|
132
|
-
account does not have access to read the password hashes. The other
|
133
|
-
account handles password hashes and is referred to as the +ph+
|
134
|
-
account. The +ph+ account sets up the database functions that can
|
135
|
-
retrieve the salt for a given account's password, and check if a
|
136
|
-
password hash matches for a given account. The +ph+ account
|
137
|
-
sets these functions up so that the +app+ account can execute the
|
138
|
-
functions using the +ph+ account's permissions. This allows the
|
139
|
-
+app+ account to check passwords without having access to read
|
140
|
-
password hashes.
|
141
|
-
|
142
|
-
While the +app+ account is not be able to read password hashes, it
|
143
|
-
is still be able to insert password hashes, update passwords hashes,
|
144
|
-
and delete password hashes, so the additional security is not that
|
145
|
-
painful.
|
146
|
-
|
147
|
-
By disallowing the +app+ account access to the password hashes,
|
148
|
-
it is much more difficult for an attacker to access the password
|
149
|
-
hashes, even if they are able to exploit an SQL injection or remote
|
150
|
-
code execution vulnerability in the application.
|
151
|
-
|
152
|
-
The reason for extra security in regards to password hashes stems from
|
153
|
-
the fact that people tend to choose poor passwords and reuse passwords,
|
154
|
-
so a compromise of one database containing password hashes can result
|
155
|
-
in account access on other sites, making password hash storage of
|
156
|
-
critical importance even if the other data stored is not that important.
|
157
|
-
|
158
|
-
If you are storing other sensitive information in your database, you
|
159
|
-
should consider using a similar approach in other areas (or all areas)
|
160
|
-
of your application.
|
161
|
-
|
162
|
-
=== Tokens
|
163
|
-
|
164
|
-
Account verification, password resets, email auth, verify login change,
|
165
|
-
remember, and lockout tokens all use a similar approach. They all
|
166
|
-
provide a token, in the format "account-id_long-random-string". By
|
167
|
-
including the id of the account in the token, an attacker can only
|
168
|
-
attempt to bruteforce the token for a single account, instead of being
|
169
|
-
able to bruteforce tokens for all accounts at once (which would be
|
170
|
-
possible if the token was just a random string).
|
171
|
-
|
172
|
-
Additionally, all comparisons of tokens use a timing-safe comparison
|
173
|
-
function to reduce the risk of timing attacks.
|
174
|
-
|
175
|
-
== HMAC
|
176
|
-
|
177
|
-
By default, for backwards compatibility, Rodauth does not use HMACs,
|
178
|
-
but you are strongly encouraged to use the +hmac_secret+ configuration
|
179
|
-
method to set an HMAC secret. Setting an HMAC secret will enable HMACs
|
180
|
-
for additional security, as described below.
|
181
|
-
|
182
|
-
=== email_base feature
|
183
|
-
|
184
|
-
All features that send email use this feature. Setting +hmac_secret+
|
185
|
-
will make the tokens sent via email use an HMAC, while the raw token
|
186
|
-
stored in the database will not use an HMAC. This will make it so
|
187
|
-
if the tokens in the database are leaked (e.g. via an SQL injection
|
188
|
-
vulnerability), they will not be usable without also having access
|
189
|
-
to the +hmac_secret+. Without an HMAC, the raw token is sent in the
|
190
|
-
email, and if the tokens in the database are leaked, they will be
|
191
|
-
usable.
|
192
|
-
|
193
|
-
To allow for an graceful transition, you can set +allow_raw_email_token?+
|
194
|
-
to true temporarily. This will allow the raw tokens in previous sent
|
195
|
-
emails to still work. This should only be set temporarily as it
|
196
|
-
removes the security that +hmac_secret+ adds. Most features that
|
197
|
-
send email have tokens that expire by default in 1 day. The
|
198
|
-
exception is the verify_account feature, which has tokens that do
|
199
|
-
not expire. For the verify_account feature, if the user requested
|
200
|
-
an email before +hmac_secret+ was set, after +allow_raw_email_token+
|
201
|
-
is no longer set, they will need to request the verification email
|
202
|
-
be resent, in which case they will receive an email with a token
|
203
|
-
that uses an HMAC.
|
204
|
-
|
205
|
-
=== remember feature
|
206
|
-
|
207
|
-
Similar to the email_base feature, this uses HMACs for remember
|
208
|
-
tokens, while storing the raw tokens in the database. This makes
|
209
|
-
it so if the raw tokens in the database are leaked, the remember
|
210
|
-
tokens are not usable without knowledge of the +hmac_secret+.
|
211
|
-
|
212
|
-
The +raw_remember_token_deadline+ configuration method can
|
213
|
-
be set to allow a previously set raw remember token to be used
|
214
|
-
if the deadline for the remember token is before the given time.
|
215
|
-
This allows for graceful transition to using HMACs for remember tokens.
|
216
|
-
By default, the deadline is 14 days after the token is created, so this
|
217
|
-
should be set to 14 days after the time you enable the HMAC for the
|
218
|
-
remember feature if you are using the defaults.
|
219
|
-
|
220
|
-
=== otp feature
|
221
|
-
|
222
|
-
Setting +hmac_secret+ will provide HMACed OTP keys to users, and
|
223
|
-
would store the raw OTP keys in the database. This will make so
|
224
|
-
if the raw OTP keys in the database are leaked, they will not be
|
225
|
-
usable for two factor authentication without knowledge of the +hmac_secret+.
|
226
|
-
|
227
|
-
Unfortunately, there can be no simple graceful transition for existing users.
|
228
|
-
When introducing +hmac_secret+ to a Rodauth installation that already uses
|
229
|
-
the otp feature, you will have to either revoke and replace all OTP keys,
|
230
|
-
set +otp_keys_use_hmac?+ to false and continue to use raw OTP keys, or override
|
231
|
-
+otp_keys_use_hmac?+ to return false if the user was issued an OTP key before
|
232
|
-
+hmac_secret+ was added to the configuration, and true otherwise.
|
233
|
-
+otp_keys_use_hmac?+ defaults to true if +hmac_secret+ is set, and false
|
234
|
-
otherwise.
|
235
|
-
|
236
|
-
If +otp_keys_use_hmac?+ is true, Rodauth will also ensure during OTP setup
|
237
|
-
that the OTP key was generated by the server. If +otp_keys_use_hmac?+ is false,
|
238
|
-
any OTP key in a valid format will be accepted during setup.
|
239
|
-
|
240
|
-
If +otp_keys_use_hmac?+ is true, the jwt and otp features are in use and you
|
241
|
-
are setting up OTP via JSON requests, you need to first send a POST request
|
242
|
-
to the OTP setup route. This will return an error with the +otp_secret+ and
|
243
|
-
+otp_raw_secret+ parameters in the JSON. These parameters should be submitted
|
244
|
-
in the POST request to setup OTP, along with a valid OTP auth code for the
|
245
|
-
+otp_secret+.
|
246
|
-
|
247
|
-
=== webauthn feature
|
248
|
-
|
249
|
-
Setting +hmac_secret+ is required to use the webauthn feature, as it is
|
250
|
-
used for checking that the provided authentication challenges have not
|
251
|
-
been modified.
|
252
|
-
|
253
|
-
=== active_sessions feature
|
254
|
-
|
255
|
-
Setting +hmac_secret+ is required to use the active_sessions feature,
|
256
|
-
as the database stores an HMAC of the active session ID.
|
257
|
-
|
258
|
-
=== single_session feature
|
259
|
-
|
260
|
-
Setting +hmac_secret+ will ensure the single session secret set in the
|
261
|
-
session will be an HMACed. This does not affect security, as the session
|
262
|
-
itself should at the least by protected by an HMAC (if not encrypted).
|
263
|
-
This is only done for consistency, so that the raw tokens in the database
|
264
|
-
are distinct from the tokens provided to the users. To allow for a
|
265
|
-
graceful transition, +allow_raw_single_session_key?+ can be set to true.
|
266
|
-
|
267
|
-
== PostgreSQL Database Setup
|
268
|
-
|
269
|
-
In order to get full advantages of Rodauth's security design on PostgreSQL,
|
270
|
-
multiple database accounts are involved:
|
271
|
-
|
272
|
-
1. database superuser account (usually postgres)
|
273
|
-
2. +app+ account (same name as application)
|
274
|
-
3. +ph+ account (application name with +_password+ appended)
|
275
|
-
|
276
|
-
The database superuser account is used to load extensions related to the
|
277
|
-
database. The application should never be run using the database
|
278
|
-
superuser account.
|
279
|
-
|
280
|
-
=== Create database accounts
|
281
|
-
|
282
|
-
If you are currently running your application using the database superuser
|
283
|
-
account, the first thing you need to do is to create the +app+ database
|
284
|
-
account. It's often best to name this account the same as the
|
285
|
-
database name.
|
286
|
-
|
287
|
-
You should also create the +ph+ database account which will handle access
|
288
|
-
to the password hashes.
|
289
|
-
|
290
|
-
Example for PostgreSQL:
|
291
|
-
|
292
|
-
createuser -U postgres ${DATABASE_NAME}
|
293
|
-
createuser -U postgres ${DATABASE_NAME}_password
|
294
|
-
|
295
|
-
Note that if the database superuser account owns all of the items in the
|
296
|
-
database, you'll need to change the ownership to the database account you
|
297
|
-
just created. See https://gist.github.com/jeremyevans/8483320
|
298
|
-
for a way to do that.
|
299
|
-
|
300
|
-
=== Create database
|
301
|
-
|
302
|
-
In general, the +app+ account is the owner of the database, since it will
|
303
|
-
own most of the tables:
|
304
|
-
|
305
|
-
createdb -U postgres -O ${DATABASE_NAME} ${DATABASE_NAME}
|
306
|
-
|
307
|
-
Note that this is not the most secure way to develop applications. For
|
308
|
-
maximum security, you would want to use a separate database account as
|
309
|
-
the owner of the tables, have the +app+ account not be the
|
310
|
-
owner of any tables, and specifically grant the +app+ account only the
|
311
|
-
minimum access it needs to work correctly. Doing that is beyond the
|
312
|
-
scope of Rodauth, though.
|
313
|
-
|
314
|
-
=== Load extensions
|
315
|
-
|
316
|
-
If you want to use the login features for Rodauth, you need to load the
|
317
|
-
citext extension if you want to support case insensitive logins.
|
318
|
-
|
319
|
-
Example:
|
320
|
-
|
321
|
-
psql -U postgres -c "CREATE EXTENSION citext" ${DATABASE_NAME}
|
322
|
-
|
323
|
-
Note that on Heroku, this extension can be loaded using a standard database
|
324
|
-
account. If you want logins to be case sensitive (generally considered a
|
325
|
-
bad idea), you don't need to use the PostgreSQL citext extension. Just
|
326
|
-
remember to modify the migration below to use +String+ instead of +citext+
|
327
|
-
for the email in that case.
|
328
|
-
|
329
|
-
=== Grant schema rights (PostgreSQL 15+)
|
330
|
-
|
331
|
-
PostgreSQL 15 changed default database security so that only the database
|
332
|
-
owner has writable access to the public schema. Rodauth expects the
|
333
|
-
+ph+ account to have writable access to the public schema when setting
|
334
|
-
things up. Temporarily grant that access (it will be revoked after the
|
335
|
-
migration has run)
|
336
|
-
|
337
|
-
psql -U postgres -c "GRANT CREATE ON SCHEMA public TO ${DATABASE_NAME}_password" ${DATABASE_NAME}
|
338
|
-
|
339
|
-
=== Using non-default schema
|
340
|
-
|
341
|
-
PostgreSQL sets up new tables in the public schema by default.
|
342
|
-
If you would like to use separate schemas per user, you can do:
|
343
|
-
|
344
|
-
psql -U postgres -c "DROP SCHEMA public;" ${DATABASE_NAME}
|
345
|
-
psql -U postgres -c "CREATE SCHEMA AUTHORIZATION ${DATABASE_NAME};" ${DATABASE_NAME}
|
346
|
-
psql -U postgres -c "CREATE SCHEMA AUTHORIZATION ${DATABASE_NAME}_password;" ${DATABASE_NAME}
|
347
|
-
psql -U postgres -c "GRANT USAGE ON SCHEMA ${DATABASE_NAME} TO ${DATABASE_NAME}_password;" ${DATABASE_NAME}
|
348
|
-
psql -U postgres -c "GRANT USAGE ON SCHEMA ${DATABASE_NAME}_password TO ${DATABASE_NAME};" ${DATABASE_NAME}
|
349
|
-
|
350
|
-
You'll need to modify the code to load the extension to specify the schema:
|
351
|
-
|
352
|
-
psql -U postgres -c "CREATE EXTENSION citext SCHEMA ${DATABASE_NAME}" ${DATABASE_NAME}
|
353
|
-
|
354
|
-
When running the migration for the +ph+ user you'll need to modify a couple
|
355
|
-
things for the schema changes:
|
356
|
-
|
357
|
-
create_table(:account_password_hashes) do
|
358
|
-
foreign_key :id, Sequel[:${DATABASE_NAME}][:accounts], primary_key: true, type: :Bignum
|
359
|
-
String :password_hash, null: false
|
360
|
-
end
|
361
|
-
Rodauth.create_database_authentication_functions(self, table_name: Sequel[:${DATABASE_NAME}_password][:account_password_hashes])
|
362
|
-
|
363
|
-
# if using the disallow_password_reuse feature:
|
364
|
-
create_table(:account_previous_password_hashes) do
|
365
|
-
primary_key :id, type: :Bignum
|
366
|
-
foreign_key :account_id, Sequel[:${DATABASE_NAME}][:accounts], type: :Bignum
|
367
|
-
String :password_hash, null: false
|
368
|
-
end
|
369
|
-
Rodauth.create_database_previous_password_check_functions(self, table_name: Sequel[:${DATABASE_NAME}_password][:account_previous_password_hashes])
|
370
|
-
|
371
|
-
You'll also need to use the following Rodauth configuration methods so that the
|
372
|
-
app account calls functions in a separate schema:
|
373
|
-
|
374
|
-
function_name do |name|
|
375
|
-
"${DATABASE_NAME}_password.#{name}"
|
376
|
-
end
|
377
|
-
password_hash_table Sequel[:${DATABASE_NAME}_password][:account_password_hashes]
|
378
|
-
|
379
|
-
# if using the disallow_password_reuse feature:
|
380
|
-
previous_password_hash_table Sequel[:${DATABASE_NAME}_password][:account_previous_password_hashes]
|
381
|
-
|
382
|
-
== MySQL Database Setup
|
383
|
-
|
384
|
-
MySQL does not have the concept of object owners, and MySQL's GRANT/REVOKE
|
385
|
-
support is much more limited than PostgreSQL's. When using MySQL, it is
|
386
|
-
recommended to GRANT the +ph+ account ALL privileges on the database,
|
387
|
-
including the ability to GRANT permissions to the +app+ account:
|
388
|
-
|
389
|
-
CREATE USER '${DATABASE_NAME}'@'localhost' IDENTIFIED BY '${PASSWORD}';
|
390
|
-
CREATE USER '${DATABASE_NAME}_password'@'localhost' IDENTIFIED BY '${OTHER_PASSWORD}';
|
391
|
-
GRANT ALL ON ${DATABASE_NAME}.* TO '${DATABASE_NAME}_password'@'localhost' WITH GRANT OPTION;
|
392
|
-
|
393
|
-
You should run all migrations as the +ph+ account, and GRANT specific access
|
394
|
-
to the +app+ account as needed.
|
395
|
-
|
396
|
-
Adding the database functions on MySQL may require setting the
|
397
|
-
<tt>log_bin_trust_function_creators=1</tt> setting in the MySQL configuration.
|
398
|
-
|
399
|
-
== Microsoft SQL Server Database Setup
|
400
|
-
|
401
|
-
Microsoft SQL Server has a concept of database owners, but similar to MySQL
|
402
|
-
usage it's recommended to use the +ph+ account as the superuser for the
|
403
|
-
database, and have it GRANT permissions to the +app+ account:
|
404
|
-
|
405
|
-
CREATE LOGIN rodauth_test WITH PASSWORD = 'rodauth_test';
|
406
|
-
CREATE LOGIN rodauth_test_password WITH PASSWORD = 'rodauth_test';
|
407
|
-
CREATE DATABASE rodauth_test;
|
408
|
-
USE rodauth_test;
|
409
|
-
CREATE USER rodauth_test FOR LOGIN rodauth_test;
|
410
|
-
GRANT CONNECT, EXECUTE TO rodauth_test;
|
411
|
-
EXECUTE sp_changedbowner 'rodauth_test_password';
|
412
|
-
|
413
|
-
You should run all migrations as the +ph+ account, and GRANT specific access
|
414
|
-
to the +app+ account as needed.
|
415
|
-
|
416
|
-
== Creating tables
|
417
|
-
|
418
|
-
Because two different database accounts are used, two different migrations
|
419
|
-
are required, one for each database account. Here are example migrations.
|
420
|
-
You can modify them to add support for additional columns, or remove tables
|
421
|
-
or columns related to features that you don't need.
|
422
|
-
|
423
|
-
First migration. On PostgreSQL, this should be run with the +app+ account,
|
424
|
-
on MySQL and Microsoft SQL Server this should be run with the +ph+ account.
|
425
|
-
|
426
|
-
Note that these migrations require Sequel 4.35.0+.
|
427
|
-
|
428
|
-
Sequel.migration do
|
429
|
-
up do
|
430
|
-
extension :date_arithmetic
|
431
|
-
|
432
|
-
# Used by the account verification and close account features
|
433
|
-
create_table(:account_statuses) do
|
434
|
-
Integer :id, primary_key: true
|
435
|
-
String :name, null: false, unique: true
|
436
|
-
end
|
437
|
-
from(:account_statuses).import([:id, :name], [[1, 'Unverified'], [2, 'Verified'], [3, 'Closed']])
|
438
|
-
|
439
|
-
db = self
|
440
|
-
create_table(:accounts) do
|
441
|
-
primary_key :id, type: :Bignum
|
442
|
-
foreign_key :status_id, :account_statuses, null: false, default: 1
|
443
|
-
if db.database_type == :postgres
|
444
|
-
citext :email, null: false
|
445
|
-
constraint :valid_email, email: /^[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+$/
|
446
|
-
else
|
447
|
-
String :email, null: false
|
448
|
-
end
|
449
|
-
if db.supports_partial_indexes?
|
450
|
-
index :email, unique: true, where: {status_id: [1, 2]}
|
451
|
-
else
|
452
|
-
index :email, unique: true
|
453
|
-
end
|
454
|
-
end
|
455
|
-
|
456
|
-
deadline_opts = proc do |days|
|
457
|
-
if database_type == :mysql
|
458
|
-
{null: false}
|
459
|
-
else
|
460
|
-
{null: false, default: Sequel.date_add(Sequel::CURRENT_TIMESTAMP, days: days)}
|
461
|
-
end
|
462
|
-
end
|
463
|
-
|
464
|
-
# Used by the audit logging feature
|
465
|
-
json_type = case database_type
|
466
|
-
when :postgres
|
467
|
-
:jsonb
|
468
|
-
when :sqlite, :mysql
|
469
|
-
:json
|
470
|
-
else
|
471
|
-
String
|
472
|
-
end
|
473
|
-
create_table(:account_authentication_audit_logs) do
|
474
|
-
primary_key :id, type: :Bignum
|
475
|
-
foreign_key :account_id, :accounts, null: false, type: :Bignum
|
476
|
-
DateTime :at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
477
|
-
String :message, null: false
|
478
|
-
column :metadata, json_type
|
479
|
-
index [:account_id, :at], name: :audit_account_at_idx
|
480
|
-
index :at, name: :audit_at_idx
|
481
|
-
end
|
482
|
-
|
483
|
-
# Used by the password reset feature
|
484
|
-
create_table(:account_password_reset_keys) do
|
485
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
486
|
-
String :key, null: false
|
487
|
-
DateTime :deadline, deadline_opts[1]
|
488
|
-
DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
|
489
|
-
end
|
490
|
-
|
491
|
-
# Used by the jwt refresh feature
|
492
|
-
create_table(:account_jwt_refresh_keys) do
|
493
|
-
primary_key :id, type: :Bignum
|
494
|
-
foreign_key :account_id, :accounts, null: false, type: :Bignum
|
495
|
-
String :key, null: false
|
496
|
-
DateTime :deadline, deadline_opts[1]
|
497
|
-
index :account_id, name: :account_jwt_rk_account_id_idx
|
498
|
-
end
|
499
|
-
|
500
|
-
# Used by the account verification feature
|
501
|
-
create_table(:account_verification_keys) do
|
502
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
503
|
-
String :key, null: false
|
504
|
-
DateTime :requested_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
505
|
-
DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
|
506
|
-
end
|
507
|
-
|
508
|
-
# Used by the verify login change feature
|
509
|
-
create_table(:account_login_change_keys) do
|
510
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
511
|
-
String :key, null: false
|
512
|
-
String :login, null: false
|
513
|
-
DateTime :deadline, deadline_opts[1]
|
514
|
-
end
|
515
|
-
|
516
|
-
# Used by the remember me feature
|
517
|
-
create_table(:account_remember_keys) do
|
518
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
519
|
-
String :key, null: false
|
520
|
-
DateTime :deadline, deadline_opts[14]
|
521
|
-
end
|
522
|
-
|
523
|
-
# Used by the lockout feature
|
524
|
-
create_table(:account_login_failures) do
|
525
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
526
|
-
Integer :number, null: false, default: 1
|
527
|
-
end
|
528
|
-
create_table(:account_lockouts) do
|
529
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
530
|
-
String :key, null: false
|
531
|
-
DateTime :deadline, deadline_opts[1]
|
532
|
-
DateTime :email_last_sent
|
533
|
-
end
|
534
|
-
|
535
|
-
# Used by the email auth feature
|
536
|
-
create_table(:account_email_auth_keys) do
|
537
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
538
|
-
String :key, null: false
|
539
|
-
DateTime :deadline, deadline_opts[1]
|
540
|
-
DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
|
541
|
-
end
|
542
|
-
|
543
|
-
# Used by the password expiration feature
|
544
|
-
create_table(:account_password_change_times) do
|
545
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
546
|
-
DateTime :changed_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
547
|
-
end
|
548
|
-
|
549
|
-
# Used by the account expiration feature
|
550
|
-
create_table(:account_activity_times) do
|
551
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
552
|
-
DateTime :last_activity_at, null: false
|
553
|
-
DateTime :last_login_at, null: false
|
554
|
-
DateTime :expired_at
|
555
|
-
end
|
556
|
-
|
557
|
-
# Used by the single session feature
|
558
|
-
create_table(:account_session_keys) do
|
559
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
560
|
-
String :key, null: false
|
561
|
-
end
|
562
|
-
|
563
|
-
# Used by the active sessions feature
|
564
|
-
create_table(:account_active_session_keys) do
|
565
|
-
foreign_key :account_id, :accounts, type: :Bignum
|
566
|
-
String :session_id
|
567
|
-
Time :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
568
|
-
Time :last_use, null: false, default: Sequel::CURRENT_TIMESTAMP
|
569
|
-
primary_key [:account_id, :session_id]
|
570
|
-
end
|
571
|
-
|
572
|
-
# Used by the webauthn feature
|
573
|
-
create_table(:account_webauthn_user_ids) do
|
574
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
575
|
-
String :webauthn_id, null: false
|
576
|
-
end
|
577
|
-
create_table(:account_webauthn_keys) do
|
578
|
-
foreign_key :account_id, :accounts, type: :Bignum
|
579
|
-
String :webauthn_id
|
580
|
-
String :public_key, null: false
|
581
|
-
Integer :sign_count, null: false
|
582
|
-
Time :last_use, null: false, default: Sequel::CURRENT_TIMESTAMP
|
583
|
-
primary_key [:account_id, :webauthn_id]
|
584
|
-
end
|
585
|
-
|
586
|
-
# Used by the otp feature
|
587
|
-
create_table(:account_otp_keys) do
|
588
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
589
|
-
String :key, null: false
|
590
|
-
Integer :num_failures, null: false, default: 0
|
591
|
-
Time :last_use, null: false, default: Sequel::CURRENT_TIMESTAMP
|
592
|
-
end
|
593
|
-
|
594
|
-
# Used by the otp_unlock feature
|
595
|
-
create_table(:account_otp_unlocks) do
|
596
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
597
|
-
Integer :num_successes, null: false, default: 1
|
598
|
-
Time :next_auth_attempt_after, null: false, default: Sequel::CURRENT_TIMESTAMP
|
599
|
-
end
|
600
|
-
|
601
|
-
# Used by the recovery codes feature
|
602
|
-
create_table(:account_recovery_codes) do
|
603
|
-
foreign_key :id, :accounts, type: :Bignum
|
604
|
-
String :code
|
605
|
-
primary_key [:id, :code]
|
606
|
-
end
|
607
|
-
|
608
|
-
# Used by the sms codes feature
|
609
|
-
create_table(:account_sms_codes) do
|
610
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
611
|
-
String :phone_number, null: false
|
612
|
-
Integer :num_failures
|
613
|
-
String :code
|
614
|
-
DateTime :code_issued_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
615
|
-
end
|
616
|
-
|
617
|
-
case database_type
|
618
|
-
when :postgres
|
619
|
-
user = get(Sequel.lit('current_user')) + '_password'
|
620
|
-
run "GRANT REFERENCES ON accounts TO #{user}"
|
621
|
-
when :mysql, :mssql
|
622
|
-
user = if database_type == :mysql
|
623
|
-
get(Sequel.lit('current_user')).sub(/_password@/, '@')
|
624
|
-
else
|
625
|
-
get(Sequel.function(:DB_NAME))
|
626
|
-
end
|
627
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_statuses TO #{user}"
|
628
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON accounts TO #{user}"
|
629
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_authentication_audit_logs TO #{user}"
|
630
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_password_reset_keys TO #{user}"
|
631
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_jwt_refresh_keys TO #{user}"
|
632
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_verification_keys TO #{user}"
|
633
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_login_change_keys TO #{user}"
|
634
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_remember_keys TO #{user}"
|
635
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_login_failures TO #{user}"
|
636
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_email_auth_keys TO #{user}"
|
637
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_lockouts TO #{user}"
|
638
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_password_change_times TO #{user}"
|
639
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_activity_times TO #{user}"
|
640
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_session_keys TO #{user}"
|
641
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_active_session_keys TO #{user}"
|
642
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_webauthn_user_ids TO #{user}"
|
643
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_webauthn_keys TO #{user}"
|
644
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_otp_keys TO #{user}"
|
645
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_otp_unlocks TO #{user}"
|
646
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_recovery_codes TO #{user}"
|
647
|
-
run "GRANT SELECT, INSERT, UPDATE, DELETE ON account_sms_codes TO #{user}"
|
648
|
-
end
|
649
|
-
end
|
650
|
-
|
651
|
-
down do
|
652
|
-
drop_table(:account_sms_codes,
|
653
|
-
:account_recovery_codes,
|
654
|
-
:account_otp_unlocks,
|
655
|
-
:account_otp_keys,
|
656
|
-
:account_webauthn_keys,
|
657
|
-
:account_webauthn_user_ids,
|
658
|
-
:account_session_keys,
|
659
|
-
:account_active_session_keys,
|
660
|
-
:account_activity_times,
|
661
|
-
:account_password_change_times,
|
662
|
-
:account_email_auth_keys,
|
663
|
-
:account_lockouts,
|
664
|
-
:account_login_failures,
|
665
|
-
:account_remember_keys,
|
666
|
-
:account_login_change_keys,
|
667
|
-
:account_verification_keys,
|
668
|
-
:account_jwt_refresh_keys,
|
669
|
-
:account_password_reset_keys,
|
670
|
-
:account_authentication_audit_logs,
|
671
|
-
:accounts,
|
672
|
-
:account_statuses)
|
673
|
-
end
|
674
|
-
end
|
675
|
-
|
676
|
-
Second migration, run using the +ph+ account:
|
677
|
-
|
678
|
-
require 'rodauth/migrations'
|
679
|
-
|
680
|
-
Sequel.migration do
|
681
|
-
up do
|
682
|
-
create_table(:account_password_hashes) do
|
683
|
-
foreign_key :id, :accounts, primary_key: true, type: :Bignum
|
684
|
-
String :password_hash, null: false
|
685
|
-
end
|
686
|
-
Rodauth.create_database_authentication_functions(self)
|
687
|
-
case database_type
|
688
|
-
when :postgres
|
689
|
-
user = get(Sequel.lit('current_user')).sub(/_password\z/, '')
|
690
|
-
run "REVOKE ALL ON account_password_hashes FROM public"
|
691
|
-
run "REVOKE ALL ON FUNCTION rodauth_get_salt(int8) FROM public"
|
692
|
-
run "REVOKE ALL ON FUNCTION rodauth_valid_password_hash(int8, text) FROM public"
|
693
|
-
run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
|
694
|
-
run "GRANT SELECT(id) ON account_password_hashes TO #{user}"
|
695
|
-
run "GRANT EXECUTE ON FUNCTION rodauth_get_salt(int8) TO #{user}"
|
696
|
-
run "GRANT EXECUTE ON FUNCTION rodauth_valid_password_hash(int8, text) TO #{user}"
|
697
|
-
when :mysql
|
698
|
-
user = get(Sequel.lit('current_user')).sub(/_password@/, '@')
|
699
|
-
db_name = get(Sequel.function(:database))
|
700
|
-
run "GRANT EXECUTE ON #{db_name}.* TO #{user}"
|
701
|
-
run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
|
702
|
-
run "GRANT SELECT (id) ON account_password_hashes TO #{user}"
|
703
|
-
when :mssql
|
704
|
-
user = get(Sequel.function(:DB_NAME))
|
705
|
-
run "GRANT EXECUTE ON rodauth_get_salt TO #{user}"
|
706
|
-
run "GRANT EXECUTE ON rodauth_valid_password_hash TO #{user}"
|
707
|
-
run "GRANT INSERT, UPDATE, DELETE ON account_password_hashes TO #{user}"
|
708
|
-
run "GRANT SELECT ON account_password_hashes(id) TO #{user}"
|
709
|
-
end
|
710
|
-
|
711
|
-
# Used by the disallow_password_reuse feature
|
712
|
-
create_table(:account_previous_password_hashes) do
|
713
|
-
primary_key :id, type: :Bignum
|
714
|
-
foreign_key :account_id, :accounts, type: :Bignum
|
715
|
-
String :password_hash, null: false
|
716
|
-
end
|
717
|
-
Rodauth.create_database_previous_password_check_functions(self)
|
718
|
-
|
719
|
-
case database_type
|
720
|
-
when :postgres
|
721
|
-
user = get(Sequel.lit('current_user')).sub(/_password\z/, '')
|
722
|
-
run "REVOKE ALL ON account_previous_password_hashes FROM public"
|
723
|
-
run "REVOKE ALL ON FUNCTION rodauth_get_previous_salt(int8) FROM public"
|
724
|
-
run "REVOKE ALL ON FUNCTION rodauth_previous_password_hash_match(int8, text) FROM public"
|
725
|
-
run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
|
726
|
-
run "GRANT SELECT(id, account_id) ON account_previous_password_hashes TO #{user}"
|
727
|
-
run "GRANT USAGE ON account_previous_password_hashes_id_seq TO #{user}"
|
728
|
-
run "GRANT EXECUTE ON FUNCTION rodauth_get_previous_salt(int8) TO #{user}"
|
729
|
-
run "GRANT EXECUTE ON FUNCTION rodauth_previous_password_hash_match(int8, text) TO #{user}"
|
730
|
-
when :mysql
|
731
|
-
user = get(Sequel.lit('current_user')).sub(/_password@/, '@')
|
732
|
-
db_name = get(Sequel.function(:database))
|
733
|
-
run "GRANT EXECUTE ON #{db_name}.* TO #{user}"
|
734
|
-
run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
|
735
|
-
run "GRANT SELECT (id, account_id) ON account_previous_password_hashes TO #{user}"
|
736
|
-
when :mssql
|
737
|
-
user = get(Sequel.function(:DB_NAME))
|
738
|
-
run "GRANT EXECUTE ON rodauth_get_previous_salt TO #{user}"
|
739
|
-
run "GRANT EXECUTE ON rodauth_previous_password_hash_match TO #{user}"
|
740
|
-
run "GRANT INSERT, UPDATE, DELETE ON account_previous_password_hashes TO #{user}"
|
741
|
-
run "GRANT SELECT ON account_previous_password_hashes(id, account_id) TO #{user}"
|
742
|
-
end
|
743
|
-
end
|
744
|
-
|
745
|
-
down do
|
746
|
-
Rodauth.drop_database_previous_password_check_functions(self)
|
747
|
-
Rodauth.drop_database_authentication_functions(self)
|
748
|
-
drop_table(:account_previous_password_hashes, :account_password_hashes)
|
749
|
-
end
|
750
|
-
end
|
751
|
-
|
752
|
-
To support multiple separate migration users, you can run the migration
|
753
|
-
for the password user using Sequel's migration API:
|
754
|
-
|
755
|
-
Sequel.extension :migration
|
756
|
-
Sequel.postgres('DATABASE_NAME', user: 'PASSWORD_USER_NAME') do |db|
|
757
|
-
Sequel::Migrator.run(db, 'path/to/password_user/migrations', table: 'schema_info_password')
|
758
|
-
end
|
759
|
-
|
760
|
-
If the database is not PostgreSQL, MySQL, or Microsoft SQL Server, or you
|
761
|
-
cannot use multiple user accounts, just combine the two migrations into a
|
762
|
-
single migration, removing all the code related to database permissions
|
763
|
-
and database functions.
|
764
|
-
|
765
|
-
One thing to notice in the above migrations is that Rodauth uses additional
|
766
|
-
tables for additional features, instead of additional columns in a single
|
767
|
-
table.
|
768
|
-
|
769
|
-
=== Revoking schema rights (PostgreSQL 15+)
|
770
|
-
|
771
|
-
If you explicit granted access to the public schema before running the
|
772
|
-
migration, revoke it afterward:
|
773
|
-
|
774
|
-
psql -U postgres -c "REVOKE CREATE ON SCHEMA public FROM ${DATABASE_NAME}_password" ${DATABASE_NAME}
|
775
|
-
|
776
|
-
=== Locking Down (PostgreSQL only)
|
777
|
-
|
778
|
-
After running the migrations, you can increase security slightly by making
|
779
|
-
it not possible for the +ph+ account to login to the database directly.
|
780
|
-
This can be accomplished by modifying the +pg_hba.conf+ file. You can also
|
781
|
-
consider restricting access using GRANT/REVOKE.
|
782
|
-
|
783
|
-
You can restrict access to the database itself to just the +app+ account. You
|
784
|
-
can run this using the +app+ account, since that account owns the database:
|
785
|
-
|
786
|
-
GRANT ALL ON DATABASE ${DATABASE_NAME} TO ${DATABASE_NAME};
|
787
|
-
REVOKE ALL ON DATABASE ${DATABASE_NAME} FROM public;
|
788
|
-
|
789
|
-
You can also restrict access to the public schema (this is not needed if you
|
790
|
-
are using a custom schema). Note that by default, the database superuser
|
791
|
-
owns the public schema, so you have to run this as the database superuser
|
792
|
-
account (generally +postgres+):
|
793
|
-
|
794
|
-
GRANT ALL ON SCHEMA public TO ${DATABASE_NAME};
|
795
|
-
GRANT USAGE ON SCHEMA public TO ${DATABASE_NAME}_password;
|
796
|
-
REVOKE ALL ON SCHEMA public FROM public;
|
797
|
-
|
798
|
-
If you are using MySQL or Microsoft SQL Server, please consult their
|
799
|
-
documentation for how to restrict access so that the +ph+ account cannot
|
800
|
-
login directly.
|
801
|
-
|
802
|
-
== Usage
|
803
|
-
|
804
|
-
=== Basic Usage
|
805
|
-
|
806
|
-
Rodauth is a Roda plugin and loaded the same way other Roda plugins
|
807
|
-
are loaded:
|
808
|
-
|
809
|
-
plugin :rodauth do
|
810
|
-
end
|
811
|
-
|
812
|
-
The block passed to the plugin call uses the Rodauth configuration DSL.
|
813
|
-
The one configuration method that should always be used is +enable+,
|
814
|
-
which chooses which features you would like to load:
|
815
|
-
|
816
|
-
plugin :rodauth do
|
817
|
-
enable :login, :logout
|
818
|
-
end
|
819
|
-
|
820
|
-
Once features are loaded, you can use any of the configuration methods
|
821
|
-
supported by the features. There are two types of configuration
|
822
|
-
methods. The first type are called auth methods, and they take a
|
823
|
-
block which overrides the default method that Rodauth uses. Inside the
|
824
|
-
block, you can call super if you want to get the default behavior, though
|
825
|
-
you must provide explicit arguments to super. There is no need to
|
826
|
-
call super in before or after hooks, though. For example, if you want to
|
827
|
-
add additional logging when a user logs in:
|
828
|
-
|
829
|
-
plugin :rodauth do
|
830
|
-
enable :login, :logout
|
831
|
-
after_login do
|
832
|
-
LOGGER.info "#{account[:email]} logged in!"
|
833
|
-
end
|
834
|
-
end
|
835
|
-
|
836
|
-
Inside the block, you are in the context of the Rodauth::Auth
|
837
|
-
instance related to the request. This object has access to everything
|
838
|
-
related to the request via methods:
|
839
|
-
|
840
|
-
request :: RodaRequest instance
|
841
|
-
response :: RodaResponse instance
|
842
|
-
scope :: Roda instance
|
843
|
-
session :: session hash
|
844
|
-
flash :: flash message hash
|
845
|
-
account :: account hash (if set by an earlier Rodauth method)
|
846
|
-
current_route :: route name symbol (if Rodauth is handling the route)
|
847
|
-
|
848
|
-
So if you want to log the IP address for the user during login:
|
849
|
-
|
850
|
-
plugin :rodauth do
|
851
|
-
enable :login, :logout
|
852
|
-
after_login do
|
853
|
-
LOGGER.info "#{account[:email]} logged in from #{request.ip}"
|
854
|
-
end
|
855
|
-
end
|
856
|
-
|
857
|
-
The second type of configuration methods are called auth value
|
858
|
-
methods. They are similar to auth methods, but instead of just
|
859
|
-
accepting a block, they can optionally accept a single argument
|
860
|
-
without a block, which will be treated as a block that just returns
|
861
|
-
that value. For example, the accounts_table method sets the database
|
862
|
-
table storing accounts, so to override it, you can call the method
|
863
|
-
with a symbol for the table:
|
864
|
-
|
865
|
-
plugin :rodauth do
|
866
|
-
enable :login, :logout
|
867
|
-
accounts_table :users
|
868
|
-
end
|
869
|
-
|
870
|
-
Note that all auth value methods can still take a block, allowing
|
871
|
-
overriding for all behavior, using any information from the request:
|
872
|
-
|
873
|
-
plugin :rodauth do
|
874
|
-
enable :login, :logout
|
875
|
-
accounts_table do
|
876
|
-
request.ip.start_with?("192.168.1.") ? :admins : :users
|
877
|
-
end
|
878
|
-
end
|
879
|
-
|
880
|
-
By allowing every configuration method to take a block, Rodauth
|
881
|
-
should be flexible enough to integrate into most legacy systems.
|
882
|
-
|
883
|
-
=== Plugin Options
|
884
|
-
|
885
|
-
When loading the rodauth plugin, you can also pass an options hash,
|
886
|
-
which configures which dependent plugins should be loaded. Options:
|
887
|
-
|
888
|
-
:csrf :: Set to +false+ to not load a csrf plugin. Set to +:rack_csrf+
|
889
|
-
to use the csrf plugin instead of the route_csrf plugin.
|
890
|
-
:flash :: Set to +false+ to not load the flash plugin
|
891
|
-
:render :: Set to +false+ to not load the render plugin. This is useful
|
892
|
-
to avoid the dependency on tilt when using alternative view libaries.
|
893
|
-
:json :: Set to +true+ to load the json and json_parser plugins. Set
|
894
|
-
to +:only+ to only load those plugins and not any other plugins.
|
895
|
-
Note that if you are enabling features that send email, you
|
896
|
-
still need to load the render plugin manually.
|
897
|
-
:name :: Provide a name for the given Rodauth configuration, used to
|
898
|
-
support multiple Rodauth configurations in a given Roda application.
|
899
|
-
:auth_class :: Provide a specific Rodauth::Auth subclass that should be set
|
900
|
-
on the Roda application. By default, an anonymous
|
901
|
-
Rodauth::Auth subclass is created.
|
902
|
-
|
903
|
-
=== Feature Documentation
|
904
|
-
|
905
|
-
The options/methods for the supported features are listed on a
|
906
|
-
separate page per feature. If these links are not active, please
|
907
|
-
view the appropriate file in the doc directory.
|
908
|
-
|
909
|
-
* {Base}[rdoc-ref:doc/base.rdoc] (this feature is autoloaded)
|
910
|
-
* {Login Password Requirements Base}[rdoc-ref:doc/login_password_requirements_base.rdoc] (this feature is autoloaded by features that set logins/passwords)
|
911
|
-
* {Email Base}[rdoc-ref:doc/email_base.rdoc] (this feature is autoloaded by features that send email)
|
912
|
-
* {Two Factor Base}[rdoc-ref:doc/two_factor_base.rdoc] (this feature is autoloaded by 2 factor authentication features)
|
913
|
-
* {Account Expiration}[rdoc-ref:doc/account_expiration.rdoc]
|
914
|
-
* {Active Sessions}[rdoc-ref:doc/active_sessions.rdoc]
|
915
|
-
* {Audit Logging}[rdoc-ref:doc/audit_logging.rdoc]
|
916
|
-
* {Argon2}[rdoc-ref:doc/argon2.rdoc]
|
917
|
-
* {Change Login}[rdoc-ref:doc/change_login.rdoc]
|
918
|
-
* {Change Password}[rdoc-ref:doc/change_password.rdoc]
|
919
|
-
* {Change Password Notify}[rdoc-ref:doc/change_password_notify.rdoc]
|
920
|
-
* {Close Account}[rdoc-ref:doc/close_account.rdoc]
|
921
|
-
* {Confirm Password}[rdoc-ref:doc/confirm_password.rdoc]
|
922
|
-
* {Create Account}[rdoc-ref:doc/create_account.rdoc]
|
923
|
-
* {Disallow Common Passwords}[rdoc-ref:doc/disallow_common_passwords.rdoc]
|
924
|
-
* {Disallow Password Reuse}[rdoc-ref:doc/disallow_password_reuse.rdoc]
|
925
|
-
* {Email Authentication}[rdoc-ref:doc/email_auth.rdoc]
|
926
|
-
* {HTTP Basic Auth}[rdoc-ref:doc/http_basic_auth.rdoc]
|
927
|
-
* {Internal Request}[rdoc-ref:doc/internal_request.rdoc]
|
928
|
-
* {JSON}[rdoc-ref:doc/json.rdoc]
|
929
|
-
* {JWT CORS}[rdoc-ref:doc/jwt_cors.rdoc]
|
930
|
-
* {JWT Refresh}[rdoc-ref:doc/jwt_refresh.rdoc]
|
931
|
-
* {JWT}[rdoc-ref:doc/jwt.rdoc]
|
932
|
-
* {Lockout}[rdoc-ref:doc/lockout.rdoc]
|
933
|
-
* {Login}[rdoc-ref:doc/login.rdoc]
|
934
|
-
* {Logout}[rdoc-ref:doc/logout.rdoc]
|
935
|
-
* {OTP}[rdoc-ref:doc/otp.rdoc]
|
936
|
-
* {OTP Lockout Email}[rdoc-ref:doc/otp_lockout_email.rdoc]
|
937
|
-
* {OTP Modify Email}[rdoc-ref:doc/otp_modify_email.rdoc]
|
938
|
-
* {OTP Unlock}[rdoc-ref:doc/otp_unlock.rdoc]
|
939
|
-
* {Password Complexity}[rdoc-ref:doc/password_complexity.rdoc]
|
940
|
-
* {Password Expiration}[rdoc-ref:doc/password_expiration.rdoc]
|
941
|
-
* {Password Grace Period}[rdoc-ref:doc/password_grace_period.rdoc]
|
942
|
-
* {Password Pepper}[rdoc-ref:doc/password_pepper.rdoc]
|
943
|
-
* {Path Class Methods}[rdoc-ref:doc/path_class_methods.rdoc]
|
944
|
-
* {Recovery Codes}[rdoc-ref:doc/recovery_codes.rdoc]
|
945
|
-
* {Remember}[rdoc-ref:doc/remember.rdoc]
|
946
|
-
* {Reset Password}[rdoc-ref:doc/reset_password.rdoc]
|
947
|
-
* {Reset Password Notify}[rdoc-ref:doc/reset_password_notify.rdoc]
|
948
|
-
* {Session Expiration}[rdoc-ref:doc/session_expiration.rdoc]
|
949
|
-
* {Single Session}[rdoc-ref:doc/single_session.rdoc]
|
950
|
-
* {SMS Codes}[rdoc-ref:doc/sms_codes.rdoc]
|
951
|
-
* {Update Password Hash}[rdoc-ref:doc/update_password_hash.rdoc]
|
952
|
-
* {Verify Account}[rdoc-ref:doc/verify_account.rdoc]
|
953
|
-
* {Verify Account Grace Period}[rdoc-ref:doc/verify_account_grace_period.rdoc]
|
954
|
-
* {Verify Login Change}[rdoc-ref:doc/verify_login_change.rdoc]
|
955
|
-
* {WebAuthn}[rdoc-ref:doc/webauthn.rdoc]
|
956
|
-
* {WebAuthn Autofill}[rdoc-ref:doc/webauthn_autofill.rdoc]
|
957
|
-
* {WebAuthn Login}[rdoc-ref:doc/webauthn_login.rdoc]
|
958
|
-
* {WebAuthn Modify Email}[rdoc-ref:doc/webauthn_modify_email.rdoc]
|
959
|
-
* {WebAuthn Verify Account}[rdoc-ref:doc/webauthn_verify_account.rdoc]
|
960
|
-
|
961
|
-
=== Calling Rodauth in the Routing Tree
|
962
|
-
|
963
|
-
In general, you will usually want to call +r.rodauth+ early in your
|
964
|
-
route block:
|
965
|
-
|
966
|
-
route do |r|
|
967
|
-
r.rodauth
|
968
|
-
|
969
|
-
# ...
|
970
|
-
end
|
971
|
-
|
972
|
-
Note that will allow Rodauth to run, but it won't force people
|
973
|
-
to login or add any security to your site. If you want to force
|
974
|
-
all users to login, you need to redirect to them login page if
|
975
|
-
they are not already logged in:
|
976
|
-
|
977
|
-
route do |r|
|
978
|
-
r.rodauth
|
979
|
-
rodauth.require_authentication
|
980
|
-
|
981
|
-
# ...
|
982
|
-
end
|
983
|
-
|
984
|
-
If only certain parts of your site require logins, then you can
|
985
|
-
only redirect if they are not logged in certain branches of the
|
986
|
-
routing tree:
|
987
|
-
|
988
|
-
route do |r|
|
989
|
-
r.rodauth
|
990
|
-
|
991
|
-
r.on "admin" do
|
992
|
-
rodauth.require_authentication
|
993
|
-
|
994
|
-
# ...
|
995
|
-
end
|
996
|
-
|
997
|
-
# ...
|
998
|
-
end
|
999
|
-
|
1000
|
-
In some cases you may want to have rodauth run inside a branch of
|
1001
|
-
the routing tree, instead of in the root. You can do this by
|
1002
|
-
setting a +:prefix+ when configuring Rodauth, and calling +r.rodauth+
|
1003
|
-
inside a matching routing tree branch:
|
1004
|
-
|
1005
|
-
plugin :rodauth do
|
1006
|
-
enable :login, :logout
|
1007
|
-
prefix "/auth"
|
1008
|
-
end
|
1009
|
-
|
1010
|
-
route do |r|
|
1011
|
-
r.on "auth" do
|
1012
|
-
r.rodauth
|
1013
|
-
end
|
1014
|
-
|
1015
|
-
rodauth.require_authentication
|
1016
|
-
|
1017
|
-
# ...
|
1018
|
-
end
|
1019
|
-
|
1020
|
-
=== +rodauth+ Methods
|
1021
|
-
|
1022
|
-
Most of Rodauth's functionality is exposed via +r.rodauth+, which allows
|
1023
|
-
Rodauth to handle routes for the features you have enabled (such as +/login+
|
1024
|
-
for login). However, as you have seen above, you may want to call methods on
|
1025
|
-
the +rodauth+ object, such as for checking if the current request has been
|
1026
|
-
authenticated.
|
1027
|
-
|
1028
|
-
Here are methods designed to be callable on the +rodauth+ object outside
|
1029
|
-
+r.rodauth+:
|
1030
|
-
|
1031
|
-
require_login :: Require the session be logged in, redirecting the request to the
|
1032
|
-
login page if the request has not been logged in.
|
1033
|
-
require_authentication :: Similar to +require_login+, but also requires
|
1034
|
-
two factor authentication if the account has setup
|
1035
|
-
two factor authentication. Redirects the request to
|
1036
|
-
the two factor authentication page if logged in but not
|
1037
|
-
authenticated via two factors.
|
1038
|
-
require_account :: Similar to +require_authentication+, but also loads the logged
|
1039
|
-
in account to ensure it exists in the database. If the account
|
1040
|
-
doesn't exist, or if it exists but isn't verified, the session
|
1041
|
-
is cleared and the request redirected to the login page.
|
1042
|
-
logged_in? :: Whether the session has been logged in.
|
1043
|
-
authenticated? :: Similar to +logged_in?+, but if the account has setup two
|
1044
|
-
factor authentication, whether the session has authenticated
|
1045
|
-
via two factors.
|
1046
|
-
account! :: Returns the current account record if it has already been loaded,
|
1047
|
-
otherwise retrieves the account from session if logged in.
|
1048
|
-
authenticated_by :: An array of strings for successful authentication methods for
|
1049
|
-
the current session (e.g. password/remember/webauthn).
|
1050
|
-
possible_authentication_methods :: An array of strings for possible authentication
|
1051
|
-
types that can be used for the account.
|
1052
|
-
autologin_type :: If the current session was authenticated via autologin, the
|
1053
|
-
type of autologin used.
|
1054
|
-
require_two_factor_setup :: (two_factor_base feature) Require the session to have
|
1055
|
-
setup two factor authentication, redirecting the
|
1056
|
-
request to the two factor authentication setup page
|
1057
|
-
if not.
|
1058
|
-
uses_two_factor_authentication? :: (two_factor_base feature) Whether the account
|
1059
|
-
for the current session has setup two factor
|
1060
|
-
authentication.
|
1061
|
-
update_last_activity :: (account_expiration feature) Update the last activity
|
1062
|
-
time for the current account. Only makes sense to use
|
1063
|
-
this if you are expiring accounts based on last activity.
|
1064
|
-
require_current_password :: (password_expiration feature) Require a current
|
1065
|
-
password, redirecting the request to the change
|
1066
|
-
password page if the password for the account has
|
1067
|
-
expired.
|
1068
|
-
require_password_authentication :: (confirm_password feature) If not authenticated
|
1069
|
-
via password and the account has a password,
|
1070
|
-
redirect to the password confirmation page,
|
1071
|
-
saving the current location to redirect back
|
1072
|
-
to after password has been successfully
|
1073
|
-
confirmed. If the password_grace_period feature
|
1074
|
-
is used, also redirect if the password has not
|
1075
|
-
been recently entered.
|
1076
|
-
load_memory :: (remember feature) If the session has not been authenticated, look
|
1077
|
-
for the remember cookie. If present and valid, automatically
|
1078
|
-
log the session in, but mark that it was logged in via a remember
|
1079
|
-
key.
|
1080
|
-
logged_in_via_remember_key? :: (remember feature) Whether the current session has
|
1081
|
-
been logged in via a remember key. For security
|
1082
|
-
sensitive actions where you want to require the user
|
1083
|
-
to reenter the password, you can use the
|
1084
|
-
confirm_password feature.
|
1085
|
-
http_basic_auth :: (http_basic_auth feature) Use HTTP Basic Authentication information
|
1086
|
-
to login the user if provided.
|
1087
|
-
require_http_basic_auth :: (http_basic_auth feature) Require that HTTP Basic
|
1088
|
-
Authentication be provided in the request.
|
1089
|
-
check_session_expiration :: (session_expiration feature) Check whether the current
|
1090
|
-
session has expired, automatically logging the session
|
1091
|
-
out if so.
|
1092
|
-
check_active_session :: (active_sessions feature) Check whether the current session
|
1093
|
-
is still active, automatically logging the session out if not.
|
1094
|
-
check_single_session :: (single_session feature) Check whether the current
|
1095
|
-
session is still the only valid session, automatically logging
|
1096
|
-
the session out if not.
|
1097
|
-
verified_account? :: (verify_grace_period feature) Whether the account is currently
|
1098
|
-
verified. If false, it is because the account is allowed to
|
1099
|
-
login as they are in the grace period.
|
1100
|
-
locked_out? :: (lockout feature) Whether the account for the current session has been
|
1101
|
-
locked out.
|
1102
|
-
authenticated_webauthn_id :: (webauthn feature) If the current session was
|
1103
|
-
authenticated via webauthn, the webauthn id of the
|
1104
|
-
credential used.
|
1105
|
-
*_path :: One of these is added for each of the routes added by Rodauth, giving the
|
1106
|
-
relative path to the route. Any options passed to this method will be
|
1107
|
-
converted into query parameters.
|
1108
|
-
*_url :: One of these is added for each of the routes added by Rodauth, giving the
|
1109
|
-
URL to the route. Any options passed to this method will be converted
|
1110
|
-
into query parameters.
|
1111
|
-
|
1112
|
-
=== Calling Rodauth Methods for Other Accounts
|
1113
|
-
|
1114
|
-
In some cases, you may want to interact with Rodauth directly on behalf
|
1115
|
-
of a user. For example, let's say you want to create accounts or change passwords
|
1116
|
-
for existing accounts. Using Rodauth's internal_request feature, you can do this
|
1117
|
-
by:
|
1118
|
-
|
1119
|
-
plugin :rodauth do
|
1120
|
-
enable :create_account, :change_password, :internal_request
|
1121
|
-
end
|
1122
|
-
rodauth.create_account(login: 'foo@example.com', password: '...')
|
1123
|
-
rodauth.change_password(account_id: 24601, password: '...')
|
1124
|
-
|
1125
|
-
Here the +rodauth+ method is called as the Roda class level, which returns
|
1126
|
-
the appropriate <tt>Rodauth::Auth</tt> subclass. You call internal request
|
1127
|
-
methods on that class to perform actions on behalf of a user. See the
|
1128
|
-
{internal request feature documentation}[rdoc-ref:doc/internal_request.rdoc]
|
1129
|
-
for details.
|
1130
|
-
|
1131
|
-
== Using Rodauth as a Library
|
1132
|
-
|
1133
|
-
Rodauth was designed to serve as an authentication framework for Rack applications.
|
1134
|
-
However, Rodauth can be used purely as a library outside of a web application. You
|
1135
|
-
can do this by requiring +rodauth+, and using the +Rodauth.lib+ method to return
|
1136
|
-
a <tt>Rodauth::Auth</tt> subclass, which you can call methods on. You pass the
|
1137
|
-
+Rodauth.lib+ method an optional hash of Rodauth plugin options and a Rodauth
|
1138
|
-
configuration block:
|
1139
|
-
|
1140
|
-
require 'rodauth'
|
1141
|
-
rodauth = Rodauth.lib do
|
1142
|
-
enable :create_account, :change_password
|
1143
|
-
end
|
1144
|
-
rodauth.create_account(login: 'foo@example.com', password: '...')
|
1145
|
-
rodauth.change_password(account_id: 24601, password: '...')
|
1146
|
-
|
1147
|
-
This supports builds on top of the internal_request support (it implicitly loads
|
1148
|
-
the internal_request feature before processing the configuration block), and
|
1149
|
-
allows the use of Rodauth in non-web applications. Note that you still have to
|
1150
|
-
setup a Sequel::Database connection for Rodauth to use for data storage.
|
1151
|
-
|
1152
|
-
=== With Multiple Configurations
|
1153
|
-
|
1154
|
-
Rodauth supports using multiple rodauth configurations in the same
|
1155
|
-
application. You just need to load the plugin a second time,
|
1156
|
-
providing a name for any alternate configuration:
|
1157
|
-
|
1158
|
-
plugin :rodauth do
|
1159
|
-
end
|
1160
|
-
plugin :rodauth, name: :secondary do
|
1161
|
-
end
|
1162
|
-
|
1163
|
-
Then in your routing code, any time you call rodauth, you can provide
|
1164
|
-
the name as an argument to use that configuration:
|
1165
|
-
|
1166
|
-
route do |r|
|
1167
|
-
r.on 'secondary' do
|
1168
|
-
r.rodauth(:secondary)
|
1169
|
-
end
|
1170
|
-
|
1171
|
-
r.rodauth
|
1172
|
-
end
|
1173
|
-
|
1174
|
-
By default, alternate configurations will use the same session keys as the
|
1175
|
-
primary configuration, which may be undesirable. To ensure session state is
|
1176
|
-
separated between configurations, you can set a session key prefix for
|
1177
|
-
alternate configurations. If you are using the remember feature in both
|
1178
|
-
configurations, you may also want to set a different remember key in the
|
1179
|
-
alternate configuration:
|
1180
|
-
|
1181
|
-
plugin :rodauth, name: :secondary do
|
1182
|
-
session_key_prefix "secondary_"
|
1183
|
-
remember_cookie_key "_secondary_remember"
|
1184
|
-
end
|
1185
|
-
|
1186
|
-
=== With Password Hashes Inside the Accounts Table
|
1187
|
-
|
1188
|
-
You can use Rodauth if you are storing password hashes in the same
|
1189
|
-
table as the accounts. You just need to specify which column
|
1190
|
-
stores the password hash:
|
1191
|
-
|
1192
|
-
plugin :rodauth do
|
1193
|
-
account_password_hash_column :password_hash
|
1194
|
-
end
|
1195
|
-
|
1196
|
-
When this option is set, Rodauth will do the password hash check
|
1197
|
-
in ruby.
|
1198
|
-
|
1199
|
-
=== When Using PostgreSQL/MySQL/Microsoft SQL Server without Database Functions
|
1200
|
-
|
1201
|
-
If you want to use Rodauth on PostgreSQL, MySQL, or Microsoft SQL Server
|
1202
|
-
without using database functions for authentication, but still storing password
|
1203
|
-
hashes in a separate table, you can do so:
|
1204
|
-
|
1205
|
-
plugin :rodauth do
|
1206
|
-
use_database_authentication_functions? false
|
1207
|
-
end
|
1208
|
-
|
1209
|
-
Conversely, if you implement the rodauth_get_salt and
|
1210
|
-
rodauth_valid_password_hash functions on a database that isn't
|
1211
|
-
PostgreSQL, MySQL, or Microsoft SQL Server, you can set this value to true.
|
1212
|
-
|
1213
|
-
=== With Custom Authentication
|
1214
|
-
|
1215
|
-
You can use Rodauth with other authentication types, by using some
|
1216
|
-
of Rodauth's configuration methods.
|
1217
|
-
|
1218
|
-
Note that when using custom authentication, using some of Rodauth's
|
1219
|
-
features such as change login and change password either would not
|
1220
|
-
make sense or would require some additional custom configuration.
|
1221
|
-
The login and logout features should work correctly with the examples
|
1222
|
-
below, though.
|
1223
|
-
|
1224
|
-
==== Using LDAP Authentication
|
1225
|
-
|
1226
|
-
If you have accounts stored in the database, but authentication happens
|
1227
|
-
via LDAP, you can use the +simple_ldap_authenticator+ library:
|
1228
|
-
|
1229
|
-
require 'simple_ldap_authenticator'
|
1230
|
-
plugin :rodauth do
|
1231
|
-
enable :login, :logout
|
1232
|
-
require_bcrypt? false
|
1233
|
-
password_match? do |password|
|
1234
|
-
SimpleLdapAuthenticator.valid?(account[:email], password)
|
1235
|
-
end
|
1236
|
-
end
|
1237
|
-
|
1238
|
-
If you aren't storing accounts in the database, but want to allow
|
1239
|
-
any valid LDAP user to login, you can do something like this:
|
1240
|
-
|
1241
|
-
require 'simple_ldap_authenticator'
|
1242
|
-
plugin :rodauth do
|
1243
|
-
enable :login, :logout
|
1244
|
-
|
1245
|
-
# Don't require the bcrypt library, since using LDAP for auth
|
1246
|
-
require_bcrypt? false
|
1247
|
-
|
1248
|
-
# Store session value in :login key, since the :account_id
|
1249
|
-
# default wouldn't make sense
|
1250
|
-
session_key :login
|
1251
|
-
|
1252
|
-
# Use the login provided as the session value
|
1253
|
-
account_session_value{account}
|
1254
|
-
|
1255
|
-
# Treat the login itself as the account
|
1256
|
-
account_from_login{|l| l.to_s}
|
1257
|
-
|
1258
|
-
password_match? do |password|
|
1259
|
-
SimpleLdapAuthenticator.valid?(account, password)
|
1260
|
-
end
|
1261
|
-
end
|
1262
|
-
|
1263
|
-
==== Using Facebook Authentication
|
1264
|
-
|
1265
|
-
Here's an example of authentication using Facebook with a JSON API.
|
1266
|
-
This setup assumes you have client-side code to submit JSON POST requests
|
1267
|
-
to +/login+ with an +access_token+ parameter that is set to the user's
|
1268
|
-
Facebook OAuth access token.
|
1269
|
-
|
1270
|
-
|
1271
|
-
require 'koala'
|
1272
|
-
plugin :rodauth do
|
1273
|
-
enable :login, :logout, :jwt
|
1274
|
-
|
1275
|
-
require_bcrypt? false
|
1276
|
-
session_key :facebook_email
|
1277
|
-
account_session_value{account}
|
1278
|
-
|
1279
|
-
login_param 'access_token'
|
1280
|
-
|
1281
|
-
account_from_login do |access_token|
|
1282
|
-
fb = Koala::Facebook::API.new(access_token)
|
1283
|
-
if me = fb.get_object('me', fields: [:email])
|
1284
|
-
me['email']
|
1285
|
-
end
|
1286
|
-
end
|
1287
|
-
|
1288
|
-
# there is no password!
|
1289
|
-
password_match? do |pass|
|
1290
|
-
true
|
1291
|
-
end
|
1292
|
-
end
|
1293
|
-
|
1294
|
-
=== With Rails
|
1295
|
-
|
1296
|
-
If you're using Rails, you can use the
|
1297
|
-
{rodauth-rails}[https://github.com/janko/rodauth-rails] gem which provides
|
1298
|
-
Rails integration for Rodauth. Some of its features include:
|
1299
|
-
|
1300
|
-
* generators for Rodauth & Sequel configuration, as well as views and mailers
|
1301
|
-
* uses Rails' flash messages and CSRF protection
|
1302
|
-
* automatically sets HMAC secret to Rails' secret key base
|
1303
|
-
* uses Action Controller & Action View for rendering templates
|
1304
|
-
* uses Action Mailer for sending emails
|
1305
|
-
|
1306
|
-
Follow the instructions in the rodauth-rails README to get started.
|
1307
|
-
|
1308
|
-
=== With Other Web Frameworks
|
1309
|
-
|
1310
|
-
You can use Rodauth even if your application does not use the Roda web
|
1311
|
-
framework. This is possible by adding a Roda middleware that uses
|
1312
|
-
Rodauth:
|
1313
|
-
|
1314
|
-
require 'roda'
|
1315
|
-
|
1316
|
-
class RodauthApp < Roda
|
1317
|
-
plugin :middleware
|
1318
|
-
plugin :rodauth do
|
1319
|
-
enable :login
|
1320
|
-
end
|
1321
|
-
|
1322
|
-
route do |r|
|
1323
|
-
r.rodauth
|
1324
|
-
rodauth.require_authentication
|
1325
|
-
env['rodauth'] = rodauth
|
1326
|
-
end
|
1327
|
-
end
|
1328
|
-
|
1329
|
-
use RodauthApp
|
1330
|
-
|
1331
|
-
Note that Rodauth expects the Roda app it is used in to provide a
|
1332
|
-
layout. So if you are using Rodauth as middleware for another app,
|
1333
|
-
if you don't have a +views/layout.erb+ file that Rodauth can use,
|
1334
|
-
you should probably also add load Roda's +render+ plugin
|
1335
|
-
with the appropriate settings that allow Rodauth to use the same
|
1336
|
-
layout as the application.
|
1337
|
-
|
1338
|
-
By setting <tt>env['rodauth'] = rodauth</tt> in the route block
|
1339
|
-
inside the middleware, you can easily provide a way for your
|
1340
|
-
application to call Rodauth methods.
|
1341
|
-
|
1342
|
-
If you're using the remember feature with +extend_remember_deadline?+ set to
|
1343
|
-
true, you'll want to load roda's middleware plugin with
|
1344
|
-
<tt>forward_response_headers: true</tt> option, so that +Set-Cookie+ header changes
|
1345
|
-
from the +load_memory+ call in the route block are propagated when the request
|
1346
|
-
is forwarded to the main app.
|
1347
|
-
|
1348
|
-
Here are some examples of integrating Rodauth into applications that
|
1349
|
-
don't use Roda:
|
1350
|
-
|
1351
|
-
* {Ginatra, a Sinatra-based git repository viewer}[https://github.com/jeremyevans/ginatra/commit/28108ebec96e8d42596ee55b01c3f7b50c155dd1]
|
1352
|
-
* {Rodauth's demo site as a Rails 6 application}[https://github.com/janko/rodauth-demo-rails]
|
1353
|
-
* {Grape application}[https://github.com/davydovanton/grape-rodauth]
|
1354
|
-
* {Hanami application}[https://github.com/davydovanton/rodauth_hanami]
|
1355
|
-
|
1356
|
-
=== Using 2 Factor Authentication
|
1357
|
-
|
1358
|
-
Rodauth ships with 2 factor authentication support via the following
|
1359
|
-
methods:
|
1360
|
-
|
1361
|
-
* WebAuthn
|
1362
|
-
* TOTP (Time-Based One-Time Passwords, RFC 6238).
|
1363
|
-
* SMS Codes
|
1364
|
-
* Recovery Codes
|
1365
|
-
|
1366
|
-
There are multiple ways to integrate 2 factor authentication with
|
1367
|
-
Rodauth, based on the needs of the application. By default, SMS
|
1368
|
-
codes and recovery codes are treated only as backup 2nd factors,
|
1369
|
-
a user cannot enable them without first enabling another 2nd factor
|
1370
|
-
authentication method. However, you can change this by using
|
1371
|
-
a configuration method.
|
1372
|
-
|
1373
|
-
If you want to support but not require 2 factor authentication:
|
1374
|
-
|
1375
|
-
plugin :rodauth do
|
1376
|
-
enable :login, :logout, :otp, :recovery_codes, :sms_codes
|
1377
|
-
end
|
1378
|
-
route do |r|
|
1379
|
-
r.rodauth
|
1380
|
-
rodauth.require_authentication
|
1381
|
-
|
1382
|
-
# ...
|
1383
|
-
end
|
1384
|
-
|
1385
|
-
If you want to force all users to use 2 factor authentication, requiring users
|
1386
|
-
that don't currently have two authentication to set it up:
|
1387
|
-
|
1388
|
-
route do |r|
|
1389
|
-
r.rodauth
|
1390
|
-
rodauth.require_authentication
|
1391
|
-
rodauth.require_two_factor_setup
|
1392
|
-
|
1393
|
-
# ...
|
1394
|
-
end
|
1395
|
-
|
1396
|
-
Similarly to requiring authentication in general, it's possible to require
|
1397
|
-
login authentication for most of the site, but require 2 factor
|
1398
|
-
authentication only for particular branches:
|
1399
|
-
|
1400
|
-
route do |r|
|
1401
|
-
r.rodauth
|
1402
|
-
rodauth.require_login
|
1403
|
-
|
1404
|
-
r.on "admin" do
|
1405
|
-
rodauth.require_two_factor_authenticated
|
1406
|
-
end
|
1407
|
-
|
1408
|
-
# ...
|
1409
|
-
end
|
1410
|
-
|
1411
|
-
=== JSON API Support
|
1412
|
-
|
1413
|
-
To add support for handling JSON responses, you can pass the +:json+
|
1414
|
-
option to the plugin, and enable the JWT feature in addition to
|
1415
|
-
other features you plan to use:
|
1416
|
-
|
1417
|
-
plugin :rodauth, json: true do
|
1418
|
-
enable :login, :logout, :jwt
|
1419
|
-
end
|
1420
|
-
|
1421
|
-
If you do not want to load the HTML plugins that Rodauth usually loads
|
1422
|
-
(render, csrf, flash, h), because you are building a JSON-only API,
|
1423
|
-
pass <tt>:json => :only</tt>
|
1424
|
-
|
1425
|
-
plugin :rodauth, json: :only do
|
1426
|
-
enable :login, :logout, :jwt
|
1427
|
-
end
|
1428
|
-
|
1429
|
-
Note that by default, the features that send email depend on the
|
1430
|
-
render plugin, so if using the <tt>json: :only</tt> option, you
|
1431
|
-
either need to load the render plugin manually or you need to
|
1432
|
-
use the necessary *_email_body configuration options to specify
|
1433
|
-
the body of the emails.
|
1434
|
-
|
1435
|
-
The JWT feature enables JSON API support for all of the other features
|
1436
|
-
that Rodauth ships with. If you would like JSON API access that still uses
|
1437
|
-
rack session for storing session data, enable the JSON feature instead:
|
1438
|
-
|
1439
|
-
plugin :rodauth, json: true do
|
1440
|
-
enable :login, :logout, :json
|
1441
|
-
only_json? true # if you want to only handle JSON requests
|
1442
|
-
end
|
1443
|
-
|
1444
|
-
=== Adding Custom Methods to the +rodauth+ Object
|
1445
|
-
|
1446
|
-
Inside the configuration block, you can use +auth_class_eval+ to add
|
1447
|
-
custom methods that will be callable on the +rodauth+ object.
|
1448
|
-
|
1449
|
-
plugin :rodauth do
|
1450
|
-
enable :login
|
1451
|
-
|
1452
|
-
auth_class_eval do
|
1453
|
-
def require_admin
|
1454
|
-
request.redirect("/") unless account[:admin]
|
1455
|
-
end
|
1456
|
-
end
|
1457
|
-
end
|
1458
|
-
|
1459
|
-
route do |r|
|
1460
|
-
r.rodauth
|
1461
|
-
|
1462
|
-
r.on "admin" do
|
1463
|
-
rodauth.require_admin
|
1464
|
-
end
|
1465
|
-
end
|
1466
|
-
|
1467
|
-
=== Using External Features
|
1468
|
-
|
1469
|
-
The +enable+ configuration method is able to load features external to
|
1470
|
-
Rodauth. You need to place the external feature file where it can be
|
1471
|
-
required via rodauth/features/feature_name. That file should
|
1472
|
-
use the following basic structure
|
1473
|
-
|
1474
|
-
module Rodauth
|
1475
|
-
# :feature_name will be the argument given to enable to
|
1476
|
-
# load the feature, :FeatureName is optional and will be used to
|
1477
|
-
# set a constant name for prettier inspect output.
|
1478
|
-
Feature.define(:feature_name, :FeatureName) do
|
1479
|
-
# Shortcut for defining auth value methods with static values
|
1480
|
-
auth_value_method :method_name, 1 # method_value
|
1481
|
-
|
1482
|
-
auth_value_methods # one argument per auth value method
|
1483
|
-
|
1484
|
-
auth_methods # one argument per auth method
|
1485
|
-
|
1486
|
-
route do |r|
|
1487
|
-
# This block is taken for requests to the feature's route.
|
1488
|
-
# This block is evaluated in the scope of the Rodauth::Auth instance.
|
1489
|
-
# r is the Roda::RodaRequest instance for the request
|
1490
|
-
|
1491
|
-
r.get do
|
1492
|
-
end
|
1493
|
-
|
1494
|
-
r.post do
|
1495
|
-
end
|
1496
|
-
end
|
1497
|
-
|
1498
|
-
configuration_eval do
|
1499
|
-
# define additional configuration specific methods here, if any
|
1500
|
-
end
|
1501
|
-
|
1502
|
-
# define the default behavior for the auth_methods
|
1503
|
-
# and auth_value_methods
|
1504
|
-
# ...
|
1505
|
-
end
|
1506
|
-
end
|
1507
|
-
|
1508
|
-
See the {internals guide}[rdoc-ref:doc/guides/internals.rdoc] for a more complete
|
1509
|
-
example of how to construct features.
|
1510
|
-
|
1511
|
-
=== Overriding Route-Level Behavior
|
1512
|
-
|
1513
|
-
All of Rodauth's configuration methods change the behavior of the
|
1514
|
-
Rodauth::Auth instance. However, in some cases you may want to
|
1515
|
-
overriding handling at the routing layer. You can do this easily
|
1516
|
-
by adding an appropriate route before calling +r.rodauth+:
|
1517
|
-
|
1518
|
-
route do |r|
|
1519
|
-
r.post 'login' do
|
1520
|
-
# Custom POST /login handling here
|
1521
|
-
end
|
1522
|
-
|
1523
|
-
r.rodauth
|
1524
|
-
end
|
1525
|
-
|
1526
|
-
=== Precompiling Rodauth Templates
|
1527
|
-
|
1528
|
-
Rodauth serves templates from it's gem folder. If you are using
|
1529
|
-
a forking webserver and want to preload the compiled templates
|
1530
|
-
to save memory, or if you are chrooting your application, you can
|
1531
|
-
benefit from precompiling your rodauth templates:
|
1532
|
-
|
1533
|
-
plugin :rodauth do
|
1534
|
-
# ...
|
1535
|
-
end
|
1536
|
-
precompile_rodauth_templates
|
1537
|
-
|
1538
|
-
== Ruby Support Policy
|
1539
|
-
|
1540
|
-
Rodauth fully supports the currently supported versions of Ruby (MRI) and JRuby. It may
|
1541
|
-
support unsupported versions of Ruby or JRuby, but such support may be dropped in any
|
1542
|
-
minor version if keeping it becomes a support issue. The minimum Ruby version
|
1543
|
-
required to run the current version of Rodauth is 1.9.2.
|
1544
|
-
|
1545
|
-
== Similar Projects
|
1546
|
-
|
1547
|
-
All of these are Rails-specific:
|
1548
|
-
|
1549
|
-
* {Devise}[https://github.com/heartcombo/devise]
|
1550
|
-
* {Authlogic}[https://github.com/binarylogic/authlogic]
|
1551
|
-
* {Sorcery}[https://github.com/Sorcery/sorcery]
|
1552
|
-
|
1553
|
-
== Author
|
1554
|
-
|
1555
|
-
Jeremy Evans <code@jeremyevans.net>
|