distributed-press-api-client 0.2.3 → 0.3.0rc0

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: 97a9c36acefc2ea476545820078749cba9d3a54c84e16ad179396193bbe4b9de
4
- data.tar.gz: 6a20be7873ecaf8a11417511e493edf3cd041195cf55b7e4e785c9ae56e24c5a
3
+ metadata.gz: 25fa00e1c0a4044f03953f30018830d9cc3e934445be8bd1e72004e0c1f5b1bf
4
+ data.tar.gz: 0ddb5921a185fb2172669957b3a22d8619fa6a0d2d53fe33a5bb10372706a1b4
5
5
  SHA512:
6
- metadata.gz: d66d0f0b879114c2feab58a353ebd508a88f79e3d5d3d4e9f6b7f16c44cad6bcb9fd9358526f72acbfe0a92fc46eed168ebc158a1a6b01c1f122b7aedd06d2c1
7
- data.tar.gz: 66edfeaa772e4b4fe763e67792ee1d1b15431714ef9a9c6fbbdf5ebd40edbd07cd62e710b598b0a4aca65d2c43fab36c202acb3bacafe78be8a47fb3112dc4f8
6
+ metadata.gz: 895ce13e70b28366fa412be512eeb075a179239a5eb7e45e302b58147baa55bccaba37505a93fdc97cef127d63faa655163eaa33c740bf2e5c334a3ab7879a5a
7
+ data.tar.gz: dcd6f8f7a6471ae979199de943350cff2b37ad5c148dbe8d143c6234cbb93e48dbc83b2794476bb0e7d193aff828dd591f96e747822bd085d89c9ef9c0a995e7
@@ -15,6 +15,7 @@ class DistributedPress
15
15
 
16
16
  # TODO: Validate domain name
17
17
  required(:domain).filled(:string)
18
+ required(:public).filled(:bool)
18
19
 
19
20
  required(:protocols).hash do
20
21
  required(:http).filled(:bool)
@@ -10,6 +10,7 @@ class DistributedPress
10
10
  class UpdateSite < Dry::Schema::JSON
11
11
  define do
12
12
  required(:id).filled(:string)
13
+ required(:public).filled(:bool)
13
14
 
14
15
  required(:protocols).hash do
