feat: Faillock-Warnung bei wiederholten Fehlversuchen
Zeigt nach dem 2. fehlgeschlagenen Login-Versuch einen Hinweis an, dass das Konto nach dem nächsten Fehlversuch gesperrt werden kann (faillock default: 3 Versuche).
This commit is contained in:
parent
c4b3dc833b
commit
6554dc625d
@ -21,11 +21,22 @@ from moongreet.sessions import Session, get_sessions
|
|||||||
from moongreet.power import reboot, shutdown
|
from moongreet.power import reboot, shutdown
|
||||||
|
|
||||||
LAST_USER_PATH = Path("/var/cache/moongreet/last-user")
|
LAST_USER_PATH = Path("/var/cache/moongreet/last-user")
|
||||||
|
FAILLOCK_MAX_ATTEMPTS = 3
|
||||||
PACKAGE_DATA = files("moongreet") / "data"
|
PACKAGE_DATA = files("moongreet") / "data"
|
||||||
DEFAULT_AVATAR_PATH = PACKAGE_DATA / "default-avatar.svg"
|
DEFAULT_AVATAR_PATH = PACKAGE_DATA / "default-avatar.svg"
|
||||||
AVATAR_SIZE = 128
|
AVATAR_SIZE = 128
|
||||||
|
|
||||||
|
|
||||||
|
def faillock_warning(attempt_count: int) -> str | None:
|
||||||
|
"""Return a warning if the user is approaching or has reached the faillock limit."""
|
||||||
|
remaining = FAILLOCK_MAX_ATTEMPTS - attempt_count
|
||||||
|
if remaining <= 0:
|
||||||
|
return "Konto ist möglicherweise gesperrt"
|
||||||
|
if remaining == 1:
|
||||||
|
return f"Noch {remaining} Versuch vor Kontosperrung!"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class GreeterWindow(Gtk.ApplicationWindow):
|
class GreeterWindow(Gtk.ApplicationWindow):
|
||||||
"""The main greeter window with login UI."""
|
"""The main greeter window with login UI."""
|
||||||
|
|
||||||
@ -41,6 +52,7 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
self._greetd_sock: socket.socket | None = None
|
self._greetd_sock: socket.socket | None = None
|
||||||
self._default_avatar_pixbuf: GdkPixbuf.Pixbuf | None = None
|
self._default_avatar_pixbuf: GdkPixbuf.Pixbuf | None = None
|
||||||
self._avatar_cache: dict[str, GdkPixbuf.Pixbuf] = {}
|
self._avatar_cache: dict[str, GdkPixbuf.Pixbuf] = {}
|
||||||
|
self._failed_attempts: dict[str, int] = {}
|
||||||
|
|
||||||
self._build_ui()
|
self._build_ui()
|
||||||
self._select_initial_user()
|
self._select_initial_user()
|
||||||
@ -388,7 +400,12 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
response = post_auth_response(sock, password)
|
response = post_auth_response(sock, password)
|
||||||
|
|
||||||
if response.get("type") == "error":
|
if response.get("type") == "error":
|
||||||
|
self._failed_attempts[user.username] = self._failed_attempts.get(user.username, 0) + 1
|
||||||
self._show_greetd_error(response, "Falsches Passwort")
|
self._show_greetd_error(response, "Falsches Passwort")
|
||||||
|
warning = faillock_warning(self._failed_attempts[user.username])
|
||||||
|
if warning:
|
||||||
|
current = self._error_label.get_text()
|
||||||
|
self._error_label.set_text(f"{current}\n{warning}")
|
||||||
self._close_greetd_sock()
|
self._close_greetd_sock()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from moongreet.greeter import faillock_warning, FAILLOCK_MAX_ATTEMPTS
|
||||||
from moongreet.ipc import create_session, post_auth_response, start_session, cancel_session
|
from moongreet.ipc import create_session, post_auth_response, start_session, cancel_session
|
||||||
|
|
||||||
|
|
||||||
@ -177,6 +178,31 @@ class TestLoginFlow:
|
|||||||
assert mock.received[1] == {"type": "cancel_session"}
|
assert mock.received[1] == {"type": "cancel_session"}
|
||||||
|
|
||||||
|
|
||||||
|
class TestFaillockWarning:
|
||||||
|
"""Tests for the faillock warning message logic."""
|
||||||
|
|
||||||
|
def test_no_warning_on_first_attempt(self) -> None:
|
||||||
|
assert faillock_warning(1) is None
|
||||||
|
|
||||||
|
def test_warning_on_second_attempt(self) -> None:
|
||||||
|
warning = faillock_warning(2)
|
||||||
|
assert warning is not None
|
||||||
|
assert "1" in warning # 1 Versuch übrig
|
||||||
|
|
||||||
|
def test_warning_on_third_attempt(self) -> None:
|
||||||
|
warning = faillock_warning(3)
|
||||||
|
assert warning is not None
|
||||||
|
assert "gesperrt" in warning.lower()
|
||||||
|
|
||||||
|
def test_warning_beyond_max_attempts(self) -> None:
|
||||||
|
warning = faillock_warning(4)
|
||||||
|
assert warning is not None
|
||||||
|
assert "gesperrt" in warning.lower()
|
||||||
|
|
||||||
|
def test_max_attempts_constant_is_three(self) -> None:
|
||||||
|
assert FAILLOCK_MAX_ATTEMPTS == 3
|
||||||
|
|
||||||
|
|
||||||
class TestLastUser:
|
class TestLastUser:
|
||||||
"""Tests for saving and loading the last logged-in user."""
|
"""Tests for saving and loading the last logged-in user."""
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user