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 PLC DID documents

alice 7d8e5e6c d7b6f27e

+49 -1
+1
docs/TEST_AUDIT.md
··· 52 52 - `app.bsky.actor.putPreferences` and `app.bsky.notification.putPreferencesV2` now have explicit shape validation plus focused regression coverage, turning an earlier hardening concern into a pinned contract. 53 53 - `com.atproto.identity.resolveHandle` should reject malformed handles with `400 InvalidHandle`, not quietly treat them as misses. 54 54 - Remote `did:web` DID docs, conservative `resolveIdentity` handle validation, and external handle adoption all need explicit coverage because small resolver-policy drifts turn into visible interop bugs quickly. 55 + - Remote `did:plc` DID docs should resolve through the PLC directory defaults even when `plc_url` is not explicitly configured; gating that path on local config silently breaks federated identity lookups. 55 56 - `com.atproto.repo.getRecord` must honor `cid` when present, and `putRecord` / `deleteRecord` must actually enforce `swapRecord`; those negative edges are now covered directly. 56 57 - `com.atproto.server.requestPasswordReset` and `com.atproto.server.deleteAccount` now follow the reference form-token flow, with focused regression coverage for missing-account and bearerless deletion semantics. 57 58 - `com.atproto.server.createAccount` with an explicit `did` must behave like an authenticated migration flow: require auth from that same DID, keep the existing DID document, and start the new account deactivated until activation catches the DID document up to the new PDS.
+1 -1
lib/ATProto/PDS/API/Builtins.pm
··· 213 213 } 214 214 215 215 sub _resolve_remote_did_doc ($c, $did) { 216 - if (is_plc_did($did) && defined($c->app->settings->{plc_url}) && length($c->app->settings->{plc_url})) { 216 + if (is_plc_did($did)) { 217 217 my $did_doc = eval { refresh_plc_did_doc($c->app->settings, $did) }; 218 218 return $did_doc unless $@; 219 219 return undef;
+47
t/remote-handle-resolution.t
··· 24 24 use Mojolicious; 25 25 use Test::Mojo; 26 26 use ATProto::PDS; 27 + use ATProto::PDS::Crypto::Secp256k1 qw(generate_keypair); 27 28 28 29 my @mock_pids; 29 30 END { ··· 39 40 40 41 my $remote_handle = 'alice.mosphere.at'; 41 42 my $remote_did = 'did:plc:pkktelaqretqiz2bddzzlv3t'; 43 + my $remote_plc_handle = 'remote-plc.example.test'; 44 + my $remote_plc_did = 'did:plc:remoteplcdocument123456'; 45 + my $remote_plc_keys = generate_keypair(); 42 46 43 47 my $appview_app = Mojolicious->new; 44 48 $appview_app->routes->get('/ready')->to(cb => sub { ··· 64 68 my ($c) = @_; 65 69 my $handle = lc($c->param('handle') // ''); 66 70 return $c->render(json => { did => $remote_did }) if $handle eq $remote_handle; 71 + return $c->render(json => { did => $remote_plc_did }) if $handle eq $remote_plc_handle; 67 72 $c->render(status => 404, json => { 68 73 error => 'HandleNotFound', 69 74 message => "No DID found for handle $handle", ··· 71 76 }); 72 77 73 78 my $appview_url = _start_mock_server($appview_app); 79 + my $plc_app = Mojolicious->new; 80 + $plc_app->routes->get('/ready')->to(cb => sub { 81 + my ($c) = @_; 82 + $c->render(text => 'ok'); 83 + }); 84 + $plc_app->routes->get('/*did/data')->to(cb => sub { 85 + my ($c) = @_; 86 + return $c->render(status => 404, json => { error => 'NotFound' }) 87 + unless ($c->param('did') // q()) eq $remote_plc_did; 88 + $c->render(json => { 89 + alsoKnownAs => ["at://$remote_plc_handle"], 90 + verificationMethods => { 91 + atproto => $remote_plc_keys->{signing_key_did}, 92 + }, 93 + services => { 94 + atproto_pds => { 95 + type => 'AtprotoPersonalDataServer', 96 + endpoint => 'https://remote-plc.example.test', 97 + }, 98 + }, 99 + }); 100 + }); 101 + my $plc_url = _start_mock_server($plc_app); 74 102 my $remote_did_web = do { 75 103 my $url = Mojo::URL->new($appview_url); 76 104 'did:web:' . (($url->host_port // q()) =~ s/:/%3A/gr) . ':actor'; ··· 85 113 jwt_secret => 'remote-handle-secret', 86 114 data_dir => $tmp, 87 115 db_path => File::Spec->catfile($tmp, 'perlsky.sqlite'), 116 + plc_url => $plc_url, 88 117 bsky_appview_url => $appview_url, 89 118 }, 90 119 ); ··· 109 138 ->json_is('/didDoc/id' => $remote_did_web) 110 139 ->json_is('/didDoc/service/0/serviceEndpoint' => 'https://actor.example.test'); 111 140 141 + $t->get_ok("/xrpc/com.atproto.identity.resolveDid?did=$remote_plc_did") 142 + ->status_is(200) 143 + ->json_is('/didDoc/id' => $remote_plc_did) 144 + ->json_is('/didDoc/alsoKnownAs/0' => "at://$remote_plc_handle") 145 + ->json_is('/didDoc/service/0/serviceEndpoint' => 'https://remote-plc.example.test'); 146 + 112 147 { 113 148 no warnings 'redefine'; 114 149 local *ATProto::PDS::Identity::_resolve_handle_dns = sub { ··· 129 164 ->json_is('/did' => $remote_did_web) 130 165 ->json_is('/handle' => 'actor.example.test') 131 166 ->json_is('/didDoc/id' => $remote_did_web); 167 + 168 + $t->get_ok("/xrpc/com.atproto.identity.resolveIdentity?identifier=$remote_plc_did") 169 + ->status_is(200) 170 + ->json_is('/did' => $remote_plc_did) 171 + ->json_is('/handle' => $remote_plc_handle) 172 + ->json_is('/didDoc/id' => $remote_plc_did); 173 + 174 + $t->get_ok("/xrpc/com.atproto.identity.resolveIdentity?identifier=$remote_plc_handle") 175 + ->status_is(200) 176 + ->json_is('/did' => $remote_plc_did) 177 + ->json_is('/handle' => $remote_plc_handle) 178 + ->json_is('/didDoc/id' => $remote_plc_did); 132 179 } 133 180 134 181 {