perlsky is a Perl 5 implementation of an AT Protocol Personal Data Server.
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

Resolve remote DID documents

alice 25923044 328f36e7

+90 -5
+67 -5
lib/ATProto/PDS/API/Builtins.pm
··· 11 11 use Mojo::UserAgent; 12 12 13 13 use ATProto::PDS::Identity qw(account_did_doc normalize_handle service_did service_did_doc); 14 + use ATProto::PDS::PLC qw(is_plc_did refresh_plc_did_doc); 14 15 15 16 our @EXPORT_OK = qw(register_builtin_handlers); 16 17 ··· 34 35 } 35 36 36 37 my $account = $c->store->get_account_by_did(_canonical_did($did)); 38 + if ($account) { 39 + return { 40 + didDoc => $account->{did_doc} || account_did_doc($c->app->settings, $account), 41 + }; 42 + } 43 + 44 + if (my $did_doc = _resolve_remote_did_doc($c, $did)) { 45 + return { 46 + didDoc => $did_doc, 47 + }; 48 + } 49 + 37 50 die { 38 51 status => 404, 39 52 error => 'DidNotFound', 40 53 message => "No DID document found for $did", 41 - } unless $account; 42 - 43 - return { 44 - didDoc => $account->{did_doc} || account_did_doc($c->app->settings, $account), 45 54 }; 46 55 }); 47 56 ··· 152 161 my $tx = eval { $ua->get($url) }; 153 162 return undef if $@ || !$tx; 154 163 155 - my $res = $tx->result; 164 + my $res = eval { $tx->result }; 165 + return undef if $@ || !$res; 156 166 return undef unless ($res->code // 0) == 200; 157 167 my $json = $res->json; 158 168 return undef unless ref($json) eq 'HASH' && defined($json->{did}) && length($json->{did}); 159 169 return $json->{did}; 170 + } 171 + 172 + sub _resolve_remote_did_doc ($c, $did) { 173 + if (is_plc_did($did) && defined($c->app->settings->{plc_url}) && length($c->app->settings->{plc_url})) { 174 + my $did_doc = eval { refresh_plc_did_doc($c->app->settings, $did) }; 175 + return $did_doc unless $@; 176 + return undef; 177 + } 178 + 179 + return undef unless $did =~ /\Adid:web:/i; 180 + 181 + state %ua_for_origin; 182 + my $origin = lc(_relaxed_did($did)); 183 + my $ua = $ua_for_origin{$origin} //= do { 184 + my $client = Mojo::UserAgent->new(max_redirects => 0); 185 + $client->request_timeout(15); 186 + $client->inactivity_timeout(15); 187 + $client; 188 + }; 189 + 190 + my ($host, $path) = _web_did_origin_and_path($did); 191 + return undef unless defined $host && defined $path; 192 + my $scheme = $host =~ /\A(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?\z/i ? 'http' : 'https'; 193 + my $url = Mojo::URL->new("$scheme://$host"); 194 + $url->path($path); 195 + 196 + my $tx = eval { $ua->get($url) }; 197 + return undef if $@ || !$tx; 198 + 199 + my $res = eval { $tx->result }; 200 + return undef if $@ || !$res; 201 + return undef unless ($res->code // 0) == 200; 202 + my $json = $res->json; 203 + return undef unless ref($json) eq 'HASH' && defined($json->{id}) && _same_did($json->{id}, $did); 204 + return $json; 205 + } 206 + 207 + sub _web_did_origin_and_path ($did) { 208 + return unless defined $did && $did =~ s/\Adid:web://i; 209 + my @parts = split /:/, $did; 210 + return unless @parts; 211 + 212 + my $host = shift @parts; 213 + $host =~ s/%3a/:/ig; 214 + if (@parts && $parts[0] =~ /\A\d+\z/ && $host !~ /:/) { 215 + $host .= ':' . shift @parts; 216 + } 217 + 218 + my $path = @parts 219 + ? '/' . join('/', map { s/%3A/:/igr } @parts) . '/did.json' 220 + : '/.well-known/did.json'; 221 + return ($host, $path); 160 222 } 161 223 162 224 sub _same_did ($left, $right) {
+23
t/remote-handle-resolution.t
··· 45 45 my ($c) = @_; 46 46 $c->render(text => 'ok'); 47 47 }); 48 + $appview_app->routes->get('/actor/did.json')->to(cb => sub { 49 + my ($c) = @_; 50 + my $host = $c->req->url->to_abs->host_port; 51 + my $did = 'did:web:' . ($host =~ s/:/%3A/gr) . ':actor'; 52 + $c->render(json => { 53 + '@context' => ['https://www.w3.org/ns/did/v1'], 54 + id => $did, 55 + service => [{ 56 + id => "$did#atproto_pds", 57 + type => 'AtprotoPersonalDataServer', 58 + serviceEndpoint => 'https://actor.example.test', 59 + }], 60 + }); 61 + }); 48 62 $appview_app->routes->get('/xrpc/com.atproto.identity.resolveHandle')->to(cb => sub { 49 63 my ($c) = @_; 50 64 my $handle = lc($c->param('handle') // ''); ··· 56 70 }); 57 71 58 72 my $appview_url = _start_mock_server($appview_app); 73 + my $remote_did_web = do { 74 + my $url = Mojo::URL->new($appview_url); 75 + 'did:web:' . (($url->host_port // q()) =~ s/:/%3A/gr) . ':actor'; 76 + }; 59 77 60 78 my $app = ATProto::PDS->new( 61 79 project_root => $root, ··· 74 92 $t->get_ok("/xrpc/com.atproto.identity.resolveHandle?handle=$remote_handle") 75 93 ->status_is(200) 76 94 ->json_is('/did' => $remote_did); 95 + 96 + $t->get_ok("/xrpc/com.atproto.identity.resolveDid?did=$remote_did_web") 97 + ->status_is(200) 98 + ->json_is('/didDoc/id' => $remote_did_web) 99 + ->json_is('/didDoc/service/0/serviceEndpoint' => 'https://actor.example.test'); 77 100 78 101 $t->get_ok('/xrpc/com.atproto.identity.resolveHandle?handle=missing.example.test') 79 102 ->status_is(404)