rodauth-rails 0.8.2 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -0
  3. data/README.md +453 -223
  4. data/lib/generators/rodauth/install_generator.rb +26 -15
  5. data/lib/generators/rodauth/migration/base.erb +2 -2
  6. data/lib/generators/rodauth/templates/app/lib/rodauth_app.rb +50 -49
  7. data/lib/generators/rodauth/templates/app/mailers/rodauth_mailer.rb +3 -3
  8. data/lib/generators/rodauth/templates/app/views/rodauth/_global_logout_field.html.erb +1 -1
  9. data/lib/generators/rodauth/templates/app/views/rodauth/_login_confirm_field.html.erb +2 -2
  10. data/lib/generators/rodauth/templates/app/views/rodauth/_login_display.html.erb +2 -2
  11. data/lib/generators/rodauth/templates/app/views/rodauth/_login_field.html.erb +2 -2
  12. data/lib/generators/rodauth/templates/app/views/rodauth/_new_password_field.html.erb +2 -2
  13. data/lib/generators/rodauth/templates/app/views/rodauth/_otp_auth_code_field.html.erb +2 -2
  14. data/lib/generators/rodauth/templates/app/views/rodauth/_password_confirm_field.html.erb +2 -2
  15. data/lib/generators/rodauth/templates/app/views/rodauth/_password_field.html.erb +2 -2
  16. data/lib/generators/rodauth/templates/app/views/rodauth/_recovery_code_field.html.erb +2 -2
  17. data/lib/generators/rodauth/templates/app/views/rodauth/_sms_code_field.html.erb +2 -2
  18. data/lib/generators/rodauth/templates/app/views/rodauth/_sms_phone_field.html.erb +2 -2
  19. data/lib/generators/rodauth/templates/app/views/rodauth/_submit.html.erb +1 -1
  20. data/lib/generators/rodauth/templates/app/views/rodauth/otp_setup.html.erb +2 -2
  21. data/lib/generators/rodauth/templates/app/views/rodauth/remember.html.erb +1 -1
  22. data/lib/generators/rodauth/templates/app/views/rodauth/webauthn_remove.html.erb +1 -1
  23. data/lib/generators/rodauth/templates/app/views/rodauth_mailer/unlock_account.text.erb +1 -1
  24. data/lib/rodauth/rails.rb +20 -0
  25. data/lib/rodauth/rails/app.rb +23 -31
  26. data/lib/rodauth/rails/app/flash.rb +7 -11
  27. data/lib/rodauth/rails/app/middleware.rb +20 -10
  28. data/lib/rodauth/rails/auth.rb +40 -0
  29. data/lib/rodauth/rails/controller_methods.rb +1 -5
  30. data/lib/rodauth/rails/feature.rb +17 -202
  31. data/lib/rodauth/rails/feature/base.rb +62 -0
  32. data/lib/rodauth/rails/feature/callbacks.rb +61 -0
  33. data/lib/rodauth/rails/feature/csrf.rb +65 -0
  34. data/lib/rodauth/rails/feature/email.rb +30 -0
  35. data/lib/rodauth/rails/feature/instrumentation.rb +71 -0
  36. data/lib/rodauth/rails/feature/render.rb +41 -0
  37. data/lib/rodauth/rails/version.rb +1 -1
  38. data/rodauth-rails.gemspec +1 -1
  39. metadata +15 -9
  40. data/lib/generators/rodauth/mailer_generator.rb +0 -37
@@ -10,17 +10,20 @@ module Rodauth
10
10
  include ::ActiveRecord::Generators::Migration
11
11
  include MigrationHelpers
12
12
 
13
+ MAILER_VIEWS = %w[
14
+ email_auth
15
+ password_changed
16
+ reset_password
17
+ unlock_account
18
+ verify_account
19
+ verify_login_change
20
+ ]
21
+
13
22
  source_root "#{__dir__}/templates"
14
23
  namespace "rodauth:install"
15
24
 
