Compare commits
2 commits
1709290ea9
...
fc0616ce6a
| Author | SHA1 | Date | |
|---|---|---|---|
| fc0616ce6a | |||
| 2c43df83a5 |
8 changed files with 54 additions and 15 deletions
17
scopes/api/auth/config.py
Normal file
17
scopes/api/auth/config.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# scopes.api.auth.config
|
||||||
|
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
from typing import Any
|
||||||
|
from scopes.storage.db.postgres import StorageFactory
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
model_config = SettingsConfigDict(env_file='.env', extra='ignore')
|
||||||
|
#, env_prefix='SCOPES_')
|
||||||
|
auth_method: str = 'oauth2'
|
||||||
|
jwt_key: str | None = None
|
||||||
|
jwt_atlgorithm: str = 'HS256'
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
|
|
||||||
|
auth_methods: dict[str, Any] = {}
|
||||||
|
|
||||||
|
|
@ -12,17 +12,19 @@ from scopes.config import settings
|
||||||
from scopes.storage import user as st_user
|
from scopes.storage import user as st_user
|
||||||
from scopes.storage.common import Storage
|
from scopes.storage.common import Storage
|
||||||
|
|
||||||
class User(BaseModel):
|
class UserBase(BaseModel):
|
||||||
uid: str | None = None
|
|
||||||
timeStamp: datetime | None = None
|
|
||||||
name: str
|
name: str
|
||||||
login: str
|
login: str
|
||||||
display_name: str
|
display_name: str
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
pw_hash: str | None = Field(exclude=True, default=None)
|
|
||||||
groups: List[str] = Field(default_factory=list)
|
groups: List[str] = Field(default_factory=list)
|
||||||
|
|
||||||
class UserIn(User):
|
class User(UserBase):
|
||||||
|
uid: str | None = None
|
||||||
|
timeStamp: datetime | None = None
|
||||||
|
pw_hash: str | None = Field(exclude=True, default=None)
|
||||||
|
|
||||||
|
class UserIn(UserBase):
|
||||||
password: str | None = None
|
password: str | None = None
|
||||||
|
|
||||||
def hashPassword(self) -> str | None:
|
def hashPassword(self) -> str | None:
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,17 @@ from fastapi import Depends
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from scopes import config
|
from scopes import config
|
||||||
|
from scopes.api.auth import config as auth_config
|
||||||
from scopes.storage import common
|
from scopes.storage import common
|
||||||
|
|
||||||
type Storage = Annotated[common.Storage, Depends(storage)]
|
type Storage = Annotated[common.Storage, Depends(storage)]
|
||||||
type Auth = Annotated[str, Depends(authenticator)]
|
type Auth = Annotated[str, Depends(auth_method())]
|
||||||
|
|
||||||
def storage():
|
def storage():
|
||||||
stf = config.StorageFactory(config.settings)
|
stf = config.StorageFactory(config.settings)
|
||||||
return stf(config.settings.dbschema)
|
return stf(config.settings.dbschema)
|
||||||
|
|
||||||
def authenticator():
|
def auth_method():
|
||||||
return config.Authenticator()
|
auth = auth_config.auth_methods.get(auth_config.settings.auth_method)
|
||||||
|
return auth
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
# scopes.config.py
|
# scopes.config.py
|
||||||
|
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
from scopes.storage.db.postgres import StorageFactory
|
from scopes.storage.db.postgres import StorageFactory
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,13 +120,15 @@ class Container:
|
||||||
changed = False
|
changed = False
|
||||||
if found.data != track.data:
|
if found.data != track.data:
|
||||||
found.update(track.data)
|
found.update(track.data)
|
||||||
|
track.data = found.data
|
||||||
changed = True
|
changed = True
|
||||||
if track.timeStamp is not None and found.timeStamp != track.timeStamp:
|
if track.timeStamp is not None and found.timeStamp != track.timeStamp:
|
||||||
found.timeStamp = track.timeStamp
|
found.timeStamp = track.timeStamp
|
||||||
changed = True
|
changed = True
|
||||||
if changed:
|
if changed:
|
||||||
self.update(found)
|
self.update(found, not track.timeStamp)
|
||||||
track.trackId = found.trackId
|
track.trackId = found.trackId
|
||||||
|
track.timeStamp = found.timeStamp
|
||||||
return found.trackId
|
return found.trackId
|
||||||
|
|
||||||
def insert(self, track, withTrackId=False):
|
def insert(self, track, withTrackId=False):
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ class User(concept.Concept):
|
||||||
def prepareData(data, obj):
|
def prepareData(data, obj):
|
||||||
data = concept.Concept.prepareData(data, obj)
|
data = concept.Concept.prepareData(data, obj)
|
||||||
pw = data.pop('password', None)
|
pw = data.pop('password', None)
|
||||||
|
if pw:
|
||||||
data['pw_hash'] = obj.hashPassword()
|
data['pw_hash'] = obj.hashPassword()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ DBUSER=testuser
|
||||||
DBPASSWORD=secret
|
DBPASSWORD=secret
|
||||||
DBSCHEMA=testing
|
DBSCHEMA=testing
|
||||||
|
|
||||||
AUTHENTICATOR=testing
|
AUTH_METHOD=testing
|
||||||
|
|
||||||
# openssl rand -hex 32:
|
# openssl rand -hex 32:
|
||||||
JWT_KEY=391a521ee5ba2b6ce67b8fdbb9f0ca88f40a87d3bc1e82974381b647bb0fc776
|
JWT_KEY=391a521ee5ba2b6ce67b8fdbb9f0ca88f40a87d3bc1e82974381b647bb0fc776
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from fastapi.testclient import TestClient
|
||||||
from httpx2 import ASGITransport, AsyncClient
|
from httpx2 import ASGITransport, AsyncClient
|
||||||
|
|
||||||
from scopes.api import main
|
from scopes.api import main
|
||||||
|
from scopes.api.auth import config as auth_config
|
||||||
from scopes.api.main import app
|
from scopes.api.main import app
|
||||||
from scopes.storage import tracking, user as st_user
|
from scopes.storage import tracking, user as st_user
|
||||||
from scopes.api.auth import user as api_user
|
from scopes.api.auth import user as api_user
|
||||||
|
|
@ -22,6 +23,10 @@ def storage():
|
||||||
storage.commit()
|
storage.commit()
|
||||||
return storage
|
return storage
|
||||||
|
|
||||||
|
def dummy_auth():
|
||||||
|
return api_user.User(name='tester', login='tester', display_name='Tester')
|
||||||
|
auth_config.auth_methods['testing'] = dummy_auth
|
||||||
|
|
||||||
def async_client():
|
def async_client():
|
||||||
return AsyncClient(transport=ASGITransport(app=app), base_url='http://test')
|
return AsyncClient(transport=ASGITransport(app=app), base_url='http://test')
|
||||||
|
|
||||||
|
|
@ -72,11 +77,20 @@ def test_user(storage, caplog):
|
||||||
assert len(users) == 1
|
assert len(users) == 1
|
||||||
u01i = users[0]
|
u01i = users[0]
|
||||||
assert u01i.get(pw_hash) is None
|
assert u01i.get(pw_hash) is None
|
||||||
resp = clt.post('/users', json={'name': 'jane', 'login': 'jane',
|
inp = {'name': 'jane', 'login': 'jane', 'display_name': 'Jane',
|
||||||
'display_name': 'Jane', 'email': 'jane@xmpl.org',
|
'email': 'jane@xmpl.org', 'password': 'secret'}
|
||||||
'password': 'secret'})
|
resp = clt.post('/users', json=inp)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
u02 = resp.json()
|
u02 = resp.json()
|
||||||
assert u02['uid'] == 'user-2'
|
assert u02['uid'] == 'user-2'
|
||||||
assert u02.get('timeStamp') is not None
|
ts02 = u02.get('timeStamp')
|
||||||
|
assert ts02 is not None
|
||||||
|
del inp['password']
|
||||||
|
inp['display_name'] = 'Jane Doe'
|
||||||
|
resp = clt.post('/users', json=inp)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
u02a = resp.json()
|
||||||
|
assert u02a['uid'] == 'user-2'
|
||||||
|
ts02a = u02a.get('timeStamp')
|
||||||
|
assert ts02a is not None
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue