mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-16 16:45:07 +00:00
4806b81dc3
An account deleted by an administrator was soft-deleted with erase_after
null, so projectsend:purge-erasures — which filters on
whereNotNull('erase_after') — never reached it, and the unique index on
users.email kept the address reserved forever. Anyone re-creating the
account got "The email has already been taken", naming a conflict nothing
on any screen could show or clear (#1648).
Both halves of the issue's option 3:
Every deletion path now schedules the erasure. The stamp lives in
ErasureSchedule — self-deletion switched to it, and StaffAccounts::delete
(shared by the web screen and the API) and both client controllers call
it right before delete(). Same grace period, same purge, whoever deleted
the account. Deliberately no backfill for rows deleted before this
change: stamping them during an update would start a countdown to data
erasure that nobody chose at deletion time; the message below covers
them instead.
The staff creation paths swap unique:users,email for AvailableEmailRule,
which refuses exactly the same things but can explain the one refusal
the stock message can't: an address held by a deleted account now names
the date it becomes available, and one deleted before scheduling existed
points at projectsend:erase-account. A living account keeps the stock
message, and public registration keeps the stock rule — telling an
anonymous visitor the address belongs to a deleted account would confirm
it had an account here.
69 lines
2.6 KiB
PHP
69 lines
2.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Modules\Identity\Erasure;
|
|
|
|
use App\Models\User;
|
|
use Closure;
|
|
use Illuminate\Contracts\Validation\ValidationRule;
|
|
use Illuminate\Translation\PotentiallyTranslatedString;
|
|
|
|
/**
|
|
* `unique:users,email` with an answer for the case that rule cannot
|
|
* explain: the address is held by a soft-deleted account.
|
|
*
|
|
* The unique index on users.email spans trashed rows on purpose — an
|
|
* email address is a login identity, and it must not become
|
|
* re-registerable while the account holding it is merely pending erasure.
|
|
* But the stock message ("has already been taken") then names a conflict
|
|
* the person at the form cannot see or clear from any screen (#1648).
|
|
* This rule keeps the refusal and explains it: when the address frees
|
|
* itself, or — for accounts deleted before erasure scheduling existed —
|
|
* which command frees it.
|
|
*
|
|
* Staff surfaces only. Public registration keeps the stock rule
|
|
* deliberately: telling an anonymous visitor "this address belongs to a
|
|
* deleted account" confirms the address had an account here, which is
|
|
* exactly the disclosure the generic message avoids.
|
|
*/
|
|
class AvailableEmailRule implements ValidationRule
|
|
{
|
|
/**
|
|
* @param Closure(string, string|null=): PotentiallyTranslatedString $fail
|
|
*/
|
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
|
{
|
|
if (! is_string($value) || $value === '') {
|
|
// required/string/email own that refusal.
|
|
return;
|
|
}
|
|
|
|
$holder = User::withTrashed()->where('email', $value)->first();
|
|
|
|
if ($holder === null) {
|
|
return;
|
|
}
|
|
|
|
if (! $holder->trashed()) {
|
|
// A living account: the stock unique message said all there
|
|
// is to say.
|
|
$fail('validation.unique')->translate();
|
|
|
|
return;
|
|
}
|
|
|
|
if ($holder->erase_after !== null) {
|
|
$fail(__('This email address belongs to a deleted account that is scheduled for permanent erasure. The address becomes available on :date. To free it sooner, erase the account with the projectsend:erase-account console command.', [
|
|
'date' => $holder->erase_after->toFormattedDateString(),
|
|
]));
|
|
|
|
return;
|
|
}
|
|
|
|
// Deleted before erasure scheduling existed, so no purge will ever
|
|
// reach it — only the operator command can free the address.
|
|
$fail(__('This email address belongs to a deleted account that has no erasure scheduled. Run the projectsend:erase-account console command to erase it and free the address.'));
|
|
}
|
|
}
|