16
- # The :api option is a Rails-recognized option that always
17
- # defaults to false, so we make it use our provided default
18
- # value instead.
19
- def self.default_value_for_option(name, options)
20
- name == :api ? options[:default] : super
21
- end
22
-
23
- class_option :api, type: :boolean, desc: "Generate JSON-only configuration"
25
+ class_option :json, type: :boolean, desc: "Configure JSON support"
26
+ class_option :jwt, type: :boolean, desc: "Configure JWT support"
24
27
 
25
28
  def create_rodauth_migration
26
29
  return unless defined?(ActiveRecord::Base)
@@ -53,6 +56,14 @@ module Rodauth
53
56
  template "app/models/account.rb"
54
57
  end
55
58
 
59
+ def create_mailer
60
+ template "app/mailers/rodauth_mailer.rb"
61
+
62
+ MAILER_VIEWS.each do |view|
63
+ template "app/views/rodauth_mailer/#{view}.text.erb"
64
+ end
65
+ end
66
+
56
67
  private
57
68
 
58
69
  def sequel_uri_scheme
@@ -83,17 +94,17 @@ module Rodauth
83
94
  end
84
95
  end
85
96
 
86
- def api_only?
87
- if options.key?(:api)
88
- options[:api]
89
- elsif ::Rails.gem_version >= Gem::Version.new("5.0")
90
- ::Rails.application.config.api_only
91
- end
97
+ def json?
98
+ options[:json]
99
+ end
100
+
101
+ def jwt?
102
+ options[:jwt] || Rodauth::Rails.api_only?
92
103
  end
93
104
 
94
105
  def migration_features
95
106
  features = [:base, :reset_password, :verify_account, :verify_login_change]
96
- features << :remember unless api_only?
107
+ features << :remember unless jwt?
97
108
  features
98
109
  end
99
110
  end
@@ -5,11 +5,11 @@ enable_extension "citext"
5
5
  create_table :accounts<%= primary_key_type %> do |t|
6
6
  <% case activerecord_adapter -%>
7
7
  <% when "postgresql" -%>
8
- t.citext :email, null: false, index: { unique: true, where: "status IN ('verified', 'unverified')" }
8
+ t.citext :email, null: false, index: { unique: true, where: "status IN ('unverified', 'verified')" }
9
9
  <% else -%>
10
10
  t.string :email, null: false, index: { unique: true }
11
11
  <% end -%>
12
- t.string :status, null: false, default: "verified"
12
+ t.string :status, null: false, default: "unverified"
13
13
  end
14
14
 
15
15
  # Used if storing password hashes in a separate table (default)
@@ -1,8 +1,8 @@
1
1
  class RodauthApp < Rodauth::Rails::App
2
- configure<%= " json: :only" if api_only? %> do
2
+ configure do
3
3
  # List of authentication features that are loaded.
4
4
  enable :create_account, :verify_account, :verify_account_grace_period,
5
- :login, :logout, <%= api_only? ? ":jwt" : ":remember" %>,
5
+ :login, :logout<%= ", :remember" unless jwt? %><%= ", :json" if json? %><%= ", :jwt" if jwt? %>,
6
6
  :reset_password, :change_password, :change_password_notify,
7
7
  :change_login, :verify_login_change,
8
8
  :close_account
@@ -14,6 +14,20 @@ class RodauthApp < Rodauth::Rails::App
14
14
  # The secret key used for hashing public-facing tokens for various features.
15
15
  # Defaults to Rails `secret_key_base`, but you can use your own secret key.
16
16
  # hmac_secret "<%= SecureRandom.hex(64) %>"
17
+ <% if jwt? -%>
18
+
19
+ # Set JWT secret, which is used to cryptographically protect the token.
20
+ jwt_secret "<%= SecureRandom.hex(64) %>"
21
+ <% end -%>
22
+ <% if json? || jwt? -%>
23
+
24
+ # Accept only JSON requests.
25
+ only_json? true
26
+
27
+ # Handle login and password confirmation fields on the client side.
28
+ # require_password_confirmation? false
29
+ # require_login_confirmation? false
30
+ <% end -%>
17
31
 
