# Pourquoi : même schéma que `refresh_token` (chaîne opaque, jamais un JWT) pour la même # raison : un jeton de réinitialisation doit être révocable d'un coup, et un JWT ne figure # dans aucune ligne à invalider. import uuid from datetime import datetime from sqlalchemy import DateTime, ForeignKey, Index, LargeBinary, Text, func from sqlalchemy.dialects.postgresql import INET from sqlalchemy.dialects.postgresql import UUID as PG_UUID from sqlalchemy.orm import Mapped, mapped_column from app.db.base import Base class PasswordResetToken(Base): __tablename__ = "password_reset_token" __table_args__ = ( Index("ix_password_reset_token_user", "user_id"), Index( "ix_password_reset_token_vivants", "user_id", postgresql_where="consumed_at is null", ), ) id: Mapped[uuid.UUID] = mapped_column( PG_UUID(as_uuid=True), primary_key=True, server_default=func.gen_random_uuid() ) user_id: Mapped[uuid.UUID] = mapped_column( PG_UUID(as_uuid=True), ForeignKey("app_user.id", ondelete="CASCADE"), nullable=False ) token_hash: Mapped[bytes] = mapped_column(LargeBinary, nullable=False, unique=True) issued_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) consumed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) client_ip: Mapped[str | None] = mapped_column(INET, nullable=True) user_agent: Mapped[str | None] = mapped_column(Text, nullable=True)