15
16
  optional(:http).filled(:bool)
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+ require 'openssl'
5
+ require 'time'
6
+ require 'digest/sha2'
7
+ require 'uri'
8
+
9
+ class DistributedPress
10
+ module V1
11
+ module Social
12
+ # Social Distributed Press APIv1 client
13
+ #
14
+ # Inspired by Mastodon's Request
15
+ #
16
+ # @todo It'd be nice to implement this on HTTParty itself
17
+ # @see {https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb}
18
+ class Client
19
+ include ::HTTParty
20
+
21
+ # Signing algorithm
22
+ #
23
+ # @todo is it possible to use other algorithms?
24
+ ALGORITHM = 'rsa-sha256'
25
+ # Required by HTTP Signatures
26
+ REQUEST_TARGET = '(request-target)'
27
+ # Headers included in the signature
28
+ SIGNABLE_HEADERS = [REQUEST_TARGET, 'Host', 'Date', 'Digest']
29
+
30
+ # API URL
31
+ # @return [String]
32
+ attr_reader :url
33
+
34
+ # RSA key size
35
+ #
36
+ # @return [Integer]
37
+ attr_reader :key_size
38
+
39
+ # Public key URL
40
+ #
41
+ # @return [String]
42
+ attr_reader :public_key_url
43
+
44
+ # @param :url [String] Social Distributed Press URL
45
+ # @param :public_key [String] URL where public key is available
46
+ # @param :private_key_pem [String] RSA Private key, PEM encoded
47
+ # @param :key_size [Integer]
48
+ def initialize(url: 'https://social.distributed.press', public_key_url:, private_key_pem: nil, key_size: 2048)
49
+ self.class.default_options[:base_uri] = @url = HTTParty.normalize_base_uri(url)
50
+
51
+ @public_key_url = public_key_url
52
+ @key_size = key_size
53
+ @private_key_pem = private_key_pem
54
+ end
55
+
56
+ # POST request
57
+ #
58
+ # @todo Use DRY-schemas
59
+ # @param endpoint [String]
60
+ # @param body [Hash]
61
+ # @return [Hash]
62
+ def post(endpoint:, body:)
63
+ body = body.to_json
64
+ headers = default_headers
65
+
66
+ add_request_specific_headers! headers, 'post', endpoint
67
+ checksum_body! body, headers
68
+ sign_headers! headers
69
+
70
+ self.class.post(endpoint, body: body, headers: headers)
71
+ end
72
+
73
+ # Loads or generates a private key
74
+ #
75
+ # @return [OpenSSL::PKey::RSA]
76
+ def private_key
77
+ @private_key ||= OpenSSL::PKey::RSA.new(@private_key_pem || key_size)
78
+ end
79
+
80
+ # Public key
81
+ #
82
+ # @return [OpenSSL::PKey::RSA]
83
+ def public_key
84
+ private_key.public_key
85
+ end
86
+
87
+ # Host
88
+ #
89
+ # @return [String]
90
+ def host
91
+ @host ||= URI.parse(url).host
92
+ end
93
+
94
+ private
95
+
96
+ def default_headers
97
+ {
98
+ 'User-Agent' => "DistributedPress/#{DistributedPress::VERSION}",
99
+ 'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
100
+ 'Host' => host
101
+ }
102
+ end
103
+
104
+ # HTTP Signatures
105
+ #
106
+ # @see {https://docs.joinmastodon.org/spec/security/}
107
+ # @param :headers [Hash]
108
+ def sign_headers!(headers)
109
+ headers['Signature'] = {
110
+ 'keyId' => public_key_url,
111
+ 'algorithm' => ALGORITHM,
112
+ 'headers' => signable_headers(headers),
113
+ 'signature' => signed_headers(headers)
114
+ }.map do |key, value|
115
+ "#{key}=\"#{value}\""
116
+ end.join(',')
117
+
118
+ headers.delete REQUEST_TARGET
119
+
120
+ nil
121
+ end
122
+
123
+ # List of headers to be signed, removing headers that don't
124
+ # exist on the request.
125
+ #
126
+ # @return [String]
127
+ def signable_headers(headers)
128
+ (SIGNABLE_HEADERS & headers.keys).join(' ').downcase
129
+ end
130
+
131
+ # Sign headers
132
+ #
133
+ # @param :headers [Hash]
134
+ # @return [String]
135
+ def signed_headers(headers)
136
+ Base64.strict_encode64(
137
+ private_key.sign(
138
+ OpenSSL::Digest.new('SHA256'),
139
+ signature_content(headers)
140
+ )
141
+ )
142
+ end
143
+
144
+ # Generates a string to be signed
145
+ #
146
+ # @param :headers [Hash]
147
+ # @return [String]
148
+ def signature_content(headers)
149
+ headers.slice(*SIGNABLE_HEADERS).map do |key, value|
150
+ "#{key.downcase}: #{value}"
151
+ end.join("\n")
152
+ end
153
+
154
+ # Generate a checksum for the request body
155
+ #
156
+ # @param :body [String]
157
+ # @param :headers [Hash]
158
+ def checksum_body!(body, headers)
159
+ headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest body}"
160
+
161
+ nil
162
+ end
163
+
164
+ # Headers specific to a single request
165
+ #
166
+ # @param :headers [Hash] Headers
167
+ # @param :verb [String] HTTP verb
168
+ # @param :endpoint [String] Path
169
+ def add_request_specific_headers!(headers, verb, endpoint)
170
+ headers['Date'] = Time.now.utc.httpdate
171
+ headers[REQUEST_TARGET] = "#{verb} #{endpoint}"
172
+
173
+ nil
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'social/client'
4
+
5
+ class DistributedPress
6
+ module V1
7
+ # Social Distributed Press APIv1
8
+ module Social
9
+ end
10
+ end
11
+ end
@@ -3,5 +3,5 @@
3
3
  # API client
4
4
  class DistributedPress
5
5
  # Version
6
- VERSION = '0.2.3'
6
+ VERSION = '0.3.0rc0'
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: distributed-press-api-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0rc0
5
5
  platform: ruby
6
6
  authors:
7
7
  - f
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-31 00:00:00.000000000 Z
11
+ date: 2023-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -277,6 +277,8 @@ files:
277
277
  - lib/distributed_press/v1/schemas/token_header.rb
278
278
  - lib/distributed_press/v1/schemas/token_payload.rb
279
279
  - lib/distributed_press/v1/schemas/update_site.rb
280
+ - lib/distributed_press/v1/social.rb
281
+ - lib/distributed_press/v1/social/client.rb
280
282
  - lib/distributed_press/v1/token.rb
281
283
  - lib/distributed_press/version.rb
282
284
  - lib/jekyll-distributed-press-v0.rb
@@ -307,9 +309,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
307
309
  version: '2.7'
308
310
  required_rubygems_version: !ruby/object:Gem::Requirement
309
311
  requirements:
310
- - - ">="
312
+ - - ">"
311
313
  - !ruby/object:Gem::Version
312
- version: '0'
314
+ version: 1.3.1
313
315
  requirements: []
314
316
  rubygems_version: 3.3.26
315
317
  signing_key: