#{rodauth.recovery_codes.map{|s| h s}.join("\n\n")}
2 | #{"#{rodauth.add_recovery_codes_heading}#{rodauth.render('recovery-codes')}" if rodauth.can_add_recovery_codes?}
3 |
--------------------------------------------------------------------------------
/templates/sms-request.str:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/templates/logout.str:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/doc/release_notes/2.42.0.txt:
--------------------------------------------------------------------------------
1 | = Improvements
2 |
3 | * Rodauth now avoids mixing string and symbol keys in a hash used to
4 | create a JWT in the jwt_refresh feature. This avoids warnings in
5 | the current json gem, and will avoid errors in json gem version 3+.
6 |
--------------------------------------------------------------------------------
/templates/sms-auth.str:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/templates/verify-login-change.str:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/templates/verify-account-email.str:
--------------------------------------------------------------------------------
1 | Someone has created an account with this email address. If you did not create
2 | this account, please ignore this message. If you created this account, please go to
3 | #{rodauth.verify_account_email_link}
4 | to verify the account.
5 |
--------------------------------------------------------------------------------
/templates/webauthn-authenticator-added-email.str:
--------------------------------------------------------------------------------
1 | Someone (hopefully you) has added a WebAuthn authenticator to the
2 | account associated to this email address. There are now #{rodauth.account_webauthn_ids.length} WebAuthn
3 | authenticator(s) with access to the account.
4 |
--------------------------------------------------------------------------------
/templates/login-form-footer.str:
--------------------------------------------------------------------------------
1 | #{rodauth.login_form_footer_links_heading}
2 |
3 | #{rodauth.login_form_footer_links.map do |_, link, text|
4 | "
7 |
--------------------------------------------------------------------------------
/templates/sms-confirm.str:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/templates/webauthn-authenticator-removed-email.str:
--------------------------------------------------------------------------------
1 | Someone (hopefully you) has removed a WebAuthn authenticator from the
2 | account associated to this email address. There are now #{rodauth.account_webauthn_ids.length} WebAuthn
3 | authenticator(s) with access to the account.
4 |
--------------------------------------------------------------------------------
/templates/login-display.str:
--------------------------------------------------------------------------------
1 |
2 | #{rodauth.login_hidden_field}
3 |
4 |
#{h rodauth.param(rodauth.login_param)}
5 |
6 |
--------------------------------------------------------------------------------
/templates/email-auth-email.str:
--------------------------------------------------------------------------------
1 | Someone has requested a login link for the account with this email
2 | address. If you did not request a login link, please ignore this
3 | message. If you requested a login link, please go to
4 | #{rodauth.email_auth_email_link}
5 | to login to this account.
6 |
--------------------------------------------------------------------------------
/spec/sql/mysql_setup.sql:
--------------------------------------------------------------------------------
1 | CREATE USER 'rodauth_test'@'localhost' IDENTIFIED BY 'rodauth_test';
2 | CREATE USER 'rodauth_test_password'@'localhost' IDENTIFIED BY 'rodauth_test';
3 | CREATE DATABASE rodauth_test;
4 | GRANT ALL ON rodauth_test.* TO 'rodauth_test_password'@'localhost' WITH GRANT OPTION;
5 |
--------------------------------------------------------------------------------
/templates/confirm-password.str:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/templates/otp-auth.str:
--------------------------------------------------------------------------------
1 |
7 |
8 | #{rodauth.otp_auth_form_footer}
9 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | Potential security issues can be publicly reported in the same
6 | manner as non-security issues (e.g. on GitHub Issues). However,
7 | if you would like to report them privately, you can report them
8 | via email to code@jeremyevans.net.
9 |
--------------------------------------------------------------------------------
/spec/migrate/002_account_password_hash_column.rb:
--------------------------------------------------------------------------------
1 | Sequel.migration do
2 | up do
3 | # Only for testing of account_password_hash_column, not recommended for new
4 | # applications
5 | add_column :accounts, :ph, String
6 | end
7 |
8 | down do
9 | drop_column :accounts, :ph
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/views/layout-other.str:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Foo #{@title}
5 |
6 |
7 | #{"
#{flash['error2']}
" if flash['error2']}
8 | #{"
#{flash['notice2']}
" if flash['notice2']}
9 | #{yield}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/templates/unlock-account-email.str:
--------------------------------------------------------------------------------
1 | Someone has requested that the account with this email be unlocked.
2 | If you did not request the unlocking of this account, please ignore this
3 | message. If you requested the unlocking of this account, please go to
4 | #{rodauth.unlock_account_email_link}
5 | to unlock this account.
6 |
--------------------------------------------------------------------------------
/templates/reset-password-email.str:
--------------------------------------------------------------------------------
1 | Someone has requested a password reset for the account with this email
2 | address. If you did not request a password reset, please ignore this
3 | message. If you requested a password reset, please go to
4 | #{rodauth.reset_password_email_link}
5 | to reset the password for the account.
6 |
--------------------------------------------------------------------------------
/templates/login-field.str:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/templates/sms-disable.str:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/doc/release_notes/1.7.0.txt:
--------------------------------------------------------------------------------
1 | = Improvements
2 |
3 | * The reset password, unlock account, and verify account features now
4 | temporarily store the feature-specific keys in the session instead
5 | of keeping them as parameters, which avoids leaking the keys to
6 | asset hosts or other external servers via the HTTP Referer header.
7 |
--------------------------------------------------------------------------------
/templates/password-field.str:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/doc/release_notes/2.25.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * You can now disable routing to specific routes by calling the
4 | related *_route configuration method with nil or false. The main
5 | reason you would want to do this is if you want to load a feature,
6 | but only want to use it for internal requests (using the
7 | internal_request feature), and not have the feature's routes exposed
8 | to users.
9 |
--------------------------------------------------------------------------------
/templates/otp-auth-code-field.str:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/doc/release_notes/1.1.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * The rodauth plugin now supports :csrf=>false and :flash=>false
4 | options. This will make it so it no longer depends on the csrf
5 | or flash plugins, which is useful when the csrf and flash
6 | functionality is provided via a different approach, such as
7 | when rodauth is being used inside middleware in a Rails
8 | application with the roda-rails library.
9 |
--------------------------------------------------------------------------------
/doc/update_password_hash.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Update Password Hash Feature
2 |
3 | The update password hash feature updates the hash for the password whenever
4 | the hash cost changes. For example, if you have a cost of 8, and later
5 | increase the cost to 10, anytime the user authenticates correctly with
6 | their password, their password hash will change from one that uses a cost
7 | of 8 to one that uses a cost of 10.
8 |
--------------------------------------------------------------------------------
/templates/otp-unlock-not-available.str:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/templates/change-login.str:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/templates/recovery-codes.str:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/templates/unlock-account-request.str:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/templates/verify-account.str:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/templates/otp-unlock-failed-email.str:
--------------------------------------------------------------------------------
1 | Someone (hopefully you) attempted to unlock TOTP authentication for the
2 | account associated to this email address, but failed as the
3 | authentication code submitted was not correct.
4 |
5 | If you did not initiate the TOTP authentication failure that generated
6 | this email, that means someone already has partial access to your
7 | account, but is unable to use TOTP authentication to fully authenticate
8 | themselves.
9 |
--------------------------------------------------------------------------------
/templates/verify-login-change-email.str:
--------------------------------------------------------------------------------
1 | Someone with an account has requested their login be changed to this email address:
2 |
3 | Old Login: #{rodauth.verify_login_change_old_login}
4 |
5 | New Login: #{rodauth.verify_login_change_new_login}
6 |
7 | If you did not request this login change, please ignore this message. If you
8 | requested this login change, please go to
9 | #{rodauth.verify_login_change_email_link}
10 | to verify the login change.
11 |
--------------------------------------------------------------------------------
/doc/release_notes/1.22.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A jwt_cors feature has been added, handling Cross-Origin Resource
4 | Sharing when using the jwt feature, including supporting CORS
5 | preflight requests.
6 |
7 | = Other Improvements
8 |
9 | * Mail templates that include links (e.g. for verifying accounts),
10 | now add a space after the link and before the newline, fixing
11 | issues with some web mail providers that have broken auto-linkers.
12 |
--------------------------------------------------------------------------------
/doc/guides/already_authenticated.rdoc:
--------------------------------------------------------------------------------
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
11 |
--------------------------------------------------------------------------------
/www/make_www.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'erb'
3 | require './lib/rodauth/version'
4 | Dir.chdir(File.dirname(__FILE__))
5 | erb = ERB.new(File.read('layout.erb'))
6 | Dir['pages/*.erb'].each do |page|
7 | public_loc = "#{page.gsub(/\Apages\//, 'public/').sub('.erb', '.html')}"
8 | content = content = ERB.new(File.read(page)).result(binding)
9 | title = title = File.basename(page.sub('.erb', ''))
10 | File.open(public_loc, 'wb'){|f| f.write(erb.result(binding))}
11 | end
12 |
--------------------------------------------------------------------------------
/lib/rodauth/features/reset_password_notify.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:reset_password_notify, :ResetPasswordNotify) do
5 | depends :reset_password
6 | loaded_templates %w'reset-password-notify-email'
7 | email :reset_password_notify, 'Password Reset Completed', :translatable=>true
8 |
9 | private
10 |
11 | def after_reset_password
12 | super
13 | send_reset_password_notify_email
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/rodauth/features/change_password_notify.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:change_password_notify, :ChangePasswordNotify) do
5 | depends :change_password, :email_base
6 | loaded_templates %w'password-changed-email'
7 | email :password_changed, 'Password Changed', :translatable=>true
8 |
9 | private
10 |
11 | def after_change_password
12 | super
13 | send_password_changed_email
14 | end
15 | end
16 | end
17 |
18 |
--------------------------------------------------------------------------------
/spec/views/layout.str:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #{@title}
5 |
6 |
7 | #{"
" if opts[:sessions_convert_symbols] ? flash['notice'] : flash[:notice]}
9 | #{yield}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/doc/release_notes/2.20.0.txt:
--------------------------------------------------------------------------------
1 | = Improvements
2 |
3 | * When using the active_sessions and remember features together,
4 | doing a global logout will automatically remove the remember key for
5 | the account, so the account will no longer be able to automatically
6 | create new sessions using the remember key.
7 |
8 | * The default value of webauthn_rp_id now removes the port from the
9 | origin if it exists, since the WebAuthn spec does not allow ports
10 | in the relying party identifier.
11 |
--------------------------------------------------------------------------------
/templates/verify-account-resend.str:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/doc/guides/links.rdoc:
--------------------------------------------------------------------------------
1 | = Display authentication links
2 |
3 | You can retrieve a relative URL to any Rodauth action by calling the
4 | corresponding *_path method on the Rodauth instance:
5 |
6 | Sign in
7 | Sign up
8 |
9 | For absolute URLs instead of paths, you can use the *_url methods:
10 |
11 | Sign in
12 | Sign up
13 |
--------------------------------------------------------------------------------
/templates/otp-locked-out-email.str:
--------------------------------------------------------------------------------
1 | TOTP authentication has been locked out on your account due to too many
2 | consecutive authentication failures. You can attempt to unlock TOTP
3 | authentication for your account by consecutively authenticating via
4 | TOTP multiple times.
5 |
6 | If you did not initiate the TOTP authentication failures that
7 | caused TOTP authentication to be locked out, that means someone already
8 | has partial access to your account, but is unable to use TOTP
9 | authentication to fully authenticate themselves.
10 |
--------------------------------------------------------------------------------
/templates/recovery-auth.str:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/doc/path_class_methods.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Path Class Methods Feature
2 |
3 | The path class methods feature allows for calling the *_path and *_url
4 | methods directly on the class, as opposed to an instance of the class.
5 |
6 | In order for the *_url methods to be used, you must use the base_url
7 | configuration so that determining the base URL doesn't depend on the
8 | submitted request, as the request will not be set when using the
9 | class method. Failure to do this will probably result in a NoMethodError
10 | being raised.
11 |
--------------------------------------------------------------------------------
/doc/release_notes/2.12.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * The following configuration methods have been added to the
4 | active_sessions feature:
5 |
6 | * active_sessions_insert_hash
7 | * active_sessions_key
8 | * active_sessions_update_hash
9 | * update_current_session?
10 |
11 | These methods allow you to control what gets inserted and
12 | updated into the active_sessions_table, and to control
13 | whether to perform updates.
14 |
15 | = Other Improvements
16 |
17 | * A typo was fixed in the default unlock account email.
18 |
--------------------------------------------------------------------------------
/doc/webauthn_verify_account.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for WebAuthn Verify Account Feature
2 |
3 | The webauthn_verify_account feature implements setting up an WebAuthn authenticator
4 | during the account verification process, and making such setup
5 | a requirement for account verification. By default, it disables
6 | asking for a password during account creation and verification,
7 | allowing for completely passwordless designs, where the only
8 | authentication option is WebAuthn. It depends on the verify_account
9 | and webauthn features.
10 |
--------------------------------------------------------------------------------
/doc/release_notes/1.4.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A update_password_hash feature has been added, which will update
4 | the password hash for the account whenever the account's current
5 | password hash has a cost different from the currently configured
6 | password hash cost.
7 |
8 | This allows you to increase the password hash cost for all
9 | accounts or for certain types of accounts, and have the password
10 | hashes automatically updated to use the new cost the next time the
11 | correct password is provided for the account.
12 |
--------------------------------------------------------------------------------
/templates/reset-password-request.str:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/templates/create-account.str:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/doc/change_password_notify.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Change Password Notify Feature
2 |
3 | The change password notify feature emails the user when their password
4 | is changed using the change password feature.
5 |
6 | == Auth Value Methods
7 |
8 | password_changed_email_body :: Body to use for the password changed emails
9 | password_changed_email_subject :: Subject to use for the password changed emails
10 |
11 | == Auth Methods
12 |
13 | create_password_changed_email :: A Mail::Message for the password changed email to send.
14 | send_password_changed_email :: Send the password changed email.
15 |
--------------------------------------------------------------------------------
/doc/release_notes/1.21.0.txt:
--------------------------------------------------------------------------------
1 | = Improvements
2 |
3 | * rotp 5.1 is now supported in the otp feature. Previous rotp
4 | versions down to rotp 2.1.1 remain supported.
5 |
6 | * When using the otp feature without the sms or recovery_codes
7 | features, if an account gets locked out from OTP authentication due
8 | to multiple invalid OTP authentication codes, automatically log
9 | them out, and redirect them to the login page. Previously, the
10 | default behavior in this case could be a redirect loop if OTP
11 | authentication is required for the user on the default_redirect
12 | page.
13 |
--------------------------------------------------------------------------------
/doc/guides/totp_or_recovery.rdoc:
--------------------------------------------------------------------------------
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
17 |
--------------------------------------------------------------------------------
/doc/guides/redirects.rdoc:
--------------------------------------------------------------------------------
1 | = Change redirect destination
2 |
3 | You can change the redirect destination for any Rodauth action by overriding
4 | the corresponding *_redirect 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
18 |
--------------------------------------------------------------------------------
/spec/views/layout-auth-check.str:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #{@title}
5 |
6 |
7 | #{"
" if opts[:sessions_convert_symbols] ? flash['notice'] : flash[:notice]}
9 | Is #{'Not ' unless rodauth.logged_in?}Logged In
10 | Is #{'Not ' unless rodauth.authenticated?}Authenticated
11 | #{yield}
12 |
13 |
14 |
--------------------------------------------------------------------------------
/doc/guides/email_only.rdoc:
--------------------------------------------------------------------------------
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.
17 |
--------------------------------------------------------------------------------
/doc/release_notes/2.24.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * rodauth.otp_available? has been added for checking whether the
4 | account is allowed to authenticate with OTP. It returns true
5 | when the account has setup OTP and OTP use is not locked out.
6 |
7 | * rodauth.recovery_codes_available? has been added for checking
8 | whether the account is allowed to authenticate using a recovery
9 | code. It returns true when there are any available recovery
10 | codes for the account to use.
11 |
12 | = Other Improvements
13 |
14 | * The otp feature no longer includes the tag for svg images,
15 | since that results in invalid HTML.
16 |
--------------------------------------------------------------------------------
/lib/rodauth/features/otp_modify_email.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:otp_modify_email, :OtpModifyEmail) do
5 | depends :otp, :email_base
6 |
7 | loaded_templates %w'otp-setup-email otp-disabled-email'
8 | email :otp_setup, 'TOTP Authentication Setup', :translatable=>true
9 | email :otp_disabled, 'TOTP Authentication Disabled', :translatable=>true
10 |
11 | private
12 |
13 | def after_otp_setup
14 | super
15 | send_otp_setup_email
16 | end
17 |
18 | def after_otp_disable
19 | super
20 | send_otp_disabled_email
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/templates/otp-unlock.str:
--------------------------------------------------------------------------------
1 |
10 |
11 | #{rodauth.otp_unlock_form_footer}
12 |
--------------------------------------------------------------------------------
/doc/release_notes/2.30.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A webauthn_autofill feature has been added to allow autofilling
4 | webauthn credentials during login (also known as conditional
5 | mediation). This allows for easier login using passkeys.
6 | This requires a supported browser and operating system on the
7 | client side to work.
8 |
9 | = Other Improvements
10 |
11 | * The load_memory method in the remember feature no longer raises
12 | a NoMethodError if the there is a remember cookie, the session is
13 | already logged in, and the account no longer exists. The
14 | load_memory method now removes the remember cookie and clears the
15 | session in that case.
16 |
--------------------------------------------------------------------------------
/doc/release_notes/2.28.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A webauthn_key_insert_hash configuration method has been added when
4 | using the webauthn feature, making it easier to add new columns to
5 | the webauthn key data, such as a custom name for the authenticator.
6 |
7 | = Other Improvements
8 |
9 | * When using the verify_account_grace_period feature, logged_in? now
10 | returns false for sessions where the grace period has expired.
11 |
12 | * When using the internal_request and reset_password features,
13 | submitting an internal request for an invalid login no longer tries
14 | to render a reset password request form.
15 |
16 | * The password_hash method is now public.
17 |
--------------------------------------------------------------------------------
/doc/release_notes/2.14.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A remembered_session_id method has been added for getting the
4 | account id from a valid remember token, without modifying the
5 | session to log the account in.
6 |
7 | = Other Improvements
8 |
9 | * The jwt_refresh feature's support for allowing refresh with
10 | an expired access token now works even if the Rodauth
11 | configuration uses an incorrect prefix.
12 |
13 | * The internal account_in_unverified_grace_period? method now
14 | returns false if an account has not been loaded and the
15 | session has not been logged in. Previously, calling this
16 | method in such cases would result in an exception being
17 | raised.
18 |
--------------------------------------------------------------------------------
/doc/guides/case_insensitive_login.rdoc:
--------------------------------------------------------------------------------
1 | = Case insensitive logins
2 |
3 | If your database schema doesn't support case insensitive logins, you can tell
4 | Rodauth to automatically lowercase login param values during authentication and
5 | persistence via the +normalize_login+ configuration option:
6 |
7 | normalize_login(&:downcase)
8 |
9 | Of the four database types Rodauth officially supports (PostgreSQL, MySQL,
10 | Microsoft SQL Server, and SQLite), only SQLite does not support a case
11 | insensitive column for storing logins by default. However, other databases could
12 | be configured to not use a case insensitive column for logins by default, in
13 | which case you would want to use this setting.
14 |
--------------------------------------------------------------------------------
/doc/release_notes/2.23.0.txt:
--------------------------------------------------------------------------------
1 | = Improvements
2 |
3 | * The otp feature now uses the :use_path option when rendering QR
4 | codes, resulting in significantly smaller svg images.
5 |
6 | * Removing all multifactor authentication methods now removes the fact
7 | that the session was authenticated via SMS, if the user used SMS as
8 | an authentication method for the current session.
9 |
10 | * The invalid domain check in the internal_request feature now works
11 | correctly when using the rack master branch.
12 |
13 | * The :httponly cookie option is no longer set automatically in the
14 | remember feature if the :http_only cookie option was provided by the
15 | user (rack recognizes both options).
16 |
--------------------------------------------------------------------------------
/doc/release_notes/2.40.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A reset_password_request_for_unverified_account configuration method
4 | is now available. This allows you to configure the behavior if an
5 | unverified account requests a password reset. If the method is not
6 | used, the default behavior remains to show an error for the login
7 | parameter.
8 |
9 | = Other Improvements
10 |
11 | * In the otp_unlock feature, instead of using a meta refresh tag in
12 | the HTML, a refresh HTTP header is used. This should fix the page
13 | not automatically refreshing in some browsers. You can customize
14 | this behavior by using the
15 | otp_unlock_not_available_set_refresh_header configuration method.
16 |
--------------------------------------------------------------------------------
/doc/guides/migrate_password_hash_algorithm.rdoc:
--------------------------------------------------------------------------------
1 | = Migrate users passwords from bcrypt to argon2 or back
2 |
3 | If you are currently using the default bcrypt password hash algorithm, and want to
4 | gradually migrate to the argon2 password hash algorithm, you can use both the argon2
5 | and update_password_hash features:
6 |
7 | plugin :rodauth do
8 | enable :login, :update_password_hash, :argon2
9 | end
10 |
11 | When a user with a current bcrypt password hash next successfully uses their
12 | password, their password hash will be migrated to argon2.
13 |
14 | If for some reason you want to migrate back from argon2 to bcrypt, you can set
15 | use_argon2? false in your Rodauth configuration.
16 |
--------------------------------------------------------------------------------
/templates/sms-setup.str:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/doc/release_notes/1.9.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * Roda.precompile_rodauth_templates has been added. This method
4 | allows for precompiling the templates that rodauth uses, which
5 | allows for memory saving when using a forking webserver that
6 | preloads the application, and also allows Rodauth to be used
7 | with an application that uses chroot after loading.
8 |
9 | = Improvements
10 |
11 | * If requesting a password reset link more than once, the same
12 | password reset key will be used. Previously, subsequent
13 | emails after the first request would contain an invalid key,
14 | so if the email for the original request was lost, you could
15 | not generate another key until that key expired.
16 |
--------------------------------------------------------------------------------
/templates/change-password.str:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/doc/release_notes/1.8.0.txt:
--------------------------------------------------------------------------------
1 | = Improvements
2 |
3 | * When using a browser, Rodauth now uses an appropriate 401, 403,
4 | or 422 error status for errors instead of using 200 success status.
5 | Many configuration methods have been added to customize the status
6 | codes used for specific types of errors.
7 |
8 | * The json_response_custom_error_status? configuration method
9 | has been added to the jwt feature, which if set to true makes
10 | the jwt feature use the same error status codes for JSON API
11 | requests that it would use for browser requests. For backward
12 | compatibility, the default is to continue to use the 400
13 | error status for all errors in the JSON API, but this will
14 | change in Rodauth 2.
15 |
--------------------------------------------------------------------------------
/doc/guides/reset_password_autologin.rdoc:
--------------------------------------------------------------------------------
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
22 |
--------------------------------------------------------------------------------
/lib/rodauth/version.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | # The major version of Rodauth, updated only for major changes that are
5 | # likely to require modification to apps using Rodauth.
6 | MAJOR = 2
7 |
8 | # The minor version of Rodauth, updated for new feature releases of Rodauth.
9 | MINOR = 42
10 |
11 | # The patch version of Rodauth, updated only for bug fixes from the last
12 | # feature release.
13 | TINY = 0
14 |
15 | # The full version of Rodauth as a string
16 | VERSION = "#{MAJOR}.#{MINOR}.#{TINY}".freeze
17 |
18 | # The full version of Rodauth as a number (1.17.0 => 11700)
19 | VERSION_NUMBER = MAJOR*10000 + MINOR*100 + TINY
20 |
21 | def self.version
22 | VERSION
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/doc/release_notes/2.16.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * Rodauth.lib has been added for using Rodauth purely as a library,
4 | useful in non-web applications:
5 |
6 | require 'rodauth'
7 | rodauth = Rodauth.lib do
8 | enable :create_account, :change_password
9 | end
10 | rodauth.create_account(login: 'foo@example.com', password: '...')
11 | rodauth.change_password(account_id: 24601, password: '...')
12 |
13 | This is built on top of the internal_request feature, and works by
14 | creating a Roda application with the rodauth plugin, and returning
15 | the related Rodauth::Auth class.
16 |
17 | = Other Improvements
18 |
19 | * The internal_request feature now works correctly for configurations
20 | where only_json? is set to true.
21 |
--------------------------------------------------------------------------------
/templates/two-factor-manage.str:
--------------------------------------------------------------------------------
1 | #{rodauth.two_factor_setup_heading unless rodauth.two_factor_setup_links.empty?}
2 |
3 |
4 | #{rodauth.two_factor_setup_links.map do |_, link, text|
5 | "
" if rodauth.two_factor_remove_links.length > 1}
16 |
17 |
--------------------------------------------------------------------------------
/lib/rodauth/features/webauthn_modify_email.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:webauthn_modify_email, :WebauthnModifyEmail) do
5 | depends :webauthn, :email_base
6 |
7 | loaded_templates %w'webauthn-authenticator-added-email webauthn-authenticator-removed-email'
8 | email :webauthn_authenticator_added, 'WebAuthn Authenticator Added', :translatable=>true
9 | email :webauthn_authenticator_removed, 'WebAuthn Authenticator Removed', :translatable=>true
10 |
11 | private
12 |
13 | def after_webauthn_setup
14 | super
15 | send_webauthn_authenticator_added_email
16 | end
17 |
18 | def after_webauthn_remove
19 | super
20 | send_webauthn_authenticator_removed_email
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/rodauth/features/update_password_hash.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:update_password_hash, :UpdatePasswordHash) do
5 | depends :login_password_requirements_base
6 |
7 | def password_match?(password)
8 | if (result = super) && update_password_hash?
9 | @update_password_hash = false
10 | set_password(password)
11 | end
12 |
13 | result
14 | end
15 |
16 | private
17 |
18 | def update_password_hash?
19 | password_hash_cost != @current_password_hash_cost || @update_password_hash
20 | end
21 |
22 | def get_password_hash
23 | if hash = super
24 | @current_password_hash_cost = extract_password_hash_cost(hash)
25 | end
26 |
27 | hash
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/rodauth/features/logout.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:logout, :Logout) do
5 | notice_flash "You have been logged out"
6 | loaded_templates %w'logout'
7 | view 'logout', 'Logout'
8 | additional_form_tags
9 | before
10 | after
11 | button 'Logout'
12 | redirect{require_login_redirect}
13 | response
14 |
15 | auth_methods :logout
16 |
17 | route do |r|
18 | before_logout_route
19 |
20 | r.get do
21 | logout_view
22 | end
23 |
24 | r.post do
25 | transaction do
26 | before_logout
27 | logout
28 | after_logout
29 | end
30 | logout_response
31 | end
32 | end
33 |
34 | def logout
35 | clear_session
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/rodauth/features/path_class_methods.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:path_class_methods, :PathClassMethods) do
5 | def post_configure
6 | super
7 |
8 | klass = self.class
9 | klass.features.each do |feature_name|
10 | feature = FEATURES[feature_name]
11 | feature.routes.each do |handle_meth|
12 | route = handle_meth.to_s.sub(/\Ahandle_/, '')
13 | path_meth = :"#{route}_path"
14 | url_meth = :"#{route}_url"
15 | instance = klass.allocate.freeze
16 | klass.define_singleton_method(path_meth){|opts={}| instance.send(path_meth, opts)}
17 | klass.define_singleton_method(url_meth){|opts={}| instance.send(url_meth, opts)}
18 | end
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/doc/reset_password_notify.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Reset Password Notify Feature
2 |
3 | The reset password notify feature emails the user after the user has
4 | reset their password. The user has already been sent a reset password
5 | email by this point, so they know a password reset was requested, but
6 | this feature allows for confirming that the password reset process
7 | was completed. Depends on the reset_password feature.
8 |
9 | == Auth Value Methods
10 |
11 | reset_password_notify_email_subject :: The subject to use for the reset password notify email.
12 | reset_password_notify_email_body :: The body to use for the reset password notify email.
13 |
14 | == Auth Methods
15 |
16 | create_reset_password_notify_email :: A Mail::Message for the reset password notify email.
17 | send_reset_password_notify_email :: Send the reset password notify email.
18 |
--------------------------------------------------------------------------------
/doc/release_notes/1.2.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * An otp_drift configuration method has been added to the otp plugin,
4 | which allows you to set the number of seconds of allowed drift. This
5 | makes the otp plugin easier to use if the client and server do not
6 | have good synchronize to the same time source.
7 |
8 | = Other Improvements
9 |
10 | * Passwords containing the ASCII NUL character "\0" are no longer
11 | allowed, as bcrypt truncates the password at the first NUL
12 | character.
13 |
14 | Note that bcrypt only uses the first 72 characters of the password
15 | when constructing the hash, but Rodauth does not enforce a limit
16 | of 72 characters. If you want to enforce a maximum password length
17 | in your application, use the password_meets_requirements?
18 | configuration method with a block and call super inside the block.
19 |
--------------------------------------------------------------------------------
/templates/webauthn-autofill.str:
--------------------------------------------------------------------------------
1 |
9 |
10 |
--------------------------------------------------------------------------------
/doc/release_notes/2.13.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A set_error_reason configuration method has been added. This method
4 | is called whenever a error occurs in Rodauth, with a symbol
5 | describing the error. The default implementation of this method does
6 | nothing, it has been added to make it easier for Rodauth users to
7 | implement custom handling for specific error types. See the Rodauth
8 | documentation for this method to see the list of symbols this method
9 | can be called with.
10 |
11 | = Other Improvements
12 |
13 | * When using active_sessions and jwt_refresh together, and allowing for
14 | expired JWTs when refreshing, you can now call
15 | rodauth.check_active_session before r.rodauth. Previously, this
16 | did not work, and you had to call rodauth.check_active_session
17 | after r.rodauth.
18 |
19 | * The default templates now also support Bootstrap 5.
20 |
--------------------------------------------------------------------------------
/doc/release_notes/2.33.0.txt:
--------------------------------------------------------------------------------
1 | = Improvements
2 |
3 | * Rodauth no longer accidentally confirms an SMS number upon valid
4 | authentication by an alternative second factor.
5 |
6 | * Rodauth now automatically expires SMS confirmation codes after 24
7 | hours by default. You can use the sms_confirm_deadline
8 | configuration method to adjust the deadline. Previously, if an
9 | invalid SMS number was submitted, or the SMS confirm code was never
10 | received, it was not possible to continue SMS setup without
11 | administrative intervention.
12 |
13 | * Rodauth no longer overwrites existing primary key values when
14 | inserting new accounts. This fixes cases such as setting account
15 | primary key values to UUIDs before inserting.
16 |
17 | * When submitting a request to a valid endpoint with a missing token,
18 | Rodauth now returns an error response instead of a 404 response.
19 |
--------------------------------------------------------------------------------
/doc/release_notes/2.39.0.txt:
--------------------------------------------------------------------------------
1 | = Improvements
2 |
3 | * Rodauth now supports Roda's plain_hash_response_headers plugin on
4 | Rack 3+, by using lowercase response header keys, instead of
5 | relying on Roda's default conversion of response header keys to
6 | lowercase.
7 |
8 | * When setting login_return_to_requested_location? to true, by
9 | default, Rodauth will no longer return to the requested location if
10 | it is more than 2048 bytes in size. This is to avoid exceeding the
11 | 4K cookie size limit. You can modify this limit using the new
12 | login_return_to_requested_location_max_path_size configuration
13 | method.
14 |
15 | * Rodauth now uses JSON.generate instead of JSON.fast_generate to
16 | avoid a deprecation warning in recent json gem versions.
17 |
18 | * Rodauth now uses allowed_origins instead of origin when using
19 | WebAuthn 3.4+ to avoid a deprecation warning.
20 |
--------------------------------------------------------------------------------
/doc/release_notes/2.9.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A json feature has been extracted from the existing jwt feature.
4 | This feature allows for the same JSON API previously supported
5 | by the JWT feature, but stores the session information in the
6 | Rack session instead of in a separate JWT. This makes it
7 | significantly easier to have certain pages use the JSON API,
8 | and other pages the HTML forms.
9 |
10 | = Other Improvements
11 |
12 | * If the remember cookie is created in an SSL request, the Secure
13 | flag is added by default, so the cookie will not be transmitted
14 | in non-SSL requests.
15 |
16 | = Backwards Compatibility
17 |
18 | * Rodauth configurations that use the remember feature and support
19 | requests over both http and https and want to have the remember
20 | cookie transmitted over both should now include :secure=>false in
21 | remember_cookie_options.
22 |
--------------------------------------------------------------------------------
/doc/http_basic_auth.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for HTTP Basic Auth Feature
2 |
3 | The HTTP basic auth feature allows logins using HTTP basic authentication,
4 | described in RFC 1945.
5 |
6 | In your routing block, you can require HTTP basic authentication via:
7 |
8 | rodauth.require_http_basic_auth
9 |
10 | If you want to allow HTTP basic authentication but not require it, you can
11 | call:
12 |
13 | rodauth.http_basic_auth
14 |
15 | == Auth Value Methods
16 |
17 | http_basic_auth_realm :: The realm to return in the WWW-Authenticate header.
18 | require_http_basic_auth? :: If true, when +rodauth.require_login+ or +rodauth.require_authentication+ is used, return a 401 status page if basic auth has not been provided, instead of redirecting to the login page. If false, +rodauth.require_login+ or +rodauth.require_authentication+ will check for HTTP basic authentication if not already logged in. False by default.
19 |
--------------------------------------------------------------------------------
/templates/webauthn-auth.str:
--------------------------------------------------------------------------------
1 |
11 |
12 |
--------------------------------------------------------------------------------
/doc/guides/password_column.rdoc:
--------------------------------------------------------------------------------
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
26 |
--------------------------------------------------------------------------------
/doc/release_notes/1.14.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A change_password_notify feature has been added, which emails the
4 | user when the change_password feature is used to change their
5 | password. This can alert the user when their password may have
6 | been changed without their knowledge.
7 |
8 | = Other Improvements
9 |
10 | * When using the account_expiration feature with the reset_password
11 | feature, resetting the passwords for expired accounts is no longer
12 | allowed. Note that the previous behavior isn't considered a
13 | security issue, because even after resetting their password,
14 | expired accounts could not login.
15 |
16 | * When using the account_expiration feature with the lockout feature,
17 | unlocking expired accounts is no longer allowed. Note that the
18 | previous behavior isn't considered a security issue, because even
19 | after unlocking the account, expired accounts could not login.
20 |
--------------------------------------------------------------------------------
/templates/webauthn-setup.str:
--------------------------------------------------------------------------------
1 |
12 |
13 |
--------------------------------------------------------------------------------
/www/public/css/rodauth.css:
--------------------------------------------------------------------------------
1 | nav.navbar.navbar-default a.navbar-brand, nav.navbar.navbar-default ul.nav > li > a {
2 | color: white;
3 | }
4 |
5 | h1.byline {
6 | margin-bottom: 30px;
7 | font-weight: bold;
8 | }
9 |
10 | a.navbar-brand {
11 | margin-right: 30px;
12 | font-size: 32px;
13 | }
14 | a.navbar-brand > img {
15 | display: inline-block;
16 | margin-top: -10px;
17 | }
18 |
19 | body #content a {
20 | color: #5E0000;
21 | font-weight: bold;
22 | text-decoration: underline;
23 | }
24 | nav.navbar {
25 | background: #2290FF;
26 | }
27 | body, p, h1, h2, h3, a {
28 | font-family: 'Open Sans', sans-serif;
29 | }
30 | body {
31 | color: #5E0000;
32 | background: #9DC6FF;
33 | }
34 |
35 | .path, .code {
36 | background: white;
37 | padding: 3px 5px;
38 | }
39 |
40 | .path, .code, pre code {
41 | font-family: 'PT Mono', monospace;
42 | font-size: 14px;
43 | }
44 |
45 | body, p, li {
46 | font-size: 16px;
47 | }
48 |
--------------------------------------------------------------------------------
/doc/guides/change_table_and_column_names.rdoc:
--------------------------------------------------------------------------------
1 | = Change table and column names
2 |
3 | All tables that Rodauth uses will have a configuration method that ends with
4 | +_table+ for configuring the table name. For example, if you store user accounts
5 | in the +users+ table instead of +accounts+ table, you can use the following
6 | in your configuration:
7 |
8 | accounts_table :users
9 |
10 | All columns that Rodauth uses will have a configuration method that ends with
11 | +_column+ for configuring the column name. For example, if you are storing the
12 | login for accounts in the +login+ column instead of the +email+ column, you
13 | can use the following in your configuration:
14 |
15 | login_column :login
16 |
17 | Please see the documentation for Rodauth features for the names of the
18 | configuration methods that you can use. You can see the default values for
19 | the tables and columns in the {"Creating tables" section of the README}[rdoc-ref:README.rdoc].
20 |
--------------------------------------------------------------------------------
/doc/release_notes/2.35.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A throw_rodauth_error method has been added to make it easier
4 | for external extensions to throw the expected error value without
5 | setting a field error.
6 |
7 | = Improvements
8 |
9 | * If an account is not currently logged in, but Rodauth knows the
10 | related account id, remove_all_active_sessions and related
11 | methods in the active_sessions plugin will now remove sessions
12 | for the related account.
13 |
14 | * When using the internal_request feature and subclasses,
15 | internal_request_configuration blocks in superclasses are now
16 | respected when creating the internal request class for a
17 | subclass. When creating the internal request in the subclass,
18 | this behaves as if all internal_request_configuration blocks
19 | were specified directly in the subclass.
20 |
21 | * An ignored block warning on Ruby 3.4 is now avoided by having
22 | Rodauth.load_dependencies accept a block.
23 |
--------------------------------------------------------------------------------
/doc/release_notes/1.15.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * create_account_set_password? and verify_account_set_password?
4 | configuration methods have been added to the create_account and
5 | verify_account features. Setting:
6 |
7 | verify_account_set_password? true
8 |
9 | in your rodauth configuration will change Rodauth so that instead
10 | of asking for a password on the create account form, it will ask for
11 | a password on the verify account form.
12 |
13 | This can fix a possible issue where an attacker creates an account
14 | for a user with a password the attacker knows. If the user clicks
15 | on the link in the verify account email and clicks on the button on
16 | the verify account page, the attacker would have have a verified
17 | account that they know the password to.
18 |
19 | By setting verify_account_set_password? to true, you can ensure that
20 | only the user who has access to the email can enter the password for
21 | the account.
22 |
--------------------------------------------------------------------------------
/doc/release_notes/1.17.0.txt:
--------------------------------------------------------------------------------
1 | = Improvements
2 |
3 | * Support has been added for using Roda's route_csrf plugin with
4 | request-specific CSRF tokens. When loading the Rodauth into
5 | your Roda app, specify the :csrf=>:route_csrf plugin option
6 | so that Rodauth will load the route_csrf plugin instead of
7 | the csrf plugin.
8 |
9 | * The use_request_specific_csrf_tokens? configuration option
10 | has been added, it defaults to true when the the
11 | :csrf=>:route_csrf option is used when loading the plugin.
12 |
13 | * If you have custom templates for the reset password request,
14 | unlock account request, or verify account resend link
15 | request, you will have to update them to use the new
16 | request-specific CSRF token feature.
17 |
18 | = Backwards Compatibility
19 |
20 | * The csrf_tag configuration method now accepts the path as
21 | an optional argument, previously it accepted no arguments.
22 | The optional argument defaults to the path of the current
23 | request.
24 |
--------------------------------------------------------------------------------
/doc/release_notes/1.3.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A login_maximum_length configuration method has been added. This
4 | defaults to 255, and rodauth will now show an error message if a
5 | user tries to create a login longer than this setting.
6 |
7 | = Backwards Compatibility
8 |
9 | * Rodauth's documentation and test code now use :Bignum instead of
10 | Bignum for database-independent 64-bit integer types. This is
11 | because using Bignum is now deprecated in Sequel as it will stop
12 | working correctly in ruby 2.4+, due to the unification of Fixnum
13 | and Bignum into Integer.
14 |
15 | Rodauth's library code does not use either :Bignum or Bignum, but if
16 | you are starting to use Rodauth and are copying the example
17 | migration from Rodauth's documentation, or you are running the
18 | migrations in Rodauth's tests, you now need to use Sequel 4.35.0+.
19 |
20 | * Some files related to the hosting of the demo site on Heroku have
21 | been removed from the repository.
22 |
--------------------------------------------------------------------------------
/doc/release_notes/2.5.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A login_return_to_requested_location_path configuration method has
4 | been added to the login feature. This controls the path to redirect
5 | to if using login_return_to_requested_location?. By default, this
6 | is the same as the fullpath of the request that required login if
7 | that request was a GET request, and nil if that request was not a
8 | GET request. Previously, the fullpath of that request was used even
9 | if it was not a GET request, which caused problems as browsers use a
10 | GET request for redirects, and it is a bad idea to redirect to a path
11 | that may not handle GET requests.
12 |
13 | * A change_login_needs_verification_notice_flash configuration method
14 | has been added to the verify_login_change feature, for allowing
15 | translations when using the feature and not using the
16 | change_login_notice_flash configuration method.
17 |
18 | = Other Improvements
19 |
20 | * new_password_label is now translatable.
21 |
--------------------------------------------------------------------------------
/spec/path_class_methods_spec.rb:
--------------------------------------------------------------------------------
1 | require_relative 'spec_helper'
2 |
3 | describe 'path_class_methods feature' do
4 | it "should add *_path and *_url methods as class methods" do
5 | rodauth do
6 | prefix '/foo'
7 | base_url 'https://foo.example.com'
8 | enable :path_class_methods, :login, :logout
9 | end
10 | roda do |r|
11 | end
12 |
13 | app.rodauth.login_path.must_equal '/foo/login'
14 | app.rodauth.logout_url.must_equal 'https://foo.example.com/foo/logout'
15 |
16 | app.rodauth.logout_path('bar'=>'baz').must_equal '/foo/logout?bar=baz'
17 | app.rodauth.login_url('bar'=>'baz').must_equal 'https://foo.example.com/foo/login?bar=baz'
18 | end
19 |
20 | it "*_path should work without base_url" do
21 | rodauth do
22 | enable :path_class_methods, :login, :logout
23 | end
24 | roda do |r|
25 | end
26 |
27 | app.rodauth.logout_path.must_equal '/logout'
28 |
29 | proc{app.rodauth.login_url}.must_raise NoMethodError
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/doc/webauthn_autofill.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for WebAuthn Autofill Feature
2 |
3 | The webauthn_autofill feature enables autofill UI (aka "conditional mediation")
4 | for WebAuthn credentials, logging the user in on selection. It depends on the
5 | webauthn_login feature.
6 |
7 | This feature allows generating WebAuthn credential options and submitting a
8 | WebAuthn login request without providing a login, which can be used
9 | independently from the autofill UI.
10 |
11 | == Auth Value Methods
12 |
13 | webauthn_autofill? :: Whether to activate the autofill UI on the login page.
14 | webauthn_autofill_js :: The javascript code to execute on the login page to enable autofill UI.
15 | webauthn_autofill_js_route :: The route to the webauthn autofill javascript file.
16 | webauthn_invalid_webauthn_id_message :: The error message to show when provided WebAuthn ID wasn't found in the database.
17 |
18 | == Auth Methods
19 |
20 | before_webauthn_autofill_js_route :: Run arbitrary code before handling a webauthn autofill javascript route.
21 |
--------------------------------------------------------------------------------
/spec/change_password_notify_spec.rb:
--------------------------------------------------------------------------------
1 | require_relative 'spec_helper'
2 |
3 | describe 'Rodauth change_password_notify feature' do
4 | it "should email when using change password" do
5 | rodauth do
6 | enable :login, :logout, :change_password_notify
7 | change_password_requires_password? false
8 | end
9 | roda do |r|
10 | r.rodauth
11 | r.root{view :content=>""}
12 | end
13 |
14 | login
15 | page.current_path.must_equal '/'
16 |
17 | visit '/change-password'
18 | fill_in 'New Password', :with=>'0123456'
19 | fill_in 'Confirm Password', :with=>'0123456'
20 | click_button 'Change Password'
21 | page.find('#notice_flash').text.must_equal "Your password has been changed"
22 |
23 | page.current_path.must_equal '/'
24 | email = email_sent
25 | email.subject.must_equal "Password Changed"
26 | email.body.to_s.must_equal <false option.
21 |
--------------------------------------------------------------------------------
/doc/guides/render_confirmation.rdoc:
--------------------------------------------------------------------------------
1 | = Render confirmation view
2 |
3 | Most Rodauth actions redirect and display a flash notice after they're successfully performed. However, in some cases you may wish to render a view confirming that the action was successful, for nicer user experience.
4 |
5 | For example, when the user creates an account, you might render a page with a call to action to verify their account. Assuming you've created an +account_created+ view template alongside your other Rodauth templates, you can configure the following:
6 |
7 | after_create_account do
8 | # render "account_created" view template with page title of "Account created!"
9 | return_response view("account_created", "Account created!")
10 | end
11 |
12 | Similarly, when the user has requested a password reset, you can render a page telling them to check their email:
13 |
14 | after_reset_password_request do
15 | # render "password_reset_sent" view template with page title of "Password sent!"
16 | return_response view("password_reset_sent", "Password sent!")
17 | end
18 |
--------------------------------------------------------------------------------
/doc/logout.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Logout Feature
2 |
3 | The logout feature implements a logout button, which clears the session.
4 | It is the simplest feature.
5 |
6 | == Auth Value Methods
7 |
8 | logout_additional_form_tags :: HTML fragment containing additional form tags to use on the logout form.
9 | logout_button :: The text to use for the logout button.
10 | logout_notice_flash :: The flash notice to show after logout.
11 | logout_page_title :: The page title to use on the logout form.
12 | logout_redirect :: Where to redirect after a logout.
13 | logout_route :: The route to the logout action. Defaults to +logout+.
14 |
15 | == Auth Methods
16 |
17 | after_logout :: Run arbitrary code after logout.
18 | before_logout :: Run arbitrary code before logout.
19 | before_logout_route :: Run arbitrary code before handling a logout route.
20 | logout :: Log the user out, by default clearing the session.
21 | logout_response :: Return a response after a successful logout. By default, redirects to +logout_redirect+.
22 | logout_view :: The HTML to use for the logout form.
23 |
--------------------------------------------------------------------------------
/spec/views/login.str:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/templates/otp-setup.str:
--------------------------------------------------------------------------------
1 |
25 |
--------------------------------------------------------------------------------
/doc/webauthn_login.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for WebAuthn Login Feature
2 |
3 | The webauthn_login feature implements passwordless authentication via
4 | WebAuthn. It depends on the login and webauthn features.
5 |
6 | == Auth Value Methods
7 |
8 | webauthn_login_user_verification_additional_factor? :: Whether passwordless login via WebAuthn should consider user verification as 2nd factor when using multifactor authentication, false by default. Setting this to true means that the app trusts the user verification done by the authenticator is strong enough to be considered an additional factor.
9 | webauthn_login_error_flash :: The flash error to show if there is a failure during passwordless login via WebAuthn.
10 | webauthn_login_failure_redirect :: Whether to redirect if there is a failure during passwordless login via WebAuthn.
11 | webauthn_login_route :: The route to the webauthn login action.
12 |
13 | == Auth Methods
14 |
15 | before_webauthn_login :: Any actions to take before passwordless login via WebAuthn.
16 | before_webauthn_login_route :: Run arbitrary code before handling a webauthn login route.
17 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015-2023 Jeremy Evans
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to
5 | deal in the Software without restriction, including without limitation the
6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 | sell copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/doc/guides/delay_password.rdoc:
--------------------------------------------------------------------------------
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 remembering 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
26 |
--------------------------------------------------------------------------------
/doc/guides/email_requirements.rdoc:
--------------------------------------------------------------------------------
1 | = Customize email requirements
2 |
3 | By default, Rodauth requires emails to have at least 3 characters and at most
4 | 255 bytes. You can modify the minimum and maximum length:
5 |
6 | plugin :rodauth do
7 | enable :login, :logout, :create_account
8 |
9 | # Require emails to have at least 5 characters
10 | login_minimum_length 5
11 |
12 | # Don't allow emails longer than 100 characters
13 | login_maximum_length 100
14 |
15 | # Don't allow emails larger than 200 bytes
16 | login_maximum_bytes 200
17 | end
18 |
19 | You can also override email address validation, and do more advanced email
20 | checks, such as checking whether the email address exists using the
21 | {Truemail}[https://github.com/truemail-rb/truemail] gem:
22 |
23 | require "truemail"
24 |
25 | Truemail.configure do |config|
26 | config.verifier_email = "verifier@example.com"
27 | end
28 |
29 | plugin :rodauth do
30 | enable :login, :logout, :create_account
31 |
32 | login_valid_email? do |email|
33 | super(email) && Truemail.valid?(email)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/templates/webauthn-remove.str:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/doc/guides/i18n.rdoc:
--------------------------------------------------------------------------------
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"
27 |
28 | Alternatively, you can use the
29 | {rodauth-i18n}[https://github.com/janko/rodauth-i18n] gem.
30 |
--------------------------------------------------------------------------------
/doc/guides/share_configuration.rdoc:
--------------------------------------------------------------------------------
1 | = Share configuration via inheritance
2 |
3 | If you have multiple configurations that needs to share some amount of
4 | authentication behaviour, you can do so through inheritance. For example:
5 |
6 | require "rodauth"
7 |
8 | class RodauthBase < Rodauth::Auth
9 | configure do
10 | # common authentication configuration
11 | end
12 | end
13 |
14 | class RodauthMain < RodauthBase # inherit common configuration
15 | configure do
16 | # main-specific authentication configuration
17 | end
18 | end
19 |
20 | class RodauthAdmin < RodauthBase # inherit common configuration
21 | configure do
22 | # admin-specific authentication configuration
23 | end
24 | end
25 |
26 | class RodauthApp < Roda
27 | plugin :rodauth, auth_class: RodauthMain
28 | plugin :rodauth, auth_class: RodauthAdmin, name: :admin
29 | # ...
30 | end
31 |
32 | However, when doing this, you need to be careful that you do not use a
33 | configuration method in a superclass, and then load a feature in a subclass
34 | that overrides the configuration you set in the superclass.
35 |
--------------------------------------------------------------------------------
/doc/release_notes/2.4.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A password_pepper feature has been added. This allows you to use a
4 | secret key (called a pepper) to append to passwords before hashing
5 | and hash checking. Using this approach, if an attacker obtains the
6 | password hash, it is unusable for cracking unless they can also
7 | get access to the pepper.
8 |
9 | The password_pepper feature also supports a list of previous peppers
10 | that can be used to implement secret rotation and to support
11 | compatibility with unpeppered passwords.
12 |
13 | Rodauth by default uses database functions for password hash
14 | checking on PostgreSQL, MySQL, and Microsoft SQL Server, which in
15 | general provides more security than a password pepper, but both
16 | approaches can be used simultaneously.
17 |
18 | * A session_key_prefix configuration method has been added for
19 | prefixing the values of all default session keys. This can be
20 | useful if you are using multiple Rodauth configurations in the same
21 | application and want to make sure the session keys for the separate
22 | configurations do not overlap.
23 |
--------------------------------------------------------------------------------
/spec/reset_password_notify_spec.rb:
--------------------------------------------------------------------------------
1 | require_relative 'spec_helper'
2 |
3 | describe 'Rodauth reset_password_notify feature' do
4 | it "should send email when password is reset" do
5 | rodauth do
6 | enable :reset_password_notify
7 | end
8 | roda do |r|
9 | r.rodauth
10 | r.root{view :content=>""}
11 | end
12 |
13 | visit '/login'
14 | login(:pass=>'01234567', :visit=>false)
15 | click_button 'Request Password Reset'
16 | page.find('#notice_flash').text.must_equal "An email has been sent to you with a link to reset the password for your account"
17 |
18 | visit email_link(/(\/reset-password\?key=.+)$/)
19 | fill_in 'Password', :with=>'0123456'
20 | fill_in 'Confirm Password', :with=>'0123456'
21 | click_button 'Reset Password'
22 | page.find('#notice_flash').text.must_equal "Your password has been reset"
23 |
24 | email = email_sent
25 | email.subject.must_equal "Password Reset Completed"
26 | email.body.to_s.must_equal <
2 | #{rodauth.remember_additional_form_tags}
3 | #{rodauth.csrf_tag}
4 |
18 | #{rodauth.button(rodauth.remember_button)}
19 |
20 |
--------------------------------------------------------------------------------
/doc/release_notes/2.29.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * When using the remember feature, by default, the remember deadline
4 | is extended while logged in, if it hasn't been extended in the last
5 | hour
6 |
7 | * An account! method has been added, which will return the hash for
8 | the account if already retrieved, or attempt to retrieve the
9 | account hash using the currently logged in session if not.
10 | Because of the ambiguity in the provenance of the returned account
11 | hash, callers should be careful when using this method.
12 |
13 | * A remove_active_session method has been added. You can call this
14 | method with a specific session id, and it will remove the related
15 | active session.
16 |
17 | * A render: false plugin option is now support, which will disable
18 | the automatic loading of the render plugin. This should only be
19 | used if you are completely replacing Rodauth's view rendering with
20 | your own.
21 |
22 | = Other Improvements
23 |
24 | * When logging in when using the active_sessions feature, if there is
25 | a current active session, it is removed before a new active session
26 | is created. This prevents some stale active sessions from remaining
27 | in the database (which would eventually be cleaned up later).
28 |
--------------------------------------------------------------------------------
/doc/release_notes/1.16.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A disallow_common_passwords feature has been added. This feature
4 | by default will disallow the 10,000 most common passwords:
5 |
6 | enable :disallow_common_passwords
7 |
8 | You can supply your own file containing common passwords separated
9 | by newlines ("\n"):
10 |
11 | most_common_passwords_file '/path/to/file'
12 |
13 | You can also supply a password dictionary directly as any object
14 | that responds to include?:
15 |
16 | most_common_passwords some_password_dictionary_object
17 |
18 | The reason only the 10,000 most common passwords are used by
19 | default is larger password files would significantly bloat the
20 | size of the gem. Also, because the most common passwords are kept
21 | in memory by default for performance reasons, larger password
22 | files can bloat the memory usage of the process (the
23 | disallow_common_passwords feature should use around 500KB of
24 | memory by default). For very large password dictionaries,
25 | consider using a custom object that does not keep all common
26 | passwords in memory.
27 |
28 | = Other Improvements
29 |
30 | * Rodauth no longer uses the Rack::Request#[] method to get
31 | parameter values. This method is deprecated in Rack 2.
32 |
--------------------------------------------------------------------------------
/doc/release_notes/2.18.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * When using the json and multifactor auth features, the JSON API can
4 | now access the multifactor-manage route to get lists of endpoints
5 | for setting up and disabling supported multifactor authentication
6 | methods. The JSON API can now also access the multifactor-auth
7 | route to get a list of endpoints for multifactor authentication for
8 | the currently logged in account.
9 |
10 | = Other Improvements
11 |
12 | * In the otp feature, the viewbox: true rqrcode option is now used
13 | when creating the QR code. This results in a QR code that is
14 | displayed better and is easier to style. This option only has
15 | an effect when using rqrcode 2+.
16 |
17 | * When using the :auth_class option when loading the rodauth plugin,
18 | the configuration name is set in the provided auth class, unless the
19 | auth class already has a configuration name set.
20 |
21 | * The example migration now recommends using a partial index on the
22 | email column in cases where the database supports partial indexes.
23 | Previously, it only recommended it on PostgreSQL.
24 |
25 | * The argon2 feature now works with argon2 2.1.0. Older versions of
26 | Rodauth work with both earlier and later versions of argon2, but
27 | not 2.1.0.
28 |
--------------------------------------------------------------------------------
/doc/guides/create_account_programmatically.rdoc:
--------------------------------------------------------------------------------
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 | )
39 |
--------------------------------------------------------------------------------
/doc/email_base.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Email Base Feature
2 |
3 | The email base feature is automatically loaded when you use a Rodauth feature
4 | that requires sending emails.
5 |
6 | == Auth Value Methods
7 |
8 | allow_raw_email_token? :: When +hmac_secret+ is used, this allows the use of the raw token. This should only be set to true temporarily during a transition period from using raw tokens to using HMACed tokens. After the transition period, this should not be set, as setting this to true removes the security that HMACed tokens add.
9 | default_post_email_redirect :: Where to redirect after sending an email. This is the default redirect location for all redirects after an email is sent when the account is not logged in. Also includes cases where an email is not sent due to rate limiting.
10 | email_from :: The from address to use for emails sent by Rodauth.
11 | email_subject_prefix :: The prefix to use for email subjects
12 | require_mail? :: Set to false to not require mail, useful if using a different library for sending email.
13 |
14 | == Auth Methods
15 |
16 | create_email(subject, body) :: Return a Mail::Message instance with the given subject and body.
17 | email_to :: The email address to send emails to, by default the login of the current account.
18 | send_email(email) :: Deliver a given Mail::Message instance.
19 |
--------------------------------------------------------------------------------
/lib/rodauth/features/otp_lockout_email.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:otp_lockout_email, :OtpLockoutEmail) do
5 | depends :otp_unlock, :email_base
6 |
7 | loaded_templates %w'otp-locked-out-email otp-unlocked-email otp-unlock-failed-email'
8 | email :otp_locked_out, 'TOTP Authentication Locked Out', :translatable=>true
9 | email :otp_unlocked, 'TOTP Authentication Unlocked', :translatable=>true
10 | email :otp_unlock_failed, 'TOTP Authentication Unlocking Failed', :translatable=>true
11 |
12 | auth_value_method :send_otp_locked_out_email?, true
13 | auth_value_method :send_otp_unlocked_email?, true
14 | auth_value_method :send_otp_unlock_failed_email?, true
15 |
16 | private
17 |
18 | def after_otp_authentication_failure
19 | super
20 |
21 | if otp_locked_out? && send_otp_locked_out_email?
22 | send_otp_locked_out_email
23 | end
24 | end
25 |
26 | def after_otp_unlock_auth_success
27 | super
28 |
29 | if !otp_locked_out? && send_otp_unlocked_email?
30 | send_otp_unlocked_email
31 | end
32 | end
33 |
34 | def after_otp_unlock_auth_failure
35 | super
36 |
37 | if send_otp_unlock_failed_email?
38 | send_otp_unlock_failed_email
39 | end
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/doc/release_notes/1.6.0.txt:
--------------------------------------------------------------------------------
1 | = New Feature
2 |
3 | * An http_basic_auth feature has been added, allowing the use of
4 | HTTP Basic Auth to login.
5 |
6 | = New Configuration Options for jwt Feature
7 |
8 | * jwt_session_hash has been added, for modifying the hash given before
9 | creating the JWT. This can be used for setting JWT claims.
10 | Example:
11 |
12 | jwt_session_hash do
13 | super().merge(:exp=>Time.now.to_i + 120)
14 | end
15 |
16 | * jwt_decode_opts has been added for specifying additional options to
17 | JWT.decode. Among other things, this allows for JWT claim
18 | verification. Example:
19 |
20 | jwt_decode_opts(:verify_expiration=>true)
21 |
22 | * jwt_session_key has been added, specifying a key in the JWT that
23 | will be used to store session information, instead of storing
24 | session keys in the root of the JWT. Use of this option can avoid
25 | issues with reserved JWT claim names, and will probably be enabled
26 | by default starting in Rodauth 2.
27 |
28 | * jwt_symbolize_deeply? configuration method has been added, for
29 | whether to symbolize nested keys when decoding a JWT session hash.
30 |
31 | = Other Improvements
32 |
33 | * The reset_password feature no longer attempts to render a template
34 | in json-only mode.
35 |
36 | * The jwt_payload method is now memoized by default.
37 |
38 |
--------------------------------------------------------------------------------
/www/pages/index.erb:
--------------------------------------------------------------------------------
1 |
Rodauth: Ruby's Most Advanced Authentication Framework
2 |
3 |
4 |
# cat config.ru
5 | require "roda"
6 |
7 | class RodauthApp < Roda
8 | secret = ENV['SESSION_SECRET']
9 | plugin :sessions, secret: secret
10 |
11 | # If using Rodauth in a non-Roda application
12 | # plugin :middleware
13 |
14 | # JSON API
15 | #plugin :json
16 | #plugin :json_parser
17 |
18 | plugin :rodauth do
19 | enable :login, :logout, :verify_account
20 | enable :webauthn, :otp, :recovery_codes
21 | hmac_secret secret
22 |
23 | # JSON API
24 | #enable :jwt
25 | #jwt_secret secret
26 | #only_json? false
27 | end
28 |
29 | route do |r|
30 | r.rodauth
31 |
32 | rodauth.require_authentication
33 |
34 | # If using Rodauth in a Roda application
35 | # Your app code here
36 | end
37 | end
38 |
39 | # If using Rodauth in a non-Roda application
40 | # use RodauthApp
41 |
42 | # If using Rodauth in a Roda application
43 | run RodauthApp
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
Rodauth is Ruby's most advanced authentication framework. Find out why you should use it.
53 |
--------------------------------------------------------------------------------
/doc/guides/password_confirmation.rdoc:
--------------------------------------------------------------------------------
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
38 |
--------------------------------------------------------------------------------
/lib/rodauth/features/password_pepper.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:password_pepper, :PasswordPepper) do
5 | depends :login_password_requirements_base
6 |
7 | auth_value_method :password_pepper, nil
8 | auth_value_method :previous_password_peppers, [""]
9 | auth_value_method :password_pepper_update?, true
10 |
11 | def password_match?(password)
12 | if (result = super) && @previous_pepper_matched && password_pepper_update?
13 | set_password(password)
14 | end
15 |
16 | result
17 | end
18 |
19 | def password_hash(password)
20 | super(password + password_pepper.to_s)
21 | end
22 |
23 | private
24 |
25 | def password_hash_match?(hash, password)
26 | return super if password_pepper.nil?
27 |
28 | return true if super(hash, password + password_pepper)
29 |
30 | @previous_pepper_matched = previous_password_peppers.any? do |pepper|
31 | super(hash, password + pepper)
32 | end
33 | end
34 |
35 | def database_function_password_match?(name, hash_id, password, salt)
36 | return super if password_pepper.nil?
37 |
38 | return true if super(name, hash_id, password + password_pepper, salt)
39 |
40 | @previous_pepper_matched = previous_password_peppers.any? do |pepper|
41 | super(name, hash_id, password + pepper, salt)
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/doc/guides/login_return.rdoc:
--------------------------------------------------------------------------------
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
38 |
--------------------------------------------------------------------------------
/doc/release_notes/2.21.0.txt:
--------------------------------------------------------------------------------
1 | = Improvements
2 |
3 | * When using the verify_account_grace_period feature, if the grace
4 | period has expired for currently logged in session, require_login
5 | will clear the session and redirect to the login page. This is
6 | implemented by having the unverified_account_session_key store the
7 | time of expiration, as an integer.
8 |
9 | * The previously private require_account method is now public. The
10 | method is used internally by Rodauth to check that not only is the
11 | current session logged in, but also that the account related to the
12 | currently logged in session still exists in the database. The only
13 | reason you would want to call require_account instead of
14 | require_authentication is if you want to handle cases where there
15 | can be logged in sessions for accounts that have been deleted.
16 |
17 | * Rodauth now avoids an unnecessary bcrypt hash calculation when
18 | updating accounts when using the account_password_hash_column
19 | configuration method.
20 |
21 | * When WebAuthn token last use times are displayed, Rodauth now uses a
22 | fixed format of YYYY-MM-DD HH:MM:SS, instead of relying on
23 | Time#to_s. If this presents an problem for your application, please
24 | open an issue and we can add a configuration method to control
25 | the behavior.
26 |
27 | * A typo in the default value of global_logout_label in the
28 | active_sessions feature has been fixed.
29 |
--------------------------------------------------------------------------------
/lib/rodauth/features/webauthn_verify_account.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:webauthn_verify_account, :WebauthnVerifyAccount) do
5 | depends :verify_account, :webauthn
6 |
7 | def verify_account_view
8 | webauthn_setup_view
9 | end
10 |
11 | def create_account_set_password?
12 | false
13 | end
14 |
15 | def verify_account_set_password?
16 | false
17 | end
18 |
19 | def autologin_session(autologin_type)
20 | super
21 | if autologin_type == 'verify_account'
22 | set_session_value(authenticated_by_session_key, ['webauthn'])
23 | remove_session_value(autologin_type_session_key)
24 | webauthn_update_session(@webauthn_credential.id)
25 | end
26 | end
27 |
28 | private
29 |
30 | def before_verify_account
31 | super
32 | if features.include?(:json) && use_json? && !param_or_nil(webauthn_setup_param)
33 | cred = new_webauthn_credential
34 | json_response[webauthn_setup_param] = cred.as_json
35 | json_response[webauthn_setup_challenge_param] = cred.challenge
36 | json_response[webauthn_setup_challenge_hmac_param] = compute_hmac(cred.challenge)
37 | end
38 | @webauthn_credential = webauthn_setup_credential_from_form_submission
39 | add_webauthn_credential(@webauthn_credential)
40 | end
41 |
42 | def webauthn_account_id
43 | super || account_id
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/doc/release_notes/2.1.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A check_csrf configuration method has been added for checking
4 | the CSRF token. This is useful in cases where the CSRF protection
5 | is provided by something other than the Roda route_csrf plugin.
6 |
7 | = Other Improvements
8 |
9 | * When using the http_basic_auth feature, logged_in? now checks for
10 | Basic authentication if the session is not already authenticated
11 | and Basic authentication has not yet been checked. This increases
12 | compatibility for applications that were using the http_basic_auth
13 | feature in Rodauth 1.
14 |
15 | * When creating accounts, the password field now correctly uses the
16 | new-password autocomplete attribute instead of the current-password
17 | autocomplete attribute.
18 |
19 | * When using the jwt feature, Rodauth no longer checks CSRF tokens
20 | in requests to Rodauth routes if the request submitted is a JSON
21 | request, includes a JWT, or Rodauth has been configured in JSON-only
22 | mode.
23 |
24 | * When using the verify_account_grace_period feature, if there is an
25 | unverified account without a password, do not consider the account
26 | open. Attempting to login into the account in such a case now
27 | shows a message letting the user know to verify the account.
28 |
29 | * The json_response_body configuration method is now used consistently
30 | in the jwt feature for all JSON responses. Previously, there were
31 | some cases that did not use it.
32 |
--------------------------------------------------------------------------------
/doc/disallow_common_passwords.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Disallow Common Passwords Feature
2 |
3 | The disallow common passwords feature disallows setting of a password
4 | that matches one of the most common passwords. By default, a list of
5 | 10,000 of the most common passwords is used, but you can supply your
6 | own file. Using a larger list is recommended, but Rodauth doesn't
7 | ship with a larger list to avoid bloating the size of the gem.
8 |
9 | == Auth Value Methods
10 |
11 | most_common_passwords :: An object that responds to +include?+ which will return true if the password given is one of the most common passwords. Useful for custom password sets where they are not stored in files and kept in memory.
12 | most_common_passwords_file :: The path to the file containing the most common passwords, which are not allowed to be used for new passwords. Defaults to a list of 10,000 most common passwords that ships with Rodauth. Can be set to nil/false if you do not want to to load common passwords from a file.
13 | password_is_one_of_the_most_common_message :: The error message fragment to display if the given password matches one of the most common passwords.
14 |
15 | == Auth Methods
16 |
17 | password_one_of_most_common?(password) :: This can be used to override the default check for whether the given password is contained in the most_common_passwords_file. This method may be useful when using very large password databases where you don't want to keep the list of most common passwords in memory.
18 |
--------------------------------------------------------------------------------
/doc/release_notes/2.11.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * An :auth_class rodauth plugin option has been added, allowing a user
4 | to specify a specific Rodauth::Auth subclass to use, instead of
5 | always using a new subclass of Rodauth::Auth. This is designed for
6 | advanced configurations or other frameworks that build on top of
7 | Rodauth, which may want to customize the Rodauth::Auth subclasses to
8 | use.
9 |
10 | * Two additional configuration methods have been added for easier
11 | translatability, fixing issues where English text was hardcoded:
12 |
13 | * same_as_current_login_message (change_login feature)
14 | * contains_null_byte_message (login_password_requirements_base
15 | feature)
16 |
17 | = Other Improvements
18 |
19 | * Loading the rodauth plugin multiple times in the same application
20 | with different blocks now works better. The same context is now
21 | shared between the blocks, so you can load features in one block
22 | and call configuration methods added by the feature in the other
23 | block. Previously, you could only call configuration methods in
24 | the block that added the feature, and enabling a feature in a
25 | block that was already enabled in a previous block did not allow
26 | the use of configuration methods related to the feature.
27 |
28 | * Passing a block when loading the rodauth plugin is now optional.
29 |
30 | * The autocomplete attribute on the reset password form now uses
31 | new-password instead of current-password.
32 |
--------------------------------------------------------------------------------
/doc/release_notes/1.11.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A rodauth.valid_jwt? method has been added, allowing for easy
4 | checking of whether a valid JWT has been submitted. If a valid
5 | JWT has been submitted, the contents of the JWT will be available
6 | in rodauth.session.
7 |
8 | * If using the jwt feature with json_response_custom_error_status?
9 | set to true, and going to a page that requires a login when not
10 | logged in, a 401 error status will now be used instead of a 400
11 | error status. You can customize this status using the new
12 | login_required_error_status configuration method.
13 |
14 | = Improvements
15 |
16 | * Time differences between the database server and the application
17 | server are now handled slightly better in the password_expiration
18 | feature. This mostly affects testing, where sometimes tests would
19 | previously fail if the database server time was ahead of the
20 | application server time when testing whether a password change was
21 | allowed.
22 |
23 | * Some methods that were private by default, but public if overridden,
24 | are now public by default. These include update_session and
25 | only_json? in the base feature, and json_request?, jwt_secret, and
26 | use_jwt? in the jwt feature.
27 |
28 | = Backwards Compatibility
29 |
30 | * The private jwt_payload method in the jwt feature now returns false
31 | instead of redirecting on error. This should not affect the
32 | application unless the method was being called explicitly.
33 |
--------------------------------------------------------------------------------
/lib/rodauth/features/disallow_common_passwords.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:disallow_common_passwords, :DisallowCommonPasswords) do
5 | depends :login_password_requirements_base
6 |
7 | auth_value_method :most_common_passwords_file, File.expand_path('../../../../dict/top-10_000-passwords.txt', __FILE__)
8 | translatable_method :password_is_one_of_the_most_common_message, "is one of the most common passwords"
9 | auth_value_method :most_common_passwords, nil
10 |
11 | auth_methods :password_one_of_most_common?
12 |
13 | def password_meets_requirements?(password)
14 | super && password_not_one_of_the_most_common?(password)
15 | end
16 |
17 | def post_configure
18 | super
19 |
20 | return if most_common_passwords || !most_common_passwords_file
21 |
22 | require 'set'
23 | most_common = Set.new(File.read(most_common_passwords_file).split("\n").each(&:freeze)).freeze
24 | self.class.send(:define_method, :most_common_passwords){most_common}
25 | end
26 |
27 | def password_one_of_most_common?(password)
28 | most_common_passwords.include?(password)
29 | end
30 |
31 | private
32 |
33 | def password_not_one_of_the_most_common?(password)
34 | return true unless password_one_of_most_common?(password)
35 | set_password_requirement_error_message(:password_is_one_of_the_most_common, password_is_one_of_the_most_common_message)
36 | false
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/www/layout.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Rodauth: <%= title == 'index' ? "Ruby's Most Advanced Authentication Framework" : title.capitalize %>
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
27 |
28 | <%= content %>
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/doc/release_notes/2.7.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * An auto_remove_recovery_codes? configuration method has been added
4 | to the recovery_codes feature. This will automatically remove
5 | recovery codes when the last multifactor authentication type other
6 | than the recovery codes has been removed.
7 |
8 | * The jwt_access_expired_status and expired_jwt_access_token_message
9 | configuration methods have been added to the jwt_refresh feature,
10 | for supporting custom statuses and messages for expired tokens.
11 |
12 | = Other Improvements
13 |
14 | * Rodauth will no longer attempt to require a feature that has
15 | already been required. Related to this is you can now use a
16 | a custom Rodauth feature without a rodauth/features/*.rb file
17 | in the Ruby library path, as long as you load the feature
18 | manually.
19 |
20 | * Rodauth now avoids method redefinition warnings in verbose
21 | warning mode. As Ruby 3 is dropping uninitialized instance
22 | variable warnings, Rodauth will be verbose warning free in
23 | Ruby 3.
24 |
25 | = Backwards Compatibility
26 |
27 | * The default remember cookie path is now set to '/'. This fixes
28 | usage in the case where rodauth is loaded under a subpath of the
29 | application (which is not the default behavior). Unfortunately,
30 | this change can negatively affect cases where multiple rodauth
31 | configurations are used in separate paths on the same domain.
32 | In these cases, you should now use remember_cookie_options and
33 | include a :path option.
34 |
--------------------------------------------------------------------------------
/doc/release_notes/1.23.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * When the email_auth feature is used, the link to request email
4 | authentication is now displayed if the user inputs an incorrect
5 | password. Previously, it was only shown if the user had not
6 | yet entered a password.
7 |
8 | * A send_email configuration method has been added, which can be
9 | overridden to customize email delivery (such as logging such
10 | email). The configuration method block accepts a Mail::Message
11 | argument.
12 |
13 | * All rodauth.*_route methods that return the name of the route
14 | segment now have rodauth.*_path and rodauth.*_url equivalents,
15 | which return the path and URL for the related routes, respectively.
16 | The rodauth.*_path methods are useful when constructing links to
17 | the related Rodauth pages on the same site, and the rodauth.*_url
18 | methods are useful for constructing link to the Rodauth pages from
19 | other sites or in email.
20 |
21 | = Other Improvements
22 |
23 | * Specs have been removed from the gem file, reducing gem size by
24 | over 20%.
25 |
26 | * rodauth.authenticated? now returns true on the OTP setup page
27 | when using the otp feature. Previously, this method returned
28 | false on the OTP setup page. However, as the user has not yet
29 | setup OTP when viewing this page, they should be considered
30 | fully authenticated, as they would be if they viewed any other
31 | page before setting up OTP. This change probably only affects
32 | cases where the layout uses rodauth.authenticated?.
33 |
--------------------------------------------------------------------------------
/www/pages/development.erb:
--------------------------------------------------------------------------------
1 |
Development
2 |
3 |
Rodauth, while a mature framework, is still under active development. You can join in on the discussions, ask questions, suggest features, and discuss Rodauth in general on GitHub Discussions or by joining the rodauth Google Group.
4 |
5 |
Reporting Bugs
6 |
7 |
To report a bug in Rodauth, use GitHub Issues. If you aren't sure if something is a bug, post a question on GitHub Discussions or the Google Group. Note that GitHub Issues should not be used to ask questions about how to use Rodauth; use GitHub Discussions or the Google Group for that.
The easiest way to contribute is to use Git, post the changes to a public repository, and send a pull request, either via GitHub, or the Google Group. Posting patches to the bug tracker or the Google Group works fine as well.
16 |
17 |
License
18 |
19 |
Rodauth is distributed under the MIT License. Patches are assumed to be submitted under the same license as Rodauth.
20 |
21 |
--------------------------------------------------------------------------------
/doc/release_notes/2.36.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * An otp_unlock feature has been added, allowing a user to unlock
4 | TOTP authentication with 3 consecutive successful TOTP
5 | authentications. Previously, once TOTP authentication was locked
6 | out, there was no way for the user to unlock it.
7 |
8 | Any unsuccessful TOTP authentication during the unlock process
9 | prevents unlocks attempts for a configurable amount of time (15
10 | minutes by default). By default, this limits brute force attempts
11 | to unlock TOTP authentication to less than 10^2 per day, with the
12 | odds of a successful unlock in each attempt being 1 in 10^18.
13 |
14 | * An otp_lockout_email feature has been added for emailing the user
15 | when their TOTP authentication has been locked out or unlocked, and
16 | when there has been a failed unlock attempt.
17 |
18 | * An otp_modify_email feature has been added for emailing the user
19 | when TOTP authentication has been setup or disabled for their
20 | account.
21 |
22 | * A webauthn_modify_email feature has been added for emailing the
23 | user when a WebAuthn authenticator has been added or removed from
24 | their account.
25 |
26 | * An account_from_id configuration method has been added for loading
27 | the account with the given account id.
28 |
29 | * A strftime_format configuration method has been added for
30 | configuring how Time values are formatted for display to the user.
31 |
32 | = Improvements
33 |
34 | * The internal_request feature now works with Roda's path_rewriter
35 | plugin.
36 |
--------------------------------------------------------------------------------
/doc/webauthn_modify_email.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for WebAuthn Modify Email Feature
2 |
3 | The webauthn_modify_email feature emails users when a WebAuthn authenticator is added to or removed from their account.
4 |
5 | The webauthn_modify_email feature depends on the webauthn and email_base features.
6 |
7 | == Auth Value Methods
8 |
9 | webauthn_authenticator_added_email_body :: Body to use for the email notifying user that a WebAuthn authenticator has been added to their account.
10 | webauthn_authenticator_added_email_subject :: Subject to use for the email notifying user that a WebAuthn authenticator has been added to their account.
11 | webauthn_authenticator_removed_email_body :: Body to use for the email notifying user that a WebAuthn authenticator has been removed from their account.
12 | webauthn_authenticator_removed_email_subject :: Subject to use for the email notifying user that a WebAuthn authenticator has been removed from their account.
13 |
14 | == Auth Methods
15 |
16 | create_webauthn_authenticator_added_email :: A Mail::Message for the email notifying user that a WebAuthn authenticator has been added to their account.
17 | create_webauthn_authenticator_removed_email :: A Mail::Message for the email notifying user that a WebAuthn authenticator has been removed from their account.
18 | send_webauthn_authenticator_added_email :: Send the email notifying user that a WebAuthn authenticator has been added to their account.
19 | send_webauthn_authenticator_removed_email :: Send the email notifying user that a WebAuthn authenticator has been removed from their account.
20 |
--------------------------------------------------------------------------------
/doc/release_notes/2.27.0.txt:
--------------------------------------------------------------------------------
1 | = Improvements
2 |
3 | * Token ids submitting in requests are now converted to integers if
4 | the configuration uses an integer primary key for the accounts
5 | table. If the configuration uses a non-integer primary key for
6 | the accounts table, the convert_token_id configuration method can
7 | be used, which should return the token id converted to the
8 | appropriate type, or nil if the token id is not valid for the type.
9 |
10 | This revised handling avoids raising a database error when an
11 | invalid token is submitted.
12 |
13 | * The button template can now be overridden in the same way that
14 | other Rodauth templates can be overridden.
15 |
16 | * When using the Bootstrap CSS framework, the text field in the
17 | Webauthn setup and auth forms is automatically hidden. The text
18 | field already had a rodauth-hidden class to make it easy to hide
19 | when using other CSS frameworks.
20 |
21 | * The email_from and email_to methods are now public instead of
22 | private.
23 |
24 | * A nicer error is raised if the Sequel Database object is missing.
25 |
26 | * A regression in the TOTP QR output that resulted in the QR codes
27 | being solid black squares has been fixed (this was fixed in
28 | Rodauth 2.26.1).
29 |
30 | = Backwards Compatibility
31 |
32 | * The webauth_credentials_for_get method in the webauthn feature has
33 | been renamed to webauthn_credentials_for_get for consistency with
34 | other methods. The webauth_credentials_for_get method will still
35 | work until Rodauth 3, but will issue deprecation warnings.
36 |
--------------------------------------------------------------------------------
/doc/guides/paths.rdoc:
--------------------------------------------------------------------------------
1 | = Change route path
2 |
3 | You can change the URL path of any Rodauth route by overriding the
4 | corresponding *_route 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 redirect when login is required to "/signin"
13 | require_login_redirect { login_path }
14 |
15 | # Change create account route to "/register"
16 | create_account_route "register"
17 |
18 | # Change password reset request route to "/reset-password/request"
19 | reset_password_request_route "reset-password/request"
20 | end
21 |
22 | If you want to add a prefix to all Rodauth routes, you should use the +prefix+
23 | setting:
24 |
25 | plugin :rodauth do
26 | enable :login, :logout
27 |
28 | # Use /auth prefix to each Rodauth route
29 | prefix "/auth"
30 | end
31 |
32 | route do |r|
33 | r.on "auth" do
34 | # Serve Rodauth routes under the /auth branch of the routing tree
35 | r.rodauth
36 | end
37 |
38 | # ...
39 | end
40 |
41 | There are cases where you may want to disable certain routes. For example, you
42 | may want to enable the create_account feature to allow creating admins, but
43 | only make it possible programmatically via internal requests. In this case,
44 | you should set the corresponding *_route method to +nil+:
45 |
46 | plugin :rodauth, name: :admin do
47 | enable :create_account
48 |
49 | # disable the /create-account route
50 | create_account_route nil
51 | end
52 |
--------------------------------------------------------------------------------
/lib/rodauth/features/password_grace_period.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:password_grace_period, :PasswordGracePeriod) do
5 | auth_value_method :password_grace_period, 300
6 | session_key :last_password_entry_session_key, :last_password_entry
7 |
8 | auth_methods :password_recently_entered?
9 |
10 | def modifications_require_password?
11 | return false unless super
12 | !password_recently_entered?
13 | end
14 |
15 | def password_match?(_)
16 | if v = super
17 | @last_password_entry = set_last_password_entry
18 | end
19 | v
20 | end
21 |
22 | def password_recently_entered?
23 | return false unless last_password_entry = session[last_password_entry_session_key]
24 | last_password_entry + password_grace_period > Time.now.to_i
25 | end
26 |
27 | def update_session
28 | super
29 | set_session_value(last_password_entry_session_key, @last_password_entry) if defined?(@last_password_entry)
30 | end
31 |
32 | private
33 |
34 | def after_create_account
35 | super if defined?(super)
36 | @last_password_entry = Time.now.to_i
37 | end
38 |
39 | def after_reset_password
40 | super if defined?(super)
41 | @last_password_entry = Time.now.to_i
42 | end
43 |
44 | def set_last_password_entry
45 | set_session_value(last_password_entry_session_key, Time.now.to_i)
46 | end
47 |
48 | def require_password_authentication?
49 | return true if defined?(super) && super
50 | !password_recently_entered?
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/doc/change_login.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Change Login Feature
2 |
3 | The change login feature implements a form that a user can use to
4 | change their login.
5 |
6 | == Auth Value Methods
7 |
8 | change_login_additional_form_tags :: HTML fragment containing additional form tags to use on the change login form.
9 | change_login_button :: The text to use for the change login button.
10 | change_login_error_flash :: The flash error to show for an unsuccessful login change.
11 | change_login_notice_flash :: The flash notice to show after a successful login change.
12 | change_login_page_title :: The page title to use on the change login form.
13 | change_login_redirect :: Where to redirect after a successful login change.
14 | change_login_requires_password? :: Whether a password is required when changing logins.
15 | change_login_route :: The route to the change login action. Defaults to +change-login+.
16 | same_as_current_login_message :: The error message to display if using the same value as the current login when changing the login.
17 |
18 | == Auth Methods
19 |
20 | after_change_login :: Run arbitrary code after successful login change.
21 | before_change_login :: Run arbitrary code before changing a login.
22 | before_change_login_route :: Run arbitrary code before handling a change login route.
23 | change_login(login) :: Change the users login to the given login, or return nil/false if the login cannot be changed to the given login.
24 | change_login_response :: Return a response after a successful login change. By default, redirects to +change_login_redirect+.
25 | change_login_view :: The HTML to use for the change login form.
26 |
--------------------------------------------------------------------------------
/doc/release_notes/1.13.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A cache_templates configuration method has been added, which can be
4 | set to false to disable the default caching of templates. The main
5 | time you would want to use this is if you were overriding Rodauth's
6 | templates with your own templates and modifying such templates in
7 | development mode. If that is the case, you may want to use
8 | something like:
9 |
10 | cache_templates(ENV['RACK_ENV'] != 'development')
11 |
12 | * An invalid_previous_password_message configuration method has been
13 | added to the change_password feature, which overrides the default
14 | invalid_password_message configuration method if the incorrect
15 | previous password is used when changing the password. This is
16 | designed for use when invalid_password_message has been overridden
17 | and the message doesn't make sense in the change password case.
18 |
19 | * A json_response_body(hash) configuration method has been added to
20 | the jwt feature, allowing for custom formatting of the JSON
21 | response body. This is called with the hash to use in the
22 | response, and should return a JSON-formatted string. Example:
23 |
24 | json_response_body do |hash|
25 | super('status'=>response.status, 'detail'=>hash)
26 | end
27 |
28 | = Other Improvements
29 |
30 | * In the jwt feature, if json_response_custom_error_status? is set to
31 | true, custom error statuses will be used if only_json? is set to
32 | true, even if the request is not in JSON format. Previously,
33 | custom error statuses were only used if the request was in JSON
34 | format.
35 |
--------------------------------------------------------------------------------
/javascript/webauthn_autofill.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var pack = function(v) { return btoa(String.fromCharCode.apply(null, new Uint8Array(v))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); };
3 | var unpack = function(v) { return Uint8Array.from(atob(v.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); };
4 | var element = document.getElementById('webauthn-login-form');
5 |
6 | if (!window.PublicKeyCredential || !PublicKeyCredential.isConditionalMediationAvailable) return;
7 |
8 | PublicKeyCredential.isConditionalMediationAvailable().then(function(available) {
9 | if (!available) return;
10 |
11 | var opts = JSON.parse(element.getAttribute("data-credential-options"));
12 | opts.challenge = unpack(opts.challenge);
13 | opts.allowCredentials.forEach(function(cred) { cred.id = unpack(cred.id); });
14 |
15 | navigator.credentials.get({mediation: "conditional", publicKey: opts}).then(function(cred) {
16 | var rawId = pack(cred.rawId);
17 | var authValue = {
18 | type: cred.type,
19 | id: rawId,
20 | rawId: rawId,
21 | response: {
22 | authenticatorData: pack(cred.response.authenticatorData),
23 | clientDataJSON: pack(cred.response.clientDataJSON),
24 | signature: pack(cred.response.signature)
25 | }
26 | };
27 |
28 | if (cred.response.userHandle) {
29 | authValue.response.userHandle = pack(cred.response.userHandle);
30 | }
31 |
32 | document.getElementById('webauthn-auth').value = JSON.stringify(authValue);
33 |
34 | element.submit();
35 | });
36 | });
37 | })();
38 |
39 |
--------------------------------------------------------------------------------
/doc/release_notes/2.6.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * An around_rodauth configuration method has been added, which is
4 | called around all Rodauth actions. This configuration method
5 | is passed a block, and is useful for cases where you want to wrap
6 | Rodauth's handling of the request.
7 |
8 | For example, if you had a method named time_block in your Roda scope
9 | that timed block execution and added a response header, you could
10 | time Rodauth actions using something like:
11 |
12 | around_rodauth do |&block|
13 | scope.time_block('Rodauth') do
14 | super(&block)
15 | end
16 | end
17 |
18 | * The allow_refresh_with_expired_jwt_access_token? configuration has
19 | been added to the jwt_refresh feature, allowing refreshing with an
20 | expired but otherwise valid access token. When using this method,
21 | it is required to have an hmac_secret specified, so that Rodauth
22 | can make sure the access token matches the refresh token.
23 |
24 | = Other Improvements
25 |
26 | * The javascript for setting up a WebAuthn token has been fixed to
27 | allow it to work correctly if there is already an existing
28 | WebAuthn token for the account.
29 |
30 | * The rodauth.setup_account_verification method has been promoted to
31 | public API. You can use this method for automatically sending
32 | account verification emails when automatically creating accounts.
33 |
34 | * Rodauth no longer loads the same feature multiple times into a
35 | single configuration. This didn't cause any problems before, but
36 | could result in duplicate entries when looking at the loaded
37 | features.
38 |
--------------------------------------------------------------------------------
/doc/release_notes/2.22.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * Rodauth now ignores parameters containing ASCII NUL bytes ("\0") by
4 | default. You can customize this behavior using the
5 | null_byte_parameter_value configuration method.
6 |
7 | * A reset_password_notify feature has been added for emailing users
8 | after successful password resets.
9 |
10 | * External features can now use the email method inside their
11 | feature definitions to DRY up the creation of email configuration
12 | methods. The email method will setup the following configuration
13 | methods for the feature:
14 |
15 | * ${name}_email_subject
16 | * ${name}_email_body
17 | * create_${name}_email
18 | * send_${name}_email
19 |
20 | = Other Improvements
21 |
22 | * The active_sessions feature now correctly handles logouts for
23 | sessions that were created before the active_sessions feature was
24 | added to the Rodauth configuration.
25 |
26 | * The change_password_notify feature now works correctly when using
27 | template precompilation.
28 |
29 | * The update_sms method now updates the in-memory sms hash instead of
30 | the in-memory account hash. This only has an effect if you are
31 | using the sms_codes feature and customizing Rodauth to access one
32 | of these hashes after a call to update_sms.
33 |
34 | = Backwards Compatibility
35 |
36 | * If your application requires the ability to submit values containing
37 | ASCII NUL bytes ("\0") as Rodauth parameters, you should use the
38 | new null_byte_parameter_value configuration method to pass the
39 | value through unchanged:
40 |
41 | null_byte_parameter_value do |_, v|
42 | v
43 | end
44 |
--------------------------------------------------------------------------------
/doc/release_notes/2.3.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * Configuration methods have been added for easier validation of
4 | logins when logins must be valid email addresses (the default):
5 |
6 | * login_valid_email?(login) can be used for full control of
7 | determining whether the login is valid.
8 |
9 | * login_email_regexp can be used to set the regexp used in the
10 | default login_valid_email? check.
11 |
12 | * login_not_valid_email_message can be used to set the field
13 | error message if the login is not a valid email. Previously, this
14 | value was hardcoded and not translatable.
15 |
16 | * The {create,drop}_database_authentication_functions now work
17 | correctly with uuid keys on PostgreSQL. All other parts of
18 | Rodauth already worked correctly with uuid keys.
19 |
20 | = Other Improvements
21 |
22 | * The before_jwt_refresh_route hook is now called before the route
23 | is taken. Previously, the configuration method had no effect.
24 |
25 | * rodauth.login can now be used by external code to login the current
26 | account (the account that rodauth.account returns). This should be
27 | passed the authentication type string used to login, such as
28 | password.
29 |
30 | * The jwt_refresh route now returns an error for requests where a
31 | valid access token for a logged in session is not provided. You
32 | can use the jwt_refresh_without_access_token_message and
33 | jwt_refresh_without_access_token_status configuration methods
34 | to configure the error response.
35 |
36 | * The new refresh token is now available to the after_refresh_token
37 | hook by looking in json_response[jwt_refresh_token_key].
38 |
--------------------------------------------------------------------------------
/doc/session_expiration.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Session Expiration Feature
2 |
3 | The session expiration feature allows setting an inactivity timeout and a max
4 | lifetime for sessions. When this feature is used, you should use
5 | +rodauth.check_session_expiration+ at the top (or other appropriate place)
6 | in your routing tree.
7 |
8 | route do |r|
9 | rodauth.check_session_expiration
10 | r.rodauth
11 |
12 | # ...
13 | end
14 |
15 | When checking session expiration, if the last activity was more than the
16 | inactivity timeout, or the session was created more the maximum lifetime
17 | ago, the session is cleared, and the user is redirected to the login page.
18 |
19 | == Auth Value Methods
20 |
21 | max_session_lifetime :: The maximum number of seconds since session creation that sessions will be valid for, regardless of session activity. 86400 by default (1 day).
22 | session_created_session_key :: The session key storing the session creation timestamp.
23 | session_expiration_default :: Whether to expire sessions that don't have the created at or last activity at timestamps set, true by default.
24 | session_expiration_error_flash :: The flash error to show if a session expires.
25 | session_expiration_error_status :: The error status to use when a JSON request is made and the session has expired, 401 by default.
26 | session_expiration_redirect :: Where to redirect if a session expires.
27 | session_inactivity_timeout :: The maximum number of seconds allowed since the last activity before the session will be considered invalid. 1800 by default (30 minutes).
28 | session_last_activity_session_key :: The session key storing the last session activity timestamp.
29 |
--------------------------------------------------------------------------------
/doc/release_notes/2.34.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * A rodauth.current_route method has been added for returning the route
4 | name symbol (if rodauth is currently handling the route). This makes it
5 | simpler to write code that extends Rodauth and works with
6 | applications that use override the default route names.
7 |
8 | * A remove_all_active_sessions_except_for method has been added to the
9 | active_sessions feature, which removes all active sessions for the
10 | current account, except for the session id given.
11 |
12 | * A remove_all_active_sessions_except_current method has been added to
13 | the active_sessions feature, which removes all active sessions for
14 | the current account, except for the current session.
15 |
16 | = Improvements
17 |
18 | * Rodauth now supports overriding webauthn_rp_id in the webauthn
19 | feature.
20 |
21 | * When using the login feature, Rodauth now defaults
22 | require_login_redirect to use the path to the login route, instead
23 | of /login.
24 |
25 | * When setting up multifactor authentication, Rodauth now handles the
26 | case where account has been deleted, instead of raising an exception.
27 |
28 | * When a database connection is not available during startup, Rodauth
29 | now handles that case instead of raising an exception. Note that in
30 | this case, Rodauth cannot automatically setup a conversion of token
31 | ids to integer, since it cannot determine whether the underlying
32 | database column uses an integer type.
33 |
34 | * When using WebAuthn 3+, Rodauth no longer defines singleton methods
35 | to work around limitations in WebAuthn. Instead, it uses public
36 | APIs that were added in WebAuthn 3.
37 |
--------------------------------------------------------------------------------
/doc/close_account.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Close Account Feature
2 |
3 | The close account feature allows users to close their accounts.
4 |
5 | == Auth Value Methods
6 |
7 | account_closed_status_value :: The integer representing closed accounts.
8 | close_account_additional_form_tags :: HTML fragment containing additional form tags to use on the close account form.
9 | close_account_button :: The text to use for the close account button.
10 | close_account_error_flash :: The flash error to show if there is an error closing the account.
11 | close_account_notice_flash :: The flash notice to show after closing the account.
12 | close_account_page_title :: The page title to use on the close account form.
13 | close_account_redirect :: Where to redirect after closing the account.
14 | close_account_requires_password? :: Whether a password is required when closing accounts.
15 | close_account_route :: The route to the close account action. Defaults to +close-account+.
16 | delete_account_on_close? :: Whether to delete the account when closing it, default value is to use +skip_status_checks?+.
17 |
18 | == Auth Methods
19 |
20 | after_close_account :: Run arbitrary code after closing the account.
21 | before_close_account :: Run arbitrary code before closing an account.
22 | before_close_account_route :: Run arbitrary code before handling a close account route.
23 | close_account :: Close the account, by default setting the account status to closed.
24 | close_account_response :: Return a response after successfully closing the account . By default, redirects to +close_account_redirect+.
25 | close_account_view :: The HTML to use for the close account form.
26 | delete_account :: If +delete_account_on_close?+ is true, delete the account when closing it.
27 |
--------------------------------------------------------------------------------
/doc/change_password.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Change Password Feature
2 |
3 | The change password feature implements a form that a user can use to
4 | change their password.
5 |
6 | == Auth Value Methods
7 |
8 | change_password_additional_form_tags :: HTML fragment containing additional form tags to use on the change password form.
9 | change_password_button :: The text to use for the change password button.
10 | change_password_error_flash :: The flash error to show for an unsuccessful password change.
11 | change_password_notice_flash :: The flash notice to show after a successful password change.
12 | change_password_page_title :: The page title to use on the change password form.
13 | change_password_redirect :: Where to redirect after a successful password change.
14 | change_password_requires_password? :: Whether a password is required when changing passwords.
15 | change_password_route :: The route to the change password action. Defaults to +change-password+.
16 | invalid_previous_password_message :: The message to use when the previous password was incorrect. Defaults to +invalid_password_message+.
17 | new_password_label :: The label to use for the new password.
18 | new_password_param :: The parameter name to use for new passwords.
19 |
20 | == Auth Methods
21 |
22 | after_change_password :: Run arbitrary code after successful password change.
23 | before_change_password :: Run arbitrary code before changing the password for an account.
24 | before_change_password_route :: Run arbitrary code before handling a change password route.
25 | change_password_response :: Return a response after a successful password change. By default, redirects to +change_password_redirect+.
26 | change_password_view :: The HTML to use for the change password form.
27 |
--------------------------------------------------------------------------------
/lib/rodauth/features/jwt_cors.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:jwt_cors, :JwtCors) do
5 | depends :jwt
6 |
7 | auth_value_method :jwt_cors_allow_origin, false
8 | auth_value_method :jwt_cors_allow_methods, 'POST'
9 | auth_value_method :jwt_cors_allow_headers, 'Content-Type, Authorization, Accept'
10 | auth_value_method :jwt_cors_expose_headers, 'Authorization'
11 | auth_value_method :jwt_cors_max_age, 86400
12 |
13 | auth_methods(:jwt_cors_allow?)
14 |
15 | def jwt_cors_allow?
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 | end
30 | end
31 |
32 | private
33 |
34 | def before_rodauth
35 | if jwt_cors_allow?
36 | set_response_header('access-control-allow-origin', request.env['HTTP_ORIGIN'])
37 |
38 | # Handle CORS preflight request
39 | if request.request_method == 'OPTIONS'
40 | set_response_header('access-control-allow-methods', jwt_cors_allow_methods)
41 | set_response_header('access-control-allow-headers', jwt_cors_allow_headers)
42 | set_response_header('access-control-max-age', jwt_cors_max_age.to_s)
43 | response.status = 204
44 | return_response
45 | end
46 |
47 | set_response_header('access-control-expose-headers', jwt_cors_expose_headers)
48 | end
49 |
50 | super
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/doc/release_notes/2.2.0.txt:
--------------------------------------------------------------------------------
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.
40 |
--------------------------------------------------------------------------------
/doc/guides/password_requirements.rdoc:
--------------------------------------------------------------------------------
1 | = Customize password requirements
2 |
3 | By default, Rodauth requires passwords to have at least 6 characters. You can
4 | modify the minimum and maximum 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 |
12 | # Don't allow passwords to be too long, to prevent long password DoS attacks
13 | password_maximum_length 64
14 | end
15 |
16 | You can use the {disallow common passwords feature}[rdoc-ref:doc/disallow_common_passwords.rdoc]
17 | to prevent the usage of common passwords (the most common 10,000 by default).
18 |
19 | You can use additional complexity checks on passwords via the {password
20 | complexity feature}[rdoc-ref:doc/password_complexity.rdoc], though most of
21 | those complexity checks are no longer considered modern security best
22 | practices and are likely to decrease overall security.
23 |
24 | If you want complete control over whether passwords meet requirements, you
25 | can use the password_meets_requirements? configuration method.
26 |
27 | plugin :rodauth do
28 | enable :login, :logout, :create_account
29 |
30 | password_meets_requirements? do |password|
31 | super(password) && password_complex_enough?(password)
32 | end
33 |
34 | auth_class_eval do
35 | # If password doesn't pass custom validation, add field error with error
36 | # reason, and return false.
37 | def password_complex_enough?(password)
38 | return true if password.match?(/\d/) && password.match?(/[^a-zA-Z\d]/)
39 | set_password_requirement_error_message(:password_simple, "requires one number and one special character")
40 | false
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/spec/otp_modify_email_spec.rb:
--------------------------------------------------------------------------------
1 | require_relative 'spec_helper'
2 |
3 | require 'rotp'
4 |
5 | describe 'Rodauth otp_lockout_email feature' do
6 | secret_length = (ROTP::Base32.respond_to?(:random_base32) ? ROTP::Base32.random_base32 : ROTP::Base32.random).length
7 |
8 | it "should email when otp authentication is locked out, unlocked, or has a failed unlock attempt" do
9 | rodauth do
10 | enable :login, :logout, :otp_modify_email
11 | hmac_secret '123'
12 | end
13 | roda do |r|
14 | r.rodauth
15 |
16 | r.redirect '/login' unless rodauth.logged_in?
17 |
18 | if rodauth.two_factor_authentication_setup?
19 | r.redirect '/otp-auth' unless rodauth.authenticated?
20 | view :content=>"With 2FA"
21 | else
22 | view :content=>"Without 2FA"
23 | end
24 | end
25 |
26 | login
27 | visit '/otp-setup'
28 | secret = page.html.match(/Secret: ([a-z2-7]{#{secret_length}})/)[1]
29 | totp = ROTP::TOTP.new(secret)
30 | fill_in 'Password', :with=>'0123456789'
31 | fill_in 'Authentication Code', :with=>totp.now
32 | click_button 'Setup TOTP Authentication'
33 | email = email_sent
34 | email.subject.must_equal "TOTP Authentication Setup"
35 | email.body.to_s.must_equal <'0123456789'
42 | click_button 'Disable TOTP Authentication'
43 | email = email_sent
44 | email.subject.must_equal "TOTP Authentication Disabled"
45 | email.body.to_s.must_equal <'foo@example.com', :password=>'0123456789').must_equal true
21 | rodauth.valid_login_and_password?(:login=>'foo@example.com', :password=>'01234567').must_equal false
22 | rodauth.valid_login_and_password?(:login=>'foo3@example.com', :password=>'0123456789').must_equal false
23 |
24 | rodauth.create_account(:login=>'foo3@example.com', :password=>'sdkjnlsalkklsda').must_be_nil
25 |
26 | rodauth.valid_login_and_password?(:login=>'foo@example.com', :password=>'0123456789').must_equal true
27 | rodauth.valid_login_and_password?(:login=>'foo@example.com', :password=>'01234567').must_equal false
28 | rodauth.valid_login_and_password?(:login=>'foo3@example.com', :password=>'sdkjnlsalkklsda').must_equal true
29 |
30 | rodauth.change_password(:account_login=>'foo@example.com', :password=>'01234567').must_be_nil
31 |
32 | rodauth.valid_login_and_password?(:login=>'foo@example.com', :password=>'0123456789').must_equal false
33 | rodauth.valid_login_and_password?(:login=>'foo@example.com', :password=>'01234567').must_equal true
34 | rodauth.valid_login_and_password?(:login=>'foo3@example.com', :password=>'sdkjnlsalkklsda').must_equal true
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/doc/guides/alternative_login.rdoc:
--------------------------------------------------------------------------------
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 column 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
47 |
--------------------------------------------------------------------------------
/javascript/webauthn_setup.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var pack = function(v) { return btoa(String.fromCharCode.apply(null, new Uint8Array(v))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); };
3 | var unpack = function(v) { return Uint8Array.from(atob(v.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); };
4 | var element = document.getElementById('webauthn-setup-form');
5 | var f = function(e) {
6 | //console.log(e);
7 | e.preventDefault();
8 | if (navigator.credentials) {
9 | var opts = JSON.parse(element.getAttribute("data-credential-options"));
10 | opts.challenge = unpack(opts.challenge);
11 | opts.user.id = unpack(opts.user.id);
12 | opts.excludeCredentials.forEach(function(cred) { cred.id = unpack(cred.id); });
13 | //console.log(opts);
14 | navigator.credentials.create({publicKey: opts}).
15 | then(function(cred){
16 | //console.log(cred);
17 | //window.cred = cred
18 |
19 | var rawId = pack(cred.rawId);
20 | document.getElementById('webauthn-setup').value = JSON.stringify({
21 | type: cred.type,
22 | id: rawId,
23 | rawId: rawId,
24 | response: {
25 | attestationObject: pack(cred.response.attestationObject),
26 | clientDataJSON: pack(cred.response.clientDataJSON)
27 | }
28 | });
29 | element.removeEventListener("submit", f);
30 | element.submit();
31 | }).
32 | catch(function(e){document.getElementById('webauthn-setup-button').innerHTML = "Error creating public key in authenticator: " + e});
33 | } else {
34 | document.getElementById('webauthn-setup-button').innerHTML = "WebAuthn not supported by browser, or browser has disabled it on this page";
35 | }
36 | };
37 | element.addEventListener("submit", f);
38 | })();
39 |
--------------------------------------------------------------------------------
/doc/disallow_password_reuse.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Disallow Password Reuse Feature
2 |
3 | The disallow password reuse feature disallows setting of a password
4 | that matches a number of previous passwords (6 by default).
5 |
6 | On databases where Rodauth supports the use of database authentication
7 | functions, Rodauth also supports the use of database functions for checking
8 | previous passwords, so previous password hashes enjoy the same database
9 | security as current password hashes.
10 |
11 | It is not recommended to use this feature unless you have a policy that
12 | requires it. This will significantly slow down setting a new password
13 | due to the need to check all of the previous stored passwords. Additionally,
14 | storing previous passwords means that if attackers can get access to the
15 | the database, they can get the previous stored passwords in addition to the
16 | current password.
17 |
18 | == Auth Value Methods
19 |
20 | password_same_as_previous_password_message :: The error message fragment to display if the given password is the same as a previous password.
21 | previous_password_account_id_column :: The column in the +previous_password_hash_table+ that stores the account id.
22 | previous_password_hash_column :: The column in the +previous_password_hash_table+ that stores the password hash.
23 | previous_password_hash_table :: The table storing previous password hashes.
24 | previous_password_id_column :: The column in the +previous_password_hash_table+ that stores the autoincrementing primary key.
25 | previous_passwords_to_check :: The number of previous password hashes to store and check.
26 |
27 | == Auth Methods
28 |
29 | add_previous_password_hash(hash) :: Add the given hash to the list of previous hashes for the current account.
30 | password_doesnt_match_previous_password?(password) :: Whether the password given matches any of the previous passwords.
31 |
--------------------------------------------------------------------------------
/doc/release_notes/2.31.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * The internal_request feature now supports WebAuthn, using
4 | the following methods:
5 |
6 | * With the webauthn feature:
7 | * webauthn_setup_params
8 | * webauthn_setup
9 | * webauthn_auth_params
10 | * webauthn_auth
11 | * webauthn_remove
12 |
13 | * With the webauthn_login feature:
14 | * webauthn_login_params
15 | * webauthn_login
16 |
17 | * A webauthn_login_user_verification_additional_factor? configuration
18 | method has been added to the webauthn_login feature. By default,
19 | this method returns false. If you configure the method to return
20 | true, and the WebAuthn credential provided specifies that it
21 | verified the user, then this will treat the user verification as
22 | a second factor, so the user will be considered multifactor
23 | authenticated after successful login. You should only set this
24 | method to true if you consider the WebAuthn user verification
25 | strong enough to be a independent factor.
26 |
27 | * A json_response_error? configuration method has been added to the
28 | json feature. This should return whether the current response
29 | should be treated as an error by the json feature. By default,
30 | it is true if json_response_error_key is set in the response,
31 | since that is the default place that Rodauth stores errors when
32 | using the json feature.
33 |
34 | * A webauthn_invalid_webauthn_id_message configuration method has
35 | been added for customizing the error message used for invalid
36 | WebAuthn IDs.
37 |
38 | = Other Improvements
39 |
40 | * The argon2 feature now supports setting the Argon2 p_cost if
41 | argon2 2.1+ is installed.
42 |
43 | * An :invalid_webauthn_id error reason is now used for invalid
44 | WebAuthn IDs.
45 |
46 | * The clear_session method now works as expected for internal
47 | requests.
48 |
--------------------------------------------------------------------------------
/lib/rodauth/features/session_expiration.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:session_expiration, :SessionExpiration) do
5 | error_flash "This session has expired, please login again"
6 | redirect{require_login_redirect}
7 |
8 | auth_value_method :max_session_lifetime, 86400
9 | session_key :session_created_session_key, :session_created_at
10 | auth_value_method :session_expiration_error_status, 401
11 | auth_value_method :session_expiration_default, true
12 | auth_value_method :session_inactivity_timeout, 1800
13 | session_key :session_last_activity_session_key, :last_session_activity_at
14 |
15 | def check_session_expiration
16 | return unless logged_in?
17 |
18 | unless session.has_key?(session_last_activity_session_key) && session.has_key?(session_created_session_key)
19 | if session_expiration_default
20 | expire_session
21 | end
22 |
23 | return
24 | end
25 |
26 | time = Time.now.to_i
27 |
28 | if session[session_last_activity_session_key] + session_inactivity_timeout < time
29 | expire_session
30 | end
31 | set_session_value(session_last_activity_session_key, time)
32 |
33 | if session[session_created_session_key] + max_session_lifetime < time
34 | expire_session
35 | end
36 | end
37 |
38 | def expire_session
39 | clear_session
40 | set_redirect_error_status session_expiration_error_status
41 | set_error_reason :session_expired
42 | set_redirect_error_flash session_expiration_error_flash
43 | redirect session_expiration_redirect
44 | end
45 |
46 | def update_session
47 | super
48 | t = Time.now.to_i
49 | set_session_value(session_last_activity_session_key, t)
50 | set_session_value(session_created_session_key, t)
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/doc/jwt_cors.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for JWT CORS Feature
2 |
3 | The jwt_cors feature adds support for Cross-Origin Resource Sharing
4 | to Rodauth's JSON API.
5 |
6 | When this feature is used, CORS requests are handled. This includes
7 | CORS preflight requests, which are required since Rodauth's JSON API
8 | uses the application/json request content type.
9 |
10 | This feature depends on the jwt feature.
11 |
12 | == Auth Value Methods
13 |
14 | jwt_cors_allow_headers :: For allowed CORS-preflight requests, the value returned in the Access-Control-Allow-Headers header (default: 'Content-Type, Authorization, Accept'). This specifies which headers can be included in CORS requests.
15 | jwt_cors_allow_methods :: For allowed CORS-preflight requests, the value returned in the Access-Control-Allow-Methods header (default: 'POST'). This specifies which methods are allowed in CORS requests.
16 | jwt_cors_allow_origin :: Which origins are allowed to perform CORS requests. The default is +false+. This can be a String, Array of Strings, Regexp, or +true+ to allow CORS requests from any domain.
17 | jwt_cors_expose_headers :: For allowed CORS requests, the value returned in the Access-Control-Expose-Headers header (default: 'Authorization'). This specifies which headers the browser is allowed to access from a response to a CORS request.
18 | jwt_cors_max_age :: For allowed CORS-preflight requests, the value returned in the Access-Control-Max-Age header (default: 86400). This specifies how long before the information returned should be considered stale and another CORS preflight request made.
19 |
20 | == Auth Methods
21 |
22 | jwt_cors_allow? :: Whether the request should be allowed. This is called for all requests for a Rodauth route that include an Origin header. It should return true or false for whether to specially handle the cross-origin request. By default, uses the +jwt_cors_allow_origin+ setting to check the origin.
23 |
--------------------------------------------------------------------------------
/doc/guides/admin_activation.rdoc:
--------------------------------------------------------------------------------
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
47 |
--------------------------------------------------------------------------------
/doc/release_notes/2.38.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * Rodauth now automatically supports fixed locals in templates if
4 | using Roda 3.88+ and Tilt 2.6+. This allows you to use the
5 | Roda default_fixed_locals: '()' template option without breaking
6 | Rodauth. If the default fixed locals support breaks your Rodauth
7 | configuration, such as if you are overriding Rodauth templates
8 | and modifying the local variables they accept, you can disable
9 | the use of fixed locals in your Rodauth configuration:
10 |
11 | use_template_fixed_locals? false
12 |
13 | * Rodauth::ConfigurationError has been added, and issues that
14 | Rodauth believes are configuration errors now use this
15 | exception class.
16 |
17 | = Other Improvements
18 |
19 | * The following methods are now public:
20 |
21 | * has_password?
22 | * email_auth_email_recently_sent?
23 | * unlock_account_email_recently_sent?
24 | * reset_password_email_recently_sent?
25 | * verify_account_email_recently_sent?
26 |
27 | This makes it supported to call these methods and use the result
28 | in your own code.
29 |
30 | * The verify-account-resend page now works if
31 | verify_account_resend_explanatory_text calls
32 | verify_account_email_recently_sent?. Rodauth does not do that
33 | by default, but if you override
34 | verify_account_resend_explanatory_text to use different text
35 | depending on whether the email was recently sent, direct
36 | navigations to the verify-account-resend page previously failed.
37 |
38 | * Rodauth now uses JWT.gem_version to check the JWT gem version, which
39 | works with JWT 2.10.0. JWT 2.10.1 restored the constants Rodauth
40 | used to check the version, but this allows the JWT to remove
41 | such constants again in the future without breaking Rodauth.
42 |
43 | = Backwards Compatibility
44 |
45 | * The change to use Rodauth::ConfigurationError can break code that
46 | rescued other exception classes, such as ArgumentError,
47 | RuntimeError, or NotImplementedError.
48 |
--------------------------------------------------------------------------------
/demo-site/views/layout.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Rodauth Demo - <%= @page_title %>
8 |
24 |
25 |
26 |
27 |
28 |
40 |
41 |
42 | <% if sms = last_sms_sent %>
43 |
<%= sms %>
44 | <% end %>
45 | <% if flash['notice'] %>
46 |
<%= flash['notice'] %>
47 | <% end %>
48 | <% if flash['error'] %>
49 |
<%= flash['error'] %>
50 | <% end %>
51 |
<%= @page_title %>
52 |
53 | <%== yield %>
54 |
55 | <% if mail = last_mail_sent %>
56 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/javascript/webauthn_auth.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var pack = function(v) { return btoa(String.fromCharCode.apply(null, new Uint8Array(v))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); };
3 | var unpack = function(v) { return Uint8Array.from(atob(v.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); };
4 | var element = document.getElementById('webauthn-auth-form');
5 | var f = function(e) {
6 | //console.log(e);
7 | e.preventDefault();
8 | if (navigator.credentials) {
9 | var opts = JSON.parse(element.getAttribute("data-credential-options"));
10 | opts.challenge = unpack(opts.challenge);
11 | opts.allowCredentials.forEach(function(cred) { cred.id = unpack(cred.id); });
12 | //console.log(opts);
13 | navigator.credentials.get({publicKey: opts}).
14 | then(function(cred){
15 | //console.log(cred);
16 | //window.cred = cred
17 |
18 | var rawId = pack(cred.rawId);
19 | var authValue = {
20 | type: cred.type,
21 | id: rawId,
22 | rawId: rawId,
23 | response: {
24 | authenticatorData: pack(cred.response.authenticatorData),
25 | clientDataJSON: pack(cred.response.clientDataJSON),
26 | signature: pack(cred.response.signature)
27 | }
28 | };
29 |
30 | if (cred.response.userHandle) {
31 | authValue.response.userHandle = pack(cred.response.userHandle);
32 | }
33 |
34 | document.getElementById('webauthn-auth').value = JSON.stringify(authValue);
35 | element.removeEventListener("submit", f);
36 | element.submit();
37 | }).
38 | catch(function(e){document.getElementById('webauthn-auth-button').innerHTML = "Error authenticating using WebAuthn: " + e});
39 | } else {
40 | document.getElementById('webauthn-auth-button').innerHTML = "WebAuthn not supported by browser, or browser has disabled it on this page";
41 | }
42 | };
43 | element.addEventListener("submit", f);
44 | })();
45 |
46 |
--------------------------------------------------------------------------------
/doc/release_notes/2.41.0.txt:
--------------------------------------------------------------------------------
1 | = Improvements
2 |
3 | * When making a change to an account (e.g. changing a login), tokens
4 | for the account are now cleared or reset. Previously, if you
5 | requested a password reset, then requested a login change, and then
6 | changed the login, the password reset link would still be valid
7 | after the login change was made, until the password reset token
8 | expired (default: 1 day). If the reason you are chaging your login
9 | is that you suspect your email may be compromised, you probably
10 | wouldn't want the reset password link to still be valid after the
11 | login change.
12 |
13 | The following account changes trigger clearing of tokens:
14 |
15 | * change login
16 | * close account
17 | * reset password
18 | * unlock account
19 | * verify account
20 |
21 | The following account tokens are cleared upon such changes:
22 |
23 | * active sessions (other than logged in session)
24 | * email auth
25 | * jwt refresh (if not logged in)
26 | * lockout (updates token if it exists)
27 | * remember (creates and uses new remember token if logged in via
28 | remember token)
29 | * reset password
30 | * single session (if not logged in)
31 | * verify account
32 | * verify login change
33 |
34 | This is a more secure default, and it is expected that it will
35 | not negatively affect the vast majority of Rodauth installations.
36 | However, due to Rodauth's very configurable nature, it is possible
37 | it will cause issues for some installations.
38 |
39 | = Backwards Compatibility
40 |
41 | * If clearing tokens on account change causes problems for your
42 | application, you can revert to clearing tokens only on account
43 | close:
44 |
45 | clear_tokens do |reason|
46 | super(reason) if reason == :close_account
47 | end
48 |
49 | * If you were calling after_close_account directly to clear tokens,
50 | you should now also call:
51 |
52 | clear_tokens(:close_account)
53 |
54 | As some token clearing now occurs in clear_tokens and not in
55 | after_close_account.
56 |
--------------------------------------------------------------------------------
/doc/release_notes/2.26.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * An argon2_secret configuration method has been added to the argon2
4 | feature, supporting argon2's built-in password peppering.
5 |
6 | = Other Improvements
7 |
8 | * Links are no longer automatically displayed for routes that are
9 | disabled by calling the *_route method with nil.
10 |
11 | * The QR code used by the otp feature now uses a white background
12 | instead of a transparent background, fixing issues when the
13 | underlying background is dark.
14 |
15 | * Input parameter bytesize is now limited to 1024 bytes by default.
16 | Parameters larger than that will be ignored, as if they weren't
17 | submitted.
18 |
19 | * The Rodauth::Auth class for internal request classes now uses the
20 | same configuration name as the class it is based on.
21 |
22 | * The session_key_prefix configuration method no longer also prefixes
23 | the keys used in the flash hash.
24 |
25 | * The *_path and *_url methods now return nil when the related *_route
26 | method returns nil, indicating the route is disabled.
27 |
28 | * A more explicit error message is raised when using a feature that
29 | requires the hmac_secret being set and not setting hmac_secret.
30 |
31 | = Backwards Compatibility
32 |
33 | * If you are using session_key_prefix and flash messages, you will
34 | probably need to adjust your code to remove the prefix from the
35 | expected flash keys, or manually prefix the flash keys by using
36 | the flash_error_key and flash_notice_key configuration methods.
37 |
38 | * The limiting of input parameter bytesizes by default could potentially
39 | break applications that use Rodauth's parameter parsing method to
40 | handle parameters that Rodauth itself doesn't handle. You can use
41 | the max_param_bytesize configuration method to set a larger bytesize,
42 | or use a value of nil with the method for the previous behavior of
43 | no limit. Additionally, to customize the behavior if a parameter
44 | is over the allowed bytesize, you can use the
45 | over_max_bytesize_param_value configuration method.
46 |
--------------------------------------------------------------------------------
/doc/create_account.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for Create Account Feature
2 |
3 | The create account feature allows users to create new accounts.
4 |
5 | == Auth Value Methods
6 |
7 | create_account_additional_form_tags :: HTML fragment containing additional form tags to use on the create account form.
8 | create_account_button :: The text to use for the create account button.
9 | create_account_error_flash :: The flash error to show for unsuccessful account creation.
10 | create_account_notice_flash :: The flash notice to show after successful account creation.
11 | create_account_page_title :: The page title to use on the create account form.
12 | create_account_redirect :: Where to redirect after creating the account.
13 | create_account_route :: The route to the create account action. Defaults to +create-account+.
14 | create_account_set_password? :: Whether to ask for a password to be set on the create account form. Defaults to true if not verifying accounts. If set to false, an alternative method to set the password should be used (assuming you want to allow password authentication).
15 |
16 | == Auth Methods
17 |
18 | after_create_account :: Run arbitrary code after creating the account.
19 | before_create_account :: Run arbitrary code before creating the account.
20 | before_create_account_route :: Run arbitrary code before handling a create account route.
21 | create_account_autologin? :: Whether to autologin the user upon successful account creation, true by default unless verifying accounts.
22 | create_account_link_text :: The text to use for a link to the create account form.
23 | create_account_response :: Return a response after successful account creation. By default, redirects to +create_account_redirect+.
24 | create_account_view :: The HTML to use for the create account form.
25 | new_account(login) :: Instantiate a new account hash for the given login, without saving it.
26 | save_account :: Insert the account into the database, or return nil/false if that was not successful.
27 | set_new_account_password :: Set the password for a new account if +account_password_hash_column+ is set, without saving.
28 |
--------------------------------------------------------------------------------
/lib/rodauth/features/webauthn_autofill.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:webauthn_autofill, :WebauthnAutofill) do
5 | depends :webauthn_login
6 |
7 | auth_value_method :webauthn_autofill?, true
8 | auth_value_method :webauthn_autofill_js, File.binread(File.expand_path('../../../../javascript/webauthn_autofill.js', __FILE__)).freeze
9 |
10 | translatable_method :webauthn_invalid_webauthn_id_message, "no webauthn key with given id found"
11 |
12 | route(:webauthn_autofill_js) do |r|
13 | before_webauthn_autofill_js_route
14 | r.get do
15 | set_response_header('content-type', 'text/javascript')
16 | webauthn_autofill_js
17 | end
18 | end
19 |
20 | def webauthn_allow
21 | return [] unless logged_in? || account
22 | super
23 | end
24 |
25 | def webauthn_user_verification
26 | 'preferred'
27 | end
28 |
29 | def webauthn_authenticator_selection
30 | super.merge({ 'residentKey' => 'required', 'requireResidentKey' => true })
31 | end
32 |
33 | def login_field_autocomplete_value
34 | request.path_info == login_path ? "#{super} webauthn" : super
35 | end
36 |
37 | private
38 |
39 | def _login_form_footer
40 | footer = super
41 | footer += render("webauthn-autofill") if webauthn_autofill? && !valid_login_entered?
42 | footer
43 | end
44 |
45 | def account_from_webauthn_login
46 | return super if param_or_nil(login_param)
47 |
48 | credential_id = webauthn_auth_data["id"]
49 | account_id = db[webauthn_keys_table]
50 | .where(webauthn_keys_webauthn_id_column => credential_id)
51 | .get(webauthn_keys_account_id_column)
52 |
53 | unless account_id
54 | throw_error_reason(:invalid_webauthn_id, invalid_field_error_status, webauthn_auth_param, webauthn_invalid_webauthn_id_message)
55 | end
56 |
57 | account_from_id(account_id)
58 | end
59 |
60 | def webauthn_login_options?
61 | return true unless param_or_nil(login_param)
62 | super
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/lib/rodauth/features/email_base.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:email_base, :EmailBase) do
5 | translatable_method :email_subject_prefix, ''
6 | auth_value_method :require_mail?, true
7 | auth_value_method :allow_raw_email_token?, false
8 |
9 | redirect :default_post_email
10 |
11 | auth_value_methods(
12 | :email_from
13 | )
14 |
15 | auth_methods(
16 | :create_email,
17 | :email_to,
18 | :send_email
19 | )
20 |
21 | def post_configure
22 | super
23 | require 'mail' if require_mail?
24 | end
25 |
26 | def email_from
27 | "webmaster@#{domain}"
28 | end
29 |
30 | def email_to
31 | account[login_column]
32 | end
33 |
34 | private
35 |
36 | def send_email(email)
37 | email.deliver!
38 | end
39 |
40 | def create_email(subject, body)
41 | create_email_to(email_to, subject, body)
42 | end
43 |
44 | def create_email_to(to, subject, body)
45 | m = Mail.new
46 | m.from = email_from
47 | m.to = to
48 | m.subject = "#{email_subject_prefix}#{subject}"
49 | m.body = body
50 | m
51 | end
52 |
53 | def token_link(route, param, key)
54 | route_url(route, param => token_param_value(key))
55 | end
56 |
57 | def token_param_value(key)
58 | "#{account_id}#{token_separator}#{convert_email_token_key(key)}"
59 | end
60 |
61 | def convert_email_token_key(key)
62 | convert_token_key(key)
63 | end
64 |
65 | def account_from_key(token, status_id=nil)
66 | id, key = split_token(token)
67 | id = convert_token_id(id)
68 | return unless id && key
69 |
70 | return unless actual = yield(id)
71 |
72 | unless (hmac_secret && timing_safe_eql?(key, convert_email_token_key(actual))) ||
73 | (hmac_secret_rotation? && timing_safe_eql?(key, compute_old_hmac(actual))) ||
74 | ((!hmac_secret || allow_raw_email_token?) && timing_safe_eql?(key, actual))
75 | return
76 | end
77 | _account_from_id(id, status_id)
78 | end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/demo-site/views/index.erb:
--------------------------------------------------------------------------------
1 | <% if rodauth.logged_in? %>
2 |
You are logged in as <%= DB[:accounts].where(:id=>rodauth.session_value).get(:email) %>
3 | <% if rodauth.two_factor_authenticated? %>
4 |
You have authenticated using multifactor authentication (<%= rodauth.authenticated_by.join(' and ') %>).
You have logged in via single factor authentication (<%= rodauth.authenticated_by.first %>), but have not authenticated using multifactor authentication.
7 | <% else %>
8 |
You have authenticated via <%= rodauth.authenticated_by.first %>
This is the demo site for Rodauth. Rodauth is an authentication and account management framework for rack applications, with the following design goals:
29 |
30 |
31 |
Security: Ship in a maximum security by default configuration
32 |
Simplicity: Allow for easy configuration via a DSL
33 |
Flexibility: Allow for easy overriding of any part of the framework
34 |
35 |
36 |
It's easiest to get started by going to the login page.
37 |
38 |
This demo site is part of the Rodauth repository, so if you want to know how it works, you can review the source.
39 | <% end %>
40 |
--------------------------------------------------------------------------------
/doc/otp_lockout_email.rdoc:
--------------------------------------------------------------------------------
1 | = Documentation for OTP Lockout Email Feature
2 |
3 | The otp_lockout_email feature emails users when:
4 |
5 | * TOTP authentication is locked out
6 | * TOTP authentication is unlocked
7 | * A TOTP unlock attempt has failed
8 |
9 | The otp_unlock_email feature depends on the otp_lockout and email_base features.
10 |
11 | == Auth Value Methods
12 |
13 | otp_locked_out_email_body :: Body to use for the email notifying user that TOTP authentication has been locked out.
14 | otp_locked_out_email_subject :: Subject to use for the email notifying user that TOTP authentication has been locked out.
15 | otp_unlock_failed_email_body :: Body to use for the email notifying user that there has been an unsuccessful attempt to unlock TOTP authentication.
16 | otp_unlock_failed_email_subject :: Subject to use for the email notifying user that there has been an unsuccessful attempt to unlock TOTP authentication.
17 | otp_unlocked_email_body :: Body to use for the email notifying user that TOTP authentication has been unlocked.
18 | otp_unlocked_email_subject :: Subject to use for the email notifying user that TOTP authentication has been unlocked.
19 | send_otp_locked_out_email? :: Whether to send an email when TOTP authentication is locked out.
20 | send_otp_unlock_failed_email? :: Whether to send an email when there has been an unsuccessful attempt to unlock TOTP authentication.
21 | send_otp_unlocked_email? :: Whether to send an email when TOTP authentication is unlocked.
22 |
23 | == Auth Methods
24 |
25 | create_otp_locked_out_email :: A Mail::Message for the email notifying user that TOTP authentication has been locked out.
26 | create_otp_unlock_failed_email :: A Mail::Message for the email notifying user that there has been an unsuccessful attempt to unlock TOTP authentication.
27 | create_otp_unlocked_email :: A Mail::Message for the email notifying user that TOTP authentication has been unlocked.
28 | send_otp_locked_out_email :: Send the email notifying user that TOTP authentication has been locked out.
29 | send_otp_unlock_failed_email :: Send the email notifying user that there has been an unsuccessful attempt to unlock TOTP authentication.
30 | send_otp_unlocked_email :: Send the email notifying user that TOTP authentication has been unlocked.
31 |
--------------------------------------------------------------------------------
/lib/rodauth/features/http_basic_auth.rb:
--------------------------------------------------------------------------------
1 | # frozen-string-literal: true
2 |
3 | module Rodauth
4 | Feature.define(:http_basic_auth, :HttpBasicAuth) do
5 | auth_value_method :http_basic_auth_realm, "protected"
6 | auth_value_method :require_http_basic_auth?, false
7 |
8 | def logged_in?
9 | ret = super
10 |
11 | if !ret && !defined?(@checked_http_basic_auth)
12 | http_basic_auth
13 | ret = super
14 | end
15 |
16 | ret
17 | end
18 |
19 | def require_login
20 | if require_http_basic_auth?
21 | require_http_basic_auth
22 | end
23 |
24 | super
25 | end
26 |
27 | def require_http_basic_auth
28 | unless http_basic_auth
29 | set_http_basic_auth_error_response
30 | return_response
31 | end
32 | end
33 |
34 | def http_basic_auth
35 | return @checked_http_basic_auth if defined?(@checked_http_basic_auth)
36 |
37 | @checked_http_basic_auth = nil
38 | return unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
39 |
40 | username, password = token.unpack("m*").first.split(/:/, 2)
41 | return unless username && password
42 |
43 | catch_error do
44 | unless account_from_login(username)
45 | throw_basic_auth_error(login_param, no_matching_login_message)
46 | end
47 |
48 | before_login_attempt
49 |
50 | unless open_account?
51 | throw_basic_auth_error(login_param, no_matching_login_message)
52 | end
53 |
54 | unless password_match?(password)
55 | after_login_failure
56 | throw_basic_auth_error(password_param, invalid_password_message)
57 | end
58 |
59 | transaction do
60 | before_login
61 | login_session('password')
62 | after_login
63 | end
64 |
65 | @checked_http_basic_auth = true
66 | return true
67 | end
68 |
69 | nil
70 | end
71 |
72 | private
73 |
74 | def set_http_basic_auth_error_response
75 | response.status = 401
76 | set_response_header("www-authenticate", "Basic realm=\"#{http_basic_auth_realm}\"")
77 | end
78 |
79 | def throw_basic_auth_error(*args)
80 | set_http_basic_auth_error_response
81 | throw_error(*args)
82 | end
83 | end
84 | end
85 |
--------------------------------------------------------------------------------
/doc/release_notes/2.15.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * An internal_request feature has been added. This feature allows
4 | for interacting with Rodauth by calling methods, instead of having
5 | to use a website or JSON API. This feature is designed primarily
6 | for administrative use, so that administrators can create accounts,
7 | change passwords or logins for accounts, and handle similar actions
8 | without the user of the account being involved.
9 |
10 | For example, assuming you've loaded the change_password and
11 | internal_request features, and that your Roda class that
12 | is loading Rodauth is named App, you can change the password
13 | for the account with id 1 using:
14 |
15 | App.rodauth.change_password(account_id: 1, password: 'foobar')
16 |
17 | The internal request methods are implemented as class methods
18 | on the Rodauth::Auth subclass (the object returned by App.rodauth).
19 | These methods call methods on a subclass of that class specific
20 | to internal requests.
21 |
22 | The reason the feature is named internal_request is that these
23 | methods are implemented by submitting a request internally, that is
24 | processed almost exactly the same way as Rodauth would process a
25 | web request.
26 |
27 | See the internal_request feature documentation for details on which
28 | internal request methods are available and the options they take.
29 |
30 | * A path_class_methods feature has been added, that allows for calling
31 | *_path and *_url as class methods. If you would like to call the
32 | *_url methods as class methods, make sure to use the base_url
33 | configuration method to set the base URL so that it does not require
34 | request-specific information.
35 |
36 | * Rodauth::Auth classes now have a configuration_name method that
37 | returns the configuration name associated with the class. They also
38 | have a configuration method that returns the configuration
39 | associated with the class.
40 |
41 | * Rodauth::Feature now supports an internal_request_method method for
42 | specifying which methods are supported as internal request methods.
43 |
44 | = Other Improvements
45 |
46 | * The default base_url configuration method will now use the domain
47 | method to get the domain to use, instead of getting the domain
48 | information directly from the request environment.
49 |
--------------------------------------------------------------------------------
/doc/release_notes/2.10.0.txt:
--------------------------------------------------------------------------------
1 | = New Features
2 |
3 | * An argon2 feature has been added that supports using the argon2
4 | password hashing algorithm instead of the bcrypt password hashing
5 | algorithm. While argon2 does not provide an advantage over bcrypt
6 | if the attacker cannot access the password hashes directly (which
7 | is how Rodauth is recommended to be used), in cases where attackers
8 | can access the password hashes directly, argon2 is thought to be
9 | more difficult or expensive to crack due to requiring more memory
10 | (bcrypt is not a memory-hard password hash algorithm).
11 |
12 | If you are using this feature with Rodauth's database authentication
13 | functions, you need to make sure that the database authentication
14 | functions are configured to support argon2 in addition to bcrypt.
15 | You can do this by passing the :argon2 option when calling the
16 | method to define the database functions. In this example, DB should
17 | be your Sequel::Database object (this could be self if used in a
18 | Sequel migration):
19 |
20 | require 'rodauth/migrations'
21 |
22 | # If the functions are already defined and you are not using PostgreSQL,
23 | # you need to drop the existing functions.
24 | Rodauth.drop_database_authentication_functions(DB)
25 |
26 | # If you are using the disallow_password_reuse feature, also drop the
27 | # database functions related to that if you are not using PostgreSQL:
28 | Rodauth.drop_database_previous_password_check_functions(DB)
29 |
30 | # Define new functions that support argon2:
31 | Rodauth.create_database_authentication_functions(DB, argon2: true)
32 |
33 | # If you are using the disallow_password_reuse feature, also define
34 | # new functions that support argon2 for that:
35 | Rodauth.create_database_previous_password_check_functions(DB, argon2: true)
36 |
37 | You can transparently migrate bcrypt password hashes to argon2
38 | password hashes whenever a user successfully uses their password
39 | by using the argon2 feature in combination with the
40 | update_password_hash feature.
41 |
42 | = Other Improvements
43 |
44 | * Unnecessary queries to determine whether the new password matches
45 | a previous password are now skipped when using the create_account
46 | or verify_account features with the disallow_password_reuse
47 | feature.
48 |
--------------------------------------------------------------------------------