Class: DIDKit::Resolver

Inherits:
Object
  • Object
show all
Includes:
Requests
Defined in:
lib/didkit/resolver.rb

Overview

A class which manages resolving of handles to DIDs and DIDs to DID documents.

Constant Summary collapse

RESERVED_DOMAINS =

These TLDs are not allowed in ATProto handles, so the resolver returns nil for them without trying to look them up.

%w(alt arpa example internal invalid local localhost onion test)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Resolver

Returns a new instance of Resolver.

Parameters:

  • (defaults to: {})

    resolver options

Options Hash (options):

  • :nameserver (String, Array<String>)

    custom DNS nameserver(s) to use (IP or an array of IPs)

  • :timeout (Integer)

    request timeout in seconds (default: 15)

  • :max_redirects (Integer)

    maximum number of redirects to follow (default: 5)



31
32
33
34
# File 'lib/didkit/resolver.rb', line 31

def initialize(options = {})
  @nameserver = options[:nameserver]
  @request_options = options.slice(:timeout, :max_redirects)
end

Instance Attribute Details

#nameserverString+

Returns custom DNS nameserver(s) to use for DNS TXT lookups.

Returns:

  • custom DNS nameserver(s) to use for DNS TXT lookups



24
25
26
# File 'lib/didkit/resolver.rb', line 24

def nameserver
  @nameserver
end

Instance Method Details

#first_verified_handle(did, handles) ⇒ String?

Returns the first handle from the list that resolves back to the given DID.

Parameters:

  • DID to verify the handles against

  • handles to check

Returns:

  • a verified handle, if found



143
144
145
# File 'lib/didkit/resolver.rb', line 143

def first_verified_handle(did, handles)
  handles.detect { |h| resolve_handle(h) == did.to_s }
end

#get_verified_handle(subject) ⇒ String?

Returns the first verified handle assigned to the given DID.

Looks up the domain handles assigned to the DID in the DID document, checks if they are verified (i.e. assigned correctly to this DID using DNS TXT or .well-known) and returns the first handle that validates correctly, or nil if none matches.

Parameters:

  • a DID or its DID document

Returns:

  • verified handle domain, if found



131
132
133
134
135
# File 'lib/didkit/resolver.rb', line 131

def get_verified_handle(subject)
  document = subject.is_a?(Document) ? subject : resolve_did(subject)

  first_verified_handle(document.did, document.handles)
end

#resolve_did(did) ⇒ Document

Resolve a DID to a DID document.

Looks up the DID document with the DID’s identity details from an appropriate source, i.e. either [plc.directory](plc.directory) for did:plc DIDs, or the did:web’s domain for did:web DIDs.

Parameters:

  • DID string or object

Returns:

  • resolved DID document

Raises:

  • if an incorrect response is returned



116
117
118
119
120
# File 'lib/didkit/resolver.rb', line 116

def resolve_did(did)
  did = DID.new(did) if did.is_a?(String)

  did.type == :plc ? resolve_did_plc(did) : resolve_did_web(did)
end

#resolve_handle(handle) ⇒ DID?

Resolve a handle into a DID. Looks up the given ATProto domain handle using the DNS TXT method and the HTTP .well-known method and returns a DID if one is assigned using either of the methods.

If a DID string or a DID object is passed, it simply returns that DID, so you can use this method to pass it an input string from the user which can be a DID or handle, without having to check which one it is.

Parameters:

  • a domain handle (may start with an ‘@`) or a DID string

Returns:

  • resolved DID if found, nil otherwise



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/didkit/resolver.rb', line 46

def resolve_handle(handle)
  if handle.is_a?(DID) || handle =~ DID::GENERIC_REGEXP
    return DID.new(handle)
  end

  domain = handle.gsub(/^@/, '')

  return nil if RESERVED_DOMAINS.include?(domain.split('.').last)

  if dns_did = resolve_handle_by_dns(domain)
    DID.new(dns_did, :dns)
  elsif http_did = resolve_handle_by_well_known(domain)
    DID.new(http_did, :http)
  else
    nil
  end
end

#resolve_handle_by_dns(domain) ⇒ String?

Tries to resolve a handle into DID using the DNS TXT method.

Checks the DNS records for a given domain for an entry ‘_atproto.#domain` whose value is a correct DID string.

Parameters:

  • a domain handle to look up

Returns:

  • resolved DID if found, nil otherwise



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/didkit/resolver.rb', line 72

def resolve_handle_by_dns(domain)
  dns_records = Resolv::DNS.open(resolv_options) do |d|
    d.getresources("_atproto.#{domain}", Resolv::DNS::Resource::IN::TXT)
  end

  if record = dns_records.first
    if string = record.strings.first
      return parse_did_from_dns(string)
    end
  end

  nil
end

#resolve_handle_by_well_known(domain) ⇒ String?

Tries to resolve a handle into DID using the HTTP .well-known method.

Checks the /.well-known/atproto-did endpoint on the given domain to see if it returns a text file that contains a correct DID string.

Parameters:

  • a domain handle to look up

Returns:

  • resolved DID if found, nil otherwise



94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/didkit/resolver.rb', line 94

def resolve_handle_by_well_known(domain)
  url = "https://#{domain}/.well-known/atproto-did"
  response = get_response(url, @request_options)

  if response.is_a?(Net::HTTPSuccess) && (text = response.body)
    return parse_did_from_well_known(text)
  end

  nil
rescue StandardError => e
  nil
end