18
32
  # Specify the controller used for view rendering and CSRF verification.
19
33
  rails_controller { RodauthController }
@@ -42,52 +56,34 @@ class RodauthApp < Rodauth::Rails::App
42
56
 
43
57
  # Redirect to the app from login and registration pages if already logged in.
44
58
  # already_logged_in { redirect login_redirect }
45
- <% if api_only? -%>
46
-
47
- # ==> JWT
48
- # Set JWT secret, which is used to cryptographically protect the token.
49
- jwt_secret "<%= SecureRandom.hex(64) %>"
50
-
51
- # Don't require login confirmation param.
52
- require_login_confirmation? false
53
-
54
- # Don't require password confirmation param.
55
- require_password_confirmation? false
56
- <% end -%>
57
59
 
58
60
  # ==> Emails
59
- # Uncomment the lines below once you've imported mailer views.
60
- # create_reset_password_email do
61
- # RodauthMailer.reset_password(email_to, reset_password_email_link)
62
- # end
63
- # create_verify_account_email do
64
- # RodauthMailer.verify_account(email_to, verify_account_email_link)
65
- # end
66
- # create_verify_login_change_email do |login|
67
- # RodauthMailer.verify_login_change(login, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_email_link)
61
+ # Use a custom mailer for delivering authentication emails.
62
+ create_reset_password_email do
63
+ RodauthMailer.reset_password(email_to, reset_password_email_link)
64
+ end
65
+ create_verify_account_email do
66
+ RodauthMailer.verify_account(email_to, verify_account_email_link)
67
+ end
68
+ create_verify_login_change_email do |login|
69
+ RodauthMailer.verify_login_change(login, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_email_link)
70
+ end
71
+ create_password_changed_email do
72
+ RodauthMailer.password_changed(email_to)
73
+ end
74
+ # create_email_auth_email do
75
+ # RodauthMailer.email_auth(email_to, email_auth_email_link)
68
76
  # end
69
- # create_password_changed_email do
70
- # RodauthMailer.password_changed(email_to)
77
+ # create_unlock_account_email do
78
+ # RodauthMailer.unlock_account(email_to, unlock_account_email_link)
71
79
  # end
72
- # # create_email_auth_email do
73
- # # RodauthMailer.email_auth(email_to, email_auth_email_link)
74
- # # end
75
- # # create_unlock_account_email do
76
- # # RodauthMailer.unlock_account(email_to, unlock_account_email_link)
77
- # # end
78
- # send_email do |email|
79
- # # queue email delivery on the mailer after the transaction commits
80
- # db.after_commit { email.deliver_later }
81
- # end
82
-
83
- # In the meantime you can tweak settings for emails created by Rodauth
84
- # email_subject_prefix "[MyApp] "
85
- # email_from "noreply@myapp.com"
86
- # send_email(&:deliver_later)
87
- # reset_password_email_body { "Click here to reset your password: #{reset_password_email_link}" }
80
+ send_email do |email|
81
+ # queue email delivery on the mailer after the transaction commits
82
+ db.after_commit { email.deliver_later }
83
+ end
88
84
 
89
85
  # ==> Flash
90
- <% unless api_only? -%>
86
+ <% unless json? || jwt? -%>
91
87
  # Match flash keys with ones already used in the Rails app.
92
88
  # flash_notice_key :success # default is :notice
93
89
  # flash_error_key :error # default is :alert
@@ -107,7 +103,7 @@ class RodauthApp < Rodauth::Rails::App
107
103
 
108
104
  # Change minimum number of password characters required when creating an account.
109
105
  # password_minimum_length 8
110
- <% unless api_only? -%>
106
+ <% unless jwt? -%>
111
107
 
112
108
  # ==> Remember Feature
113
109
  # Remember all logged in users.
@@ -128,13 +124,14 @@ class RodauthApp < Rodauth::Rails::App
128
124
 
129
125
  # Perform additional actions after the account is created.
130
126
  # after_create_account do
