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 +4 -4
- data/LICENSE.txt +21 -0
- data/README.md +188 -0
- data/lib/repost/extend_controller.rb +12 -7
- data/lib/repost/senpai.rb +40 -23
- data/lib/repost/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c72321c2698ca6688271f051495bae51c38f6ab3395833dba654314a33c5e942
|
|
4
|
+
data.tar.gz: a24252efb08f6e194628c033c06acf61d28ef18ae075b25f294149e8b0da9412
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
+
[](https://badge.fury.io/rb/repost)
|
|
17
|
+
[](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
|
+
[](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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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:
|
|
16
|
-
autosubmit_nonce:
|
|
20
|
+
authenticity_token: authenticity_token,
|
|
21
|
+
autosubmit_nonce: autosubmit_nonce
|
|
17
22
|
}.compact)
|
|
18
23
|
)
|
|
19
24
|
|
|
20
|
-
render html:
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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="#{
|
|
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
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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-#{
|
|
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
|
data/lib/repost/version.rb
CHANGED
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.
|
|
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-
|
|
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:
|
|
71
|
+
version: 2.7.0
|
|
69
72
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
73
|
requirements:
|
|
71
74
|
- - ">="
|