didkit 0.2.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1dfc5eee5f96a374d8f67151f8d44f5eafffc8a8e3624d7fe507e783d5ff814
4
- data.tar.gz: 74f0cd8da39a5e723b806e07560bd15325d2176c93ca746d619975e6b0510c77
3
+ metadata.gz: ae1ff103a9695991ae3e0e97072c2b10731e7c18e44cfa5f715995ef0631df4f
4
+ data.tar.gz: b2bc822873a7804515d3ad06caa6c15e4177943668f6bcbb79f8c48fa87e4733
5
5
  SHA512:
6
- metadata.gz: 976f7d9a63ad453dde3064872dafb558cbc80f96db0a61d5243abb68664b360a38ede6cfe5a702df7609b60c6d7c6e917ffc1a0479351bb89c7c693af5ffb3c7
7
- data.tar.gz: da1e08939b8c19d62ceef10bd48d278b7190fffdbcad65385f9ca36edd9377d0ee37d1b1d78e8ae963304dd06863cdcf9fa992a504b3e219c1f01dbf60248f6a
6
+ metadata.gz: 5e4bbd991480a0a98c13c514e52a65bdceafd1c1a4feb75d60db07b22af10f376edaaea89bd98122c94c902c718c241a01965abaa801433b2a48162bb86d6ec1
7
+ data.tar.gz: 791481ed39520a72eb750e2d511f6017ca476a1386c15d6d6a667e2a170a7e63d0723e868b2d843c87d66d3d33306fcc6bd5cd3d359cbea8e213e6ba8a401f99
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [0.2.3] - 2024-07-02
2
+
3
+ - added a `DID#get_audit_log` method that fetches the PLC audit log for a DID
4
+ - added a way to set an error handler in `PLCImporter`
5
+ - reverted the change from 0.2.1 that added Ruby stdlib dependencies explicitly to the gemspec, since this causes more problems than it's worth
6
+ - minor bug fixes
7
+
8
+ ## [0.2.2] - 2024-04-01
9
+
10
+ - added helpers for checking if a DID is known by (federated with) a relay or if the repo exists on its assigned PDS
11
+
1
12
  ## [0.2.1] - 2024-03-26
2
13
 
3
14
  - tweaked validations in `Document` and `PLCOperation` to make them more aligned with what might be expected
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
- # DIDKit
1
+ # DIDKit 🪪
2
2
 
