didkit 0.2.0 → 0.2.2

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: 0170f70f452400a399264cccd61a96a0ef96d570caca53547b2794eb8067d4bd
4
- data.tar.gz: 68197fe06b766fbe69d09d6d85b8abf59467f09560cb600b9af34bb900efaf6b
3
+ metadata.gz: fd7709e2b0973a44b71e939798901a6fd74bc9fa25e284958948fc4fe22de51e
4
+ data.tar.gz: b38397ddc0244f9035719dba7eb0fdcd844feb549e9628462da16b4aba662cf4
5
5
  SHA512:
6
- metadata.gz: 367f5acd94953397d5ca72edec0a43f2a96e8dfef3615d16210edc203abe14b1875c1e78316efe351e223c881cbc80d2fd6cf7314753c78caea6c9ccb7ed78d0
7
- data.tar.gz: 15ade27998fbc1881f28bc07fe7d625f310e686be7f101508460db48df7ca263adfc6731bf9f8bb935363fb23e51f33b1f560183595ba624d1fd7ad4e8dcf00f
6
+ metadata.gz: 2d5ddb2ce60022530ffd59879d5734d4300a091eb78db13028ae10a55d8ee83981ba6d20a7c933c2b6249245f40adf7458d0687f7349fa042915445e260b2e3a
7
+ data.tar.gz: 9a8c796fee2372323a405ec8962671438cbd7263b1265f31eb45a324dfcab7e33557dbf74b1f4b87fed0492467965ef7222569ee7fad2511efb41b7b52d33ed9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## [0.2.2] - 2024-04-01
2
+
3
+ - added helpers for checking if a DID is known by (federated with) a relay or if the repo exists on its assigned PDS
4
+
5
+ ## [0.2.1] - 2024-03-26
6
+
7
+ - tweaked validations in `Document` and `PLCOperation` to make them more aligned with what might be expected
8
+ - added Ruby stdlib dependencies explicitly to the gemspec
9
+
1
10
  ## [0.2.0] - 2024-03-19
2
11
 
3
12
  - added `PLCImporter` class, which lets you import operations from PLC in pages of 1000 through the "export" API
@@ -6,9 +6,8 @@ module DIDKit
6
6
  def parse_also_known_as(aka)
7
7
  raise FormatError, "Invalid alsoKnownAs: #{aka.inspect}" unless aka.is_a?(Array)
8
8
  raise FormatError, "Invalid alsoKnownAs: #{aka.inspect}" unless aka.all? { |x| x.is_a?(String) }
9
- raise FormatError, "Invalid alsoKnownAs: #{aka.inspect}" unless aka.all? { |x| x =~ %r(\Aat://[^/]+\z) }
10
9
 
11
- aka.map { |x| x.gsub('at://', '') }
10
+ aka.select { |x| x =~ %r(\Aat://[^/]+\z) }.map { |x| x.gsub('at://', '') }
12
11
  end
13
12
  end
14
13
  end
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
@@ -38,6 +41,34 @@ module DIDKit
38
41
  did.gsub(/^did\:web\:/, '') if type == :web
39
42
  end
40
43
 
44
+ def is_known_by_relay?(relay, options = {})
45
+ relay_host = relay.include?('://') ? URI(relay).origin : "https://#{relay}"
46
+ url = URI("#{relay_host}/xrpc/com.atproto.sync.getLatestCommit")
47
+ url.query = URI.encode_www_form(:did => did)
48
+
49
+ response = get_response(url, { timeout: 30, max_redirects: 5 }.merge(options))
50
+ status = response.code.to_i
51
+ is_json = (response['Content-Type'] =~ /^application\/json(;.*)?$/)
52
+
53
+ if status == 200
54
+ true
55
+ elsif status == 400 && is_json && JSON.parse(response.body)['error'] == 'RepoNotFound'
56
+ false
57
+ elsif status == 404 && is_json && JSON.parse(response.body)['error']
58
+ false
59
+ else
60
+ raise APIError.new(response)
61
+ end
62
+ end
63
+
64
+ def account_exists?
65
+ doc = get_document
66
+ return false if doc.pds_endpoint.nil?
67
+
68
+ pds_host = URI(doc.pds_endpoint).origin
69
+ is_known_by_relay?(pds_host, timeout: 10)
70
+ end
71
+
41
72
  def ==(other)
42
73
  if other.is_a?(String)
43
74
  self.did == other
@@ -21,22 +21,18 @@ module DIDKit
21
21
  @did = did
22
22
  @json = json
23
23
 
24
- service = json['service']
25
- raise FormatError, "Missing service key" if service.nil?
26
- raise FormatError, "Invalid service data" unless service.is_a?(Array) && service.all? { |x| x.is_a?(Hash) }
27
-
28
- @services = service.map { |x|
29
- id, type, endpoint = x.values_at('id', 'type', 'serviceEndpoint')
30
-
31
- raise FormatError, "Missing service id" unless id
32
- raise FormatError, "Invalid service id: #{id.inspect}" unless id.is_a?(String) && id.start_with?('#')
33
- raise FormatError, "Missing service type" unless type
34
- raise FormatError, "Invalid service type: #{type.inspect}" unless type.is_a?(String)
35
- raise FormatError, "Missing service endpoint" unless endpoint
36
- raise FormatError, "Invalid service endpoint: #{endpoint.inspect}" unless endpoint.is_a?(String)
37
-
38
- ServiceRecord.new(id.gsub(/^#/, ''), type, endpoint)
39
- }
24
+ if service = json['service']
25
+ raise FormatError, "Invalid service data" unless service.is_a?(Array) && service.all? { |x| x.is_a?(Hash) }
26
+
27
+ @services = service.filter_map { |x|
28
+ id, type, endpoint = x.values_at('id', 'type', 'serviceEndpoint')
29
+ next unless id.is_a?(String) && id.start_with?('#') && type.is_a?(String) && endpoint.is_a?(String)
30
+
31
+ ServiceRecord.new(id.gsub(/^#/, ''), type, endpoint)
32
+ }
33
+ else
34
+ @services = []
35
+ end
40
36
 
41
37
  @handles = parse_also_known_as(json['alsoKnownAs'] || [])
42
38
  end
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
@@ -51,7 +51,7 @@ module DIDKit
51
51
  ServiceRecord.new(k, type, endpoint)
52
52
  }
53
53
 
54
- @handles = parse_also_known_as(operation['alsoKnownAs'] || [])
54
+ @handles = parse_also_known_as(operation['alsoKnownAs'])
55
55
  end
56
56
  end
57
57
  end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DIDKit
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.2"
5
5
  end
metadata CHANGED
@@ -1,15 +1,99 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: didkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
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-19 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-03-31 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'
13
97
  description:
14
98
  email:
15
99
  - jakub.suder@gmail.com
@@ -27,6 +111,7 @@ files:
27
111
  - lib/didkit/errors.rb
28
112
  - lib/didkit/plc_importer.rb
29
113
  - lib/didkit/plc_operation.rb
114
+ - lib/didkit/requests.rb
30
115
  - lib/didkit/resolver.rb
31
116
  - lib/didkit/service_record.rb
32
117
  - lib/didkit/services.rb