from __future__ import annotations from datetime import datetime from sqlalchemy import DateTime, ForeignKey, Index, String, func from sqlalchemy.orm import Mapped, mapped_column from app.core.database import Base from app.utils.ids import prefixed_id class PasswordResetToken(Base): """One-time password recovery token. Only the SHA-256 digest is persisted. The raw token exists only in the recovery link sent to the student and can therefore never be recovered from a database dump or application log. """ __tablename__ = "password_reset_tokens" __table_args__ = ( Index("ix_password_reset_tokens_user_created", "user_id", "created_at"), Index("ix_password_reset_tokens_hash", "token_hash", unique=True), ) id: Mapped[str] = mapped_column( String(40), primary_key=True, default=lambda: prefixed_id("pwreset"), ) user_id: Mapped[str] = mapped_column( String(40), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, ) token_hash: Mapped[str] = mapped_column(String(64), nullable=False) expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False, )