panther 4.3.7__py3-none-any.whl → 5.0.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- panther/__init__.py +1 -1
- panther/_load_configs.py +78 -64
- panther/_utils.py +1 -1
- panther/app.py +126 -60
- panther/authentications.py +26 -9
- panther/base_request.py +27 -2
- panther/base_websocket.py +26 -27
- panther/cli/create_command.py +1 -0
- panther/cli/main.py +19 -27
- panther/cli/monitor_command.py +8 -4
- panther/cli/template.py +11 -6
- panther/cli/utils.py +3 -2
- panther/configs.py +7 -9
- panther/db/cursor.py +23 -7
- panther/db/models.py +26 -19
- panther/db/queries/base_queries.py +1 -1
- panther/db/queries/mongodb_queries.py +172 -10
- panther/db/queries/pantherdb_queries.py +5 -5
- panther/db/queries/queries.py +1 -1
- panther/events.py +10 -4
- panther/exceptions.py +24 -2
- panther/generics.py +2 -2
- panther/main.py +80 -117
- panther/middlewares/__init__.py +1 -1
- panther/middlewares/base.py +15 -19
- panther/middlewares/monitoring.py +42 -0
- panther/openapi/__init__.py +1 -0
- panther/openapi/templates/openapi.html +27 -0
- panther/openapi/urls.py +5 -0
- panther/openapi/utils.py +167 -0
- panther/openapi/views.py +101 -0
- panther/pagination.py +1 -1
- panther/panel/middlewares.py +10 -0
- panther/panel/templates/base.html +14 -0
- panther/panel/templates/create.html +21 -0
- panther/panel/templates/create.js +1270 -0
- panther/panel/templates/detail.html +55 -0
- panther/panel/templates/home.html +9 -0
- panther/panel/templates/home.js +30 -0
- panther/panel/templates/login.html +47 -0
- panther/panel/templates/sidebar.html +13 -0
- panther/panel/templates/table.html +73 -0
- panther/panel/templates/table.js +339 -0
- panther/panel/urls.py +10 -5
- panther/panel/utils.py +98 -0
- panther/panel/views.py +143 -0
- panther/request.py +3 -0
- panther/response.py +91 -53
- panther/routings.py +7 -2
- panther/serializer.py +1 -1
- panther/utils.py +34 -26
- panther/websocket.py +3 -0
- {panther-4.3.7.dist-info → panther-5.0.0b1.dist-info}/METADATA +19 -17
- panther-5.0.0b1.dist-info/RECORD +75 -0
- {panther-4.3.7.dist-info → panther-5.0.0b1.dist-info}/WHEEL +1 -1
- panther-4.3.7.dist-info/RECORD +0 -57
- {panther-4.3.7.dist-info → panther-5.0.0b1.dist-info}/entry_points.txt +0 -0
- {panther-4.3.7.dist-info → panther-5.0.0b1.dist-info}/licenses/LICENSE +0 -0
- {panther-4.3.7.dist-info → panther-5.0.0b1.dist-info}/top_level.txt +0 -0
panther/base_websocket.py
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import asyncio
|
4
|
-
import
|
4
|
+
import logging
|
5
5
|
from multiprocessing.managers import SyncManager
|
6
6
|
from typing import TYPE_CHECKING, Literal
|
7
7
|
|
8
|
-
import
|
8
|
+
import orjson as json
|
9
|
+
import ulid
|
10
|
+
|
9
11
|
from panther import status
|
10
12
|
from panther.base_request import BaseRequest
|
11
13
|
from panther.configs import config
|
12
14
|
from panther.db.connections import redis
|
13
|
-
from panther.exceptions import
|
14
|
-
from panther.
|
15
|
-
from panther.utils import Singleton, ULID
|
15
|
+
from panther.exceptions import InvalidPathVariableAPIError, BaseError
|
16
|
+
from panther.utils import Singleton
|
16
17
|
|
17
18
|
if TYPE_CHECKING:
|
18
19
|
from redis.asyncio import Redis
|
@@ -108,7 +109,7 @@ class WebsocketConnections(Singleton):
|
|
108
109
|
else:
|
109
110
|
self.pubsub.publish(publish_data)
|
110
111
|
|
111
|
-
async def listen(self, connection: Websocket)
|
112
|
+
async def listen(self, connection: Websocket):
|
112
113
|
# 1. Authentication
|
113
114
|
if not connection.is_rejected:
|
114
115
|
await self.handle_authentication(connection=connection)
|
@@ -125,7 +126,7 @@ class WebsocketConnections(Singleton):
|
|
125
126
|
try:
|
126
127
|
kwargs = connection.clean_parameters(connection.connect)
|
127
128
|
except InvalidPathVariableAPIError as e:
|
128
|
-
connection.
|
129
|
+
connection.change_state(state='Rejected', message=e.detail)
|
129
130
|
return await connection.close()
|
130
131
|
|
131
132
|
# 4. Connect To Endpoint
|
@@ -139,6 +140,8 @@ class WebsocketConnections(Singleton):
|
|
139
140
|
# 6. Listen Connection
|
140
141
|
await self.listen_connection(connection=connection)
|
141
142
|
|
143
|
+
return connection
|
144
|
+
|
142
145
|
async def listen_connection(self, connection: Websocket):
|
143
146
|
while True:
|
144
147
|
response = await connection.asgi_receive()
|
@@ -146,7 +149,7 @@ class WebsocketConnections(Singleton):
|
|
146
149
|
continue
|
147
150
|
|
148
151
|
if response['type'] == 'websocket.disconnect':
|
149
|
-
#
|
152
|
+
# Connection has to be closed by the client.
|
150
153
|
await self.connection_closed(connection=connection)
|
151
154
|
break
|
152
155
|
|
@@ -157,25 +160,20 @@ class WebsocketConnections(Singleton):
|
|
157
160
|
|
158
161
|
async def connection_accepted(self, connection: Websocket) -> None:
|
159
162
|
# Generate ConnectionID
|
160
|
-
connection._connection_id =
|
163
|
+
connection._connection_id = ulid.new()
|
161
164
|
|
162
165
|
# Save Connection
|
163
166
|
self.connections[connection.connection_id] = connection
|
164
|
-
|
165
|
-
# Logs
|
166
|
-
await connection.monitoring.after('Accepted')
|
167
|
-
connection.log(f'Accepted {connection.connection_id}')
|
167
|
+
connection.change_state(state='Accepted')
|
168
168
|
|
169
169
|
async def connection_closed(self, connection: Websocket, from_server: bool = False) -> None:
|
170
170
|
if connection.is_connected:
|
171
171
|
del self.connections[connection.connection_id]
|
172
|
-
|
173
|
-
connection.log(f'Closed {connection.connection_id}')
|
172
|
+
connection.change_state(state='Closed')
|
174
173
|
connection._connection_id = ''
|
175
174
|
|
176
175
|
elif connection.is_rejected is False and from_server is True:
|
177
|
-
|
178
|
-
connection.log('Rejected')
|
176
|
+
connection.change_state(state='Rejected')
|
179
177
|
connection._is_rejected = True
|
180
178
|
|
181
179
|
async def start(self):
|
@@ -203,8 +201,8 @@ class WebsocketConnections(Singleton):
|
|
203
201
|
else:
|
204
202
|
try:
|
205
203
|
connection.user = await config.WS_AUTHENTICATION.authentication(connection)
|
206
|
-
except
|
207
|
-
connection.
|
204
|
+
except BaseError as e:
|
205
|
+
connection.change_state(state='Rejected', message=e.detail)
|
208
206
|
await connection.close()
|
209
207
|
|
210
208
|
@classmethod
|
@@ -215,16 +213,16 @@ class WebsocketConnections(Singleton):
|
|
215
213
|
logger.critical(f'{perm.__name__}.authorization should be "classmethod"')
|
216
214
|
await connection.close()
|
217
215
|
elif await perm.authorization(connection) is False:
|
218
|
-
connection.
|
216
|
+
connection.change_state(state='Rejected', message='Permission Denied')
|
219
217
|
await connection.close()
|
220
218
|
|
221
219
|
|
222
220
|
class Websocket(BaseRequest):
|
223
221
|
auth: bool = False
|
224
222
|
permissions: list = []
|
223
|
+
state: str = 'Connected'
|
225
224
|
_connection_id: str = ''
|
226
225
|
_is_rejected: bool = False
|
227
|
-
_monitoring: Monitoring
|
228
226
|
|
229
227
|
def __init_subclass__(cls, **kwargs):
|
230
228
|
if cls.__module__ != 'panther.websocket':
|
@@ -274,9 +272,10 @@ class Websocket(BaseRequest):
|
|
274
272
|
def is_rejected(self) -> bool:
|
275
273
|
return self._is_rejected
|
276
274
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
275
|
+
def change_state(self, state: Literal['Accepted', 'Closed', 'Rejected'], message: str = ''):
|
276
|
+
self.state = state
|
277
|
+
if message:
|
278
|
+
message = f' | {message}'
|
279
|
+
if self.is_connected:
|
280
|
+
message = f' | {self.connection_id}{message}'
|
281
|
+
logger.debug(f'WS {self.path} --> {state}{message}')
|
panther/cli/create_command.py
CHANGED
panther/cli/main.py
CHANGED
@@ -1,40 +1,32 @@
|
|
1
|
-
import
|
1
|
+
import logging
|
2
2
|
import sys
|
3
3
|
|
4
4
|
from panther import version as panther_version
|
5
5
|
from panther.cli.create_command import create
|
6
6
|
from panther.cli.monitor_command import monitor
|
7
7
|
from panther.cli.run_command import run
|
8
|
-
from panther.cli.utils import cli_error,
|
8
|
+
from panther.cli.utils import cli_error, print_help_message
|
9
9
|
|
10
|
+
logger = logging.getLogger('panther')
|
10
11
|
|
11
|
-
|
12
|
+
|
13
|
+
def shell(args) -> None:
|
12
14
|
if len(args) == 0:
|
13
|
-
|
14
|
-
|
15
|
+
return cli_error(
|
16
|
+
'Not Enough Arguments, Give me a file path that contains `Panther()` app.\n'
|
17
|
+
' * Make sure to run `panther shell` in the same directory as that file!\n'
|
18
|
+
' * Example: `panther shell main.py`'
|
19
|
+
)
|
15
20
|
elif len(args) != 1:
|
16
21
|
return cli_error('Too Many Arguments.')
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
os.system('bpython')
|
26
|
-
except ImportError as e:
|
27
|
-
cli_warning(e, 'Hint: "pip install bpython"')
|
28
|
-
os.system('python')
|
29
|
-
|
30
|
-
# Ipython
|
31
|
-
elif shell_type == 'ipython':
|
32
|
-
try:
|
33
|
-
import IPython
|
34
|
-
os.system('ipython')
|
35
|
-
except ImportError as e:
|
36
|
-
cli_warning(e, 'Hint: "pip install ipython"')
|
37
|
-
os.system('python')
|
22
|
+
|
23
|
+
package = args[0].removesuffix('.py')
|
24
|
+
try:
|
25
|
+
from IPython import start_ipython
|
26
|
+
|
27
|
+
start_ipython(('--gui', 'asyncio', '-c', f'"import {package}"', '-i'))
|
28
|
+
except ImportError:
|
29
|
+
logger.error('Make sure `ipython` is installed -> Hint: `pip install ipython`')
|
38
30
|
|
39
31
|
|
40
32
|
def version() -> None:
|
@@ -54,7 +46,7 @@ def start() -> None:
|
|
54
46
|
shell(args)
|
55
47
|
case 'monitor':
|
56
48
|
monitor()
|
57
|
-
case 'version':
|
49
|
+
case 'version' | '--version':
|
58
50
|
version()
|
59
51
|
case _:
|
60
52
|
cli_error('Invalid Arguments.')
|
panther/cli/monitor_command.py
CHANGED
@@ -15,6 +15,7 @@ from rich.table import Table
|
|
15
15
|
|
16
16
|
from panther.cli.utils import import_error
|
17
17
|
from panther.configs import config
|
18
|
+
from panther.middlewares.monitoring import WebsocketMonitoringMiddleware
|
18
19
|
|
19
20
|
with contextlib.suppress(ImportError):
|
20
21
|
from watchfiles import watch
|
@@ -53,7 +54,7 @@ class Monitoring:
|
|
53
54
|
for line in f.readlines():
|
54
55
|
# line = date_time | method | path | ip:port | response_time(seconds) | status
|
55
56
|
columns = line.split('|')
|
56
|
-
columns[4] = self._clean_response_time(
|
57
|
+
columns[4] = self._clean_response_time(columns[4])
|
57
58
|
self.rows.append(columns)
|
58
59
|
live.update(self.generate_table())
|
59
60
|
|
@@ -66,7 +67,7 @@ class Monitoring:
|
|
66
67
|
|
67
68
|
# Check log file
|
68
69
|
if not self.monitoring_log_file.exists():
|
69
|
-
return f'`{self.monitoring_log_file}` file not found. (Make sure `
|
70
|
+
return f'`{self.monitoring_log_file}` file not found. (Make sure `panther.middlewares.monitoring.MonitoringMiddleware` is in your `MIDDLEWARES`)'
|
70
71
|
|
71
72
|
# Initialize Deque
|
72
73
|
self.update_rows()
|
@@ -104,14 +105,17 @@ class Monitoring:
|
|
104
105
|
self.rows = deque(self.rows, maxlen=lines)
|
105
106
|
|
106
107
|
@classmethod
|
107
|
-
def _clean_response_time(cls, response_time:
|
108
|
+
def _clean_response_time(cls, response_time: str) -> str:
|
109
|
+
if response_time == WebsocketMonitoringMiddleware.ConnectedConnectionTime:
|
110
|
+
return response_time
|
111
|
+
response_time = float(response_time)
|
108
112
|
time_unit = ' s'
|
109
113
|
|
110
114
|
if response_time < 0.01:
|
111
115
|
response_time = response_time * 1_000
|
112
116
|
time_unit = 'ms'
|
113
117
|
|
114
|
-
elif response_time >=
|
118
|
+
elif response_time >= 60:
|
115
119
|
response_time = response_time / 60
|
116
120
|
time_unit = ' m'
|
117
121
|
|
panther/cli/template.py
CHANGED
@@ -63,7 +63,11 @@ from panther.utils import load_env
|
|
63
63
|
BASE_DIR = Path(__name__).resolve().parent
|
64
64
|
env = load_env(BASE_DIR / '.env')
|
65
65
|
|
66
|
-
SECRET_KEY = env['SECRET_KEY']{DATABASE}{REDIS}{USER_MODEL}{AUTHENTICATION}{
|
66
|
+
SECRET_KEY = env['SECRET_KEY']{DATABASE}{REDIS}{USER_MODEL}{AUTHENTICATION}{LOG_QUERIES}{AUTO_REFORMAT}
|
67
|
+
|
68
|
+
MIDDLEWARES = [
|
69
|
+
{MONITORING}
|
70
|
+
]
|
67
71
|
|
68
72
|
# More Info: https://PantherPy.GitHub.io/urls/
|
69
73
|
URLs = 'core.urls.url_routing'
|
@@ -134,7 +138,11 @@ from panther.utils import load_env, timezone_now
|
|
134
138
|
BASE_DIR = Path(__name__).resolve().parent
|
135
139
|
env = load_env(BASE_DIR / '.env')
|
136
140
|
|
137
|
-
SECRET_KEY = env['SECRET_KEY']{DATABASE}{REDIS}{USER_MODEL}{AUTHENTICATION}{
|
141
|
+
SECRET_KEY = env['SECRET_KEY']{DATABASE}{REDIS}{USER_MODEL}{AUTHENTICATION}{LOG_QUERIES}{AUTO_REFORMAT}
|
142
|
+
|
143
|
+
MIDDLEWARES = [
|
144
|
+
{MONITORING}
|
145
|
+
]
|
138
146
|
|
139
147
|
InfoThrottling = Throttling(rate=5, duration=timedelta(minutes=1))
|
140
148
|
|
@@ -216,10 +224,7 @@ AUTHENTICATION_PART = """
|
|
216
224
|
# More Info: https://PantherPy.GitHub.io/authentications/
|
217
225
|
AUTHENTICATION = 'panther.authentications.JWTAuthentication'"""
|
218
226
|
|
219
|
-
MONITORING_PART = """
|
220
|
-
|
221
|
-
# More Info: https://PantherPy.GitHub.io/monitoring/
|
222
|
-
MONITORING = True"""
|
227
|
+
MONITORING_PART = """'panther.middlewares.monitoring.MonitoringMiddleware'"""
|
223
228
|
|
224
229
|
LOG_QUERIES_PART = """
|
225
230
|
|
panther/cli/utils.py
CHANGED
@@ -48,13 +48,14 @@ help_message = f"""{logo}
|
|
48
48
|
{h} - panther run [--reload | --help] {h}
|
49
49
|
{h} Run your project with uvicorn {h}
|
50
50
|
{h} {h}
|
51
|
-
{h} - panther shell
|
51
|
+
{h} - panther shell <application file path> {h}
|
52
52
|
{h} Run interactive python shell {h}
|
53
|
+
{h} * Example: `panther shell main.py` {h}
|
53
54
|
{h} {h}
|
54
55
|
{h} - panther monitor {h}
|
55
56
|
{h} Show the monitor :) {h}
|
56
57
|
{h} {h}
|
57
|
-
{h} - panther version
|
58
|
+
{h} - panther version | --version {h}
|
58
59
|
{h} Print the current version of Panther {h}
|
59
60
|
{h} {h}
|
60
61
|
{h} - panther h | help | --help | -h {h}
|
panther/configs.py
CHANGED
@@ -6,11 +6,9 @@ from pathlib import Path
|
|
6
6
|
from typing import Callable
|
7
7
|
|
8
8
|
import jinja2
|
9
|
-
from pydantic
|
10
|
-
|
9
|
+
from pydantic import BaseModel as PydanticBaseModel
|
11
10
|
from panther.throttling import Throttling
|
12
11
|
|
13
|
-
|
14
12
|
class JWTConfig:
|
15
13
|
def __init__(
|
16
14
|
self,
|
@@ -53,13 +51,13 @@ class Config:
|
|
53
51
|
DEFAULT_CACHE_EXP: timedelta | None
|
54
52
|
THROTTLING: Throttling | None
|
55
53
|
SECRET_KEY: bytes | None
|
56
|
-
HTTP_MIDDLEWARES: list
|
57
|
-
WS_MIDDLEWARES: list
|
58
|
-
USER_MODEL:
|
59
|
-
AUTHENTICATION:
|
60
|
-
WS_AUTHENTICATION:
|
54
|
+
HTTP_MIDDLEWARES: list
|
55
|
+
WS_MIDDLEWARES: list
|
56
|
+
USER_MODEL: type[PydanticBaseModel] | None # type: type[panther.db.Model]
|
57
|
+
AUTHENTICATION: type[PydanticBaseModel] | None
|
58
|
+
WS_AUTHENTICATION: type[PydanticBaseModel] | None
|
61
59
|
JWT_CONFIG: JWTConfig | None
|
62
|
-
MODELS: list[
|
60
|
+
MODELS: list[type[PydanticBaseModel]] # type: type[panther.db.Model]
|
63
61
|
FLAT_URLS: dict
|
64
62
|
URLS: dict
|
65
63
|
WEBSOCKET_CONNECTIONS: typing.Callable | None
|
panther/db/cursor.py
CHANGED
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from sys import version_info
|
4
4
|
|
5
|
+
from panther.utils import run_coroutine
|
6
|
+
|
5
7
|
try:
|
6
8
|
from pymongo.cursor import Cursor as _Cursor
|
7
9
|
except ImportError:
|
@@ -31,13 +33,27 @@ class Cursor(_Cursor):
|
|
31
33
|
self.cls = self.models[collection.name]
|
32
34
|
super().__init__(collection, *args, **kwargs)
|
33
35
|
|
34
|
-
def
|
35
|
-
return self
|
36
|
+
def __aiter__(self) -> Self:
|
37
|
+
return self
|
38
|
+
|
39
|
+
async def next(self) -> Self:
|
40
|
+
return await self.cls._create_model_instance(document=super().next())
|
41
|
+
|
42
|
+
async def __anext__(self) -> Self:
|
43
|
+
try:
|
44
|
+
return await self.cls._create_model_instance(document=super().next())
|
45
|
+
except StopIteration:
|
46
|
+
raise StopAsyncIteration
|
47
|
+
|
48
|
+
def __next__(self) -> Self:
|
49
|
+
try:
|
50
|
+
return run_coroutine(self.cls._create_model_instance(document=super().next()))
|
51
|
+
except StopIteration:
|
52
|
+
raise
|
36
53
|
|
37
|
-
__next__ = next
|
38
54
|
|
39
55
|
def __getitem__(self, index: int | slice) -> Cursor[Self] | Self:
|
40
|
-
|
41
|
-
if isinstance(
|
42
|
-
return self.cls._create_model_instance(document=
|
43
|
-
return
|
56
|
+
document = super().__getitem__(index)
|
57
|
+
if isinstance(document, dict):
|
58
|
+
return run_coroutine(self.cls._create_model_instance(document=document))
|
59
|
+
return document
|
panther/db/models.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import contextlib
|
2
2
|
import os
|
3
3
|
from datetime import datetime
|
4
|
-
from typing import Annotated
|
4
|
+
from typing import Annotated, ClassVar
|
5
5
|
|
6
6
|
from pydantic import Field, WrapValidator, PlainSerializer, BaseModel as PydanticBaseModel
|
7
7
|
|
@@ -15,16 +15,17 @@ with contextlib.suppress(ImportError):
|
|
15
15
|
|
16
16
|
|
17
17
|
def validate_object_id(value, handler):
|
18
|
-
if config.DATABASE.__class__.__name__
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
if config.DATABASE.__class__.__name__ != 'MongoDBConnection':
|
19
|
+
return str(value)
|
20
|
+
|
21
|
+
if isinstance(value, bson.ObjectId):
|
22
|
+
return value
|
23
|
+
|
24
|
+
try:
|
25
|
+
return bson.ObjectId(value)
|
26
|
+
except Exception as e:
|
27
|
+
msg = 'Invalid ObjectId'
|
28
|
+
raise ValueError(msg) from e
|
28
29
|
|
29
30
|
|
30
31
|
ID = Annotated[str, WrapValidator(validate_object_id), PlainSerializer(lambda x: str(x), return_type=str)]
|
@@ -36,23 +37,28 @@ class Model(PydanticBaseModel, Query):
|
|
36
37
|
return
|
37
38
|
config.MODELS.append(cls)
|
38
39
|
|
39
|
-
id: ID | None = Field(None, validation_alias='_id')
|
40
|
+
id: ID | None = Field(None, validation_alias='_id', alias='_id')
|
40
41
|
|
41
42
|
@property
|
42
43
|
def _id(self):
|
43
44
|
"""
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
Returns the actual ID value:
|
46
|
+
- For MongoDB: returns ObjectId
|
47
|
+
- For PantherDB: returns str
|
47
48
|
"""
|
49
|
+
if config.DATABASE.__class__.__name__ == 'MongoDBConnection':
|
50
|
+
return bson.ObjectId(self.id)
|
48
51
|
return self.id
|
49
52
|
|
50
53
|
|
51
54
|
class BaseUser(Model):
|
55
|
+
username: str
|
52
56
|
password: str = Field('', max_length=64)
|
53
57
|
last_login: datetime | None = None
|
54
58
|
date_created: datetime | None = Field(default_factory=timezone_now)
|
55
59
|
|
60
|
+
USERNAME_FIELD: ClassVar = 'username'
|
61
|
+
|
56
62
|
async def update_last_login(self) -> None:
|
57
63
|
await self.update(last_login=timezone_now())
|
58
64
|
|
@@ -63,7 +69,7 @@ class BaseUser(Model):
|
|
63
69
|
async def logout(self) -> dict:
|
64
70
|
return await config.AUTHENTICATION.logout(self._auth_token)
|
65
71
|
|
66
|
-
def set_password(self, password: str):
|
72
|
+
async def set_password(self, password: str):
|
67
73
|
"""
|
68
74
|
URANDOM_SIZE = 16 char -->
|
69
75
|
salt = 16 bytes
|
@@ -73,12 +79,13 @@ class BaseUser(Model):
|
|
73
79
|
salt = os.urandom(URANDOM_SIZE)
|
74
80
|
derived_key = scrypt(password=password, salt=salt, digest=True)
|
75
81
|
|
76
|
-
|
82
|
+
hashed_password = f'{salt.hex()}{derived_key}'
|
83
|
+
await self.update(password=hashed_password)
|
77
84
|
|
78
|
-
def check_password(self,
|
85
|
+
def check_password(self, password: str) -> bool:
|
79
86
|
size = URANDOM_SIZE * 2
|
80
87
|
salt = self.password[:size]
|
81
88
|
stored_hash = self.password[size:]
|
82
|
-
derived_key = scrypt(password=
|
89
|
+
derived_key = scrypt(password=password, salt=bytes.fromhex(salt), digest=True)
|
83
90
|
|
84
91
|
return derived_key == stored_hash
|
@@ -46,7 +46,7 @@ class BaseQuery:
|
|
46
46
|
raise DatabaseError(error)
|
47
47
|
|
48
48
|
@classmethod
|
49
|
-
def _create_model_instance(cls, document: dict):
|
49
|
+
async def _create_model_instance(cls, document: dict):
|
50
50
|
"""Prevent getting errors from document insertion"""
|
51
51
|
try:
|
52
52
|
return cls(**document)
|