rodauth-rails 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0d00b7ad2f6198fff3a5cc3c720c6f30d9296898e3fd764ddf7408e36232a6d
4
- data.tar.gz: 9ba008116fc5521c98ed62dda5b1f6b2eccd733d06ccb0a2c5b9b4db8df94539
3
+ metadata.gz: 00d7ab9dd749cbae17cddc2788005d8570d8d46f89f427ad624c7e61fe177665
4
+ data.tar.gz: d62aed32823b0be9c74d7281de80650b8faf600c01db22ad941a35f30bfeb002
5
5
  SHA512:
6
- metadata.gz: 848873e599cfb8dc8a5d274ac5fec5987cf4c895c77757a4b21529ddcd9a635ba8086c037f55c55ef097ae926549234e5abebb97a5300f0ead6118927d737d91
7
- data.tar.gz: aa75fb48217e79c40000cf1f226f36a65fdffdcb764233572ae45341b7ab9dd2f1d8f8470e593d8260495962b1b5c352e62d0d5e71fed0cb96891da93a0f344c
6
+ metadata.gz: 7ab0afe5e95fab1af706b64ef5c494252fac49481d49dad1c2ef3510c17ca96050c58bf0832a36af761673137f2fd63d7d49a692656bcf06748840de96f60875
7
+ data.tar.gz: 729bf3b5887647c23f4b11d821d3829c4b4d290c546d2f19ba6b393dd47bf0b357d128577e877ac3e0a4352972b79325d4217d62935d4a683e78ff8e910d13a2
@@ -1,3 +1,13 @@
1
+ ## 0.6.0 (2020-11-22)
2
+
3
+ * Add `Rodauth::Rails.rodauth` method for retrieving Rodauth instance outside of request context (@janko)
4
+
5
+ * Add default Action Dispatch response headers in Rodauth responses (@janko)
6
+
7
+ * Run controller rescue handlers around Rodauth actions (@janko)
8
+
9
+ * Run controller action callbacks around Rodauth actions (@janko)
10
+
1
11
  ## 0.5.0 (2020-11-16)
2
12
 
3
13
  * Support more Active Record adapters in `rodauth:install` generator (@janko)
data/README.md CHANGED
@@ -4,16 +4,22 @@ Provides Rails integration for the [Rodauth] authentication framework.
4
4
 
5
5
  ## Resources
6
6
 
