From 12c099b06b9e0ddcd9da12f0ca24e4156b68270f Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 5 Nov 2026 12:53:26 +0100 Subject: [PATCH] api.auth: User with password hash --- pyproject.toml | 2 +- scopes/api/auth/user.py | 13 ++++++++++++- tests/test_api.py | 7 ++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3c08a38..691ef76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ zope = [ "zope.traversing", ] api = ["fastapi[standard]"] -auth = ["authlib", "pyjwt[crypto]", "cryptography", "requests"] +auth = ["authlib", "pyjwt[crypto]", "cryptography", "pwdlib[argon2]", "requests"] zopetest = ["zope.testrunner"] apitest = ["pytest", "httpx2"] diff --git a/scopes/api/auth/user.py b/scopes/api/auth/user.py index 597636f..2af8b84 100644 --- a/scopes/api/auth/user.py +++ b/scopes/api/auth/user.py @@ -1,6 +1,7 @@ # scopes.api.auth user from fastapi import APIRouter +from pwdlib import PasswordHash from pydantic import BaseModel, Field from typing import List @@ -13,9 +14,19 @@ class User(BaseModel): login: str display_name: str email: str | None = None - pw_hash: str | None = None + pw_hash: str | None = Field(exclude=True, default=None) groups: List[str] = Field(default_factory=list) +# password handling + +pw_hash = PasswordHash.recommended() + +def hashPassword(pw): + return pw_hash.hash(pw) + +def verifyPassword(plain, hashed): + return pw_hash.verify(plain, hashed) + # routes router = APIRouter() diff --git a/tests/test_api.py b/tests/test_api.py index 5442d92..9e95fc3 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -61,10 +61,15 @@ def test_tracking(storage, caplog): def test_user(storage, caplog): users = storage.create(st_user.Users) - u01 = st_user.User('jim', 'jim', data=dict(display_name='Jim')) + pw = 'secret' + pw_hash = api_user.hashPassword(pw) + u01 = st_user.User('jim', 'jim', data=dict(display_name='Jim', pw_hash=pw_hash)) u01id = users.save(u01) storage.commit() with TestClient(app) as clt: caplog.set_level(config.settings.log_level) users = clt.get('/users').json() assert len(users) == 1 + u01i = users[0] + assert u01i.get(pw_hash) is None +