sweatstack 0.59.0__py3-none-any.whl → 0.61.0__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.
- sweatstack/client.py +67 -27
- sweatstack/fastapi/__init__.py +82 -0
- sweatstack/fastapi/config.py +223 -0
- sweatstack/fastapi/dependencies.py +293 -0
- sweatstack/fastapi/models.py +109 -0
- sweatstack/fastapi/routes.py +312 -0
- sweatstack/fastapi/session.py +102 -0
- {sweatstack-0.59.0.dist-info → sweatstack-0.61.0.dist-info}/METADATA +4 -1
- {sweatstack-0.59.0.dist-info → sweatstack-0.61.0.dist-info}/RECORD +11 -5
- {sweatstack-0.59.0.dist-info → sweatstack-0.61.0.dist-info}/WHEEL +0 -0
- {sweatstack-0.59.0.dist-info → sweatstack-0.61.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Session encryption and cookie helpers."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from cryptography.fernet import Fernet, InvalidToken
|
|
8
|
+
from fastapi import Response
|
|
9
|
+
|
|
10
|
+
from .config import get_config
|
|
11
|
+
|
|
12
|
+
SESSION_COOKIE_NAME = "sweatstack_session"
|
|
13
|
+
STATE_COOKIE_NAME = "sweatstack_oauth_state"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_fernet_instances() -> list[Fernet]:
|
|
17
|
+
"""Get Fernet instances for encryption/decryption."""
|
|
18
|
+
config = get_config()
|
|
19
|
+
secrets = config.session_secret
|
|
20
|
+
if not isinstance(secrets, list):
|
|
21
|
+
secrets = [secrets]
|
|
22
|
+
return [Fernet(secret.get_secret_value().encode()) for secret in secrets]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def encrypt_session(data: dict[str, Any]) -> str:
|
|
26
|
+
"""Encrypt session data.
|
|
27
|
+
|
|
28
|
+
Uses the first configured key for encryption.
|
|
29
|
+
"""
|
|
30
|
+
fernets = _get_fernet_instances()
|
|
31
|
+
json_data = json.dumps(data)
|
|
32
|
+
return fernets[0].encrypt(json_data.encode()).decode()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def decrypt_session(encrypted: str | None) -> dict[str, Any] | None:
|
|
36
|
+
"""Decrypt session data.
|
|
37
|
+
|
|
38
|
+
Tries all configured keys for decryption (supports key rotation).
|
|
39
|
+
Returns None if decryption fails or data is missing.
|
|
40
|
+
"""
|
|
41
|
+
if not encrypted:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
fernets = _get_fernet_instances()
|
|
45
|
+
|
|
46
|
+
for fernet in fernets:
|
|
47
|
+
try:
|
|
48
|
+
decrypted = fernet.decrypt(encrypted.encode())
|
|
49
|
+
return json.loads(decrypted)
|
|
50
|
+
except InvalidToken:
|
|
51
|
+
continue
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logging.warning(f"Session decryption error: {e}")
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
# All keys failed
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def set_session_cookie(response: Response, session_data: dict[str, Any]) -> None:
|
|
61
|
+
"""Set the encrypted session cookie on the response."""
|
|
62
|
+
config = get_config()
|
|
63
|
+
encrypted = encrypt_session(session_data)
|
|
64
|
+
response.set_cookie(
|
|
65
|
+
key=SESSION_COOKIE_NAME,
|
|
66
|
+
value=encrypted,
|
|
67
|
+
httponly=True,
|
|
68
|
+
secure=config.cookie_secure,
|
|
69
|
+
samesite="lax",
|
|
70
|
+
max_age=config.cookie_max_age,
|
|
71
|
+
path="/",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def clear_session_cookie(response: Response) -> None:
|
|
76
|
+
"""Clear the session cookie."""
|
|
77
|
+
response.delete_cookie(
|
|
78
|
+
key=SESSION_COOKIE_NAME,
|
|
79
|
+
path="/",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def set_state_cookie(response: Response, state: str) -> None:
|
|
84
|
+
"""Set the OAuth state cookie (short-lived, 5 minutes)."""
|
|
85
|
+
config = get_config()
|
|
86
|
+
response.set_cookie(
|
|
87
|
+
key=STATE_COOKIE_NAME,
|
|
88
|
+
value=state,
|
|
89
|
+
httponly=True,
|
|
90
|
+
secure=config.cookie_secure,
|
|
91
|
+
samesite="lax",
|
|
92
|
+
max_age=300, # 5 minutes
|
|
93
|
+
path="/",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def clear_state_cookie(response: Response) -> None:
|
|
98
|
+
"""Clear the OAuth state cookie."""
|
|
99
|
+
response.delete_cookie(
|
|
100
|
+
key=STATE_COOKIE_NAME,
|
|
101
|
+
path="/",
|
|
102
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sweatstack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.61.0
|
|
4
4
|
Summary: The official Python client for SweatStack
|
|
5
5
|
Author-email: Aart Goossens <aart@gssns.io>
|
|
6
6
|
Requires-Python: >=3.9
|
|
@@ -10,6 +10,9 @@ Requires-Dist: pandas>=2.2.3
|
|
|
10
10
|
Requires-Dist: platformdirs>=4.0.0
|
|
11
11
|
Requires-Dist: pyarrow>=18.0.0
|
|
12
12
|
Requires-Dist: pydantic>=2.10.5
|
|
13
|
+
Provides-Extra: fastapi
|
|
14
|
+
Requires-Dist: cryptography>=41.0.0; extra == 'fastapi'
|
|
15
|
+
Requires-Dist: fastapi[standard]>=0.100.0; extra == 'fastapi'
|
|
13
16
|
Provides-Extra: jupyter
|
|
14
17
|
Requires-Dist: ipython>=8.31.0; extra == 'jupyter'
|
|
15
18
|
Requires-Dist: jupyterlab>=4.3.4; extra == 'jupyter'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
sweatstack/__init__.py,sha256=tiVfgKlswRPaDMEy0gA7u8rveqEYZTA_kyB9lJ3J6Sc,21
|
|
2
2
|
sweatstack/cli.py,sha256=N1NWOgEZR2yaJvIXxo9qvp_jFlypZYb0nujpbVNYQ6A,720
|
|
3
|
-
sweatstack/client.py,sha256=
|
|
3
|
+
sweatstack/client.py,sha256=0SipHY_USSMnxEb3PdmQ4ogUZTB81nC-eAJjthwqy6g,68175
|
|
4
4
|
sweatstack/constants.py,sha256=fGO6ksOv5HeISv9lHRoYm4besW1GTveXS8YD3K0ljg0,41
|
|
5
5
|
sweatstack/ipython_init.py,sha256=OtBB9dQvyLXklD4kA2x1swaVtU9u73fG4V4-zz4YRAg,139
|
|
6
6
|
sweatstack/jupyterlab_oauth2_startup.py,sha256=YcjXvzeZ459vL_dCkFi1IxX_RNAu80ZX9rwa0OXJfTM,1023
|
|
@@ -11,7 +11,13 @@ sweatstack/streamlit.py,sha256=wnabWhife9eMAdkECPjRKkzE82KZoi_H8YzucZl_m9s,19604
|
|
|
11
11
|
sweatstack/sweatshell.py,sha256=MYLNcWbOdceqKJ3S0Pe8dwHXEeYsGJNjQoYUXpMTftA,333
|
|
12
12
|
sweatstack/utils.py,sha256=AwHRdC1ziOZ5o9RBIB21Uxm-DoClVRAJSVvgsmSmvps,1801
|
|
13
13
|
sweatstack/Sweat Stack examples/Getting started.ipynb,sha256=k2hiSffWecoQ0VxjdpDcgFzBXDQiYEebhnAYlu8cgX8,6335204
|
|
14
|
-
sweatstack
|
|
15
|
-
sweatstack
|
|
16
|
-
sweatstack
|
|
17
|
-
sweatstack
|
|
14
|
+
sweatstack/fastapi/__init__.py,sha256=J20u-R3ABLP0vGLl3m_H76nTvpoMHtpKpyH8vufb9kM,2465
|
|
15
|
+
sweatstack/fastapi/config.py,sha256=S9Y5G5YprSugICtkCdVEUBwdbGsg2MuzdPx8QJaP8XA,7850
|
|
16
|
+
sweatstack/fastapi/dependencies.py,sha256=6QrWCcYJJXI0-Tn2A4hKimVMCa45rRUAu-gijtgAq4k,8739
|
|
17
|
+
sweatstack/fastapi/models.py,sha256=2VNKITN7LKacQxxVgYJjDaZ6Xq2eYBtvkQbq7H6bLlY,3386
|
|
18
|
+
sweatstack/fastapi/routes.py,sha256=Y-g8DMM2gG_8ETnLN7ZfUqBT8AkwIG9WFbEqJtyyKcM,10058
|
|
19
|
+
sweatstack/fastapi/session.py,sha256=BtRPCmIEaToJPwFyZ0fqWGlmnDHuWKy8nri9dJrPXaA,2717
|
|
20
|
+
sweatstack-0.61.0.dist-info/METADATA,sha256=RGfVVMy3zO08wiJw98gF-9IW_8gjuYsJ3Kg58BAJyi4,994
|
|
21
|
+
sweatstack-0.61.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
22
|
+
sweatstack-0.61.0.dist-info/entry_points.txt,sha256=kCzOUQI3dqbTpEYqtgYDeiKFaqaA7BMlV6D24BMzCFU,208
|
|
23
|
+
sweatstack-0.61.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|