api_key.py 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  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_manage_library: Mapped[bool] = mapped_column(
  26. Boolean, default=True
  27. ) # Upload/rename/delete own library files + MakerWorld import
  28. can_manage_inventory: Mapped[bool] = mapped_column(
  29. Boolean, default=True
  30. ) # Inventory write ops (incl. SpoolBuddy kiosk NFC/scale/system)
  31. can_manage_maintenance: Mapped[bool] = mapped_column(
  32. Boolean, default=True
  33. ) # Log/reset per-printer maintenance, edit intervals, manage the type catalog (#1832 follow-up)
  34. can_manage_archives: Mapped[bool] = mapped_column(
  35. Boolean, default=True
  36. ) # Create/update/delete print archives (not purge) (#1888)
  37. can_manage_projects: Mapped[bool] = mapped_column(
  38. Boolean, default=True
  39. ) # Create/update/delete projects + manage membership (add archives) (#1893)
  40. can_access_cloud: Mapped[bool] = mapped_column(Boolean, default=False) # Read /cloud/* on the owner's behalf
  41. # Narrowly-scoped settings write: only POST /settings/electricity-price.
  42. # Lets HA/Tibber-style automations push dynamic tariff updates without
  43. # granting full SETTINGS_UPDATE (which is denied for API keys because it
  44. # could rewrite SMTP/LDAP/MQTT credentials).
  45. can_update_energy_cost: Mapped[bool] = mapped_column(Boolean, default=False)
  46. # Optional scope limits
  47. printer_ids: Mapped[list | None] = mapped_column(JSON, nullable=True) # null = all printers
  48. enabled: Mapped[bool] = mapped_column(Boolean, default=True)
  49. last_used: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
  50. created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
  51. expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) # Optional expiry