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 identities conservatively

alice fde9773e 91069764

+93 -15
+54 -15
lib/ATProto/PDS/API/Builtins.pm
··· 69 69 error => 'InvalidHandle', 70 70 message => 'Handle is invalid', 71 71 } unless defined $handle && length $handle; 72 - if (my $account = $c->store->get_account_by_handle($handle)) { 73 - return { did => $account->{did} }; 74 - } 75 - 76 - if ($handle eq $service_handle) { 77 - return { 78 - did => service_did($c->app->settings), 79 - }; 80 - } 81 - 82 - if (my $did = resolve_handle_to_did($c->app->settings, $handle)) { 83 - return { did => $did }; 84 - } 85 - 86 - if (my $did = _resolve_remote_handle_via_appview($c, $handle)) { 72 + if (my $did = _resolve_handle_to_did($c, $handle)) { 87 73 return { did => $did }; 88 74 } 89 75 ··· 104 90 handle => $account->{handle}, 105 91 didDoc => $account->{did_doc} || account_did_doc($c->app->settings, $account), 106 92 }; 93 + } 94 + if (my $remote = _resolve_remote_identity($c, $identifier)) { 95 + return $remote; 107 96 } 108 97 109 98 die { ··· 149 138 }); 150 139 } 151 140 141 + sub _resolve_handle_to_did ($c, $handle) { 142 + return undef unless defined $handle && length $handle; 143 + my $service_handle = lc($c->config_value('service_handle_domain', 'localhost')); 144 + if (my $account = $c->store->get_account_by_handle($handle)) { 145 + return $account->{did}; 146 + } 147 + return service_did($c->app->settings) if $handle eq $service_handle; 148 + return resolve_handle_to_did($c->app->settings, $handle) 149 + // _resolve_remote_handle_via_appview($c, $handle); 150 + } 151 + 152 + sub _resolve_remote_identity ($c, $identifier) { 153 + if ($identifier =~ /^did:/) { 154 + my $did_doc = _resolve_remote_did_doc($c, $identifier) or return undef; 155 + return _identity_info_from_did_doc($c, $did_doc); 156 + } 157 + 158 + my $handle = normalize_handle($identifier, undef, { no_append => 1 }); 159 + return undef unless defined $handle && length $handle; 160 + my $did = _resolve_handle_to_did($c, $handle) or return undef; 161 + my $did_doc = _resolve_remote_did_doc($c, $did) or return undef; 162 + return _identity_info_from_did_doc($c, $did_doc, $handle); 163 + } 164 + 152 165 sub _resolve_remote_handle_via_appview ($c, $handle) { 153 166 my $origin = $c->config_value('bsky_appview_url', 'https://api.bsky.app'); 154 167 return undef unless defined $origin && length $origin; ··· 171 184 my $json = $res->json; 172 185 return undef unless ref($json) eq 'HASH' && defined($json->{did}) && length($json->{did}); 173 186 return $json->{did}; 187 + } 188 + 189 + sub _identity_info_from_did_doc ($c, $did_doc, $fallback = undef) { 190 + return { 191 + did => $did_doc->{id}, 192 + handle => _validated_handle_for_did_doc($c, $did_doc, $fallback), 193 + didDoc => $did_doc, 194 + }; 195 + } 196 + 197 + sub _validated_handle_for_did_doc ($c, $did_doc, $fallback = undef) { 198 + my $candidate = _did_doc_handle($did_doc) // $fallback; 199 + return 'handle.invalid' unless defined $candidate && length $candidate; 200 + my $resolved = _resolve_handle_to_did($c, $candidate); 201 + return 'handle.invalid' unless defined $resolved && _same_did($resolved, $did_doc->{id}); 202 + return $candidate; 203 + } 204 + 205 + sub _did_doc_handle ($did_doc) { 206 + return undef unless ref($did_doc) eq 'HASH'; 207 + for my $aka (@{ $did_doc->{alsoKnownAs} || [] }) { 208 + next unless defined $aka && $aka =~ /\Aat:\/\/(.+)\z/i; 209 + my $handle = normalize_handle($1, undef, { no_append => 1 }); 210 + return $handle if defined $handle; 211 + } 212 + return undef; 174 213 } 175 214 176 215 sub _resolve_remote_did_doc ($c, $did) {
+39
t/remote-handle-resolution.t
··· 52 52 $c->render(json => { 53 53 '@context' => ['https://www.w3.org/ns/did/v1'], 54 54 id => $did, 55 + alsoKnownAs => ['at://actor.example.test'], 55 56 service => [{ 56 57 id => "$did#atproto_pds", 57 58 type => 'AtprotoPersonalDataServer', ··· 107 108 ->status_is(200) 108 109 ->json_is('/didDoc/id' => $remote_did_web) 109 110 ->json_is('/didDoc/service/0/serviceEndpoint' => 'https://actor.example.test'); 111 + 112 + { 113 + no warnings 'redefine'; 114 + local *ATProto::PDS::Identity::_resolve_handle_dns = sub { 115 + my ($handle) = @_; 116 + return $remote_did_web if $handle eq 'actor.example.test'; 117 + return undef; 118 + }; 119 + local *ATProto::PDS::Identity::_resolve_handle_well_known = sub { return undef; }; 120 + 121 + $t->get_ok("/xrpc/com.atproto.identity.resolveIdentity?identifier=$remote_did_web") 122 + ->status_is(200) 123 + ->json_is('/did' => $remote_did_web) 124 + ->json_is('/handle' => 'actor.example.test') 125 + ->json_is('/didDoc/id' => $remote_did_web); 126 + 127 + $t->get_ok('/xrpc/com.atproto.identity.resolveIdentity?identifier=actor.example.test') 128 + ->status_is(200) 129 + ->json_is('/did' => $remote_did_web) 130 + ->json_is('/handle' => 'actor.example.test') 131 + ->json_is('/didDoc/id' => $remote_did_web); 132 + } 133 + 134 + { 135 + no warnings 'redefine'; 136 + local *ATProto::PDS::Identity::_resolve_handle_dns = sub { 137 + my ($handle) = @_; 138 + return 'did:web:127.0.0.1%3A65535:someone-else' if $handle eq 'actor.example.test'; 139 + return undef; 140 + }; 141 + local *ATProto::PDS::Identity::_resolve_handle_well_known = sub { return undef; }; 142 + 143 + $t->get_ok("/xrpc/com.atproto.identity.resolveIdentity?identifier=$remote_did_web") 144 + ->status_is(200) 145 + ->json_is('/did' => $remote_did_web) 146 + ->json_is('/handle' => 'handle.invalid') 147 + ->json_is('/didDoc/id' => $remote_did_web); 148 + } 110 149 111 150 done_testing; 112 151