import re from datetime import UTC, datetime from uuid import UUID, uuid7 from sqlalchemy import DateTime from sqlmodel import Field, Relationship, SQLModel from starlette.authentication import BaseUser from authentication.mixins import ( OAuth2AuthorizationCodeMixin, OAuth2ClientMixin, OAuth2TokenMixin, ) from authentication.utils import bcrypt_sha256_hash class BaseModel(SQLModel): id: UUID | None = Field(primary_key=True, default_factory=uuid7) created: datetime = Field( default_factory=lambda: datetime.now(tz=UTC), sa_type=DateTime(timezone=True), ) updated: datetime = Field( default=None, nullable=True, sa_column_kwargs={ "onupdate": lambda: datetime.now(tz=UTC), }, sa_type=DateTime(timezone=True), ) class User(BaseModel, BaseUser, table=True): username: str = Field(unique=True, index=True) password: str = Field(nullable=False) email: str def set_password(self, value): self.password = bcrypt_sha256_hash(value) def is_authenticated(self) -> bool: return True class Session(BaseModel, table=True): user_id: UUID = Field(foreign_key="user.id", exclude=True) user: User = Relationship() class ClientApplication(BaseModel, OAuth2ClientMixin, table=True): name: str def check_redirect_uri(self, redirect_uri: str) -> bool: return any( re.fullmatch(re.escape(str(pattern)).replace(r"\*", r"\d+"), redirect_uri) for pattern in self.redirect_uris ) class AuthCode(BaseModel, OAuth2AuthorizationCodeMixin, table=True): user_id: UUID = Field(foreign_key="user.id", ondelete="CASCADE") user: User = Relationship() class AuthToken(BaseModel, OAuth2TokenMixin, table=True): user_id: UUID = Field(foreign_key="user.id", ondelete="CASCADE") user: User = Relationship()