feat(access): add guarded local password lifecycle and recovery

This commit is contained in:
2026-09-08 07:47:18 +02:00
parent 8f8072b4ae
commit 7eb67b9fa7
31 changed files with 2377 additions and 33 deletions
+4 -3
View File
@@ -18,7 +18,7 @@ class LoginSecurityTests(unittest.TestCase):
@staticmethod
def _session_with_account(account: object | None) -> MagicMock:
session = MagicMock()
session.query.return_value.filter.return_value.one_or_none.return_value = (
session.query.return_value.filter.return_value.populate_existing.return_value.with_for_update.return_value.one_or_none.return_value = (
account
)
return session
@@ -43,7 +43,7 @@ class LoginSecurityTests(unittest.TestCase):
self,
) -> None:
account_hash = "pbkdf2_sha256$260000$account-salt$account-digest"
account = SimpleNamespace(password_hash=account_hash)
account = SimpleNamespace(password_hash=account_hash, auth_provider="local")
payload = LoginRequest(email="known@example.test", password="wrong-password")
with patch.object(auth, "verify_password", return_value=False) as verifier:
@@ -55,7 +55,7 @@ class LoginSecurityTests(unittest.TestCase):
self.assertEqual(raised.exception.detail, "Invalid login")
def test_passwordless_account_cannot_authenticate_with_dummy_password(self) -> None:
account = SimpleNamespace(password_hash=None)
account = SimpleNamespace(password_hash=None, auth_provider="local")
payload = LoginRequest(
email="passwordless@example.test", password="not-a-user-password"
)
@@ -69,6 +69,7 @@ class LoginSecurityTests(unittest.TestCase):
def test_account_without_active_membership_uses_same_generic_failure(self) -> None:
account = SimpleNamespace(
id="account-1",
auth_provider="local",
password_hash="pbkdf2_sha256$260000$account-salt$account-digest",
)
session = self._session_with_account(account)