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

daycry / auth / 25518434194

07 May 2026 07:49PM UTC coverage: 58.608% (-6.4%) from 64.989%
25518434194

push

github

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

Implement security enhancements and new account features

277 of 1030 new or added lines in 55 files covered. (26.89%)

11 existing lines in 6 files now uncovered.

3544 of 6047 relevant lines covered (58.61%)

47.97 hits per line

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

0.0
/src/Models/PasswordHistoryModel.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\User;
18

19
/**
20
 * Stores hashes of recently used passwords to prevent reuse.
21
 *
22
 * Only the last N hashes (configured via `AuthSecurity::$passwordHistorySize`)
23
 * are retained per user — older entries are pruned on each insert.
24
 */
25
class PasswordHistoryModel extends BaseModel
26
{
27
    protected $primaryKey     = 'id';
28
    protected $returnType     = 'array';
29
    protected $useSoftDeletes = false;
30
    protected $allowedFields  = [
31
        'user_id',
32
        'password_hash',
33
        'created_at',
34
    ];
35
    protected $useTimestamps = false;
36

NEW
37
    protected function initialize(): void
×
38
    {
NEW
39
        parent::initialize();
×
40

NEW
41
        $this->table = $this->tables['password_history'] ?? 'auth_password_history';
×
42
    }
43

44
    /**
45
     * Records a previously-used password hash for this user. Trims older
46
     * entries beyond the configured retention window.
47
     */
NEW
48
    public function recordHash(User $user, string $passwordHash, int $retain): void
×
49
    {
NEW
50
        if ($retain <= 0 || $passwordHash === '') {
×
NEW
51
            return;
×
52
        }
53

NEW
54
        $this->insert([
×
NEW
55
            'user_id'       => (int) $user->id,
×
NEW
56
            'password_hash' => $passwordHash,
×
NEW
57
            'created_at'    => Time::now()->toDateTimeString(),
×
NEW
58
        ]);
×
59

NEW
60
        $this->pruneToRetention($user, $retain);
×
61
    }
62

63
    /**
64
     * Returns true when the supplied plaintext password matches one of the
65
     * user's recent password hashes.
66
     */
NEW
67
    public function matchesRecent(User $user, string $plainPassword, int $retain): bool
×
68
    {
NEW
69
        if ($retain <= 0 || $plainPassword === '') {
×
NEW
70
            return false;
×
71
        }
72

NEW
73
        $rows = $this->where('user_id', $user->id)
×
NEW
74
            ->orderBy('id', 'DESC')
×
NEW
75
            ->limit($retain)
×
NEW
76
            ->find();
×
77

NEW
78
        foreach ($rows as $row) {
×
NEW
79
            $hash = (string) ($row['password_hash'] ?? '');
×
80

NEW
81
            if ($hash !== '' && password_verify($plainPassword, $hash)) {
×
NEW
82
                return true;
×
83
            }
84
        }
85

NEW
86
        return false;
×
87
    }
88

89
    /**
90
     * Removes all history for a user (e.g. on user deletion / anonymization).
91
     */
NEW
92
    public function purgeForUser(User $user): void
×
93
    {
NEW
94
        $this->where('user_id', $user->id)->delete();
×
95
    }
96

97
    /**
98
     * Keeps only the most recent $retain entries for the user.
99
     */
NEW
100
    private function pruneToRetention(User $user, int $retain): void
×
101
    {
NEW
102
        $keep = $this->where('user_id', $user->id)
×
NEW
103
            ->orderBy('id', 'DESC')
×
NEW
104
            ->limit($retain)
×
NEW
105
            ->select('id')
×
NEW
106
            ->find();
×
107

NEW
108
        if ($keep === []) {
×
NEW
109
            return;
×
110
        }
111

NEW
112
        $keepIds = [];
×
113

NEW
114
        foreach ($keep as $row) {
×
NEW
115
            $keepIds[] = (int) ($row['id'] ?? 0);
×
116
        }
117

NEW
118
        $this->where('user_id', $user->id)
×
NEW
119
            ->whereNotIn('id', $keepIds)
×
NEW
120
            ->delete();
×
121
    }
122
}
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