59 lines
2.3 KiB
Python
59 lines
2.3 KiB
Python
"""Ports — the contracts the application layer depends on.
|
|
|
|
These are Protocols, not implementations. Concrete adapters live in
|
|
``app.infrastructure`` (repositories) and ``app.core.security`` (crypto) and
|
|
are bound to these ports at the composition root (``app.api.deps``).
|
|
"""
|
|
|
|
import datetime as dt
|
|
import uuid
|
|
from typing import Protocol
|
|
|
|
from app.domain.entities import Credentials, User
|
|
from app.domain.tokens import IssuedToken, TokenClaims, TokenType
|
|
|
|
|
|
class UserRepository(Protocol):
|
|
async def get_by_id(self, user_id: uuid.UUID) -> User | None: ...
|
|
async def get_credentials_by_username(self, username: str) -> Credentials | None: ...
|
|
async def add(self, *, username: str, password_hash: str, is_superuser: bool) -> User: ...
|
|
async def list(self, *, limit: int, offset: int) -> list[User]: ...
|
|
async def set_password_hash(self, user_id: uuid.UUID, password_hash: str) -> None: ...
|
|
async def set_superuser(self, user_id: uuid.UUID, is_superuser: bool) -> User: ...
|
|
async def set_active(self, user_id: uuid.UUID, is_active: bool) -> User: ...
|
|
async def count(self) -> int: ...
|
|
|
|
|
|
class RefreshTokenRepository(Protocol):
|
|
async def add(
|
|
self,
|
|
*,
|
|
jti: uuid.UUID,
|
|
user_id: uuid.UUID,
|
|
token_hash: str,
|
|
expires_at: dt.datetime,
|
|
) -> None: ...
|
|
async def is_valid(self, jti: uuid.UUID) -> bool:
|
|
"""True iff a row exists for ``jti`` that is neither revoked nor expired."""
|
|
...
|
|
|
|
async def revoke(self, jti: uuid.UUID) -> None: ...
|
|
async def revoke_all_for_user(self, user_id: uuid.UUID) -> None: ...
|
|
|
|
|
|
class PasswordHasher(Protocol):
|
|
def hash(self, password: str) -> str: ...
|
|
def verify_and_update(self, password: str, password_hash: str) -> tuple[bool, str | None]:
|
|
"""Verify ``password`` against ``password_hash``. Returns
|
|
``(is_valid, updated_hash)`` where ``updated_hash`` is a fresh hash to
|
|
persist when the stored one uses outdated parameters, else ``None``."""
|
|
...
|
|
|
|
|
|
class TokenService(Protocol):
|
|
def issue(self, *, subject: uuid.UUID, token_type: TokenType) -> IssuedToken: ...
|
|
def decode(self, encoded: str) -> TokenClaims:
|
|
"""Verify signature + expiry and return claims. Raises
|
|
:class:`~app.domain.errors.AuthenticationError` on any failure."""
|
|
...
|