action_controller-stashed_redirects 0.1.0 → 0.2.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57726ac99490f85375fd07300740792ad9ce85efda8c4eb728794c4243391636
4
- data.tar.gz: 2a3059590ed5e4032477d5600304668a5af6c4b4b55a51a31a3246a59be64e5e
3
+ metadata.gz: cf64ad8ee2fb60526d8517bb26be521de7952bcfd87ed860bfe5ce2fe8a0deb0
4
+ data.tar.gz: ce4550656d8f30fc4629f72329229542c457403e95803177260fbe575e9ab51a
5
5
  SHA512:
6
- metadata.gz: bf260715badd07d6a091254fecbc3890ff21b69317fd0c05c8d50971a18f47be87a182da0f9ea6ffe2c4140d71b101f30003c24fe4ab28feb8671945aa260def
7
- data.tar.gz: 949bf794275255b14aeb5b7e46e6f0558218cf736eb7a1b686063c02af2c545128e8dc55f2e0984a097b844bb8f0dcc7ea89575e67181b1a06793f239d40294a
6
+ metadata.gz: f4188865c205f58bf080d879f50ad818716bac84145b568db85324ea5cbb1aa0f2afe1df87a8e9f2cb29be276ed66dfdceb9ca359e58292e7f11cfff5163c76e
7
+ data.tar.gz: 2ab7965676ca7d50f881f26ae8fb5bdac89624a02642303feeab5db5191065d73aa7af578533d98fa7b61eb72f8d34c33e8c2f6ad686aaf2a44d07ba32659f75
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2022-04-21
4
+
5
+ - `stash_redirect_for` raises `ArgumentError` on invalid redirect URL
6
+
7
+ Protects against storing a URL that `redirect_to` can't redirect to later.
8
+
9
+ - `redirect_from_stashed` raises `ActionController::StashedRedirects::MissingRedirectError`
10
+
11
+ Useful to add a specific general fallback:
12
+
13
+ ```ruby
14
+ class ApplicationController < ActionController::Base
15
+ rescue_from(ActionController::StashedRedirects::MissingRedirectError) { redirect_to root_url }
16
+ end
17
+ ```
18
+
3
19
  ## [0.1.0] - 2022-04-21
4
20
 
5
21
  - Initial release
data/Gemfile CHANGED
@@ -8,5 +8,5 @@ gemspec
8
8
  gem "rake", "~> 13.0"
9
9
 
10
10
  gem "debug"
11
- gem "minitest", "~> 5.0"
11
+ gem "minitest"
12
12
  gem "railties"
