keymint 2.3.1__tar.gz → 2.5.0__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.
- {keymint-2.3.1/keymint.egg-info → keymint-2.5.0}/PKG-INFO +5 -7
- {keymint-2.3.1 → keymint-2.5.0}/README.md +2 -0
- {keymint-2.3.1 → keymint-2.5.0}/keymint/__init__.py +53 -26
- {keymint-2.3.1 → keymint-2.5.0}/keymint/_version.py +1 -1
- {keymint-2.3.1 → keymint-2.5.0}/keymint/identity.py +9 -10
- keymint-2.5.0/keymint/types.py +287 -0
- {keymint-2.3.1 → keymint-2.5.0/keymint.egg-info}/PKG-INFO +5 -7
- {keymint-2.3.1 → keymint-2.5.0}/keymint.egg-info/SOURCES.txt +2 -1
- {keymint-2.3.1 → keymint-2.5.0}/keymint.egg-info/requires.txt +1 -1
- {keymint-2.3.1 → keymint-2.5.0}/setup.py +4 -8
- keymint-2.5.0/tests/test_contracts.py +52 -0
- keymint-2.3.1/keymint/types.py +0 -237
- {keymint-2.3.1 → keymint-2.5.0}/LICENSE +0 -0
- {keymint-2.3.1 → keymint-2.5.0}/MANIFEST.in +0 -0
- {keymint-2.3.1 → keymint-2.5.0}/keymint.egg-info/dependency_links.txt +0 -0
- {keymint-2.3.1 → keymint-2.5.0}/keymint.egg-info/top_level.txt +0 -0
- {keymint-2.3.1 → keymint-2.5.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: keymint
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: License key validation, activation, and management for Python. Supports node-locking, offline licensing, and hardware fingerprinting.
|
|
5
5
|
Home-page: https://github.com/keymint-dev/keymint-python
|
|
6
6
|
Author: KeyMint
|
|
@@ -16,18 +16,14 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
16
16
|
Classifier: Topic :: System :: Software Distribution
|
|
17
17
|
Classifier: License :: OSI Approved :: MIT License
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
23
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
24
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
25
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
26
22
|
Classifier: Operating System :: OS Independent
|
|
27
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.10
|
|
28
24
|
Description-Content-Type: text/markdown
|
|
29
25
|
License-File: LICENSE
|
|
30
|
-
Requires-Dist: requests>=2.
|
|
26
|
+
Requires-Dist: requests>=2.32.4
|
|
31
27
|
Provides-Extra: dev
|
|
32
28
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
33
29
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -107,8 +103,10 @@ Keymint provides utilities to uniquely identify machines for node-locking:
|
|
|
107
103
|
| `activate_key` | Activates a license key for a device. |
|
|
108
104
|
| `deactivate_key` | Deactivates a device from a license key. |
|
|
109
105
|
| `get_key` | Retrieves detailed information about a key. |
|
|
106
|
+
| `update_key` | Updates an existing license key. |
|
|
110
107
|
| `block_key` | Blocks a license key. |
|
|
111
108
|
| `unblock_key` | Unblocks a previously blocked license key. |
|
|
109
|
+
| `sign_key` | Signs a key for offline validation using an admin API key.|
|
|
112
110
|
| `floating_checkout` | Checks out a floating license seat. |
|
|
113
111
|
| `floating_heartbeat`| Sends a heartbeat to keep a session alive. |
|
|
114
112
|
| `floating_checkin` | Checks in a session, releasing the seat. |
|
|
@@ -58,8 +58,10 @@ Keymint provides utilities to uniquely identify machines for node-locking:
|
|
|
58
58
|
| `activate_key` | Activates a license key for a device. |
|
|
59
59
|
| `deactivate_key` | Deactivates a device from a license key. |
|
|
60
60
|
| `get_key` | Retrieves detailed information about a key. |
|
|
61
|
+
| `update_key` | Updates an existing license key. |
|
|
61
62
|
| `block_key` | Blocks a license key. |
|
|
62
63
|
| `unblock_key` | Unblocks a previously blocked license key. |
|
|
64
|
+
| `sign_key` | Signs a key for offline validation using an admin API key.|
|
|
63
65
|
| `floating_checkout` | Checks out a floating license seat. |
|
|
64
66
|
| `floating_heartbeat`| Sends a heartbeat to keep a session alive. |
|
|
65
67
|
| `floating_checkin` | Checks in a session, releasing the seat. |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import requests
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
from ._version import __version__
|
|
4
|
+
from .types import *
|
|
4
5
|
|
|
5
6
|
__all__ = ['KeyMint', 'KeyMintApiError', '__version__']
|
|
6
7
|
|
|
@@ -16,19 +17,23 @@ class KeyMint:
|
|
|
16
17
|
'Content-Type': 'application/json'
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
def _handle_request(self, method: str, endpoint: str, params: dict = None, query_params: dict = None, idempotency_key: str = None):
|
|
20
|
+
def _handle_request(self, method: str, endpoint: str, params: dict | None = None, query_params: dict | None = None, idempotency_key: str | None = None, extra_headers: dict | None = None):
|
|
20
21
|
url = f'{self.base_url}{endpoint}'
|
|
21
22
|
headers = self.headers.copy()
|
|
22
23
|
if idempotency_key:
|
|
23
24
|
headers['Idempotency-Key'] = idempotency_key
|
|
25
|
+
if extra_headers:
|
|
26
|
+
headers.update(extra_headers)
|
|
24
27
|
|
|
25
28
|
try:
|
|
26
29
|
if method.upper() == 'GET':
|
|
27
30
|
response = requests.get(url, params=query_params, headers=headers)
|
|
28
31
|
elif method.upper() == 'POST':
|
|
29
|
-
response = requests.post(url, json=params, headers=headers)
|
|
32
|
+
response = requests.post(url, json=params, params=query_params, headers=headers)
|
|
30
33
|
elif method.upper() == 'PUT':
|
|
31
|
-
response = requests.put(url, json=params, headers=headers)
|
|
34
|
+
response = requests.put(url, json=params, params=query_params, headers=headers)
|
|
35
|
+
elif method.upper() == 'PATCH':
|
|
36
|
+
response = requests.patch(url, json=params, params=query_params, headers=headers)
|
|
32
37
|
elif method.upper() == 'DELETE':
|
|
33
38
|
response = requests.delete(url, params=query_params, headers=headers)
|
|
34
39
|
else:
|
|
@@ -39,8 +44,9 @@ class KeyMint:
|
|
|
39
44
|
except requests.exceptions.HTTPError as http_err:
|
|
40
45
|
try:
|
|
41
46
|
error_data = http_err.response.json()
|
|
47
|
+
nested_error = error_data.get('error') if isinstance(error_data.get('error'), dict) else {}
|
|
42
48
|
raise KeyMintApiError(
|
|
43
|
-
message=error_data.get('message'
|
|
49
|
+
message=error_data.get('message') or nested_error.get('message') or 'An API error occurred',
|
|
44
50
|
code=error_data.get('code', -1),
|
|
45
51
|
status=http_err.response.status_code
|
|
46
52
|
)
|
|
@@ -53,7 +59,7 @@ class KeyMint:
|
|
|
53
59
|
except Exception as err:
|
|
54
60
|
raise KeyMintApiError(message=str(err), code=-1)
|
|
55
61
|
|
|
56
|
-
def create_key(self, params: CreateKeyParams, idempotency_key: str = None) -> CreateKeyResponse:
|
|
62
|
+
def create_key(self, params: CreateKeyParams, idempotency_key: str | None = None) -> CreateKeyResponse:
|
|
57
63
|
"""
|
|
58
64
|
Creates a new license key.
|
|
59
65
|
:param params: Parameters for creating the key.
|
|
@@ -62,7 +68,7 @@ class KeyMint:
|
|
|
62
68
|
"""
|
|
63
69
|
return self._handle_request('POST', '/key', params, idempotency_key=idempotency_key)
|
|
64
70
|
|
|
65
|
-
def activate_key(self, params: ActivateKeyParams, idempotency_key: str = None) -> ActivateKeyResponse:
|
|
71
|
+
def activate_key(self, params: ActivateKeyParams, idempotency_key: str | None = None) -> ActivateKeyResponse:
|
|
66
72
|
"""
|
|
67
73
|
Activates a license key for a specific device.
|
|
68
74
|
|
|
@@ -77,7 +83,7 @@ class KeyMint:
|
|
|
77
83
|
"""
|
|
78
84
|
return self._handle_request('POST', '/key/activate', params, idempotency_key=idempotency_key)
|
|
79
85
|
|
|
80
|
-
def deactivate_key(self, params: DeactivateKeyParams, idempotency_key: str = None) -> DeactivateKeyResponse:
|
|
86
|
+
def deactivate_key(self, params: DeactivateKeyParams, idempotency_key: str | None = None) -> DeactivateKeyResponse:
|
|
81
87
|
"""
|
|
82
88
|
Deactivates a device from a license key.
|
|
83
89
|
:param params: Parameters for deactivating the key.
|
|
@@ -86,7 +92,7 @@ class KeyMint:
|
|
|
86
92
|
"""
|
|
87
93
|
return self._handle_request('POST', '/key/deactivate', params, idempotency_key=idempotency_key)
|
|
88
94
|
|
|
89
|
-
def floating_checkout(self, params: FloatingCheckoutParams, idempotency_key: str = None) -> FloatingCheckoutResponse:
|
|
95
|
+
def floating_checkout(self, params: FloatingCheckoutParams, idempotency_key: str | None = None) -> FloatingCheckoutResponse:
|
|
90
96
|
"""
|
|
91
97
|
Checks out a floating license seat.
|
|
92
98
|
:param params: Parameters for checking out the license.
|
|
@@ -95,7 +101,7 @@ class KeyMint:
|
|
|
95
101
|
"""
|
|
96
102
|
return self._handle_request('POST', '/key/checkout', params, idempotency_key=idempotency_key)
|
|
97
103
|
|
|
98
|
-
def floating_heartbeat(self, params: FloatingHeartbeatParams, idempotency_key: str = None) -> FloatingHeartbeatResponse:
|
|
104
|
+
def floating_heartbeat(self, params: FloatingHeartbeatParams, idempotency_key: str | None = None) -> FloatingHeartbeatResponse:
|
|
99
105
|
"""
|
|
100
106
|
Sends a heartbeat to keep a floating license session alive.
|
|
101
107
|
:param params: Parameters for the heartbeat (includes rotating signature).
|
|
@@ -104,7 +110,7 @@ class KeyMint:
|
|
|
104
110
|
"""
|
|
105
111
|
return self._handle_request('POST', '/key/heartbeat', params, idempotency_key=idempotency_key)
|
|
106
112
|
|
|
107
|
-
def floating_checkin(self, params: FloatingCheckinParams, idempotency_key: str = None) -> FloatingCheckinResponse:
|
|
113
|
+
def floating_checkin(self, params: FloatingCheckinParams, idempotency_key: str | None = None) -> FloatingCheckinResponse:
|
|
108
114
|
"""
|
|
109
115
|
Checks in a floating license session, releasing the seat.
|
|
110
116
|
:param params: Parameters for checking in the license (includes rotating signature).
|
|
@@ -120,12 +126,14 @@ class KeyMint:
|
|
|
120
126
|
:returns: The license key details.
|
|
121
127
|
"""
|
|
122
128
|
query_params = {
|
|
123
|
-
'productId': params['productId']
|
|
124
|
-
'licenseKey': params['licenseKey']
|
|
129
|
+
'productId': params['productId']
|
|
125
130
|
}
|
|
126
|
-
return self._handle_request(
|
|
131
|
+
return self._handle_request(
|
|
132
|
+
'GET', '/key', query_params=query_params,
|
|
133
|
+
extra_headers={'x-license-key': params['licenseKey']}
|
|
134
|
+
)
|
|
127
135
|
|
|
128
|
-
def block_key(self, params: BlockKeyParams, idempotency_key: str = None) -> BlockKeyResponse:
|
|
136
|
+
def block_key(self, params: BlockKeyParams, idempotency_key: str | None = None) -> BlockKeyResponse:
|
|
129
137
|
"""
|
|
130
138
|
Blocks a specific license key.
|
|
131
139
|
:param params: Parameters for blocking the key.
|
|
@@ -134,7 +142,7 @@ class KeyMint:
|
|
|
134
142
|
"""
|
|
135
143
|
return self._handle_request('POST', '/key/block', params, idempotency_key=idempotency_key)
|
|
136
144
|
|
|
137
|
-
def unblock_key(self, params: UnblockKeyParams, idempotency_key: str = None) -> UnblockKeyResponse:
|
|
145
|
+
def unblock_key(self, params: UnblockKeyParams, idempotency_key: str | None = None) -> UnblockKeyResponse:
|
|
138
146
|
"""
|
|
139
147
|
Unblocks a previously blocked license key.
|
|
140
148
|
:param params: Parameters for unblocking the key.
|
|
@@ -143,9 +151,29 @@ class KeyMint:
|
|
|
143
151
|
"""
|
|
144
152
|
return self._handle_request('POST', '/key/unblock', params, idempotency_key=idempotency_key)
|
|
145
153
|
|
|
154
|
+
def update_key(self, params: 'UpdateKeyParams', idempotency_key: str | None = None) -> 'UpdateKeyResponse':
|
|
155
|
+
"""
|
|
156
|
+
Updates an existing license key.
|
|
157
|
+
Requires productId and licenseKey. All other fields are optional.
|
|
158
|
+
:param params: Parameters for updating the key.
|
|
159
|
+
:param idempotency_key: Optional unique identifier to ensure request idempotency.
|
|
160
|
+
:returns: The update confirmation.
|
|
161
|
+
"""
|
|
162
|
+
return self._handle_request('PATCH', '/key', params, idempotency_key=idempotency_key)
|
|
163
|
+
|
|
164
|
+
def sign_key(self, params: 'SignKeyParams', idempotency_key: str | None = None) -> 'SignKeyResponse':
|
|
165
|
+
"""
|
|
166
|
+
Signs a license key for offline (air-gapped) validation.
|
|
167
|
+
Requires admin API key scope and Standard plan.
|
|
168
|
+
:param params: Signing parameters (hostId is required).
|
|
169
|
+
:param idempotency_key: Optional unique identifier to ensure request idempotency.
|
|
170
|
+
:returns: The signed license file.
|
|
171
|
+
"""
|
|
172
|
+
return self._handle_request('POST', '/key/sign', params, idempotency_key=idempotency_key)
|
|
173
|
+
|
|
146
174
|
# Customer Management Methods
|
|
147
175
|
|
|
148
|
-
def create_customer(self, params: CreateCustomerParams, idempotency_key: str = None) -> CreateCustomerResponse:
|
|
176
|
+
def create_customer(self, params: CreateCustomerParams, idempotency_key: str | None = None) -> CreateCustomerResponse:
|
|
149
177
|
"""
|
|
150
178
|
Creates a new customer.
|
|
151
179
|
:param params: Parameters for creating the customer.
|
|
@@ -171,7 +199,7 @@ class KeyMint:
|
|
|
171
199
|
query_params = {'customerId': params['customerId']}
|
|
172
200
|
return self._handle_request('GET', '/customer/by-id', query_params=query_params)
|
|
173
201
|
|
|
174
|
-
def update_customer(self, params: UpdateCustomerParams, idempotency_key: str = None) -> UpdateCustomerResponse:
|
|
202
|
+
def update_customer(self, params: UpdateCustomerParams, idempotency_key: str | None = None) -> UpdateCustomerResponse:
|
|
175
203
|
"""
|
|
176
204
|
Updates an existing customer's information.
|
|
177
205
|
:param params: Parameters for updating the customer.
|
|
@@ -180,7 +208,7 @@ class KeyMint:
|
|
|
180
208
|
"""
|
|
181
209
|
return self._handle_request('PUT', '/customer/by-id', params, idempotency_key=idempotency_key)
|
|
182
210
|
|
|
183
|
-
def delete_customer(self, params: DeleteCustomerParams, idempotency_key: str = None) -> DeleteCustomerResponse:
|
|
211
|
+
def delete_customer(self, params: DeleteCustomerParams, idempotency_key: str | None = None) -> DeleteCustomerResponse:
|
|
184
212
|
"""
|
|
185
213
|
Permanently deletes a customer and all associated license keys.
|
|
186
214
|
:param params: Parameters containing the customer ID.
|
|
@@ -190,16 +218,16 @@ class KeyMint:
|
|
|
190
218
|
query_params = {'customerId': params['customerId']}
|
|
191
219
|
return self._handle_request('DELETE', '/customer/by-id', query_params=query_params, idempotency_key=idempotency_key)
|
|
192
220
|
|
|
193
|
-
def get_customer_with_keys(self, params: GetCustomerWithKeysParams) ->
|
|
221
|
+
def get_customer_with_keys(self, params: GetCustomerWithKeysParams) -> List[Dict[str, Any]]:
|
|
194
222
|
"""
|
|
195
|
-
Retrieves
|
|
223
|
+
Retrieves license keys belonging to a specific customer.
|
|
196
224
|
:param params: Parameters containing the customer ID.
|
|
197
|
-
:returns:
|
|
225
|
+
:returns: A flat list of license key objects.
|
|
198
226
|
"""
|
|
199
227
|
query_params = {'customerId': params['customerId']}
|
|
200
228
|
return self._handle_request('GET', '/customer/keys', query_params=query_params)
|
|
201
229
|
|
|
202
|
-
def toggle_customer_status(self, params: ToggleCustomerStatusParams, idempotency_key: str = None) -> ToggleCustomerStatusResponse:
|
|
230
|
+
def toggle_customer_status(self, params: ToggleCustomerStatusParams, idempotency_key: str | None = None) -> ToggleCustomerStatusResponse:
|
|
203
231
|
"""
|
|
204
232
|
Toggles the active status of a customer account (disable or enable).
|
|
205
233
|
:param params: Parameters containing the customer ID.
|
|
@@ -219,8 +247,8 @@ class KeyMint:
|
|
|
219
247
|
:param tolerance_seconds: Time tolerance in seconds to prevent replay attacks. Defaults to 300 (5 minutes).
|
|
220
248
|
:returns: True if the signature is valid, False otherwise.
|
|
221
249
|
"""
|
|
222
|
-
import hmac
|
|
223
250
|
import hashlib
|
|
251
|
+
import hmac
|
|
224
252
|
import time
|
|
225
253
|
|
|
226
254
|
if not header or not secret:
|
|
@@ -253,7 +281,7 @@ class KeyMint:
|
|
|
253
281
|
return False
|
|
254
282
|
|
|
255
283
|
# Verify HMAC signature
|
|
256
|
-
signable_content = f"{timestamp_str}.{payload}".encode(
|
|
284
|
+
signable_content = f"{timestamp_str}.{payload}".encode()
|
|
257
285
|
expected_signature = hmac.new(
|
|
258
286
|
secret.encode("utf-8"),
|
|
259
287
|
signable_content,
|
|
@@ -265,4 +293,3 @@ class KeyMint:
|
|
|
265
293
|
except Exception:
|
|
266
294
|
return False
|
|
267
295
|
|
|
268
|
-
|
|
@@ -10,14 +10,13 @@ import hashlib
|
|
|
10
10
|
import hmac
|
|
11
11
|
import os
|
|
12
12
|
import platform
|
|
13
|
-
import subprocess
|
|
14
|
-
import uuid
|
|
15
13
|
import re
|
|
14
|
+
import subprocess
|
|
16
15
|
import time
|
|
16
|
+
import uuid
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from typing import Optional
|
|
19
19
|
|
|
20
|
-
|
|
21
20
|
# ─── Garbage Detection ──────────────────────────────────────────────────
|
|
22
21
|
|
|
23
22
|
_GARBAGE_PATTERNS = [
|
|
@@ -54,7 +53,7 @@ def _hash(value: str) -> str:
|
|
|
54
53
|
|
|
55
54
|
# ─── Fingerprint Layers ────────────────────────────────────────────────
|
|
56
55
|
|
|
57
|
-
def _get_bios_uuid() ->
|
|
56
|
+
def _get_bios_uuid() -> str | None:
|
|
58
57
|
"""Layer 1: BIOS / Hardware UUID."""
|
|
59
58
|
system = platform.system()
|
|
60
59
|
try:
|
|
@@ -82,7 +81,7 @@ def _get_bios_uuid() -> Optional[str]:
|
|
|
82
81
|
return None
|
|
83
82
|
|
|
84
83
|
|
|
85
|
-
def _get_os_machine_id() ->
|
|
84
|
+
def _get_os_machine_id() -> str | None:
|
|
86
85
|
"""Layer 2: OS-level persistent machine ID."""
|
|
87
86
|
system = platform.system()
|
|
88
87
|
try:
|
|
@@ -112,11 +111,11 @@ def _get_os_machine_id() -> Optional[str]:
|
|
|
112
111
|
return None
|
|
113
112
|
|
|
114
113
|
|
|
115
|
-
def _get_primary_mac() ->
|
|
114
|
+
def _get_primary_mac() -> str | None:
|
|
116
115
|
"""Layer 3: Primary network interface MAC address."""
|
|
117
116
|
try:
|
|
118
|
-
import socket
|
|
119
117
|
import fcntl
|
|
118
|
+
import socket
|
|
120
119
|
import struct
|
|
121
120
|
except ImportError:
|
|
122
121
|
pass
|
|
@@ -136,7 +135,7 @@ def _get_primary_mac() -> Optional[str]:
|
|
|
136
135
|
|
|
137
136
|
# ─── Public API ─────────────────────────────────────────────────────────
|
|
138
137
|
|
|
139
|
-
def get_machine_id() ->
|
|
138
|
+
def get_machine_id() -> str | None:
|
|
140
139
|
"""
|
|
141
140
|
Best-effort hardware fingerprint. Attempts to read the machine's
|
|
142
141
|
BIOS/System UUID, then falls back through OS-level IDs and network
|
|
@@ -162,7 +161,7 @@ def get_machine_id() -> Optional[str]:
|
|
|
162
161
|
return None
|
|
163
162
|
|
|
164
163
|
|
|
165
|
-
def get_or_create_installation_id(storage_path:
|
|
164
|
+
def get_or_create_installation_id(storage_path: str | None = None) -> str:
|
|
166
165
|
"""
|
|
167
166
|
Returns a guaranteed-unique, guaranteed-stable installation identifier.
|
|
168
167
|
On first call, generates a UUIDv4 seeded with whatever hardware info
|
|
@@ -218,5 +217,5 @@ def generate_session_signature(session_id: str, nonce: str, session_secret: str)
|
|
|
218
217
|
A 64-character hexadecimal signature string.
|
|
219
218
|
"""
|
|
220
219
|
key_bytes = session_secret.encode('utf-8')
|
|
221
|
-
msg_bytes = f"{session_id}:{nonce}".encode(
|
|
220
|
+
msg_bytes = f"{session_id}:{nonce}".encode()
|
|
222
221
|
return hmac.new(key_bytes, msg_bytes, hashlib.sha256).hexdigest()
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, TypedDict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NewCustomer(TypedDict):
|
|
5
|
+
name: str
|
|
6
|
+
email: str | None
|
|
7
|
+
|
|
8
|
+
class KeyFormat(TypedDict):
|
|
9
|
+
sections: int | None
|
|
10
|
+
sectionLength: int | None
|
|
11
|
+
separator: str | None
|
|
12
|
+
charset: str | None
|
|
13
|
+
prefix: str | None
|
|
14
|
+
suffix: str | None
|
|
15
|
+
case: str | None # 'upper' | 'lower' | 'mixed'
|
|
16
|
+
|
|
17
|
+
class CreateKeyParams(TypedDict):
|
|
18
|
+
productId: str
|
|
19
|
+
maxActivations: str | None
|
|
20
|
+
expiryDate: str | None
|
|
21
|
+
customerId: str | None
|
|
22
|
+
versionId: str | None
|
|
23
|
+
metadata: dict[str, Any] | None
|
|
24
|
+
newCustomer: NewCustomer | None
|
|
25
|
+
allowedHosts: list[str] | None
|
|
26
|
+
format: KeyFormat | None
|
|
27
|
+
amountKeys: str | None
|
|
28
|
+
licenseType: str | None # 'node-locked' | 'floating'
|
|
29
|
+
maxConcurrentSessions: int | None
|
|
30
|
+
heartbeatInterval: int | None
|
|
31
|
+
sessionLeaseDuration: int | None
|
|
32
|
+
|
|
33
|
+
class CreateKeyResponse(TypedDict, total=False):
|
|
34
|
+
code: int
|
|
35
|
+
key: str
|
|
36
|
+
keys: list[str]
|
|
37
|
+
|
|
38
|
+
class KeyMintApiError(Exception):
|
|
39
|
+
def __init__(self, message: str, code: int, status: int | None = None):
|
|
40
|
+
super().__init__(message)
|
|
41
|
+
self.message = message
|
|
42
|
+
self.code = code
|
|
43
|
+
self.status = status
|
|
44
|
+
|
|
45
|
+
class ActivateKeyParams(TypedDict):
|
|
46
|
+
productId: str
|
|
47
|
+
licenseKey: str
|
|
48
|
+
hostId: str | None
|
|
49
|
+
deviceTag: str | None
|
|
50
|
+
licensee: dict[str, str] | None # { name, email }
|
|
51
|
+
version: str | None
|
|
52
|
+
|
|
53
|
+
class ActivateKeyResponse(TypedDict):
|
|
54
|
+
code: int
|
|
55
|
+
message: str
|
|
56
|
+
licenseeName: str | None
|
|
57
|
+
licenseeEmail: str | None
|
|
58
|
+
metadata: dict[str, Any] | None
|
|
59
|
+
versionId: str | None
|
|
60
|
+
version: dict[str, Any] | None
|
|
61
|
+
allowedHosts: list[str] | None
|
|
62
|
+
|
|
63
|
+
class DeactivateKeyParams(TypedDict):
|
|
64
|
+
productId: str
|
|
65
|
+
licenseKey: str
|
|
66
|
+
hostId: str | None
|
|
67
|
+
|
|
68
|
+
class DeactivateKeyResponse(TypedDict):
|
|
69
|
+
message: str
|
|
70
|
+
code: int
|
|
71
|
+
devicesRemoved: int
|
|
72
|
+
|
|
73
|
+
class DeviceDetails(TypedDict):
|
|
74
|
+
hostId: str
|
|
75
|
+
deviceTag: str | None
|
|
76
|
+
ipAddress: str | None
|
|
77
|
+
activationTime: str
|
|
78
|
+
|
|
79
|
+
class LicenseDetails(TypedDict):
|
|
80
|
+
id: str
|
|
81
|
+
key: str
|
|
82
|
+
productId: str
|
|
83
|
+
maxActivations: int
|
|
84
|
+
activations: int
|
|
85
|
+
devices: list[DeviceDetails]
|
|
86
|
+
activated: bool
|
|
87
|
+
expirationDate: str | None
|
|
88
|
+
versionId: str | None
|
|
89
|
+
metadata: dict[str, Any] | None
|
|
90
|
+
allowedHosts: list[str] | None
|
|
91
|
+
version: dict[str, Any] | None
|
|
92
|
+
|
|
93
|
+
class CustomerDetails(TypedDict):
|
|
94
|
+
id: str
|
|
95
|
+
name: str | None
|
|
96
|
+
email: str | None
|
|
97
|
+
active: bool
|
|
98
|
+
|
|
99
|
+
class GetKeyParams(TypedDict):
|
|
100
|
+
productId: str
|
|
101
|
+
licenseKey: str
|
|
102
|
+
|
|
103
|
+
class GetKeyResponse(TypedDict):
|
|
104
|
+
data: dict[str, Any]
|
|
105
|
+
code: int
|
|
106
|
+
|
|
107
|
+
class BlockKeyParams(TypedDict):
|
|
108
|
+
productId: str
|
|
109
|
+
licenseKey: str
|
|
110
|
+
|
|
111
|
+
class BlockKeyResponse(TypedDict):
|
|
112
|
+
message: str
|
|
113
|
+
code: int
|
|
114
|
+
|
|
115
|
+
class UnblockKeyParams(TypedDict):
|
|
116
|
+
productId: str
|
|
117
|
+
licenseKey: str
|
|
118
|
+
|
|
119
|
+
class UnblockKeyResponse(TypedDict):
|
|
120
|
+
message: str
|
|
121
|
+
code: int
|
|
122
|
+
|
|
123
|
+
# Customer Management Types
|
|
124
|
+
|
|
125
|
+
class CreateCustomerParams(TypedDict):
|
|
126
|
+
name: str
|
|
127
|
+
email: str | None
|
|
128
|
+
|
|
129
|
+
class CreateCustomerResponse(TypedDict):
|
|
130
|
+
action: str
|
|
131
|
+
status: bool
|
|
132
|
+
message: str
|
|
133
|
+
data: dict[str, Any]
|
|
134
|
+
code: int
|
|
135
|
+
|
|
136
|
+
class GetAllCustomersParams(TypedDict):
|
|
137
|
+
page: int | None
|
|
138
|
+
limit: int | None
|
|
139
|
+
email: str | None
|
|
140
|
+
|
|
141
|
+
class PaginationMeta(TypedDict):
|
|
142
|
+
total: int
|
|
143
|
+
page: int
|
|
144
|
+
limit: int
|
|
145
|
+
totalPages: int
|
|
146
|
+
|
|
147
|
+
class GetAllCustomersResponse(TypedDict):
|
|
148
|
+
action: str
|
|
149
|
+
status: bool
|
|
150
|
+
data: list[dict[str, Any]]
|
|
151
|
+
meta: PaginationMeta | None
|
|
152
|
+
code: int
|
|
153
|
+
|
|
154
|
+
class GetCustomerByIdParams(TypedDict):
|
|
155
|
+
customerId: str
|
|
156
|
+
|
|
157
|
+
class GetCustomerByIdResponse(TypedDict):
|
|
158
|
+
action: str
|
|
159
|
+
status: bool
|
|
160
|
+
data: list[dict[str, Any]]
|
|
161
|
+
code: int
|
|
162
|
+
|
|
163
|
+
class UpdateCustomerParams(TypedDict):
|
|
164
|
+
customerId: str
|
|
165
|
+
name: str | None # Optional: Updated customer name
|
|
166
|
+
email: str | None # Optional: Updated customer email
|
|
167
|
+
|
|
168
|
+
class UpdateCustomerResponse(TypedDict):
|
|
169
|
+
action: str
|
|
170
|
+
status: bool
|
|
171
|
+
code: int
|
|
172
|
+
|
|
173
|
+
class DeleteCustomerParams(TypedDict):
|
|
174
|
+
customerId: str
|
|
175
|
+
|
|
176
|
+
class DeleteCustomerResponse(TypedDict):
|
|
177
|
+
action: str
|
|
178
|
+
status: bool
|
|
179
|
+
code: int
|
|
180
|
+
|
|
181
|
+
class ToggleCustomerStatusParams(TypedDict):
|
|
182
|
+
customerId: str
|
|
183
|
+
|
|
184
|
+
class ToggleCustomerStatusResponse(TypedDict, total=False):
|
|
185
|
+
action: str
|
|
186
|
+
status: bool
|
|
187
|
+
message: str
|
|
188
|
+
code: int
|
|
189
|
+
customerName: str
|
|
190
|
+
active: bool
|
|
191
|
+
|
|
192
|
+
class CustomerLicenseKey(TypedDict):
|
|
193
|
+
id: str
|
|
194
|
+
key: str
|
|
195
|
+
productId: str
|
|
196
|
+
maxActivations: int
|
|
197
|
+
activations: int
|
|
198
|
+
activated: bool
|
|
199
|
+
expirationDate: str | None
|
|
200
|
+
versionId: str | None
|
|
201
|
+
metadata: dict[str, Any] | None
|
|
202
|
+
allowedHosts: list[str] | None
|
|
203
|
+
|
|
204
|
+
class GetCustomerWithKeysParams(TypedDict):
|
|
205
|
+
customerId: str
|
|
206
|
+
|
|
207
|
+
# GetCustomerWithKeys returns a flat LicenseKey[] — no wrapper object.
|
|
208
|
+
# Use List[Dict[str, Any]] for the response type.
|
|
209
|
+
|
|
210
|
+
class FloatingCheckoutParams(TypedDict):
|
|
211
|
+
productId: str
|
|
212
|
+
licenseKey: str
|
|
213
|
+
hostId: str
|
|
214
|
+
deviceTag: str | None
|
|
215
|
+
userIdentifier: str | None
|
|
216
|
+
apiKey: str | None
|
|
217
|
+
timestamp: Any | None
|
|
218
|
+
signature: str | None
|
|
219
|
+
|
|
220
|
+
class FloatingCheckoutResponse(TypedDict):
|
|
221
|
+
code: int
|
|
222
|
+
message: str
|
|
223
|
+
sessionId: str
|
|
224
|
+
sessionSecret: str
|
|
225
|
+
nextNonce: str
|
|
226
|
+
expiresAt: str
|
|
227
|
+
heartbeatInterval: int
|
|
228
|
+
metadata: dict[str, Any] | None
|
|
229
|
+
currentSessions: int | None
|
|
230
|
+
maxSessions: int | None
|
|
231
|
+
licenseeName: str | None
|
|
232
|
+
licenseeEmail: str | None
|
|
233
|
+
|
|
234
|
+
class FloatingHeartbeatParams(TypedDict):
|
|
235
|
+
productId: str
|
|
236
|
+
licenseKey: str
|
|
237
|
+
sessionId: str
|
|
238
|
+
timestamp: Any # rotating nonce (nextNonce) received from previous response
|
|
239
|
+
signature: str
|
|
240
|
+
apiKey: str | None
|
|
241
|
+
|
|
242
|
+
class FloatingHeartbeatResponse(TypedDict):
|
|
243
|
+
code: int
|
|
244
|
+
message: str
|
|
245
|
+
expiresAt: str
|
|
246
|
+
nextNonce: str
|
|
247
|
+
|
|
248
|
+
class FloatingCheckinParams(TypedDict):
|
|
249
|
+
productId: str
|
|
250
|
+
licenseKey: str
|
|
251
|
+
sessionId: str
|
|
252
|
+
timestamp: Any # rotating nonce (nextNonce) received from previous response
|
|
253
|
+
signature: str
|
|
254
|
+
apiKey: str | None
|
|
255
|
+
|
|
256
|
+
class FloatingCheckinResponse(TypedDict):
|
|
257
|
+
code: int
|
|
258
|
+
message: str
|
|
259
|
+
|
|
260
|
+
class UpdateKeyParams(TypedDict):
|
|
261
|
+
productId: str
|
|
262
|
+
licenseKey: str
|
|
263
|
+
maxActivations: Any | None # string or number
|
|
264
|
+
expiryDate: str | None
|
|
265
|
+
customerId: str | None
|
|
266
|
+
newCustomer: NewCustomer | None
|
|
267
|
+
metadata: dict[str, Any] | None
|
|
268
|
+
versionId: str | None
|
|
269
|
+
allowedHosts: list[str] | None
|
|
270
|
+
licenseType: str | None # 'node-locked' | 'floating'
|
|
271
|
+
maxConcurrentSessions: int | None
|
|
272
|
+
heartbeatInterval: int | None
|
|
273
|
+
sessionLeaseDuration: int | None
|
|
274
|
+
|
|
275
|
+
class UpdateKeyResponse(TypedDict):
|
|
276
|
+
code: int
|
|
277
|
+
message: str
|
|
278
|
+
affectedCount: int | None
|
|
279
|
+
|
|
280
|
+
class SignKeyParams(TypedDict):
|
|
281
|
+
productId: str
|
|
282
|
+
licenseKey: str
|
|
283
|
+
hostId: str
|
|
284
|
+
ttl: int | None
|
|
285
|
+
|
|
286
|
+
class SignKeyResponse(TypedDict):
|
|
287
|
+
file: str # Serialized signed license file returned by the API
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: keymint
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: License key validation, activation, and management for Python. Supports node-locking, offline licensing, and hardware fingerprinting.
|
|
5
5
|
Home-page: https://github.com/keymint-dev/keymint-python
|
|
6
6
|
Author: KeyMint
|
|
@@ -16,18 +16,14 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
16
16
|
Classifier: Topic :: System :: Software Distribution
|
|
17
17
|
Classifier: License :: OSI Approved :: MIT License
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
23
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
24
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
25
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
26
22
|
Classifier: Operating System :: OS Independent
|
|
27
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.10
|
|
28
24
|
Description-Content-Type: text/markdown
|
|
29
25
|
License-File: LICENSE
|
|
30
|
-
Requires-Dist: requests>=2.
|
|
26
|
+
Requires-Dist: requests>=2.32.4
|
|
31
27
|
Provides-Extra: dev
|
|
32
28
|
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
33
29
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -107,8 +103,10 @@ Keymint provides utilities to uniquely identify machines for node-locking:
|
|
|
107
103
|
| `activate_key` | Activates a license key for a device. |
|
|
108
104
|
| `deactivate_key` | Deactivates a device from a license key. |
|
|
109
105
|
| `get_key` | Retrieves detailed information about a key. |
|
|
106
|
+
| `update_key` | Updates an existing license key. |
|
|
110
107
|
| `block_key` | Blocks a license key. |
|
|
111
108
|
| `unblock_key` | Unblocks a previously blocked license key. |
|
|
109
|
+
| `sign_key` | Signs a key for offline validation using an admin API key.|
|
|
112
110
|
| `floating_checkout` | Checks out a floating license seat. |
|
|
113
111
|
| `floating_heartbeat`| Sends a heartbeat to keep a session alive. |
|
|
114
112
|
| `floating_checkin` | Checks in a session, releasing the seat. |
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from setuptools import
|
|
1
|
+
from setuptools import find_packages, setup
|
|
2
2
|
|
|
3
3
|
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
4
|
long_description = fh.read()
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name="keymint",
|
|
8
|
-
version="2.
|
|
8
|
+
version="2.5.0",
|
|
9
9
|
author="KeyMint",
|
|
10
10
|
author_email="cliff@keymint.dev",
|
|
11
11
|
description="License key validation, activation, and management for Python. Supports node-locking, offline licensing, and hardware fingerprinting.",
|
|
@@ -26,19 +26,15 @@ setup(
|
|
|
26
26
|
"Topic :: System :: Software Distribution",
|
|
27
27
|
"License :: OSI Approved :: MIT License",
|
|
28
28
|
"Programming Language :: Python :: 3",
|
|
29
|
-
"Programming Language :: Python :: 3.6",
|
|
30
|
-
"Programming Language :: Python :: 3.7",
|
|
31
|
-
"Programming Language :: Python :: 3.8",
|
|
32
|
-
"Programming Language :: Python :: 3.9",
|
|
33
29
|
"Programming Language :: Python :: 3.10",
|
|
34
30
|
"Programming Language :: Python :: 3.11",
|
|
35
31
|
"Programming Language :: Python :: 3.12",
|
|
36
32
|
"Operating System :: OS Independent",
|
|
37
33
|
],
|
|
38
34
|
keywords="license-key validation activation node-lock hardware-id copy-protection product-key drm offline-licensing keymint license licensing api",
|
|
39
|
-
python_requires='>=3.
|
|
35
|
+
python_requires='>=3.10',
|
|
40
36
|
install_requires=[
|
|
41
|
-
'requests>=2.
|
|
37
|
+
'requests>=2.32.4',
|
|
42
38
|
],
|
|
43
39
|
extras_require={
|
|
44
40
|
'dev': [
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from unittest.mock import Mock, patch
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from keymint import KeyMint, KeyMintApiError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def response(payload, status=200):
|
|
10
|
+
result = Mock()
|
|
11
|
+
result.json.return_value = payload
|
|
12
|
+
result.status_code = status
|
|
13
|
+
if status >= 400:
|
|
14
|
+
error = requests.exceptions.HTTPError()
|
|
15
|
+
error.response = result
|
|
16
|
+
result.raise_for_status.side_effect = error
|
|
17
|
+
return result
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@patch("keymint.requests.get")
|
|
21
|
+
def test_get_key_uses_header_not_query(mock_get):
|
|
22
|
+
mock_get.return_value = response({"code": 0, "data": {"license": {"productId": "product 123"}}})
|
|
23
|
+
|
|
24
|
+
KeyMint("readonly_test").get_key({"productId": "product 123", "licenseKey": "secret/license+key"})
|
|
25
|
+
|
|
26
|
+
_, kwargs = mock_get.call_args
|
|
27
|
+
assert kwargs["params"] == {"productId": "product 123"}
|
|
28
|
+
assert kwargs["headers"]["x-license-key"] == "secret/license+key"
|
|
29
|
+
assert "licenseKey" not in kwargs["params"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@patch("keymint.requests.post")
|
|
33
|
+
def test_sign_key_returns_serialized_file(mock_post):
|
|
34
|
+
mock_post.return_value = response({"file": '{"signedKey":"signature","keyId":"key_123"}'})
|
|
35
|
+
|
|
36
|
+
result = KeyMint("admin_test").sign_key({
|
|
37
|
+
"productId": "product_123", "licenseKey": "license_123", "hostId": "host_123"
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
assert isinstance(result["file"], str)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@patch("keymint.requests.post")
|
|
44
|
+
def test_nested_error_preserves_server_message(mock_post):
|
|
45
|
+
mock_post.return_value = response({
|
|
46
|
+
"success": False,
|
|
47
|
+
"error": {"code": "CUSTOMER_EMAIL_EXISTS", "message": "Customer email already exists in team"},
|
|
48
|
+
"code": 1,
|
|
49
|
+
}, 409)
|
|
50
|
+
|
|
51
|
+
with pytest.raises(KeyMintApiError, match="Customer email already exists in team"):
|
|
52
|
+
KeyMint("admin_test").create_key({"productId": "product_123"})
|
keymint-2.3.1/keymint/types.py
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
from typing import TypedDict, Optional, List, Dict, Any
|
|
2
|
-
|
|
3
|
-
class NewCustomer(TypedDict):
|
|
4
|
-
name: str
|
|
5
|
-
email: Optional[str]
|
|
6
|
-
|
|
7
|
-
class CreateKeyParams(TypedDict):
|
|
8
|
-
productId: str
|
|
9
|
-
maxActivations: Optional[str]
|
|
10
|
-
expiryDate: Optional[str]
|
|
11
|
-
customerId: Optional[str]
|
|
12
|
-
versionId: Optional[str]
|
|
13
|
-
metadata: Optional[Dict[str, Any]]
|
|
14
|
-
newCustomer: Optional[NewCustomer]
|
|
15
|
-
allowedHosts: Optional[List[str]]
|
|
16
|
-
|
|
17
|
-
class CreateKeyResponse(TypedDict):
|
|
18
|
-
code: int
|
|
19
|
-
key: str
|
|
20
|
-
|
|
21
|
-
class KeyMintApiError(Exception):
|
|
22
|
-
def __init__(self, message: str, code: int, status: Optional[int] = None):
|
|
23
|
-
super().__init__(message)
|
|
24
|
-
self.message = message
|
|
25
|
-
self.code = code
|
|
26
|
-
self.status = status
|
|
27
|
-
|
|
28
|
-
class ActivateKeyParams(TypedDict):
|
|
29
|
-
productId: str
|
|
30
|
-
licenseKey: str
|
|
31
|
-
hostId: Optional[str]
|
|
32
|
-
deviceTag: Optional[str]
|
|
33
|
-
|
|
34
|
-
class ActivateKeyResponse(TypedDict):
|
|
35
|
-
code: int
|
|
36
|
-
message: str
|
|
37
|
-
licenseeName: Optional[str]
|
|
38
|
-
licenseeEmail: Optional[str]
|
|
39
|
-
metadata: Optional[Dict[str, Any]]
|
|
40
|
-
versionId: Optional[str]
|
|
41
|
-
version: Optional[Dict[str, Any]]
|
|
42
|
-
allowedHosts: Optional[List[str]]
|
|
43
|
-
|
|
44
|
-
class DeactivateKeyParams(TypedDict):
|
|
45
|
-
productId: str
|
|
46
|
-
licenseKey: str
|
|
47
|
-
hostId: Optional[str]
|
|
48
|
-
|
|
49
|
-
class DeactivateKeyResponse(TypedDict):
|
|
50
|
-
message: str
|
|
51
|
-
code: int
|
|
52
|
-
|
|
53
|
-
class DeviceDetails(TypedDict):
|
|
54
|
-
hostId: str
|
|
55
|
-
deviceTag: Optional[str]
|
|
56
|
-
ipAddress: Optional[str]
|
|
57
|
-
activationTime: str
|
|
58
|
-
|
|
59
|
-
class LicenseDetails(TypedDict):
|
|
60
|
-
id: str
|
|
61
|
-
key: str
|
|
62
|
-
productId: str
|
|
63
|
-
maxActivations: int
|
|
64
|
-
activations: int
|
|
65
|
-
devices: List[DeviceDetails]
|
|
66
|
-
activated: bool
|
|
67
|
-
expirationDate: Optional[str]
|
|
68
|
-
versionId: Optional[str]
|
|
69
|
-
metadata: Optional[Dict[str, Any]]
|
|
70
|
-
allowedHosts: Optional[List[str]]
|
|
71
|
-
version: Optional[Dict[str, Any]]
|
|
72
|
-
|
|
73
|
-
class CustomerDetails(TypedDict):
|
|
74
|
-
id: str
|
|
75
|
-
name: Optional[str]
|
|
76
|
-
email: Optional[str]
|
|
77
|
-
active: bool
|
|
78
|
-
|
|
79
|
-
class GetKeyParams(TypedDict):
|
|
80
|
-
productId: str
|
|
81
|
-
licenseKey: str
|
|
82
|
-
|
|
83
|
-
class GetKeyResponse(TypedDict):
|
|
84
|
-
data: Dict[str, Any]
|
|
85
|
-
code: int
|
|
86
|
-
|
|
87
|
-
class BlockKeyParams(TypedDict):
|
|
88
|
-
productId: str
|
|
89
|
-
licenseKey: str
|
|
90
|
-
|
|
91
|
-
class BlockKeyResponse(TypedDict):
|
|
92
|
-
message: str
|
|
93
|
-
code: int
|
|
94
|
-
|
|
95
|
-
class UnblockKeyParams(TypedDict):
|
|
96
|
-
productId: str
|
|
97
|
-
licenseKey: str
|
|
98
|
-
|
|
99
|
-
class UnblockKeyResponse(TypedDict):
|
|
100
|
-
message: str
|
|
101
|
-
code: int
|
|
102
|
-
|
|
103
|
-
# Customer Management Types
|
|
104
|
-
|
|
105
|
-
class CreateCustomerParams(TypedDict):
|
|
106
|
-
name: str
|
|
107
|
-
email: str
|
|
108
|
-
|
|
109
|
-
class CreateCustomerResponse(TypedDict):
|
|
110
|
-
action: str
|
|
111
|
-
status: bool
|
|
112
|
-
message: str
|
|
113
|
-
data: Dict[str, Any]
|
|
114
|
-
code: int
|
|
115
|
-
|
|
116
|
-
class GetAllCustomersParams(TypedDict):
|
|
117
|
-
page: Optional[int]
|
|
118
|
-
limit: Optional[int]
|
|
119
|
-
email: Optional[str]
|
|
120
|
-
|
|
121
|
-
class PaginationMeta(TypedDict):
|
|
122
|
-
total: int
|
|
123
|
-
page: int
|
|
124
|
-
limit: int
|
|
125
|
-
totalPages: int
|
|
126
|
-
|
|
127
|
-
class GetAllCustomersResponse(TypedDict):
|
|
128
|
-
action: str
|
|
129
|
-
status: bool
|
|
130
|
-
data: List[Dict[str, Any]]
|
|
131
|
-
meta: Optional[PaginationMeta]
|
|
132
|
-
code: int
|
|
133
|
-
|
|
134
|
-
class GetCustomerByIdParams(TypedDict):
|
|
135
|
-
customerId: str
|
|
136
|
-
|
|
137
|
-
class GetCustomerByIdResponse(TypedDict):
|
|
138
|
-
action: str
|
|
139
|
-
status: bool
|
|
140
|
-
data: List[Dict[str, Any]]
|
|
141
|
-
code: int
|
|
142
|
-
|
|
143
|
-
class UpdateCustomerParams(TypedDict):
|
|
144
|
-
name: str
|
|
145
|
-
email: str
|
|
146
|
-
customerId: str
|
|
147
|
-
|
|
148
|
-
class UpdateCustomerResponse(TypedDict):
|
|
149
|
-
action: str
|
|
150
|
-
status: bool
|
|
151
|
-
code: int
|
|
152
|
-
|
|
153
|
-
class DeleteCustomerParams(TypedDict):
|
|
154
|
-
customerId: str
|
|
155
|
-
|
|
156
|
-
class DeleteCustomerResponse(TypedDict):
|
|
157
|
-
action: str
|
|
158
|
-
status: bool
|
|
159
|
-
code: int
|
|
160
|
-
|
|
161
|
-
class ToggleCustomerStatusParams(TypedDict):
|
|
162
|
-
customerId: str
|
|
163
|
-
|
|
164
|
-
class ToggleCustomerStatusResponse(TypedDict):
|
|
165
|
-
action: str
|
|
166
|
-
status: bool
|
|
167
|
-
message: str
|
|
168
|
-
code: int
|
|
169
|
-
|
|
170
|
-
class CustomerLicenseKey(TypedDict):
|
|
171
|
-
id: str
|
|
172
|
-
key: str
|
|
173
|
-
productId: str
|
|
174
|
-
maxActivations: int
|
|
175
|
-
activations: int
|
|
176
|
-
activated: bool
|
|
177
|
-
expirationDate: Optional[str]
|
|
178
|
-
versionId: Optional[str]
|
|
179
|
-
metadata: Optional[Dict[str, Any]]
|
|
180
|
-
allowedHosts: Optional[List[str]]
|
|
181
|
-
|
|
182
|
-
class GetCustomerWithKeysParams(TypedDict):
|
|
183
|
-
customerId: str
|
|
184
|
-
|
|
185
|
-
class GetCustomerWithKeysResponse(TypedDict):
|
|
186
|
-
action: str
|
|
187
|
-
status: bool
|
|
188
|
-
data: Dict[str, Any] # Contains customer and licenseKeys
|
|
189
|
-
code: int
|
|
190
|
-
|
|
191
|
-
class FloatingCheckoutParams(TypedDict):
|
|
192
|
-
productId: str
|
|
193
|
-
licenseKey: str
|
|
194
|
-
hostId: str
|
|
195
|
-
deviceTag: Optional[str]
|
|
196
|
-
userIdentifier: Optional[str]
|
|
197
|
-
apiKey: Optional[str]
|
|
198
|
-
|
|
199
|
-
class FloatingCheckoutResponse(TypedDict):
|
|
200
|
-
code: int
|
|
201
|
-
message: str
|
|
202
|
-
sessionId: str
|
|
203
|
-
sessionSecret: str
|
|
204
|
-
nextNonce: str
|
|
205
|
-
expiresAt: str
|
|
206
|
-
heartbeatInterval: int
|
|
207
|
-
metadata: Optional[Dict[str, Any]]
|
|
208
|
-
currentSessions: Optional[int]
|
|
209
|
-
maxSessions: Optional[int]
|
|
210
|
-
licenseeName: Optional[str]
|
|
211
|
-
licenseeEmail: Optional[str]
|
|
212
|
-
|
|
213
|
-
class FloatingHeartbeatParams(TypedDict):
|
|
214
|
-
productId: str
|
|
215
|
-
licenseKey: str
|
|
216
|
-
sessionId: str
|
|
217
|
-
timestamp: Any # rotating nonce (nextNonce) received from previous response
|
|
218
|
-
signature: str
|
|
219
|
-
apiKey: Optional[str]
|
|
220
|
-
|
|
221
|
-
class FloatingHeartbeatResponse(TypedDict):
|
|
222
|
-
code: int
|
|
223
|
-
message: str
|
|
224
|
-
expiresAt: str
|
|
225
|
-
nextNonce: str
|
|
226
|
-
|
|
227
|
-
class FloatingCheckinParams(TypedDict):
|
|
228
|
-
productId: str
|
|
229
|
-
licenseKey: str
|
|
230
|
-
sessionId: str
|
|
231
|
-
timestamp: Any # rotating nonce (nextNonce) received from previous response
|
|
232
|
-
signature: str
|
|
233
|
-
apiKey: Optional[str]
|
|
234
|
-
|
|
235
|
-
class FloatingCheckinResponse(TypedDict):
|
|
236
|
-
code: int
|
|
237
|
-
message: str
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|