bifroest/app/identity/models.py
2026-10-27 08:34:17 +01:00

63 lines
2.8 KiB
Python

"""Nutzer, Teams und Web-Sitzungen (→ Konzept 5)."""
from datetime import datetime
from sqlalchemy import Boolean, ForeignKey, Integer, String, false, true
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base, now
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str] = mapped_column(String(64), unique=True)
display_name: Mapped[str] = mapped_column(String(200))
# Globale Administration (Modelle, Connectoren, Policies)
is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
# Darf Anträge mit Freigabestufe global_approver entscheiden, auch ohne Mitglied des Workspace zu sein
is_global_approver: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false())
# Für Benachrichtigungen; notify = Nutzer möchte E-Mails erhalten
email: Mapped[str | None] = mapped_column(String(254))
notify: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true())
# Kennung aus dem späteren Identity Provider (OIDC/Keycloak); leer bei Demo-Nutzern
external_id: Mapped[str | None] = mapped_column(String(200), unique=True)
active: Mapped[bool] = mapped_column(Boolean, default=True)
# Eigenes Passwort (Argon2id); leer = nur Demo-Anmeldung bzw. später Keycloak
password_hash: Mapped[str | None] = mapped_column(String(200))
# Nach Anlegen oder Zurücksetzen: erst ein eigenes Passwort setzen
must_change_password: Mapped[bool] = mapped_column(Boolean, default=False)
failed_logins: Mapped[int] = mapped_column(Integer, default=0)
locked_until: Mapped[datetime | None]
password_changed: Mapped[datetime | None]
created: Mapped[datetime] = mapped_column(default=now)
class Team(Base):
__tablename__ = "teams"
id: Mapped[int] = mapped_column(primary_key=True)
slug: Mapped[str] = mapped_column(String(64), unique=True)
name: Mapped[str] = mapped_column(String(200))
created: Mapped[datetime] = mapped_column(default=now)
class TeamMembership(Base):
__tablename__ = "team_memberships"
team_id: Mapped[int] = mapped_column(ForeignKey("teams.id", ondelete="CASCADE"), primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), primary_key=True)
class WebSession(Base):
"""Serverseitige Sitzung; im Cookie steht nur das Token, hier sein SHA-256."""
__tablename__ = "web_sessions"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
csrf: Mapped[str] = mapped_column(String(64))
created: Mapped[datetime] = mapped_column(default=now)
expires_at: Mapped[datetime]
revoked_at: Mapped[datetime | None]