*/ public readonly array $context = [], ?Throwable $previous = null, ) { parent::__construct($message, $status ?? 0, $previous); } public static function misconfigured(string $key): self { return new self( "The Pireon OAuth client is not configured: [{$key}] is missing. ". 'Register this app on the identity server and fill in the PIREON_* keys in .env.' ); } /** * @param array $body */ public static function tokenRequestFailed(int $status, array $body): self { $error = $body['error'] ?? 'unknown_error'; $description = $body['error_description'] ?? $body['message'] ?? 'no description given'; return new self( "The identity server rejected the token request [{$error}]: {$description}", status: $status, context: $body, ); } /** * Whether the identity server actually judged the credentials, as opposed * to never getting far enough to look at them. * * The difference matters to whoever is staring at the screen: "those * credentials are wrong" sends them to check their password, and if the * truth is that the server answered 500 — or did not answer at all — that * is an hour of their life spent on the wrong problem. */ public function isRejection(): bool { return in_array($this->status, [400, 401, 403], true); } public static function unreachable(string $url, ?Throwable $previous = null): self { return new self( "The identity server at [{$url}] could not be reached.", context: ['url' => $url], previous: $previous, ); } public static function invalidState(): self { return new self( 'The OAuth callback state did not match the one we issued. '. 'The login attempt was discarded; please start again.', status: 400, ); } }