Files
CIAgent/apps/api/app/models/email_code.py
T
saksham 1a4c80958f Initial commit: CI Agent competitive-intelligence monitoring app
FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring,
source collection, LLM-based change analysis, enrichment, and account
security (Turnstile, escalating lockout, email verification).
2026-08-05 10:48:20 -04:00

38 lines
1.3 KiB
Python

"""Email verification / password-reset codes.
Only a SHA-256 hash of the 6-digit code is stored, never the raw value -
same "never store the raw secret" precedent as RefreshToken.token_hash. A
short numeric code doesn't need Argon2's cost; it needs short expiry plus
the IP throttle system (app/services/ip_throttle_service.py) guarding how
often it can be guessed or resent.
"""
from __future__ import annotations
import uuid
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, Enum, ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
from app.models.enums import EmailCodePurpose
if TYPE_CHECKING:
from app.models.user import User
class EmailCode(Base, UUIDPrimaryKeyMixin, TimestampMixin):
__tablename__ = "email_codes"
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
purpose: Mapped[EmailCodePurpose] = mapped_column(
Enum(EmailCodePurpose, native_enum=False, length=20)
)
code_hash: Mapped[str] = mapped_column(String(64), index=True)
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
user: Mapped[User] = relationship()