feat(FN-011): complete Step 2 — credential model and storage interface

Fusion-Task-Id: FN-011
Fusion-Task-Lineage: 4a9aca6f-9d91-43aa-8d2a-d59657c1541a
This commit is contained in:
Fusion
2026-05-14 07:29:19 +02:00
parent 59d727072e
commit 2037359e8d
2 changed files with 60 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
"""Credential models and storage interface.
Security rules:
- No plaintext ``private_key`` or ``token`` fields exist on any model class.
- The ``encrypted_payload`` field is opaque bytes encoded as a string.
"""
import abc
import uuid
from datetime import datetime, timezone
from pydantic import BaseModel, ConfigDict, Field
from app.git.types import CredentialKind
class GitCredential(BaseModel):
"""Base credential model.
Never stores plaintext secrets. The ``encrypted_payload`` field holds
opaque encrypted data.
"""
model_config = ConfigDict(extra="forbid")
id: uuid.UUID = Field(default_factory=uuid.uuid4)
kind: CredentialKind
encrypted_payload: str = Field(repr=False)
created_at: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc)
)
updated_at: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc)
)
class AccessTokenCredential(GitCredential):
"""Access-token credential discriminated by ``kind``."""
kind: CredentialKind = CredentialKind.access_token
class CredentialStorage(abc.ABC):
"""Abstract storage backend for :class:`GitCredential` records."""
@abc.abstractmethod
def create(self, credential: GitCredential) -> uuid.UUID:
"""Persist *credential* and return its ID."""
@abc.abstractmethod
def get(self, credential_id: uuid.UUID) -> GitCredential | None:
"""Retrieve a credential by ID, or ``None`` if not found."""
@abc.abstractmethod
def delete(self, credential_id: uuid.UUID) -> None:
"""Remove a credential by ID."""