feat(auth): implement API key authentication and management
Introduce a new API key authentication mechanism to support service-to-service communication (e.g., Airflow, CI/CD) without browser-based JWT login. - Add `APIKey` model and `APIKeyPrincipal` dependency for RBAC. - Implement `require_api_key_or_jwt` dependency with environment scoping. - Add admin CRUD endpoints for API key lifecycle management. - Add API key management UI in System Settings. - Update maintenance API to support explicit `environment_id` and API key auth. - Add i18n support for API keys and connection settings. - Include Python and Bash usage examples in the `examples/` directory. - Update technical specifications and documentation.
This commit is contained in:
36
backend/src/models/api_key.py
Normal file
36
backend/src/models/api_key.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# #region APIKeyModel [C:1] [TYPE Module] [SEMANTICS sqlalchemy, api_key, auth, model]
|
||||
# @BRIEF SQLAlchemy model for API Key authentication — stores SHA-256 hash only, raw key shown once at creation.
|
||||
# @LAYER Domain
|
||||
# @RELATION DEPENDS_ON -> [MappingModels]
|
||||
# @INVARIANT key_hash is stored as SHA-256 hex digest (64 chars). prefix stores "ssk_" + first 7 chars.
|
||||
# @INVARIANT Raw key is NEVER stored — shown once at creation and discarded.
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, JSON, String
|
||||
|
||||
from .mapping import Base
|
||||
|
||||
|
||||
# #region APIKey [C:1] [TYPE Class]
|
||||
# @BRIEF Stores API key metadata and SHA-256 hash for service-to-service authentication.
|
||||
class APIKey(Base):
|
||||
__tablename__ = "api_keys"
|
||||
|
||||
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
key_hash = Column(String(64), unique=True, nullable=False, index=True) # SHA-256 hex digest
|
||||
prefix = Column(String(11), nullable=False) # "ssk_" + 7 chars
|
||||
name = Column(String(255), nullable=False) # Human-readable label
|
||||
environment_id = Column(String(50), nullable=True) # Scoped to environment; null = all
|
||||
permissions = Column(JSON, nullable=False) # e.g. ["maintenance:start", "maintenance:end"]
|
||||
active = Column(Boolean, default=True, nullable=False)
|
||||
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False)
|
||||
expires_at = Column(DateTime, nullable=True)
|
||||
last_used_at = Column(DateTime, nullable=True)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<APIKey {self.prefix}... ({self.name})>"
|
||||
|
||||
# #endregion APIKey
|
||||
# #endregion APIKeyModel
|
||||
Reference in New Issue
Block a user