tynn 1.0.0.rc1 → 1.0.0.rc2
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 +1 -1
- data/lib/tynn.rb +49 -15
- data/lib/tynn/all_methods.rb +12 -10
- data/lib/tynn/environment.rb +39 -37
- data/lib/tynn/erubis.rb +10 -8
- data/lib/tynn/json.rb +36 -5
- data/lib/tynn/matchers.rb +49 -47
- data/lib/tynn/not_found.rb +12 -10
- data/lib/tynn/protection.rb +35 -6
- data/lib/tynn/render.rb +34 -42
- data/lib/tynn/request.rb +2 -0
- data/lib/tynn/response.rb +36 -2
- data/lib/tynn/secure_headers.rb +42 -40
- data/lib/tynn/session.rb +80 -78
- data/lib/tynn/ssl.rb +52 -50
- data/lib/tynn/static.rb +34 -32
- data/lib/tynn/test.rb +42 -40
- data/lib/tynn/version.rb +2 -2
- data/test/core_test.rb +0 -8
- data/test/middleware_test.rb +4 -4
- data/test/render_test.rb +1 -1
- metadata +5 -11
- data/lib/tynn/hmote.rb +0 -40
- data/lib/tynn/json_parser.rb +0 -36
- data/test/hmote_test.rb +0 -78
- data/test/json_parser_test.rb +0 -22
data/lib/tynn/protection.rb
CHANGED
@@ -1,13 +1,42 @@
|
|
1
1
|
require_relative "secure_headers"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
class Tynn
|
4
|
+
# Adds security measures against common attacks.
|
5
|
+
#
|
6
|
+
# ```
|
7
|
+
# require "tynn"
|
8
|
+
# require "tynn/protection"
|
9
|
+
#
|
10
|
+
# Tynn.helpers(Tynn::Protection)
|
11
|
+
# ```
|
12
|
+
#
|
13
|
+
# If you are using SSL/TLS (HTTPS), it's recommended to set
|
14
|
+
# the `:ssl` option:
|
15
|
+
#
|
16
|
+
# ```
|
17
|
+
# require "tynn"
|
18
|
+
# require "tynn/protection"
|
19
|
+
#
|
20
|
+
# Tynn.helpers(Tynn::Protection, ssl: true)
|
21
|
+
# ```
|
22
|
+
#
|
23
|
+
# By default, it includes the following security helpers:
|
24
|
+
#
|
25
|
+
# * Tynn::SecureHeaders
|
26
|
+
#
|
27
|
+
# If the `:ssl` option is `true`, includes:
|
28
|
+
#
|
29
|
+
# * Tynn::SSL
|
30
|
+
#
|
31
|
+
module Protection
|
32
|
+
def self.setup(app, ssl: false, hsts: {}) # :nodoc:
|
33
|
+
app.helpers(Tynn::SecureHeaders)
|
6
34
|
|
7
|
-
|
8
|
-
|
35
|
+
if ssl
|
36
|
+
require_relative "ssl"
|
9
37
|
|
10
|
-
|
38
|
+
app.helpers(Tynn::SSL, hsts: hsts)
|
39
|
+
end
|
11
40
|
end
|
12
41
|
end
|
13
42
|
end
|
data/lib/tynn/render.rb
CHANGED
@@ -1,56 +1,48 @@
|
|
1
1
|
require "tilt"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
options =
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
}.merge!(options[:options])
|
16
|
-
|
17
|
-
app.settings[:render] ||= options
|
18
|
-
end
|
19
|
-
|
20
|
-
module ClassMethods
|
21
|
-
def layout(layout)
|
22
|
-
settings[:render][:layout] = layout
|
3
|
+
class Tynn
|
4
|
+
module Render
|
5
|
+
def self.setup(app, options = {}) # :nodoc:
|
6
|
+
app.settings.update(
|
7
|
+
layout: options.fetch(:layout, "layout"),
|
8
|
+
views: options.fetch(:views, File.expand_path("views", Dir.pwd)),
|
9
|
+
engine: options.fetch(:engine, "erb"),
|
10
|
+
engine_opts: {
|
11
|
+
default_encoding: Encoding.default_external,
|
12
|
+
outvar: "@_output"
|
13
|
+
}.merge!(options.fetch(:options, {}))
|
14
|
+
)
|
23
15
|
end
|
24
|
-
end
|
25
16
|
|
26
|
-
|
27
|
-
|
17
|
+
def render(template, locals = {}, layout = settings[:layout])
|
18
|
+
res.headers[Rack::CONTENT_TYPE] ||= Syro::Response::DEFAULT
|
28
19
|
|
29
|
-
|
30
|
-
|
20
|
+
res.write(view(template, locals, layout))
|
21
|
+
end
|
31
22
|
|
32
|
-
|
33
|
-
|
34
|
-
|
23
|
+
def view(template, locals = {}, layout = settings[:layout])
|
24
|
+
return partial(layout, locals.merge(content: partial(template, locals)))
|
25
|
+
end
|
35
26
|
|
36
|
-
|
37
|
-
|
38
|
-
|
27
|
+
def partial(template, locals = {})
|
28
|
+
return tilt(template_path(template), locals, settings[:engine_opts])
|
29
|
+
end
|
39
30
|
|
40
|
-
|
31
|
+
private
|
41
32
|
|
42
|
-
|
43
|
-
|
44
|
-
|
33
|
+
def tilt(file, locals = {}, opts = {})
|
34
|
+
return tilt_cache.fetch(file) { Tilt.new(file, 1, opts) }.render(self, locals)
|
35
|
+
end
|
45
36
|
|
46
|
-
|
47
|
-
|
48
|
-
|
37
|
+
def tilt_cache
|
38
|
+
return Thread.current[:tilt_cache] ||= Tilt::Cache.new
|
39
|
+
end
|
49
40
|
|
50
|
-
|
51
|
-
|
52
|
-
|
41
|
+
def template_path(template)
|
42
|
+
dir = settings[:views]
|
43
|
+
ext = settings[:engine]
|
53
44
|
|
54
|
-
|
45
|
+
return File.join(dir, "#{ template }.#{ ext }")
|
46
|
+
end
|
55
47
|
end
|
56
48
|
end
|
data/lib/tynn/request.rb
CHANGED
data/lib/tynn/response.rb
CHANGED
@@ -1,5 +1,39 @@
|
|
1
1
|
class Tynn
|
2
|
+
# It provides convenience methods to construct a Rack response.
|
3
|
+
#
|
4
|
+
# ```
|
5
|
+
# res = Tynn::Response.new
|
6
|
+
# res.status = 200
|
7
|
+
# res["Content-Type"] = "text/html"
|
8
|
+
# res.write("foo")
|
9
|
+
# ```
|
10
|
+
#
|
11
|
+
# [Tynn::Response#finish][finish] returns a response as per
|
12
|
+
# [Rack's specification][rack-spec].
|
13
|
+
#
|
14
|
+
# ```
|
15
|
+
# res.finish
|
16
|
+
# # => [200, { "Content-Type" => "text/html", "Content-Length" => 3 }, ["foo"]]
|
17
|
+
# ```
|
18
|
+
#
|
19
|
+
# [finish]: #method-i-finish
|
20
|
+
# [rack-spec]: http://www.rubydoc.info/github/rack/rack/master/file/SPEC
|
21
|
+
#
|
2
22
|
class Response < Syro::Response
|
23
|
+
##
|
24
|
+
# :method: new
|
25
|
+
# :call-seq: new(headers = {})
|
26
|
+
#
|
27
|
+
# Initializes a new response object with the given `headers`.
|
28
|
+
#
|
29
|
+
# ```
|
30
|
+
# Tynn::Response.new.headers
|
31
|
+
# # => {}
|
32
|
+
#
|
33
|
+
# Tynn::Response.new("Content-Type" => "text/plain").headers
|
34
|
+
# # => { "Content-Type" => "text/plain" }
|
35
|
+
# ```
|
36
|
+
|
3
37
|
##
|
4
38
|
# :method: []
|
5
39
|
#
|
@@ -48,9 +82,9 @@ class Tynn
|
|
48
82
|
# # => [404, {}, []]
|
49
83
|
#
|
50
84
|
# res.status = nil
|
51
|
-
# res.write("yo
|
85
|
+
# res.write("yo")
|
52
86
|
# res.finish
|
53
|
-
# # => [200, { "Content-Type" => "text/html" }, ["yo
|
87
|
+
# # => [200, { "Content-Type" => "text/html", "Content-Length" => 2 }, ["yo"]]
|
54
88
|
|
55
89
|
##
|
56
90
|
# :method: headers
|
data/lib/tynn/secure_headers.rb
CHANGED
@@ -1,43 +1,45 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# require "tynn
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# [
|
28
|
-
# [
|
29
|
-
# [
|
30
|
-
# [
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
1
|
+
class Tynn
|
2
|
+
# Adds security related HTTP headers.
|
3
|
+
#
|
4
|
+
# ```
|
5
|
+
# require "tynn"
|
6
|
+
# require "tynn/secure_headers"
|
7
|
+
#
|
8
|
+
# Tynn.helpers(Tynn::SecureHeaders)
|
9
|
+
# ```
|
10
|
+
#
|
11
|
+
# This helper applies the following headers:
|
12
|
+
#
|
13
|
+
# * **X-Content-Type-Options:** Prevents IE and Chrome from
|
14
|
+
# [content type sniffing][mime-sniffing].
|
15
|
+
#
|
16
|
+
# * **X-Frame-Options (XFO):** Provides [Clickjacking][clickjacking]
|
17
|
+
# protection. Check the [X-Frame-Options draft][x-frame-options] for
|
18
|
+
# more information.
|
19
|
+
#
|
20
|
+
# * **X-Permitted-Cross-Domain-Policies:** Restricts Adobe Flash Player's
|
21
|
+
# access to data. Check this [article][pcdp] for more information.
|
22
|
+
#
|
23
|
+
# * **X-XSS-Protection:** Enables the [XSS][xss] protection filter built
|
24
|
+
# into IE, Chrome and Safari. This filter is usually enabled by default,
|
25
|
+
# the use of this header is to re-enable it if it was disabled by the user.
|
26
|
+
#
|
27
|
+
# [clickjacking]: https://www.owasp.org/index.php/Clickjacking
|
28
|
+
# [mime-sniffing]: https://msdn.microsoft.com/library/gg622941(v=vs.85).aspx
|
29
|
+
# [pcdp]: https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html
|
30
|
+
# [x-frame-options]: https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-02
|
31
|
+
# [xss]: https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
|
32
|
+
#
|
33
|
+
module SecureHeaders
|
34
|
+
HEADERS = {
|
35
|
+
"X-Content-Type-Options" => "nosniff",
|
36
|
+
"X-Frame-Options" => "SAMEORIGIN",
|
37
|
+
"X-Permitted-Cross-Domain-Policies" => "none",
|
38
|
+
"X-XSS-Protection" => "1; mode=block"
|
39
|
+
} # :nodoc:
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
def default_headers # :nodoc:
|
42
|
+
return super.merge(HEADERS)
|
43
|
+
end
|
42
44
|
end
|
43
45
|
end
|
data/lib/tynn/session.rb
CHANGED
@@ -1,85 +1,87 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# ```
|
6
|
-
# require "tynn"
|
7
|
-
# require "tynn/session"
|
8
|
-
#
|
9
|
-
# Tynn.helpers(Tynn::Session, secret: "__change_me__")
|
10
|
-
#
|
11
|
-
# Tynn.define do
|
12
|
-
# root do
|
13
|
-
# res.write(sprintf("hei %s", session[:username]))
|
14
|
-
# end
|
15
|
-
#
|
16
|
-
# on(:username) do |username|
|
17
|
-
# session[:username] = username
|
18
|
-
# end
|
19
|
-
# end
|
20
|
-
# ```
|
21
|
-
#
|
22
|
-
# The following command generates a cryptographically secure secret ready
|
23
|
-
# to use:
|
24
|
-
#
|
25
|
-
# ```
|
26
|
-
# $ ruby -r securerandom -e "puts SecureRandom.hex(64)"
|
27
|
-
# ```
|
28
|
-
#
|
29
|
-
# It's important to keep the token secret. Knowing the token allows an
|
30
|
-
# attacker to tamper the data. So, it's recommended to load the token
|
31
|
-
# from the environment.
|
32
|
-
#
|
33
|
-
# ```
|
34
|
-
# Tynn.helpers(Tynn::Session, secret: ENV["SESSION_SECRET"])
|
35
|
-
# ```
|
36
|
-
#
|
37
|
-
# Under the hood, Tynn::Session uses the [Rack::Session::Cookie][rack-session]
|
38
|
-
# middleware. Thus, supports all the options available for this middleware.
|
39
|
-
#
|
40
|
-
# * `:key` - the name of the cookie. Defaults to `"rack.session"`.
|
41
|
-
# * `:expire_after` - sets the lifespan of the cookie. If `nil`,
|
42
|
-
# the cookie will be deleted after the user close the browser.
|
43
|
-
# Defaults to `nil`.
|
44
|
-
# * `:httponly` - if `true`, sets the [HttpOnly][cookie-httponly] attribute.
|
45
|
-
# This mitigates the risk of client side scripting accessing the cookie.
|
46
|
-
# Defaults to `true`.
|
47
|
-
# * `:secure` - if `true`, sets the [Secure][cookie-secure] attribute.
|
48
|
-
# This tells the browser to only transmit the cookie over HTTPS. Defaults
|
49
|
-
# to `false`.
|
50
|
-
#
|
51
|
-
# ```
|
52
|
-
# Tynn.helpers(
|
53
|
-
# Tynn::Session,
|
54
|
-
# key: "app",
|
55
|
-
# secret: ENV["SESSION_SECRET"],
|
56
|
-
# expire_after: 36_000, # seconds
|
57
|
-
# httponly: true,
|
58
|
-
# secure: true
|
59
|
-
# )
|
60
|
-
# ```
|
61
|
-
#
|
62
|
-
# [cookie-httponly]: https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#HttpOnly_Attribute
|
63
|
-
# [cookie-secure]: https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Secure_Attribute
|
64
|
-
# [rack-session]: http://www.rubydoc.info/gems/rack/Rack/Session/Cookie
|
65
|
-
#
|
66
|
-
module Tynn::Session
|
67
|
-
RACK_SESSION = "rack.session".freeze # :nodoc:
|
68
|
-
|
69
|
-
def self.setup(app, options = {}) # :nodoc:
|
70
|
-
app.use(Rack::Session::Cookie, options)
|
71
|
-
end
|
72
|
-
|
73
|
-
# Returns the session hash.
|
1
|
+
class Tynn
|
2
|
+
# Adds simple cookie based session management. If a secret token is
|
3
|
+
# given, it signs the cookie data to ensure that it cannot be altered
|
4
|
+
# by unauthorized means.
|
74
5
|
#
|
75
6
|
# ```
|
76
|
-
#
|
7
|
+
# require "tynn"
|
8
|
+
# require "tynn/session"
|
77
9
|
#
|
78
|
-
#
|
79
|
-
#
|
10
|
+
# Tynn.helpers(Tynn::Session, secret: "__change_me__")
|
11
|
+
#
|
12
|
+
# Tynn.define do
|
13
|
+
# root do
|
14
|
+
# res.write(sprintf("hei %s", session[:username]))
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# on(:username) do |username|
|
18
|
+
# session[:username] = username
|
19
|
+
# end
|
20
|
+
# end
|
80
21
|
# ```
|
81
22
|
#
|
82
|
-
|
83
|
-
|
23
|
+
# The following command generates a cryptographically secure secret ready
|
24
|
+
# to use:
|
25
|
+
#
|
26
|
+
# ```
|
27
|
+
# $ ruby -r securerandom -e "puts SecureRandom.hex(64)"
|
28
|
+
# ```
|
29
|
+
#
|
30
|
+
# It's important to keep the token secret. Knowing the token allows an
|
31
|
+
# attacker to tamper the data. So, it's recommended to load the token
|
32
|
+
# from the environment.
|
33
|
+
#
|
34
|
+
# ```
|
35
|
+
# Tynn.helpers(Tynn::Session, secret: ENV["SESSION_SECRET"])
|
36
|
+
# ```
|
37
|
+
#
|
38
|
+
# Under the hood, Tynn::Session uses the [Rack::Session::Cookie][rack-session]
|
39
|
+
# middleware. Thus, supports all the options available for this middleware.
|
40
|
+
#
|
41
|
+
# * `:key` - the name of the cookie. Defaults to `"rack.session"`.
|
42
|
+
# * `:expire_after` - sets the lifespan of the cookie. If `nil`,
|
43
|
+
# the cookie will be deleted after the user close the browser.
|
44
|
+
# Defaults to `nil`.
|
45
|
+
# * `:httponly` - if `true`, sets the [HttpOnly][cookie-httponly] attribute.
|
46
|
+
# This mitigates the risk of client side scripting accessing the cookie.
|
47
|
+
# Defaults to `true`.
|
48
|
+
# * `:secure` - if `true`, sets the [Secure][cookie-secure] attribute.
|
49
|
+
# This tells the browser to only transmit the cookie over HTTPS. Defaults
|
50
|
+
# to `false`.
|
51
|
+
#
|
52
|
+
# ```
|
53
|
+
# Tynn.helpers(
|
54
|
+
# Tynn::Session,
|
55
|
+
# key: "app",
|
56
|
+
# secret: ENV["SESSION_SECRET"],
|
57
|
+
# expire_after: 36_000, # seconds
|
58
|
+
# httponly: true,
|
59
|
+
# secure: true
|
60
|
+
# )
|
61
|
+
# ```
|
62
|
+
#
|
63
|
+
# [cookie-httponly]: https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#HttpOnly_Attribute
|
64
|
+
# [cookie-secure]: https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Secure_Attribute
|
65
|
+
# [rack-session]: http://www.rubydoc.info/gems/rack/Rack/Session/Cookie
|
66
|
+
#
|
67
|
+
module Session
|
68
|
+
RACK_SESSION = "rack.session".freeze # :nodoc:
|
69
|
+
|
70
|
+
def self.setup(app, options = {}) # :nodoc:
|
71
|
+
app.use(Rack::Session::Cookie, options)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the session hash.
|
75
|
+
#
|
76
|
+
# ```
|
77
|
+
# session # => {}
|
78
|
+
#
|
79
|
+
# session[:foo] = "foo"
|
80
|
+
# session[:foo] # => "foo"
|
81
|
+
# ```
|
82
|
+
#
|
83
|
+
def session
|
84
|
+
return env[RACK_SESSION]
|
85
|
+
end
|
84
86
|
end
|
85
87
|
end
|