Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
ApiKey
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
4 / 4
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasScope
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isValid
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 verifySecret
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Core\Api\Domain\Model;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use DateTimeImmutable;
12
13/**
14 * Scoped API Key Domain Entity.
15 *
16 * Represents an authenticated API client identity with granular permission scopes,
17 * rate limit thresholds, and expiration lifecycle (NIST SP 800-63B / OWASP ASVS V2.10).
18 *
19 * @package App\Core\Api\Domain\Model
20 */
21final readonly class ApiKey
22{
23    /**
24     * ApiKey constructor.
25     *
26     * @param int                    $id         Primary database key.
27     * @param string                 $name       Human-readable client/application name.
28     * @param string                 $keyPrefix  Public prefix for fast lookup.
29     * @param string                 $keyHash    Cryptographic hash of the API key secret.
30     * @param list<string>           $scopes     Granted API capability scopes.
31     * @param int                    $rateLimit  Allowed requests per minute threshold.
32     * @param DateTimeImmutable|null $expiresAt  Optional expiration timestamp.
33     * @param DateTimeImmutable|null $lastUsedAt Optional last activity timestamp.
34     * @param bool                   $isActive   Whether key is active and enabled.
35     */
36    public function __construct(
37        public int $id,
38        public string $name,
39        public string $keyPrefix,
40        public string $keyHash,
41        public array $scopes = ['*'],
42        public int $rateLimit = 120,
43        public ?DateTimeImmutable $expiresAt = null,
44        public ?DateTimeImmutable $lastUsedAt = null,
45        public bool $isActive = true,
46    ) {
47    }
48
49    /**
50     * Checks if the API key grants the requested scope.
51     *
52     * @param string $scope Requested capability scope (e.g. 'engine:read', 'mail:send').
53     * @return bool True if granted or if wildcard '*' is present.
54     */
55    public function hasScope(string $scope): bool
56    {
57        if (in_array('*', $this->scopes, true)) {
58            return true;
59        }
60
61        return in_array($scope, $this->scopes, true);
62    }
63
64    /**
65     * Checks if the API key is currently active and not expired.
66     *
67     * @param DateTimeImmutable|null $now Optional reference time.
68     * @return bool True if key can be used.
69     */
70    public function isValid(?DateTimeImmutable $now = null): bool
71    {
72        if (!$this->isActive) {
73            return false;
74        }
75
76        if ($this->expiresAt === null) {
77            return true;
78        }
79
80        $currentTime = $now ?? new DateTimeImmutable();
81
82        return $this->expiresAt > $currentTime;
83    }
84
85    /**
86     * Verifies raw token against stored cryptographic hash in constant time.
87     *
88     * @param string $rawToken Raw bearer token from HTTP Authorization header.
89     * @return bool True if matching.
90     */
91    public function verifySecret(string $rawToken): bool
92    {
93        $computedHash = hash('sha256', $rawToken);
94
95        return hash_equals($this->keyHash, $computedHash);
96    }
97}