data/Gemfile.lock CHANGED
@@ -1,89 +1,119 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- action_controller-stashed_redirects (0.1.0)
4
+ action_controller-stashed_redirects (0.2.2)
5
5
  actionpack (>= 7.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- actionpack (7.0.2.3)
11
- actionview (= 7.0.2.3)
12
- activesupport (= 7.0.2.3)
13
- rack (~> 2.0, >= 2.2.0)
10
+ actionpack (7.1.3.4)
11
+ actionview (= 7.1.3.4)
12
+ activesupport (= 7.1.3.4)
13
+ nokogiri (>= 1.8.5)
14
+ racc
15
+ rack (>= 2.2.4)
16
+ rack-session (>= 1.0.1)
14
17
  rack-test (>= 0.6.3)
15
- rails-dom-testing (~> 2.0)
16
- rails-html-sanitizer (~> 1.0, >= 1.2.0)
17
- actionview (7.0.2.3)
18
- activesupport (= 7.0.2.3)
18
+ rails-dom-testing (~> 2.2)
19
+ rails-html-sanitizer (~> 1.6)
20
+ actionview (7.1.3.4)
21
+ activesupport (= 7.1.3.4)
19
22
  builder (~> 3.1)
20
- erubi (~> 1.4)
21
- rails-dom-testing (~> 2.0)
22
- rails-html-sanitizer (~> 1.1, >= 1.2.0)
23
- activesupport (7.0.2.3)
23
+ erubi (~> 1.11)
24
+ rails-dom-testing (~> 2.2)
25
+ rails-html-sanitizer (~> 1.6)
26
+ activesupport (7.1.3.4)
27
+ base64
28
+ bigdecimal
24
29
  concurrent-ruby (~> 1.0, >= 1.0.2)
30
+ connection_pool (>= 2.2.5)
31
+ drb
25
32
  i18n (>= 1.6, < 2)
26
33
  minitest (>= 5.1)
34
+ mutex_m
27
35
  tzinfo (~> 2.0)
28
- builder (3.2.4)
29
- concurrent-ruby (1.1.10)
36
+ base64 (0.2.0)
37
+ bigdecimal (3.1.8)
38
+ builder (3.3.0)
39
+ concurrent-ruby (1.3.3)
40
+ connection_pool (2.4.1)
30
41
  crass (1.0.6)
31
- debug (1.5.0)
32
- irb (>= 1.3.6)
33
- reline (>= 0.2.7)
34
- erubi (1.10.0)
35
- i18n (1.10.0)
42
+ debug (1.9.2)
43
+ irb (~> 1.10)
44
+ reline (>= 0.3.8)
45
+ drb (2.2.1)
46
+ erubi (1.13.0)
47
+ i18n (1.14.5)
36
48
  concurrent-ruby (~> 1.0)
37
- io-console (0.5.11)
38
- irb (1.4.1)
39
- reline (>= 0.3.0)
40
- loofah (2.16.0)
49
+ io-console (0.7.2)
50
+ irb (1.13.2)
51
+ rdoc (>= 4.0.0)
52
+ reline (>= 0.4.2)
53
+ loofah (2.22.0)
41
54
  crass (~> 1.0.2)
42
- nokogiri (>= 1.5.9)
43
- method_source (1.0.0)
44
- minitest (5.15.0)
45
- nokogiri (1.13.4-aarch64-linux)
55
+ nokogiri (>= 1.12.0)
56
+ minitest (5.24.1)
57
+ mutex_m (0.2.0)
58
+ nokogiri (1.16.6-aarch64-linux)
46
59
  racc (~> 1.4)
47
- nokogiri (1.13.4-arm64-darwin)
60
+ nokogiri (1.16.6-arm-linux)
48
61
  racc (~> 1.4)
49
- nokogiri (1.13.4-x86-linux)
62
+ nokogiri (1.16.6-arm64-darwin)
50
63
  racc (~> 1.4)
51
- nokogiri (1.13.4-x86_64-linux)
64
+ nokogiri (1.16.6-x86-linux)
52
65
  racc (~> 1.4)
53
- racc (1.6.0)
54
- rack (2.2.3)
55
- rack-test (1.1.0)
56
- rack (>= 1.0, < 3)
57
- rails-dom-testing (2.0.3)
58
- activesupport (>= 4.2.0)
66
+ nokogiri (1.16.6-x86_64-linux)
67
+ racc (~> 1.4)
68
+ psych (5.1.2)
69
+ stringio
70
+ racc (1.8.0)
71
+ rack (3.1.6)
72
+ rack-session (2.0.0)
73
+ rack (>= 3.0.0)
74
+ rack-test (2.1.0)
75
+ rack (>= 1.3)
76
+ rackup (2.1.0)
77
+ rack (>= 3)
78
+ webrick (~> 1.8)
79
+ rails-dom-testing (2.2.0)
80
+ activesupport (>= 5.0.0)
81
+ minitest
59
82
  nokogiri (>= 1.6)
60
- rails-html-sanitizer (1.4.2)
61
- loofah (~> 2.3)
62
- railties (7.0.2.3)
63
- actionpack (= 7.0.2.3)
64
- activesupport (= 7.0.2.3)
65
- method_source
83
+ rails-html-sanitizer (1.6.0)
84
+ loofah (~> 2.21)
85
+ nokogiri (~> 1.14)
86
+ railties (7.1.3.4)
87
+ actionpack (= 7.1.3.4)
88
+ activesupport (= 7.1.3.4)
89
+ irb
90
+ rackup (>= 1.0.0)
66
91
  rake (>= 12.2)
67
- thor (~> 1.0)
68
- zeitwerk (~> 2.5)
69
- rake (13.0.6)
70
- reline (0.3.1)
92
+ thor (~> 1.0, >= 1.2.2)
93
+ zeitwerk (~> 2.6)
94
+ rake (13.2.1)
95
+ rdoc (6.7.0)
96
+ psych (>= 4.0.0)
97
+ reline (0.5.9)
71
98
  io-console (~> 0.5)
72
- thor (1.2.1)
73
- tzinfo (2.0.4)
99
+ stringio (3.1.1)
100
+ thor (1.3.1)
101
+ tzinfo (2.0.6)
74
102
  concurrent-ruby (~> 1.0)
75
- zeitwerk (2.5.4)
103
+ webrick (1.8.1)
104
+ zeitwerk (2.6.16)
76
105
 
77
106
  PLATFORMS
78
107
  arm64-darwin-20
108
+ arm64-darwin-23
79
109
  linux
80
110
 
81
111
  DEPENDENCIES
82
112
  action_controller-stashed_redirects!
83
113
  debug
84
- minitest (~> 5.0)
114
+ minitest
85
115
  railties
86
116
  rake (~> 13.0)
87
117
 
88
118
  BUNDLED WITH
89
- 2.3.11
119
+ 2.5.14
data/README.md CHANGED
@@ -12,13 +12,14 @@ class ApplicationController < ActionController::Base
12
12
 
13
13
  private
14
14
  def authenticate
15
- redirect_to new_session_url unless Current.user
15
+ # Pass `redirect_url:` to pass the URL we're currently on.
16
+ redirect_to new_session_url(redirect_url: request.url) unless Current.user
16
17
  end
17
18
  end
18
19
 
19
20
  class SessionsController < ApplicationController
20
21
  # Stash a redirect at the start of the session authentication flow,
21
- # from either params[:redirect_url] or request.referer in that order.
22
+ # from `params[:redirect_url]` automatically.
22
23
  stash_redirect_for :sign_in, on: :new
23
24
 
24
25
  def new
@@ -28,6 +29,8 @@ class SessionsController < ApplicationController
28
29
  if User.authenticate_by(session_params)
29
30
  # On success, redirect the user back to where they first tried to access before being authenticated.
30
31
  redirect_from_stashed :sign_in
32
+ else
33
+ render :new, status: :unprocessable_entity
31
34
  end
32
35
  end
33
36
  end
@@ -37,6 +40,107 @@ See the internal documentation for more usage information.
37
40
 
38
41
  Only internal redirects are allowed, so attackers can't pass an external `redirect_url`.
39
42
 
43
+ ### Making a sudo authentication system
44
+
45
+ Consider a flow where you want to require super-user, or sudo, privileges for a given action, e.g. type in your password before you can change your credit card.
46
+
47
+ We'll make a `require_sudo` API that we can annotate our controller with like this:
48
+
49
+ ```ruby
50
+ class Billing::CreditCardsController < ApplicationController
51
+ require_sudo # Require sudo on all actions in this controller.
52
+ # require_sudo_on :edit, :update # Or just for some actions.
53
+
54
+ def edit
55
+ end
56
+
57
+ def update
58
+ Current.user.billing.credit_cards.find(params[:id]).update!(credit_card_params)
59
+ end
60
+ end
61
+ ```
62
+
63
+ `require_sudo` or `require_sudo_on` can come from a controller concern like this:
64
+
65
+ ```ruby
66
+ # app/controllers/concerns/sudo/examination.rb
67
+ module Sudo::Examination
68
+ def self.included(klass) = klass.singleton_class.class_eval do
69
+ def require_sudo_on(*actions, **) = require_sudo(only: *actions, **)
70
+ def require_sudo(...) = before_action(:require_sudo, ...)
71
+ end
72
+
73
+ private
74
+ def require_sudo
75
+ if sudo.exam_needed?
76
+ raise "Non-get: can't redirect back here, make sure you do …something with an interstitial page?" unless request.get?
77
+ redirect_to new_sudo_exams_url(redirect_url: request.url)
78
+ end
79
+ end
80
+
81
+ def sudo = Sudo.new(session)
82
+ end
83
+
84
+ # Which we include in ApplicationController:
85
+ class ApplicationController < ActionController::Base
86
+ include Sudo::Examination
87
+ end
88
+ ```
89
+
90
+ Notice how in `redirect_to new_sudo_exams_url(redirect_url: request.url)` we're passing the `redirect_url:` along that `ActionController::StashedRedirects` will need.
91
+ It's pointing back to the page we're on, which required sudo authentication, so we can redirect back to it after the sudo exam has been passed.
92
+
93
+ Next up, we can add an in-memory PORO model to give the behavior some better names:
94
+
95
+ ```ruby
96
+ # app/models/sudo.rb
97
+ class Sudo < Data.define(:store)
98
+ def passed!
99
+ store[:sudo_expires_at] = 15.minutes.from_now
100
+ end
101
+
102
+ def exam_needed?
103
+ expires_at = store[:sudo_expires_at]
104
+ expires_at.nil? || Time.parse(expires_at).past?
105
+ end
106
+ end
107
+ ```
108
+
109
+ Next, we can add the authenticating sudo controller itself, where `stash_redirect_for` will use the `redirect_url:` from earlier:
110
+
111
+ ```ruby
112
+ # app/controllers/sudo/exams_controller.rb
113
+ class Sudo::ExamsController < ApplicationController
114
+ stash_redirect_for :sudo, on: :new
115
+
116
+ def new
117
+ redirect_from_stashed :sudo unless sudo.exam_needed?
118
+ end
119
+
120
+ def create
121
+ if pass_sudo_exam?
122
+ sudo.passed!
123
+ redirect_from_stashed :sudo
124
+ else
125
+ render :new, status: :unprocessable_entity
126
+ end
127
+ end
128
+ private def pass_sudo_exam? = Current.user.authenticate_password(params[:password])
129
+ end
130
+ ```
131
+
132
+ Finally, we mount the routes for the controller:
133
+
134
+ ```ruby
135
+ # config/routes.rb
136
+ namespace :sudo do
137
+ resources :exams
138
+ end
139
+ ```
140
+
141
+ Users can now fill-in their password, which will hit `sudo/exams#create` and redirect them back to the edit form on the
142
+ credit cards flow if it's the correct password.
143
+
40
144
  ## Installation
41
145
 
42
146
  Install the gem and add to the application's Gemfile by executing:
@@ -55,7 +159,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
55
159
 
56
160
  ## Contributing
57
161
 
58
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/action_controller-stashed_redirects.
162
+ Bug reports and pull requests are welcome on GitHub at https://github.com/kaspth/action_controller-stashed_redirects.
59
163
 
60
164
  ## License
61
165
 
data/Rakefile CHANGED
@@ -1,12 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- require "rake/testtask"
5
-
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << "test"
8
- t.libs << "lib"
9
- t.test_files = FileList["test/**/test_*.rb"]
10
- end
11
-
12
- task default: :test
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = "Embed a controller flow within another by stashing the final redirect upfront and performing it after completing."
12
12
  spec.homepage = "https://github.com/kaspth/action_controller-stashed_redirects"
13
13
  spec.license = "MIT"
14
- spec.required_ruby_version = ">= 2.7.0"
14
+ spec.required_ruby_version = ">= 3.0.0"
15
15
 
16
16
  spec.metadata["homepage_uri"] = spec.homepage
17
17
  spec.metadata["source_code_uri"] = spec.homepage
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActionController
4
4
  module StashedRedirects
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.2"
6
6
  end
7
7
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support"
4
- require_relative "stashed_redirects/version"
5
4
 
6
5
  # Pass between different controller flows via stashed redirects
7
6
  #
@@ -9,67 +8,99 @@ require_relative "stashed_redirects/version"
9
8
  module ActionController::StashedRedirects
10
9
  extend ActiveSupport::Concern
11
10
 
12
- class_methods do
13
- # Adds a before_action to stash a redirect for a given `on:` action.
14
- #
15
- # stash_redirect_for :sudo_authentication, on: :new
16
- # stash_redirect_for :sign_in, from: :referer, on: :new
17
- # stash_redirect_for :sign_in, from: -> { update_post_path(@post) }
18
- def stash_redirect_for(purpose, on:, from: nil)
19
- before_action(-> { stash_redirect_for(purpose, from: from.respond_to?(:call) ? instance_exec(&from) : from) }, only: on)
11
+ autoload :VERSION, "action_controller/stashed_redirects/version"
12
+
13
+ # Allow a general `rescue ActionController::StashedRedirects::Error`.
14
+ Error = Module.new
15
+
16
+ class MissingRedirectError < StandardError
17
+ include Error
18
+
19
+ attr_reader :purpose
20
+
21
+ def initialize(purpose)
22
+ super "can't extract a stashed redirect_url to redirect_to"
23
+ @purpose = purpose
20
24
  end
21
25
  end
22
26
 
23
- # Stashes a redirect URL in the `session` under the given +purpose+.
24
- #
25
- # An explicit +redirect_url+ can be passsed, otherwise the redirect URL is
26
- # derived from `params[:redirect_url]` then falling back to `request.referer`.
27
- #
28
- # stash_redirect_for :sign_in
29
- # stash_redirect_for :sign_in, from: url_from(params[:redirect_url]) || root_url
30
- # stash_redirect_for :sign_in, from: :param # Only derive the redirect URL from `params[:redirect_url]`.
31
- # stash_redirect_for :sign_in, from: :referer # Only derive the redirect URL from `request.referer`.
32
- def stash_redirect_for(purpose, from: nil)
33
- if url = derive_stash_redirect_url_from(from)
34
- session[KEY_GENERATOR.(purpose)] = url
35
- else
36
- raise ArgumentError, "missing a redirect_url to stash, pass one via from: or via a redirect_url URL param"
27
+ class_methods do
28
+ # Adds a `before_action` to stash a redirect in a given `on:` action.
29
+ #
30
+ # stash_redirect_for :sign_in, on: :new
31
+ # stash_redirect_for :sign_in, on: %i[ new edit ]
32
+ # stash_redirect_for :sign_in, on: :new, url: -> { update_post_path(@post) }
33
+ def stash_redirect_for(purpose, on:, url: DEFAULT_URL)
34
+ before_action(-> { stash_redirect_for(purpose, url: url) }, only: on)
37
35
  end
38
36
  end
39
37
 
40
- # Finds and deletes the redirect stashed in `session` under the given +purpose+, then redirects.
41
- #
42
- # redirect_from_stashed :login
43
- #
44
- # Raises if no stashed redirect is found under the given +purpose+.
45
- #
46
- # Relies on +redirect_to+'s open redirect protection, see it's documentation for more.
47
- def redirect_from_stashed(purpose)
48
- redirect_to stashed_redirect_url_for(purpose)
49
- end
38
+ private
39
+ # Stashes a redirect URL in the `session` under the given +purpose+.
40
+ #
41
+ # An explicit +redirect_url+ can be passed in `from:`, otherwise the redirect URL is
42
+ # derived from `params[:redirect_url]` then falling back to `request.referer` on GET requests.
43
+ #
44
+ # stash_redirect_for :sign_in
45
+ # stash_redirect_for :sign_in, from: url_from(params[:redirect_url]) || root_url
46
+ # stash_redirect_for :sign_in, from: :param # Only derive the redirect URL from `params[:redirect_url]`.
47
+ # stash_redirect_for :sign_in, from: :referer # Only derive the redirect URL from `request.referer`.
48
+ def stash_redirect_for(purpose, url: DEFAULT_URL)
49
+ if url = derive_stash_redirect_url_from(url)
50
+ session[KEY_GENERATOR.(purpose)] = url
51
+ else
52
+ raise ArgumentError, "missing a redirect_url to stash, pass one via from: or via a redirect_url URL param"
53
+ end
54
+ end
50
55
 
51
- # Deletes the redirect stashed in the `session` under the given +purpose+ and returns it if any.
52
- #
53
- # discard_stashed_redirect_for :login # => the login redirect URL or nil.
54
- def discard_stashed_redirect_for(purpose)
55
- session.delete(KEY_GENERATOR.(purpose))
56
- end
56
+ # Finds and deletes the redirect stashed in `session` under the given +purpose+, then redirects.
57
+ #
58
+ # redirect_from_stashed :sign_in
59
+ #
60
+ # Raises if no stashed redirect is found under the given +purpose+.
61
+ #
62
+ # Relies on +redirect_to+'s open redirect protection, see it's documentation for more.
63
+ def redirect_from_stashed(purpose)
64
+ redirect_to stashed_redirect_url_for(purpose)
65
+ end
57
66
 
58
- private
59
- KEY_GENERATOR = ->(purpose) { "__url_stash_#{purpose}" }
60
- private_constant :KEY_GENERATOR
67
+ # Deletes and returns the redirect stashed in the `session` under the given +purpose+ if any.
68
+ #
69
+ # discard_stashed_redirect_for :sign_in # => the sign_in redirect URL or nil.
70
+ def discard_stashed_redirect_for(purpose)
71
+ session.delete(KEY_GENERATOR.(purpose))
72
+ end
61
73
 
62
74
  def stashed_redirect_url_for(purpose)
63
- raise ArgumentError, "can't extract a stashed redirect_url from session, none found" \
64
- unless redirect_url = discard_stashed_redirect_for(purpose)
75
+ url_from(discard_stashed_redirect_for(purpose)) or raise MissingRedirectError, purpose
76
+ end
65
77
 
66
- url_from(redirect_url)
78
+ def derive_stash_redirect_url_from(url)
79
+ case url
80
+ when DEFAULT_URL then redirect_url
81
+ when String then url_from url
82
+ when Symbol, Proc then url_from instance_exec(self, &url)
83
+ end
67
84
  end
68
85
 
69
- def derive_stash_redirect_url_from(from)
70
- from ||= %i[ param referer ]
71
- { param: params[:redirect_url], referer: request.get? && request.referer }.values_at(*from).find(&:present?) || from
86
+ # Syntactic sugar for redirecting to `redirect_url` with a fallback.
87
+ #
88
+ # redirect_forward_or_to root_url # => redirect_to redirect_url || root_url
89
+ def redirect_forward_or_to(fallback_url)
90
+ redirect_to redirect_url || fallback_url
72
91
  end
92
+
93
+ # Looks up a redirect URL from `params[:redirect_url]` using
94
+ # Rails' `url_from` as the protection mechanism to ensure it's a valid internal redirect.
95
+ # See the `url_from` docs: https://api.rubyonrails.org/classes/ActionController/Redirecting.html#method-i-url_from
96
+ #
97
+ # You probably want to use `redirect_forward_or_to`.
98
+ def redirect_url = url_from(params[:redirect_url])
99
+
100
+ DEFAULT_URL = Object.new
101
+
102
+ KEY_GENERATOR = ->(purpose) { "__url_stash_#{purpose}" }
103
+ private_constant :KEY_GENERATOR
73
104
  end
74
105
 
75
106
  ActiveSupport.on_load(:action_controller) { include ActionController::StashedRedirects }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_controller-stashed_redirects
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kasper Timm Hansen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-21 00:00:00.000000000 Z
11
+ date: 2024-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -55,14 +55,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
55
55
  requirements:
56
56
  - - ">="
57
57
  - !ruby/object:Gem::Version
58
- version: 2.7.0
58
+ version: 3.0.0
59
59
  required_rubygems_version: !ruby/object:Gem::Requirement
60
60
  requirements:
61
61
  - - ">="
62
62
  - !ruby/object:Gem::Version
63
63
  version: '0'
64
64
  requirements: []
65
- rubygems_version: 3.3.11
65
+ rubygems_version: 3.5.10
66
66
  signing_key:
67
67
  specification_version: 4
68
68
  summary: Embed a controller flow within another by stashing the final redirect upfront