Files
projectsend/app/Modules/Clients/ClientsServiceProvider.php
ignacionelson c21658f6f7 Let a client account expire on a date
Staff can give a client an expiry date on the create and edit screens,
and through /api/v1/clients. When the date passes, the client is refused
at sign-in and on their next request, and their API access ends too.
Files and history stay, and a later date (or none) brings them back.

Access is checked through one predicate, User::maySignIn(), at every
door: sign-in, the web session, API tokens and the two-factor
challenge. An hourly sweep also switches `active` off, so the list,
its filter and seat counts agree. The sweep is not what enforces it,
so a scheduler that is not running cannot keep an account open.

An account cannot be active with a date that has passed. Reactivating
an expired client needs a new date in the same save.

The day-means-end-of-day-where-you-are rule moved out of FileExpiry
into a shared DateInput, so file and account expiry read dates the
same way.

Requested by @Drardollan in #1310.
2026-09-13 14:57:16 -03:00

47 lines
2.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Modules\Clients;
use App\Modules\Notifications\NotificationTypeDefinition;
use App\Modules\Notifications\NotificationTypeRegistry;
use Illuminate\Support\ServiceProvider;
class ClientsServiceProvider extends ServiceProvider
{
public function boot(): void
{
// In-app only, the same reasoning client_uploaded gives: email for
// this event is already sent separately, to whatever raw addresses
// Setting::AdminNotificationEmails lists, via
// AdminClientRegisteredNotification. Routing it through Notifier's
// mail dispatch as well would risk double-emailing any staff member
// who also appears in that list.
//
// One type for both doors, deliberately. A client arriving through
// the public form and one arriving through an invitation are the
// same event to the person being told — an account now exists that
// did not — and a second type would buy nothing: preferences here
// govern email only (see NotificationPreferences), so it could not
// be switched off separately, and which door it came through is one
// click away in the activity log and on the invitations screen.
$this->app->make(NotificationTypeRegistry::class)->register(new NotificationTypeDefinition(
key: 'client_registered',
label: 'A new client registered an account',
template: ':clientName (:clientEmail) registered a client account',
// The list, filtered to this address — not clients.edit, which
// is gated by edit_clients while the recipients below are chosen
// by manage_clients. A notification that refuses the person it
// was sent to is worse than one that lands a click short.
url: fn (array $data): string => route('clients.index', ['search' => $data['clientEmail']]),
));
if ($this->app->runningInConsole()) {
$this->commands([
Console\ExpireClientAccountsCommand::class,
]);
}
}
}