vortex-python-sdk 0.0.2__py3-none-any.whl → 0.0.3__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.
- {vortex_python_sdk-0.0.2.dist-info → vortex_python_sdk-0.0.3.dist-info}/METADATA +4 -4
- vortex_python_sdk-0.0.3.dist-info/RECORD +9 -0
- vortex_sdk/__init__.py +1 -1
- vortex_sdk/types.py +2 -1
- vortex_sdk/vortex.py +74 -26
- vortex_python_sdk-0.0.2.dist-info/RECORD +0 -9
- {vortex_python_sdk-0.0.2.dist-info → vortex_python_sdk-0.0.3.dist-info}/WHEEL +0 -0
- {vortex_python_sdk-0.0.2.dist-info → vortex_python_sdk-0.0.3.dist-info}/licenses/LICENSE +0 -0
- {vortex_python_sdk-0.0.2.dist-info → vortex_python_sdk-0.0.3.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vortex-python-sdk
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Vortex Python SDK for invitation management and JWT generation
|
|
5
5
|
Author-email: TeamVortexSoftware <support@vortexsoftware.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -55,11 +55,11 @@ pip install vortex-python-sdk
|
|
|
55
55
|
```python
|
|
56
56
|
from vortex_sdk import Vortex
|
|
57
57
|
|
|
58
|
-
# Initialize the client
|
|
59
|
-
vortex = Vortex(api_key="your-api-key")
|
|
58
|
+
# Initialize the client with your Vortex API key
|
|
59
|
+
vortex = Vortex(api_key="your-vortex-api-key")
|
|
60
60
|
|
|
61
61
|
# Or with custom base URL
|
|
62
|
-
vortex = Vortex(api_key="your-api-key", base_url="https://custom-api.example.com")
|
|
62
|
+
vortex = Vortex(api_key="your-vortex-api-key", base_url="https://custom-api.example.com")
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
### JWT Generation
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
vortex_python_sdk-0.0.3.dist-info/licenses/LICENSE,sha256=VndlWxbL4-w3YDf2yE5gJscj4zVXF0qlSq0LDtay3lo,1074
|
|
2
|
+
vortex_sdk/__init__.py,sha256=0Q9cyxYBvCQtLsKWJJu-lAjPFp5HCXnIYKtp6xYAYFA,709
|
|
3
|
+
vortex_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
vortex_sdk/types.py,sha256=Si5fXbf2gkcK3FoirJURNBBPLOJM090P5uhlHx6kXZQ,3079
|
|
5
|
+
vortex_sdk/vortex.py,sha256=B7XAT8CAEJLEH5kxYF3KpMo4kAO_LwCBs8-VEBgC3cw,14754
|
|
6
|
+
vortex_python_sdk-0.0.3.dist-info/METADATA,sha256=bACa6c5OMHoIwGBKC2oH8uNKVIdHmlIzzrHYnftqr5w,6237
|
|
7
|
+
vortex_python_sdk-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
vortex_python_sdk-0.0.3.dist-info/top_level.txt,sha256=xFDlEcXIIi_sBhkse0YfMnSdg2IlaYUd0oP2UCDc_Y0,11
|
|
9
|
+
vortex_python_sdk-0.0.3.dist-info/RECORD,,
|
vortex_sdk/__init__.py
CHANGED
vortex_sdk/types.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Dict, List, Optional, Union, Literal
|
|
1
|
+
from typing import Dict, List, Optional, Union, Literal, Any
|
|
2
2
|
from pydantic import BaseModel, Field
|
|
3
3
|
|
|
4
4
|
|
|
@@ -58,6 +58,7 @@ class JwtPayload(BaseModel):
|
|
|
58
58
|
identifiers: List[IdentifierInput]
|
|
59
59
|
groups: Optional[List[GroupInput]] = None
|
|
60
60
|
role: Optional[str] = None
|
|
61
|
+
attributes: Optional[Dict[str, Any]] = None
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
class InvitationTarget(BaseModel):
|
vortex_sdk/vortex.py
CHANGED
|
@@ -2,6 +2,8 @@ import json
|
|
|
2
2
|
import hmac
|
|
3
3
|
import hashlib
|
|
4
4
|
import base64
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
5
7
|
from typing import Dict, List, Optional, Union, Literal
|
|
6
8
|
from urllib.parse import urlencode
|
|
7
9
|
import httpx
|
|
@@ -32,58 +34,104 @@ class Vortex:
|
|
|
32
34
|
|
|
33
35
|
def generate_jwt(self, payload: Union[JwtPayload, Dict]) -> str:
|
|
34
36
|
"""
|
|
35
|
-
Generate a JWT token for the given payload
|
|
37
|
+
Generate a JWT token for the given payload matching Node.js SDK implementation
|
|
36
38
|
|
|
37
39
|
Args:
|
|
38
40
|
payload: JWT payload containing user_id, identifiers, groups, and role
|
|
39
41
|
|
|
40
42
|
Returns:
|
|
41
43
|
JWT token string
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
ValueError: If API key format is invalid
|
|
42
47
|
"""
|
|
43
48
|
if isinstance(payload, dict):
|
|
44
49
|
payload = JwtPayload(**payload)
|
|
45
50
|
|
|
46
|
-
#
|
|
51
|
+
# Parse API key (format: VRTX.base64url(uuid).key)
|
|
52
|
+
parts = self.api_key.split('.')
|
|
53
|
+
if len(parts) != 3:
|
|
54
|
+
raise ValueError('Invalid API key format. Expected: VRTX.{encodedId}.{key}')
|
|
55
|
+
|
|
56
|
+
prefix, encoded_id, key = parts
|
|
57
|
+
|
|
58
|
+
if prefix != 'VRTX':
|
|
59
|
+
raise ValueError('Invalid API key prefix. Expected: VRTX')
|
|
60
|
+
|
|
61
|
+
# Decode UUID from base64url
|
|
62
|
+
# Add padding if needed
|
|
63
|
+
padding = 4 - len(encoded_id) % 4
|
|
64
|
+
if padding != 4:
|
|
65
|
+
encoded_id_padded = encoded_id + ('=' * padding)
|
|
66
|
+
else:
|
|
67
|
+
encoded_id_padded = encoded_id
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
uuid_bytes = base64.urlsafe_b64decode(encoded_id_padded)
|
|
71
|
+
kid = str(uuid.UUID(bytes=uuid_bytes))
|
|
72
|
+
except Exception as e:
|
|
73
|
+
raise ValueError(f'Invalid UUID in API key: {e}')
|
|
74
|
+
|
|
75
|
+
# Generate timestamps
|
|
76
|
+
iat = int(time.time())
|
|
77
|
+
expires = iat + 3600
|
|
78
|
+
|
|
79
|
+
# Step 1: Derive signing key from API key + UUID
|
|
80
|
+
signing_key = hmac.new(
|
|
81
|
+
key.encode(),
|
|
82
|
+
kid.encode(),
|
|
83
|
+
hashlib.sha256
|
|
84
|
+
).digest()
|
|
85
|
+
|
|
86
|
+
# Step 2: Build header + payload
|
|
47
87
|
header = {
|
|
48
|
-
|
|
49
|
-
|
|
88
|
+
'iat': iat,
|
|
89
|
+
'alg': 'HS256',
|
|
90
|
+
'typ': 'JWT',
|
|
91
|
+
'kid': kid,
|
|
50
92
|
}
|
|
51
93
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
"userId": payload.user_id,
|
|
55
|
-
"identifiers": [{"type": id.type, "value": id.value} for id in payload.identifiers],
|
|
56
|
-
}
|
|
94
|
+
# Serialize identifiers
|
|
95
|
+
identifiers_list = [{"type": id.type, "value": id.value} for id in payload.identifiers]
|
|
57
96
|
|
|
97
|
+
# Serialize groups
|
|
98
|
+
groups_list = None
|
|
58
99
|
if payload.groups is not None:
|
|
59
|
-
|
|
60
|
-
jwt_payload["groups"] = [
|
|
100
|
+
groups_list = [
|
|
61
101
|
{k: v for k, v in group.model_dump(by_alias=True, exclude_none=True).items()}
|
|
62
102
|
for group in payload.groups
|
|
63
103
|
]
|
|
64
|
-
if payload.role is not None:
|
|
65
|
-
jwt_payload["role"] = payload.role
|
|
66
104
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
105
|
+
jwt_payload = {
|
|
106
|
+
'userId': payload.user_id,
|
|
107
|
+
'groups': groups_list,
|
|
108
|
+
'role': payload.role,
|
|
109
|
+
'expires': expires,
|
|
110
|
+
'identifiers': identifiers_list,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Add attributes if provided
|
|
114
|
+
if hasattr(payload, 'attributes') and payload.attributes:
|
|
115
|
+
jwt_payload['attributes'] = payload.attributes
|
|
116
|
+
|
|
117
|
+
# Step 3: Base64URL encode (without padding)
|
|
118
|
+
header_json = json.dumps(header, separators=(',', ':'))
|
|
119
|
+
payload_json = json.dumps(jwt_payload, separators=(',', ':'))
|
|
71
120
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
).decode().rstrip('=')
|
|
121
|
+
header_b64 = base64.urlsafe_b64encode(header_json.encode()).decode().rstrip('=')
|
|
122
|
+
payload_b64 = base64.urlsafe_b64encode(payload_json.encode()).decode().rstrip('=')
|
|
75
123
|
|
|
76
|
-
#
|
|
77
|
-
|
|
124
|
+
# Step 4: Sign
|
|
125
|
+
to_sign = f'{header_b64}.{payload_b64}'
|
|
78
126
|
signature = hmac.new(
|
|
79
|
-
|
|
80
|
-
|
|
127
|
+
signing_key,
|
|
128
|
+
to_sign.encode(),
|
|
81
129
|
hashlib.sha256
|
|
82
130
|
).digest()
|
|
83
131
|
|
|
84
|
-
|
|
132
|
+
signature_b64 = base64.urlsafe_b64encode(signature).decode().rstrip('=')
|
|
85
133
|
|
|
86
|
-
return f
|
|
134
|
+
return f'{to_sign}.{signature_b64}'
|
|
87
135
|
|
|
88
136
|
async def _vortex_api_request(
|
|
89
137
|
self,
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
vortex_python_sdk-0.0.2.dist-info/licenses/LICENSE,sha256=VndlWxbL4-w3YDf2yE5gJscj4zVXF0qlSq0LDtay3lo,1074
|
|
2
|
-
vortex_sdk/__init__.py,sha256=BVRNzjJmtAX7QjUSqG1qL_l3jpVp-AOxMhuBdCCPFOg,709
|
|
3
|
-
vortex_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
vortex_sdk/types.py,sha256=49M5wb1PjFPeeqzZurd91rIaCTJK2Ll-L28BQhxd4wI,3026
|
|
5
|
-
vortex_sdk/vortex.py,sha256=gtIuLkW4XY6zMClNeMukicEU8Afr6RYA7RHG25YTtmE,13275
|
|
6
|
-
vortex_python_sdk-0.0.2.dist-info/METADATA,sha256=iFMbcUiLIgSOhbijKHdXYQkKJb2rgFjPrCRrxnBu33g,6198
|
|
7
|
-
vortex_python_sdk-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
-
vortex_python_sdk-0.0.2.dist-info/top_level.txt,sha256=xFDlEcXIIi_sBhkse0YfMnSdg2IlaYUd0oP2UCDc_Y0,11
|
|
9
|
-
vortex_python_sdk-0.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|