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.

Harden email confirmation and blob writes

alice 800c6baf 7ae958cf

+92 -7
+6 -2
lib/ATProto/PDS/API/Repo.pm
··· 162 162 make_path($blob_dir); 163 163 my $path = File::Spec->catfile($blob_dir, $cid); 164 164 open(my $fh, '>:raw', $path) or xrpc_error(500, 'StorageFailure', "Unable to write blob $cid"); 165 - print {$fh} $bytes; 166 - close($fh); 165 + my $write_ok = print {$fh} $bytes; 166 + my $close_ok = close($fh); 167 + unless ($write_ok && $close_ok) { 168 + unlink $path if -e $path; 169 + xrpc_error(500, 'StorageFailure', "Unable to write blob $cid"); 170 + } 167 171 168 172 my $mime_type = $c->req->headers->content_type || 'application/octet-stream'; 169 173 $c->observe_blob_ingress($mime_type, length($bytes));
+10 -5
lib/ATProto/PDS/API/Server.pm
··· 375 375 376 376 $registry->register('com.atproto.server.confirmEmail', sub ($c, $endpoint) { 377 377 my $body = $c->req->json || {}; 378 - my $account = $c->store->get_account_by_email($body->{email} // q()); 379 - xrpc_error(404, 'AccountNotFound', 'Account was not found') unless $account; 380 378 my $token = _require_action_token($c, 381 379 token => $body->{token}, 382 380 purpose => 'email_confirm', 383 381 ); 382 + my $account = $c->store->get_account_by_did($token->{did}); 383 + xrpc_error(404, 'AccountNotFound', 'Account was not found') unless $account; 384 + my $email = $body->{email} // q(); 384 385 xrpc_error(400, 'InvalidEmail', 'Token was not issued for that email') 385 - unless ($token->{email} // q()) eq ($body->{email} // q()); 386 - $c->store->update_account($account->{did}, email_confirmed_at => time); 387 - $c->store->consume_action_token($token->{token}); 386 + unless length($email) 387 + && ($token->{email} // q()) eq $email 388 + && ($account->{email} // q()) eq $email; 389 + $c->store->txn(sub ($dbh) { 390 + $c->store->update_account($account->{did}, email_confirmed_at => time); 391 + $c->store->consume_action_token($token->{token}); 392 + }); 388 393 return {}; 389 394 }); 390 395
+76
t/email-confirmation.t
··· 1 + use v5.34; 2 + use warnings; 3 + 4 + use Config (); 5 + use File::Spec; 6 + use File::Temp qw(tempdir); 7 + use FindBin qw($Bin); 8 + use Test::More; 9 + 10 + BEGIN { 11 + require lib; 12 + my $root = File::Spec->rel2abs(File::Spec->catdir($Bin, '..')); 13 + lib->import( 14 + File::Spec->catdir($root, 'lib'), 15 + File::Spec->catdir($root, 'local', 'lib', 'perl5'), 16 + File::Spec->catdir($root, 'local', 'lib', 'perl5', $Config::Config{archname}), 17 + ); 18 + } 19 + 20 + use Test::Mojo; 21 + use ATProto::PDS; 22 + 23 + my $root = File::Spec->rel2abs(File::Spec->catdir($Bin, '..')); 24 + my $tmp = tempdir(CLEANUP => 1); 25 + 26 + my $app = ATProto::PDS->new( 27 + project_root => $root, 28 + settings => { 29 + base_url => 'http://127.0.0.1:7755', 30 + service_handle_domain => 'example.test', 31 + service_did_method => 'did:web', 32 + jwt_secret => 'email-confirm-secret', 33 + data_dir => $tmp, 34 + db_path => File::Spec->catfile($tmp, 'perlsky.sqlite'), 35 + }, 36 + ); 37 + 38 + my $t = Test::Mojo->new($app); 39 + 40 + $t->post_ok('/xrpc/com.atproto.server.createAccount' => json => { 41 + handle => 'alice.example.test', 42 + email => 'alice@example.test', 43 + password => 'hunter22', 44 + })->status_is(200); 45 + my $alice = $t->tx->res->json; 46 + 47 + $app->store->update_account($alice->{did}, email_confirmed_at => undef); 48 + 49 + $t->post_ok('/xrpc/com.atproto.server.requestEmailConfirmation' => { 50 + Authorization => "Bearer $alice->{accessJwt}", 51 + } => json => {})->status_is(200); 52 + 53 + my $token = $app->store->latest_action_token( 54 + did => $alice->{did}, 55 + purpose => 'email_confirm', 56 + ); 57 + ok($token, 'email confirmation token was created'); 58 + 59 + $app->store->update_account( 60 + $alice->{did}, 61 + email => 'alice+new@example.test', 62 + email_confirmed_at => undef, 63 + ); 64 + 65 + $t->post_ok('/xrpc/com.atproto.server.confirmEmail' => json => { 66 + email => 'alice@example.test', 67 + token => $token->{token}, 68 + })->status_is(400) 69 + ->json_is('/error' => 'InvalidEmail'); 70 + 71 + ok( 72 + !defined $app->store->get_account_by_did($alice->{did})->{email_confirmed_at}, 73 + 'stale confirmation tokens cannot confirm a changed email address', 74 + ); 75 + 76 + done_testing;