bearerToken(); if (blank($token) || ! $this->client->isConfigured()) { return null; } $identity = $this->resolveIdentity($token); if ($identity === null || ! $identity->isUsable()) { return null; } $user = $this->localUser($identity); // Locally deactivating an account revokes API access immediately, // without needing anything to change upstream. return $user->is_active ? $user : null; } protected function resolveIdentity(string $token): ?PireonIdentity { $key = 'pireon:introspect:'.hash('sha256', $token); $ttl = (int) config('pireon.introspection_ttl', 60); $cached = Cache::get($key); if (is_array($cached)) { return $cached === [] ? null : PireonIdentity::fromResponse($cached); } try { $identity = $this->client->identity($token); } catch (PireonException $e) { // Cache the rejection too, so a client hammering us with a dead // token cannot turn into a flood of upstream requests. Never cache // a transport failure — that would lock out valid tokens whenever // the identity server has a blip. if ($e->status === 401) { Cache::put($key, [], min($ttl, 60)); } return null; } Cache::put($key, $identity->toArray(), $ttl); return $identity; } /** * Mirror the identity locally so policies, ownership checks and foreign * keys have a real row to work with. Local roles are left untouched. */ protected function localUser(PireonIdentity $identity): User { $user = User::query()->where('oauth_id', $identity->id)->first(); return $user ?? $this->sync->handle($identity); } }