• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daycry / auth / 26937880755

04 Jun 2026 07:38AM UTC coverage: 75.983% (+4.4%) from 71.569%
26937880755

push

github

web-flow
Merge pull request #56 from daycry/development

feat

613 of 719 new or added lines in 42 files covered. (85.26%)

3 existing lines in 3 files now uncovered.

5179 of 6816 relevant lines covered (75.98%)

69.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.12
/src/Models/WebAuthnCredentialRepository.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of Daycry Auth.
7
 *
8
 * (c) Daycry <daycry9@proton.me>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace Daycry\Auth\Models;
15

16
use CodeIgniter\I18n\Time;
17
use Daycry\Auth\Entities\WebAuthnCredential;
18
use Symfony\Component\Serializer\SerializerInterface;
19
use Webauthn\CredentialRecord;
20
use Webauthn\PublicKeyCredentialDescriptor;
21

22
/**
23
 * Persistence seam mapping auth_webauthn_credentials rows to/from the
24
 * web-auth/webauthn-lib CredentialRecord. Implements no library interface
25
 * (v5 pure-PHP needs none); mirrors OAuthTokenRepository.
26
 */
27
class WebAuthnCredentialRepository
28
{
29
    public function __construct(
17✔
30
        private readonly WebAuthnCredentialModel $model,
31
        private readonly SerializerInterface $serializer,
32
    ) {
33
    }
17✔
34

35
    private function b64url(string $bytes): string
13✔
36
    {
37
        return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');
13✔
38
    }
39

NEW
40
    public function findEntityByCredentialId(string $credentialIdBase64Url): ?WebAuthnCredential
×
41
    {
NEW
42
        return $this->model->firstActiveByCredentialId($credentialIdBase64Url);
×
43
    }
44

45
    /**
46
     * Whether the given credential record is already persisted (by its
47
     * credential id, computed exactly as {@see store()} does). Considers ALL
48
     * rows including revoked ones, mirroring the UNIQUE constraint which
49
     * ignores revoked_at.
50
     */
51
    public function existsByCredentialId(CredentialRecord $record): bool
11✔
52
    {
53
        return $this->model->existsByCredentialId($this->b64url($record->publicKeyCredentialId));
11✔
54
    }
55

56
    public function findRecordByCredentialId(string $credentialIdBase64Url): ?CredentialRecord
7✔
57
    {
58
        $row = $this->model->firstActiveByCredentialId($credentialIdBase64Url);
7✔
59
        if ($row === null) {
7✔
60
            return null;
1✔
61
        }
62

63
        return $this->serializer->deserialize($row->credential, CredentialRecord::class, 'json');
6✔
64
    }
65

66
    public function userIdForCredentialId(string $credentialIdBase64Url): int|string|null
8✔
67
    {
68
        $row = $this->model->firstActiveByCredentialId($credentialIdBase64Url);
8✔
69

70
        return $row?->user_id;
8✔
71
    }
72

73
    /**
74
     * @return list<PublicKeyCredentialDescriptor>
75
     */
76
    public function descriptorsForUser(int|string $userId): array
12✔
77
    {
78
        $descriptors = [];
12✔
79

80
        foreach ($this->model->activeForUser($userId) as $row) {
12✔
81
            $record        = $this->serializer->deserialize($row->credential, CredentialRecord::class, 'json');
4✔
82
            $descriptors[] = $record->getPublicKeyCredentialDescriptor();
4✔
83
        }
84

85
        return $descriptors;
12✔
86
    }
87

88
    public function countActiveForUser(int|string $userId): int
14✔
89
    {
90
        return $this->model->countActiveForUser($userId);
14✔
91
    }
92

93
    public function store(int|string $userId, CredentialRecord $record, ?string $name): WebAuthnCredential
13✔
94
    {
95
        $credentialId = $this->b64url($record->publicKeyCredentialId);
13✔
96

97
        $id = $this->model->insert([
13✔
98
            'user_id'       => $userId,
13✔
99
            'credential_id' => $credentialId,
13✔
100
            'credential'    => $this->serializer->serialize($record, 'json'),
13✔
101
            'user_handle'   => $record->userHandle,
13✔
102
            'name'          => $name,
13✔
103
            'sign_count'    => $record->counter,
13✔
104
            'transports'    => json_encode($record->transports),
13✔
105
            'aaguid'        => $record->aaguid->toRfc4122(),
13✔
106
        ], true);
13✔
107

108
        /** @var WebAuthnCredential $row */
109
        $row = $this->model->find($id);
13✔
110

111
        return $row;
13✔
112
    }
113

114
    public function updateCounter(CredentialRecord $record): void
5✔
115
    {
116
        $credentialId = $this->b64url($record->publicKeyCredentialId);
5✔
117

118
        $this->model->where('credential_id', $credentialId)->set([
5✔
119
            'credential'   => $this->serializer->serialize($record, 'json'),
5✔
120
            'sign_count'   => $record->counter,
5✔
121
            'last_used_at' => Time::now()->format('Y-m-d H:i:s'),
5✔
122
        ])->update();
5✔
123
    }
124

125
    public function revokeByUuid(int|string $userId, string $uuid): bool
2✔
126
    {
127
        $row = $this->model->where('user_id', $userId)->where('uuid', $uuid)->where('revoked_at')->first();
2✔
128
        if ($row === null) {
2✔
NEW
129
            return false;
×
130
        }
131

132
        $this->model->where('id', $row->id)->set(['revoked_at' => Time::now()->format('Y-m-d H:i:s')])->update();
2✔
133

134
        return true;
2✔
135
    }
136
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc