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.
@@ -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.59.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=dTx7Cqpd56NH32-Ndc6vo1WnzsN1C9BfpFrchV9NTdQ,66366
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-0.59.0.dist-info/METADATA,sha256=811kowZZQ5K40LdVRA5JeLk95TBj5MHY6UGWn91g4Kg,852
15
- sweatstack-0.59.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
- sweatstack-0.59.0.dist-info/entry_points.txt,sha256=kCzOUQI3dqbTpEYqtgYDeiKFaqaA7BMlV6D24BMzCFU,208
17
- sweatstack-0.59.0.dist-info/RECORD,,
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,,