import re from datetime import datetime, UTC from urllib.parse import urlsplit from uuid import uuid4, UUID, uuid7 from sqlalchemy import DateTime from sqlmodel import SQLModel, Field, Relationship from starlette.authentication import BaseUser from authentication.mixins import OAuth2ClientMixin, OAuth2AuthorizationCodeMixin, 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()