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

daycry / auth / 22527658769

28 Feb 2026 07:41PM UTC coverage: 63.267% (-3.6%) from 66.864%
22527658769

push

github

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

Implement TOTP 2FA, JWT auth, device session tracking, and docs overhaul

465 of 1168 new or added lines in 52 files covered. (39.81%)

129 existing lines in 46 files now uncovered.

3064 of 4843 relevant lines covered (63.27%)

41.53 hits per line

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

76.92
/src/Entities/DeviceSession.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\Entities;
15

16
use CodeIgniter\I18n\Time;
17

18
/**
19
 * Represents a single authenticated device session for a user.
20
 *
21
 * @property Time        $created_at
22
 * @property string|null $device_name
23
 * @property int         $id
24
 * @property string      $ip_address
25
 * @property Time        $last_active
26
 * @property Time|null   $logged_out_at
27
 * @property string      $session_id
28
 * @property Time|null   $updated_at
29
 * @property string|null $user_agent
30
 * @property int         $user_id
31
 */
32
class DeviceSession extends Entity
33
{
34
    /**
35
     * @var list<string>
36
     */
37
    protected $dates = [
38
        'last_active',
39
        'logged_out_at',
40
        'created_at',
41
        'updated_at',
42
    ];
43

44
    /**
45
     * @var array<string, string>
46
     */
47
    protected $casts = [
48
        'id'      => '?integer',
49
        'user_id' => 'integer',
50
    ];
51

52
    /**
53
     * Returns true when the session has not been terminated.
54
     */
55
    public function isActive(): bool
5✔
56
    {
57
        $loggedOutAt = $this->attributes['logged_out_at'] ?? null;
5✔
58

59
        return $loggedOutAt === null || $loggedOutAt === '';
5✔
60
    }
61

62
    /**
63
     * Generates a human-readable device name from the user agent string.
64
     * Falls back to the stored device_name if present.
65
     */
66
    public function getDeviceLabel(): string
2✔
67
    {
68
        if ($this->device_name !== null && $this->device_name !== '') {
2✔
69
            return $this->device_name;
1✔
70
        }
71

72
        return self::parseUserAgent($this->attributes['user_agent'] ?? '');
1✔
73
    }
74

75
    /**
76
     * Parses a user agent string into a readable browser + OS label.
77
     */
78
    public static function parseUserAgent(string $userAgent): string
19✔
79
    {
80
        if ($userAgent === '') {
19✔
81
            return 'Unknown Device';
13✔
82
        }
83

84
        $browser = 'Unknown Browser';
6✔
85
        $os      = 'Unknown OS';
6✔
86

87
        // Detect browser (order matters — Edge/Opera must come before Chrome)
88
        if (str_contains($userAgent, 'Edg/') || str_contains($userAgent, 'Edge/')) {
6✔
89
            $browser = 'Microsoft Edge';
1✔
90
        } elseif (str_contains($userAgent, 'OPR/') || str_contains($userAgent, 'Opera/')) {
5✔
NEW
91
            $browser = 'Opera';
×
92
        } elseif (str_contains($userAgent, 'Firefox/')) {
5✔
93
            $browser = 'Firefox';
1✔
94
        } elseif (str_contains($userAgent, 'Chrome/')) {
4✔
95
            $browser = 'Chrome';
3✔
96
        } elseif (str_contains($userAgent, 'Safari/')) {
1✔
97
            $browser = 'Safari';
1✔
NEW
98
        } elseif (str_contains($userAgent, 'MSIE') || str_contains($userAgent, 'Trident/')) {
×
NEW
99
            $browser = 'Internet Explorer';
×
NEW
100
        } elseif (str_contains($userAgent, 'curl/')) {
×
NEW
101
            $browser = 'cURL';
×
NEW
102
        } elseif (str_contains($userAgent, 'Postman')) {
×
NEW
103
            $browser = 'Postman';
×
104
        }
105

106
        // Detect OS
107
        if (str_contains($userAgent, 'Windows')) {
6✔
108
            $os = 'Windows';
4✔
109
        } elseif (str_contains($userAgent, 'Macintosh') || str_contains($userAgent, 'Mac OS X')) {
2✔
110
            $os = 'macOS';
1✔
111
        } elseif (str_contains($userAgent, 'iPhone') || str_contains($userAgent, 'iPad')) {
1✔
NEW
112
            $os = 'iOS';
×
113
        } elseif (str_contains($userAgent, 'Android')) {
1✔
NEW
114
            $os = 'Android';
×
115
        } elseif (str_contains($userAgent, 'Linux')) {
1✔
116
            $os = 'Linux';
1✔
117
        }
118

119
        return $browser . ' on ' . $os;
6✔
120
    }
121
}
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