vortex-python-sdk 0.0.1__tar.gz → 0.0.3__tar.gz
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.
Potentially problematic release.
This version of vortex-python-sdk might be problematic. Click here for more details.
- vortex_python_sdk-0.0.3/CHANGELOG.md +77 -0
- {vortex_python_sdk-0.0.1/src/vortex_python_sdk.egg-info → vortex_python_sdk-0.0.3}/PKG-INFO +27 -10
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/README.md +26 -9
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/pyproject.toml +1 -1
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3/src/vortex_python_sdk.egg-info}/PKG-INFO +27 -10
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/src/vortex_sdk/__init__.py +5 -1
- vortex_python_sdk-0.0.3/src/vortex_sdk/types.py +103 -0
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/src/vortex_sdk/vortex.py +76 -24
- vortex_python_sdk-0.0.1/CHANGELOG.md +0 -32
- vortex_python_sdk-0.0.1/src/vortex_sdk/types.py +0 -59
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/LICENSE +0 -0
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/MANIFEST.in +0 -0
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/setup.cfg +0 -0
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/src/vortex_python_sdk.egg-info/SOURCES.txt +0 -0
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/src/vortex_python_sdk.egg-info/dependency_links.txt +0 -0
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/src/vortex_python_sdk.egg-info/requires.txt +0 -0
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/src/vortex_python_sdk.egg-info/top_level.txt +0 -0
- {vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/src/vortex_sdk/py.typed +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.0.3] - 2025-01-31
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **CRITICAL FIX**: JWT generation now matches Node.js SDK implementation exactly
|
|
14
|
+
- Improved JWT signing algorithm to match Node.js SDK
|
|
15
|
+
- Added `iat` (issued at) and `expires` timestamp fields to JWT
|
|
16
|
+
- Added `attributes` field support in JwtPayload for custom user attributes
|
|
17
|
+
- Fixed base64url encoding
|
|
18
|
+
- Added comprehensive tests verifying JWT output matches Node.js SDK byte-for-byte
|
|
19
|
+
|
|
20
|
+
### Breaking Changes
|
|
21
|
+
- JWT structure changed significantly - tokens from 0.0.2 are incompatible with 0.0.3
|
|
22
|
+
- JWTs now include `iat` and `expires` fields for proper token lifecycle management
|
|
23
|
+
|
|
24
|
+
## [0.0.2] - 2025-01-31
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **BREAKING FIX**: JWT payload format now matches TypeScript SDK
|
|
28
|
+
- `identifiers` changed from `Dict[str, str]` to `List[Dict]` with `type` and `value` fields
|
|
29
|
+
- `groups` structure now properly includes `type`, `id`/`groupId`, and `name` fields
|
|
30
|
+
- Added `IdentifierInput` type for type-safe identifier creation
|
|
31
|
+
- Updated `GroupInput` to support both `id` (legacy) and `groupId` (preferred) with proper camelCase serialization
|
|
32
|
+
- Updated documentation with correct JWT generation examples
|
|
33
|
+
|
|
34
|
+
### Migration Guide
|
|
35
|
+
If you're upgrading from 0.0.1, update your JWT generation code:
|
|
36
|
+
|
|
37
|
+
**Before (0.0.1):**
|
|
38
|
+
```python
|
|
39
|
+
jwt = vortex.generate_jwt({
|
|
40
|
+
"user_id": "user-123",
|
|
41
|
+
"identifiers": {"email": "user@example.com"}, # Dict
|
|
42
|
+
"groups": ["admin"], # List of strings
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**After (0.0.2):**
|
|
47
|
+
```python
|
|
48
|
+
jwt = vortex.generate_jwt({
|
|
49
|
+
"user_id": "user-123",
|
|
50
|
+
"identifiers": [{"type": "email", "value": "user@example.com"}], # List of dicts
|
|
51
|
+
"groups": [{"type": "team", "id": "team-1", "name": "Engineering"}], # List of objects
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## [0.0.1] - 2024-10-10
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
- Initial release of Vortex Python SDK
|
|
59
|
+
- JWT generation with HMAC-SHA256 signing
|
|
60
|
+
- Complete invitation management API
|
|
61
|
+
- Async and sync HTTP client methods
|
|
62
|
+
- Type safety with Pydantic models
|
|
63
|
+
- Context manager support for resource cleanup
|
|
64
|
+
- Comprehensive error handling with VortexApiError
|
|
65
|
+
- Full compatibility with Node.js SDK API
|
|
66
|
+
|
|
67
|
+
### Features
|
|
68
|
+
- `generate_jwt()` - Generate Vortex JWT tokens
|
|
69
|
+
- `get_invitations_by_target()` - Get invitations by email/username/phone
|
|
70
|
+
- `accept_invitations()` - Accept multiple invitations
|
|
71
|
+
- `get_invitation()` - Get specific invitation by ID
|
|
72
|
+
- `revoke_invitation()` - Revoke invitation
|
|
73
|
+
- `get_invitations_by_group()` - Get invitations for a group
|
|
74
|
+
- `delete_invitations_by_group()` - Delete all group invitations
|
|
75
|
+
- `reinvite()` - Reinvite functionality
|
|
76
|
+
- Both async and sync versions of all methods
|
|
77
|
+
- Python 3.8+ support
|
|
@@ -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
|
|
@@ -67,16 +67,33 @@ vortex = Vortex(api_key="your-api-key", base_url="https://custom-api.example.com
|
|
|
67
67
|
```python
|
|
68
68
|
# Generate JWT for a user
|
|
69
69
|
jwt = vortex.generate_jwt({
|
|
70
|
-
"user_id": "
|
|
71
|
-
"identifiers":
|
|
72
|
-
"email": "user@example.com"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
"user_id": "user-123",
|
|
71
|
+
"identifiers": [
|
|
72
|
+
{"type": "email", "value": "user@example.com"}
|
|
73
|
+
],
|
|
74
|
+
"groups": [
|
|
75
|
+
{"type": "team", "id": "team-1", "name": "Engineering"}
|
|
76
|
+
],
|
|
76
77
|
"role": "admin"
|
|
77
78
|
})
|
|
78
79
|
|
|
79
80
|
print(f"JWT: {jwt}")
|
|
81
|
+
|
|
82
|
+
# Or using type-safe models
|
|
83
|
+
from vortex_sdk import JwtPayload, IdentifierInput, GroupInput
|
|
84
|
+
|
|
85
|
+
jwt = vortex.generate_jwt(
|
|
86
|
+
JwtPayload(
|
|
87
|
+
user_id="user-123",
|
|
88
|
+
identifiers=[
|
|
89
|
+
IdentifierInput(type="email", value="user@example.com")
|
|
90
|
+
],
|
|
91
|
+
groups=[
|
|
92
|
+
GroupInput(type="team", id="team-1", name="Engineering")
|
|
93
|
+
],
|
|
94
|
+
role="admin"
|
|
95
|
+
)
|
|
96
|
+
)
|
|
80
97
|
```
|
|
81
98
|
|
|
82
99
|
### Invitation Management
|
|
@@ -17,11 +17,11 @@ pip install vortex-python-sdk
|
|
|
17
17
|
```python
|
|
18
18
|
from vortex_sdk import Vortex
|
|
19
19
|
|
|
20
|
-
# Initialize the client
|
|
21
|
-
vortex = Vortex(api_key="your-api-key")
|
|
20
|
+
# Initialize the client with your Vortex API key
|
|
21
|
+
vortex = Vortex(api_key="your-vortex-api-key")
|
|
22
22
|
|
|
23
23
|
# Or with custom base URL
|
|
24
|
-
vortex = Vortex(api_key="your-api-key", base_url="https://custom-api.example.com")
|
|
24
|
+
vortex = Vortex(api_key="your-vortex-api-key", base_url="https://custom-api.example.com")
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
### JWT Generation
|
|
@@ -29,16 +29,33 @@ vortex = Vortex(api_key="your-api-key", base_url="https://custom-api.example.com
|
|
|
29
29
|
```python
|
|
30
30
|
# Generate JWT for a user
|
|
31
31
|
jwt = vortex.generate_jwt({
|
|
32
|
-
"user_id": "
|
|
33
|
-
"identifiers":
|
|
34
|
-
"email": "user@example.com"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
"user_id": "user-123",
|
|
33
|
+
"identifiers": [
|
|
34
|
+
{"type": "email", "value": "user@example.com"}
|
|
35
|
+
],
|
|
36
|
+
"groups": [
|
|
37
|
+
{"type": "team", "id": "team-1", "name": "Engineering"}
|
|
38
|
+
],
|
|
38
39
|
"role": "admin"
|
|
39
40
|
})
|
|
40
41
|
|
|
41
42
|
print(f"JWT: {jwt}")
|
|
43
|
+
|
|
44
|
+
# Or using type-safe models
|
|
45
|
+
from vortex_sdk import JwtPayload, IdentifierInput, GroupInput
|
|
46
|
+
|
|
47
|
+
jwt = vortex.generate_jwt(
|
|
48
|
+
JwtPayload(
|
|
49
|
+
user_id="user-123",
|
|
50
|
+
identifiers=[
|
|
51
|
+
IdentifierInput(type="email", value="user@example.com")
|
|
52
|
+
],
|
|
53
|
+
groups=[
|
|
54
|
+
GroupInput(type="team", id="team-1", name="Engineering")
|
|
55
|
+
],
|
|
56
|
+
role="admin"
|
|
57
|
+
)
|
|
58
|
+
)
|
|
42
59
|
```
|
|
43
60
|
|
|
44
61
|
### Invitation Management
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vortex-python-sdk"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.3"
|
|
8
8
|
description = "Vortex Python SDK for invitation management and JWT generation"
|
|
9
9
|
authors = [{name = "TeamVortexSoftware", email = "support@vortexsoftware.com"}]
|
|
10
10
|
readme = "README.md"
|
|
@@ -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
|
|
@@ -67,16 +67,33 @@ vortex = Vortex(api_key="your-api-key", base_url="https://custom-api.example.com
|
|
|
67
67
|
```python
|
|
68
68
|
# Generate JWT for a user
|
|
69
69
|
jwt = vortex.generate_jwt({
|
|
70
|
-
"user_id": "
|
|
71
|
-
"identifiers":
|
|
72
|
-
"email": "user@example.com"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
"user_id": "user-123",
|
|
71
|
+
"identifiers": [
|
|
72
|
+
{"type": "email", "value": "user@example.com"}
|
|
73
|
+
],
|
|
74
|
+
"groups": [
|
|
75
|
+
{"type": "team", "id": "team-1", "name": "Engineering"}
|
|
76
|
+
],
|
|
76
77
|
"role": "admin"
|
|
77
78
|
})
|
|
78
79
|
|
|
79
80
|
print(f"JWT: {jwt}")
|
|
81
|
+
|
|
82
|
+
# Or using type-safe models
|
|
83
|
+
from vortex_sdk import JwtPayload, IdentifierInput, GroupInput
|
|
84
|
+
|
|
85
|
+
jwt = vortex.generate_jwt(
|
|
86
|
+
JwtPayload(
|
|
87
|
+
user_id="user-123",
|
|
88
|
+
identifiers=[
|
|
89
|
+
IdentifierInput(type="email", value="user@example.com")
|
|
90
|
+
],
|
|
91
|
+
groups=[
|
|
92
|
+
GroupInput(type="team", id="team-1", name="Engineering")
|
|
93
|
+
],
|
|
94
|
+
role="admin"
|
|
95
|
+
)
|
|
96
|
+
)
|
|
80
97
|
```
|
|
81
98
|
|
|
82
99
|
### Invitation Management
|
|
@@ -8,6 +8,8 @@ from .vortex import Vortex
|
|
|
8
8
|
from .types import (
|
|
9
9
|
AuthenticatedUser,
|
|
10
10
|
JwtPayload,
|
|
11
|
+
IdentifierInput,
|
|
12
|
+
GroupInput,
|
|
11
13
|
InvitationTarget,
|
|
12
14
|
Invitation,
|
|
13
15
|
CreateInvitationRequest,
|
|
@@ -16,7 +18,7 @@ from .types import (
|
|
|
16
18
|
VortexApiError
|
|
17
19
|
)
|
|
18
20
|
|
|
19
|
-
__version__ = "0.0.
|
|
21
|
+
__version__ = "0.0.3"
|
|
20
22
|
__author__ = "TeamVortexSoftware"
|
|
21
23
|
__email__ = "support@vortexsoftware.com"
|
|
22
24
|
|
|
@@ -24,6 +26,8 @@ __all__ = [
|
|
|
24
26
|
"Vortex",
|
|
25
27
|
"AuthenticatedUser",
|
|
26
28
|
"JwtPayload",
|
|
29
|
+
"IdentifierInput",
|
|
30
|
+
"GroupInput",
|
|
27
31
|
"InvitationTarget",
|
|
28
32
|
"Invitation",
|
|
29
33
|
"CreateInvitationRequest",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Dict, List, Optional, Union, Literal, Any
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class IdentifierInput(BaseModel):
|
|
6
|
+
"""Identifier structure for JWT generation"""
|
|
7
|
+
type: Literal["email", "sms"]
|
|
8
|
+
value: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GroupInput(BaseModel):
|
|
12
|
+
"""Group structure for JWT generation (input)"""
|
|
13
|
+
type: str
|
|
14
|
+
id: Optional[str] = None # Legacy field (deprecated, use groupId)
|
|
15
|
+
groupId: Optional[str] = Field(None, alias="group_id", serialization_alias="groupId") # Preferred: Customer's group ID
|
|
16
|
+
name: str
|
|
17
|
+
|
|
18
|
+
class Config:
|
|
19
|
+
populate_by_name = True
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class InvitationGroup(BaseModel):
|
|
23
|
+
"""
|
|
24
|
+
Invitation group from API responses
|
|
25
|
+
This matches the MemberGroups table structure from the API
|
|
26
|
+
"""
|
|
27
|
+
id: str # Vortex internal UUID
|
|
28
|
+
account_id: str # Vortex account ID (camelCase in JSON: accountId)
|
|
29
|
+
group_id: str # Customer's group ID (camelCase in JSON: groupId)
|
|
30
|
+
type: str # Group type (e.g., "workspace", "team")
|
|
31
|
+
name: str # Group name
|
|
32
|
+
created_at: str # ISO 8601 timestamp (camelCase in JSON: createdAt)
|
|
33
|
+
|
|
34
|
+
class Config:
|
|
35
|
+
# Allow both snake_case (Python) and camelCase (JSON) field names
|
|
36
|
+
populate_by_name = True
|
|
37
|
+
json_schema_extra = {
|
|
38
|
+
"example": {
|
|
39
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
40
|
+
"accountId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
41
|
+
"groupId": "workspace-123",
|
|
42
|
+
"type": "workspace",
|
|
43
|
+
"name": "My Workspace",
|
|
44
|
+
"createdAt": "2025-01-27T12:00:00.000Z"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AuthenticatedUser(BaseModel):
|
|
50
|
+
user_id: str
|
|
51
|
+
identifiers: List[IdentifierInput]
|
|
52
|
+
groups: Optional[List[GroupInput]] = None
|
|
53
|
+
role: Optional[str] = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class JwtPayload(BaseModel):
|
|
57
|
+
user_id: str
|
|
58
|
+
identifiers: List[IdentifierInput]
|
|
59
|
+
groups: Optional[List[GroupInput]] = None
|
|
60
|
+
role: Optional[str] = None
|
|
61
|
+
attributes: Optional[Dict[str, Any]] = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class InvitationTarget(BaseModel):
|
|
65
|
+
type: Literal["email", "username", "phoneNumber"]
|
|
66
|
+
value: str
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Invitation(BaseModel):
|
|
70
|
+
id: str
|
|
71
|
+
target: InvitationTarget
|
|
72
|
+
groups: Optional[List[InvitationGroup]] = None # Full group information
|
|
73
|
+
status: str
|
|
74
|
+
created_at: str
|
|
75
|
+
updated_at: Optional[str] = None
|
|
76
|
+
expires_at: Optional[str] = None
|
|
77
|
+
metadata: Optional[Dict[str, Union[str, int, bool]]] = None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CreateInvitationRequest(BaseModel):
|
|
81
|
+
target: InvitationTarget
|
|
82
|
+
group_type: Optional[str] = None
|
|
83
|
+
group_id: Optional[str] = None
|
|
84
|
+
expires_at: Optional[str] = None
|
|
85
|
+
metadata: Optional[Dict[str, Union[str, int, bool]]] = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class AcceptInvitationsRequest(BaseModel):
|
|
89
|
+
invitation_ids: List[str]
|
|
90
|
+
target: InvitationTarget
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ApiResponse(BaseModel):
|
|
94
|
+
data: Optional[Dict] = None
|
|
95
|
+
error: Optional[str] = None
|
|
96
|
+
status_code: int = 200
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class VortexApiError(Exception):
|
|
100
|
+
def __init__(self, message: str, status_code: int = 500):
|
|
101
|
+
self.message = message
|
|
102
|
+
self.status_code = status_code
|
|
103
|
+
super().__init__(message)
|
|
@@ -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,54 +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
|
-
#
|
|
94
|
+
# Serialize identifiers
|
|
95
|
+
identifiers_list = [{"type": id.type, "value": id.value} for id in payload.identifiers]
|
|
96
|
+
|
|
97
|
+
# Serialize groups
|
|
98
|
+
groups_list = None
|
|
99
|
+
if payload.groups is not None:
|
|
100
|
+
groups_list = [
|
|
101
|
+
{k: v for k, v in group.model_dump(by_alias=True, exclude_none=True).items()}
|
|
102
|
+
for group in payload.groups
|
|
103
|
+
]
|
|
104
|
+
|
|
53
105
|
jwt_payload = {
|
|
54
|
-
|
|
55
|
-
|
|
106
|
+
'userId': payload.user_id,
|
|
107
|
+
'groups': groups_list,
|
|
108
|
+
'role': payload.role,
|
|
109
|
+
'expires': expires,
|
|
110
|
+
'identifiers': identifiers_list,
|
|
56
111
|
}
|
|
57
112
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
jwt_payload["role"] = payload.role
|
|
113
|
+
# Add attributes if provided
|
|
114
|
+
if hasattr(payload, 'attributes') and payload.attributes:
|
|
115
|
+
jwt_payload['attributes'] = payload.attributes
|
|
62
116
|
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
).decode().rstrip('=')
|
|
117
|
+
# Step 3: Base64URL encode (without padding)
|
|
118
|
+
header_json = json.dumps(header, separators=(',', ':'))
|
|
119
|
+
payload_json = json.dumps(jwt_payload, separators=(',', ':'))
|
|
67
120
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
).decode().rstrip('=')
|
|
121
|
+
header_b64 = base64.urlsafe_b64encode(header_json.encode()).decode().rstrip('=')
|
|
122
|
+
payload_b64 = base64.urlsafe_b64encode(payload_json.encode()).decode().rstrip('=')
|
|
71
123
|
|
|
72
|
-
#
|
|
73
|
-
|
|
124
|
+
# Step 4: Sign
|
|
125
|
+
to_sign = f'{header_b64}.{payload_b64}'
|
|
74
126
|
signature = hmac.new(
|
|
75
|
-
|
|
76
|
-
|
|
127
|
+
signing_key,
|
|
128
|
+
to_sign.encode(),
|
|
77
129
|
hashlib.sha256
|
|
78
130
|
).digest()
|
|
79
131
|
|
|
80
|
-
|
|
132
|
+
signature_b64 = base64.urlsafe_b64encode(signature).decode().rstrip('=')
|
|
81
133
|
|
|
82
|
-
return f
|
|
134
|
+
return f'{to_sign}.{signature_b64}'
|
|
83
135
|
|
|
84
136
|
async def _vortex_api_request(
|
|
85
137
|
self,
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [Unreleased]
|
|
9
|
-
|
|
10
|
-
## [0.0.1] - 2024-10-10
|
|
11
|
-
|
|
12
|
-
### Added
|
|
13
|
-
- Initial release of Vortex Python SDK
|
|
14
|
-
- JWT generation with HMAC-SHA256 signing
|
|
15
|
-
- Complete invitation management API
|
|
16
|
-
- Async and sync HTTP client methods
|
|
17
|
-
- Type safety with Pydantic models
|
|
18
|
-
- Context manager support for resource cleanup
|
|
19
|
-
- Comprehensive error handling with VortexApiError
|
|
20
|
-
- Full compatibility with Node.js SDK API
|
|
21
|
-
|
|
22
|
-
### Features
|
|
23
|
-
- `generate_jwt()` - Generate Vortex JWT tokens
|
|
24
|
-
- `get_invitations_by_target()` - Get invitations by email/username/phone
|
|
25
|
-
- `accept_invitations()` - Accept multiple invitations
|
|
26
|
-
- `get_invitation()` - Get specific invitation by ID
|
|
27
|
-
- `revoke_invitation()` - Revoke invitation
|
|
28
|
-
- `get_invitations_by_group()` - Get invitations for a group
|
|
29
|
-
- `delete_invitations_by_group()` - Delete all group invitations
|
|
30
|
-
- `reinvite()` - Reinvite functionality
|
|
31
|
-
- Both async and sync versions of all methods
|
|
32
|
-
- Python 3.8+ support
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
from typing import Dict, List, Optional, Union, Literal
|
|
2
|
-
from pydantic import BaseModel
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class AuthenticatedUser(BaseModel):
|
|
6
|
-
user_id: str
|
|
7
|
-
identifiers: Dict[str, str]
|
|
8
|
-
groups: Optional[List[str]] = None
|
|
9
|
-
role: Optional[str] = None
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class JwtPayload(BaseModel):
|
|
13
|
-
user_id: str
|
|
14
|
-
identifiers: Dict[str, str]
|
|
15
|
-
groups: Optional[List[str]] = None
|
|
16
|
-
role: Optional[str] = None
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class InvitationTarget(BaseModel):
|
|
20
|
-
type: Literal["email", "username", "phoneNumber"]
|
|
21
|
-
value: str
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class Invitation(BaseModel):
|
|
25
|
-
id: str
|
|
26
|
-
target: InvitationTarget
|
|
27
|
-
group_type: Optional[str] = None
|
|
28
|
-
group_id: Optional[str] = None
|
|
29
|
-
status: str
|
|
30
|
-
created_at: str
|
|
31
|
-
updated_at: Optional[str] = None
|
|
32
|
-
expires_at: Optional[str] = None
|
|
33
|
-
metadata: Optional[Dict[str, Union[str, int, bool]]] = None
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class CreateInvitationRequest(BaseModel):
|
|
37
|
-
target: InvitationTarget
|
|
38
|
-
group_type: Optional[str] = None
|
|
39
|
-
group_id: Optional[str] = None
|
|
40
|
-
expires_at: Optional[str] = None
|
|
41
|
-
metadata: Optional[Dict[str, Union[str, int, bool]]] = None
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class AcceptInvitationsRequest(BaseModel):
|
|
45
|
-
invitation_ids: List[str]
|
|
46
|
-
target: InvitationTarget
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class ApiResponse(BaseModel):
|
|
50
|
-
data: Optional[Dict] = None
|
|
51
|
-
error: Optional[str] = None
|
|
52
|
-
status_code: int = 200
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class VortexApiError(Exception):
|
|
56
|
-
def __init__(self, message: str, status_code: int = 500):
|
|
57
|
-
self.message = message
|
|
58
|
-
self.status_code = status_code
|
|
59
|
-
super().__init__(message)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/src/vortex_python_sdk.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/src/vortex_python_sdk.egg-info/requires.txt
RENAMED
|
File without changes
|
{vortex_python_sdk-0.0.1 → vortex_python_sdk-0.0.3}/src/vortex_python_sdk.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|