didkit 0.3.1 → 0.3.2
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 +4 -4
- data/CHANGELOG.md +7 -0
- data/LICENSE.txt +1 -1
- data/README.md +8 -4
- data/lib/didkit/at_handles.rb +11 -2
- data/lib/didkit/did.rb +89 -2
- data/lib/didkit/document.rb +41 -4
- data/lib/didkit/errors.rb +22 -2
- data/lib/didkit/plc_importer.rb +2 -0
- data/lib/didkit/plc_operation.rb +46 -4
- data/lib/didkit/requests.rb +10 -0
- data/lib/didkit/resolver.rb +84 -16
- data/lib/didkit/service_record.rb +23 -3
- data/lib/didkit/services.rb +42 -0
- data/lib/didkit/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: d7a4ebcd65dd0bb01a1dfd790dafaa6db2780e704558b8da171eae1db45053d5
|
|
4
|
+
data.tar.gz: 27bd5afc5f6bec8207f07b152092337c028abfee26cf0684f5c04d1352241b03
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5cfcbe474eb92088fef122b10df8776b99589a5aa8f1725c5303305cc5ece4696d291f82931e691c92d7b3eb16b0f874efc0fb8bfbfc01f4267defa4e3cc28e4
|
|
7
|
+
data.tar.gz: 984039e433ef24d7499c70c5bcd336c4f826195b59b3143a03ca131741146c18fdae3b9088a201b3556169871b8ddd93062e7dfcc7292db4294fb45e464b9e17
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [0.3.2] - 2026-02-15
|
|
2
|
+
|
|
3
|
+
- added YARD API documentation
|
|
4
|
+
- marked some helper methods in `requests.rb`, `at_handles.rb` and `DIDKit::Resolver` as private
|
|
5
|
+
- merged all "FormatErrors" into one `DIDKit::FormatError`
|
|
6
|
+
- added `frozen_string_literal` directive everywhere to minimize garbage collection
|
|
7
|
+
|
|
1
8
|
## [0.3.1] - 2025-12-19
|
|
2
9
|
|
|
3
10
|
- allow passing a DID string or object to `#resolve_handle` and just return that DID – so you can have a script that accepts either a handle or a DID, and passes the input to `DID.resolve_handle` without checking which one it is
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -13,11 +13,13 @@ Accounts on Bluesky use identifiers like [did:plc:oio4hkxaop4ao4wz2pp3f4cr](http
|
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
To use DIDKit, you need a reasonably new version of Ruby – it should run on Ruby 2.6 and above, although it's recommended to use a version that's still getting maintainance updates, i.e. currently 3.2+. A compatible version should be preinstalled on macOS Big Sur and above and on many Linux systems. Otherwise, you can install one using tools such as [RVM](https://rvm.io), [asdf](https://asdf-vm.com), [ruby-install](https://github.com/postmodern/ruby-install) or [ruby-build](https://github.com/rbenv/ruby-build), or `rpm` or `apt-get` on Linux (see more installation options on [ruby-lang.org](https://www.ruby-lang.org/en/downloads/)).
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
To install the gem, run in the command line:
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
[sudo] gem install didkit
|
|
21
|
+
|
|
22
|
+
Or add this to your app's `Gemfile`:
|
|
21
23
|
|
|
22
24
|
gem 'didkit', '~> 0.3'
|
|
23
25
|
|
|
@@ -106,6 +108,8 @@ resolver.get_verified_handle(did)
|
|
|
106
108
|
|
|
107
109
|
## Credits
|
|
108
110
|
|
|
109
|
-
Copyright ©
|
|
111
|
+
Copyright © 2026 Kuba Suder ([@mackuba.eu](https://bsky.app/profile/did:plc:oio4hkxaop4ao4wz2pp3f4cr)).
|
|
110
112
|
|
|
111
113
|
The code is available under the terms of the [zlib license](https://choosealicense.com/licenses/zlib/) (permissive, similar to MIT).
|
|
114
|
+
|
|
115
|
+
Bug reports and pull requests are welcome 😎
|
data/lib/didkit/at_handles.rb
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'errors'
|
|
4
|
+
|
|
1
5
|
module DIDKit
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# @private
|
|
9
|
+
#
|
|
10
|
+
|
|
2
11
|
module AtHandles
|
|
3
|
-
|
|
4
|
-
|
|
12
|
+
|
|
13
|
+
private
|
|
5
14
|
|
|
6
15
|
def parse_also_known_as(aka)
|
|
7
16
|
raise FormatError, "Invalid alsoKnownAs: #{aka.inspect}" unless aka.is_a?(Array)
|
data/lib/didkit/did.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'json'
|
|
2
4
|
require 'uri'
|
|
3
5
|
|
|
@@ -6,16 +8,52 @@ require_relative 'requests'
|
|
|
6
8
|
require_relative 'resolver'
|
|
7
9
|
|
|
8
10
|
module DIDKit
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Represents a DID identifier (account on the ATProto network). This class serves as an entry
|
|
14
|
+
# point to various lookup helpers. For convenience it can also be accessed as just `DID` without
|
|
15
|
+
# the `DIDKit::` prefix.
|
|
16
|
+
#
|
|
17
|
+
# @example Resolving a handle
|
|
18
|
+
# did = DID.resolve_handle('bsky.app')
|
|
19
|
+
#
|
|
20
|
+
|
|
9
21
|
class DID
|
|
10
22
|
GENERIC_REGEXP = /\Adid\:\w+\:.+\z/
|
|
11
23
|
|
|
12
24
|
include Requests
|
|
13
25
|
|
|
26
|
+
# Resolve a handle into a DID. Looks up the given ATProto domain handle using the DNS TXT method
|
|
27
|
+
# and the HTTP .well-known method and returns a DID if one is assigned using either of the methods.
|
|
28
|
+
#
|
|
29
|
+
# If a DID string or a {DID} object is passed, it simply returns that DID, so you can use this
|
|
30
|
+
# method to pass it an input string from the user which can be a DID or handle, without having to
|
|
31
|
+
# check which one it is.
|
|
32
|
+
#
|
|
33
|
+
# @param handle [String, DID] a domain handle (may start with an `@`) or a DID string
|
|
34
|
+
# @return [DID, nil] resolved DID if found, nil otherwise
|
|
35
|
+
|
|
14
36
|
def self.resolve_handle(handle)
|
|
15
37
|
Resolver.new.resolve_handle(handle)
|
|
16
38
|
end
|
|
17
39
|
|
|
18
|
-
|
|
40
|
+
# @return [Symbol] DID type (`:plc` or `:web`)
|
|
41
|
+
attr_reader :type
|
|
42
|
+
|
|
43
|
+
# @return [String] DID identifier string
|
|
44
|
+
attr_reader :did
|
|
45
|
+
|
|
46
|
+
# @return [Symbol, nil] `:dns` or `:http` if the DID was looked up using one of those methods
|
|
47
|
+
attr_reader :resolved_by
|
|
48
|
+
|
|
49
|
+
alias to_s did
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Create a DID object from a DID string.
|
|
53
|
+
#
|
|
54
|
+
# @param did [String, DID] DID string or another DID object
|
|
55
|
+
# @param resolved_by [Symbol, nil] optionally, how the DID was looked up (`:dns` or `:http`)
|
|
56
|
+
# @raise [DIDError] when the DID format or type is invalid
|
|
19
57
|
|
|
20
58
|
def initialize(did, resolved_by = nil)
|
|
21
59
|
if did.is_a?(DID)
|
|
@@ -36,20 +74,39 @@ module DIDKit
|
|
|
36
74
|
@resolved_by = resolved_by
|
|
37
75
|
end
|
|
38
76
|
|
|
39
|
-
|
|
77
|
+
# Returns or looks up the DID document with the DID's identity details from an appropriate source.
|
|
78
|
+
# This method caches the document in a local variable if it's called again.
|
|
79
|
+
#
|
|
80
|
+
# @return [Document] resolved DID document
|
|
40
81
|
|
|
41
82
|
def document
|
|
42
83
|
@document ||= get_document
|
|
43
84
|
end
|
|
44
85
|
|
|
86
|
+
# Looks up the DID document with the DID's identity details from an appropriate source.
|
|
87
|
+
# @return [Document] resolved DID document
|
|
88
|
+
|
|
45
89
|
def get_document
|
|
46
90
|
Resolver.new.resolve_did(self)
|
|
47
91
|
end
|
|
48
92
|
|
|
93
|
+
# Returns the first verified handle assigned to this DID.
|
|
94
|
+
#
|
|
95
|
+
# Looks up the domain handles assigned to this DID in its DID document, checks if they are
|
|
96
|
+
# verified (i.e. assigned correctly to this DID using DNS TXT or .well-known) and returns
|
|
97
|
+
# the first handle that validates correctly, or nil if none matches.
|
|
98
|
+
#
|
|
99
|
+
# @return [String, nil] verified handle domain, if found
|
|
100
|
+
|
|
49
101
|
def get_verified_handle
|
|
50
102
|
Resolver.new.get_verified_handle(document)
|
|
51
103
|
end
|
|
52
104
|
|
|
105
|
+
# Fetches the PLC audit log (list of all previous operations) for a did:plc DID.
|
|
106
|
+
#
|
|
107
|
+
# @return [Array<PLCOperation>] list of PLC operations in the audit log
|
|
108
|
+
# @raise [DIDError] when the DID is not a did:plc
|
|
109
|
+
|
|
53
110
|
def get_audit_log
|
|
54
111
|
if @type == :plc
|
|
55
112
|
PLCImporter.new.fetch_audit_log(self)
|
|
@@ -58,10 +115,23 @@ module DIDKit
|
|
|
58
115
|
end
|
|
59
116
|
end
|
|
60
117
|
|
|
118
|
+
# Returns the domain portion of a did:web identifier.
|
|
119
|
+
#
|
|
120
|
+
# @return [String, nil] DID domain if the DID is a did:web, nil for did:plc
|
|
121
|
+
|
|
61
122
|
def web_domain
|
|
62
123
|
did.gsub(/^did\:web\:/, '') if type == :web
|
|
63
124
|
end
|
|
64
125
|
|
|
126
|
+
# Checks the status of the account/repo on its own PDS using the `getRepoStatus` endpoint.
|
|
127
|
+
#
|
|
128
|
+
# @param request_options [Hash] request options to override
|
|
129
|
+
# @option request_options [Integer] :timeout request timeout (default: 15)
|
|
130
|
+
# @option request_options [Integer] :max_redirects maximum number of redirects to follow (default: 5)
|
|
131
|
+
#
|
|
132
|
+
# @return [Symbol, nil] `:active`, or returned inactive status, or `nil` if account is not found
|
|
133
|
+
# @raise [APIError] when the response is invalid
|
|
134
|
+
|
|
65
135
|
def account_status(request_options = {})
|
|
66
136
|
doc = self.document
|
|
67
137
|
return nil if doc.pds_endpoint.nil?
|
|
@@ -91,14 +161,31 @@ module DIDKit
|
|
|
91
161
|
end
|
|
92
162
|
end
|
|
93
163
|
|
|
164
|
+
# Checks if the account is seen as active on its own PDS, using the `getRepoStatus` endpoint.
|
|
165
|
+
# This is a helper which calls the {#account_status} method and checks if the status is `:active`.
|
|
166
|
+
#
|
|
167
|
+
# @return [Boolean] true if the returned status is active
|
|
168
|
+
# @raise [APIError] when the response is invalid
|
|
169
|
+
|
|
94
170
|
def account_active?
|
|
95
171
|
account_status == :active
|
|
96
172
|
end
|
|
97
173
|
|
|
174
|
+
# Checks if the account exists its own PDS, using the `getRepoStatus` endpoint.
|
|
175
|
+
# This is a helper which calls the {#account_status} method and checks if the repo is found at all.
|
|
176
|
+
#
|
|
177
|
+
# @return [Boolean] true if the returned status is valid, false if repo is not found
|
|
178
|
+
# @raise [APIError] when the response is invalid
|
|
179
|
+
|
|
98
180
|
def account_exists?
|
|
99
181
|
account_status != nil
|
|
100
182
|
end
|
|
101
183
|
|
|
184
|
+
# Compares the DID to another DID object or string.
|
|
185
|
+
#
|
|
186
|
+
# @param other [DID, String] other DID to compare with
|
|
187
|
+
# @return [Boolean] true if it's the same DID
|
|
188
|
+
|
|
102
189
|
def ==(other)
|
|
103
190
|
if other.is_a?(String)
|
|
104
191
|
self.did == other
|
data/lib/didkit/document.rb
CHANGED
|
@@ -1,17 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'at_handles'
|
|
4
|
+
require_relative 'errors'
|
|
2
5
|
require_relative 'resolver'
|
|
3
6
|
require_relative 'service_record'
|
|
4
7
|
require_relative 'services'
|
|
5
8
|
|
|
6
9
|
module DIDKit
|
|
7
|
-
class Document
|
|
8
|
-
class FormatError < StandardError
|
|
9
|
-
end
|
|
10
10
|
|
|
11
|
+
#
|
|
12
|
+
# Parsed DID document from a JSON file loaded from [plc.directory](https://plc.directory) or a did:web domain.
|
|
13
|
+
#
|
|
14
|
+
# Use {DID#document} or {Resolver#resolve_did} to fetch a DID document and return this object.
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
class Document
|
|
11
18
|
include AtHandles
|
|
12
19
|
include Services
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
# @return [Hash] the complete JSON data of the DID document
|
|
22
|
+
attr_reader :json
|
|
23
|
+
|
|
24
|
+
# @return [DID] the DID that this document describes
|
|
25
|
+
attr_reader :did
|
|
26
|
+
|
|
27
|
+
# Returns a list of handles assigned to this DID in its DID document.
|
|
28
|
+
#
|
|
29
|
+
# Note: the handles aren't guaranteed to be verified (validated in the other direction).
|
|
30
|
+
# Use {#get_verified_handle} to find a handle that is correctly verified.
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<String>]
|
|
33
|
+
attr_reader :handles
|
|
34
|
+
|
|
35
|
+
# @return [Array<ServiceRecords>] service records like PDS details assigned to the DID
|
|
36
|
+
attr_reader :services
|
|
37
|
+
|
|
38
|
+
# Creates a DID document object.
|
|
39
|
+
#
|
|
40
|
+
# @param did [DID] DID object
|
|
41
|
+
# @param json [Hash] DID document JSON
|
|
42
|
+
# @raise [FormatError] when required fields are missing or invalid.
|
|
15
43
|
|
|
16
44
|
def initialize(did, json)
|
|
17
45
|
raise FormatError, "Missing id field" if json['id'].nil?
|
|
@@ -25,10 +53,19 @@ module DIDKit
|
|
|
25
53
|
@handles = parse_also_known_as(json['alsoKnownAs'] || [])
|
|
26
54
|
end
|
|
27
55
|
|
|
56
|
+
# Returns the first verified handle assigned to the DID.
|
|
57
|
+
#
|
|
58
|
+
# Looks up the domain handles assigned to this DID in the DID document, checks if they are
|
|
59
|
+
# verified (i.e. assigned correctly to this DID using DNS TXT or .well-known) and returns
|
|
60
|
+
# the first handle that validates correctly, or nil if none matches.
|
|
61
|
+
#
|
|
62
|
+
# @return [String, nil] verified handle domain, if found
|
|
63
|
+
|
|
28
64
|
def get_verified_handle
|
|
29
65
|
Resolver.new.get_verified_handle(self)
|
|
30
66
|
end
|
|
31
67
|
|
|
68
|
+
|
|
32
69
|
private
|
|
33
70
|
|
|
34
71
|
def parse_services(service_data)
|
data/lib/didkit/errors.rb
CHANGED
|
@@ -1,21 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module DIDKit
|
|
2
|
-
class DIDError < StandardError
|
|
3
|
-
end
|
|
4
4
|
|
|
5
|
+
#
|
|
6
|
+
# Raised when an HTTP request returns a response with an error status.
|
|
7
|
+
#
|
|
5
8
|
class APIError < StandardError
|
|
9
|
+
|
|
10
|
+
# @return [Net::HTTPResponse] the returned HTTP response
|
|
6
11
|
attr_reader :response
|
|
7
12
|
|
|
13
|
+
# @param response [Net::HTTPResponse] the returned HTTP response
|
|
8
14
|
def initialize(response)
|
|
9
15
|
@response = response
|
|
10
16
|
super("APIError: #{response}")
|
|
11
17
|
end
|
|
12
18
|
|
|
19
|
+
# @return [Integer] HTTP status code
|
|
13
20
|
def status
|
|
14
21
|
response.code.to_i
|
|
15
22
|
end
|
|
16
23
|
|
|
24
|
+
# @return [String] HTTP response body
|
|
17
25
|
def body
|
|
18
26
|
response.body
|
|
19
27
|
end
|
|
20
28
|
end
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# Raised when a string is not a valid DID or not of the right type.
|
|
32
|
+
#
|
|
33
|
+
class DIDError < StandardError
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# Raised when the loaded data has some missing or invalid fields.
|
|
38
|
+
#
|
|
39
|
+
class FormatError < StandardError
|
|
40
|
+
end
|
|
21
41
|
end
|
data/lib/didkit/plc_importer.rb
CHANGED
data/lib/didkit/plc_operation.rb
CHANGED
|
@@ -1,18 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'time'
|
|
2
4
|
|
|
3
5
|
require_relative 'at_handles'
|
|
6
|
+
require_relative 'errors'
|
|
4
7
|
require_relative 'service_record'
|
|
5
8
|
require_relative 'services'
|
|
6
9
|
|
|
7
10
|
module DIDKit
|
|
8
|
-
class PLCOperation
|
|
9
|
-
class FormatError < StandardError
|
|
10
|
-
end
|
|
11
11
|
|
|
12
|
+
#
|
|
13
|
+
# Represents a single operation of changing a specific DID's data in the [plc.directory](https://plc.directory)
|
|
14
|
+
# (e.g. changing assigned handles or migrating to a different PDS).
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
class PLCOperation
|
|
12
18
|
include AtHandles
|
|
13
19
|
include Services
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
# @return [Hash] the JSON from which the operation is parsed
|
|
22
|
+
attr_reader :json
|
|
23
|
+
|
|
24
|
+
# @return [String] the DID which the operation concerns
|
|
25
|
+
attr_reader :did
|
|
26
|
+
|
|
27
|
+
# @return [String] CID (Content Identifier) of the operation
|
|
28
|
+
attr_reader :cid
|
|
29
|
+
|
|
30
|
+
# Returns a sequential number of the operation (only used in the new export API).
|
|
31
|
+
# @return [Integer, nil] sequential number of the operation
|
|
32
|
+
attr_reader :seq
|
|
33
|
+
|
|
34
|
+
# @return [Time] time when the operation was created
|
|
35
|
+
attr_reader :created_at
|
|
36
|
+
|
|
37
|
+
# Returns the `type` field of the operation (usually `"plc_operation"`).
|
|
38
|
+
# @return [String] the operation type
|
|
39
|
+
attr_reader :type
|
|
40
|
+
|
|
41
|
+
# Returns a list of handles assigned to the DID in this operation.
|
|
42
|
+
#
|
|
43
|
+
# Note: the handles aren't guaranteed to be verified (validated in the other direction).
|
|
44
|
+
# Use {DID#get_verified_handle} or {Document#get_verified_handle} to find a handle that is
|
|
45
|
+
# correctly verified.
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<String>]
|
|
48
|
+
attr_reader :handles
|
|
49
|
+
|
|
50
|
+
# @return [Array<ServiceRecords>] service records like PDS details assigned to the DID
|
|
51
|
+
attr_reader :services
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Creates a PLCOperation object.
|
|
55
|
+
#
|
|
56
|
+
# @param json [Hash] operation JSON
|
|
57
|
+
# @raise [FormatError] when required fields are missing or invalid
|
|
16
58
|
|
|
17
59
|
def initialize(json)
|
|
18
60
|
@json = json
|
data/lib/didkit/requests.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'json'
|
|
2
4
|
require 'net/http'
|
|
3
5
|
require 'uri'
|
|
@@ -5,7 +7,15 @@ require 'uri'
|
|
|
5
7
|
require_relative 'errors'
|
|
6
8
|
|
|
7
9
|
module DIDKit
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# @private
|
|
13
|
+
#
|
|
14
|
+
|
|
8
15
|
module Requests
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
9
19
|
def get_response(url, options = {})
|
|
10
20
|
url = URI(url) unless url.is_a?(URI)
|
|
11
21
|
|
data/lib/didkit/resolver.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'net/http'
|
|
2
4
|
require 'resolv'
|
|
3
5
|
|
|
@@ -6,18 +8,41 @@ require_relative 'document'
|
|
|
6
8
|
require_relative 'requests'
|
|
7
9
|
|
|
8
10
|
module DIDKit
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# A class which manages resolving of handles to DIDs and DIDs to DID documents.
|
|
14
|
+
#
|
|
15
|
+
|
|
9
16
|
class Resolver
|
|
17
|
+
# These TLDs are not allowed in ATProto handles, so the resolver returns nil for them
|
|
18
|
+
# without trying to look them up.
|
|
10
19
|
RESERVED_DOMAINS = %w(alt arpa example internal invalid local localhost onion test)
|
|
11
20
|
|
|
12
21
|
include Requests
|
|
13
22
|
|
|
23
|
+
# @return [String, Array<String>] custom DNS nameserver(s) to use for DNS TXT lookups
|
|
14
24
|
attr_accessor :nameserver
|
|
15
25
|
|
|
26
|
+
# @param options [Hash] resolver options
|
|
27
|
+
# @option options [String, Array<String>] :nameserver custom DNS nameserver(s) to use (IP or an array of IPs)
|
|
28
|
+
# @option options [Integer] :timeout request timeout in seconds (default: 15)
|
|
29
|
+
# @option options [Integer] :max_redirects maximum number of redirects to follow (default: 5)
|
|
30
|
+
|
|
16
31
|
def initialize(options = {})
|
|
17
32
|
@nameserver = options[:nameserver]
|
|
18
33
|
@request_options = options.slice(:timeout, :max_redirects)
|
|
19
34
|
end
|
|
20
35
|
|
|
36
|
+
# Resolve a handle into a DID. Looks up the given ATProto domain handle using the DNS TXT method
|
|
37
|
+
# and the HTTP .well-known method and returns a DID if one is assigned using either of the methods.
|
|
38
|
+
#
|
|
39
|
+
# If a DID string or a {DID} object is passed, it simply returns that DID, so you can use this
|
|
40
|
+
# method to pass it an input string from the user which can be a DID or handle, without having to
|
|
41
|
+
# check which one it is.
|
|
42
|
+
#
|
|
43
|
+
# @param handle [String, DID] a domain handle (may start with an `@`) or a DID string
|
|
44
|
+
# @return [DID, nil] resolved DID if found, nil otherwise
|
|
45
|
+
|
|
21
46
|
def resolve_handle(handle)
|
|
22
47
|
if handle.is_a?(DID) || handle =~ DID::GENERIC_REGEXP
|
|
23
48
|
return DID.new(handle)
|
|
@@ -36,6 +61,14 @@ module DIDKit
|
|
|
36
61
|
end
|
|
37
62
|
end
|
|
38
63
|
|
|
64
|
+
# Tries to resolve a handle into DID using the DNS TXT method.
|
|
65
|
+
#
|
|
66
|
+
# Checks the DNS records for a given domain for an entry `_atproto.#{domain}` whose value is
|
|
67
|
+
# a correct DID string.
|
|
68
|
+
#
|
|
69
|
+
# @param domain [String] a domain handle to look up
|
|
70
|
+
# @return [String, nil] resolved DID if found, nil otherwise
|
|
71
|
+
|
|
39
72
|
def resolve_handle_by_dns(domain)
|
|
40
73
|
dns_records = Resolv::DNS.open(resolv_options) do |d|
|
|
41
74
|
d.getresources("_atproto.#{domain}", Resolv::DNS::Resource::IN::TXT)
|
|
@@ -50,6 +83,14 @@ module DIDKit
|
|
|
50
83
|
nil
|
|
51
84
|
end
|
|
52
85
|
|
|
86
|
+
# Tries to resolve a handle into DID using the HTTP .well-known method.
|
|
87
|
+
#
|
|
88
|
+
# Checks the `/.well-known/atproto-did` endpoint on the given domain to see if it returns
|
|
89
|
+
# a text file that contains a correct DID string.
|
|
90
|
+
#
|
|
91
|
+
# @param domain [String] a domain handle to look up
|
|
92
|
+
# @return [String, nil] resolved DID if found, nil otherwise
|
|
93
|
+
|
|
53
94
|
def resolve_handle_by_well_known(domain)
|
|
54
95
|
url = "https://#{domain}/.well-known/atproto-did"
|
|
55
96
|
response = get_response(url, @request_options)
|
|
@@ -63,6 +104,49 @@ module DIDKit
|
|
|
63
104
|
nil
|
|
64
105
|
end
|
|
65
106
|
|
|
107
|
+
# Resolve a DID to a DID document.
|
|
108
|
+
#
|
|
109
|
+
# Looks up the DID document with the DID's identity details from an appropriate source, i.e. either
|
|
110
|
+
# [plc.directory](https://plc.directory) for did:plc DIDs, or the did:web's domain for did:web DIDs.
|
|
111
|
+
#
|
|
112
|
+
# @param did [String, DID] DID string or object
|
|
113
|
+
# @return [Document] resolved DID document
|
|
114
|
+
# @raise [APIError] if an incorrect response is returned
|
|
115
|
+
|
|
116
|
+
def resolve_did(did)
|
|
117
|
+
did = DID.new(did) if did.is_a?(String)
|
|
118
|
+
|
|
119
|
+
did.type == :plc ? resolve_did_plc(did) : resolve_did_web(did)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Returns the first verified handle assigned to the given DID.
|
|
123
|
+
#
|
|
124
|
+
# Looks up the domain handles assigned to the DID in the DID document, checks if they are
|
|
125
|
+
# verified (i.e. assigned correctly to this DID using DNS TXT or .well-known) and returns
|
|
126
|
+
# the first handle that validates correctly, or nil if none matches.
|
|
127
|
+
#
|
|
128
|
+
# @param subject [String, DID, Document] a DID or its DID document
|
|
129
|
+
# @return [String, nil] verified handle domain, if found
|
|
130
|
+
|
|
131
|
+
def get_verified_handle(subject)
|
|
132
|
+
document = subject.is_a?(Document) ? subject : resolve_did(subject)
|
|
133
|
+
|
|
134
|
+
first_verified_handle(document.did, document.handles)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Returns the first handle from the list that resolves back to the given DID.
|
|
138
|
+
#
|
|
139
|
+
# @param did [DID, String] DID to verify the handles against
|
|
140
|
+
# @param handles [Array<String>] handles to check
|
|
141
|
+
# @return [String, nil] a verified handle, if found
|
|
142
|
+
|
|
143
|
+
def first_verified_handle(did, handles)
|
|
144
|
+
handles.detect { |h| resolve_handle(h) == did.to_s }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
66
150
|
def resolv_options
|
|
67
151
|
options = Resolv::DNS::Config.default_config_hash.dup
|
|
68
152
|
options[:nameserver] = nameserver if nameserver
|
|
@@ -78,12 +162,6 @@ module DIDKit
|
|
|
78
162
|
text.lines.length == 1 && text =~ DID::GENERIC_REGEXP ? text : nil
|
|
79
163
|
end
|
|
80
164
|
|
|
81
|
-
def resolve_did(did)
|
|
82
|
-
did = DID.new(did) if did.is_a?(String)
|
|
83
|
-
|
|
84
|
-
did.type == :plc ? resolve_did_plc(did) : resolve_did_web(did)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
165
|
def resolve_did_plc(did)
|
|
88
166
|
json = get_json("https://plc.directory/#{did}", content_type: /^application\/did\+ld\+json(;.+)?$/)
|
|
89
167
|
Document.new(did, json)
|
|
@@ -93,15 +171,5 @@ module DIDKit
|
|
|
93
171
|
json = get_json("https://#{did.web_domain}/.well-known/did.json")
|
|
94
172
|
Document.new(did, json)
|
|
95
173
|
end
|
|
96
|
-
|
|
97
|
-
def get_verified_handle(subject)
|
|
98
|
-
document = subject.is_a?(Document) ? subject : resolve_did(subject)
|
|
99
|
-
|
|
100
|
-
first_verified_handle(document.did, document.handles)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def first_verified_handle(did, handles)
|
|
104
|
-
handles.detect { |h| resolve_handle(h) == did.to_s }
|
|
105
|
-
end
|
|
106
174
|
end
|
|
107
175
|
end
|
|
@@ -1,12 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'uri'
|
|
2
4
|
require_relative 'errors'
|
|
3
5
|
|
|
4
6
|
module DIDKit
|
|
7
|
+
|
|
8
|
+
# A parsed service record from either a DID document's `service` field or a PLC directory
|
|
9
|
+
# operation's `services` field.
|
|
10
|
+
|
|
5
11
|
class ServiceRecord
|
|
6
|
-
class FormatError < StandardError
|
|
7
|
-
end
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
# Returns the service's identifier (without `#`), like "atproto_pds".
|
|
14
|
+
# @return [String] service's identifier
|
|
15
|
+
attr_reader :key
|
|
16
|
+
|
|
17
|
+
# Returns the service's type field, like "AtprotoPersonalDataServer".
|
|
18
|
+
# @return [String] service's type
|
|
19
|
+
attr_reader :type
|
|
20
|
+
|
|
21
|
+
# @return [String] service's endpoint URL
|
|
22
|
+
attr_reader :endpoint
|
|
23
|
+
|
|
24
|
+
# Create a service record from DID document fields.
|
|
25
|
+
#
|
|
26
|
+
# @param key [String] service identifier (without `#`)
|
|
27
|
+
# @param type [String] service type
|
|
28
|
+
# @param endpoint [String] service endpoint URL
|
|
29
|
+
# @raise [FormatError] when the endpoint is not a valid URI
|
|
10
30
|
|
|
11
31
|
def initialize(key, type, endpoint)
|
|
12
32
|
begin
|
data/lib/didkit/services.rb
CHANGED
|
@@ -1,23 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'uri'
|
|
2
4
|
|
|
3
5
|
module DIDKit
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
#
|
|
10
|
+
|
|
4
11
|
module Services
|
|
12
|
+
|
|
13
|
+
# Finds a service entry matching the given key and type.
|
|
14
|
+
#
|
|
15
|
+
# @api public
|
|
16
|
+
# @param key [String] service key in the DID document
|
|
17
|
+
# @param type [String] service type identifier
|
|
18
|
+
# @return [ServiceRecord, nil] matching service record, if found
|
|
19
|
+
|
|
5
20
|
def get_service(key, type)
|
|
6
21
|
@services&.detect { |s| s.key == key && s.type == type }
|
|
7
22
|
end
|
|
8
23
|
|
|
24
|
+
# Returns the PDS service endpoint, if present.
|
|
25
|
+
#
|
|
26
|
+
# If the DID has an `#atproto_pds` service declared in its `service` section,
|
|
27
|
+
# returns the URL in its `serviceEndpoint` field. In other words, this is the URL
|
|
28
|
+
# of the PDS assigned to a given user, which stores the user's account and repo.
|
|
29
|
+
#
|
|
30
|
+
# @api public
|
|
31
|
+
# @return [String, nil] PDS service endpoint URL
|
|
32
|
+
|
|
9
33
|
def pds_endpoint
|
|
10
34
|
@pds_endpoint ||= get_service('atproto_pds', 'AtprotoPersonalDataServer')&.endpoint
|
|
11
35
|
end
|
|
12
36
|
|
|
37
|
+
# Returns the labeler service endpoint, if present.
|
|
38
|
+
#
|
|
39
|
+
# If the DID has an `#atproto_labeler` service declared in its `service` section,
|
|
40
|
+
# returns the URL in its `serviceEndpoint` field.
|
|
41
|
+
#
|
|
42
|
+
# @api public
|
|
43
|
+
# @return [String, nil] labeler service endpoint URL
|
|
44
|
+
|
|
13
45
|
def labeler_endpoint
|
|
14
46
|
@labeler_endpoint ||= get_service('atproto_labeler', 'AtprotoLabeler')&.endpoint
|
|
15
47
|
end
|
|
16
48
|
|
|
49
|
+
# Returns the hostname of the PDS service, if present.
|
|
50
|
+
#
|
|
51
|
+
# @api public
|
|
52
|
+
# @return [String, nil] hostname of the PDS endpoint URL
|
|
53
|
+
|
|
17
54
|
def pds_host
|
|
18
55
|
pds_endpoint&.then { |x| URI(x).host }
|
|
19
56
|
end
|
|
20
57
|
|
|
58
|
+
# Returns the hostname of the labeler service, if present.
|
|
59
|
+
#
|
|
60
|
+
# @api public
|
|
61
|
+
# @return [String, nil] hostname of the labeler endpoint URL
|
|
62
|
+
|
|
21
63
|
def labeler_host
|
|
22
64
|
labeler_endpoint&.then { |x| URI(x).host }
|
|
23
65
|
end
|
data/lib/didkit/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: didkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kuba Suder
|
|
@@ -52,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '0'
|
|
54
54
|
requirements: []
|
|
55
|
-
rubygems_version: 4.0.
|
|
55
|
+
rubygems_version: 4.0.3
|
|
56
56
|
specification_version: 4
|
|
57
57
|
summary: A library for handling Distributed ID (DID) identifiers used in Bluesky AT
|
|
58
58
|
Protocol
|