Until now, an account switched off or a token revoked at the identity server kept working here: bearer tokens until the introspection cache ran out, panel sessions until they ended, and machine tokens for good, as they only look at the local account. POST /api/v1/webhooks/pireon takes user.deactivated (the account goes inactive, which stops its machine tokens and the panel; its sessions end and its cached tokens are forgotten) and token.revoked (sessions and cached tokens go). Pireon signs each delivery with the secret of the client that registered the webhook, so either PIREON_WEBHOOK_SECRET (password client) or PIREON_REDIRECT_WEBHOOK_SECRET («Entrar con Pireon») verifies it; with neither, the endpoint refuses everything. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
2.3 KiB
PHP
80 lines
2.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Actions\Auth;
|
|
|
|
use App\Auth\PireonTokenGuard;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* What a Pireon webhook changes here.
|
|
*
|
|
* - `user.deactivated`: the account goes inactive (which also stops its
|
|
* machine tokens and the panel), its panel sessions end and its cached
|
|
* bearer tokens are forgotten. Signing in again once Pireon reactivates it
|
|
* turns it back on, as every sign-in copies the upstream state.
|
|
* - `token.revoked`: only token hashes are cached, so every cached token and
|
|
* panel session of that person goes; they sign in again.
|
|
*
|
|
* Anything else, or a person who never signed in here, is ignored. Repeating
|
|
* a delivery (one arrives per registered client) changes nothing.
|
|
*/
|
|
class HandlePireonWebhook
|
|
{
|
|
public const EVENTS = ['user.deactivated', 'token.revoked'];
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
*/
|
|
public function handle(string $event, array $data): void
|
|
{
|
|
if (! in_array($event, self::EVENTS, true)) {
|
|
Log::debug('Unhandled Pireon webhook', ['event' => $event]);
|
|
|
|
return;
|
|
}
|
|
|
|
$oauthId = $data['user_id'] ?? $data['userId'] ?? ($data['user']['id'] ?? null);
|
|
|
|
if ($oauthId === null || $oauthId === '') {
|
|
Log::warning('Pireon webhook without a user id, ignored', ['event' => $event]);
|
|
|
|
return;
|
|
}
|
|
|
|
PireonTokenGuard::forgetUser((string) $oauthId);
|
|
|
|
$user = User::query()->where('oauth_id', $oauthId)->first();
|
|
|
|
if ($user === null) {
|
|
return;
|
|
}
|
|
|
|
if ($event === 'user.deactivated' && $user->is_active) {
|
|
$user->forceFill(['is_active' => false])->save();
|
|
}
|
|
|
|
$this->endSessions($user);
|
|
}
|
|
|
|
/**
|
|
* Sessions kept in the database can be ended one person at a time. With
|
|
* any other driver they cannot, but an inactive account is still turned
|
|
* away on its next request (canAccessPanel).
|
|
*/
|
|
protected function endSessions(User $user): void
|
|
{
|
|
if (config('session.driver') !== 'database') {
|
|
return;
|
|
}
|
|
|
|
DB::connection(config('session.connection'))
|
|
->table((string) config('session.table', 'sessions'))
|
|
->where('user_id', $user->getKey())
|
|
->delete();
|
|
}
|
|
}
|