"""Phase 14: API Key model for programmatic access.""" import hashlib import secrets import uuid from datetime import datetime from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Index, String, Text from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import relationship from app.database import Base # ── Key generation constants ────────────────────────────────────────────────── KEY_PREFIX = "aegis_" KEY_BYTES = 32 # 32 random bytes → 64 hex chars → 70-char key total DISPLAY_LEN = 12 # chars stored as prefix for UI display def generate_raw_key() -> str: """Generate a fresh raw API key (must be shown to user only once).""" return KEY_PREFIX + secrets.token_hex(KEY_BYTES) def hash_key(raw_key: str) -> str: """SHA-256 hash of a raw API key for secure storage.""" return hashlib.sha256(raw_key.encode()).hexdigest() def key_prefix_display(raw_key: str) -> str: """First DISPLAY_LEN characters of the raw key (safe for UI).""" return raw_key[:DISPLAY_LEN] # ── Valid scopes ────────────────────────────────────────────────────────────── VALID_SCOPES = {"read", "write", "admin"} class ApiKey(Base): """ Scoped API key for programmatic / BI / SOAR access. The full raw key is **never stored** — only a SHA-256 hash. The first 12 characters (``key_prefix``) are retained for display. """ __tablename__ = "api_keys" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name = Column(String(200), nullable=False) description = Column(Text, nullable=True) # Display only — never use for auth key_prefix = Column(String(DISPLAY_LEN + 1), nullable=False) # Auth token — SHA-256 of the full raw key key_hash = Column(String(64), nullable=False, unique=True) # Owner user_id = Column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ) # Permissions scopes = Column(JSONB, nullable=False, default=["read"]) # ["read","write","admin"] # Lifecycle last_used_at = Column(DateTime, nullable=True) expires_at = Column(DateTime, nullable=True) is_active = Column(Boolean, nullable=False, default=True) created_at = Column(DateTime, default=datetime.utcnow) user = relationship("User", foreign_keys=[user_id]) __table_args__ = ( Index("ix_api_keys_user_id", "user_id"), Index("ix_api_keys_key_hash", "key_hash"), Index("ix_api_keys_active", "is_active"), )