tynn 2.0.0.alpha → 2.0.0.beta1
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/README.md +168 -29
- data/lib/tynn.rb +0 -4
- data/lib/tynn/base.rb +368 -24
- data/lib/tynn/errors.rb +7 -0
- data/lib/tynn/json.rb +18 -5
- data/lib/tynn/render.rb +36 -25
- data/lib/tynn/request.rb +86 -15
- data/lib/tynn/response.rb +214 -12
- data/lib/tynn/secure_headers.rb +2 -6
- data/lib/tynn/session.rb +14 -5
- data/lib/tynn/ssl.rb +19 -16
- data/lib/tynn/test.rb +45 -38
- data/lib/tynn/utils.rb +15 -0
- data/lib/tynn/version.rb +1 -1
- data/test/default_headers_test.rb +1 -1
- data/test/environment_test.rb +1 -1
- data/test/helper.rb +9 -0
- data/test/json_test.rb +21 -6
- data/test/middleware_test.rb +23 -13
- data/test/plugin_test.rb +1 -1
- data/test/render_test.rb +24 -15
- data/test/request_headers_test.rb +8 -4
- data/test/request_test.rb +9 -0
- data/test/response_test.rb +217 -0
- data/test/routing_test.rb +128 -38
- data/test/secure_headers_test.rb +1 -1
- data/test/session_test.rb +6 -6
- data/test/settings_test.rb +3 -3
- data/test/ssl_test.rb +3 -3
- data/test/static_test.rb +1 -1
- metadata +14 -24
- data/lib/tynn/default_headers.rb +0 -50
- data/lib/tynn/settings.rb +0 -107
data/lib/tynn/secure_headers.rb
CHANGED
@@ -9,10 +9,7 @@ class Tynn
|
|
9
9
|
#
|
10
10
|
# [X-Frame-Options]
|
11
11
|
# Provides {Clickjacking}[https://www.owasp.org/index.php/Clickjacking]
|
12
|
-
# protection. Defaults to <tt>"
|
13
|
-
#
|
14
|
-
# [X-Permitted-Cross-Domain-Policies]
|
15
|
-
# Restricts Adobe Flash Player's access to data. Defaults to <tt>"none"</tt>.
|
12
|
+
# protection. Defaults to <tt>"deny"</tt>.
|
16
13
|
#
|
17
14
|
# [X-XSS-Protection]
|
18
15
|
# Enables the XSS protection filter built into IE, Chrome and Safari.
|
@@ -29,8 +26,7 @@ class Tynn
|
|
29
26
|
module SecureHeaders
|
30
27
|
HEADERS = {
|
31
28
|
"X-Content-Type-Options" => "nosniff",
|
32
|
-
"X-Frame-Options" => "
|
33
|
-
"X-Permitted-Cross-Domain-Policies" => "none",
|
29
|
+
"X-Frame-Options" => "deny",
|
34
30
|
"X-XSS-Protection" => "1; mode=block"
|
35
31
|
}.freeze # :nodoc:
|
36
32
|
|
data/lib/tynn/session.rb
CHANGED
@@ -10,8 +10,8 @@ class Tynn
|
|
10
10
|
# Tynn.plugin(Tynn::Session, secret: "__change_me_not_secure__")
|
11
11
|
#
|
12
12
|
# Tynn.define do
|
13
|
-
# on
|
14
|
-
# post do
|
13
|
+
# on "login" do
|
14
|
+
# on post do
|
15
15
|
# # ...
|
16
16
|
#
|
17
17
|
# session[:user_id] = user.id
|
@@ -46,6 +46,14 @@ class Tynn
|
|
46
46
|
# If <tt>true</tt>, sets the <tt>Secure</tt> flag. This tells the browser
|
47
47
|
# to only transmit the cookie over HTTPS. Defaults to <tt>false</tt>.
|
48
48
|
#
|
49
|
+
# [same_site]
|
50
|
+
# Disables third-party usage for cookies. There are two possible values
|
51
|
+
# <tt>:Lax</tt> and <tt>:Strict</tt>. In <tt>Strict</tt> mode, the cookie
|
52
|
+
# is restrain to any cross-site usage; in <tt>Lax</tt> mode, some cross-site
|
53
|
+
# usage is allowed. Defaults to <tt>:Lax</tt>. If <tt>nil</tt> is passed,
|
54
|
+
# the flag is not included. Check this article[http://www.sjoerdlangkemper.nl/2016/04/14/preventing-csrf-with-samesite-cookie-attribute/]
|
55
|
+
# for more information.
|
56
|
+
#
|
49
57
|
# [expire_after]
|
50
58
|
# The lifespan of the cookie. If <tt>nil</tt>, the session cookie is temporary
|
51
59
|
# and is no retained after the browser is closed. Defaults to <tt>nil</tt>.
|
@@ -58,7 +66,8 @@ class Tynn
|
|
58
66
|
# secret: ENV["SESSION_SECRET"],
|
59
67
|
# expire_after: 36_000, # seconds
|
60
68
|
# httponly: true,
|
61
|
-
# secure: true
|
69
|
+
# secure: true,
|
70
|
+
# same_site: :Strict
|
62
71
|
# )
|
63
72
|
#
|
64
73
|
module Session
|
@@ -68,7 +77,7 @@ class Tynn
|
|
68
77
|
secret = options[:secret]
|
69
78
|
|
70
79
|
if secret.nil?
|
71
|
-
raise <<~MSG
|
80
|
+
raise Tynn::Error, <<~MSG
|
72
81
|
No secret option provided to Tynn::Session.
|
73
82
|
|
74
83
|
Tynn::Session uses a secret token to sign the cookie data, thus
|
@@ -86,7 +95,7 @@ class Tynn
|
|
86
95
|
end
|
87
96
|
|
88
97
|
if secret.length < SECRET_MIN_LENGTH
|
89
|
-
raise <<~MSG
|
98
|
+
raise Tynn::Error, <<~MSG
|
90
99
|
The secret provided is shorter than the minimum length.
|
91
100
|
|
92
101
|
Make sure the secret is long and all random. You can generate a
|
data/lib/tynn/ssl.rb
CHANGED
@@ -13,21 +13,24 @@ class Tynn
|
|
13
13
|
# 3. Setting the <tt>secure</tt> flag on cookies. This tells the browser to
|
14
14
|
# only transmit them over HTTPS.
|
15
15
|
#
|
16
|
-
# You can configure HSTS passing
|
17
|
-
# are supported:
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
# <tt>
|
16
|
+
# You can configure HSTS passing through the <tt>:hsts</tt> option.
|
17
|
+
# The following options are supported:
|
18
|
+
#
|
19
|
+
# [expires]
|
20
|
+
# The time, in seconds, that the browser access the site only by HTTPS.
|
21
|
+
# Defaults to 180 days.
|
22
|
+
#
|
23
|
+
# [subdomains]
|
24
|
+
# If this is <tt>true</tt>, the rule applies to all the site's subdomains as
|
25
|
+
# well. Defaults to <tt>true</tt>.
|
26
|
+
#
|
27
|
+
# [preload]
|
28
|
+
# A limitation of HSTS is that the initial request remains unprotected if it
|
29
|
+
# uses HTTP. The same applies to the first request after the activity period
|
30
|
+
# specified by <tt>max-age</tt>. Modern browsers implements a "HSTS preload
|
31
|
+
# list", which contains known sites supporting HSTS. If you would like to
|
32
|
+
# include your website into the list, set this option to <tt>true</tt> and
|
33
|
+
# submit your domain to this form[https://hstspreload.appspot.com/].
|
31
34
|
# Supported by Chrome, Firefox, IE11+ and IE Edge.
|
32
35
|
#
|
33
36
|
# To disable HSTS, you will need to tell the browser to expire it immediately.
|
@@ -74,7 +77,7 @@ class Tynn
|
|
74
77
|
end
|
75
78
|
|
76
79
|
def call(env)
|
77
|
-
request =
|
80
|
+
request = Tynn::Request.new(env)
|
78
81
|
|
79
82
|
return redirect_to_https(request) unless request.ssl?
|
80
83
|
|
data/lib/tynn/test.rb
CHANGED
@@ -7,16 +7,16 @@ class Tynn
|
|
7
7
|
# require "tynn/test"
|
8
8
|
#
|
9
9
|
# Tynn.define do
|
10
|
-
#
|
10
|
+
# on get do
|
11
11
|
# res.write("Hei!")
|
12
12
|
# end
|
13
13
|
# end
|
14
14
|
#
|
15
|
-
#
|
16
|
-
#
|
15
|
+
# ts = Tynn::Test.new
|
16
|
+
# ts.get("/")
|
17
17
|
#
|
18
|
-
#
|
19
|
-
#
|
18
|
+
# ts.res.status # => 200
|
19
|
+
# ts.res.body.join # => "Hei!"
|
20
20
|
#
|
21
21
|
class Test
|
22
22
|
attr_reader :app # :nodoc:
|
@@ -26,8 +26,8 @@ class Tynn
|
|
26
26
|
# class API < Tynn
|
27
27
|
# end
|
28
28
|
#
|
29
|
-
#
|
30
|
-
#
|
29
|
+
# ts = Tynn::Test.new(API)
|
30
|
+
# ts.get("/user.json")
|
31
31
|
#
|
32
32
|
def initialize(app = Tynn)
|
33
33
|
@app = app
|
@@ -52,40 +52,38 @@ class Tynn
|
|
52
52
|
# end
|
53
53
|
#
|
54
54
|
module Methods
|
55
|
-
# If a request has been issued, returns an instance of
|
56
|
-
# Rack::Request[http://www.rubydoc.info/gems/rack/Rack/Request].
|
55
|
+
# If a request has been issued, returns an instance of Tynn::Request.
|
57
56
|
# Otherwise, returns <tt>nil</tt>.
|
58
57
|
#
|
59
|
-
#
|
60
|
-
#
|
58
|
+
# ts = Tynn::Test.new
|
59
|
+
# ts.get("/", { foo: "foo" }, { "HTTP_USER_AGENT" => "Tynn::Test" })
|
61
60
|
#
|
62
|
-
#
|
61
|
+
# ts.req.get?
|
63
62
|
# # => true
|
64
63
|
#
|
65
|
-
#
|
64
|
+
# ts.req.params["foo"]
|
66
65
|
# # => "foo"
|
67
66
|
#
|
68
|
-
#
|
67
|
+
# ts.req.env["HTTP_USER_AGENT"]
|
69
68
|
# # => "Tynn::Test"
|
70
69
|
#
|
71
70
|
def req
|
72
71
|
@__req
|
73
72
|
end
|
74
73
|
|
75
|
-
# If a request has been issued, returns an instance of
|
76
|
-
# Rack::MockResponse[http://www.rubydoc.info/gems/rack/Rack/MockResponse].
|
74
|
+
# If a request has been issued, returns an instance of Tynn::Response
|
77
75
|
# Otherwise, returns <tt>nil</tt>.
|
78
76
|
#
|
79
|
-
#
|
80
|
-
#
|
77
|
+
# ts = Tynn::Test.new
|
78
|
+
# ts.get("/", name: "Jane")
|
81
79
|
#
|
82
|
-
#
|
80
|
+
# ts.res.status
|
83
81
|
# # => 200
|
84
82
|
#
|
85
|
-
#
|
83
|
+
# ts.res.body.join
|
86
84
|
# # => "Hello Jane!"
|
87
85
|
#
|
88
|
-
#
|
86
|
+
# ts.res.headers["Content-Type"]
|
89
87
|
# # => "text/html"
|
90
88
|
#
|
91
89
|
def res
|
@@ -99,9 +97,9 @@ class Tynn
|
|
99
97
|
# or <tt>nil</tt>.
|
100
98
|
# [env] A Hash of Rack environment values.
|
101
99
|
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
100
|
+
# ts = Tynn::Test.new
|
101
|
+
# ts.get("/search", name: "jane")
|
102
|
+
# ts.get("/cart", {}, { "HTTPS" => "on" })
|
105
103
|
#
|
106
104
|
def get(path, params = {}, env = {})
|
107
105
|
request(path, env.merge(method: "GET", params: params))
|
@@ -109,8 +107,8 @@ class Tynn
|
|
109
107
|
|
110
108
|
# Issues a <tt>POST</tt> request. See #get for more information.
|
111
109
|
#
|
112
|
-
#
|
113
|
-
#
|
110
|
+
# ts = Tynn::Test.new
|
111
|
+
# ts.post("/signup", username: "janedoe", password: "secret")
|
114
112
|
#
|
115
113
|
def post(path, params = {}, env = {})
|
116
114
|
request(path, env.merge(method: "POST", params: params))
|
@@ -118,8 +116,8 @@ class Tynn
|
|
118
116
|
|
119
117
|
# Issues a <tt>PUT</tt> request. See #get for more information.
|
120
118
|
#
|
121
|
-
#
|
122
|
-
#
|
119
|
+
# ts = Tynn::Test.new
|
120
|
+
# ts.put("/users/1", username: "johndoe", name: "John")
|
123
121
|
#
|
124
122
|
def put(path, params = {}, env = {})
|
125
123
|
request(path, env.merge(method: "PUT", params: params))
|
@@ -127,8 +125,8 @@ class Tynn
|
|
127
125
|
|
128
126
|
# Issues a <tt>PATCH</tt> request. See #get for more information.
|
129
127
|
#
|
130
|
-
#
|
131
|
-
#
|
128
|
+
# ts = Tynn::Test.new
|
129
|
+
# ts.patch("/users/1", username: "janedoe")
|
132
130
|
#
|
133
131
|
def patch(path, params = {}, env = {})
|
134
132
|
request(path, env.merge(method: "PATCH", params: params))
|
@@ -136,8 +134,8 @@ class Tynn
|
|
136
134
|
|
137
135
|
# Issues a <tt>DELETE</tt> request. See #get for more information.
|
138
136
|
#
|
139
|
-
#
|
140
|
-
#
|
137
|
+
# ts = Tynn::Test.new
|
138
|
+
# ts.delete("/users/1")
|
141
139
|
#
|
142
140
|
def delete(path, params = {}, env = {})
|
143
141
|
request(path, env.merge(method: "DELETE", params: params))
|
@@ -145,8 +143,8 @@ class Tynn
|
|
145
143
|
|
146
144
|
# Issues a <tt>HEAD</tt> request. See #get for more information.
|
147
145
|
#
|
148
|
-
#
|
149
|
-
#
|
146
|
+
# ts = Tynn::Test.new
|
147
|
+
# ts.head("/users/1")
|
150
148
|
#
|
151
149
|
def head(path, params = {}, env = {})
|
152
150
|
request(path, env.merge(method: Rack::HEAD, params: params))
|
@@ -154,8 +152,8 @@ class Tynn
|
|
154
152
|
|
155
153
|
# Issues a <tt>OPTIONS</tt> request. See #get for more information.
|
156
154
|
#
|
157
|
-
#
|
158
|
-
#
|
155
|
+
# ts = Tynn::Test.new
|
156
|
+
# ts.options("/users")
|
159
157
|
#
|
160
158
|
def options(path, params = {}, env = {})
|
161
159
|
request(path, env.merge(method: "OPTIONS", params: params))
|
@@ -164,8 +162,17 @@ class Tynn
|
|
164
162
|
private
|
165
163
|
|
166
164
|
def request(path, opts = {})
|
167
|
-
@__req =
|
168
|
-
@__res =
|
165
|
+
@__req = Tynn::Request.new(Rack::MockRequest.env_for(path, opts))
|
166
|
+
@__res = make_response(*app.call(@__req.env))
|
167
|
+
end
|
168
|
+
|
169
|
+
def make_response(status, headers, body)
|
170
|
+
res = Tynn::Response.new(headers)
|
171
|
+
res.status = status
|
172
|
+
|
173
|
+
body.each { |b| res.write(b) }
|
174
|
+
|
175
|
+
res
|
169
176
|
end
|
170
177
|
end
|
171
178
|
|
data/lib/tynn/utils.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Tynn
|
4
|
+
module Utils # :nodoc:
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def deepclone_hash(hash)
|
8
|
+
default_proc, hash.default_proc = hash.default_proc, nil
|
9
|
+
|
10
|
+
Marshal.load(Marshal.dump(hash))
|
11
|
+
ensure
|
12
|
+
hash.default_proc = default_proc
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/tynn/version.rb
CHANGED
data/test/environment_test.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -5,3 +5,12 @@ require "minitest/autorun"
|
|
5
5
|
require "minitest/pride"
|
6
6
|
require_relative "../lib/tynn"
|
7
7
|
require_relative "../lib/tynn/test"
|
8
|
+
|
9
|
+
class Minitest::Test
|
10
|
+
def new_app(parent: Tynn, lint: true)
|
11
|
+
app = Class.new(parent)
|
12
|
+
app.use(Rack::Lint) if lint
|
13
|
+
|
14
|
+
app
|
15
|
+
end
|
16
|
+
end
|
data/test/json_test.rb
CHANGED
@@ -5,14 +5,14 @@ require_relative "../lib/tynn/json"
|
|
5
5
|
|
6
6
|
class JSONTest < Minitest::Test
|
7
7
|
def setup
|
8
|
-
@app =
|
8
|
+
@app = new_app
|
9
9
|
end
|
10
10
|
|
11
11
|
def test_respond_json_object
|
12
12
|
@app.plugin(Tynn::JSON)
|
13
13
|
|
14
14
|
@app.define do
|
15
|
-
get do
|
15
|
+
on get do
|
16
16
|
json(foo: "foo")
|
17
17
|
end
|
18
18
|
end
|
@@ -20,7 +20,7 @@ class JSONTest < Minitest::Test
|
|
20
20
|
ts = Tynn::Test.new(@app)
|
21
21
|
ts.get("/")
|
22
22
|
|
23
|
-
object = JSON.parse(ts.res.body)
|
23
|
+
object = JSON.parse(ts.res.body.join)
|
24
24
|
|
25
25
|
assert_equal "foo", object["foo"]
|
26
26
|
end
|
@@ -29,7 +29,7 @@ class JSONTest < Minitest::Test
|
|
29
29
|
@app.plugin(Tynn::JSON)
|
30
30
|
|
31
31
|
@app.define do
|
32
|
-
get do
|
32
|
+
on get do
|
33
33
|
json(%w(foo bar baz))
|
34
34
|
end
|
35
35
|
end
|
@@ -37,14 +37,14 @@ class JSONTest < Minitest::Test
|
|
37
37
|
ts = Tynn::Test.new(@app)
|
38
38
|
ts.get("/")
|
39
39
|
|
40
|
-
assert_equal %w(foo bar baz), JSON.parse(ts.res.body)
|
40
|
+
assert_equal %w(foo bar baz), JSON.parse(ts.res.body.join)
|
41
41
|
end
|
42
42
|
|
43
43
|
def test_content_type
|
44
44
|
@app.plugin(Tynn::JSON)
|
45
45
|
|
46
46
|
@app.define do
|
47
|
-
get do
|
47
|
+
on get do
|
48
48
|
json(ok: true)
|
49
49
|
end
|
50
50
|
end
|
@@ -54,4 +54,19 @@ class JSONTest < Minitest::Test
|
|
54
54
|
|
55
55
|
assert_equal "application/json", ts.res.content_type
|
56
56
|
end
|
57
|
+
|
58
|
+
def test_custom_content_type
|
59
|
+
@app.plugin(Tynn::JSON, content_type: "application/js")
|
60
|
+
|
61
|
+
@app.define do
|
62
|
+
on get do
|
63
|
+
json(ok: true)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
ts = Tynn::Test.new(@app)
|
68
|
+
ts.get("/")
|
69
|
+
|
70
|
+
assert_equal "application/js", ts.res.content_type
|
71
|
+
end
|
57
72
|
end
|
data/test/middleware_test.rb
CHANGED
@@ -16,12 +16,12 @@ class MiddlewareTest < Minitest::Test
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def test_middleware_works
|
19
|
-
app =
|
19
|
+
app = new_app
|
20
20
|
|
21
21
|
app.use(Shrimp)
|
22
22
|
|
23
23
|
app.define do
|
24
|
-
get do
|
24
|
+
on get do
|
25
25
|
res.write("1")
|
26
26
|
res.write("2")
|
27
27
|
end
|
@@ -31,23 +31,23 @@ class MiddlewareTest < Minitest::Test
|
|
31
31
|
ts.get("/")
|
32
32
|
|
33
33
|
assert_equal 200, ts.res.status
|
34
|
-
assert_equal "21", ts.res.body
|
34
|
+
assert_equal "21", ts.res.body.join
|
35
35
|
end
|
36
36
|
|
37
37
|
def test_middleware_with_composition
|
38
|
-
app =
|
39
|
-
api =
|
38
|
+
app = new_app
|
39
|
+
api = new_app(lint: false)
|
40
40
|
|
41
41
|
app.use(Shrimp)
|
42
42
|
|
43
43
|
app.define do
|
44
|
-
on
|
44
|
+
on "api" do
|
45
45
|
run(api)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
49
|
api.define do
|
50
|
-
get do
|
50
|
+
on get do
|
51
51
|
res.write("1")
|
52
52
|
res.write("2")
|
53
53
|
end
|
@@ -57,23 +57,23 @@ class MiddlewareTest < Minitest::Test
|
|
57
57
|
ts.get("/api")
|
58
58
|
|
59
59
|
assert_equal 200, ts.res.status
|
60
|
-
assert_equal "21", ts.res.body
|
60
|
+
assert_equal "21", ts.res.body.join
|
61
61
|
end
|
62
62
|
|
63
63
|
def test_middleware_for_sub_application
|
64
|
-
app =
|
65
|
-
api =
|
64
|
+
app = new_app
|
65
|
+
api = new_app
|
66
66
|
|
67
67
|
api.use(Shrimp)
|
68
68
|
|
69
69
|
app.define do
|
70
|
-
on
|
70
|
+
on "api" do
|
71
71
|
run(api)
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
75
|
api.define do
|
76
|
-
get do
|
76
|
+
on get do
|
77
77
|
res.write("1")
|
78
78
|
res.write("2")
|
79
79
|
end
|
@@ -83,6 +83,16 @@ class MiddlewareTest < Minitest::Test
|
|
83
83
|
ts.get("/api")
|
84
84
|
|
85
85
|
assert_equal 200, ts.res.status
|
86
|
-
assert_equal "21", ts.res.body
|
86
|
+
assert_equal "21", ts.res.body.join
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_raise_if_frozen
|
90
|
+
app = new_app
|
91
|
+
|
92
|
+
app.define {}
|
93
|
+
|
94
|
+
assert_raises(Tynn::Error) do
|
95
|
+
app.use(Shrimp, name: "foo", bar: 1)
|
96
|
+
end
|
87
97
|
end
|
88
98
|
end
|