diff --git a/src/Auth/Eloquent/User.php b/src/Auth/Eloquent/User.php index 35094be0bd6..f86ae94cef9 100644 --- a/src/Auth/Eloquent/User.php +++ b/src/Auth/Eloquent/User.php @@ -2,6 +2,7 @@ namespace Statamic\Auth\Eloquent; +use Carbon\CarbonInterface; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; @@ -370,7 +371,7 @@ public function lastLogin() return null; } - return $date instanceof Carbon ? $date : Carbon::createFromFormat($this->model()->getDateFormat(), $date); + return $date instanceof CarbonInterface ? $date : Carbon::createFromFormat($this->model()->getDateFormat(), $date); } public function setLastLogin($time) diff --git a/src/Auth/WebAuthn/Passkey.php b/src/Auth/WebAuthn/Passkey.php index a807b4dded4..da354673b67 100644 --- a/src/Auth/WebAuthn/Passkey.php +++ b/src/Auth/WebAuthn/Passkey.php @@ -3,6 +3,7 @@ namespace Statamic\Auth\WebAuthn; use Carbon\Carbon; +use Carbon\CarbonInterface; use ParagonIE\ConstantTime\Base64UrlSafe; use Statamic\Contracts\Auth\Passkey as Contract; use Statamic\Contracts\Auth\User as UserContract; @@ -68,7 +69,7 @@ public function lastLogin(): ?Carbon return $this->lastLogin; } - public function setLastLogin(null|int|Carbon $time): Contract + public function setLastLogin(null|int|CarbonInterface $time): Contract { if ($time === null) { $this->lastLogin = null; @@ -76,7 +77,7 @@ public function setLastLogin(null|int|Carbon $time): Contract return $this; } - $this->lastLogin = $time instanceof Carbon ? $time : Carbon::createFromTimestamp($time); + $this->lastLogin = is_int($time) ? Carbon::createFromTimestamp($time) : Carbon::instance($time); return $this; } diff --git a/src/Contracts/Auth/Passkey.php b/src/Contracts/Auth/Passkey.php index 81404728c10..6c93f6b1a23 100644 --- a/src/Contracts/Auth/Passkey.php +++ b/src/Contracts/Auth/Passkey.php @@ -3,6 +3,7 @@ namespace Statamic\Contracts\Auth; use Carbon\Carbon; +use Carbon\CarbonInterface; use Webauthn\PublicKeyCredentialSource; interface Passkey @@ -23,7 +24,7 @@ public function setCredential(array|PublicKeyCredentialSource $credential): self public function lastLogin(): ?Carbon; - public function setLastLogin(null|int|Carbon $time): self; + public function setLastLogin(null|int|CarbonInterface $time): self; public function save(): bool; diff --git a/tests/Auth/Eloquent/EloquentUserTest.php b/tests/Auth/Eloquent/EloquentUserTest.php index 92520cf03d4..c001e1213a0 100644 --- a/tests/Auth/Eloquent/EloquentUserTest.php +++ b/tests/Auth/Eloquent/EloquentUserTest.php @@ -2,10 +2,14 @@ namespace Tests\Auth\Eloquent; +use Carbon\CarbonImmutable; +use Carbon\CarbonInterface; +use DateTime; use Illuminate\Filesystem\Filesystem; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Date; use ParagonIE\ConstantTime\Base64UrlSafe; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; @@ -310,6 +314,27 @@ public function it_gets_the_timestamps_property_from_the_model() $this->assertFalse($user->timestamps); } + #[Test] + public function it_gets_the_last_login_when_the_app_uses_immutable_dates() + { + Date::use(CarbonImmutable::class); + + // The Control Panel's Localize middleware changes the format too. + Date::setToStringFormat(DateTime::ATOM); + + $this->beforeApplicationDestroyed(function () { + Date::useDefault(); + Date::resetToStringFormat(); + }); + + $user = $this->makeUser(); + $user->model()->mergeCasts(['last_login' => 'datetime']); + $user->setLastLogin(now()); + + $this->assertInstanceOf(CarbonInterface::class, $user->lastLogin()); + $this->assertEquals('2019-11-21 23:39:29', $user->lastLogin()->format('Y-m-d H:i:s')); + } + #[Test] public function it_gets_super_correctly_on_the_model() { diff --git a/tests/Auth/WebAuthn/EloquentPasskeyTest.php b/tests/Auth/WebAuthn/EloquentPasskeyTest.php index 9bf27c38b5f..913737006d7 100644 --- a/tests/Auth/WebAuthn/EloquentPasskeyTest.php +++ b/tests/Auth/WebAuthn/EloquentPasskeyTest.php @@ -3,8 +3,10 @@ namespace Tests\Auth\WebAuthn; use Carbon\Carbon; +use Carbon\CarbonImmutable; use Illuminate\Filesystem\Filesystem; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\Date; use ParagonIE\ConstantTime\Base64UrlSafe; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\Test; @@ -157,6 +159,26 @@ public function it_updates_existing_passkey() ]); } + #[Test] + public function it_saves_a_passkey_with_a_last_login_when_the_app_uses_immutable_dates() + { + Date::use(CarbonImmutable::class); + + $this->beforeApplicationDestroyed(fn () => Date::useDefault()); + + $user = User::make()->email('test@example.com')->set('name', 'Test User'); + $user->save(); + + $passkey = (new Passkey) + ->setName('My Passkey') + ->setUser($user) + ->setCredential($this->createTestCredential()) + ->setLastLogin(now()); + + $this->assertTrue($passkey->save()); + $this->assertEquals('2019-11-21 23:39:29', $passkey->lastLogin()->format('Y-m-d H:i:s')); + } + #[Test] public function it_saves_multiple_passkeys_for_same_user() { diff --git a/tests/Auth/WebAuthn/PasskeyTests.php b/tests/Auth/WebAuthn/PasskeyTests.php index afa97c20f18..645f8fd6317 100644 --- a/tests/Auth/WebAuthn/PasskeyTests.php +++ b/tests/Auth/WebAuthn/PasskeyTests.php @@ -3,6 +3,7 @@ namespace Tests\Auth\WebAuthn; use Carbon\Carbon; +use Carbon\CarbonImmutable; use PHPUnit\Framework\Attributes\Test; use Statamic\Contracts\Auth\Passkey; use Statamic\Facades\User; @@ -31,6 +32,22 @@ public function it_gets_last_login() $this->assertEquals('2024-01-15 10:30:00', $passkey->lastLogin()->format('Y-m-d H:i:s')); } + #[Test] + public function it_sets_last_login_from_an_immutable_date() + { + $user = tap(User::make()->email('test@example.com')->data(['name' => 'John Smith']))->save(); + $credential = $this->createTestCredential(); + + $passkey = $this->newPasskey() + ->setName('My Passkey') + ->setUser($user) + ->setCredential($credential) + ->setLastLogin(CarbonImmutable::create(2024, 1, 15, 10, 30, 0)); + + $this->assertInstanceOf(Carbon::class, $passkey->lastLogin()); + $this->assertEquals('2024-01-15 10:30:00', $passkey->lastLogin()->format('Y-m-d H:i:s')); + } + #[Test] public function it_handles_null_last_login() {