Files
landing-api/app/Services/Pireon/TokenStore.php
Josep Piguillem ec4f687ca8
Some checks failed
Tests / lint (push) Has been cancelled
Tests / test (push) Has been cancelled
Tests / test-postgres (push) Has been cancelled
Tests / mcp (push) Has been cancelled
Offer «Entrar con Pireon» on the panel's login page
Under the e-mail and password form, a button takes the redirect flow
(Authorization Code + PKCE), so the password is only ever typed at the
identity server. The routes for it were already there; the page had no
way to reach them.

A Pireon client allows one grant type and the panel's is a password
client, so the redirect flow can use its own: PIREON_REDIRECT_CLIENT_ID
and PIREON_REDIRECT_CLIENT_SECRET (the main client when they are empty).
The button shows up once that client is set, or with
PIREON_REDIRECT_LOGIN=true. Each session renews its token with the
client that issued it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 12:46:07 +02:00

96 lines
2.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Pireon;
use Illuminate\Contracts\Session\Session;
/**
* Keeps the signed-in panel user's token set in the session and transparently
* refreshes it when it ages out.
*
* Tokens live in the session rather than the database on purpose: they are
* per-session credentials, and logging out or the session expiring should take
* them with it.
*/
class TokenStore
{
protected const KEY = 'pireon.token';
/** Which client issued it: a refresh token only works with that one. */
protected const ISSUER = 'pireon.token_issuer';
public function __construct(
protected Session $session,
protected PireonClient $client,
) {}
/**
* @param bool|null $viaRedirect Whether the redirect flow's client
* issued it; null keeps what was said.
*/
public function put(AccessToken $token, ?bool $viaRedirect = null): void
{
$this->session->put(self::KEY, $token->toArray());
if ($viaRedirect !== null) {
$this->session->put(self::ISSUER, $viaRedirect ? 'redirect' : 'password');
}
}
public function get(): ?AccessToken
{
$state = $this->session->get(self::KEY);
return is_array($state) ? AccessToken::fromArray($state) : null;
}
public function forget(): void
{
$this->session->forget([self::KEY, self::ISSUER]);
}
/**
* The current access token, refreshed first if it has expired.
*
* Returns null when there is no token, or when the refresh failed — the
* caller should treat that as "this session is no longer authenticated"
* and send the user back through the login flow.
*/
public function accessToken(): ?string
{
$token = $this->get();
if ($token === null) {
return null;
}
if (! $token->hasExpired()) {
return $token->accessToken;
}
if (! $token->isRefreshable()) {
$this->forget();
return null;
}
try {
$issuer = $this->session->get(self::ISSUER) === 'redirect'
? PireonClient::forRedirect()
: $this->client;
$refreshed = $issuer->refresh($token->refreshToken);
} catch (PireonException) {
// The refresh token was revoked or expired upstream.
$this->forget();
return null;
}
$this->put($refreshed);
return $refreshed->accessToken;
}
}