Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
AuthCodeRepository
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
4 / 4
10
100.00% covered (success)
100.00%
1 / 1
 getNewAuthCode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 persistNewAuthCode
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
5
 revokeAuthCode
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 isAuthCodeRevoked
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3/**
4 * OAuth2 auth code 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\AuthCodeEntityInterface;
23use League\OAuth2\Server\Entities\ScopeEntityInterface;
24use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
25use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
26use phpMyFAQ\Auth\OAuth2\Entity\AuthCodeEntity;
27
28final class AuthCodeRepository extends AbstractRepository implements AuthCodeRepositoryInterface
29{
30    public function getNewAuthCode(): AuthCodeEntityInterface
31    {
32        return new AuthCodeEntity();
33    }
34
35    public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity): void
36    {
37        $scopes = array_map(
38            static fn(ScopeEntityInterface $scope): string => $scope->getIdentifier(),
39            $authCodeEntity->getScopes(),
40        );
41
42        $userIdentifier = $authCodeEntity->getUserIdentifier();
43
44        $userIdentifier = $userIdentifier === null ? null : (string) $userIdentifier;
45
46        $redirectUri = $authCodeEntity->getRedirectUri();
47
48        $insert = sprintf(
49            "INSERT INTO %s (identifier, client_id, user_id, redirect_uri, scopes, revoked, expires_at, created)
50             VALUES ('%s', '%s', %s, %s, '%s', 0, '%s', %s)",
51            $this->table('faqoauth_auth_codes'),
52            $this->db()->escape($authCodeEntity->getIdentifier()),
53            $this->db()->escape($authCodeEntity->getClient()->getIdentifier()),
54            $userIdentifier === null ? 'NULL' : "'" . $this->db()->escape($userIdentifier) . "'",
55            $redirectUri === null ? 'NULL' : "'" . $this->db()->escape($redirectUri) . "'",
56            $this->db()->escape((string) json_encode($scopes)),
57            $authCodeEntity->getExpiryDateTime()->format('Y-m-d H:i:s'),
58            $this->db()->now(),
59        );
60
61        if ($this->db()->query($insert) === false) {
62            throw UniqueTokenIdentifierConstraintViolationException::create();
63        }
64    }
65
66    public function revokeAuthCode(string $codeId): void
67    {
68        $this->db()->query(sprintf(
69            "UPDATE %s SET revoked = 1 WHERE identifier = '%s'",
70            $this->table('faqoauth_auth_codes'),
71            $this->db()->escape($codeId),
72        ));
73    }
74
75    public function isAuthCodeRevoked(string $codeId): bool
76    {
77        $result = $this->db()->query(sprintf(
78            "SELECT revoked FROM %s WHERE identifier = '%s'",
79            $this->table('faqoauth_auth_codes'),
80            $this->db()->escape($codeId),
81        ));
82
83        if ($result === false) {
84            return true;
85        }
86
87        $row = $this->db()->fetchObject($result);
88        if (!is_object($row)) {
89            return true;
90        }
91
92        return (int) ($row->revoked ?? 1) === 1;
93    }
94}