1+ <?php
2+ namespace App \Services \Auth ;
3+ /**
4+ * Copyright 2025 OpenStack Foundation
5+ * Licensed under the Apache License, Version 2.0 (the "License");
6+ * you may not use this file except in compliance with the License.
7+ * You may obtain a copy of the License at
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ **/
15+
16+ use App \libs \Auth \Models \UserTrustedDevice ;
17+ use Auth \Repositories \IUserTrustedDeviceRepository ;
18+ use Auth \User ;
19+ use DateTime ;
20+ use DateInterval ;
21+ use DateTimeZone ;
22+
23+ /**
24+ * Class DeviceTrustService
25+ * @package App\Services\Auth
26+ */
27+ final class DeviceTrustService implements IDeviceTrustService
28+ {
29+ public function __construct (private readonly IUserTrustedDeviceRepository $ repository )
30+ {
31+ }
32+
33+ public function generateDeviceIdentifier (string $ token ): string
34+ {
35+ return hash ('sha256 ' , $ token );
36+ }
37+
38+ public function trustDevice (User $ user , string $ userAgent , string $ ipAddress ): string
39+ {
40+ $ rawToken = bin2hex (random_bytes (64 ));
41+
42+ $ lifetimeDays = (int ) config ('two_factor.device_trust_lifetime_days ' , 30 );
43+ $ now = new DateTime ('now ' , new DateTimeZone ('UTC ' ));
44+ $ expiresAt = clone $ now ;
45+ $ expiresAt ->add (new DateInterval ("P {$ lifetimeDays }D " ));
46+
47+ $ device = new UserTrustedDevice ();
48+ $ device ->setUser ($ user );
49+ $ device ->setDeviceIdentifier ($ this ->generateDeviceIdentifier ($ rawToken ));
50+ $ device ->setDeviceName (substr ($ userAgent , 0 , 255 ));
51+ $ device ->setIpAddress ($ ipAddress );
52+ $ device ->setUserAgent ($ userAgent );
53+ $ device ->setTrustedAt ($ now );
54+ $ device ->setExpiresAt ($ expiresAt );
55+ $ device ->setLastSeenAt (clone $ now );
56+ $ device ->setIsRevoked (false );
57+
58+ $ this ->repository ->add ($ device , true );
59+
60+ return $ rawToken ;
61+ }
62+
63+ public function isDeviceTrusted (User $ user , ?string $ cookieToken ): bool
64+ {
65+ if (empty ($ cookieToken )) {
66+ return false ;
67+ }
68+
69+ $ identifier = $ this ->generateDeviceIdentifier ($ cookieToken );
70+ $ device = $ this ->repository ->getByUserAndDeviceIdentifier ($ user , $ identifier );
71+
72+ if ($ device instanceof UserTrustedDevice === false || $ device ->isRevoked () || $ device ->isExpired ()) {
73+ return false ;
74+ }
75+
76+ $ device ->setLastSeenAt (new DateTime ('now ' , new DateTimeZone ('UTC ' )));
77+ return true ;
78+ }
79+
80+ public function removeTrustedDevices (User $ user ): void
81+ {
82+ $ this ->repository ->revokeAllForUser ($ user );
83+ }
84+ }
0 commit comments