} */ public function create(): array { if ($this->record() !== null) { throw new RuntimeException('An API key already exists. Rotate it to create a replacement.'); } $apiKey = $this->generate(); $now = current_time('mysql', true); $record = $this->makeRecord($apiKey, $now, null); if (!add_option(self::OPTION, $record, '', false)) { throw new RuntimeException('The API key could not be created. Reload Health and try again.'); } return ['api_key' => $apiKey, 'status' => $this->metadata($record)]; } /** @return array{api_key:string,status:array} */ public function rotate(): array { $current = $this->record(); if ($current === null) { throw new RuntimeException('Create the first API key before rotating it.'); } $apiKey = $this->generate(); $now = current_time('mysql', true); $record = $this->makeRecord( $apiKey, (string) ($current['created_at'] ?? $now), $now ); update_option(self::OPTION, $record, false); return ['api_key' => $apiKey, 'status' => $this->metadata($record)]; } public function verify(string $candidate): bool { if (!str_starts_with($candidate, 'croco_wp_')) { return false; } $record = $this->record(); $hash = is_array($record) ? (string) ($record['hash'] ?? '') : ''; return $hash !== '' && password_verify($candidate, $hash); } /** @return array */ public function status(): array { return $this->metadata($this->record()); } private function generate(): string { return 'croco_wp_' . rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '='); } /** @return array{hash:string,fingerprint:string,created_at:string,rotated_at:?string} */ private function makeRecord(string $apiKey, string $createdAt, ?string $rotatedAt): array { $hash = password_hash($apiKey, PASSWORD_DEFAULT); return [ 'hash' => $hash, 'fingerprint' => substr(hash('sha256', $apiKey), 0, 12), 'created_at' => $createdAt, 'rotated_at' => $rotatedAt, ]; } /** @return array|null */ private function record(): ?array { $record = get_option(self::OPTION, null); return is_array($record) && !empty($record['hash']) ? $record : null; } /** @param array|null $record * @return array */ private function metadata(?array $record): array { if ($record === null) { return [ 'configured' => false, 'fingerprint' => null, 'created_at' => null, 'rotated_at' => null, ]; } return [ 'configured' => true, 'fingerprint' => (string) ($record['fingerprint'] ?? ''), 'created_at' => (string) ($record['created_at'] ?? ''), 'rotated_at' => empty($record['rotated_at']) ? null : (string) $record['rotated_at'], ]; } }