src/pqc_agent_wallet/credential.py
| 1 | """Credential data structures.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from dataclasses import asdict, dataclass, field |
| 6 | from typing import Any |
| 7 | |
| 8 | |
| 9 | @dataclass |
| 10 | class CredentialMetadata: |
| 11 | """Non-secret metadata about a credential.""" |
| 12 | |
| 13 | name: str |
| 14 | scheme: str = "api-key" # api-key | oauth | password | cert | token |
| 15 | service: str = "" # e.g. "openai", "anthropic", "postgres" |
| 16 | description: str = "" |
| 17 | created_at: str = "" |
| 18 | rotated_at: str = "" |
| 19 | expires_at: str = "" |
| 20 | tags: list[str] = field(default_factory=list) |
| 21 | |
| 22 | def to_dict(self) -> dict[str, Any]: |
| 23 | return asdict(self) |
| 24 | |
| 25 | @classmethod |
| 26 | def from_dict(cls, data: dict[str, Any]) -> CredentialMetadata: |
| 27 | return cls( |
| 28 | name=data["name"], |
| 29 | scheme=data.get("scheme", "api-key"), |
| 30 | service=data.get("service", ""), |
| 31 | description=data.get("description", ""), |
| 32 | created_at=data.get("created_at", ""), |
| 33 | rotated_at=data.get("rotated_at", ""), |
| 34 | expires_at=data.get("expires_at", ""), |
| 35 | tags=list(data.get("tags", [])), |
| 36 | ) |
| 37 | |
| 38 | |
| 39 | @dataclass |
| 40 | class Credential: |
| 41 | """A stored credential. The `value` is plaintext only when wallet is unlocked.""" |
| 42 | |
| 43 | metadata: CredentialMetadata |
| 44 | value: str = "" # secret in plaintext (only when unlocked) |
| 45 | |
| 46 | def to_safe_dict(self) -> dict[str, Any]: |
| 47 | """Serialize without the secret (for logging or public display).""" |
| 48 | return { |
| 49 | "metadata": self.metadata.to_dict(), |
| 50 | "value": "<redacted>", |
| 51 | } |
| 52 | |