rodauth 2.1.0 → 2.2.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 +18 -0
- data/doc/guides/admin_activation.rdoc +46 -0
- data/doc/guides/already_authenticated.rdoc +10 -0
- data/doc/guides/alternative_login.rdoc +46 -0
- data/doc/guides/create_account_programmatically.rdoc +38 -0
- data/doc/guides/delay_password.rdoc +25 -0
- data/doc/guides/email_only.rdoc +16 -0
- data/doc/guides/i18n.rdoc +26 -0
- data/doc/{internals.rdoc → guides/internals.rdoc} +0 -0
- data/doc/guides/links.rdoc +12 -0
- data/doc/guides/login_return.rdoc +37 -0
- data/doc/guides/password_column.rdoc +25 -0
- data/doc/guides/password_confirmation.rdoc +37 -0
- data/doc/guides/password_requirements.rdoc +30 -0
- data/doc/guides/paths.rdoc +36 -0
- data/doc/guides/query_params.rdoc +9 -0
- data/doc/guides/redirects.rdoc +17 -0
- data/doc/guides/registration_field.rdoc +68 -0
- data/doc/guides/require_mfa.rdoc +30 -0
- data/doc/guides/reset_password_autologin.rdoc +21 -0
- data/doc/guides/status_column.rdoc +28 -0
- data/doc/guides/totp_or_recovery.rdoc +16 -0
- data/doc/jwt_refresh.rdoc +6 -0
- data/doc/otp.rdoc +1 -0
- data/doc/release_notes/2.2.0.txt +39 -0
- data/lib/rodauth.rb +4 -4
- data/lib/rodauth/features/active_sessions.rb +5 -7
- data/lib/rodauth/features/audit_logging.rb +2 -0
- data/lib/rodauth/features/email_auth.rb +1 -1
- data/lib/rodauth/features/jwt.rb +5 -6
- data/lib/rodauth/features/jwt_cors.rb +15 -15
- data/lib/rodauth/features/jwt_refresh.rb +29 -6
- data/lib/rodauth/features/otp.rb +5 -1
- data/lib/rodauth/features/password_complexity.rb +4 -2
- data/lib/rodauth/features/single_session.rb +1 -1
- data/lib/rodauth/features/sms_codes.rb +0 -1
- data/lib/rodauth/features/two_factor_base.rb +4 -4
- data/lib/rodauth/features/verify_account.rb +4 -0
- data/lib/rodauth/features/verify_account_grace_period.rb +2 -4
- data/lib/rodauth/features/webauthn.rb +1 -3
- data/lib/rodauth/version.rb +1 -1
- metadata +25 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f672a0312d4a0c6103bb763d339161119551a0e64cc248546a0cb52f4e6784c1
|
4
|
+
data.tar.gz: 530d322cc3ddba655959238aafe155512de11f090afb9465980181dcc729d1b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8018f5e02d077bd8625c17c8c5a1f607b986c1916296132ef68e2651d2af26d65ddbdfdc9b5f1352431c74caa30e94d270cc03840207303f8fa0e6a44910a0b
|
7
|
+
data.tar.gz: 5f24ce4d800d3bbe914dac40dba1cd68b78af4aff5efdaf976b32702d45b5fe7809453d2a650f407437afb1b9d6206ff3d79e6f703fcd85d8d55a0f0658464eb
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
=== 2.2.0 (2020-07-20)
|
2
|
+
|
3
|
+
* Allow removing all jwt_refresh tokens when logging out by providing a value of "all" as the token to remove (jeremyevans)
|
4
|
+
|
5
|
+
* Allow removing specific jwt_refresh token when logging out by providing the token to remove (jeremyevans)
|
6
|
+
|
7
|
+
* Avoid NoMethodError when checking if session is authenticated when using two factor auth, verify_account_grace_period, and email_auth (jeremyevans) (#105)
|
8
|
+
|
9
|
+
* Reduce queries in #authenticated? and #require_authentication when using two factor authentication (janko) (#106)
|
10
|
+
|
11
|
+
* Treat verify_account_email_resend returning false as an error in the verify_account feature (jeremyevans)
|
12
|
+
|
13
|
+
* Fix use of password_dictionary configuration method in password_complexity feature (jeremyevans)
|
14
|
+
|
15
|
+
* Remove unnecessary conditionals (jeremyevans)
|
16
|
+
|
17
|
+
* Add otp_last_use to the otp feature, returning the time of last successful OTP use (jeremyevans) (#103)
|
18
|
+
|
1
19
|
=== 2.1.0 (2020-06-09)
|
2
20
|
|
3
21
|
* Do not check CSRF tokens by default for requests using JWT (janko, jeremyevans) (#99)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
= Require account verification by admin
|
2
|
+
|
3
|
+
There are scenarios in which, instead of allowing the user to verify they have
|
4
|
+
access to the email for the account, you may want to have an admin or moderator
|
5
|
+
approve new accounts manually. One way this can be achieved by sending the
|
6
|
+
account verification email to the admin:
|
7
|
+
|
8
|
+
plugin :rodauth do
|
9
|
+
enable :login, :logout, :verify_account, :reset_password
|
10
|
+
|
11
|
+
# Send account verification email to the admin
|
12
|
+
email_to do
|
13
|
+
if account[account_status_column] == account_unverified_status_value
|
14
|
+
"admin@myapp.com"
|
15
|
+
else
|
16
|
+
super()
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Do not ask for password when creating or verifying account
|
21
|
+
verify_account_set_password? false
|
22
|
+
create_account_set_password? false
|
23
|
+
|
24
|
+
# Adjust the account verification email subject and body
|
25
|
+
verify_account_email_subject "New User Awaiting Admin Approval"
|
26
|
+
verify_account_email_body do
|
27
|
+
"The user #{account[login_column]} has created an account. Click here to approve it: #{verify_account_email_link}."
|
28
|
+
end
|
29
|
+
|
30
|
+
# Display this message to the user after they've created their account
|
31
|
+
verify_account_email_sent_notice_flash "Your account has been created and is awaiting approval"
|
32
|
+
|
33
|
+
# Prevent the admin from being logged in after confirming the account
|
34
|
+
verify_account_autologin? false
|
35
|
+
verify_account_notice_flash "The account has been approved"
|
36
|
+
|
37
|
+
# Send a reset password email after verifying the account.
|
38
|
+
# This allows the user to choose the password for the account,
|
39
|
+
# and also makes sure the user can only log in if they have
|
40
|
+
# access to the email address for the account.
|
41
|
+
after_verify_account do
|
42
|
+
generate_reset_password_key_value
|
43
|
+
create_reset_password_key
|
44
|
+
send_reset_password_email
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
= Skip login page if already authenticated
|
2
|
+
|
3
|
+
In some cases it may be useful to skip login/registration pages when the user
|
4
|
+
is already logged in. This can be achieved as follows. Note that this only
|
5
|
+
matters if the user manually navigates to the login or create account pages.
|
6
|
+
|
7
|
+
plugin :rodauth do
|
8
|
+
# Redirect logged in users to the wherever login redirects to
|
9
|
+
already_logged_in { redirect login_redirect }
|
10
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
= Use a non-email login
|
2
|
+
|
3
|
+
Rodauth's by default uses email addresses for identifying users, since that is
|
4
|
+
the most common form of identifier currently. In some cases, you might want
|
5
|
+
to allow logging in via alternative identifiers, such as a username. In this
|
6
|
+
case, it is best to choose a different column name for the login, such as
|
7
|
+
+:username+. Among other things, this also makes it so that the login field
|
8
|
+
does not expect an email address to be provided.
|
9
|
+
|
10
|
+
plugin :rodauth do
|
11
|
+
enable :login, :logout
|
12
|
+
login_column :username
|
13
|
+
end
|
14
|
+
|
15
|
+
Note that Rodauth features that require sending email need an email address, and
|
16
|
+
that defaults to the value of the login column. If you have both a username and
|
17
|
+
an email for an account, you can have the login column be the user, and use the
|
18
|
+
value of the email colummn for the email address.
|
19
|
+
|
20
|
+
plugin :rodauth do
|
21
|
+
enable :login, :logout, :reset_password
|
22
|
+
|
23
|
+
login_column :username
|
24
|
+
email_to do
|
25
|
+
account[:email]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
An alternative approach would be to accept a login and automatically change it
|
30
|
+
to an email address. If you have a +username+ field on the +accounts+ table,
|
31
|
+
then you can configure Rodauth to allow entering a username instead of email
|
32
|
+
during login. See the {Adding new registration field}[rdoc-ref:doc/guides/registration_field.rdoc]
|
33
|
+
guide for instructions on requiring add an additional field during registration.
|
34
|
+
|
35
|
+
plugin :rodauth do
|
36
|
+
enable :login, :logout
|
37
|
+
|
38
|
+
account_from_login do |login|
|
39
|
+
# handle the case when login parameter is a username
|
40
|
+
unless login.include?("@")
|
41
|
+
login = db[:accounts].where(username: login).get(:email)
|
42
|
+
end
|
43
|
+
|
44
|
+
super(login)
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
= Create an account record programmatically
|
2
|
+
|
3
|
+
In some scenarios you might want to create an account records programmatically,
|
4
|
+
for example in your tests.
|
5
|
+
|
6
|
+
If you're storing passwords in a separate table, you can create an account
|
7
|
+
records as follows:
|
8
|
+
|
9
|
+
account_id = DB[:accounts].insert(
|
10
|
+
email: "name@example.com",
|
11
|
+
status_id: 2, # verified
|
12
|
+
)
|
13
|
+
|
14
|
+
DB[:account_password_hashes].insert(
|
15
|
+
id: account_id,
|
16
|
+
password_hash: BCrypt::Password.create("secret").to_s,
|
17
|
+
)
|
18
|
+
|
19
|
+
If the password is stored in a column in the accounts table:
|
20
|
+
|
21
|
+
account_id = DB[:accounts].insert(
|
22
|
+
email: "name@example.com",
|
23
|
+
password_hash: BCrypt::Password.create("secret").to_s,
|
24
|
+
status_id: 2, # verified
|
25
|
+
)
|
26
|
+
|
27
|
+
If you are creating accounts in your tests, you probably want to use
|
28
|
+
the +:cost+ option, otherwise you will have very slow tests:
|
29
|
+
|
30
|
+
account_id = DB[:accounts].insert(
|
31
|
+
email: "name@example.com",
|
32
|
+
status_id: 2, # verified
|
33
|
+
)
|
34
|
+
|
35
|
+
DB[:account_password_hashes].insert(
|
36
|
+
id: account_id,
|
37
|
+
password_hash: BCrypt::Password.create("secret", cost: BCrypt::Engine::MIN_COST).to_s,
|
38
|
+
)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
= Set password when verifying account
|
2
|
+
|
3
|
+
If you want to request less information from the user on registration, you can
|
4
|
+
ask the user to set their password only when they verify their account:
|
5
|
+
|
6
|
+
plugin :rodauth do
|
7
|
+
enable :login, :logout, :verify_account
|
8
|
+
verify_account_set_password? true
|
9
|
+
end
|
10
|
+
|
11
|
+
Note that this is already the default behaviour when verify account feature is
|
12
|
+
loaded, but it's not when verify account grace period is used, because it would
|
13
|
+
prevent the account from logging in during the grace period. You can work around
|
14
|
+
this by automatically remebering their login during account creation using the
|
15
|
+
remember feature. Be aware that remembering accounts has effects beyond the
|
16
|
+
verification period, and this would only allow automatic logins from the browser
|
17
|
+
that created the account.
|
18
|
+
|
19
|
+
plugin :rodauth do
|
20
|
+
enable :login, :logout, :verify_account_grace_period, :remember
|
21
|
+
verify_account_set_password? true
|
22
|
+
after_create_account do
|
23
|
+
remember_login
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
= Allow only email authentication
|
2
|
+
|
3
|
+
When using the email authentication feature, you can avoid other authentication
|
4
|
+
mechanisms entirely as follows:
|
5
|
+
|
6
|
+
plugin :rodauth do
|
7
|
+
enable :login, :email_auth, :create_account, :verify_account
|
8
|
+
|
9
|
+
create_account_set_password? false
|
10
|
+
verify_account_set_password? false
|
11
|
+
force_email_auth? true
|
12
|
+
end
|
13
|
+
|
14
|
+
With this configuration, users won't be required to enter a password on
|
15
|
+
registration, and on login the email authentication link will automatically be
|
16
|
+
sent after the email address is entered.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
= Translate with i18n gem
|
2
|
+
|
3
|
+
Rodauth allows transforming user-facing text configuration such as flash
|
4
|
+
messages, validation errors, labels etc. via the +translate+ configuration
|
5
|
+
method. This method receives a name of a configuration along with its default
|
6
|
+
value, and is expected to return the result text.
|
7
|
+
|
8
|
+
You can use this to perform translations using the
|
9
|
+
{i18n gem}[https://github.com/ruby-i18n/i18n]:
|
10
|
+
|
11
|
+
plugin :rodauth do
|
12
|
+
enable :login, :logout, :reset_password
|
13
|
+
|
14
|
+
translate do |key, default|
|
15
|
+
I18n.translate("rodauth.#{key}") || default
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Your translation file may then look something like this:
|
20
|
+
|
21
|
+
en:
|
22
|
+
rodauth:
|
23
|
+
login_notice_flash: "You have been signed in"
|
24
|
+
require_login_error_flash: "Login is required for accessing this page"
|
25
|
+
no_matching_login_message: "user with this email address doesn't exist"
|
26
|
+
reset_password_email_subject: "Password Reset Instructions"
|
File without changes
|
@@ -0,0 +1,12 @@
|
|
1
|
+
= Display authentication links
|
2
|
+
|
3
|
+
You can retrieve a relative URL to any Rodauth action by calling the
|
4
|
+
corresponding <tt>*_path</tt> method on the Rodauth instance:
|
5
|
+
|
6
|
+
<a href="<%= rodauth.login_path %>">Sign in</a>
|
7
|
+
<a href="<%= rodauth.create_account_path %>">Sign up</a>
|
8
|
+
|
9
|
+
For absolute URLs instead of paths, you can use the <tt>*_url</tt> methods:
|
10
|
+
|
11
|
+
<a href="<%= rodauth.login_url %>">Sign in</a>
|
12
|
+
<a href="<%= rodauth.create_account_url %>">Sign up</a>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
= Redirect to original page after login
|
2
|
+
|
3
|
+
When the user attempts to open a page that requires authentication, Rodauth
|
4
|
+
redirects them to the login page. It can be useful to redirect them back to
|
5
|
+
the page they originally requested after successful login. Similarly, you
|
6
|
+
can do this for pages requiring multifactor authentication.
|
7
|
+
|
8
|
+
plugin :rodauth do
|
9
|
+
enable :login, :logout, :otp
|
10
|
+
|
11
|
+
# Have successful login redirect back to originally requested page
|
12
|
+
login_return_to_requested_location? true
|
13
|
+
|
14
|
+
# Have successful multifactor authentication redirect back to
|
15
|
+
# originally requested page
|
16
|
+
two_factor_auth_return_to_requested_location? true
|
17
|
+
end
|
18
|
+
|
19
|
+
You can manually set which page to redirect after login or multifactor
|
20
|
+
authentication, though it is questionable whether the user will desire
|
21
|
+
this behavior compared to the default.
|
22
|
+
|
23
|
+
route do |r|
|
24
|
+
r.rodauth
|
25
|
+
|
26
|
+
# Return the last visited path after login
|
27
|
+
if rodauth.logged_in?
|
28
|
+
# Return to the last visited page after multifactor authentication
|
29
|
+
unless rodauth.two_factor_authenticated?
|
30
|
+
session[rodauth.two_factor_auth_redirect_session_key] = request.fullpath
|
31
|
+
end
|
32
|
+
else
|
33
|
+
session[rodauth.login_redirect_session_key] = request.fullpath
|
34
|
+
end
|
35
|
+
|
36
|
+
# rest of routes
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
= Store password hash in accounts table
|
2
|
+
|
3
|
+
By default, Rodauth stores the password hash in a separate
|
4
|
+
+account_password_hashes+ table. This makes it a lot less likely that the
|
5
|
+
password hashes will be leaked, especially if you use Rodauth's default
|
6
|
+
approach of using database functions for checking the hashes.
|
7
|
+
|
8
|
+
However, if you have reasons for storing the password hashes in +accounts+
|
9
|
+
table that outweigh the security benefits of Rodauth's default approach,
|
10
|
+
Rodauth supports that.
|
11
|
+
|
12
|
+
To do this, add the password hash column to the +accounts+ table:
|
13
|
+
|
14
|
+
alter_table :accounts do
|
15
|
+
add_column :password_hash, String
|
16
|
+
end
|
17
|
+
|
18
|
+
And then tell Rodauth to use it:
|
19
|
+
|
20
|
+
plugin :rodauth do
|
21
|
+
enable :login, :logout
|
22
|
+
|
23
|
+
# Use the password_hash column in the accounts table
|
24
|
+
account_password_hash_column :password_hash
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
= Require password confirmation for certain actions
|
2
|
+
|
3
|
+
You might want to require the user to enter their password before accessing
|
4
|
+
sensitive sections of the app. This functionality is provided by the confirm
|
5
|
+
password feature, which accompanied with the password grace period feature will
|
6
|
+
remember the entered password for a period of time:
|
7
|
+
|
8
|
+
plugin :rodauth do
|
9
|
+
enable :confirm_password, :password_grace_period
|
10
|
+
|
11
|
+
# Remember the password for 1 hour
|
12
|
+
password_grace_period 60*60
|
13
|
+
end
|
14
|
+
|
15
|
+
route do |r|
|
16
|
+
r.rodauth
|
17
|
+
|
18
|
+
r.is 'some-action' do
|
19
|
+
# Require password authentication if the password has not been
|
20
|
+
# input recently.
|
21
|
+
rodauth.require_password_authentication
|
22
|
+
|
23
|
+
# ...
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
You can also do this for Rodauth actions that normally require a password.
|
28
|
+
Which essentially moves the password confirmation into a separate step, as
|
29
|
+
Rodauth's behavior with the password grace period feature is to ask for the
|
30
|
+
password on the same form.
|
31
|
+
|
32
|
+
plugin :rodauth do
|
33
|
+
enable :confirm_password, :password_grace_period, :change_login, :change_password
|
34
|
+
|
35
|
+
before_change_login_route { require_password_authentication }
|
36
|
+
before_change_password_route { require_password_authentication }
|
37
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
= Customize password requirements
|
2
|
+
|
3
|
+
By default, Rodauth requires passwords to have at least 6 characters. You can
|
4
|
+
modify the minimum length:
|
5
|
+
|
6
|
+
plugin :rodauth do
|
7
|
+
enable :login, :logout, :create_account
|
8
|
+
|
9
|
+
# Require passwords to have at least 8 characters
|
10
|
+
password_minimum_length 8
|
11
|
+
end
|
12
|
+
|
13
|
+
You can use the {disallow common passwords feature}[rdoc-ref:doc/disallow_common_passwords.rdoc]
|
14
|
+
to prevent the usage of common passwords (the most common 10,000 by default).
|
15
|
+
|
16
|
+
You can use additional complexity checks on passwords via the {password
|
17
|
+
complexity feature}[rdoc-ref:doc/password_complexity.rdoc], though most of
|
18
|
+
those complexity checks are no longer considered modern security best
|
19
|
+
practices and are likely to decrease overall security.
|
20
|
+
|
21
|
+
If you want complete control over whether passwords meet requirements, you
|
22
|
+
can use the <tt>password_meets_requirements?</tt> configuration method.
|
23
|
+
|
24
|
+
plugin :rodauth do
|
25
|
+
enable :login, :logout, :create_account
|
26
|
+
|
27
|
+
password_meets_requirements? do |password|
|
28
|
+
#true if password meets requirements, false otherwise
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
= Change route path
|
2
|
+
|
3
|
+
You can change the URL path of any Rodauth route by overriding the
|
4
|
+
corresponding <tt>*_route</tt> method:
|
5
|
+
|
6
|
+
plugin :rodauth do
|
7
|
+
enable :login, :logout, :create_account, :reset_password
|
8
|
+
|
9
|
+
# Change login route to "/signin"
|
10
|
+
login_route "signin"
|
11
|
+
|
12
|
+
# Change create account route to "/register"
|
13
|
+
create_account_route "register"
|
14
|
+
|
15
|
+
# Change password reset request route to "/reset-password/request"
|
16
|
+
reset_password_request_route "reset-password/request"
|
17
|
+
end
|
18
|
+
|
19
|
+
If you want to add a prefix to all Rodauth routes, you should use the +prefix+
|
20
|
+
setting:
|
21
|
+
|
22
|
+
plugin :rodauth do
|
23
|
+
enable :login, :logout
|
24
|
+
|
25
|
+
# Use /auth prefix to each Rodauth route
|
26
|
+
prefix "/auth"
|
27
|
+
end
|
28
|
+
|
29
|
+
route do |r|
|
30
|
+
r.on "auth" do
|
31
|
+
# Serve Rodauth routes under the /auth branch of the routing tree
|
32
|
+
r.rodauth
|
33
|
+
end
|
34
|
+
|
35
|
+
# ...
|
36
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
= Pass query parameters to auth URLs
|
2
|
+
|
3
|
+
The <tt>*_path</tt> and <tt>*_url</tt> methods allow passing additional query parameters:
|
4
|
+
|
5
|
+
rodauth.create_account_path(type: "seller")
|
6
|
+
#=> "/create-account?type=seller"
|
7
|
+
|
8
|
+
rodauth.login_url(type: "operator")
|
9
|
+
#=> "https//example.com/login?type=operator"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
= Change redirect destination
|
2
|
+
|
3
|
+
You can change the redirect destination for any Rodauth action by overriding
|
4
|
+
the corresponding <tt>*_redirect</tt> method:
|
5
|
+
|
6
|
+
plugin :rodauth do
|
7
|
+
enable :login, :logout, :create_account, :reset_password
|
8
|
+
|
9
|
+
# Redirect to "/dashboard" after login
|
10
|
+
login_redirect "/dashboard"
|
11
|
+
|
12
|
+
# Redirect to wherever login redirects to after creating account
|
13
|
+
create_account_redirect { login_redirect }
|
14
|
+
|
15
|
+
# Redirect to login page after password reset
|
16
|
+
reset_password_redirect { login_path }
|
17
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
= Add new field during account creation
|
2
|
+
|
3
|
+
The create account form only handles login and password parameters by
|
4
|
+
default. However, you might want to ask for additional information during
|
5
|
+
account creation, such as requiring the user to also enter their full name
|
6
|
+
or their company's name.
|
7
|
+
|
8
|
+
== A) Accounts table
|
9
|
+
|
10
|
+
Let's assume you wanted to wanted to store the additional field(s) directly on
|
11
|
+
the +accounts+ table:
|
12
|
+
|
13
|
+
atler_table :accounts do
|
14
|
+
add_column :name, String
|
15
|
+
end
|
16
|
+
|
17
|
+
You need to override the <tt>create-account</tt> template, which by default in
|
18
|
+
Rodauth you can do by adding a <tt>create-account.erb</tt> template in your
|
19
|
+
Roda +views+ directory.
|
20
|
+
|
21
|
+
Once you've added the <tt>create-account.erb</tt> template, and had it include
|
22
|
+
a field for the +name+, you can handle the submission of that field in a before
|
23
|
+
create account hook:
|
24
|
+
|
25
|
+
plugin :rodauth do
|
26
|
+
enable :login, :logout, :create_account
|
27
|
+
|
28
|
+
before_create_account do
|
29
|
+
# Validate presence of the name field
|
30
|
+
unless name = param_or_nil("name")
|
31
|
+
throw_error_status(422, "name", "must be present")
|
32
|
+
end
|
33
|
+
|
34
|
+
# Assign the new field to the account record
|
35
|
+
account[:name] = name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
== B) Separate table
|
40
|
+
|
41
|
+
Alternatively, you can store the additional field(s) in separate table, for
|
42
|
+
example:
|
43
|
+
|
44
|
+
create_table :account_names do
|
45
|
+
foreign_key :account_id, :accounts, primary_key: true, type: :Bignum
|
46
|
+
String :name, null: false
|
47
|
+
end
|
48
|
+
|
49
|
+
You can then handle the new submitted field as follows:
|
50
|
+
|
51
|
+
plugin :rodauth do
|
52
|
+
enable :login, :logout, :create_account
|
53
|
+
|
54
|
+
before_create_account do
|
55
|
+
# Validate presence of the name field
|
56
|
+
throw_error_status(422, "name", "must be present") unless param_or_nil("name")
|
57
|
+
end
|
58
|
+
|
59
|
+
after_create_account do
|
60
|
+
# Create the associated record
|
61
|
+
db[:account_names].insert(account_id: account[:id], name: param("name"))
|
62
|
+
end
|
63
|
+
|
64
|
+
after_close_account do
|
65
|
+
# Delete the associated record
|
66
|
+
db[:account_names].where(account_id: account[:id]).delete
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
= Require multifactor authentication after login
|
2
|
+
|
3
|
+
You may want to require multifactor authentication on login for people
|
4
|
+
that have multifactor authentication set up. The +require_authentication+
|
5
|
+
Rodauth method works for pages that require an authenticated user, but not for
|
6
|
+
pages where authentication is optional.
|
7
|
+
|
8
|
+
You can set this up as follows:
|
9
|
+
|
10
|
+
plugin :rodauth do
|
11
|
+
enable :login, :logout, :otp
|
12
|
+
|
13
|
+
# If you don't want to show an error message when redirecting
|
14
|
+
# to the multifactor authentication page.
|
15
|
+
two_factor_need_authentication_error_flash nil
|
16
|
+
|
17
|
+
# Display the same flash message after multifactor
|
18
|
+
# authentication than is displayed after login
|
19
|
+
two_factor_auth_notice_flash { login_notice_flash }
|
20
|
+
end
|
21
|
+
|
22
|
+
route do |r|
|
23
|
+
r.rodauth
|
24
|
+
|
25
|
+
if rodauth.logged_in? && rodauth.two_factor_authentication_setup?
|
26
|
+
rodauth.require_two_factor_authenticated
|
27
|
+
end
|
28
|
+
|
29
|
+
# ...
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
= Autologin after password reset
|
2
|
+
|
3
|
+
When the user resets their password, by default they are not automatically
|
4
|
+
logged in. You can change this behaviour and login the user automatically
|
5
|
+
after password reset.
|
6
|
+
|
7
|
+
plugin :rodauth do
|
8
|
+
enable :login, :logout, :reset_password
|
9
|
+
|
10
|
+
reset_password_autologin? true
|
11
|
+
end
|
12
|
+
|
13
|
+
Similarly, when the verify login change feature is used, the user is not
|
14
|
+
automatically logged in after verifying the login change. You can configure
|
15
|
+
Rodauth to automatically log the user in in this case:
|
16
|
+
|
17
|
+
plugin :rodauth do
|
18
|
+
enable :login, :logout, :verify_login_change
|
19
|
+
|
20
|
+
verify_login_change_autologin? true
|
21
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
= Store account status in a text column
|
2
|
+
|
3
|
+
By default, Rodauth recommends using a separate table for account statuses, and
|
4
|
+
linking them via foreign keys. This is useful as it achieves an enum-like
|
5
|
+
behaviour, where the database ensures a constrained set of status values.
|
6
|
+
|
7
|
+
However, if you use a testing environment that starts with a blank database,
|
8
|
+
and don't want to fix your testing environment to support real foreign keys,
|
9
|
+
you can configure Rodauth to store the account status in a text column.
|
10
|
+
Doing so results in problems if a text value you do not expect gets stored
|
11
|
+
in the column. We can mitigate the problems by using a CHECK constraint
|
12
|
+
on the column.
|
13
|
+
|
14
|
+
create_table :accounts do
|
15
|
+
# ...
|
16
|
+
String :status, null: false, default: "verified",
|
17
|
+
check: {status: %w'unverified verified closed'}
|
18
|
+
end
|
19
|
+
|
20
|
+
Then we can configure Rodauth to support this.
|
21
|
+
|
22
|
+
plugin :rodauth do
|
23
|
+
# ...
|
24
|
+
account_status_column :status
|
25
|
+
account_unverified_status_value "unverified"
|
26
|
+
account_open_status_value "verified"
|
27
|
+
account_closed_status_value "closed"
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
= Allow recovery code on TOTP code field
|
2
|
+
|
3
|
+
If using the otp feature, for convenience you might want to allow
|
4
|
+
the user to enter the recovery code into the TOTP code field, instead
|
5
|
+
of requiring they use the separate recovery codes form. You can
|
6
|
+
implement this using the following configuration:
|
7
|
+
|
8
|
+
plugin :rodauth do
|
9
|
+
enable :login, :logout, :otp, :recovery_codes
|
10
|
+
|
11
|
+
before_otp_auth_route do
|
12
|
+
if recovery_code_match?(param(otp_auth_param))
|
13
|
+
two_factor_authenticate("recovery_code")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/doc/jwt_refresh.rdoc
CHANGED
@@ -13,6 +13,12 @@ older access tokens. Older access tokens remain valid until they expire. You
|
|
13
13
|
can use the active_sessions feature if you want previous access tokens to be invalid
|
14
14
|
as soon as the refresh token is used.
|
15
15
|
|
16
|
+
You can have multiple active refresh tokens active at a time, since each browser session
|
17
|
+
will generally use a separate refresh token. If you would like to revoke a refresh token
|
18
|
+
when logging out, provide the refresh token when submitting the JSON request to logout.
|
19
|
+
If you would like to remove all refresh tokens for the account when logging out, provide
|
20
|
+
a value of <tt>all</tt> as the token value.
|
21
|
+
|
16
22
|
This feature depends on the jwt feature.
|
17
23
|
|
18
24
|
== Auth Value Methods
|
data/doc/otp.rdoc
CHANGED
@@ -73,6 +73,7 @@ otp_auth_view :: The HTML to use for the OTP authentication form.
|
|
73
73
|
otp_disable_view :: The HTML to use for the OTP disable form.
|
74
74
|
otp_exists? :: Whether the current account has setup OTP.
|
75
75
|
otp_key :: The stored OTP secret for the account.
|
76
|
+
otp_last_use :: The last time OTP authentication was successful for the account.
|
76
77
|
otp_locked_out? :: Whether the current account has been locked out of OTP authentication.
|
77
78
|
otp_new_secret :: A new secret to use when setting up OTP.
|
78
79
|
otp_provisioning_name :: The provisioning name to use during OTP setup, defaults to the account's email.
|
@@ -0,0 +1,39 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* When using the jwt_refresh feature, you can remove the current
|
4
|
+
refresh token when logging out by submitting the refresh token
|
5
|
+
in the logout request, the same as when submitting the refresh
|
6
|
+
token to obtain a new refresh token. You can also use a value
|
7
|
+
of "all" instead of the refresh token to remove all refresh
|
8
|
+
tokens when logging out.
|
9
|
+
|
10
|
+
* A rodauth.otp_last_use method has been added to the otp feature,
|
11
|
+
allowing you to determine when the otp was last used.
|
12
|
+
|
13
|
+
= Other Improvements
|
14
|
+
|
15
|
+
* When using multifactor authentication, rodauth.authenticated? and
|
16
|
+
rodauth.require_authentication now cache values in the session and
|
17
|
+
do not perform queries every time they are called.
|
18
|
+
|
19
|
+
* Many guides for common scenarios have been added to the
|
20
|
+
documentation. These augment Rodauth's existing comprehensive
|
21
|
+
feature documentation, which is aimed to be more of a reference
|
22
|
+
and less of a guide.
|
23
|
+
|
24
|
+
* When the verify_account_grace_period and email_auth features are
|
25
|
+
used with a multifactor authentication feature, and the
|
26
|
+
verify_account_set_password? configuration method is set to true,
|
27
|
+
Rodauth no longer raises a NoMethodError when checking if the
|
28
|
+
session was authenticated.
|
29
|
+
|
30
|
+
* In the verify_account feature, if verify_account_email_resend
|
31
|
+
returns false indicating no email was sent, an error message
|
32
|
+
is now used, instead of a success message.
|
33
|
+
|
34
|
+
* In the password_complexity feature, the password_dictionary
|
35
|
+
configuration method was previously ignored if the default
|
36
|
+
password dictionary file existed.
|
37
|
+
|
38
|
+
* Rodauth and all features that ship with it now have 100% branch
|
39
|
+
coverage.
|
data/lib/rodauth.rb
CHANGED
@@ -137,7 +137,9 @@ module Rodauth
|
|
137
137
|
feature.module_eval(&block)
|
138
138
|
configuration.def_configuration_methods(feature)
|
139
139
|
|
140
|
+
# :nocov:
|
140
141
|
if constant
|
142
|
+
# :nocov:
|
141
143
|
Rodauth.const_set(constant, feature)
|
142
144
|
Rodauth::FeatureConfiguration.const_set(constant, configuration)
|
143
145
|
end
|
@@ -336,10 +338,8 @@ module Rodauth
|
|
336
338
|
end
|
337
339
|
|
338
340
|
def freeze
|
339
|
-
|
340
|
-
|
341
|
-
opts[:rodauths].freeze
|
342
|
-
end
|
341
|
+
opts[:rodauths].each_value(&:freeze)
|
342
|
+
opts[:rodauths].freeze
|
343
343
|
super
|
344
344
|
end
|
345
345
|
end
|
@@ -118,14 +118,12 @@ module Rodauth
|
|
118
118
|
end
|
119
119
|
|
120
120
|
def before_logout
|
121
|
-
if
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
remove_current_session
|
126
|
-
end
|
121
|
+
if param_or_nil(global_logout_param)
|
122
|
+
remove_all_active_sessions
|
123
|
+
else
|
124
|
+
remove_current_session
|
127
125
|
end
|
128
|
-
super
|
126
|
+
super
|
129
127
|
end
|
130
128
|
|
131
129
|
def session_inactivity_deadline_condition
|
@@ -82,7 +82,9 @@ module Rodauth
|
|
82
82
|
|
83
83
|
def audit_log_ds
|
84
84
|
ds = db[audit_logging_table]
|
85
|
+
# :nocov:
|
85
86
|
if db.database_type == :postgres
|
87
|
+
# :nocov:
|
86
88
|
# For PostgreSQL, use RETURNING NULL. This allows the feature
|
87
89
|
# to be used with INSERT but not SELECT permissions on the
|
88
90
|
# table, useful for audit logging where the database user
|
data/lib/rodauth/features/jwt.rb
CHANGED
@@ -138,6 +138,11 @@ module Rodauth
|
|
138
138
|
!!(jwt_token && jwt_payload)
|
139
139
|
end
|
140
140
|
|
141
|
+
def view(page, title)
|
142
|
+
return super unless use_jwt?
|
143
|
+
return_json_response
|
144
|
+
end
|
145
|
+
|
141
146
|
private
|
142
147
|
|
143
148
|
def check_csrf?
|
@@ -256,12 +261,6 @@ module Rodauth
|
|
256
261
|
@json_response ||= {}
|
257
262
|
end
|
258
263
|
|
259
|
-
def _view(meth, page)
|
260
|
-
return super unless use_jwt?
|
261
|
-
return super if meth == :render
|
262
|
-
return_json_response
|
263
|
-
end
|
264
|
-
|
265
264
|
def _json_response_body(hash)
|
266
265
|
request.send(:convert_to_json, hash)
|
267
266
|
end
|
@@ -13,27 +13,27 @@ module Rodauth
|
|
13
13
|
auth_methods(:jwt_cors_allow?)
|
14
14
|
|
15
15
|
def jwt_cors_allow?
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
16
|
+
return false unless origin = request.env['HTTP_ORIGIN']
|
17
|
+
|
18
|
+
case allowed = jwt_cors_allow_origin
|
19
|
+
when String
|
20
|
+
timing_safe_eql?(origin, allowed)
|
21
|
+
when Array
|
22
|
+
allowed.any?{|s| timing_safe_eql?(origin, s)}
|
23
|
+
when Regexp
|
24
|
+
allowed =~ origin
|
25
|
+
when true
|
26
|
+
true
|
27
|
+
else
|
28
|
+
false
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
33
33
|
|
34
34
|
def before_rodauth
|
35
|
-
if
|
36
|
-
response['Access-Control-Allow-Origin'] =
|
35
|
+
if jwt_cors_allow?
|
36
|
+
response['Access-Control-Allow-Origin'] = request.env['HTTP_ORIGIN']
|
37
37
|
|
38
38
|
# Handle CORS preflight request
|
39
39
|
if request.request_method == 'OPTIONS'
|
@@ -80,14 +80,10 @@ module Rodauth
|
|
80
80
|
private
|
81
81
|
|
82
82
|
def _account_from_refresh_token(token)
|
83
|
-
id,
|
84
|
-
return unless id && token
|
85
|
-
|
86
|
-
token_id, key = split_token(token)
|
87
|
-
return unless token_id && key
|
83
|
+
id, token_id, key = _account_refresh_token_split(token)
|
88
84
|
|
85
|
+
return unless key
|
89
86
|
return unless actual = get_active_refresh_token(id, token_id)
|
90
|
-
|
91
87
|
return unless timing_safe_eql?(key, convert_token_key(actual))
|
92
88
|
|
93
89
|
ds = account_ds(id)
|
@@ -95,6 +91,16 @@ module Rodauth
|
|
95
91
|
ds.first
|
96
92
|
end
|
97
93
|
|
94
|
+
def _account_refresh_token_split(token)
|
95
|
+
id, token = split_token(token)
|
96
|
+
return unless id && token
|
97
|
+
|
98
|
+
token_id, key = split_token(token)
|
99
|
+
return unless token_id && key
|
100
|
+
|
101
|
+
[id, token_id, key]
|
102
|
+
end
|
103
|
+
|
98
104
|
def get_active_refresh_token(account_id, token_id)
|
99
105
|
jwt_refresh_token_account_ds(account_id).
|
100
106
|
where(Sequel::CURRENT_TIMESTAMP > jwt_refresh_token_deadline_column).
|
@@ -134,6 +140,23 @@ module Rodauth
|
|
134
140
|
hash
|
135
141
|
end
|
136
142
|
|
143
|
+
def before_logout
|
144
|
+
if token = param_or_nil(jwt_refresh_token_key_param)
|
145
|
+
if token == 'all'
|
146
|
+
jwt_refresh_token_account_ds(session_value).delete
|
147
|
+
else
|
148
|
+
id, token_id, key = _account_refresh_token_split(token)
|
149
|
+
|
150
|
+
if id && token_id && key && (actual = get_active_refresh_token(session_value, token_id)) && timing_safe_eql?(key, convert_token_key(actual))
|
151
|
+
jwt_refresh_token_account_ds(id).
|
152
|
+
where(jwt_refresh_token_id_column=>token_id).
|
153
|
+
delete
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
super if defined?(super)
|
158
|
+
end
|
159
|
+
|
137
160
|
def after_close_account
|
138
161
|
jwt_refresh_token_account_ds(account_id).delete
|
139
162
|
super if defined?(super)
|
data/lib/rodauth/features/otp.rb
CHANGED
@@ -79,6 +79,7 @@ module Rodauth
|
|
79
79
|
:otp,
|
80
80
|
:otp_exists?,
|
81
81
|
:otp_key,
|
82
|
+
:otp_last_use,
|
82
83
|
:otp_locked_out?,
|
83
84
|
:otp_new_secret,
|
84
85
|
:otp_provisioning_name,
|
@@ -255,7 +256,6 @@ module Rodauth
|
|
255
256
|
def otp_remove
|
256
257
|
otp_key_ds.delete
|
257
258
|
@otp_key = nil
|
258
|
-
super if defined?(super)
|
259
259
|
end
|
260
260
|
|
261
261
|
def otp_add_key
|
@@ -269,6 +269,10 @@ module Rodauth
|
|
269
269
|
update(otp_keys_last_use_column=>Sequel::CURRENT_TIMESTAMP) == 1
|
270
270
|
end
|
271
271
|
|
272
|
+
def otp_last_use
|
273
|
+
convert_timestamp(otp_key_ds.get(otp_keys_last_use_column))
|
274
|
+
end
|
275
|
+
|
272
276
|
def otp_record_authentication_failure
|
273
277
|
otp_key_ds.update(otp_keys_failures_column=>Sequel.identifier(otp_keys_failures_column) + 1)
|
274
278
|
end
|
@@ -26,14 +26,16 @@ module Rodauth
|
|
26
26
|
|
27
27
|
def post_configure
|
28
28
|
super
|
29
|
-
return if
|
29
|
+
return if method(:password_dictionary).owner != Rodauth::PasswordComplexity
|
30
30
|
|
31
31
|
case password_dictionary_file
|
32
32
|
when false
|
33
|
-
|
33
|
+
# nothing
|
34
34
|
when nil
|
35
35
|
default_dictionary_file = '/usr/share/dict/words'
|
36
|
+
# :nocov:
|
36
37
|
if File.file?(default_dictionary_file)
|
38
|
+
# :nocov:
|
37
39
|
words = File.read(default_dictionary_file)
|
38
40
|
end
|
39
41
|
else
|
@@ -125,7 +125,7 @@ module Rodauth
|
|
125
125
|
return true if two_factor_authenticated?
|
126
126
|
|
127
127
|
# True if authenticated via single factor and 2nd factor not setup
|
128
|
-
!
|
128
|
+
!uses_two_factor_authentication?
|
129
129
|
end
|
130
130
|
|
131
131
|
def require_authentication
|
@@ -134,7 +134,7 @@ module Rodauth
|
|
134
134
|
# Avoid database query if already authenticated via 2nd factor
|
135
135
|
return if two_factor_authenticated?
|
136
136
|
|
137
|
-
require_two_factor_authenticated if
|
137
|
+
require_two_factor_authenticated if uses_two_factor_authentication?
|
138
138
|
end
|
139
139
|
|
140
140
|
def require_two_factor_setup
|
@@ -208,11 +208,11 @@ module Rodauth
|
|
208
208
|
end
|
209
209
|
|
210
210
|
def _two_factor_setup_links
|
211
|
-
|
211
|
+
[]
|
212
212
|
end
|
213
213
|
|
214
214
|
def _two_factor_remove_links
|
215
|
-
|
215
|
+
[]
|
216
216
|
end
|
217
217
|
|
218
218
|
def _two_factor_remove_all_from_session
|
@@ -70,6 +70,7 @@ module Rodauth
|
|
70
70
|
end
|
71
71
|
|
72
72
|
r.post do
|
73
|
+
verified = false
|
73
74
|
if account_from_login(param(login_param)) && allow_resending_verify_account_email?
|
74
75
|
if verify_account_email_recently_sent?
|
75
76
|
set_redirect_error_flash verify_account_email_recently_sent_error_flash
|
@@ -79,8 +80,11 @@ module Rodauth
|
|
79
80
|
before_verify_account_email_resend
|
80
81
|
if verify_account_email_resend
|
81
82
|
after_verify_account_email_resend
|
83
|
+
verified = true
|
82
84
|
end
|
85
|
+
end
|
83
86
|
|
87
|
+
if verified
|
84
88
|
set_notice_flash verify_account_email_sent_notice_flash
|
85
89
|
else
|
86
90
|
set_redirect_error_status(no_matching_login_error_status)
|
@@ -53,10 +53,7 @@ module Rodauth
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def allow_email_auth?
|
56
|
-
|
57
|
-
return false unless super
|
58
|
-
end
|
59
|
-
!account_in_unverified_grace_period?
|
56
|
+
(defined?(super) ? super : true) && !account_in_unverified_grace_period?
|
60
57
|
end
|
61
58
|
|
62
59
|
def verify_account_check_already_logged_in
|
@@ -75,6 +72,7 @@ module Rodauth
|
|
75
72
|
end
|
76
73
|
|
77
74
|
def account_in_unverified_grace_period?
|
75
|
+
account || account_from_session
|
78
76
|
account[account_status_column] == account_unverified_status_value &&
|
79
77
|
verify_account_grace_period &&
|
80
78
|
!verify_account_ds.where(Sequel.date_add(verification_requested_at_column, :seconds=>verify_account_grace_period) > Sequel::CURRENT_TIMESTAMP).empty?
|
@@ -377,9 +377,7 @@ module Rodauth
|
|
377
377
|
end
|
378
378
|
|
379
379
|
def remove_webauthn_key(webauthn_id)
|
380
|
-
|
381
|
-
super if defined?(super)
|
382
|
-
ret
|
380
|
+
webauthn_keys_ds.where(webauthn_keys_webauthn_id_column=>webauthn_id).delete == 1
|
383
381
|
end
|
384
382
|
|
385
383
|
def remove_all_webauthn_keys_and_user_ids
|
data/lib/rodauth/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-07-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -247,7 +247,6 @@ extra_rdoc_files:
|
|
247
247
|
- doc/http_basic_auth.rdoc
|
248
248
|
- doc/create_account.rdoc
|
249
249
|
- doc/email_base.rdoc
|
250
|
-
- doc/internals.rdoc
|
251
250
|
- doc/disallow_common_passwords.rdoc
|
252
251
|
- doc/disallow_password_reuse.rdoc
|
253
252
|
- doc/password_complexity.rdoc
|
@@ -304,6 +303,7 @@ extra_rdoc_files:
|
|
304
303
|
- doc/release_notes/1.23.0.txt
|
305
304
|
- doc/release_notes/2.0.0.txt
|
306
305
|
- doc/release_notes/2.1.0.txt
|
306
|
+
- doc/release_notes/2.2.0.txt
|
307
307
|
files:
|
308
308
|
- CHANGELOG
|
309
309
|
- MIT-LICENSE
|
@@ -323,8 +323,28 @@ files:
|
|
323
323
|
- doc/disallow_password_reuse.rdoc
|
324
324
|
- doc/email_auth.rdoc
|
325
325
|
- doc/email_base.rdoc
|
326
|
+
- doc/guides/admin_activation.rdoc
|
327
|
+
- doc/guides/already_authenticated.rdoc
|
328
|
+
- doc/guides/alternative_login.rdoc
|
329
|
+
- doc/guides/create_account_programmatically.rdoc
|
330
|
+
- doc/guides/delay_password.rdoc
|
331
|
+
- doc/guides/email_only.rdoc
|
332
|
+
- doc/guides/i18n.rdoc
|
333
|
+
- doc/guides/internals.rdoc
|
334
|
+
- doc/guides/links.rdoc
|
335
|
+
- doc/guides/login_return.rdoc
|
336
|
+
- doc/guides/password_column.rdoc
|
337
|
+
- doc/guides/password_confirmation.rdoc
|
338
|
+
- doc/guides/password_requirements.rdoc
|
339
|
+
- doc/guides/paths.rdoc
|
340
|
+
- doc/guides/query_params.rdoc
|
341
|
+
- doc/guides/redirects.rdoc
|
342
|
+
- doc/guides/registration_field.rdoc
|
343
|
+
- doc/guides/require_mfa.rdoc
|
344
|
+
- doc/guides/reset_password_autologin.rdoc
|
345
|
+
- doc/guides/status_column.rdoc
|
346
|
+
- doc/guides/totp_or_recovery.rdoc
|
326
347
|
- doc/http_basic_auth.rdoc
|
327
|
-
- doc/internals.rdoc
|
328
348
|
- doc/jwt.rdoc
|
329
349
|
- doc/jwt_cors.rdoc
|
330
350
|
- doc/jwt_refresh.rdoc
|
@@ -363,6 +383,7 @@ files:
|
|
363
383
|
- doc/release_notes/1.9.0.txt
|
364
384
|
- doc/release_notes/2.0.0.txt
|
365
385
|
- doc/release_notes/2.1.0.txt
|
386
|
+
- doc/release_notes/2.2.0.txt
|
366
387
|
- doc/remember.rdoc
|
367
388
|
- doc/reset_password.rdoc
|
368
389
|
- doc/session_expiration.rdoc
|