import datetime as dt
from typing import TypedDict
from fastapi import status
from sqlalchemy import Boolean, Column, Integer, LargeBinary, String
from sqlalchemy.orm import Mapped, relationship
from hyd.backend.db import EXTEND_EXISTING, DeclarativeMeta
from hyd.backend.exc import HTTPException_NO_PERMISSION
from hyd.backend.token.models import TokenEntry, TokenSchema
from hyd.backend.util.const import MAX_LENGTH_STR_ID
from hyd.backend.util.models import (
BASE_API_RESPONSE_SCHEMA,
DETAIL_STR,
NameStr,
PrimaryKey,
TimeStampMixin,
)
####################################################################################################
#### SQLAlchemy table definitions
####################################################################################################
[docs]class UserEntry(DeclarativeMeta, TimeStampMixin):
__tablename__ = "user_table"
__table_args__ = {"extend_existing": EXTEND_EXISTING}
id: Mapped[PrimaryKey] = Column(Integer, primary_key=True)
username: Mapped[NameStr] = Column(String(length=MAX_LENGTH_STR_ID), unique=True)
hashed_password: Mapped[bytes] = Column(LargeBinary)
is_admin: Mapped[bool] = Column(Boolean)
is_disabled: Mapped[bool] = Column(Boolean, default=False)
token_entries: Mapped[list[TokenEntry]] = relationship(
"TokenEntry", back_populates="user_entry"
)
login_token_entries: Mapped[list[TokenEntry]] = relationship(
"TokenEntry",
primaryjoin=("and_(TokenEntry.user_id==UserEntry.id, TokenEntry.is_login_token==True)"),
viewonly=True,
)
_session_token_entry: TokenEntry | None = None
_session_permitted_scopes: list[str] | None = None
@property
def session_token_entry(self) -> TokenEntry:
return self._session_token_entry
[docs] def check_scope_permission(self, *, scope: str) -> bool: # TODO raise if None
"""Check if the given scope is permitted for the given session."""
return scope in self._session_permitted_scopes
def check_token_project_permission(self, *, project_id: PrimaryKey) -> None:
token_project_id = self._session_token_entry.project_id
if token_project_id is None:
return
if token_project_id != project_id:
raise HTTPException_NO_PERMISSION
####################################################################################################
#### Response schema
####################################################################################################
# NOTE currently unused
[docs]class UserResponseSchema(TypedDict):
id: PrimaryKey
username: NameStr
is_admin: bool
is_disabled: bool
created_at: dt.datetime
####################################################################################################
#### OpenAPI definitions
####################################################################################################
API_V1_LOGIN__POST = {
**BASE_API_RESPONSE_SCHEMA,
status.HTTP_200_OK: {"model": TokenSchema},
status.HTTP_400_BAD_REQUEST: DETAIL_STR,
}
API_V1_LOGOUT__PATCH = {
**BASE_API_RESPONSE_SCHEMA,
status.HTTP_200_OK: {"model": str},
}
API_V1_CHANGE_PASSWORD__PATCH = {
**BASE_API_RESPONSE_SCHEMA,
status.HTTP_200_OK: {"model": str},
status.HTTP_400_BAD_REQUEST: DETAIL_STR,
}
API_V1_GREET__GET = {
**BASE_API_RESPONSE_SCHEMA,
status.HTTP_200_OK: {"model": str},
}