liberty 0.3.1 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44e1f59a2b827b9bda2ec873fca9e2592db73e23d9ad946ee673be3cca0c8d37
4
- data.tar.gz: 8ed828dddae3cc7fbce9957bce6d66a11801f13499761728ca03befd954655cf
3
+ metadata.gz: 9450a8fadf0c6726d4249f2b6a62f4918045795f199be8ec8bec29412bbaebc1
4
+ data.tar.gz: 55ea606054dabaeec94add7f6b5fe63a5d38add6eca13dc9f4fc1ef2754ad5c2
5
5
  SHA512:
6
- metadata.gz: 77a90f8920aef4886192435fecc60c46bdb317405ff447db995b9fd264136bd996c6b3b1123c502b7ad375addc8c2756a6dcecfeccdd3bf573b67b54e4d62703
7
- data.tar.gz: 7891e701f9c931e6e43488e2b45bdd47eb0790e4b8fd098aeb7870386460db43d36357768f1010bac54e819530bdad99dc7e26147e92e351af24a0cbb7b2ad84
6
+ metadata.gz: 17e9cd7824c5b5038daee7fd5d3d441886865dc78562797cd502b8f96f6fd9cd48b2ed92da934265321757a2237e3682092e8653b1b4370260d73b3d9caef253
7
+ data.tar.gz: 7416e1f47f06cb05dd7fbaadc777f71bbac9bc828a5954f4db401c52e42abc88777ee3fd49ad1e24e22e412953e7d2b188b2a6983b153ea36f535f02a5f7d48c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## [0.4.0] - 2026-09-15
2
+
3
+ ### Breaking changes
4
+
5
+ - Endpoints now offer `authenticated?` and `authorized?` hooks for plugging in your own auth handlers. Both default to `false` to prevent endpoints from being exposed accidentally, so every existing endpoint responds with a 401 until it overrides them. Applications upgrading from older versions must override these methods, either to return `true` or with code that handles auth.
6
+
7
+ ### Added
8
+
9
+ - Responds with `401 Authentication required` when `authenticated?` returns `false`, and with `403 Forbidden` when `authorized?` returns `false`. The endpoint's status, headers, and content are only consulted when both return `true`.
10
+ - Adds a `www_authenticate_header` hook to endpoints. When it returns a challenge, such as `Bearer realm="api"`, 401 responses include it as the `WWW-Authenticate` header. It defaults to `nil`, which omits the header.
11
+ - Exposes the request's `Authorization` header as `request.headers[:authorization]` so endpoints can read credentials.
12
+ - Responds to `HEAD` requests that fail auth with an empty body and a `content-length` header, as the Rack specification requires.
13
+
1
14
  ## [0.3.1] - 2026-09-07
2
15
 
3
16
  ### Fixed
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- liberty (0.3.1)
4
+ liberty (0.4.0)
5
5
  json (~> 2.3)
6
6
  mustermann (~> 4.0)
7
7
  mustermann-contrib (~> 4.0)
data/README.md CHANGED
@@ -50,6 +50,14 @@ Here's an example for an HTTP get to the `/hello` route, which returns a hello w
50
50
  class MyEndpoint < Liberty::Endpoint
51
51
  responds_to :get, '/hello'
52
52
 
53
+ def authenticated?
54
+ true
55
+ end
56
+
57
+ def authorized?
58
+ true
59
+ end
60
+
53
61
  def status
54
62
  200
55
63
  end
@@ -83,6 +91,49 @@ end
83
91
  If you configure your CORS headers before you launch your application, `Endpoints` will
84
92
  automatically respond with the right headers.
85
93
 
