rodauth-rails 0.8.2 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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