Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
RefreshTokenRepository
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
4 / 4
7
100.00% covered (success)
100.00%
1 / 1
 getNewRefreshToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 persistNewRefreshToken
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 revokeRefreshToken
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 isRefreshTokenRevoked
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3/**
4 * OAuth2 refresh token repository.
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2026-02-09
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Auth\OAuth2\Repository;
21
22use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
23use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
24use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
25use phpMyFAQ\Auth\OAuth2\Entity\RefreshTokenEntity;
26
27final class RefreshTokenRepository extends AbstractRepository implements RefreshTokenRepositoryInterface
28{
29    public function getNewRefreshToken(): ?RefreshTokenEntityInterface
30    {
31        return new RefreshTokenEntity();
32    }
33
34    public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity): void
35    {
36        $insert = sprintf(
37            "INSERT INTO %s (identifier, access_token_identifier, revoked, expires_at, created)
38             VALUES ('%s', '%s', 0, '%s', %s)",
39            $this->table('faqoauth_refresh_tokens'),
40            $this->db()->escape($refreshTokenEntity->getIdentifier()),
41            $this->db()->escape($refreshTokenEntity->getAccessToken()->getIdentifier()),
42            $refreshTokenEntity->getExpiryDateTime()->format('Y-m-d H:i:s'),
43            $this->db()->now(),
44        );
45
46        if ($this->db()->query($insert) === false) {
47            throw UniqueTokenIdentifierConstraintViolationException::create();
48        }
49    }
50
51    public function revokeRefreshToken(string $tokenId): void
52    {
53        $this->db()->query(sprintf(
54            "UPDATE %s SET revoked = 1 WHERE identifier = '%s'",
55            $this->table('faqoauth_refresh_tokens'),
56            $this->db()->escape($tokenId),
57        ));
58    }
59
60    public function isRefreshTokenRevoked(string $tokenId): bool
61    {
62        $result = $this->db()->query(sprintf(
63            "SELECT revoked FROM %s WHERE identifier = '%s'",
64            $this->table('faqoauth_refresh_tokens'),
65            $this->db()->escape($tokenId),
66        ));
67
68        if ($result === false) {
69            return true;
70        }
71
72        $row = $this->db()->fetchObject($result);
73        if (!is_object($row)) {
74            return true;
75        }
76
77        return (int) ($row->revoked ?? 1) === 1;
78    }
79}