tynn 1.0.0.rc2 → 1.0.0.rc3
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 -3
- data/lib/tynn.rb +61 -102
- data/lib/tynn/all_methods.rb +11 -9
- data/lib/tynn/environment.rb +13 -13
- data/lib/tynn/force_ssl.rb +55 -0
- data/lib/tynn/hmote.rb +34 -0
- data/lib/tynn/hsts.rb +64 -0
- data/lib/tynn/json.rb +34 -30
- data/lib/tynn/matchers.rb +50 -48
- data/lib/tynn/not_found.rb +11 -9
- data/lib/tynn/protection.rb +29 -18
- data/lib/tynn/render.rb +24 -23
- data/lib/tynn/response.rb +138 -126
- data/lib/tynn/secure_headers.rb +29 -30
- data/lib/tynn/session.rb +62 -64
- data/lib/tynn/settings.rb +28 -0
- data/lib/tynn/static.rb +23 -20
- data/lib/tynn/test.rb +30 -31
- data/lib/tynn/version.rb +1 -1
- data/test/default_headers_test.rb +14 -0
- data/test/environment_test.rb +14 -18
- data/test/force_ssl_test.rb +32 -0
- data/test/hmote_test.rb +78 -0
- data/test/hsts_test.rb +29 -0
- data/test/protection_test.rb +0 -30
- data/test/render_test.rb +14 -1
- data/test/secure_headers_test.rb +8 -1
- metadata +15 -22
- data/lib/tynn/erubis.rb +0 -17
- data/lib/tynn/ssl.rb +0 -73
- data/test/erubis_test.rb +0 -20
- data/test/ssl_test.rb +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e2fe7081578c608775773d67ab5e4dc92a33414
|
4
|
+
data.tar.gz: 96e869a9ec51ed45ab5baf116cd8126c32c4b597
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd4c49a2f298af29cefebca3650c488ab2bbb527299f700e9a72964e01a4fc1352fa7da6ef2264b25be24cedaaf6272ac53b5a603619da19c3d24a1b207235e4
|
7
|
+
data.tar.gz: 4ba63a68c74cbc71463b0e010ae985b8dab7d34d55e2043f1ed776774c8de93b4e07a33d7b08724fb1bacfd9e3156fa70bde9a37fe0912c214ecff1c159f6779
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
tynn [](https://travis-ci.org/frodsan/tynn) [](https://codeclimate.com/github/frodsan/tynn)
|
2
2
|
====
|
3
3
|
|
4
4
|
A thin library for web development.
|
@@ -48,8 +48,6 @@ Contributing
|
|
48
48
|
You can install the gems globally, but we recommend [gs][gs] (or
|
49
49
|
[gst][gst] if you're using chruby) to keep things isolated.
|
50
50
|
|
51
|
-
[cuba]: https://github.com/soveran/cuba
|
52
|
-
[rack]: https://github.com/rack/rack
|
53
51
|
[syro]: https://github.com/soveran/syro
|
54
52
|
[gs]: https://github.com/soveran/gs
|
55
53
|
[gst]: https://github.com/tonchis/gst
|
data/lib/tynn.rb
CHANGED
@@ -1,45 +1,70 @@
|
|
1
|
-
require "seteable"
|
2
1
|
require "syro"
|
3
2
|
require_relative "tynn/request"
|
4
3
|
require_relative "tynn/response"
|
4
|
+
require_relative "tynn/settings"
|
5
5
|
require_relative "tynn/version"
|
6
6
|
|
7
7
|
class Tynn
|
8
|
-
include Seteable
|
9
8
|
include Syro::Deck::API
|
10
9
|
|
11
|
-
#
|
10
|
+
# Public: Extends Tynn functionality with the given +helper+ module.
|
12
11
|
#
|
13
|
-
#
|
14
|
-
# class Users < Tynn
|
15
|
-
# end
|
12
|
+
# Examples
|
16
13
|
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# end
|
14
|
+
# require "tynn"
|
15
|
+
# require "tynn/protection"
|
16
|
+
#
|
17
|
+
# Tynn.helpers(Tynn::Protection)
|
22
18
|
#
|
23
|
-
|
24
|
-
|
19
|
+
def self.helpers(helper, *args, &block)
|
20
|
+
if defined?(helper::InstanceMethods)
|
21
|
+
self.include(helper::InstanceMethods)
|
22
|
+
end
|
23
|
+
|
24
|
+
if defined?(helper::ClassMethods)
|
25
|
+
self.extend(helper::ClassMethods)
|
26
|
+
end
|
27
|
+
|
28
|
+
if helper.respond_to?(:setup)
|
29
|
+
helper.setup(self, *args, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Internal: Default helpers.
|
34
|
+
helpers(Tynn::Settings)
|
35
|
+
|
36
|
+
# Public: Sets the application handler.
|
37
|
+
#
|
38
|
+
# Examples
|
39
|
+
#
|
40
|
+
# class Users < Tynn
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# Users.define do
|
44
|
+
# on(:id) do |id|
|
45
|
+
# get do
|
46
|
+
# res.write("GET /users")
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# post do
|
50
|
+
# res.write("POST /users")
|
51
|
+
# end
|
25
52
|
# end
|
26
53
|
# end
|
27
|
-
# end
|
28
|
-
# ```
|
29
54
|
#
|
30
55
|
def self.define(&block)
|
31
56
|
build_app(Syro.new(self, &block))
|
32
57
|
end
|
33
58
|
|
34
|
-
# Adds given Rack
|
59
|
+
# Public: Adds given Rack +middleware+ to the stack.
|
60
|
+
#
|
61
|
+
# Examples
|
35
62
|
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
# require "rack/show_exceptions"
|
63
|
+
# require "rack/common_logger"
|
64
|
+
# require "rack/show_exceptions"
|
39
65
|
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
# ```
|
66
|
+
# Tynn.use(Rack::CommonLogger)
|
67
|
+
# Tynn.use(Rack::ShowExceptions)
|
43
68
|
#
|
44
69
|
def self.use(middleware, *args, &block)
|
45
70
|
__middleware << proc { |app| middleware.new(app, *args, &block) }
|
@@ -66,57 +91,27 @@ class Tynn
|
|
66
91
|
@middleware = []
|
67
92
|
end
|
68
93
|
|
69
|
-
#
|
70
|
-
#
|
71
|
-
# ```
|
72
|
-
# module AppName
|
73
|
-
# def self.setup(app, name)
|
74
|
-
# app.set(:app_name, name)
|
75
|
-
# end
|
76
|
-
#
|
77
|
-
# def app_name
|
78
|
-
# return self.class.app_name
|
79
|
-
# end
|
94
|
+
# Public: Sets an +option+ to the given +value+.
|
80
95
|
#
|
81
|
-
#
|
82
|
-
# def app_name
|
83
|
-
# return settings[:app_name]
|
84
|
-
# end
|
85
|
-
# end
|
86
|
-
# end
|
96
|
+
# Examples
|
87
97
|
#
|
88
|
-
#
|
98
|
+
# require "tynn"
|
99
|
+
# require "tynn/environment"
|
89
100
|
#
|
90
|
-
#
|
101
|
+
# Tynn.helpers(Tynn::Environment)
|
91
102
|
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
# Tynn.define do
|
96
|
-
# root do
|
97
|
-
# res.write(app_name)
|
98
|
-
# end
|
99
|
-
# end
|
100
|
-
# ```
|
103
|
+
# Tynn.set(:environment, :staging)
|
104
|
+
# Tynn.environment
|
105
|
+
# # => :staging
|
101
106
|
#
|
102
|
-
|
103
|
-
|
104
|
-
# [examples]: https://github.com/frodsan/tynn/tree/master/lib/tynn
|
105
|
-
#
|
106
|
-
def self.helpers(helper, *args, &block)
|
107
|
-
self.include(helper)
|
108
|
-
|
109
|
-
if defined?(helper::ClassMethods)
|
110
|
-
self.extend(helper::ClassMethods)
|
111
|
-
end
|
112
|
-
|
113
|
-
if helper.respond_to?(:setup)
|
114
|
-
helper.setup(self, *args, &block)
|
115
|
-
end
|
107
|
+
def self.set(option, value)
|
108
|
+
settings[option] = value
|
116
109
|
end
|
117
110
|
|
118
|
-
|
119
|
-
|
111
|
+
set(:default_headers, {})
|
112
|
+
|
113
|
+
def default_headers # :nodoc:
|
114
|
+
return Hash[settings[:default_headers]]
|
120
115
|
end
|
121
116
|
|
122
117
|
def request_class # :nodoc:
|
@@ -126,40 +121,4 @@ class Tynn
|
|
126
121
|
def response_class # :nodoc:
|
127
122
|
return Tynn::Response
|
128
123
|
end
|
129
|
-
|
130
|
-
##
|
131
|
-
# :method: halt(response)
|
132
|
-
#
|
133
|
-
# Immediately stops the request and returns `response` as per
|
134
|
-
# Rack's specification.
|
135
|
-
#
|
136
|
-
# ```
|
137
|
-
# halt([200, { "Content-Type" => "text/html" }, ["hello"]])
|
138
|
-
# halt([res.status, res.headers, res.body])
|
139
|
-
# halt(res.finish)
|
140
|
-
# ```
|
141
|
-
|
142
|
-
##
|
143
|
-
# :method: req
|
144
|
-
#
|
145
|
-
# Returns the incoming request object. This object is an instance
|
146
|
-
# of Tynn::Request.
|
147
|
-
#
|
148
|
-
# ```
|
149
|
-
# req.post? # => true
|
150
|
-
# req.params # => { "username" => "bob", "password" => "secret" }
|
151
|
-
# req[:username] # => "bob"
|
152
|
-
# ```
|
153
|
-
|
154
|
-
##
|
155
|
-
# :method: res
|
156
|
-
#
|
157
|
-
# Returns the current response object. This object is an instance
|
158
|
-
# of Tynn::Response.
|
159
|
-
#
|
160
|
-
# ```
|
161
|
-
# res.status = 200
|
162
|
-
# res["Content-Type"] = "text/html"
|
163
|
-
# res.write("<h1>Welcome back!</h1>")
|
164
|
-
# ```
|
165
124
|
end
|
data/lib/tynn/all_methods.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
class Tynn
|
2
2
|
module AllMethods
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
module InstanceMethods
|
4
|
+
def head
|
5
|
+
if root? && req.head?
|
6
|
+
yield
|
6
7
|
|
7
|
-
|
8
|
+
halt(res.finish)
|
9
|
+
end
|
8
10
|
end
|
9
|
-
end
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
def options
|
13
|
+
if root? && req.options?
|
14
|
+
yield
|
14
15
|
|
15
|
-
|
16
|
+
halt(res.finish)
|
17
|
+
end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
data/lib/tynn/environment.rb
CHANGED
@@ -1,24 +1,24 @@
|
|
1
1
|
class Tynn
|
2
|
-
# Adds helper methods to get and check the current environment.
|
2
|
+
# Public: Adds helper methods to get and check the current environment.
|
3
3
|
#
|
4
|
-
#
|
5
|
-
# require "tynn"
|
6
|
-
# require "tynn/environment"
|
4
|
+
# Examples
|
7
5
|
#
|
8
|
-
#
|
6
|
+
# require "tynn"
|
7
|
+
# require "tynn/environment"
|
9
8
|
#
|
10
|
-
#
|
9
|
+
# Tynn.helpers(Tynn::Environment)
|
11
10
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
11
|
+
# Tynn.environment # => :development
|
12
|
+
#
|
13
|
+
# Tynn.development? # => true
|
14
|
+
# Tynn.production? # => false
|
15
|
+
# Tynn.test? # => false
|
16
16
|
#
|
17
17
|
# By default, the environment is based on `ENV["RACK_ENV"]`.
|
18
18
|
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
19
|
+
# Examples
|
20
|
+
#
|
21
|
+
# Tynn.helpers(Tynn::Environment, env: ENV["RACK_ENV"])
|
22
22
|
#
|
23
23
|
module Environment
|
24
24
|
def self.setup(app, env: ENV["RACK_ENV"]) # :nodoc:
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class Tynn
|
2
|
+
# Public: HTTP requests are permanently redirected to their HTTPS
|
3
|
+
# counterparts.
|
4
|
+
#
|
5
|
+
# Examples
|
6
|
+
#
|
7
|
+
# require "tynn"
|
8
|
+
# require "tynn/force_ssl"
|
9
|
+
# require "tynn/test"
|
10
|
+
#
|
11
|
+
# Tynn.helpers(Tynn::ForceSSL)
|
12
|
+
#
|
13
|
+
# Tynn.define { }
|
14
|
+
#
|
15
|
+
# app = Tynn::Test.new
|
16
|
+
# app.get("/", {}, "HTTP_HOST" => "tynn.ru")
|
17
|
+
#
|
18
|
+
# app.res.headers["Location"]
|
19
|
+
# # => "https://tynn.ru/"
|
20
|
+
#
|
21
|
+
module ForceSSL
|
22
|
+
# Internal: Sets the HTTPS redirect middleware.
|
23
|
+
def self.setup(app)
|
24
|
+
app.use(Tynn::ForceSSL::Middleware)
|
25
|
+
end
|
26
|
+
|
27
|
+
class Middleware # :nodoc:
|
28
|
+
def initialize(app)
|
29
|
+
@app = app
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(env)
|
33
|
+
request = Rack::Request.new(env)
|
34
|
+
|
35
|
+
if request.ssl?
|
36
|
+
return @app.call(env)
|
37
|
+
else
|
38
|
+
return [301, redirect_headers(request), []]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def redirect_headers(request)
|
45
|
+
return { "Location" => https_location(request) }
|
46
|
+
end
|
47
|
+
|
48
|
+
HTTPS_LOCATION = "https://%s%s".freeze
|
49
|
+
|
50
|
+
def https_location(request)
|
51
|
+
return sprintf(HTTPS_LOCATION, request.host, request.fullpath)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/tynn/hmote.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require "hmote"
|
2
|
+
|
3
|
+
class Tynn
|
4
|
+
module HMote
|
5
|
+
def self.setup(app, options = {}) # :nodoc:
|
6
|
+
app.set(:layout, options.fetch(:layout, "layout"))
|
7
|
+
app.set(:views, options.fetch(:views, File.expand_path("views", Dir.pwd)))
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
include ::HMote::Helpers
|
12
|
+
|
13
|
+
def render(template, locals = {}, layout = settings[:layout])
|
14
|
+
res.headers[Rack::CONTENT_TYPE] ||= Syro::Response::DEFAULT
|
15
|
+
|
16
|
+
res.write(view(template, locals, layout))
|
17
|
+
end
|
18
|
+
|
19
|
+
def view(template, locals = {}, layout = settings[:layout])
|
20
|
+
return partial(layout, locals.merge(content: partial(template, locals)))
|
21
|
+
end
|
22
|
+
|
23
|
+
def partial(template, locals = {})
|
24
|
+
return hmote(template_path(template), locals.merge(app: self), TOPLEVEL_BINDING)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def template_path(template)
|
30
|
+
return File.join(settings[:views], "#{ template }.mote")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/tynn/hsts.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
class Tynn
|
2
|
+
# Public: Sets the +Strict-Transport-Security+ header. This ensures the
|
3
|
+
# browser never visits the http version of a website. This reduces the
|
4
|
+
# impact of leaking session data through cookies and external links, and
|
5
|
+
# defends against Man-in-the-middle attacks.
|
6
|
+
#
|
7
|
+
# Examples
|
8
|
+
#
|
9
|
+
# require "tynn"
|
10
|
+
# require "tynn/hsts"
|
11
|
+
#
|
12
|
+
# Tynn.helpers(Tynn::HSTS)
|
13
|
+
#
|
14
|
+
# Tynn.define { }
|
15
|
+
#
|
16
|
+
# Tynn.call("PATH_INFO" => "/")[1]["Strict-Transport-Security"]
|
17
|
+
# # => "max-age=15552000; includeSubdomains"
|
18
|
+
#
|
19
|
+
# It supports the following options:
|
20
|
+
#
|
21
|
+
# expires - The time, in seconds, that the browser access the site only
|
22
|
+
# by HTTPS. Defaults to 180 days.
|
23
|
+
# subdomains - If this is +true+, the rule applies to all the site's
|
24
|
+
# subdomains as well. Defaults to +true+.
|
25
|
+
# preload - A limitation of HSTS is that the initial request remains
|
26
|
+
# unprotected if it uses HTTP. The same applies to the first
|
27
|
+
# request after the activity period specified by +max-age+.
|
28
|
+
# Modern browsers implements a "STS preloaded list", which
|
29
|
+
# contains known sites supporting HSTS. If you would like to
|
30
|
+
# include your website into the list, set this options to +true+
|
31
|
+
# and submit your domain to this {form}[https://hstspreload.appspot.com/].
|
32
|
+
# Supported by Chrome, Firefox, IE11+ and IE Edge.
|
33
|
+
#
|
34
|
+
# Examples
|
35
|
+
#
|
36
|
+
# Tynn.helpers(
|
37
|
+
# Tynn::HSTS,
|
38
|
+
# expires: 31_536_000,
|
39
|
+
# includeSubdomains: true,
|
40
|
+
# preload: true
|
41
|
+
# )
|
42
|
+
#
|
43
|
+
# Tynn.define { }
|
44
|
+
#
|
45
|
+
# Tynn.call("PATH_INFO" => "/")[1]["Strict-Transport-Security"]
|
46
|
+
# # => "max-age=31536000; includeSubdomains; preload"
|
47
|
+
#
|
48
|
+
# To disable HSTS, you will need to tell the browser to expire it immediately.
|
49
|
+
#
|
50
|
+
# Examples
|
51
|
+
#
|
52
|
+
# Tynn.helpers(Tynn::HSTS, expires: 0)
|
53
|
+
#
|
54
|
+
module HSTS
|
55
|
+
# Internal: Sets the HSTS header as a default header.
|
56
|
+
def self.setup(app, options = {})
|
57
|
+
header = sprintf("max-age=%i", options.fetch(:expires, 15_552_000))
|
58
|
+
header << "; includeSubdomains" if options.fetch(:subdomains, true)
|
59
|
+
header << "; preload" if options[:preload]
|
60
|
+
|
61
|
+
app.settings[:default_headers]["Strict-Transport-Security"] = header
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|