94
+ ### Authentication & Authorization
95
+
96
+ Every `Endpoint` has two hooks: `#authenticated?` and `#authorized?`. Both default to `false`,
97
+ so an endpoint that does not implement them responds with `401 Authentication required`. Override
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]`.
102
+
103
+ A 401 response should tell the client how to authenticate. Override `#www_authenticate_header`
104
+ to return the challenge, such as `Bearer realm="api"`, and Liberty adds it as the
105
+ `WWW-Authenticate` header on 401 responses. When it returns `nil` the header is omitted.
106
+
107
+ Here's an example of how to use `#authenticated?` and `#authorized?`:
108
+
109
+ ```ruby
110
+ class YourEndpoint < Liberty::Endpoint
111
+ responds_to :get, "/your_endpoint"
112
+
113
+ def authenticated?
114
+ !current_user.nil?
115
+ end
116
+
117
+ def www_authenticate_header
118
+ 'Bearer realm="api"'
119
+ end
120
+
121
+ def authorized?
122
+ current_user.admin?
123
+ end
124
+
125
+ def current_user
126
+ @current_user ||= Sessions.find_by_token(bearer_token)
127
+ end
128
+
129
+ private
130
+
131
+ def bearer_token
132
+ request.headers[:authorization].to_s.delete_prefix("Bearer ")
133
+ end
134
+ end
135
+ ```
136
+
86
137
  ## Installation
87
138
 
88
139
  Install the gem and add to the application's Gemfile by executing:
@@ -0,0 +1,30 @@
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
@@ -0,0 +1,19 @@
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
@@ -7,6 +7,7 @@ module Liberty
7
7
  class Request
8
8
  PARSED_BODY = "parsed_body"
9
9
  ROUTER_PARAMS = "router.params"
10
+ AUTHORIZATION = "HTTP_AUTHORIZATION"
10
11
 
11
12
  attr_reader :env
12
13
 
@@ -15,10 +16,7 @@ module Liberty
15
16
  end
16
17
 
17
18
  def headers
18
- @headers ||= env ? {
19
- accept_media_types: accept_media_types,
20
- preferred_media_type: preferred_media_type
21
- } : {}
19
+ @headers ||= env ? env_headers : {}
22
20
  end
23
21
 
24
22
  def params
@@ -31,6 +29,14 @@ module Liberty
31
29
 
32
30
  private
33
31
 
32
+ def env_headers
33
+ {
34
+ accept_media_types: accept_media_types,
35
+ preferred_media_type: preferred_media_type,
36
+ authorization: env[AUTHORIZATION]
37
+ }
38
+ end
39
+
34
40
  def all_params
35
41
  form_params.merge!(body_params).merge!(url_params)
36
42
  end
@@ -73,7 +79,9 @@ module Liberty
73
79
  end
74
80
 
75
81
  def symbolize_keys(hash)
76
- hash.each_with_object({}) { |(key, value), obj| obj[key.to_sym] = value }
82
+ hash.each_with_object({}) do |(key, value), obj|
83
+ obj[key.to_sym] = value
84
+ end
77
85
  end
78
86
 
79
87
  def rack_request
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "forbidden_response"
4
+ require_relative "unauthenticated_response"
5
+
3
6
  module Liberty
4
7
  module Adapters
5
8
  class Response
@@ -17,11 +20,22 @@ module Liberty
17
20
  end
18
21
 
19
22
  def to_rack_response
23
+ return unauthenticated_response unless endpoint.authenticated?
24
+ return forbidden_response unless endpoint.authorized?
25
+
20
26
  [status, headers, rack_body]
21
27
  end
22
28
 
23
29
  private
24
30
 
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
+
25
39
  def status
26
40
  endpoint.status
27
41
  end
@@ -0,0 +1,29 @@
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
@@ -22,6 +22,17 @@ module Liberty
22
22
  request.headers[:preferred_media_type]
23
23
  end
24
24
 
25
+ def authenticated?
26
+ false
27
+ end
28
+
29
+ def www_authenticate_header
30
+ end
31
+
32
+ def authorized?
33
+ false
34
+ end
35
+
25
36
  def status
26
37
  DEFAULT_STATUS
27
38
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Liberty
4
- VERSION = "0.3.1"
4
+ VERSION = "0.4.0"
5
5
  end
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.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alan Ridlehoover
@@ -116,11 +116,14 @@ 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
119
121
  - lib/liberty/adapters/parsers/factory.rb
120
122
  - lib/liberty/adapters/parsers/json.rb
121
123
  - lib/liberty/adapters/parsers/null.rb
122
124
  - lib/liberty/adapters/request.rb
123
125
  - lib/liberty/adapters/response.rb
126
+ - lib/liberty/adapters/unauthenticated_response.rb
124
127
  - lib/liberty/application.rb
125
128
  - lib/liberty/cors.rb
126
129
  - lib/liberty/cors/middleware.rb