liberty 0.4.0 → 0.5.0
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/CHANGELOG.md +17 -0
- data/Gemfile.lock +1 -1
- data/README.md +106 -37
- data/lib/liberty/adapters/response.rb +0 -14
- data/lib/liberty/application.rb +11 -5
- data/lib/liberty/authenticator.rb +33 -0
- data/lib/liberty/authenticators/public.rb +19 -0
- data/lib/liberty/endpoint.rb +10 -15
- data/lib/liberty/endpoint_builder.rb +49 -0
- data/lib/liberty/errors.rb +13 -0
- data/lib/liberty/version.rb +1 -1
- data/lib/liberty.rb +10 -6
- metadata +5 -4
- data/lib/liberty/adapters/error_response.rb +0 -30
- data/lib/liberty/adapters/forbidden_response.rb +0 -19
- data/lib/liberty/adapters/unauthenticated_response.rb +0 -29
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fb4d162f0b2ce1791b670b9b9093a8638737aec1c142908d321ebcba16499c4f
|
|
4
|
+
data.tar.gz: 7da18983c0aa557d727059daef386d429a8a1e8abeb4cd3473b8ec2ee0e76104
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9b657a059f19264ebab5a4cd059f50bf5d6c9057b700b4a6e4121ca35583ad1dd1f3cbe28988993a59f0e44713d8912a8d47699b0cbad66ae2e9ebcaae17e489
|
|
7
|
+
data.tar.gz: 3a6e8297c743040dc5d74f1cfbba70c13ba40a2d575e9da70859c924c0f403959b9ff13f4ec733a12e82f7b8ef1d94646d025c97fb6f749bb16202e237b56880
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
## [0.5.0] - 2026-09-17
|
|
2
|
+
|
|
3
|
+
### Breaking changes
|
|
4
|
+
|
|
5
|
+
- Replaces the 0.4.0 authentication hooks, which could only answer a 401. Routes now name their authenticator: `responds_to` takes a required `authenticated_by:` keyword, and a route that names none fails at load. Gone are the endpoint hooks `authenticated?`, `authorized?`, and `www_authenticate_header`, and Liberty's built-in 401 and 403 responses. Applications on 0.4.0 move their credential check into an authenticator and their 401 into a challenge endpoint. Applications on 0.3.x name `Liberty::Authenticators::Public` on every route they mean to leave open.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Authenticators. A subclass of `Liberty::Authenticator` answers `principal` and `challenge_endpoint_class`. Liberty builds one per request, injects the request, and asks before the endpoint exists, then builds either the endpoint or the challenge. The challenge is an ordinary endpoint class the application writes, so a form login can answer a 303 to its login page and a token API can answer a 401 with a `WWW-Authenticate` header, each through the same response pipeline as every other endpoint.
|
|
10
|
+
- `Liberty::Error`, the base for every error Liberty raises, and `Liberty::AbstractMethodError`, raised when an authenticator leaves one of its two answers unimplemented.
|
|
11
|
+
- `Liberty::Authenticators::Public`, the authenticator for open routes. It finds no principal and never challenges.
|
|
12
|
+
- `Endpoint#principal`, whoever the authenticator found for the request, or `nil`.
|
|
13
|
+
|
|
14
|
+
### Kept
|
|
15
|
+
|
|
16
|
+
- `request.headers[:authorization]`, and empty bodies with a `content-length` for `HEAD` requests, both from 0.4.0.
|
|
17
|
+
|
|
1
18
|
## [0.4.0] - 2026-09-15
|
|
2
19
|
|
|
3
20
|
### Breaking changes
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -37,10 +37,13 @@ That said, Liberty does not use convention. It relies on classes declaratively r
|
|
|
37
37
|
|
|
38
38
|
## Usage
|
|
39
39
|
|
|
40
|
-
Liberty consists of
|
|
40
|
+
Liberty consists of these top level classes:
|
|
41
41
|
* Endpoint
|
|
42
|
+
* Authenticator
|
|
43
|
+
* Authenticators::Public
|
|
42
44
|
* CORS
|
|
43
45
|
* Application (private)
|
|
46
|
+
* EndpointBuilder (private)
|
|
44
47
|
* Router (private)
|
|
45
48
|
|
|
46
49
|
Inherit from the `Endpoint` class to create class that responds to a single type of request.
|
|
@@ -48,15 +51,7 @@ Here's an example for an HTTP get to the `/hello` route, which returns a hello w
|
|
|
48
51
|
|
|
49
52
|
```ruby
|
|
50
53
|
class MyEndpoint < Liberty::Endpoint
|
|
51
|
-
responds_to :get, '/hello'
|
|
52
|
-
|
|
53
|
-
def authenticated?
|
|
54
|
-
true
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def authorized?
|
|
58
|
-
true
|
|
59
|
-
end
|
|
54
|
+
responds_to :get, '/hello', authenticated_by: Liberty::Authenticators::Public
|
|
60
55
|
|
|
61
56
|
def status
|
|
62
57
|
200
|
|
@@ -70,7 +65,8 @@ end
|
|
|
70
65
|
|
|
71
66
|
Using `responds_to` as in the example above registers a class to receive traffic on that route
|
|
72
67
|
with our incredibly fast `Router`. You shouldn't ever need to use the `Router` directly. Just
|
|
73
|
-
use `responds_to` to register your route.
|
|
68
|
+
use `responds_to` to register your route. Every route names its authenticator; `Public` is the
|
|
69
|
+
one Liberty ships for routes open to anyone. See [Authentication](#authentication) below.
|
|
74
70
|
|
|
75
71
|
The `Application` class is another private class that turns each `Endpoint` class into a
|
|
76
72
|
Rack application. This is also a class you won't use directly.
|
|
@@ -91,49 +87,122 @@ end
|
|
|
91
87
|
If you configure your CORS headers before you launch your application, `Endpoints` will
|
|
92
88
|
automatically respond with the right headers.
|
|
93
89
|
|
|
94
|
-
### Authentication
|
|
90
|
+
### Authentication
|
|
91
|
+
|
|
92
|
+
Every route names its authenticator:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
class Journal < Liberty::Endpoint
|
|
96
|
+
responds_to :get, "/journal", authenticated_by: Authenticators::Session
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`authenticated_by:` is required. A route that does not name one fails when the class loads, so
|
|
101
|
+
nothing is exposed by omission. To open a route to anyone, name `Liberty::Authenticators::Public`.
|
|
102
|
+
|
|
103
|
+
An authenticator is a subclass of `Liberty::Authenticator` that answers two
|
|
104
|
+
questions:
|
|
105
|
+
|
|
106
|
+
- `principal`: who the request is from, or `nil`. The principal is whoever the request has been
|
|
107
|
+
authenticated as, whether a user, a service account, or an API client. It is any object your
|
|
108
|
+
application chooses.
|
|
109
|
+
- `challenge_endpoint_class`: the endpoint class that answers when there is no principal, or
|
|
110
|
+
`nil` to admit the request anyway.
|
|
95
111
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
them to plug in your own auth. Liberty checks `#authenticated?` first and responds with a 401 when
|
|
99
|
-
it returns `false`. It then checks `#authorized?` and responds with `403 Forbidden` when that
|
|
100
|
-
returns `false`. Your endpoint's status, headers, and content are only consulted when both return
|
|
101
|
-
`true`. The raw `Authorization` header is available as `request.headers[:authorization]`.
|
|
112
|
+
The base class raises on both until you override them, so an authenticator cannot admit anyone
|
|
113
|
+
by omission either.
|
|
102
114
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
Liberty builds a new authenticator for every request and injects the request, available as
|
|
116
|
+
`request`, the same way it builds your endpoint. Every name ending in `_class` follows one rule
|
|
117
|
+
throughout Liberty: it names a class that Liberty instantiates for you. With a principal, Liberty
|
|
118
|
+
builds your endpoint. Without one, it builds the challenge instead. Either way, the endpoint
|
|
119
|
+
receives the request and the principal, available as `principal`, and its answers go through the
|
|
120
|
+
same response pipeline, so `HEAD` requests, `content-length`, and `content-type` are handled once.
|
|
106
121
|
|
|
107
|
-
|
|
122
|
+
Liberty ships no authenticator but `Public`, and no 401 or 403 of its own. What a request without
|
|
123
|
+
a principal sees is the application's to decide: a login page for a form, a `WWW-Authenticate`
|
|
124
|
+
challenge for a token.
|
|
125
|
+
|
|
126
|
+
Here's a form login backed by a session. The request is injected after construction, so the
|
|
127
|
+
authenticator takes its repository through its own initializer with a production default, and a
|
|
128
|
+
test can hand it a fake:
|
|
108
129
|
|
|
109
130
|
```ruby
|
|
110
|
-
|
|
111
|
-
|
|
131
|
+
module Authenticators
|
|
132
|
+
class Session < Liberty::Authenticator
|
|
133
|
+
def initialize(users: UsersRepository.new)
|
|
134
|
+
@users = users
|
|
135
|
+
end
|
|
112
136
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
137
|
+
def principal
|
|
138
|
+
@users.find(request.env["rack.session"][:user_id])
|
|
139
|
+
end
|
|
116
140
|
|
|
117
|
-
|
|
118
|
-
'Bearer realm="api"'
|
|
141
|
+
def challenge_endpoint_class = RedirectToLogin
|
|
119
142
|
end
|
|
143
|
+
end
|
|
120
144
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
145
|
+
class RedirectToLogin < Liberty::Endpoint
|
|
146
|
+
def status = 303
|
|
147
|
+
|
|
148
|
+
def headers = {"location" => "/login"}
|
|
149
|
+
end
|
|
124
150
|
|
|
125
|
-
|
|
126
|
-
|
|
151
|
+
class Journal < Liberty::Endpoint
|
|
152
|
+
responds_to :get, "/journal", authenticated_by: Authenticators::Session
|
|
153
|
+
|
|
154
|
+
def html = "<h1>Welcome, #{principal.name}</h1>"
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Here's a bearer token. The credential arrives in the `Authorization` header, which every request
|
|
159
|
+
exposes as `request.headers[:authorization]`. Despite its name, that header carries a credential
|
|
160
|
+
that has not been checked yet. Checking it is the authenticator's job:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
module Authenticators
|
|
164
|
+
class Token < Liberty::Authenticator
|
|
165
|
+
def initialize(sessions: ApiSessionsRepository.new)
|
|
166
|
+
@sessions = sessions
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def principal
|
|
170
|
+
@sessions.find_by_token(bearer_token)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def challenge_endpoint_class = TokenChallenge
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
def bearer_token
|
|
178
|
+
request.headers[:authorization].to_s.delete_prefix("Bearer ")
|
|
179
|
+
end
|
|
127
180
|
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
class TokenChallenge < Liberty::Endpoint
|
|
184
|
+
def status = 401
|
|
128
185
|
|
|
129
|
-
|
|
186
|
+
def headers = {"www-authenticate" => 'Bearer realm="api"'}
|
|
130
187
|
|
|
131
|
-
def
|
|
132
|
-
|
|
188
|
+
def text = "Authentication required"
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
A page that anyone may see, but that still wants to know a signed-in user, is a session
|
|
193
|
+
authenticator that never challenges:
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
module Authenticators
|
|
197
|
+
class Optional < Session
|
|
198
|
+
def challenge_endpoint_class = nil
|
|
133
199
|
end
|
|
134
200
|
end
|
|
135
201
|
```
|
|
136
202
|
|
|
203
|
+
Authorization is the endpoint's own answer. When the principal may not do what the request asks,
|
|
204
|
+
the endpoint responds with the status and content it chooses, such as a 403.
|
|
205
|
+
|
|
137
206
|
## Installation
|
|
138
207
|
|
|
139
208
|
Install the gem and add to the application's Gemfile by executing:
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "forbidden_response"
|
|
4
|
-
require_relative "unauthenticated_response"
|
|
5
|
-
|
|
6
3
|
module Liberty
|
|
7
4
|
module Adapters
|
|
8
5
|
class Response
|
|
@@ -20,22 +17,11 @@ module Liberty
|
|
|
20
17
|
end
|
|
21
18
|
|
|
22
19
|
def to_rack_response
|
|
23
|
-
return unauthenticated_response unless endpoint.authenticated?
|
|
24
|
-
return forbidden_response unless endpoint.authorized?
|
|
25
|
-
|
|
26
20
|
[status, headers, rack_body]
|
|
27
21
|
end
|
|
28
22
|
|
|
29
23
|
private
|
|
30
24
|
|
|
31
|
-
def unauthenticated_response
|
|
32
|
-
UnauthenticatedResponse.new(endpoint).to_rack_response
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def forbidden_response
|
|
36
|
-
ForbiddenResponse.new(endpoint).to_rack_response
|
|
37
|
-
end
|
|
38
|
-
|
|
39
25
|
def status
|
|
40
26
|
endpoint.status
|
|
41
27
|
end
|
data/lib/liberty/application.rb
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "adapters/request"
|
|
4
4
|
require_relative "adapters/response"
|
|
5
|
+
require_relative "endpoint_builder"
|
|
5
6
|
|
|
6
7
|
module Liberty
|
|
7
8
|
class Application
|
|
8
|
-
attr_reader :endpoint_class
|
|
9
|
+
attr_reader :endpoint_class, :authenticator_class
|
|
9
10
|
|
|
10
|
-
def initialize(endpoint_class:)
|
|
11
|
+
def initialize(endpoint_class:, authenticator_class:)
|
|
11
12
|
@endpoint_class = endpoint_class
|
|
13
|
+
@authenticator_class = authenticator_class
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def call(env)
|
|
@@ -18,15 +20,19 @@ module Liberty
|
|
|
18
20
|
private
|
|
19
21
|
|
|
20
22
|
def response(env)
|
|
21
|
-
Adapters::Response.new(endpoint(
|
|
23
|
+
Adapters::Response.new(endpoint(env))
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def request(env)
|
|
25
27
|
Adapters::Request.new(env)
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
def endpoint(
|
|
29
|
-
|
|
30
|
+
def endpoint(env)
|
|
31
|
+
EndpointBuilder.new(
|
|
32
|
+
request: request(env),
|
|
33
|
+
endpoint_class: endpoint_class,
|
|
34
|
+
authenticator_class: authenticator_class
|
|
35
|
+
).endpoint
|
|
30
36
|
end
|
|
31
37
|
end
|
|
32
38
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
|
|
5
|
+
module Liberty
|
|
6
|
+
# The base authenticator. Liberty builds one per request, injects the
|
|
7
|
+
# request, and asks two questions. Subclasses must answer both.
|
|
8
|
+
#
|
|
9
|
+
# principal:
|
|
10
|
+
# who the request is from, or nil
|
|
11
|
+
#
|
|
12
|
+
# challenge_endpoint_class:
|
|
13
|
+
# the endpoint class that answers when there is no principal, or nil
|
|
14
|
+
# to admit the request anyway
|
|
15
|
+
#
|
|
16
|
+
# The request is injected after construction, so a subclass is free to
|
|
17
|
+
# define its own initializer with default dependencies.
|
|
18
|
+
class Authenticator
|
|
19
|
+
attr_reader :request
|
|
20
|
+
|
|
21
|
+
def inject(request:)
|
|
22
|
+
@request = request
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def principal
|
|
26
|
+
raise AbstractMethodError, "#{self.class} must implement #principal"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def challenge_endpoint_class
|
|
30
|
+
raise AbstractMethodError, "#{self.class} must implement #challenge_endpoint_class"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../authenticator"
|
|
4
|
+
|
|
5
|
+
module Liberty
|
|
6
|
+
module Authenticators
|
|
7
|
+
# The authenticator for open routes. It never finds a principal and never
|
|
8
|
+
# challenges, so every request is admitted with a nil principal.
|
|
9
|
+
class Public < Liberty::Authenticator
|
|
10
|
+
def principal
|
|
11
|
+
nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def challenge_endpoint_class
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/liberty/endpoint.rb
CHANGED
|
@@ -4,14 +4,20 @@ module Liberty
|
|
|
4
4
|
class Endpoint
|
|
5
5
|
DEFAULT_STATUS = 200
|
|
6
6
|
|
|
7
|
-
def self.responds_to(verb, path)
|
|
8
|
-
Liberty.add_endpoint(
|
|
7
|
+
def self.responds_to(verb, path, authenticated_by:)
|
|
8
|
+
Liberty.add_endpoint(
|
|
9
|
+
verb: verb,
|
|
10
|
+
path: path,
|
|
11
|
+
endpoint_class: self,
|
|
12
|
+
authenticator_class: authenticated_by
|
|
13
|
+
)
|
|
9
14
|
end
|
|
10
15
|
|
|
11
|
-
attr_reader :request
|
|
16
|
+
attr_reader :request, :principal
|
|
12
17
|
|
|
13
|
-
def inject(request:)
|
|
18
|
+
def inject(request:, principal: nil)
|
|
14
19
|
@request = request
|
|
20
|
+
@principal = principal
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
def params
|
|
@@ -22,17 +28,6 @@ module Liberty
|
|
|
22
28
|
request.headers[:preferred_media_type]
|
|
23
29
|
end
|
|
24
30
|
|
|
25
|
-
def authenticated?
|
|
26
|
-
false
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def www_authenticate_header
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def authorized?
|
|
33
|
-
false
|
|
34
|
-
end
|
|
35
|
-
|
|
36
31
|
def status
|
|
37
32
|
DEFAULT_STATUS
|
|
38
33
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Liberty
|
|
4
|
+
class EndpointBuilder
|
|
5
|
+
attr_reader :request, :endpoint_class, :authenticator_class
|
|
6
|
+
|
|
7
|
+
def initialize(request:, endpoint_class:, authenticator_class:)
|
|
8
|
+
@request = request
|
|
9
|
+
@endpoint_class = endpoint_class
|
|
10
|
+
@authenticator_class = authenticator_class
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def endpoint
|
|
14
|
+
return build_challenge_endpoint if challenge?
|
|
15
|
+
|
|
16
|
+
build_endpoint
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def build_challenge_endpoint
|
|
22
|
+
challenge_endpoint_class.new.tap { |endpoint| inject_dependencies(endpoint) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def build_endpoint
|
|
26
|
+
endpoint_class.new.tap { |endpoint| inject_dependencies(endpoint) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def principal
|
|
30
|
+
@principal ||= authenticator.principal
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def challenge_endpoint_class
|
|
34
|
+
@challenge_endpoint_class ||= authenticator.challenge_endpoint_class
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def challenge?
|
|
38
|
+
principal.nil? && !challenge_endpoint_class.nil?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def authenticator
|
|
42
|
+
@authenticator ||= authenticator_class.new.tap { |authenticator| authenticator.inject(request: request) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def inject_dependencies(endpoint)
|
|
46
|
+
endpoint.inject(request: request, principal: principal)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Liberty
|
|
4
|
+
# The base for every error Liberty raises, so an application can rescue
|
|
5
|
+
# them all with one clause.
|
|
6
|
+
class Error < StandardError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Raised when a subclass leaves a method unimplemented that Liberty
|
|
10
|
+
# requires an answer to.
|
|
11
|
+
class AbstractMethodError < Error
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/liberty/version.rb
CHANGED
data/lib/liberty.rb
CHANGED
|
@@ -7,8 +7,11 @@ require "rack/accept_media_types"
|
|
|
7
7
|
require "rack/abstract_format"
|
|
8
8
|
|
|
9
9
|
require_relative "liberty/version"
|
|
10
|
+
require_relative "liberty/errors"
|
|
10
11
|
require_relative "liberty/endpoint"
|
|
12
|
+
require_relative "liberty/authenticators/public"
|
|
11
13
|
require_relative "liberty/router"
|
|
14
|
+
require_relative "liberty/endpoint_builder"
|
|
12
15
|
require_relative "liberty/application"
|
|
13
16
|
require_relative "liberty/cors"
|
|
14
17
|
|
|
@@ -18,14 +21,15 @@ module Liberty
|
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def self.middleware
|
|
21
|
-
@middleware ||=
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
@middleware ||=
|
|
25
|
+
Rack::Builder.new(Liberty.router) do
|
|
26
|
+
use(Liberty::CORS::Middleware)
|
|
27
|
+
use(Rack::AbstractFormat)
|
|
28
|
+
end
|
|
25
29
|
end
|
|
26
30
|
|
|
27
|
-
def self.add_endpoint(verb:, path:, endpoint_class:)
|
|
28
|
-
app = Application.new(endpoint_class:
|
|
31
|
+
def self.add_endpoint(verb:, path:, endpoint_class:, authenticator_class:)
|
|
32
|
+
app = Application.new(endpoint_class:, authenticator_class:)
|
|
29
33
|
router.public_send(verb, path, to: app)
|
|
30
34
|
end
|
|
31
35
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: liberty
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alan Ridlehoover
|
|
@@ -116,18 +116,19 @@ files:
|
|
|
116
116
|
- Rakefile
|
|
117
117
|
- assets/liberty.png
|
|
118
118
|
- lib/liberty.rb
|
|
119
|
-
- lib/liberty/adapters/error_response.rb
|
|
120
|
-
- lib/liberty/adapters/forbidden_response.rb
|
|
121
119
|
- lib/liberty/adapters/parsers/factory.rb
|
|
122
120
|
- lib/liberty/adapters/parsers/json.rb
|
|
123
121
|
- lib/liberty/adapters/parsers/null.rb
|
|
124
122
|
- lib/liberty/adapters/request.rb
|
|
125
123
|
- lib/liberty/adapters/response.rb
|
|
126
|
-
- lib/liberty/adapters/unauthenticated_response.rb
|
|
127
124
|
- lib/liberty/application.rb
|
|
125
|
+
- lib/liberty/authenticator.rb
|
|
126
|
+
- lib/liberty/authenticators/public.rb
|
|
128
127
|
- lib/liberty/cors.rb
|
|
129
128
|
- lib/liberty/cors/middleware.rb
|
|
130
129
|
- lib/liberty/endpoint.rb
|
|
130
|
+
- lib/liberty/endpoint_builder.rb
|
|
131
|
+
- lib/liberty/errors.rb
|
|
131
132
|
- lib/liberty/router.rb
|
|
132
133
|
- lib/liberty/router/node.rb
|
|
133
134
|
- lib/liberty/router/printer.rb
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Liberty
|
|
4
|
-
module Adapters
|
|
5
|
-
class ErrorResponse
|
|
6
|
-
attr_reader :endpoint
|
|
7
|
-
|
|
8
|
-
def initialize(endpoint)
|
|
9
|
-
@endpoint = endpoint
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def to_rack_response
|
|
13
|
-
[status, headers, rack_body]
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def headers
|
|
19
|
-
{
|
|
20
|
-
Response::CONTENT_LENGTH => body.bytesize.to_s,
|
|
21
|
-
Response::CONTENT_TYPE => Response::MIME_TYPE_TEXT
|
|
22
|
-
}
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def rack_body
|
|
26
|
-
endpoint.request.head? ? [] : [body]
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "error_response"
|
|
4
|
-
|
|
5
|
-
module Liberty
|
|
6
|
-
module Adapters
|
|
7
|
-
class ForbiddenResponse < ErrorResponse
|
|
8
|
-
private
|
|
9
|
-
|
|
10
|
-
def status
|
|
11
|
-
403
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def body
|
|
15
|
-
"Forbidden"
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "error_response"
|
|
4
|
-
|
|
5
|
-
module Liberty
|
|
6
|
-
module Adapters
|
|
7
|
-
class UnauthenticatedResponse < ErrorResponse
|
|
8
|
-
WWW_AUTHENTICATE = "www-authenticate"
|
|
9
|
-
|
|
10
|
-
private
|
|
11
|
-
|
|
12
|
-
def status
|
|
13
|
-
401
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def headers
|
|
17
|
-
challenge ? super.merge(WWW_AUTHENTICATE => challenge) : super
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def challenge
|
|
21
|
-
endpoint.www_authenticate_header
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def body
|
|
25
|
-
"Authentication required"
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|