Add users

This commit is contained in:
Campbell Alden 2026-08-01 01:09:51 +09:00
parent 6700bc9763
commit 12797e3cae
7 changed files with 194 additions and 2 deletions

0
src/infra/__init__.py Normal file
View file

84
src/infra/users.py Normal file
View file

@ -0,0 +1,84 @@
import logging
from argon2.exceptions import VerifyMismatchError
from src.utils.secret import SecretBox
from src.config.email import EmailAddress
from sqlalchemy import String, Boolean, VARCHAR, Engine, select
from sqlalchemy.orm import mapped_column, Mapped, Session
from argon2 import PasswordHasher
from src.infra.db import Base
from src.services.users import UserRepo, UserDTO, User
class UserModel(Base):
__tablename__ = 'user'
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String(254), unique=True)
email_confirmed: Mapped[bool] = mapped_column(Boolean())
password_hash: Mapped[str] = mapped_column(VARCHAR(255))
def model_to_user(db_user: UserModel) -> User:
return User(
id=db_user.id,
email=db_user.email,
email_confirmed=db_user.email_confirmed,
password_hash=SecretBox(db_user.password_hash),
)
class UserRepoImpl(UserRepo):
def __init__(self, db: Engine):
self._db = db
self._hasher = PasswordHasher()
self._logger = logging.getLogger('UserRepoImpl')
def create_user(self, user: UserDTO) -> User:
pw = self._hasher.hash(user.raw_password.expose_secret())
db_user = UserModel(email=user.email, password_hash=pw)
with Session(self._db) as session:
session.add(db_user)
session.commit()
return model_to_user(db_user)
def get_user_by_id(self, user_id: int) -> User | None:
with Session(self._db) as session:
user = session.get(UserModel, user_id)
if user:
return model_to_user(user)
def get_user_by_email(self, email: EmailAddress) -> User | None:
with Session(self._db) as session:
user = session.scalar(select(UserModel).where(UserModel.email == email))
if user:
return model_to_user(user)
def update_user(self, user: User):
with Session(self._db) as session:
db_user = session.get(UserModel, user.id)
if db_user is None:
self._logger.warning(f'Attempted to update non-existing user {user.id}')
return
# These fields can be set directly
db_user.email_confirmed = user.email_confirmed
db_user.password_hash = user.password_hash.expose_secret()
# If the email is being updated then it should not be considered confirmed.
if db_user.email != user.email:
db_user.email = user.email
db_user.email_confirmed = False
session.commit()
def auth_as_user(self, user: UserDTO) -> User | None:
full_user = self.get_user_by_email(user.email)
if full_user is None:
return None
try:
self._hasher.verify(full_user.password_hash.expose_secret(), user.raw_password.expose_secret())
except VerifyMismatchError:
return None