Spaces:
Running
Running
| """Author RAG β Email Verification Token Model. | |
| Used for: password reset requests. | |
| Token is HMAC-signed (same pattern as subscription tokens), | |
| single-use (used_at is set on consumption), and expires after 1 hour. | |
| NOT used for email verification at signup β accounts are created | |
| only after successful Stripe payment, so the email is already | |
| confirmed via the Stripe Checkout flow. | |
| """ | |
| from datetime import datetime | |
| from sqlalchemy import Boolean, DateTime, String | |
| from sqlalchemy import ForeignKey | |
| from sqlalchemy.orm import Mapped, mapped_column | |
| from app.models.base import Base, generate_uuid | |
| class EmailVerification(Base): | |
| """Single-use HMAC-signed token for password reset.""" | |
| __tablename__ = "email_verifications" | |
| id: Mapped[str] = mapped_column(String(36), primary_key=True, default=generate_uuid) | |
| user_id: Mapped[str] = mapped_column( | |
| String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True | |
| ) | |
| # HMAC-signed token β never store the raw token, only the hash | |
| token_hash: Mapped[str] = mapped_column(String(255), nullable=False, unique=True) | |
| purpose: Mapped[str] = mapped_column( | |
| String(30), nullable=False, default="password_reset" | |
| ) # "password_reset" | "email_change" | |
| # Lifecycle | |
| created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) | |
| expires_at: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), nullable=False, index=True | |
| ) # Typically now + 1 hour | |
| used_at: Mapped[datetime | None] = mapped_column( | |
| DateTime(timezone=True), nullable=True | |
| ) # Set on consumption β single-use enforcement | |
| # IP for audit trail | |
| requested_from_ip: Mapped[str | None] = mapped_column(String(50), nullable=True) | |
| def is_expired(self) -> bool: | |
| from datetime import timezone | |
| return datetime.now(timezone.utc) > self.expires_at | |
| def is_used(self) -> bool: | |
| return self.used_at is not None | |
| def is_valid(self) -> bool: | |
| return not self.is_expired and not self.is_used | |
| def __repr__(self) -> str: | |
| return f"<EmailVerification user={self.user_id} purpose={self.purpose} valid={self.is_valid}>" | |