131
- # Profile.create!(account_id: account[:id], name: param("name"))
127
+ # Profile.create!(account_id: account_id, name: param("name"))
132
128
  # end
133
129
 
134
130
  # Do additional cleanup after the account is closed.
135
131
  # after_close_account do
136
- # Profile.find_by!(account_id: account[:id]).destroy
132
+ # Profile.find_by!(account_id: account_id).destroy
137
133
  # end
134
+ <% unless json? || jwt? -%>
138
135
 
139
136
  # ==> Redirects
140
137
  # Redirect to home page after logout.
@@ -145,25 +142,27 @@ class RodauthApp < Rodauth::Rails::App
145
142
 
146
143
  # Redirect to login page after password reset.
147
144
  reset_password_redirect { login_path }
145
+ <% end -%>
148
146
 
149
147
  # ==> Deadlines
150
148
  # Change default deadlines for some actions.
151
149
  # verify_account_grace_period 3.days
152
150
  # reset_password_deadline_interval Hash[hours: 6]
153
151
  # verify_login_change_deadline_interval Hash[days: 2]
152
+ <% unless jwt? -%>
154
153
  # remember_deadline_interval Hash[days: 30]
154
+ <% end -%>
155
155
  end
156
156
 
157
157
  # ==> Multiple configurations
158
158
  # configure(:admin) do
159
- # enable :http_basic_auth
160
- #
159
+ # enable :http_basic_auth # enable different set of features
161
160
  # prefix "/admin"
162
- # session_key :admin_id
161
+ # session_key_prefix "admin_"
163
162
  # end
164
163
 
165
164
  route do |r|
166
- <% unless api_only? -%>
165
+ <% unless jwt? -%>
167
166
  rodauth.load_memory # autologin remembered users
168
167
 
169
168
  <% end -%>
@@ -188,6 +187,8 @@ class RodauthApp < Rodauth::Rails::App
188
187
  # unless rodauth(:admin).logged_in?
189
188
  # rodauth(:admin).require_http_basic_auth
190
189
  # end
190
+ #
191
+ # r.pass # allow the Rails app to handle other "/admin/*" requests
191
192
  # end
192
193
  end
193
194
  end
@@ -1,4 +1,4 @@
1
- class <%= options[:name].camelize %>Mailer < ApplicationMailer
1
+ class RodauthMailer < ApplicationMailer
2
2
  def verify_account(recipient, email_link)
3
3
  @email_link = email_link
4
4
 
@@ -25,13 +25,13 @@ class <%= options[:name].camelize %>Mailer < ApplicationMailer
25
25
 
26
26
  # def email_auth(recipient, email_link)
27
27
  # @email_link = email_link
28
-
28
+ #
29
29
  # mail to: recipient
30
30
  # end
31
31
 
32
32
  # def unlock_account(recipient, email_link)
33
33
  # @email_link = email_link
34
-
34
+ #
35
35
  # mail to: recipient
36
36
  # end
37
37
  end
@@ -1,4 +1,4 @@
1
- <div class="form-group">
1
+ <div class="form-group mb-3">
2
2
  <div class="form-check">
3
3
  <%%= check_box_tag rodauth.global_logout_param, "t", false, id: "global-logout", class: "form-check-input" %>
4
4
  <%%= label_tag "global-logout", "Logout all Logged In Sessons?", class: "form-check-label" %>
@@ -1,4 +1,4 @@
1
- <div class="form-group">
2
- <%%= label_tag "login-confirm", "Confirm Login" %>
1
+ <div class="form-group mb-3">
2
+ <%%= label_tag "login-confirm", "Confirm Login", class: "form-label" %>
3
3
  <%%= render "field", name: rodauth.login_confirm_param, id: "login-confirm", type: :email, autocomplete: "email" %>
4
4
  </div>
@@ -1,4 +1,4 @@
1
- <div class="form-group">
2
- <%%= label_tag "login", "Login" %>
1
+ <div class="form-group mb-3">
2
+ <%%= label_tag "login", "Login", class: "form-label" %>
3
3
  <%%= email_field_tag rodauth.login_param, params[rodauth.login_param], id: "login", readonly: true, class: "form-control-plaintext" %>
