blue_factory 0.1.2 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +51 -3
- data/lib/blue_factory/configuration.rb +1 -1
- data/lib/blue_factory/errors.rb +12 -0
- data/lib/blue_factory/server.rb +62 -20
- data/lib/blue_factory/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7f8f8505228a075534b74c48c3011441aca5f8710bb1dc6fd769fd62cb60d71
|
4
|
+
data.tar.gz: 303454ba19fee635367049603810cb1ebb7662665ccc05ce99c035b063c94b2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17b3606d68a5bc89557e78fc85478319499e84ce903fdc2f4e56a8221f923435f0c5a55b43a1a64fc974dc843b38d6d9a672480e56fe12c3f5eb6e26fbb5cff6
|
7
|
+
data.tar.gz: b53d4ac56b668b15f256bf5a87abd8f8195d0a91e3f7308b78f24454be9d436a05c539fb31b14fd6febe3b6a7621b7e151ef2c90b3963c64d52c743bd30bb7c6
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## [0.1.4] - 2023-08-19
|
2
|
+
|
3
|
+
- implemented partial authentication, without signature verification (`enable_unsafe_auth` option)
|
4
|
+
|
5
|
+
## [0.1.3] - 2023-07-27
|
6
|
+
|
7
|
+
- fixed incorrect response when reaching the end of the feed
|
8
|
+
|
1
9
|
## [0.1.2] - 2023-06-15
|
2
10
|
|
3
11
|
- added validation for feed rkey
|
data/README.md
CHANGED
@@ -40,7 +40,8 @@ BlueFactory.add_feed 'starwars', StarWarsFeed.new
|
|
40
40
|
|
41
41
|
The `get_posts` method of the feed object should:
|
42
42
|
|
43
|
-
- accept a
|
43
|
+
- accept a `params` argument which is a hash with fields: `:feed`, `:cursor` and `:limit` (the last two are optional)
|
44
|
+
- optionally, accept a second `current_user` argument which is a string with the authenticated user's DID (depends on authentication config - [see below](#authentication))
|
44
45
|
- return a hash with two fields: `:cursor` and `:posts`
|
45
46
|
|
46
47
|
The `:feed` is the `at://` URI of the feed. The `:cursor` param, if included, should be a cursor returned by your feed from one of the previous requests, so it should be in the format used by the same function - but anyone can call the endpoint with any params, so you should validate it. The cursor is used for pagination to provide more pages further down in the feed (the first request to load the top of the feed doesn't include a cursor).
|
@@ -59,7 +60,7 @@ An example implementation could look like this:
|
|
59
60
|
require 'time'
|
60
61
|
|
61
62
|
class StarWarsFeed
|
62
|
-
def get_posts(params)
|
63
|
+
def get_posts(params, current_user = nil)
|
63
64
|
limit = check_query_limit(params)
|
64
65
|
query = Post.select('uri, time').order('time DESC').limit(limit)
|
65
66
|
|
@@ -122,7 +123,54 @@ server {
|
|
122
123
|
}
|
123
124
|
```
|
124
125
|
|
125
|
-
|
126
|
+
## Authentication
|
127
|
+
|
128
|
+
Feeds are authenticated using [JSON Web Tokens](https://jwt.io). When a user opens, refreshes or scrolls down a feed in their app, a request is made to the feed service from the Bluesky network's IP address with user's authentication token in the `Authorization` HTTP header.
|
129
|
+
|
130
|
+
At the moment, Blue Factory handles authentication in a very simplified way - it extracts the user's DID from the authentication header, but it does not verify the signature. This means that anyone can trivially prepare a fake token and make requests to the `getFeedSkeleton` endpoint as a different user.
|
131
|
+
|
132
|
+
As such, this authentication should not be used for anything critical. It may be used for things like logging, analytics, or as "security by obscurity" to just discourage others from accessing the feed in the app.
|
133
|
+
|
134
|
+
To use this simple authentication, set the `enable_unsafe_auth` option:
|
135
|
+
|
136
|
+
```rb
|
137
|
+
BlueFactory.set :enable_unsafe_auth, true
|
138
|
+
```
|
139
|
+
|
140
|
+
The user's DID extracted from the token is passed as a second argument to `#get_posts`. You may, for example, return an empty list when the user is not authorized to use it:
|
141
|
+
|
142
|
+
```rb
|
143
|
+
class HiddenFeed
|
144
|
+
def get_posts(params, current_user)
|
145
|
+
if AUTHORIZED_USERS.include?(current_user)
|
146
|
+
# ...
|
147
|
+
else
|
148
|
+
{ posts: [] }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
Alternatively, you can raise a `BlueFactory::AuthorizationError` with an optional custom message. This will return a 401 status response to the Bluesky app, which will make it display the pink error banner in the app:
|
155
|
+
|
156
|
+
```rb
|
157
|
+
class HiddenFeed
|
158
|
+
def get_posts(params, current_user)
|
159
|
+
if AUTHORIZED_USERS.include?(current_user)
|
160
|
+
# ...
|
161
|
+
else
|
162
|
+
raise BlueFactory::AuthorizationError, "You shall not pass!"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
<p><img width="400" src="https://github.com/mackuba/blue_factory/assets/28465/9197c0ec-9302-4ca0-b06c-3fce2e0fa4f4"></p>
|
169
|
+
|
170
|
+
Note: the `current_user` may be `nil` if the authentication header is not set at all (which may happen if you access the endpoint e.g. with `curl` or in a browser).
|
171
|
+
|
172
|
+
|
173
|
+
## Additional configuration & customizing
|
126
174
|
|
127
175
|
You can use the [Sinatra API](https://sinatrarb.com/intro.html#configuration) to do any additional configuration, like changing the server port, enabling/disabling logging and so on.
|
128
176
|
|
@@ -13,7 +13,7 @@ module BlueFactory
|
|
13
13
|
(ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
|
14
14
|
end
|
15
15
|
|
16
|
-
configurable :publisher_did, :hostname, :validate_responses
|
16
|
+
configurable :publisher_did, :hostname, :validate_responses, :enable_unsafe_auth
|
17
17
|
|
18
18
|
set :validate_responses, (environment != :production)
|
19
19
|
end
|
data/lib/blue_factory/errors.rb
CHANGED
@@ -1,4 +1,13 @@
|
|
1
1
|
module BlueFactory
|
2
|
+
class AuthorizationError < StandardError
|
3
|
+
attr_reader :error_type
|
4
|
+
|
5
|
+
def initialize(message = "Authentication required", error_type = nil)
|
6
|
+
super(message)
|
7
|
+
@error_type = error_type
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
2
11
|
class InvalidKeyError < StandardError
|
3
12
|
end
|
4
13
|
|
@@ -13,4 +22,7 @@ module BlueFactory
|
|
13
22
|
|
14
23
|
class InvalidResponseError < StandardError
|
15
24
|
end
|
25
|
+
|
26
|
+
class UnsupportedAlgorithmError < StandardError
|
27
|
+
end
|
16
28
|
end
|
data/lib/blue_factory/server.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'base64'
|
1
2
|
require 'json'
|
2
3
|
require 'sinatra/base'
|
3
4
|
|
@@ -33,6 +34,48 @@ module BlueFactory
|
|
33
34
|
[status, JSON.generate({ error: name, message: message })]
|
34
35
|
end
|
35
36
|
|
37
|
+
def get_feed
|
38
|
+
if params[:feed].to_s.empty?
|
39
|
+
raise InvalidResponseError, "Error: Params must have the property \"feed\""
|
40
|
+
end
|
41
|
+
|
42
|
+
if params[:feed] !~ %r(^at://[\w\-\.\:]+/[\w\.]+/[\w\.\-]+$)
|
43
|
+
raise InvalidResponseError, "Error: feed must be a valid at-uri"
|
44
|
+
end
|
45
|
+
|
46
|
+
feed_key = params[:feed].split('/').last
|
47
|
+
feed = config.get_feed(feed_key)
|
48
|
+
|
49
|
+
if feed.nil? || feed_uri(feed_key) != params[:feed]
|
50
|
+
raise UnsupportedAlgorithmError, "Unsupported algorithm"
|
51
|
+
end
|
52
|
+
|
53
|
+
feed
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse_did_from_token
|
57
|
+
auth = env['HTTP_AUTHORIZATION']
|
58
|
+
|
59
|
+
if auth.to_s.strip.empty?
|
60
|
+
return nil
|
61
|
+
end
|
62
|
+
|
63
|
+
if !auth.start_with?('Bearer ')
|
64
|
+
raise AuthorizationError, "Unsupported authorization method"
|
65
|
+
end
|
66
|
+
|
67
|
+
token = auth.gsub(/^Bearer /, '')
|
68
|
+
parts = token.split('.')
|
69
|
+
raise AuthorizationError.new("Invalid JWT format", "BadJwt") unless parts.length == 3
|
70
|
+
|
71
|
+
begin
|
72
|
+
payload = JSON.parse(Base64.decode64(parts[1]))
|
73
|
+
payload['iss']
|
74
|
+
rescue StandardError => e
|
75
|
+
raise AuthorizationError.new("Invalid JWT format", "BadJwt")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
36
79
|
def validate_response(response)
|
37
80
|
cursor = response[:cursor]
|
38
81
|
raise InvalidResponseError, ":cursor key is missing" unless response.has_key?(:cursor)
|
@@ -50,31 +93,30 @@ module BlueFactory
|
|
50
93
|
end
|
51
94
|
|
52
95
|
get '/xrpc/app.bsky.feed.getFeedSkeleton' do
|
53
|
-
if params[:feed].to_s.empty?
|
54
|
-
return json_error("InvalidRequest", "Error: Params must have the property \"feed\"")
|
55
|
-
end
|
56
|
-
|
57
|
-
if params[:feed] !~ %r(^at://[\w\-\.\:]+/[\w\.]+/[\w\.\-]+$)
|
58
|
-
return json_error("InvalidRequest", "Error: feed must be a valid at-uri")
|
59
|
-
end
|
60
|
-
|
61
|
-
feed_key = params[:feed].split('/').last
|
62
|
-
feed = config.get_feed(feed_key)
|
63
|
-
|
64
|
-
if feed.nil? || feed_uri(feed_key) != params[:feed]
|
65
|
-
return json_error("UnsupportedAlgorithm", "Unsupported algorithm")
|
66
|
-
end
|
67
|
-
|
68
96
|
begin
|
69
|
-
|
97
|
+
feed = get_feed
|
98
|
+
args = params.slice(:feed, :cursor, :limit)
|
99
|
+
|
100
|
+
if config.enable_unsafe_auth
|
101
|
+
did = parse_did_from_token
|
102
|
+
response = feed.get_posts(args, did)
|
103
|
+
else
|
104
|
+
response = feed.get_posts(args)
|
105
|
+
end
|
106
|
+
|
70
107
|
validate_response(response) if config.validate_responses
|
71
108
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
109
|
+
output = {}
|
110
|
+
output[:feed] = response[:posts].map { |s| { post: s }}
|
111
|
+
output[:cursor] = response[:cursor] if response[:cursor]
|
112
|
+
|
113
|
+
return json(output)
|
76
114
|
rescue InvalidRequestError => e
|
77
115
|
return json_error(e.error_type || "InvalidRequest", e.message)
|
116
|
+
rescue AuthorizationError => e
|
117
|
+
return json_error(e.error_type || "AuthenticationRequired", e.message, status: 401)
|
118
|
+
rescue UnsupportedAlgorithmError => e
|
119
|
+
return json_error("UnsupportedAlgorithm", e.message)
|
78
120
|
rescue InvalidResponseError => e
|
79
121
|
return json_error("InvalidResponse", e.message)
|
80
122
|
end
|
data/lib/blue_factory/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blue_factory
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kuba Suder
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sinatra
|