mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-24 04:16:20 +00:00
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.
This commit is contained in:
@@ -32,6 +32,7 @@ use Laravel\Sanctum\HasApiTokens;
|
||||
* @property int|null $dashboard_columns
|
||||
* @property int $storage_quota_mb
|
||||
* @property Carbon|null $erase_after
|
||||
* @property \Carbon\Carbon|null $expires_at
|
||||
* @property-read Role|null $role
|
||||
*/
|
||||
class User extends Authenticatable implements HasLocalePreference
|
||||
@@ -96,6 +97,32 @@ class User extends Authenticatable implements HasLocalePreference
|
||||
return $this->isStaff() && $this->role?->client_scoped === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this account's expiry date has passed. Only client accounts
|
||||
* are given one (see the client screens and /api/v1/clients).
|
||||
*/
|
||||
public function hasExpired(): bool
|
||||
{
|
||||
return $this->expires_at !== null && $this->expires_at->isPast();
|
||||
}
|
||||
|
||||
/**
|
||||
* The one question every door into the application asks of an account
|
||||
* that has already proved who it is: sign-in, every web request, every
|
||||
* API request, and the second-factor challenge.
|
||||
*
|
||||
* Expiry is checked here as well as by the hourly sweep that switches
|
||||
* `active` off, and neither is enough alone. The sweep is what keeps
|
||||
* everything else that reads `active` — lists, filters, seat counts —
|
||||
* in step. But a sweep runs on a schedule, and a scheduler that is not
|
||||
* running would leave an expired account working forever. So access
|
||||
* is refused the moment the date passes, whatever the flag says.
|
||||
*/
|
||||
public function maySignIn(): bool
|
||||
{
|
||||
return $this->active && ! $this->hasExpired();
|
||||
}
|
||||
|
||||
public function hasTwoFactorEnabled(): bool
|
||||
{
|
||||
return $this->two_factor_confirmed_at !== null;
|
||||
@@ -183,6 +210,10 @@ class User extends Authenticatable implements HasLocalePreference
|
||||
// is not something those call sites should depend on.
|
||||
'storage_quota_mb' => 'integer',
|
||||
'erase_after' => 'datetime',
|
||||
// Deliberately absent from $fillable too: when an account stops
|
||||
// working is decided by staff, never by a payload the account
|
||||
// itself could send (the profile form fills from its request).
|
||||
'expires_at' => 'datetime',
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'two_factor_secret' => 'encrypted',
|
||||
|
||||
Reference in New Issue
Block a user