7
+ Useful links:
8
+
7
9
  * [Rodauth documentation](http://rodauth.jeremyevans.net/documentation.html)
8
- * [rodauth-rails wiki](https://github.com/janko/rodauth-rails/wiki)
9
10
  * [Rails demo](https://github.com/janko/rodauth-demo-rails)
10
11
 
12
+ Articles:
13
+
14
+ * [Rodauth: A Refreshing Authentication Solution for Ruby](https://janko.io/rodauth-a-refreshing-authentication-solution-for-ruby/)
15
+ * [Adding Authentication in Rails 6 with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
16
+
11
17
  ## Installation
12
18
 
13
19
  Add the gem to your Gemfile:
14
20
 
15
21
  ```rb
16
- gem "rodauth-rails", "~> 0.4"
22
+ gem "rodauth-rails", "~> 0.6"
17
23
 
18
24
  # gem "jwt", require: false # for JWT feature
19
25
  # gem "rotp", require: false # for OTP feature
@@ -111,8 +117,9 @@ end
111
117
 
112
118
  ### Controller
113
119
 
114
- Your Rodauth app will by default use `RodauthController` for view rendering
115
- and CSRF protection.
120
+ Your Rodauth app will by default use `RodauthController` for view rendering,
121
+ CSRF protection, and running controller callbacks and rescue handlers around
122
+ Rodauth actions.
116
123
 
117
124
  ```rb
118
125
  # app/controllers/rodauth_controller.rb
@@ -131,9 +138,11 @@ class Account < ApplicationRecord
131
138
  end
132
139
  ```
133
140
 
134
- ## Getting started
141
+ ## Usage
135
142
 
136
- First, let's see what routes our Rodauth middleware will handle:
143
+ ### Routes
144
+
145
+ We can see the list of routes our Rodauth middleware handles:
137
146
 
138
147
  ```sh
139
148
  $ rails rodauth:routes
@@ -155,8 +164,8 @@ Routes handled by RodauthApp:
155
164
  /close-account rodauth.close_account_path
156
165
  ```
157
166
 
158
- We can use this information to add some basic authentication navigation links
159
- to our home page:
167
+ Using this information, we could add some basic authentication links to our
168
+ navigation header:
160
169
 
161
170
  ```erb
162
171
  <ul>
@@ -169,40 +178,45 @@ to our home page:
169
178
  </ul>
170
179
  ```
171
180
 
172
- These links are fully functional, feel free to visit them and interact with the
181
+ These routes are fully functional, feel free to visit them and interact with the
173
182
  pages. The templates that ship with Rodauth aim to provide a complete
174
183
  authentication experience, and the forms use [Bootstrap] markup.
175
184
 
176
- Let's also load the account record for authenticated requests and expose it via
177
- `#current_account`:
185
+ ### Current account
186
+
187
+ To be able to fetch currently authenticated account, let's define a
188
+ `#current_account` method that fetches the account id from session and
189
+ retrieves the corresponding account record:
178
190
 
179
191
  ```rb
180
192
  # app/controllers/application_controller.rb
181
193
  class ApplicationController < ActionController::Base
182
- before_action :load_account, if: -> { rodauth.authenticated? }
194
+ before_action :current_account, if: -> { rodauth.authenticated? }
183
195
 
184
196
  private
185
197
 
186
- def load_account
187
- @current_account = Account.find(rodauth.session_value)
198
+ def current_account
199
+ @current_account ||= Account.find(rodauth.session_value)
188
200
  rescue ActiveRecord::RecordNotFound
189
201
  rodauth.logout
190
202
  rodauth.login_required
191
203
  end
192
-
193
- attr_reader :current_account
194
204
  helper_method :current_account
195
205
  end
196
206
  ```
207
+
208
+ This allows us to access the current account in controllers and views:
209
+
197
210
  ```erb
198
211
  <p>Authenticated as: <%= current_account.email %></p>
199
212
  ```
200
213
 
201
214
  ### Requiring authentication
202
215
 
203
- Next, we'll likely want to require authentication for certain sections/pages of
204
- our app. We can do this in our Rodauth app's routing block, which helps keep
205
- the authentication logic encapsulated:
216
+ We'll likely want to require authentication for certain parts of our app,
217
+ redirecting the user to the login page if they're not logged in. We can do this
218
+ in our Rodauth app's routing block, which helps keep the authentication logic
219
+ encapsulated:
206
220
 
207
221
  ```rb
208
222
  # app/lib/rodauth_app.rb
@@ -260,9 +274,9 @@ end
260
274
 
261
275
  ### Views
262
276
 
263
- The templates built into Rodauth are useful when getting started, but at some
264
- point we'll probably want more control over the markup. For that we can run the
265
- following command:
277
+ The templates built into Rodauth are useful when getting started, but soon
278
+ you'll want to start editing the markup. You can run the following command to
279
+ copy Rodauth templates into your Rails app:
266
280
 
267
281
  ```sh
268
282
  $ rails generate rodauth:views
@@ -286,7 +300,7 @@ $ rails generate rodauth:views --all
286
300
  ```
287
301
 
288
302
  You can also tell the generator to create views into another directory (in this
289
- case make sure to rename the Rodauth controller accordingly).
303
+ case make sure to rename the Rodauth controller accordingly):
290
304
 
291
305
  ```sh
292
306
  # generates views into app/views/authentication
@@ -366,8 +380,8 @@ end
366
380
  ```
367
381
 
368
382
  You can then uncomment the lines in your Rodauth configuration to have it call
369
- your mailer. If you've enabled additional authentication features, make sure to
370
- override their `send_*_email` methods as well.
383
+ your mailer. If you've enabled additional authentication features that send
384
+ emails, make sure to override their `send_*_email` methods as well.
371
385
 
372
386
  ```rb
373
387
  # app/lib/rodauth_app.rb
@@ -408,10 +422,11 @@ end
408
422
 
409
423
  ### Migrations
410
424
 
411
- The install generator will have created some default tables, but you can use
412
- the migration generator to create tables for any additional Rodauth features:
425
+ The install generator will create a migration for tables used by the Rodauth
426
+ features enabled by default. For any additional features, you can use the
427
+ migration generator to create the corresponding tables:
413
428
 
414
- ```
429
+ ```sh
415
430
  $ rails generate rodauth:migration otp sms_codes recovery_codes
416
431
  ```
417
432
  ```rb
@@ -456,6 +471,25 @@ the configure method.
456
471
  Make sure to store the `jwt_secret` in a secure place, such as Rails
457
472
  credentials or environment variables.
458
473
 
474
+ ### Rodauth instance
475
+
476
+ In some cases you might need to use Rodauth more programmatically, and perform
477
+ Rodauth operations outside of the request context. rodauth-rails gives you the
478
+ ability to retrieve the Rodauth instance:
479
+
480
+ ```rb
481
+ rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:secondary)
482
+
483
+ rodauth.login_url #=> "https://example.com/login"
484
+ rodauth.account_from_login("user@example.com") # loads user by email
485
+ rodauth.password_match?("secret") #=> true
486
+ rodauth.setup_account_verification
487
+ rodauth.close_account
488
+ ```
489
+
490
+ This Rodauth instance will be initialized with basic Rack env that allows is it
491
+ to generate URLs, using `config.action_mailer.default_url_options` options.
492
+
459
493
  ## How it works
460
494
 
461
495
  ### Middleware
@@ -500,11 +534,12 @@ end
500
534
  The `Rodauth::Rails::App` class is a [Roda] subclass that provides Rails
501
535
  integration for Rodauth:
502
536
 
503
- * uses Rails' flash instead of Roda's
504
- * uses Rails' CSRF protection instead of Roda's
537
+ * uses Action Dispatch flash instead of Roda's
538
+ * uses Action Dispatch CSRF protection instead of Roda's
505
539
  * sets [HMAC] secret to Rails' secret key base
506
- * uses ActionController for rendering templates
507
- * uses ActionMailer for sending emails
540
+ * uses Action Controller for rendering templates
541
+ * runs Action Controller callbacks & rescue handlers around Rodauth actions
542
+ * uses Action Mailer for sending emails
508
543
 
509
544
  The `configure { ... }` method wraps configuring the Rodauth plugin, forwarding
510
545
  any additional [plugin options].
@@ -635,15 +670,11 @@ Rodauth method for creating database functions:
635
670
  # db/migrate/*_create_rodauth_database_functions.rb
636
671
  class CreateRodauthDatabaseFunctions < ActiveRecord::Migration
637
672
  def up
638
- # ...
639
673
  Rodauth.create_database_authentication_functions(DB)
640
- # ...
641
674
  end
642
675
 
643
676
  def down
644
- # ...
645
677
  Rodauth.drop_database_authentication_functions(DB)
646
- # ...
647
678
  end
648
679
  end
649
680
  ```
@@ -700,7 +731,6 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
700
731
 
701
732
  [Rodauth]: https://github.com/jeremyevans/rodauth
702
733
  [Sequel]: https://github.com/jeremyevans/sequel
703
- [rendering views outside of controllers]: https://blog.bigbinary.com/2016/01/08/rendering-views-outside-of-controllers-in-rails-5.html
704
734
  [feature documentation]: http://rodauth.jeremyevans.net/documentation.html
705
735
  [JWT feature]: http://rodauth.jeremyevans.net/rdoc/files/doc/jwt_rdoc.html
706
736
  [JWT gem]: https://github.com/jwt/ruby-jwt
@@ -9,14 +9,33 @@ module Rodauth
9
9
  # This allows the developer to avoid loading Rodauth at boot time.
10
10
  autoload :App, "rodauth/rails/app"
11
11
 
12
- def self.configure
13
- yield self
14
- end
15
-
16
12
  @app = nil
17
13
  @middleware = true
18
14
 
19
15
  class << self
16
+ def rodauth(name = nil)
17
+ url_options = ActionMailer::Base.default_url_options
18
+
19
+ scheme = url_options[:protocol] || "http"
20
+ port = url_options[:port]
21
+ port ||= Rack::Request::DEFAULT_PORTS[scheme] if Gem::Version.new(Rack.release) < Gem::Version.new("2.0")
22
+ host = url_options[:host]
23
+ host += ":#{port}" if port
24
+
25
+ rack_env = {
26
+ "HTTP_HOST" => host,
27
+ "rack.url_scheme" => scheme,
28
+ }
29
+
30
+ scope = app.new(rack_env)
31
+
32
+ scope.rodauth(name)
33
+ end
34
+
35
+ def configure
36
+ yield self
37
+ end
38
+
20
39
  attr_writer :app
21
40
  attr_writer :middleware
22
41
 
@@ -10,7 +10,7 @@ module Rodauth
10
10
 
11
11
  def self.configure(name = nil, **options, &block)
12
12
  unless options[:json] == :only
13
- require "rodauth/rails/app/flash"
13
+ require "rodauth/rails/flash"
14
14
  plugin Flash
15
15
  end
16
16
 
@@ -9,10 +9,11 @@ module Rodauth
9
9
  :rails_csrf_param,
10
10
  :rails_csrf_token,
11
11
  :rails_check_csrf!,
12
- :rails_controller_instance,
13
12
  :rails_controller,
14
13
  )
15
14
 
15
+ auth_cached_method :rails_controller_instance
16
+
16
17
  # Renders templates with layout. First tries to render a user-defined
17
18
  # template, otherwise falls back to Rodauth's template.
18
19
  def view(page, *)
@@ -28,6 +29,11 @@ module Rodauth
28
29
  super
29
30
  end
30
31
 
32
+ # Render Rails CSRF tags in Rodauth templates.
33
+ def csrf_tag(*)
34
+ rails_csrf_tag
35
+ end
36
+
31
37
  # Verify Rails' authenticity token.
32
38
  def check_csrf
33
39
  rails_check_csrf!
@@ -38,11 +44,6 @@ module Rodauth
38
44
  true
39
45
  end
40
46
 
41
- # Render Rails CSRF tags in Rodauth templates.
42
- def csrf_tag(*)
43
- rails_csrf_tag
44
- end
45
-
46
47
  # Default the flash error key to Rails' default :alert.
47
48
  def flash_error_key
48
49
  :alert
@@ -50,6 +51,59 @@ module Rodauth
50
51
 
51
52
  private
52
53
 
54
+ # Runs controller callbacks and rescue handlers around Rodauth actions.
55
+ def _around_rodauth(&block)
56
+ result = nil
57
+
58
+ rails_controller_rescue do
59
+ rails_controller_callbacks do
60
+ result = catch(:halt) { super(&block) }
61
+ end
62
+ end
63
+
64
+ if rails_controller_instance.performed?
65
+ rails_controller_response
66
+ else
67
+ result[1].merge!(rails_controller_instance.response.headers)
68
+ throw :halt, result
69
+ end
70
+ end
71
+
72
+ # Runs any #(before|around|after)_action controller callbacks.
73
+ def rails_controller_callbacks
74
+ # don't verify CSRF token as part of callbacks, Rodauth will do that
75
+ rails_controller_instance.allow_forgery_protection = false
76
+
77
+ rails_controller_instance.run_callbacks(:process_action) do
78
+ # turn the setting back to default so that form tags generate CSRF tags
79
+ rails_controller_instance.allow_forgery_protection = rails_controller.allow_forgery_protection
80
+
81
+ yield
82
+ end
83
+ end
84
+
85
+ # Runs any registered #rescue_from controller handlers.
86
+ def rails_controller_rescue
87
+ yield
88
+ rescue Exception => exception
89
+ rails_controller_instance.rescue_with_handler(exception) || raise
90
+
91
+ unless rails_controller_instance.performed?
92
+ raise Rodauth::Rails::Error, "rescue_from handler didn't write any response"
93
+ end
94
+ end
95
+
96
+ # Returns Roda response from controller response if set.
97
+ def rails_controller_response
98
+ controller_response = rails_controller_instance.response
99
+
100
+ response.status = controller_response.status
101
+ response.headers.merge! controller_response.headers
102
+ response.write controller_response.body
103
+
104
+ request.halt
105
+ end
106
+
53
107
  # Create emails with ActionMailer which uses configured delivery method.
54
108
  def create_email_to(to, subject, body)
55
109
  Mailer.create_email(to: to, from: email_from, subject: "#{email_subject_prefix}#{subject}", body: body)
@@ -64,11 +118,14 @@ module Rodauth
64
118
  def rails_render(*args)
65
119
  return if only_json?
66
120
 
67
- begin
68
- rails_controller_instance.render_to_string(*args)
69
- rescue ActionView::MissingTemplate
70
- nil
71
- end
121
+ rails_controller_instance.render_to_string(*args)
122
+ rescue ActionView::MissingTemplate
123
+ nil
124
+ end
125
+
126
+ # Calls the controller to verify the authenticity token.
127
+ def rails_check_csrf!
128
+ rails_controller_instance.send(:verify_authenticity_token)
72
129
  end
73
130
 
74
131
  # Hidden tag with Rails CSRF token inserted into Rodauth templates.
@@ -86,13 +143,8 @@ module Rodauth
86
143
  rails_controller_instance.send(:form_authenticity_token)
87
144
  end
88
145
 
89
- # Calls the controller to verify the authenticity token.
90
- def rails_check_csrf!
91
- rails_controller_instance.send(:verify_authenticity_token)
92
- end
93
-
94
146
  # Instances of the configured controller with current request's env hash.
95
- def rails_controller_instance
147
+ def _rails_controller_instance
96
148
  request = ActionDispatch::Request.new(scope.env)
97
149
  instance = rails_controller.new
98
150
 
@@ -0,0 +1,48 @@
1
+ module Rodauth
2
+ module Rails
3
+ # Roda plugin that sets up Rails flash integration.
4
+ module Flash
5
+ def self.load_dependencies(app)
6
+ app.plugin :hooks
7
+ end
8
+
9
+ def self.configure(app)
10
+ app.before { request.flash } # load flash
11
+ app.after { request.commit_flash } # save flash
12
+ end
13
+
14
+ module InstanceMethods
15
+ def flash
16
+ request.flash
17
+ end
18
+ end
19
+
20
+ module RequestMethods
21
+ # If the redirect would bubble up outside of the Roda app, the after
22
+ # hook would never get called, so we make sure to commit the flash.
23
+ def redirect(*)
24
+ commit_flash
25
+ super
26
+ end
27
+
28
+ def flash
29
+ rails_request.flash
30
+ end
31
+
32
+ def commit_flash
33
+ if ActionPack.version >= Gem::Version.new("5.0")
34
+ rails_request.commit_flash
35
+ else
36
+ # ActionPack 4.2 automatically commits flash
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def rails_request
43
+ ActionDispatch::Request.new(env)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -3,19 +3,16 @@ namespace :rodauth do
3
3
  app = Rodauth::Rails.app
4
4
 
5
5
  puts "Routes handled by #{app}:"
6
- puts
7
6
 
8
- app.opts[:rodauths].each do |rodauth_name, rodauth_class|
9
- route_names = rodauth_class.routes
10
- .map { |handle_method| handle_method.to_s.sub(/\Ahandle_/, "") }
11
- .uniq
7
+ app.opts[:rodauths].each_key do |rodauth_name|
8
+ rodauth = Rodauth::Rails.rodauth(rodauth_name)
12
9
 
13
- rodauth = rodauth_class.allocate
10
+ routes = rodauth.class.routes.map do |handle_method|
11
+ path_method = "#{handle_method.to_s.sub(/\Ahandle_/, "")}_path"
14
12
 
15
- routes = route_names.map do |name|
16
13
  [
17
- rodauth.public_send(:"#{name}_path"),
18
- "rodauth#{rodauth_name && "(:#{rodauth_name})"}.#{name}_path",
14
+ rodauth.public_send(path_method),
15
+ "rodauth#{rodauth_name && "(:#{rodauth_name})"}.#{path_method}",
19
16
  ]
20
17
  end
21
18
 
@@ -25,8 +22,7 @@ namespace :rodauth do
25
22
  "#{path.ljust(padding)} #{code}"
26
23
  end
27
24
 
28
- puts " #{route_lines.join("\n ")}"
29
- puts
25
+ puts "\n #{route_lines.join("\n ")}"
30
26
  end
31
27
  end
32
28
  end
@@ -1,5 +1,5 @@
1
1
  module Rodauth
2
2
  module Rails
3
- VERSION = "0.5.0"
3
+ VERSION = "0.6.0"
4
4
  end
5
5
  end
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.require_paths = ["lib"]
18
18
 
19
19
  spec.add_dependency "railties", ">= 4.2", "< 7"
20
- spec.add_dependency "rodauth", "~> 2.1"
20
+ spec.add_dependency "rodauth", "~> 2.6"
21
21
  spec.add_dependency "sequel-activerecord_connection", "~> 1.1"
22
22
  spec.add_dependency "tilt"
23
23
  spec.add_dependency "bcrypt"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rodauth-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-16 00:00:00.000000000 Z
11
+ date: 2020-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -36,14 +36,14 @@ dependencies:
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '2.1'
39
+ version: '2.6'
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '2.1'
46
+ version: '2.6'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: sequel-activerecord_connection
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -190,9 +190,9 @@ files:
190
190
  - lib/rodauth/features/rails.rb
191
191
  - lib/rodauth/rails.rb
192
192
  - lib/rodauth/rails/app.rb
193
- - lib/rodauth/rails/app/flash.rb
194
193
  - lib/rodauth/rails/controller_methods.rb
195
194
  - lib/rodauth/rails/feature.rb
195
+ - lib/rodauth/rails/flash.rb
196
196
  - lib/rodauth/rails/middleware.rb
197
197
  - lib/rodauth/rails/railtie.rb
198
198
  - lib/rodauth/rails/tasks.rake
@@ -1,50 +0,0 @@
1
- module Rodauth
2
- module Rails
3
- class App
4
- # Sets up Rails' flash integration.
5
- module Flash
6
- def self.load_dependencies(app)
7
- app.plugin :hooks
8
- end
9
-
10
- def self.configure(app)
11
- app.before { request.flash } # load flash
12
- app.after { request.commit_flash } # save flash
13
- end
14
-
15
- module InstanceMethods
16
- def flash
17
- request.flash
18
- end
19
- end
20
-
21
- module RequestMethods
22
- # If the redirect would bubble up outside of the Roda app, the after
23
- # hook would never get called, so we make sure to commit the flash.
24
- def redirect(*)
25
- commit_flash
26
- super
27
- end
28
-
29
- def flash
30
- rails_request.flash
31
- end
32
-
33
- def commit_flash
34
- if ActionPack.version >= Gem::Version.new("5.0")
35
- rails_request.commit_flash
36
- else
37
- # ActionPack 4.2 automatically commits flash
38
- end
39
- end
40
-
41
- private
42
-
43
- def rails_request
44
- ActionDispatch::Request.new(env)
45
- end
46
- end
47
- end
48
- end
49
- end
50
- end