hypern 0.3.8__cp311-cp311-win32.whl → 0.3.9__cp311-cp311-win32.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.
- hypern/application.py +3 -0
- hypern/args_parser.py +7 -0
- hypern/database/sqlalchemy/__init__.py +4 -0
- hypern/database/{addons/sqlalchemy/__init__.py → sqlalchemy/config.py} +0 -5
- hypern/database/{sql → sqlx}/query.py +1 -1
- hypern/hypern.cp311-win32.pyd +0 -0
- hypern/hypern.pyi +1 -0
- {hypern-0.3.8.dist-info → hypern-0.3.9.dist-info}/METADATA +2 -9
- {hypern-0.3.8.dist-info → hypern-0.3.9.dist-info}/RECORD +16 -32
- hypern/database/addons/__init__.py +0 -5
- hypern/database/addons/sqlalchemy/fields/__init__.py +0 -14
- hypern/database/addons/sqlalchemy/fields/color.py +0 -16
- hypern/database/addons/sqlalchemy/fields/daterange.py +0 -23
- hypern/database/addons/sqlalchemy/fields/datetime.py +0 -22
- hypern/database/addons/sqlalchemy/fields/encrypted.py +0 -58
- hypern/database/addons/sqlalchemy/fields/password.py +0 -171
- hypern/database/addons/sqlalchemy/fields/ts_vector.py +0 -46
- hypern/database/addons/sqlalchemy/fields/unicode.py +0 -15
- hypern/database/nosql/__init__.py +0 -25
- hypern/database/nosql/addons/__init__.py +0 -4
- hypern/database/nosql/addons/color.py +0 -16
- hypern/database/nosql/addons/daterange.py +0 -30
- hypern/database/nosql/addons/encrypted.py +0 -53
- hypern/database/nosql/addons/password.py +0 -134
- hypern/database/nosql/addons/unicode.py +0 -10
- hypern/security.py +0 -44
- /hypern/database/{addons/sqlalchemy → sqlalchemy}/repository.py +0 -0
- /hypern/database/{sql → sqlx}/__init__.py +0 -0
- /hypern/database/{sql → sqlx}/field.py +0 -0
- /hypern/database/{sql → sqlx}/migrate.py +0 -0
- /hypern/database/{sql → sqlx}/model.py +0 -0
- {hypern-0.3.8.dist-info → hypern-0.3.9.dist-info}/WHEEL +0 -0
- {hypern-0.3.8.dist-info → hypern-0.3.9.dist-info}/licenses/LICENSE +0 -0
hypern/application.py
CHANGED
@@ -437,6 +437,9 @@ class Hypern:
|
|
437
437
|
self.args.workers = self.thread_config.workers
|
438
438
|
self.args.max_blocking_threads = self.thread_config.max_blocking_threads
|
439
439
|
|
440
|
+
if self.args.http2:
|
441
|
+
server.enable_http2()
|
442
|
+
|
440
443
|
run_processes(
|
441
444
|
server=server,
|
442
445
|
host=self.args.host,
|
hypern/args_parser.py
CHANGED
@@ -55,6 +55,12 @@ class ArgsConfig:
|
|
55
55
|
help="It sets the number of workers and max-blocking-threads automatically.",
|
56
56
|
)
|
57
57
|
|
58
|
+
parser.add_argument(
|
59
|
+
"--http2",
|
60
|
+
action="store_true",
|
61
|
+
help="Enable HTTP/2 support.",
|
62
|
+
)
|
63
|
+
|
58
64
|
args, _ = parser.parse_known_args()
|
59
65
|
|
60
66
|
self.host = args.host or "127.0.0.1"
|
@@ -64,3 +70,4 @@ class ArgsConfig:
|
|
64
70
|
self.workers = args.workers or 1
|
65
71
|
self.reload = args.reload or False
|
66
72
|
self.auto_workers = args.auto_workers
|
73
|
+
self.http2 = args.http2 or False
|
@@ -7,8 +7,6 @@ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_scoped_sessi
|
|
7
7
|
from sqlalchemy.orm import Session, sessionmaker
|
8
8
|
from sqlalchemy.sql.expression import Delete, Insert, Update
|
9
9
|
|
10
|
-
from .repository import Model, PostgresRepository
|
11
|
-
|
12
10
|
|
13
11
|
class SqlalchemyConfig:
|
14
12
|
def __init__(self, default_engine: AsyncEngine | None = None, reader_engine: AsyncEngine | None = None, writer_engine: AsyncEngine | None = None):
|
@@ -66,6 +64,3 @@ class SqlalchemyConfig:
|
|
66
64
|
|
67
65
|
def init_app(self, app):
|
68
66
|
app.inject("get_session", self.get_session)
|
69
|
-
|
70
|
-
|
71
|
-
__all__ = ["Model", "PostgresRepository", "SqlalchemyConfig"]
|
hypern/hypern.cp311-win32.pyd
CHANGED
Binary file
|
hypern/hypern.pyi
CHANGED
@@ -188,6 +188,7 @@ class Server:
|
|
188
188
|
def set_shutdown_handler(self, on_shutdown: FunctionInfo) -> None: ...
|
189
189
|
def set_database_config(self, config: DatabaseConfig) -> None: ...
|
190
190
|
def set_dependencies(self, dependencies: Dict[str, Any]) -> None: ...
|
191
|
+
def enable_http2(self) -> None: ...
|
191
192
|
|
192
193
|
class Route:
|
193
194
|
path: str
|
@@ -1,31 +1,24 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hypern
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.9
|
4
4
|
Classifier: Programming Language :: Rust
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
6
6
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
7
7
|
Requires-Dist: sqlalchemy[asyncio] ==2.0.31
|
8
|
-
Requires-Dist: passlib ==1.7.4
|
9
8
|
Requires-Dist: pyjwt ==2.8.0
|
10
9
|
Requires-Dist: pydash ==8.0.3
|
11
10
|
Requires-Dist: sentry-sdk ==2.11.0
|
12
|
-
Requires-Dist: pydantic-settings ==2.3.4
|
13
11
|
Requires-Dist: celery ==5.4.0
|
14
|
-
Requires-Dist: asgiref ==3.8.1
|
15
12
|
Requires-Dist: psycopg ==3.2.3
|
16
13
|
Requires-Dist: pyyaml ==6.0.2
|
17
|
-
Requires-Dist: mongoengine ==0.29.1
|
18
|
-
Requires-Dist: argon2-cffi ==23.1.0
|
19
|
-
Requires-Dist: bcrypt ==4.2.0
|
20
14
|
Requires-Dist: orjson ==3.10.11
|
21
15
|
Requires-Dist: multiprocess ==0.70.17
|
22
16
|
Requires-Dist: uvloop ==0.21.0 ; sys_platform != 'win32' and platform_python_implementation == 'CPython' and platform_machine != 'armv7l'
|
23
|
-
Requires-Dist: cryptography ==43.0.3
|
24
17
|
Requires-Dist: watchdog ==6.0.0
|
25
|
-
Requires-Dist: jsonschema ==4.23.0
|
26
18
|
Requires-Dist: psutil ==6.1.0
|
27
19
|
Requires-Dist: msgpack ==1.1.0
|
28
20
|
Requires-Dist: redis ==5.2.1
|
21
|
+
Requires-Dist: pydantic ==2.10.4
|
29
22
|
License-File: LICENSE
|
30
23
|
Summary: A Fast Async Python backend with a Rust runtime.
|
31
24
|
Author-email: Martin Dang <vannghiem848@gmail.com>
|
@@ -1,8 +1,8 @@
|
|
1
|
-
hypern-0.3.
|
2
|
-
hypern-0.3.
|
3
|
-
hypern-0.3.
|
4
|
-
hypern/application.py,sha256=
|
5
|
-
hypern/args_parser.py,sha256=
|
1
|
+
hypern-0.3.9.dist-info/METADATA,sha256=So9YgdPdWF6zU6X3v8wRPflQyHZvCMJBMWHTriMz7SQ,4112
|
2
|
+
hypern-0.3.9.dist-info/WHEEL,sha256=nZYUEa61gMm872poFFPMY96BdrHLzPbU4DPYHZlOKjU,92
|
3
|
+
hypern-0.3.9.dist-info/licenses/LICENSE,sha256=qbYKAIJLS6jYg5hYncKE7OtWmqOtpVTvKNkwOa0Iwwg,1328
|
4
|
+
hypern/application.py,sha256=EVa3bjUjgEmM7jDpGTVcFlBa7kk2m69QMlpdsp8AWpU,18068
|
5
|
+
hypern/args_parser.py,sha256=1v41RZT-35iY80I5OugjeFPXPr28HUvniQwu-29y1vw,2222
|
6
6
|
hypern/auth/authorization.py,sha256=-NprZsI0np889ZN1fp-MiVFrPoMNzUtatBJaCMtkllM,32
|
7
7
|
hypern/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
hypern/background.py,sha256=xy38nQZSJsYFRXr3-uFJeNW9E1GiXXOC7lSe5pC0eyE,124
|
@@ -13,29 +13,14 @@ hypern/caching/__init__.py,sha256=ODO7zMm4iFG8wcvrhKmukryG5wOTW0DnzFvNMfF57Cc,35
|
|
13
13
|
hypern/cli/commands.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
14
|
hypern/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
15
|
hypern/config.py,sha256=Jij9eGg5NgC8Un5Lw5i7ghuEMAfkVctdcoE4RaN5LTE,8157
|
16
|
-
hypern/database/
|
17
|
-
hypern/database/
|
18
|
-
hypern/database/
|
19
|
-
hypern/database/
|
20
|
-
hypern/database/
|
21
|
-
hypern/database/
|
22
|
-
hypern/database/
|
23
|
-
hypern/database/
|
24
|
-
hypern/database/addons/sqlalchemy/repository.py,sha256=ue6vWOTrnEPyDevlyh3v-7PU6GSfrZHYKrbXVuoS8UA,9516
|
25
|
-
hypern/database/addons/sqlalchemy/__init__.py,sha256=FuY78ubEwtifdQTVHhCrscYaAarlp2urgYBc_R77yt0,2766
|
26
|
-
hypern/database/addons/__init__.py,sha256=mdW0P0xvnK8htUk02ujvIaeHXl6w53JjrTS4ioNi1Bw,63
|
27
|
-
hypern/database/nosql/addons/color.py,sha256=bAGRuARCAYwZ1nO4jK0lzGYKmavTDtS34BxvrsetF74,446
|
28
|
-
hypern/database/nosql/addons/daterange.py,sha256=hGUSoVFqatNY-TB5wjZTq62iZpHpdsyRJIsHxsj1uDs,1192
|
29
|
-
hypern/database/nosql/addons/encrypted.py,sha256=B0M-uDqvZHVmIZcFdwcuC2MGsv0pGJFQ1lrOg8klR9U,1741
|
30
|
-
hypern/database/nosql/addons/password.py,sha256=jfZxvWFm6nV9EWpXq5Mj-jpqnl9QbokZj9WT14n7dKE,5035
|
31
|
-
hypern/database/nosql/addons/unicode.py,sha256=LaDpLfdoTcJuASPE-8fqOVD05H_uOx8gOdnyDn5Iu0c,268
|
32
|
-
hypern/database/nosql/addons/__init__.py,sha256=WEtPM8sPHilvga7zxwqvINeTkF0hdcfgPcAnHc4MASE,125
|
33
|
-
hypern/database/nosql/__init__.py,sha256=MH9YvlbRlbBCrQVNOdfTaK-hINwJxbJLmxwY9Mei7I8,644
|
34
|
-
hypern/database/sql/field.py,sha256=gV9u_BvMIoxoDT3_J7sL5XJNa5XFsAO9w324ThwHbNs,9121
|
35
|
-
hypern/database/sql/migrate.py,sha256=BTtAs3-iMyMDzIWl6B3rM9sj7XGggLDRjD0h_WgGPtc,9742
|
36
|
-
hypern/database/sql/model.py,sha256=C8_rJA1Adw1yPWthjmAGh26hjTBuwwlEdtH45ADxvL0,4044
|
37
|
-
hypern/database/sql/query.py,sha256=En19t27zt6iUDQbFgO_wLEWPQCkPeBuH3s37fzlhMVc,33345
|
38
|
-
hypern/database/sql/__init__.py,sha256=dbSAz2nP0DPKK4Bb_jJdObSaSYQfgZ8D4U1TJdc4e7c,645
|
16
|
+
hypern/database/sqlalchemy/config.py,sha256=3BKY8YoHKEvwdDulp1que6pqmyyzX3CgfrQHtDHZWeE,2646
|
17
|
+
hypern/database/sqlalchemy/repository.py,sha256=ue6vWOTrnEPyDevlyh3v-7PU6GSfrZHYKrbXVuoS8UA,9516
|
18
|
+
hypern/database/sqlalchemy/__init__.py,sha256=xOMrzdPyJBzwaZifxYg2XD3TSorCOBJOIKYbnbESTuo,154
|
19
|
+
hypern/database/sqlx/field.py,sha256=gV9u_BvMIoxoDT3_J7sL5XJNa5XFsAO9w324ThwHbNs,9121
|
20
|
+
hypern/database/sqlx/migrate.py,sha256=BTtAs3-iMyMDzIWl6B3rM9sj7XGggLDRjD0h_WgGPtc,9742
|
21
|
+
hypern/database/sqlx/model.py,sha256=C8_rJA1Adw1yPWthjmAGh26hjTBuwwlEdtH45ADxvL0,4044
|
22
|
+
hypern/database/sqlx/query.py,sha256=db4gKmw_Lvud2xUe97Kec886vLVMRkjoqFasSHxZMiA,33346
|
23
|
+
hypern/database/sqlx/__init__.py,sha256=dbSAz2nP0DPKK4Bb_jJdObSaSYQfgZ8D4U1TJdc4e7c,645
|
39
24
|
hypern/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
25
|
hypern/datastructures.py,sha256=Pxr9KsZZTFfp0KC1-A4v5AkQfmrUyvVwxKuToQUOLoE,882
|
41
26
|
hypern/enum.py,sha256=KcVziJj7vWvyie0r2rtxhrLzdtkZAsf0DY58oJ4tQl4,360
|
@@ -50,7 +35,7 @@ hypern/gateway/gateway.py,sha256=26K2qvJUR-0JnN4IlhwvSSt7EYcpYrBVDuzZ1ivQQ34,147
|
|
50
35
|
hypern/gateway/proxy.py,sha256=w1wcTplDnVrfjn7hb0M0yBVth5TGl88irF-MUYHysQQ,2463
|
51
36
|
hypern/gateway/service.py,sha256=PkRaM08olqM_j_4wRjEJCR8X8ZysAF2WOcfhWjaX2eo,1701
|
52
37
|
hypern/gateway/__init__.py,sha256=TpFWtqnJerW1-jCWq5fjypJcw9Y6ytyrkvkzby1Eg0E,235
|
53
|
-
hypern/hypern.pyi,sha256=
|
38
|
+
hypern/hypern.pyi,sha256=C_RAN4E9m4FFGGTd3k6JVi59ORl_knLQtIHM1wqDlBg,9051
|
54
39
|
hypern/i18n/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
40
|
hypern/logging/logger.py,sha256=WACam_IJiCMXX0hGVKMGSxUQpY4DgAXy7M1dD3q-Z9s,3256
|
56
41
|
hypern/logging/__init__.py,sha256=6eVriyncsJ4J73fGYhoejv9MX7aGTkRezTpPxO4DX1I,52
|
@@ -77,7 +62,6 @@ hypern/routing/queue.py,sha256=NtFBbogU22ddyyX-CuQMip1XFDPZdMCVMIeUCQ-CR6Y,7176
|
|
77
62
|
hypern/routing/route.py,sha256=kan47-UeL-OPwcpp0rEhmBaaum6hN7FUj13Y8pZDEYA,10256
|
78
63
|
hypern/routing/__init__.py,sha256=U4xW5fDRsn03z4cVLT4dJHHGGU6SVxyv2DL86LXodeE,162
|
79
64
|
hypern/scheduler.py,sha256=-k3tW2AGCnHYSthKXk-FOs_SCtWp3yIxQzwzUJMJsbo,67
|
80
|
-
hypern/security.py,sha256=3E86Yp_eOSVa1emUvBrDgoF0Sn6eNX0CfLnt87w5CPI,1773
|
81
65
|
hypern/worker.py,sha256=ksJW8jWQg3HbIYnIZ5qdAmO-yh5hLpwvTT3dKkHR4Eo,9761
|
82
66
|
hypern/ws/channel.py,sha256=0ns2qmeoFJOpGLXS_hqldhywDQm_DxHwj6KloQx4Q3I,3183
|
83
67
|
hypern/ws/heartbeat.py,sha256=sWMXzQm6cbDHHA2NHc-gFjv7G_E56XtxswHQ93_BueM,2861
|
@@ -85,5 +69,5 @@ hypern/ws/room.py,sha256=0_L6Nun0n007F0rfNY8yX5x_A8EuXuI67JqpMkJ4RNI,2598
|
|
85
69
|
hypern/ws/route.py,sha256=fGQ2RC708MPOiiIHPUo8aZ-oK379TTAyQYm4htNA5jM,803
|
86
70
|
hypern/ws/__init__.py,sha256=dhRoRY683_rfPfSPM5qUczfTuyYDeuLOCFxY4hIdKt8,131
|
87
71
|
hypern/__init__.py,sha256=p3AtJQbsPs1RYEiN1thxH-k5UP8FfLeiJSoP0Vt3MDg,639
|
88
|
-
hypern/hypern.cp311-win32.pyd,sha256=
|
89
|
-
hypern-0.3.
|
72
|
+
hypern/hypern.cp311-win32.pyd,sha256=t6KXHHYeTGv5Btug8BJONgd6rXRq9hE5JNdEqWTH5HQ,8248320
|
73
|
+
hypern-0.3.9.dist-info/RECORD,,
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
from .ts_vector import TSVector
|
3
|
-
from .datetime import DatetimeType
|
4
|
-
from .password import PasswordType
|
5
|
-
from .encrypted import StringEncryptType, LargeBinaryEncryptType, AESEngine
|
6
|
-
|
7
|
-
__all__ = [
|
8
|
-
"TSVector",
|
9
|
-
"DatetimeType",
|
10
|
-
"PasswordType",
|
11
|
-
"StringEncryptType",
|
12
|
-
"LargeBinaryEncryptType",
|
13
|
-
"AESEngine",
|
14
|
-
]
|
@@ -1,16 +0,0 @@
|
|
1
|
-
import re
|
2
|
-
|
3
|
-
from sqlalchemy.types import String, TypeDecorator
|
4
|
-
|
5
|
-
|
6
|
-
class ColorField(TypeDecorator):
|
7
|
-
impl = String
|
8
|
-
|
9
|
-
def process_bind_param(self, value, dialect):
|
10
|
-
color_regex = r"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
|
11
|
-
if not re.match(color_regex, value):
|
12
|
-
raise ValueError("Invalid color format. Use hexadecimal color codes (e.g., #FF0000)")
|
13
|
-
return value
|
14
|
-
|
15
|
-
def process_result_value(self, value, dialect):
|
16
|
-
return value
|
@@ -1,23 +0,0 @@
|
|
1
|
-
from datetime import datetime
|
2
|
-
|
3
|
-
from sqlalchemy.dialects.postgresql import DATERANGE
|
4
|
-
from sqlalchemy.types import TypeDecorator
|
5
|
-
|
6
|
-
|
7
|
-
class DateRangeField(TypeDecorator):
|
8
|
-
impl = DATERANGE
|
9
|
-
|
10
|
-
def process_bind_param(self, value, dialect):
|
11
|
-
if value is None:
|
12
|
-
return None
|
13
|
-
elif "start" in value and "end" in value:
|
14
|
-
return f"['{value['start']}', '{value['end']}']"
|
15
|
-
else:
|
16
|
-
raise ValueError('DateRangeField must be a dictionary with "start" and "end" keys')
|
17
|
-
|
18
|
-
def process_result_value(self, value, dialect):
|
19
|
-
if value is None:
|
20
|
-
return None
|
21
|
-
else:
|
22
|
-
start, end = value[1:-1].split(",")
|
23
|
-
return {"start": datetime.strptime(start.strip("'"), "%Y-%m-%d %H:%M:%S.%f"), "end": datetime.strptime(end.strip("'"), "%Y-%m-%d %H:%M:%S.%f")}
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
from sqlalchemy import types
|
3
|
-
|
4
|
-
|
5
|
-
class DatetimeType(types.TypeDecorator):
|
6
|
-
impl = types.DateTime
|
7
|
-
cache_ok = True
|
8
|
-
|
9
|
-
def load_dialect_impl(self, dialect):
|
10
|
-
if dialect.name == "sqlite":
|
11
|
-
return dialect.type_descriptor(types.TEXT)
|
12
|
-
return dialect.type_descriptor(self.impl)
|
13
|
-
|
14
|
-
def process_bind_param(self, value, dialect):
|
15
|
-
if dialect.name == "sqlite":
|
16
|
-
return value.isoformat()
|
17
|
-
return value
|
18
|
-
|
19
|
-
def process_result_value(self, value, dialect):
|
20
|
-
if dialect.name != "sqlite":
|
21
|
-
return value.timestamp()
|
22
|
-
return value
|
@@ -1,58 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
import os
|
3
|
-
import typing
|
4
|
-
|
5
|
-
from cryptography.hazmat.primitives import padding
|
6
|
-
from sqlalchemy.types import LargeBinary, String, TypeDecorator
|
7
|
-
|
8
|
-
from hypern.security import AESEngine, EDEngine
|
9
|
-
|
10
|
-
|
11
|
-
class StringEncryptType(TypeDecorator):
|
12
|
-
impl = String
|
13
|
-
cache_ok = True
|
14
|
-
|
15
|
-
def __init__(self, engine: typing.Optional[EDEngine] = None, *args, **kwargs) -> None:
|
16
|
-
super().__init__(*args, **kwargs)
|
17
|
-
|
18
|
-
if not engine:
|
19
|
-
key = os.urandom(32)
|
20
|
-
iv = os.urandom(16)
|
21
|
-
padding_class = padding.PKCS7
|
22
|
-
self.engine = AESEngine(secret_key=key, iv=iv, padding_class=padding_class)
|
23
|
-
else:
|
24
|
-
self.engine = engine # type: ignore
|
25
|
-
|
26
|
-
def process_bind_param(self, value, dialect):
|
27
|
-
if value is None:
|
28
|
-
return value
|
29
|
-
if not isinstance(value, str):
|
30
|
-
raise ValueError("Value String Encrypt Type must be a string")
|
31
|
-
return self.engine.encrypt(value).decode(encoding="utf-8")
|
32
|
-
|
33
|
-
def process_result_value(self, value, dialect):
|
34
|
-
if value is None:
|
35
|
-
return value
|
36
|
-
return self.engine.decrypt(value)
|
37
|
-
|
38
|
-
|
39
|
-
class LargeBinaryEncryptType(StringEncryptType):
|
40
|
-
impl = LargeBinary
|
41
|
-
cache_ok = True
|
42
|
-
|
43
|
-
def __init__(self, engine: typing.Optional[EDEngine] = None, *args, **kwargs) -> None:
|
44
|
-
super().__init__(engine=engine, *args, **kwargs) # type: ignore
|
45
|
-
|
46
|
-
def process_bind_param(self, value, dialect):
|
47
|
-
if value is None:
|
48
|
-
return value
|
49
|
-
value = super().process_bind_param(value, dialect)
|
50
|
-
if isinstance(value, str):
|
51
|
-
return value.encode("utf-8")
|
52
|
-
return value
|
53
|
-
|
54
|
-
def process_result_value(self, value, dialect):
|
55
|
-
if isinstance(value, bytes):
|
56
|
-
value = value.decode("utf-8")
|
57
|
-
return super().process_result_value(value, dialect)
|
58
|
-
return value
|
@@ -1,171 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
|
3
|
-
import weakref
|
4
|
-
|
5
|
-
import passlib
|
6
|
-
from passlib.context import LazyCryptContext
|
7
|
-
from sqlalchemy import types
|
8
|
-
from sqlalchemy.dialects import oracle, postgresql, sqlite
|
9
|
-
from sqlalchemy.ext.mutable import Mutable
|
10
|
-
|
11
|
-
|
12
|
-
class Password(Mutable):
|
13
|
-
@classmethod
|
14
|
-
def coerce(cls, key, value):
|
15
|
-
if isinstance(value, Password):
|
16
|
-
return value
|
17
|
-
|
18
|
-
if isinstance(value, (str, bytes)):
|
19
|
-
return cls(value, secret=True)
|
20
|
-
|
21
|
-
super().coerce(key, value)
|
22
|
-
|
23
|
-
def __init__(self, value, context=None, secret=False):
|
24
|
-
# Store the hash (if it is one).
|
25
|
-
self.hash = value if not secret else None
|
26
|
-
|
27
|
-
# Store the secret if we have one.
|
28
|
-
self.secret = value if secret else None
|
29
|
-
|
30
|
-
# The hash should be bytes.
|
31
|
-
if isinstance(self.hash, str):
|
32
|
-
self.hash = self.hash.encode("utf8")
|
33
|
-
|
34
|
-
# Save weakref of the password context (if we have one)
|
35
|
-
self.context = weakref.proxy(context) if context is not None else None
|
36
|
-
|
37
|
-
def __eq__(self, value):
|
38
|
-
if self.hash is None or value is None:
|
39
|
-
# Ensure that we don't continue comparison if one of us is None.
|
40
|
-
return self.hash is value
|
41
|
-
|
42
|
-
if isinstance(value, Password):
|
43
|
-
# Comparing 2 hashes isn't very useful; but this equality
|
44
|
-
# method breaks otherwise.
|
45
|
-
return value.hash == self.hash
|
46
|
-
|
47
|
-
if self.context is None:
|
48
|
-
# Compare 2 hashes again as we don't know how to validate.
|
49
|
-
return value == self
|
50
|
-
|
51
|
-
if isinstance(value, (str, bytes)):
|
52
|
-
valid, new = self.context.verify_and_update(value, self.hash)
|
53
|
-
if valid and new:
|
54
|
-
# New hash was calculated due to various reasons; stored one
|
55
|
-
# wasn't optimal, etc.
|
56
|
-
self.hash = new
|
57
|
-
|
58
|
-
# The hash should be bytes.
|
59
|
-
if isinstance(self.hash, str):
|
60
|
-
self.hash = self.hash.encode("utf8")
|
61
|
-
self.changed()
|
62
|
-
|
63
|
-
return valid
|
64
|
-
|
65
|
-
return False
|
66
|
-
|
67
|
-
def __ne__(self, value):
|
68
|
-
return self != value
|
69
|
-
|
70
|
-
|
71
|
-
class PasswordType(types.TypeDecorator):
|
72
|
-
impl = types.String
|
73
|
-
cache_ok = True
|
74
|
-
|
75
|
-
def __init__(self, max_length=None, **kwargs):
|
76
|
-
# Fail if passlib is not found.
|
77
|
-
if passlib is None:
|
78
|
-
raise ImportError("'passlib' is required to use 'PasswordType'")
|
79
|
-
|
80
|
-
# Construct the passlib crypt context.
|
81
|
-
self.context = LazyCryptContext(**kwargs)
|
82
|
-
self._max_length = max_length
|
83
|
-
|
84
|
-
@property
|
85
|
-
def hashing_method(self):
|
86
|
-
return "hash" if hasattr(self.context, "hash") else "encrypt"
|
87
|
-
|
88
|
-
@property
|
89
|
-
def max_length(self):
|
90
|
-
"""Get column length."""
|
91
|
-
if self._max_length is None:
|
92
|
-
self._max_length = self.calculate_max_length()
|
93
|
-
|
94
|
-
return self._max_length
|
95
|
-
|
96
|
-
def calculate_max_length(self):
|
97
|
-
# Calculate the largest possible encoded password.
|
98
|
-
# name + rounds + salt + hash + ($ * 4) of largest hash
|
99
|
-
max_lengths = [1024]
|
100
|
-
for name in self.context.schemes():
|
101
|
-
scheme = getattr(__import__("passlib.hash").hash, name)
|
102
|
-
length = 4 + len(scheme.name)
|
103
|
-
length += len(str(getattr(scheme, "max_rounds", "")))
|
104
|
-
length += getattr(scheme, "max_salt_size", 0) or 0
|
105
|
-
length += getattr(scheme, "encoded_checksum_size", scheme.checksum_size)
|
106
|
-
max_lengths.append(length)
|
107
|
-
|
108
|
-
# Return the maximum calculated max length.
|
109
|
-
return max(max_lengths)
|
110
|
-
|
111
|
-
def load_dialect_impl(self, dialect):
|
112
|
-
if dialect.name == "postgresql":
|
113
|
-
# Use a BYTEA type for postgresql.
|
114
|
-
impl = postgresql.BYTEA(self.max_length)
|
115
|
-
elif dialect.name == "oracle":
|
116
|
-
# Use a RAW type for oracle.
|
117
|
-
impl = oracle.RAW(self.max_length)
|
118
|
-
elif dialect.name == "sqlite":
|
119
|
-
# Use a BLOB type for sqlite
|
120
|
-
impl = sqlite.BLOB(self.max_length)
|
121
|
-
else:
|
122
|
-
# Use a VARBINARY for all other dialects.
|
123
|
-
impl = types.VARBINARY(self.max_length)
|
124
|
-
return dialect.type_descriptor(impl)
|
125
|
-
|
126
|
-
def process_bind_param(self, value, dialect):
|
127
|
-
if isinstance(value, Password):
|
128
|
-
# If were given a password secret; hash it.
|
129
|
-
if value.secret is not None:
|
130
|
-
return self._hash(value.secret).encode("utf8")
|
131
|
-
|
132
|
-
# Value has already been hashed.
|
133
|
-
return value.hash
|
134
|
-
|
135
|
-
if isinstance(value, str):
|
136
|
-
# Assume value has not been hashed.
|
137
|
-
return self._hash(value).encode("utf8")
|
138
|
-
|
139
|
-
def process_result_value(self, value, dialect):
|
140
|
-
if value is not None:
|
141
|
-
return Password(value, self.context)
|
142
|
-
|
143
|
-
def _hash(self, value):
|
144
|
-
return getattr(self.context, self.hashing_method)(value)
|
145
|
-
|
146
|
-
def _coerce(self, value):
|
147
|
-
if value is None:
|
148
|
-
return
|
149
|
-
|
150
|
-
if not isinstance(value, Password):
|
151
|
-
# Hash the password using the default scheme.
|
152
|
-
value = self._hash(value).encode("utf8")
|
153
|
-
return Password(value, context=self.context)
|
154
|
-
|
155
|
-
else:
|
156
|
-
# If were given a password object; ensure the context is right.
|
157
|
-
value.context = weakref.proxy(self.context)
|
158
|
-
|
159
|
-
# If were given a password secret; hash it.
|
160
|
-
if value.secret is not None:
|
161
|
-
value.hash = self._hash(value.secret).encode("utf8")
|
162
|
-
value.secret = None
|
163
|
-
|
164
|
-
return value
|
165
|
-
|
166
|
-
@property
|
167
|
-
def python_type(self):
|
168
|
-
return self.impl.type.python_type
|
169
|
-
|
170
|
-
|
171
|
-
Password.associate_with(PasswordType)
|
@@ -1,46 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
|
3
|
-
import sqlalchemy as sa
|
4
|
-
from sqlalchemy.dialects.postgresql import TSVECTOR
|
5
|
-
|
6
|
-
|
7
|
-
class TSVector(sa.types.TypeDecorator):
|
8
|
-
"""
|
9
|
-
.. _TSVECTOR:
|
10
|
-
https://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#full-text-search
|
11
|
-
|
12
|
-
|
13
|
-
class IndexModel(Model):
|
14
|
-
....
|
15
|
-
search_vector = Column(
|
16
|
-
TSVector(),
|
17
|
-
Computed(
|
18
|
-
"to_tsvector('english', some_text|| ' ' ||some_text)",
|
19
|
-
persisted=True,
|
20
|
-
),
|
21
|
-
)
|
22
|
-
session.query(IndexModel).filter(IndexModel.search_vector.match('foo'))
|
23
|
-
|
24
|
-
session.query(IndexModel).filter(
|
25
|
-
(IndexModel.name_vector | IndexModel.content_vector).match('foo')
|
26
|
-
)
|
27
|
-
|
28
|
-
"""
|
29
|
-
|
30
|
-
impl = TSVECTOR
|
31
|
-
cache_ok = True
|
32
|
-
|
33
|
-
class comparator_factory(TSVECTOR.Comparator):
|
34
|
-
def match(self, other, **kwargs):
|
35
|
-
if "postgresql_regconfig" not in kwargs:
|
36
|
-
if "regconfig" in self.type.options:
|
37
|
-
kwargs["postgresql_regconfig"] = self.type.options["regconfig"]
|
38
|
-
return TSVECTOR.Comparator.match(self, other, **kwargs)
|
39
|
-
|
40
|
-
def __or__(self, other):
|
41
|
-
return self.op("||")(other)
|
42
|
-
|
43
|
-
def __init__(self, *args, **kwargs):
|
44
|
-
self.columns = args
|
45
|
-
self.options = kwargs
|
46
|
-
super(TSVector, self).__init__()
|
@@ -1,15 +0,0 @@
|
|
1
|
-
from sqlalchemy.types import TypeDecorator, Unicode
|
2
|
-
|
3
|
-
|
4
|
-
class UnicodeField(TypeDecorator):
|
5
|
-
impl = Unicode
|
6
|
-
|
7
|
-
def process_bind_param(self, value, dialect):
|
8
|
-
try:
|
9
|
-
value.encode("utf-8")
|
10
|
-
except UnicodeEncodeError:
|
11
|
-
raise ValueError("Value must be valid Unicode")
|
12
|
-
return value
|
13
|
-
|
14
|
-
def process_result_value(self, value, dialect):
|
15
|
-
return value
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
from typing import List, TypedDict
|
3
|
-
|
4
|
-
from uuid import uuid4
|
5
|
-
from mongoengine import connect
|
6
|
-
|
7
|
-
|
8
|
-
class TypedDictModel(TypedDict):
|
9
|
-
host: str
|
10
|
-
alias: str
|
11
|
-
|
12
|
-
|
13
|
-
class NoSqlConfig:
|
14
|
-
def __init__(self, dbs_config: List[TypedDictModel]):
|
15
|
-
self.dbs_config = dbs_config
|
16
|
-
|
17
|
-
def _connect_db(self, db_config: TypedDictModel):
|
18
|
-
_alias = db_config.get("alias", str(uuid4()))
|
19
|
-
connect(host=db_config["host"], alias=_alias)
|
20
|
-
|
21
|
-
def init_app(self, app):
|
22
|
-
self.app = app # noqa
|
23
|
-
# connect
|
24
|
-
for db_config in self.dbs_config:
|
25
|
-
self._connect_db(db_config)
|
@@ -1,16 +0,0 @@
|
|
1
|
-
from mongoengine import BaseField
|
2
|
-
import re
|
3
|
-
|
4
|
-
|
5
|
-
class ColorField(BaseField):
|
6
|
-
def validate(self, value):
|
7
|
-
color_regex = r"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
|
8
|
-
if not re.match(color_regex, value):
|
9
|
-
self.error("Invalid color format. Use hexadecimal color codes (e.g., #FF0000)")
|
10
|
-
return True
|
11
|
-
|
12
|
-
def to_mongo(self, value):
|
13
|
-
return value
|
14
|
-
|
15
|
-
def to_python(self, value):
|
16
|
-
return value
|
@@ -1,30 +0,0 @@
|
|
1
|
-
from mongoengine import BaseField
|
2
|
-
from datetime import datetime
|
3
|
-
|
4
|
-
|
5
|
-
class DateRangeField(BaseField):
|
6
|
-
def __init__(self, **kwargs):
|
7
|
-
super(DateRangeField, self).__init__(**kwargs)
|
8
|
-
|
9
|
-
def validate(self, value):
|
10
|
-
if not isinstance(value, dict) or "start" not in value or "end" not in value:
|
11
|
-
self.error('DateRangeField must be a dictionary with "start" and "end" keys')
|
12
|
-
# Use get to safely access keys
|
13
|
-
start = value.get("start")
|
14
|
-
end = value.get("end")
|
15
|
-
# Check if both "start" and "end" are present
|
16
|
-
if start is None or end is None:
|
17
|
-
self.error('DateRangeField must contain both "start" and "end" keys')
|
18
|
-
|
19
|
-
# Check if "start" and "end" are datetime objects
|
20
|
-
if not isinstance(value["start"], datetime) or not isinstance(value["end"], datetime):
|
21
|
-
self.error('DateRangeField "start" and "end" must be datetime objects')
|
22
|
-
if value["start"] > value["end"]:
|
23
|
-
self.error('DateRangeField "start" must be earlier than "end"')
|
24
|
-
return True
|
25
|
-
|
26
|
-
def to_mongo(self, value):
|
27
|
-
return value
|
28
|
-
|
29
|
-
def to_python(self, value):
|
30
|
-
return value
|
@@ -1,53 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
from typing import Any, Optional
|
3
|
-
from mongoengine.base import BaseField
|
4
|
-
|
5
|
-
from cryptography.hazmat.primitives import padding
|
6
|
-
|
7
|
-
from hypern.security import EDEngine, AESEngine
|
8
|
-
|
9
|
-
|
10
|
-
class EncryptedField(BaseField):
|
11
|
-
"""
|
12
|
-
A custom MongoEngine field that encrypts data using AES-256-CBC.
|
13
|
-
|
14
|
-
The field automatically handles encryption when saving to MongoDB and
|
15
|
-
decryption when retrieving data.
|
16
|
-
|
17
|
-
Attributes:
|
18
|
-
engine: Encryption engine to use. If not provided, will use AES-256-CBC
|
19
|
-
"""
|
20
|
-
|
21
|
-
def __init__(self, engine: Optional[EDEngine] = None, **kwargs):
|
22
|
-
if not engine:
|
23
|
-
key = os.urandom(32)
|
24
|
-
iv = os.urandom(16)
|
25
|
-
padding_class = padding.PKCS7
|
26
|
-
self.engine = AESEngine(secret_key=key, iv=iv, padding_class=padding_class)
|
27
|
-
else:
|
28
|
-
self.engine = engine # type: ignore
|
29
|
-
super(EncryptedField, self).__init__(**kwargs)
|
30
|
-
|
31
|
-
def to_mongo(self, value: Any) -> Optional[str]:
|
32
|
-
"""Convert a Python object to a MongoDB-compatible format."""
|
33
|
-
if value is None:
|
34
|
-
return None
|
35
|
-
return self.engine.encrypt(value)
|
36
|
-
|
37
|
-
def to_python(self, value: Optional[str]) -> Optional[str]:
|
38
|
-
"""Convert a MongoDB-compatible format to a Python object."""
|
39
|
-
if value is None:
|
40
|
-
return None
|
41
|
-
if isinstance(value, bytes):
|
42
|
-
return self.engine.decrypt(value)
|
43
|
-
return value
|
44
|
-
|
45
|
-
def prepare_query_value(self, op, value: Any) -> Optional[str]:
|
46
|
-
"""Prepare a value used in a query."""
|
47
|
-
if value is None:
|
48
|
-
return None
|
49
|
-
|
50
|
-
if op in ("set", "upsert"):
|
51
|
-
return self.to_mongo(value)
|
52
|
-
|
53
|
-
return value
|
@@ -1,134 +0,0 @@
|
|
1
|
-
from mongoengine.base import BaseField
|
2
|
-
import weakref
|
3
|
-
from passlib.context import CryptContext
|
4
|
-
import re
|
5
|
-
from typing import Optional, Any
|
6
|
-
|
7
|
-
|
8
|
-
class PasswordField(BaseField):
|
9
|
-
"""
|
10
|
-
A custom password field using passlib for hashing and weakref for reference management.
|
11
|
-
Supports multiple hashing schemes and automatic upgrade of hash algorithms.
|
12
|
-
"""
|
13
|
-
|
14
|
-
# Class-level password context - shared across all instances
|
15
|
-
pwd_context = CryptContext(
|
16
|
-
# List of hashing schemes in order of preference
|
17
|
-
schemes=["argon2", "pbkdf2_sha256", "bcrypt_sha256"],
|
18
|
-
# Mark argon2 as default
|
19
|
-
default="argon2",
|
20
|
-
# Argon2 parameters
|
21
|
-
argon2__rounds=4,
|
22
|
-
argon2__memory_cost=65536,
|
23
|
-
argon2__parallelism=2,
|
24
|
-
# PBKDF2 parameters
|
25
|
-
pbkdf2_sha256__rounds=29000,
|
26
|
-
)
|
27
|
-
|
28
|
-
def __init__(
|
29
|
-
self,
|
30
|
-
min_length: int = 8,
|
31
|
-
require_number: bool = False,
|
32
|
-
require_special: bool = False,
|
33
|
-
require_uppercase: bool = False,
|
34
|
-
require_lowercase: bool = False,
|
35
|
-
**kwargs,
|
36
|
-
):
|
37
|
-
"""
|
38
|
-
Initialize the password field with validation rules.
|
39
|
-
|
40
|
-
Args:
|
41
|
-
min_length: Minimum password length
|
42
|
-
require_number: Require at least one number
|
43
|
-
require_special: Require at least one special character
|
44
|
-
require_uppercase: Require at least one uppercase letter
|
45
|
-
require_lowercase: Require at least one lowercase letter
|
46
|
-
"""
|
47
|
-
self.min_length = min_length
|
48
|
-
self.require_number = require_number
|
49
|
-
self.require_special = require_special
|
50
|
-
self.require_uppercase = require_uppercase
|
51
|
-
self.require_lowercase = require_lowercase
|
52
|
-
|
53
|
-
# Use weakref to store references to parent documents
|
54
|
-
self.instances = weakref.WeakKeyDictionary()
|
55
|
-
|
56
|
-
kwargs["required"] = True
|
57
|
-
super(PasswordField, self).__init__(**kwargs)
|
58
|
-
|
59
|
-
def validate_password(self, password: str) -> tuple[bool, str]:
|
60
|
-
"""Validate password strength."""
|
61
|
-
|
62
|
-
if len(password) < self.min_length:
|
63
|
-
return False, f"Password must be at least {self.min_length} characters long"
|
64
|
-
|
65
|
-
if self.require_number and not re.search(r"\d", password):
|
66
|
-
return False, "Password must contain at least one number"
|
67
|
-
|
68
|
-
if self.require_special and not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password):
|
69
|
-
return False, "Password must contain at least one special character"
|
70
|
-
|
71
|
-
if self.require_uppercase and not re.search(r"[A-Z]", password):
|
72
|
-
return False, "Password must contain at least one uppercase letter"
|
73
|
-
|
74
|
-
if self.require_lowercase and not re.search(r"[a-z]", password):
|
75
|
-
return False, "Password must contain at least one lowercase letter"
|
76
|
-
|
77
|
-
return True, ""
|
78
|
-
|
79
|
-
def hash_password(self, password: str) -> str:
|
80
|
-
"""Hash password using the configured passlib context."""
|
81
|
-
return self.pwd_context.hash(password)
|
82
|
-
|
83
|
-
def verify_password(self, password: str, hash: str) -> tuple[bool, Optional[str]]:
|
84
|
-
"""
|
85
|
-
Verify password and return tuple of (is_valid, new_hash).
|
86
|
-
new_hash is provided if the hash needs to be upgraded.
|
87
|
-
"""
|
88
|
-
try:
|
89
|
-
is_valid = self.pwd_context.verify(password, hash)
|
90
|
-
# Check if the hash needs to be upgraded
|
91
|
-
if is_valid and self.pwd_context.needs_update(hash):
|
92
|
-
return True, self.hash_password(password)
|
93
|
-
return is_valid, None
|
94
|
-
except Exception:
|
95
|
-
return False, None
|
96
|
-
|
97
|
-
def __get__(self, instance, owner):
|
98
|
-
"""Custom getter using weakref."""
|
99
|
-
if instance is None:
|
100
|
-
return self
|
101
|
-
return self.instances.get(instance)
|
102
|
-
|
103
|
-
def __set__(self, instance, value):
|
104
|
-
"""Custom setter using weakref."""
|
105
|
-
if value and isinstance(value, str):
|
106
|
-
# Validate and hash new password
|
107
|
-
is_valid, error = self.validate_password(value)
|
108
|
-
if not is_valid:
|
109
|
-
raise ValueError(error)
|
110
|
-
hashed = self.hash_password(value)
|
111
|
-
self.instances[instance] = hashed
|
112
|
-
instance._data[self.name] = hashed
|
113
|
-
else:
|
114
|
-
# If it's already hashed or None
|
115
|
-
self.instances[instance] = value
|
116
|
-
instance._data[self.name] = value
|
117
|
-
|
118
|
-
def to_mongo(self, value: str) -> Optional[str]:
|
119
|
-
"""Convert to MongoDB-compatible value."""
|
120
|
-
if value is None:
|
121
|
-
return None
|
122
|
-
return self.hash_password(value)
|
123
|
-
|
124
|
-
def to_python(self, value: str) -> str:
|
125
|
-
"""Convert from MongoDB to Python."""
|
126
|
-
return value
|
127
|
-
|
128
|
-
def prepare_query_value(self, op, value: Any) -> Optional[str]:
|
129
|
-
"""Prepare value for database operations."""
|
130
|
-
if value is None:
|
131
|
-
return None
|
132
|
-
if op == "exact":
|
133
|
-
return self.hash_password(value)
|
134
|
-
return value
|
hypern/security.py
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
from abc import ABC, abstractmethod
|
3
|
-
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
4
|
-
from cryptography.hazmat.backends import default_backend
|
5
|
-
from base64 import b64encode, b64decode
|
6
|
-
|
7
|
-
import typing
|
8
|
-
|
9
|
-
|
10
|
-
class EDEngine(ABC):
|
11
|
-
@abstractmethod
|
12
|
-
def encrypt(self, data: str) -> str:
|
13
|
-
raise NotImplementedError("Method not implemented")
|
14
|
-
|
15
|
-
@abstractmethod
|
16
|
-
def decrypt(self, data: str) -> str:
|
17
|
-
raise NotImplementedError("Method not implemented")
|
18
|
-
|
19
|
-
|
20
|
-
class AESEngine(EDEngine):
|
21
|
-
def __init__(self, secret_key: bytes, iv: bytes, padding_class: typing.Type) -> None:
|
22
|
-
super().__init__()
|
23
|
-
self.secret_key = secret_key
|
24
|
-
self.iv = iv
|
25
|
-
self.padding = padding_class(128)
|
26
|
-
|
27
|
-
def encrypt(self, data: str) -> bytes:
|
28
|
-
bytes_data = data.encode("utf-8")
|
29
|
-
encryptor = Cipher(algorithms.AES(self.secret_key), modes.GCM(self.iv), backend=default_backend()).encryptor()
|
30
|
-
padder = self.padding.padder()
|
31
|
-
padded_data = padder.update(bytes_data) + padder.finalize()
|
32
|
-
enctyped_data = encryptor.update(padded_data) + encryptor.finalize()
|
33
|
-
tag = encryptor.tag
|
34
|
-
return b64encode(tag + enctyped_data)
|
35
|
-
|
36
|
-
def decrypt(self, data: bytes) -> str:
|
37
|
-
data = b64decode(data)
|
38
|
-
tag = data[:16]
|
39
|
-
encrypted_data = data[16:]
|
40
|
-
decryptor = Cipher(algorithms.AES(self.secret_key), modes.GCM(self.iv, tag), backend=default_backend()).decryptor()
|
41
|
-
unpadder = self.padding.unpadder()
|
42
|
-
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
|
43
|
-
unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
|
44
|
-
return unpadded_data.decode("utf-8")
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|