4
4
  </div>
@@ -1,4 +1,4 @@
1
- <div class="form-group">
2
- <%%= label_tag "login", "Login" %>
1
+ <div class="form-group mb-3">
2
+ <%%= label_tag "login", "Login", class: "form-label" %>
3
3
  <%%= render "field", name: rodauth.login_param, id: "login", type: :email, autocomplete: "email" %>
4
4
  </div>
@@ -1,4 +1,4 @@
1
- <div class="form-group">
2
- <%%= label_tag "new-password", "New Password" %>
1
+ <div class="form-group mb-3">
2
+ <%%= label_tag "new-password", "New Password", class: "form-label" %>
3
3
  <%%= render "field", name: rodauth.new_password_param, id: "new-password", type: "password", value: "", autocomplete: "new-password" %>
4
4
  </div>
@@ -1,5 +1,5 @@
1
- <div class="form-group">
2
- <%%= label_tag "otp-auth-code", "Authentication Code" %>
1
+ <div class="form-group mb-3">
2
+ <%%= label_tag "otp-auth-code", "Authentication Code", class: "form-label" %>
3
3
  <div class="row">
4
4
  <div class="col-sm-3">
5
5
  <%%= render "field", name: rodauth.otp_auth_param, id: "otp-auth-code", value: "", autocomplete: "off", inputmode: "numeric" %>
@@ -1,4 +1,4 @@
1
- <div class="form-group">
2
- <%%= label_tag "password-confirm", "Confirm Password" %>
1
+ <div class="form-group mb-3">
2
+ <%%= label_tag "password-confirm", "Confirm Password", class: "form-label" %>
3
3
  <%%= render "field", name: rodauth.password_confirm_param, id: "password-confirm", type: :password, value: "", autocomplete: "new-password" %>
4
4
  </div>
@@ -1,4 +1,4 @@
1
- <div class="form-group">
2
- <%%= label_tag "password", "Password" %>
1
+ <div class="form-group mb-3">
2
+ <%%= label_tag "password", "Password", class: "form-label" %>
3
3
  <%%= render "field", name: rodauth.password_param, id: "password", type: :password, value: "", autocomplete: rodauth.password_field_autocomplete_value %>
4
4
  </div>
@@ -1,4 +1,4 @@
1
- <div class="form-group">
2
- <%%= label_tag "recovery_code", "Recovery Code" %>
1
+ <div class="form-group mb-3">
2
+ <%%= label_tag "recovery_code", "Recovery Code", class: "form-label" %>
3
3
  <%%= render "field", name: rodauth.recovery_codes_param, id: "recovery_code", value: "", autocomplete: "off" %>
4
4
  </div>
@@ -1,5 +1,5 @@
1
- <div class="form-group">
2
- <%%= label_tag "sms-code", "SMS Code" %>
1
+ <div class="form-group mb-3">
2
+ <%%= label_tag "sms-code", "SMS Code", class: "form-label" %>
3
3
  <div class="row">
4
4
  <div class="col-sm-3">
5
5
  <%%= render "field", name: rodauth.sms_code_param, id: "sms-code", value: "", autocomplete: "one-time-code", inputmode: "numeric" %>
@@ -1,5 +1,5 @@
1
- <div class="form-group">
2
- <%%= label_tag "sms-phone", "Phone Number" %>
1
+ <div class="form-group mb-3">
2
+ <%%= label_tag "sms-phone", "Phone Number", class: "form-label" %>
3
3
  <div class="row">
4
4
  <div class="col-sm-3">
5
5
  <%%= render "field", name: rodauth.sms_phone_param, id: "sms-phone", type: :tel, autocomplete: "tel" %>
@@ -1,3 +1,3 @@
1
- <div class="form-group">
1
+ <div class="form-group mb-3">
2
2
  <%%= submit_tag local_assigns[:value], name: local_assigns[:name], class: local_assigns[:class] || "btn btn-primary" %>
