diff --git a/src/Entities/User.php b/src/Entities/User.php index 1ffd1f8b1..fe5b0538c 100644 --- a/src/Entities/User.php +++ b/src/Entities/User.php @@ -242,6 +242,17 @@ public function getPasswordHash(): ?string return $this->password_hash; } + /** + * Returns the previous login information for this user + */ + public function previousLogin(): ?Login + { + /** @var LoginModel $logins */ + $logins = model(LoginModel::class); + + return $logins->previousLogin($this); + } + /** * Returns the last login information for this user as */ diff --git a/src/Models/LoginModel.php b/src/Models/LoginModel.php index f36d91989..2d136763c 100644 --- a/src/Models/LoginModel.php +++ b/src/Models/LoginModel.php @@ -67,6 +67,19 @@ public function recordLoginAttempt( $this->checkQueryReturn($return); } + /** + * Returns the previous login information for the user, + * useful to display to the user the last time the account + * was accessed. + */ + public function previousLogin(User $user): ?Login + { + return $this->where('success', 1) + ->where('user_id', $user->id) + ->orderBy('id', 'desc') + ->limit(1, 1)->first(); + } + /** * Returns the last login information for the user */ diff --git a/tests/Unit/UserTest.php b/tests/Unit/UserTest.php index 3c19549b8..204b07acb 100644 --- a/tests/Unit/UserTest.php +++ b/tests/Unit/UserTest.php @@ -114,6 +114,45 @@ public function testLastLogin(): void $this->assertInstanceOf(Time::class, $last->date); } + public function testPreviousLogin(): void + { + fake( + UserIdentityModel::class, + ['user_id' => $this->user->id, 'type' => Session::ID_TYPE_EMAIL_PASSWORD, 'secret' => 'foo@example.com'] + ); + + // No logins found. + $this->assertNull($this->user->previousLogin()); + + $login1 = fake( + LoginModel::class, + ['id_type' => 'email', 'identifier' => $this->user->email, 'user_id' => $this->user->id] + ); + + // The very most login is skipped. + $this->assertNull($this->user->previousLogin()); + + fake( + LoginModel::class, + ['id_type' => 'email', 'identifier' => $this->user->email, 'user_id' => $this->user->id] + ); + fake( + LoginModel::class, + [ + 'id_type' => 'email', + 'identifier' => $this->user->email, + 'user_id' => $this->user->id, + 'success' => false, + ] + ); + + $previous = $this->user->previousLogin(); + + $this->assertInstanceOf(Login::class, $previous); // @phpstan-ignore-line + $this->assertSame($login1->id, $previous->id); + $this->assertInstanceOf(Time::class, $previous->date); + } + /** * @see https://github.com/codeigniter4/shield/issues/103 */