connected_app.py 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
  1. """Connected apps: external applications that sign users in with Bambuddy.
  2. A connected app is registered by an admin with one exact callback URL. It
  3. signs a user in through a minimal OAuth 2.0 authorization-code flow with PKCE
  4. (see ``api/routes/connected_apps.py``): Bambuddy hands the app a single-use,
  5. 60-second code, and the app's server swaps it for the user's identity and
  6. permissions. The app never sees the user's Bambuddy login token.
  7. """
  8. from datetime import datetime
  9. from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, String, func
  10. from sqlalchemy.orm import Mapped, mapped_column
  11. from backend.app.core.database import Base
  12. class ConnectedApp(Base):
  13. __tablename__ = "connected_apps"
  14. id: Mapped[int] = mapped_column(primary_key=True)
  15. name: Mapped[str] = mapped_column(String(100))
  16. # Public identifier the app sends on every request.
  17. client_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
  18. # bcrypt hash; the secret itself is shown once, at creation or rotation.
  19. client_secret_hash: Mapped[str] = mapped_column(String(255))
  20. # Codes are only ever delivered here. Compared exactly, never by prefix.
  21. redirect_uri: Mapped[str] = mapped_column(String(500))
  22. enabled: Mapped[bool] = mapped_column(Boolean, default=True)
  23. created_by_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
  24. created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
  25. last_used_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
  26. class ConnectedAppGrant(Base):
  27. """A user's consent for one app, so the consent screen is shown only once.
  28. Deleting the app or the user removes the grant, and the next sign-in asks
  29. again.
  30. """
  31. __tablename__ = "connected_app_grants"
  32. __table_args__ = (Index("uq_connected_app_grants_app_user", "app_id", "user_id", unique=True),)
  33. id: Mapped[int] = mapped_column(Integer, primary_key=True)
  34. app_id: Mapped[int] = mapped_column(ForeignKey("connected_apps.id", ondelete="CASCADE"), index=True)
  35. user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
  36. granted_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())