3
3
  </div>
@@ -2,14 +2,14 @@
2
2
  <%%= hidden_field_tag rodauth.otp_setup_param, rodauth.otp_user_key, id: "otp-key" %>
3
3
  <%%= hidden_field_tag rodauth.otp_setup_raw_param, rodauth.otp_key, id: "otp-hmac-secret" if rodauth.otp_keys_use_hmac? %>
4
4
 
5
- <div class="form-group">
5
+ <div class="form-group mb-3">
6
6
  <p>Secret: <%%= rodauth.otp_user_key %></p>
7
7
  <p>Provisioning URL: <%%= rodauth.otp_provisioning_uri %></p>
8
8
  </div>
9
9
 
10
10
  <div class="row">
11
11
  <div class="col-lg-6 col-lg">
12
- <div class="form-group">
12
+ <div class="form-group mb-3">
13
13
  <p><%%= rodauth.otp_qr_code.html_safe %></p>
14
14
  </div>
15
15
  </div>
@@ -1,5 +1,5 @@
1
1
  <%%= form_tag rodauth.remember_path, method: :post do %>
2
- <fieldset class="form-group">
2
+ <fieldset class="form-group mb-3">
3
3
  <div class="form-check">
4
4
  <%%= radio_button_tag rodauth.remember_param, rodauth.remember_remember_param_value, false, id: "remember-remember", class: "form-check-input" %>
5
5
  <%%= label_tag "remember-remember", "Remember Me", class: "form-check-label" %>
@@ -1,6 +1,6 @@
1
1
  <%%= form_tag rodauth.webauthn_remove_path, method: :post, id: "webauthn-remove-form" do %>
2
2
  <%%= render "password_field" if rodauth.two_factor_modifications_require_password? %>
3
- <fieldset class="form-group">
3
+ <fieldset class="form-group mb-3">
4
4
  <%% (usage = rodauth.account_webauthn_usage).each do |id, last_use| %>
5
5
  <div class="form-check">
6
6
  <%%= render "field", name: rodauth.webauthn_remove_param, id: "webauthn-remove-#{id}", type: :radio, class: "form-check-input", skip_error_message: true, value: id, required: false %>
@@ -1,4 +1,4 @@
1
- Someone has requested a that the account with this email be unlocked.
1
+ Someone has requested that the account with this email be unlocked.
2
2
  If you did not request the unlocking of this account, please ignore this
3
3
  message. If you requested the unlocking of this account, please go to
4
4
  <%%= @email_link %>
data/lib/rodauth/rails.rb CHANGED
@@ -8,6 +8,7 @@ module Rodauth
8
8
 
9
9
  # This allows the developer to avoid loading Rodauth at boot time.
10
10
  autoload :App, "rodauth/rails/app"
11
+ autoload :Auth, "rodauth/rails/auth"
11
12
 
12
13
  @app = nil
13
14
  @middleware = true
@@ -32,6 +33,15 @@ module Rodauth
32
33
  scope.rodauth(name)
33
34
  end
34
35
 
36
+ # routing constraint that requires authentication
37
+ def authenticated(name = nil, &condition)
38
+ lambda do |request|
39
+ rodauth = request.env.fetch ["rodauth", *name].join(".")
40
+ rodauth.require_authentication
41
+ rodauth.authenticated? && (condition.nil? || condition.call(rodauth))
42
+ end
43
+ end
44
+
35
45
  if ::Rails.gem_version >= Gem::Version.new("5.2")
36
46
  def secret_key_base
37
47
  ::Rails.application.secret_key_base
@@ -42,6 +52,16 @@ module Rodauth
42
52
  end
43
53
  end
44
54
 
55
+ if ::Rails.gem_version >= Gem::Version.new("5.0")
56
+ def api_only?
57
+ ::Rails.application.config.api_only
58
+ end
59
+ else
60
+ def api_only?
61
+ false
62
+ end
63
+ end
64
+
45
65
  def configure
46
66
  yield self
47
67
  end