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
+4
View File
@@ -1,11 +1,15 @@
"""Git provider abstraction, credentials, SSH keys, and operations.""" """Git provider abstraction, credentials, SSH keys, and operations."""
from app.git.credentials import AccessTokenCredential, CredentialStorage, GitCredential
from app.git.provider import GitProvider from app.git.provider import GitProvider
from app.git.types import ConnectionStatus, CredentialKind, ProviderKind, SshKeyStatus from app.git.types import ConnectionStatus, CredentialKind, ProviderKind, SshKeyStatus
__all__ = [ __all__ = [
"AccessTokenCredential",
"ConnectionStatus", "ConnectionStatus",
"CredentialKind", "CredentialKind",
"CredentialStorage",
"GitCredential",
"GitProvider", "GitProvider",
"ProviderKind", "ProviderKind",
"SshKeyStatus", "SshKeyStatus",
+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."""