3
- A small Ruby gem for handling Distributed Identifiers (DIDs) in Bluesky / AT Protocol
3
+ A small Ruby gem for handling Distributed Identifiers (DIDs) in Bluesky / AT Protocol.
4
+
5
+ > [!NOTE]
6
+ > ATProto Ruby gems collection: [skyfall](https://github.com/mackuba/skyfall) | [blue_factory](https://github.com/mackuba/blue_factory) | [minisky](https://github.com/mackuba/minisky) | [didkit](https://github.com/mackuba/didkit)
4
7
 
5
8
 
6
9
  ## What does it do
data/lib/didkit/did.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  require_relative 'errors'
2
+ require_relative 'requests'
2
3
  require_relative 'resolver'
3
4
 
4
5
  module DIDKit
5
6
  class DID
7
+ include Requests
8
+
6
9
  def self.resolve_handle(handle)
7
10
  Resolver.new.resolve_handle(handle)
8
11
  end
@@ -34,10 +37,46 @@ module DIDKit
34
37
  Resolver.new.get_validated_handle(self)
35
38
  end
36
39
 
40
+ def get_audit_log
41
+ if @type == :plc
42
+ PLCImporter.new.fetch_audit_log(self)
43
+ else
44
+ raise DIDError.new("Audit log not supported for did:#{@type}")
45
+ end
46
+ end
47
+
37
48
  def web_domain
38
49
  did.gsub(/^did\:web\:/, '') if type == :web
39
50
  end
40
51
 
52
+ def is_known_by_relay?(relay, options = {})
53
+ relay_host = relay.include?('://') ? URI(relay).origin : "https://#{relay}"
54
+ url = URI("#{relay_host}/xrpc/com.atproto.sync.getLatestCommit")
55
+ url.query = URI.encode_www_form(:did => did)
56
+
57
+ response = get_response(url, { timeout: 30, max_redirects: 5 }.merge(options))
58
+ status = response.code.to_i
59
+ is_json = (response['Content-Type'] =~ /^application\/json(;.*)?$/)
60
+
61
+ if status == 200
62
+ true
63
+ elsif status == 400 && is_json && JSON.parse(response.body)['error'] == 'RepoNotFound'
64
+ false
65
+ elsif status == 404 && is_json && JSON.parse(response.body)['error']
66
+ false
67
+ else
68
+ raise APIError.new(response)
69
+ end
70
+ end
71
+
72
+ def account_exists?
73
+ doc = get_document
74
+ return false if doc.pds_endpoint.nil?
75
+
76
+ pds_host = URI(doc.pds_endpoint).origin
77
+ is_known_by_relay?(pds_host, timeout: 10)
78
+ end
79
+
41
80
  def ==(other)
42
81
  if other.is_a?(String)
43
82
  self.did == other
data/lib/didkit/errors.rb CHANGED
@@ -1,4 +1,21 @@
1
1
  module DIDKit
2
2
  class DIDError < StandardError
3
3
  end
4
+
5
+ class APIError < StandardError
6
+ attr_reader :response
7
+
8
+ def initialize(response)
9
+ @response = response
10
+ super("APIError: #{response}")
11
+ end
12
+
13
+ def status
14
+ response.code.to_i
15
+ end
16
+
17
+ def body
18
+ response.body
19
+ end
20
+ end
4
21
  end
@@ -9,7 +9,7 @@ module DIDKit
9
9
  PLC_SERVICE = 'plc.directory'
10
10
  MAX_PAGE = 1000
11
11
 
12
- attr_accessor :ignore_errors, :last_date
12
+ attr_accessor :ignore_errors, :last_date, :error_handler
13
13
 
14
14
  def initialize(since: nil)
15
15
  if since.to_s == 'beginning'
@@ -22,14 +22,22 @@ module DIDKit
22
22
  @last_date = Time.now
23
23
  @eof = true
24
24
  end
25
-
26
- @ignore_errors = false
27
25
  end
28
26
 
29
27
  def plc_service
30
28
  PLC_SERVICE
31
29
  end
32
30
 
31
+ def ignore_errors=(val)
32
+ @ignore_errors = val
33
+
34
+ if val
35
+ @error_handler = proc { |e, j| "(ignore error)" }
36
+ else
37
+ @error_handler = nil
38
+ end
39
+ end
40
+
33
41
  def get_export(args = {})
34
42
  url = URI("https://#{plc_service}/export")
35
43
  url.query = URI.encode_www_form(args)
@@ -38,6 +46,11 @@ module DIDKit
38
46
  data.lines.map(&:strip).reject(&:empty?).map { |x| JSON.parse(x) }
39
47
  end
40
48
 
49
+ def fetch_audit_log(did)
50
+ response = URI.open("https://#{plc_service}/#{did}/log/audit").read
51
+ JSON.parse(response).map { |j| PLCOperation.new(j) }
52
+ end
53
+
41
54
  def fetch_page
42
55
  request_time = Time.now
43
56
 
@@ -47,8 +60,9 @@ module DIDKit
47
60
  operations = rows.filter_map do |json|
48
61
  begin
49
62
  PLCOperation.new(json)
50
- rescue PLCOperation::FormatError => e
51
- ignore_errors ? nil : raise
63
+ rescue PLCOperation::FormatError, AtHandles::FormatError, ServiceRecord::FormatError => e
64
+ @error_handler ? @error_handler.call(e, json) : raise
65
+ nil
52
66
  end
53
67
  end
54
68
 
@@ -0,0 +1,28 @@
1
+ module DIDKit::Requests
2
+ def get_response(url, options = {})
3
+ url = URI(url) unless url.is_a?(URI)
4
+ request_options = { use_ssl: true }
5
+
6
+ if timeout = options[:timeout]
7
+ request_options[:open_timeout] = timeout
8
+ request_options[:read_timeout] = timeout
9
+ end
10
+
11
+ redirects = 0
12
+ max_redirects = options[:max_redirects] || 0
13
+
14
+ loop do
15
+ response = Net::HTTP.start(url.host, url.port, request_options) do |http|
16
+ request = Net::HTTP::Get.new(url)
17
+ http.request(request)
18
+ end
19
+
20
+ if response.is_a?(Net::HTTPRedirection) && redirects < max_redirects && (location = response['Location'])
21
+ url = URI(location.include?('://') ? location : (url.origin + location))
22
+ redirects += 1
23
+ else
24
+ return response
25
+ end
26
+ end
27
+ end
28
+ end
@@ -5,12 +5,15 @@ require 'resolv'
5
5
 
6
6
  require_relative 'did'
7
7
  require_relative 'document'
8
+ require_relative 'requests'
8
9
 
9
10
  module DIDKit
10
11
  class Resolver
11
12
  RESERVED_DOMAINS = %w(alt arpa example internal invalid local localhost onion test)
12
13
  MAX_REDIRECTS = 5
13
14
 
15
+ include Requests
16
+
14
17
  attr_accessor :nameserver
15
18
 
16
19
  def initialize(options = {})
@@ -32,9 +35,9 @@ module DIDKit
32
35
  end
33
36
 
34
37
  def resolve_handle_by_dns(domain)
35
- dns_records = Resolv::DNS.open(resolv_options) { |d|
38
+ dns_records = Resolv::DNS.open(resolv_options) do |d|
36
39
  d.getresources("_atproto.#{domain}", Resolv::DNS::Resource::IN::TXT)
37
- }
40
+ end
38
41
 
39
42
  if record = dns_records.first
40
43
  if string = record.strings.first
@@ -46,26 +49,11 @@ module DIDKit
46
49
  end
47
50
 
48
51
  def resolve_handle_by_well_known(domain)
49
- resolve_handle_from_url("https://#{domain}/.well-known/atproto-did")
50
- end
51
-
52
- def resolve_handle_from_url(url, redirects = 0)
53
- url = URI(url) unless url.is_a?(URI)
52
+ url = "https://#{domain}/.well-known/atproto-did"
53
+ response = get_response(url, timeout: 10, max_redirects: MAX_REDIRECTS)
54
54
 
55
- response = Net::HTTP.start(url.host, url.port, use_ssl: true, open_timeout: 10, read_timeout: 10) do |http|
56
- request = Net::HTTP::Get.new(url)
57
- http.request(request)
58
- end
59
-
60
- if response.is_a?(Net::HTTPSuccess)
61
- if text = response.body
62
- return parse_did_from_well_known(text)
63
- end
64
- elsif response.is_a?(Net::HTTPRedirection) && redirects < MAX_REDIRECTS
65
- if location = response['Location']
66
- target_url = location.include?('://') ? location : (url.origin + location)
67
- return resolve_handle_from_url(target_url, redirects + 1)
68
- end
55
+ if response.is_a?(Net::HTTPSuccess) && (text = response.body)
56
+ return parse_did_from_well_known(text)
69
57
  end
70
58
 
71
59
  nil
@@ -3,6 +3,9 @@ require_relative 'errors'
3
3
 
4
4
  module DIDKit
5
5
  class ServiceRecord
6
+ class FormatError < StandardError
7
+ end
8
+
6
9
  attr_reader :key, :type, :endpoint
7
10
 
8
11
  def initialize(key, type, endpoint)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DIDKit
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.3"
5
5
  end
metadata CHANGED
@@ -1,99 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: didkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kuba Suder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-26 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: json
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '2.5'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '2.5'
27
- - !ruby/object:Gem::Dependency
28
- name: net-http
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '0.1'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '0.1'
41
- - !ruby/object:Gem::Dependency
42
- name: open-uri
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '0.1'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '0.1'
55
- - !ruby/object:Gem::Dependency
56
- name: resolv
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '0.1'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '0.1'
69
- - !ruby/object:Gem::Dependency
70
- name: time
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '0.3'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '0.3'
83
- - !ruby/object:Gem::Dependency
84
- name: uri
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '0.13'
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '0.13'
11
+ date: 2024-07-02 00:00:00.000000000 Z
12
+ dependencies: []
97
13
  description:
98
14
  email:
99
15
  - jakub.suder@gmail.com
@@ -111,6 +27,7 @@ files:
111
27
  - lib/didkit/errors.rb
112
28
  - lib/didkit/plc_importer.rb
113
29
  - lib/didkit/plc_operation.rb
30
+ - lib/didkit/requests.rb
114
31
  - lib/didkit/resolver.rb
115
32
  - lib/didkit/service_record.rb
116
33
  - lib/didkit/services.rb