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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortex-python-sdk
3
- Version: 0.0.2
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
@@ -18,7 +18,7 @@ from .types import (
18
18
  VortexApiError
19
19
  )
20
20
 
21
- __version__ = "0.0.2"
21
+ __version__ = "0.0.3"
22
22
  __author__ = "TeamVortexSoftware"
23
23
  __email__ = "support@vortexsoftware.com"
24
24
 
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
- # JWT Header
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
- "alg": "HS256",
49
- "typ": "JWT"
88
+ 'iat': iat,
89
+ 'alg': 'HS256',
90
+ 'typ': 'JWT',
91
+ 'kid': kid,
50
92
  }
51
93
 
52
- # JWT Payload - serialize identifiers and groups to dicts
53
- jwt_payload = {
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
- # Serialize groups, using model_dump to handle camelCase conversion
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
- # Encode header and payload
68
- header_encoded = base64.urlsafe_b64encode(
69
- json.dumps(header, separators=(',', ':')).encode()
70
- ).decode().rstrip('=')
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
- payload_encoded = base64.urlsafe_b64encode(
73
- json.dumps(jwt_payload, separators=(',', ':')).encode()
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
- # Create signature
77
- message = f"{header_encoded}.{payload_encoded}"
124
+ # Step 4: Sign
125
+ to_sign = f'{header_b64}.{payload_b64}'
78
126
  signature = hmac.new(
79
- self.api_key.encode(),
80
- message.encode(),
127
+ signing_key,
128
+ to_sign.encode(),
81
129
  hashlib.sha256
82
130
  ).digest()
83
131
 
84
- signature_encoded = base64.urlsafe_b64encode(signature).decode().rstrip('=')
132
+ signature_b64 = base64.urlsafe_b64encode(signature).decode().rstrip('=')
85
133
 
86
- return f"{message}.{signature_encoded}"
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,,