rodauth-rails 0.8.0 → 0.10.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.
@@ -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
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
@@ -1,6 +1,5 @@
1
1
  require "roda"
2
- require "rodauth"
3
- require "rodauth/rails/feature"
2
+ require "rodauth/rails/auth"
4
3
 
5
4
  module Rodauth
6
5
  module Rails
@@ -11,38 +10,29 @@ module Rodauth
11
10
 
12
11
  plugin :hooks
13
12
  plugin :render, layout: false
13
+ plugin :pass
14
14
 
15
- def self.configure(name = nil, **options, &block)
16
- unless options[:json] == :only
17
- require "rodauth/rails/app/flash"
18
- plugin Flash
19
- end
20
-
21
- plugin :rodauth, name: name, csrf: false, flash: false, **options do
22
- # load the Rails integration
23
- enable :rails
15
+ unless Rodauth::Rails.api_only?
16
+ require "rodauth/rails/app/flash"
17
+ plugin Flash
18
+ end
24
19
 
25
- # database functions are more complex to set up, so disable them by default
26
- use_database_authentication_functions? false
20
+ def self.configure(*args, **options, &block)
21
+ auth_class = args.shift if args[0].is_a?(Class)
22
+ name = args.shift if args[0].is_a?(Symbol)
27
23
 
28
- # avoid having to set deadline values in column default values
29
- set_deadline_values? true
24
+ fail ArgumentError, "need to pass optional Rodauth::Auth subclass and optional configuration name" if args.any?
30
25
 
31
- # use HMACs for additional security
32
- hmac_secret { Rodauth::Rails.secret_key_base }
26
+ auth_class ||= Class.new(Rodauth::Rails::Auth)
33
27
 
34
- # evaluate user configuration
35
- instance_exec(&block)
28
+ plugin :rodauth, auth_class: auth_class, name: name, csrf: false, flash: false, json: true, **options do
29
+ instance_exec(&block) if block
36
30
  end
37
31
  end
38
32
 
39
33
  before do
40
- (opts[:rodauths] || {}).each do |name, _|
41
- if name
42
- env["rodauth.#{name}"] = rodauth(name)
43
- else
44
- env["rodauth"] = rodauth
45
- end
34
+ opts[:rodauths]&.each_key do |name|
35
+ env[["rodauth", *name].join(".")] = rodauth(name)
46
36
  end
47
37
  end
48
38
  end
@@ -30,10 +30,12 @@ module Rodauth
30
30
  rails_request.flash
31
31
  end
32
32
 
33
- def commit_flash
34
- if ActionPack.version >= Gem::Version.new("5.0")
33
+ if ActionPack.version >= Gem::Version.new("5.0")
34
+ def commit_flash
35
35
  rails_request.commit_flash
36
- else
36
+ end
37
+ else
38
+ def commit_flash
37
39
  # ActionPack 4.2 automatically commits flash
38
40
  end
39
41
  end
@@ -3,20 +3,30 @@ module Rodauth
3
3
  class App
4
4
  # Roda plugin that extends middleware plugin by propagating response headers.
5
5
  module Middleware
6
- def self.load_dependencies(app)
7
- app.plugin :hooks
8
- end
9
-
10
6
  def self.configure(app)
11
- app.after do
12
- if response.empty? && response.headers.any?
13
- env["rodauth.rails.headers"] = response.headers
7
+ handle_result = -> (env, res) do
8
+ if headers = env.delete("rodauth.rails.headers")
9
+ res[1] = headers.merge(res[1])
14
10
  end
15
11
  end
16
12
 
17
- app.plugin :middleware, handle_result: -> (env, res) do
18
- if headers = env.delete("rodauth.rails.headers")
19
- res[1] = headers.merge(res[1])
13
+ app.plugin :middleware, handle_result: handle_result do |middleware|
14
+ middleware.plugin :hooks
15
+
16
+ middleware.after do
17
+ if response.empty? && response.headers.any?
18
+ env["rodauth.rails.headers"] = response.headers
19
+ end
20
+ end
21
+
22
+ middleware.class_eval do
23
+ def self.inspect
24
+ "#{superclass}::Middleware"
25
+ end
26
+
27
+ def inspect
28
+ "#<#{self.class.inspect} request=#{request.inspect} response=#{response.inspect}>"
29
+ end
20
30
  end
21
31
  end
22
32
  end
@@ -0,0 +1,40 @@
1
+ require "rodauth"
2
+ require "rodauth/rails/feature"
3
+
4
+ module Rodauth
5
+ module Rails
6
+ # Base auth class that applies some default configuration and supports
7
+ # multi-level inheritance.
8
+ class Auth < Rodauth::Auth
9
+ class << self
10
+ attr_writer :features
11
+ attr_writer :routes
12
+ attr_accessor :configuration
13
+ end
14
+
15
+ def self.inherited(auth_class)
16
+ super
17
+ auth_class.roda_class = Rodauth::Rails.app
18
+ auth_class.features = features.dup
19
+ auth_class.routes = routes.dup
20
+ auth_class.route_hash = route_hash.dup
21
+ auth_class.configuration = configuration.clone
22
+ auth_class.configuration.instance_variable_set(:@auth, auth_class)
23
+ end
24
+
25
+ # apply default configuration
26
+ configure do
27
+ enable :rails
28
+
29
+ # database functions are more complex to set up, so disable them by default
30
+ use_database_authentication_functions? false
31
+
32
+ # avoid having to set deadline values in column default values
33
+ set_deadline_values? true
34
+
35
+ # use HMACs for additional security
36
+ hmac_secret { Rodauth::Rails.secret_key_base }
37
+ end
38
+ end
39
+ end
40
+ end