api_key.py 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  1. from datetime import datetime
  2. from sqlalchemy import JSON, Boolean, DateTime, ForeignKey, Integer, String, func
  3. from sqlalchemy.orm import Mapped, mapped_column
  4. from backend.app.core.database import Base
  5. class APIKey(Base):
  6. """API key for external webhook access."""
  7. __tablename__ = "api_keys"
  8. id: Mapped[int] = mapped_column(primary_key=True)
  9. name: Mapped[str] = mapped_column(String(100)) # User-friendly name
  10. key_hash: Mapped[str] = mapped_column(String(255)) # bcrypt hash of the key
  11. key_prefix: Mapped[str] = mapped_column(String(20)) # First 8 chars + "..." for display
  12. # Owner — required for new keys, NULL only on legacy rows that predate per-user
  13. # ownership. Cloud routes reject calls from keys without an owner so callers are
  14. # forced to recreate them. CASCADE so deleting a user removes their keys.
  15. user_id: Mapped[int | None] = mapped_column(
  16. Integer,
  17. ForeignKey("users.id", ondelete="CASCADE"),
  18. nullable=True,
  19. index=True,
  20. )
  21. # Permissions
  22. can_queue: Mapped[bool] = mapped_column(Boolean, default=True) # Add to queue
  23. can_control_printer: Mapped[bool] = mapped_column(Boolean, default=False) # Start/stop/cancel
  24. can_read_status: Mapped[bool] = mapped_column(Boolean, default=True) # Query status
  25. can_access_cloud: Mapped[bool] = mapped_column(Boolean, default=False) # Read /cloud/* on the owner's behalf
  26. # Narrowly-scoped settings write: only POST /settings/electricity-price.
  27. # Lets HA/Tibber-style automations push dynamic tariff updates without
  28. # granting full SETTINGS_UPDATE (which is denied for API keys because it
  29. # could rewrite SMTP/LDAP/MQTT credentials).
  30. can_update_energy_cost: Mapped[bool] = mapped_column(Boolean, default=False)
  31. # Optional scope limits
  32. printer_ids: Mapped[list | None] = mapped_column(JSON, nullable=True) # null = all printers
  33. enabled: Mapped[bool] = mapped_column(Boolean, default=True)
  34. last_used: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
  35. created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
  36. expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) # Optional expiry