diff --git a/pdm.lock b/pdm.lock index f41ebbf..46253a2 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "dev", "mqtt"] strategy = ["cross_platform"] lock_version = "4.4.1" -content_hash = "sha256:0d9a5dffa5c1766fc8e78cd0c4faffa2d4640e5bb98e7456413ce64d346e0b86" +content_hash = "sha256:5ff6f2ee76615362a8ae23881594246320dde93e417b6abc972506f1bf27816b" [[package]] name = "aiomqtt" @@ -1207,6 +1207,16 @@ files = [ {file = "traitlets-5.14.0.tar.gz", hash = "sha256:fcdaa8ac49c04dfa0ed3ee3384ef6dfdb5d6f3741502be247279407679296772"}, ] +[[package]] +name = "types-passlib" +version = "1.7.7.20240311" +requires_python = ">=3.8" +summary = "Typing stubs for passlib" +files = [ + {file = "types-passlib-1.7.7.20240311.tar.gz", hash = "sha256:287dd27cec5421daf6be5c295f681baf343c146038c8bde4db783bcac1beccb7"}, + {file = "types_passlib-1.7.7.20240311-py3-none-any.whl", hash = "sha256:cd44166e9347ae516f4830046cd1673c1ef90a5cc7ddd1356cf8a14892f29249"}, +] + [[package]] name = "types-psycopg2" version = "2.9.21.20" @@ -1217,6 +1227,29 @@ files = [ {file = "types_psycopg2-2.9.21.20-py3-none-any.whl", hash = "sha256:5b1e2e1d9478f8a298ea7038f8ea988e0ccc1f0af39f84636d57ef0da6f29e95"}, ] +[[package]] +name = "types-pyasn1" +version = "0.5.0.20240301" +requires_python = ">=3.8" +summary = "Typing stubs for pyasn1" +files = [ + {file = "types-pyasn1-0.5.0.20240301.tar.gz", hash = "sha256:da328f5771d54a2016863270b281047f9cc38e39f65a297ba9f987d5de3403f1"}, + {file = "types_pyasn1-0.5.0.20240301-py3-none-any.whl", hash = "sha256:d9989899184bbd6e2adf6f812c8f49c48197fceea251a6fb13666dae3203f80d"}, +] + +[[package]] +name = "types-python-jose" +version = "3.3.4.20240106" +requires_python = ">=3.8" +summary = "Typing stubs for python-jose" +dependencies = [ + "types-pyasn1", +] +files = [ + {file = "types-python-jose-3.3.4.20240106.tar.gz", hash = "sha256:b18cf8c5080bbfe1ef7c3b707986435d9efca3e90889acb6a06f65e06bc3405a"}, + {file = "types_python_jose-3.3.4.20240106-py3-none-any.whl", hash = "sha256:b515a6c0c61f5e2a53bc93e3a2b024cbd42563e2e19cbde9fd1c2cc2cfe77ccc"}, +] + [[package]] name = "types-pytz" version = "2023.3.1.1" diff --git a/pyproject.toml b/pyproject.toml index b1f5288..0f94b2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,4 +54,6 @@ dev = [ "types-psycopg2>=2.9.21.20", "types-PyYAML>=6.0.12.12", "asyncpg-stubs>=0.29.1", + "types-python-jose>=3.3.4.20240106", + "types-passlib>=1.7.7.20240311", ] diff --git a/src/gisaf/models/authentication.py b/src/gisaf/models/authentication.py index 32a4324..13c3be6 100644 --- a/src/gisaf/models/authentication.py +++ b/src/gisaf/models/authentication.py @@ -1,4 +1,6 @@ -from sqlmodel import Field, SQLModel, Relationship +from datetime import datetime + +from sqlmodel import Field, SQLModel, Relationship, String from pydantic import BaseModel from gisaf.models.metadata import gisaf_admin @@ -27,10 +29,20 @@ class UserBase(SQLModel): class User(UserBase, table=True): __table_args__ = gisaf_admin.table_args - id: str | None = Field(default=None, primary_key=True) + + id: int = Field(primary_key=True) + username: str = Field(String(255), unique=True, index=True) + email: str = Field(sa_type=String(50), unique=True) + password: str = Field(sa_type=String(255)) + active: bool + confirmed_at: datetime + last_login_at: datetime + current_login_at: datetime + last_login_ip: str = Field(sa_type=String(255)) + current_login_ip: str = Field(sa_type=String(255)) + login_count: int roles: list["Role"] = Relationship(back_populates="users", link_model=UserRoleLink) - password: str | None = None def can_view(self, model) -> bool: role = getattr(model, 'viewable_role', None) diff --git a/src/gisaf/security.py b/src/gisaf/security.py index c0c698b..0744375 100644 --- a/src/gisaf/security.py +++ b/src/gisaf/security.py @@ -10,7 +10,7 @@ from pydantic import BaseModel from sqlmodel.ext.asyncio.session import AsyncSession from jose import JWTError, jwt, ExpiredSignatureError -from sqlalchemy import select +from sqlmodel import select from sqlalchemy.orm import selectinload from gisaf.config import conf @@ -72,7 +72,8 @@ async def enable_user(session: AsyncSession, username: str, enable=True): await session.commit() -async def create_user(session: AsyncSession, username: str, password: str, full_name: str, +async def create_user(session: AsyncSession, username: str, + password: str, full_name: str, email: str, **kwargs): user_in_db: User | None = await get_user(session, username) if user_in_db is None: @@ -93,10 +94,10 @@ async def create_user(session: AsyncSession, username: str, password: str, full_ async def get_user( session: AsyncSession, - username: str) -> (User | None): + username: str) -> User | None: query = select(User).where(User.username==username).options(selectinload(User.roles)) data = await session.exec(query) - return data.scalar() + return data.one_or_none() def verify_password(user: User, plain_password):