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.

Align createAccount duplicate email errors

alice bc8691d8 2b302427

+41 -16
+2 -1
docs/TEST_AUDIT.md
··· 13 13 The current baseline for saying "the audited suite is green" is: 14 14 15 15 - `prove -lr t` 16 - - latest full green result in the realigned Meridian worktree: `Files=48, Tests=2922` 16 + - latest full green result in the realigned Meridian worktree: `Files=48, Tests=2926` 17 17 - `prove -lv t/server-auth.t` 18 18 - `perl -c script/differential-validate` 19 19 - `PERLSKY_RUN_REFERENCE_DIFF=1 prove -lv t/reference-differential.t` ··· 51 51 - Account email handling needs consistent normalization on write, lookup, session creation, and confirmation checks; treating email case inconsistently leaves both tests and user-facing auth behavior brittle. 52 52 - `com.atproto.server.requestEmailConfirmation`, `requestEmailUpdate`, and `requestAccountDelete` should reject accounts with no stored email using the official `400 InvalidRequest` / `account does not have an email address` shape, and `updateEmail` should reject unsupported syntax with the official `This email address is not supported, please use a different email.` message. 53 53 - perlsky intentionally still allows test-friendly no-email local accounts, but once an email is supplied `com.atproto.server.createAccount` now follows the same unsupported-syntax rejection shape as `updateEmail` and the official runtime. 54 + - `com.atproto.server.createAccount` must not turn duplicate-email requests into a `500`; it now follows the official client-visible `400 InvalidRequest` / `Email already taken: ...` shape instead. 54 55 - `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. 55 56 - `com.atproto.identity.resolveHandle` should reject malformed handles with `400 InvalidRequest`, not quietly treat them as misses or return a local `InvalidHandle` variant. 56 57 - `com.atproto.identity.resolveHandle` should treat well-formed but unresolved handles as `400 InvalidRequest` with `Unable to resolve handle`, matching the official runtime instead of returning a local `404 HandleNotFound`.
+25 -15
lib/ATProto/PDS/API/Server.pm
··· 82 82 $email = _supported_email($body->{email}); 83 83 xrpc_error(400, 'InvalidRequest', 'This email address is not supported, please use a different email.') 84 84 unless defined $email; 85 + xrpc_error(400, 'InvalidRequest', "Email already taken: $body->{email}") 86 + if $c->store->get_account_by_email($email); 85 87 } 86 88 87 89 my $invite; ··· 137 139 signing_key_did => $keys->{signing_key_did}, 138 140 }); 139 141 140 - my $account = $c->store->create_account( 141 - account_id => $account_id, 142 - did => $did, 143 - handle => $handle, 144 - email => $email, 145 - email_confirmed_at => _initial_email_confirmed_at($c, $email), 146 - password_hash => $password_record->{hash}, 147 - password_salt => $password_record->{salt}, 148 - deactivated_at => $deactivated_at, 149 - did_doc => $did_doc, 150 - private_key => $keys->{private_key}, 151 - public_key => $keys->{public_key}, 152 - public_key_multibase => $keys->{public_key_multibase}, 153 - signing_key_did => $keys->{signing_key_did}, 154 - ); 142 + my $account = eval { 143 + $c->store->create_account( 144 + account_id => $account_id, 145 + did => $did, 146 + handle => $handle, 147 + email => $email, 148 + email_confirmed_at => _initial_email_confirmed_at($c, $email), 149 + password_hash => $password_record->{hash}, 150 + password_salt => $password_record->{salt}, 151 + deactivated_at => $deactivated_at, 152 + did_doc => $did_doc, 153 + private_key => $keys->{private_key}, 154 + public_key => $keys->{public_key}, 155 + public_key_multibase => $keys->{public_key_multibase}, 156 + signing_key_did => $keys->{signing_key_did}, 157 + ); 158 + }; 159 + if (!$account) { 160 + my $err = $@; 161 + xrpc_error(400, 'InvalidRequest', "Email already taken: $body->{email}") 162 + if !ref($err) && defined($email) && ($err // q()) =~ /UNIQUE constraint failed: accounts\.email/; 163 + die $err; 164 + } 155 165 156 166 my $repo = $c->repo_manager->initialize_repo($account); 157 167 $account = $c->store->update_account($account->{did},
+6
script/differential-validate
··· 572 572 email => 'not-an-email', 573 573 password => 'hunter22', 574 574 }); 575 + my $duplicate_email_create = post_json($server{$name}{origin}, 'com.atproto.server.createAccount', { 576 + handle => "dupemail-$name-" . substr(random_hex(3), 0, 6), 577 + email => uc($server{$name}{email}), 578 + password => 'hunter22', 579 + }); 575 580 my $too_long_session = post_json($server{$name}{origin}, 'com.atproto.server.createSession', { 576 581 identifier => $server{$name}{handle}, 577 582 password => ('x' x 513), ··· 583 588 $server{$name}{password_boundaries} = { 584 589 create_too_long => normalize_xrpc_error($too_long_create), 585 590 create_bad_email => normalize_xrpc_error($invalid_email_create), 591 + create_dup_email => normalize_xrpc_error($duplicate_email_create), 586 592 session_too_long => normalize_xrpc_error($too_long_session), 587 593 reset_uppercase => { 588 594 status => $password_reset_upper->code // 0,
+8
t/app.t
··· 98 98 ->json_is('/error' => 'InvalidRequest') 99 99 ->json_is('/message' => 'This email address is not supported, please use a different email.'); 100 100 101 + $fresh->post_ok('/xrpc/com.atproto.server.createAccount' => json => { 102 + handle => 'dupemail.localhost', 103 + email => 'ALICE@example.test', 104 + password => 'hunter22', 105 + })->status_is(400) 106 + ->json_is('/error' => 'InvalidRequest') 107 + ->json_is('/message' => 'Email already taken: ALICE@example.test'); 108 + 101 109 $fresh->get_ok("/xrpc/com.atproto.identity.resolveHandle?handle=alice.localhost") 102 110 ->status_is(200) 103 111 ->json_is('/did' => $user_did);