repost 0.5.0 → 0.5.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: 7399517ab4d8add105196f6d329ede828d10f90f5fa3eb47e79c645d8f8269c2
4
- data.tar.gz: 18dbb378fddea7a84897dac996a5561ad2627496250d331a8c3a20500aa3b45c
3
+ metadata.gz: c72321c2698ca6688271f051495bae51c38f6ab3395833dba654314a33c5e942
4
+ data.tar.gz: a24252efb08f6e194628c033c06acf61d28ef18ae075b25f294149e8b0da9412
5
5
  SHA512:
6
- metadata.gz: c4caed61603b62417cb1367fc8e162758ba134739f84c6ba5dc3c097d010c38c6024a3d3efe3b9e00461de5a761a6e99f387874b0d840532e5fa9dbd8811e6d7
7
- data.tar.gz: ad8fe848e483398718e7214744c6f207b75079a89f5644b969b0617c9eb99c4bbe5902f6ada82d19805082342420322547bac638bbb70d91f20fc9c1d29e4c76
6
+ metadata.gz: 5cdc9fc605bf4358f9105a51d341d8b0bcf50f81e61787730fef1a9338e9ebaccb91f32ec795d570f9e7db08cf05ee57e8d413c2161444c24cbafe005c84e899
7
+ data.tar.gz: 29850c217d7e15f1ef4df3a9eda4d772a98a7a728ace1969c18b52ef68676b338fd2c1df137f439c400a29250dcdbce42c47bc9cfff1856dcb28810d6f585c8c
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 YaroslavO
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,188 @@
1
+ <p align="right">
2
+ <a href="https://github.com/vergilet/repost"><img align="" src="https://user-images.githubusercontent.com/2478436/51829223-cb05d600-22f5-11e9-9245-bc6e82dcf028.png" width="56" height="56" /></a>
3
+ <a href="https://rubygems.org/gems/repost"><img align="right" src="https://user-images.githubusercontent.com/2478436/51829691-c55cc000-22f6-11e9-99a5-42f88a8f2a55.png" width="56" height="56" /></a>
4
+ </p>
5
+
6
+
7
+ <p align="center">
8
+ <a href="https://rubygems.org/gems/repost">
9
+ <img width="460" src="https://user-images.githubusercontent.com/2478436/55672583-44491880-58a5-11e9-945c-939f90470df8.png"></a>
10
+ </p>
11
+
12
+ Gem **Repost** implements Redirect using POST method.
13
+
14
+ Implementation story and some details in the following article [Redirect using POST in Rails](https://medium.com/@momlookhowican/redirect-using-post-in-rails-5748da354343).
15
+
16
+ [![Gem Version](https://badge.fury.io/rb/repost.svg)](https://badge.fury.io/rb/repost)
17
+ [![Build Status](https://travis-ci.com/vergilet/repost.svg?branch=master)](https://app.travis-ci.com/github/vergilet/repost)
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'repost'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ $ bundle
30
+
31
+ Or install it yourself as:
32
+
33
+ $ gem install repost
34
+
35
+
36
+
37
+ ## What problem does it solve?
38
+
39
+ When you need to send some parameters to an endpoint which should redirect you after execution. There wouldn't be a problem if an endpoint receives [GET], because you can just use:
40
+ ```ruby
41
+ redirect_to entity_url(id: @model.id, token: model.token...)
42
+ ```
43
+
44
+
45
+ But when an endpoint receives [POST], you have to generate html form and submit it. So `repost` gem helps to avoid creation of additional view with html form, just use `redirect_post` method instead.
46
+ I faced with this problem when was dealing with bank transactions. You can see the approximate scheme:
47
+
48
+ <p align="center">
49
+ <a href="https://user-images.githubusercontent.com/2478436/55143646-d0da3500-5147-11e9-91a3-1bac9d560fb2.png">
50
+ <img src="https://user-images.githubusercontent.com/2478436/55143646-d0da3500-5147-11e9-91a3-1bac9d560fb2.png"></a>
51
+ </p>
52
+
53
+ > **P.S. The `repost` gem was initially created in response to the redirection process required by [Adyen 3D Secure 1](https://docs.adyen.com/online-payments/classic-integrations/api-integration-ecommerce/3d-secure/3d-secure-1/#step-2-redirect-to-the-card-issuer), which often involved creating an HTML form and submitting it via POST. <br>
54
+ However, with the advent of 3D Secure 2, which aims for a more integrated authentication experience, the use of such forms for POST submissions has not been encountered. 3D Secure 2 typically manages authentication data exchanges in the background, potentially eliminating the need for manual form submission.**
55
+
56
+
57
+ ## Usage
58
+
59
+ If you use Rails, gem automatically includes helper methods to your controllers:
60
+
61
+ ```ruby
62
+ repost(...)
63
+ ```
64
+ and, as an alias
65
+
66
+ ```ruby
67
+ redirect_post(...)
68
+ ```
69
+
70
+ *Under the hood it calls `render` method of current controller with `html:`.*
71
+
72
+ ### Example in Rails app:
73
+
74
+ ```ruby
75
+ class MyController < ApplicationController
76
+ ...
77
+ def index
78
+ repost(...)
79
+ end
80
+ ...
81
+ # or
82
+ def show
83
+ redirect_post(...)
84
+ end
85
+ end
86
+ ```
87
+ ______________
88
+
89
+ If you use Sinatra, Roda or etc., you need to require it first somewhere in you project:
90
+
91
+ ```ruby
92
+ require 'repost'
93
+ ```
94
+
95
+ Then ask senpai to generate a string with html:
96
+
97
+
98
+ ```ruby
99
+ Repost::Senpai.perform(...)
100
+ ```
101
+
102
+ ### Example in Sinatra, Roda, etc. app:
103
+
104
+ ```ruby
105
+ class MyController < Sinatra::Base
106
+ get '/' do
107
+ Repost::Senpai.perform(...)
108
+ end
109
+ end
110
+ ```
111
+
112
+
113
+
114
+ #### *Reminder:*
115
+
116
+ - *In Rails app use `repost` or `redirect_post` method in your controller which performs 'redirect' when it is called.*
117
+
118
+ - *In Sinatra, Roda, etc. app or if you need html output - call Senpai*
119
+
120
+
121
+ #### Full example:
122
+
123
+ *UPD: authenticity token is **turned off** by default. Use `:auto` or `'auto'` to turn on default authenticity token from Rails. Any other string value would be treated as custom auth token value.*
124
+
125
+ ```ruby
126
+ # plain ruby
127
+ # Repost::Senpai.perform('http://......)
128
+
129
+
130
+ # Rails
131
+ redirect_post('http://examp.io/endpoint', # URL, looks understandable
132
+ params: {
133
+ a: 1,
134
+ 'b': { "c": 2 },
135
+ d: [ 3, 4, 5 ],
136
+ e: { f: 'string', g: [ 6, 7, 8 ] }
137
+ }, # Your request body, also nested params and arrays
138
+ options: {
139
+ method: :post, # OPTIONAL - DEFAULT is :post. :put/:patch/:delete are sent as POST with a hidden "_method" field (Rails-style override)
140
+ status: :ok, # OPTIONAL - DEFAULT is :ok. This is the http status that the form will be returned with.
141
+ authenticity_token: 'auto', # OPTIONAL - :auto or 'auto' for Rails form_authenticity_token, string - custom token
142
+ charset: 'Windows-1251', # OPTIONAL - DEFAULT is "UTF-8", corresponds for accept-charset
143
+ form_id: 'CustomFormID', # OPTIONAL - DEFAULT is autogenerated
144
+ autosubmit: false, # OPTIONAL - DEFAULT is true, if you want to get a confirmation for redirect
145
+ autosubmit_nonce: '1d3n7i4ier', # RAILS - DEFAULT is content_security_policy_nonce, for pure Ruby - string identifier, more info - https://edgeguides.rubyonrails.org/security.html#content-security-policy
146
+ decor: { # If autosubmit is turned off or Javascript is disabled on client
147
+ section: { # ... you can decorate confirmation section and button
148
+ classes: 'red-bg red-text', # OPTIONAL - <DIV> section, set classNames, separate with space
149
+ html: '<h1>Press this button, dude!</h1>' # OPTIONAL - Any html, which will appear before submit button. Rendered as-is (NOT escaped) - never pass user input here
150
+ },
151
+ submit: {
152
+ classes: 'button-decorated round-border', # OPTIONAL - <Input> with type submit, set classNames, separate with space
153
+ text: 'c0n71nue ...' # OPTIONAL - DEFAULT is 'Continue'
154
+ }
155
+ }
156
+ }
157
+ )
158
+
159
+ ```
160
+
161
+ ### Authenticity Token (Rails)
162
+
163
+ Currently you can pass the **authenticity token** in two ways:
164
+
165
+ * Recommended:
166
+
167
+ *Use `options` and `:auto` to pass the auth token. That should protect you from any implementation changes in future Rails versions*
168
+
169
+ ```ruby
170
+ redirect_post('https://exmaple.io/endpoint', options: {authenticity_token: :auto})
171
+ ```
172
+ * Or, it is still valid to:
173
+
174
+ *use `params` and `form_authenticity_token` method directly from ActionController*
175
+ ```ruby
176
+ redirect_post('https://exmaple.io/endpoint', params: {authenticity_token: form_authenticity_token})
177
+ ```
178
+
179
+
180
+
181
+ ## License
182
+ The gem is available as open source under the terms of the MIT License.
183
+
184
+ Copyright © 2019 Yaro.
185
+
186
+ [![GitHub license](https://img.shields.io/badge/license-MIT-brightgreen)](https://raw.githubusercontent.com/vergilet/repost/master/LICENSE.txt)
187
+
188
+ **That's all folks.**
@@ -2,22 +2,27 @@ if defined?(Rails) && defined?(ActiveSupport)
2
2
  ActiveSupport.on_load(:action_controller) do
3
3
  class ::ActionController::Base
4
4
  def repost(url, params: {}, options: {})
5
+ options = options.dup
5
6
  status = options.delete(:status) || :ok
6
-
7
- token = if ['auto', :auto].include?(options[:authenticity_token])
8
- form_authenticity_token
9
- end
7
+ authenticity_token = if ['auto', :auto].include?(options[:authenticity_token])
8
+ form_authenticity_token
9
+ else
10
+ options[:authenticity_token]
11
+ end
12
+ # Rails' per-request CSP nonce is the default; an explicitly passed
13
+ # :autosubmit_nonce (even nil, to opt out) takes precedence.
14
+ autosubmit_nonce = options.fetch(:autosubmit_nonce) { content_security_policy_nonce }
10
15
 
11
16
  html_payload = Repost::Senpai.perform(
12
17
  url,
13
18
  params: params,
14
19
  options: options.merge({
15
- authenticity_token: token,
16
- autosubmit_nonce: content_security_policy_nonce,
20
+ authenticity_token: authenticity_token,
21
+ autosubmit_nonce: autosubmit_nonce
17
22
  }.compact)
18
23
  )
19
24
 
20
- render html: ActionController::Base.helpers.sanitize(html_payload), status: status
25
+ render html: html_payload.html_safe, status: status
21
26
  end
22
27
 
23
28
  alias :redirect_post :repost
data/lib/repost/senpai.rb CHANGED
@@ -1,13 +1,19 @@
1
+ require 'cgi'
2
+ require 'securerandom'
3
+
1
4
  module Repost
2
5
  class Senpai < Action
3
6
  DEFAULT_SUBMIT_BUTTON_TEXT = 'Continue'
4
7
  DEFAULT_CHARSET = 'UTF-8'
8
+ # Browsers only submit forms via GET or POST; other verbs are sent as POST
9
+ # with a Rails-style "_method" override field.
10
+ OVERRIDE_METHODS = %w[put patch delete].freeze
5
11
 
6
12
  def initialize(url, params: {}, options: {})
7
13
  @url = url
8
14
  @params = params
9
15
  @options = options
10
- @method = options.fetch(:method, :post)
16
+ @method = options.fetch(:method, :post).to_s.downcase
11
17
  @authenticity_token = options.fetch(:authenticity_token, nil)
12
18
  @charset = options.fetch(:charset, DEFAULT_CHARSET)
13
19
  @form_id = options.fetch(:form_id, generated_form_id)
@@ -21,10 +27,10 @@ module Repost
21
27
 
22
28
  def perform
23
29
  compiled_body = if autosubmit
24
- form_body << auto_submit_script << no_script
25
- else
26
- form_body << submit_section
27
- end
30
+ form_body << auto_submit_script << no_script
31
+ else
32
+ form_body << submit_section
33
+ end
28
34
  form_head << compiled_body << form_footer
29
35
  end
30
36
 
@@ -34,8 +40,20 @@ module Repost
34
40
  :section_classes, :section_html, :submit_classes,
35
41
  :submit_text, :authenticity_token, :charset, :autosubmit_nonce
36
42
 
43
+ def escape(value)
44
+ CGI.escapeHTML(value.to_s)
45
+ end
46
+
37
47
  def form_head
38
- %Q(<form id="#{form_id}" action="#{url}" method="#{method}" accept-charset="#{charset}">)
48
+ %Q(<form id="#{escape(form_id)}" action="#{escape(url)}" method="#{escape(form_method)}" accept-charset="#{escape(charset)}">)
49
+ end
50
+
51
+ def form_method
52
+ method_override? ? 'post' : method
53
+ end
54
+
55
+ def method_override?
56
+ OVERRIDE_METHODS.include?(method)
39
57
  end
40
58
 
41
59
  def form_body
@@ -43,9 +61,14 @@ module Repost
43
61
  form_input(key, value)
44
62
  end
45
63
  inputs.unshift(csrf_token) if authenticity_token
64
+ inputs.unshift(method_override_input) if method_override?
46
65
  inputs.join
47
66
  end
48
67
 
68
+ def method_override_input
69
+ %Q(<input type="hidden" name="_method" value="#{escape(method)}">)
70
+ end
71
+
49
72
  def form_input(key, value)
50
73
  case value
51
74
  when Hash
@@ -57,7 +80,7 @@ module Repost
57
80
  form_input("#{key}[]", inner_value)
58
81
  end.join
59
82
  else
60
- %Q(<input type="hidden" name="#{key}" value=#{process_value(value)}>)
83
+ %Q(<input type="hidden" name="#{escape(key)}" value="#{escape(value)}">)
61
84
  end
62
85
  end
63
86
 
@@ -66,36 +89,30 @@ module Repost
66
89
  end
67
90
 
68
91
  def csrf_token
69
- %Q(<input name="authenticity_token" value="#{authenticity_token}" type="hidden">)
92
+ %Q(<input name="authenticity_token" value="#{escape(authenticity_token)}" type="hidden">)
70
93
  end
71
94
 
72
95
  def no_script
73
- %Q(<noscript>
74
- #{submit_section}
75
- </noscript>)
96
+ %Q(<noscript>#{submit_section}</noscript>)
76
97
  end
77
98
 
78
99
  def submit_section
79
- %Q(<div class="#{section_classes}">
80
- #{section_html}
81
- <input class="#{submit_classes}" type="submit" value="#{submit_text}"></input>
82
- </div>)
100
+ %Q(<div#{class_attr(section_classes)}>#{section_html}<input#{class_attr(submit_classes)} type="submit" value="#{escape(submit_text)}"></div>)
101
+ end
102
+
103
+ def class_attr(classes)
104
+ %Q( class="#{escape(classes)}") if classes
83
105
  end
84
106
 
85
107
  def generated_form_id
86
- "repost-#{Time.now.to_i}"
108
+ "repost-#{SecureRandom.hex(8)}"
87
109
  end
88
110
 
89
111
  def auto_submit_script
90
- nonce_attr = %Q( nonce="#{autosubmit_nonce}") if autosubmit_nonce
112
+ nonce_attr = %Q( nonce="#{escape(autosubmit_nonce)}") if autosubmit_nonce
91
113
  %Q(<script#{nonce_attr}>
92
- document.getElementById("#{form_id}").submit();
114
+ document.getElementById("#{escape(form_id)}").submit();
93
115
  </script>)
94
116
  end
95
-
96
- def process_value(value)
97
- return value if value.is_a?(Integer)
98
- %Q("#{value.to_s.gsub("\"", '\'')}")
99
- end
100
117
  end
101
118
  end
@@ -1,3 +1,3 @@
1
1
  module Repost
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: repost
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - YaroslavO
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-28 00:00:00.000000000 Z
11
+ date: 2026-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -46,6 +46,8 @@ executables: []
46
46
  extensions: []
47
47
  extra_rdoc_files: []
48
48
  files:
49
+ - LICENSE.txt
50
+ - README.md
49
51
  - Rakefile
50
52
  - lib/repost.rb
51
53
  - lib/repost/action.rb
@@ -57,6 +59,7 @@ licenses:
57
59
  - MIT
58
60
  metadata:
59
61
  homepage_uri: https://github.com/vergilet/repost
62
+ source_code_uri: https://github.com/vergilet/repost
60
63
  post_install_message:
61
64
  rdoc_options: []
62
65
  require_paths:
@@ -65,7 +68,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
65
68
  requirements:
66
69
  - - ">="
67
70
  - !ruby/object:Gem::Version
68
- version: '0'
71
+ version: 2.7.0
69
72
  required_rubygems_version: !ruby/object:Gem::Requirement
70
73
  requirements:
71
74
  - - ">="