authsec-sdk 4.0.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.
- authsec_sdk/__init__.py +44 -0
- authsec_sdk/ciba_sdk.py +196 -0
- authsec_sdk/core.py +1049 -0
- authsec_sdk/spiffe_workload_api/__init__.py +31 -0
- authsec_sdk/spiffe_workload_api/api/__init__.py +9 -0
- authsec_sdk/spiffe_workload_api/api/workload.proto +126 -0
- authsec_sdk/spiffe_workload_api/api/workload_pb2.py +81 -0
- authsec_sdk/spiffe_workload_api/api/workload_pb2_grpc.py +278 -0
- authsec_sdk/spiffe_workload_api/client.py +437 -0
- authsec_sdk/spiffe_workload_api/simple.py +248 -0
- authsec_sdk/spire_sdk.py +368 -0
- authsec_sdk-4.0.0.dist-info/METADATA +415 -0
- authsec_sdk-4.0.0.dist-info/RECORD +15 -0
- authsec_sdk-4.0.0.dist-info/WHEEL +5 -0
- authsec_sdk-4.0.0.dist-info/top_level.txt +1 -0
authsec_sdk/__init__.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from .core import (
|
|
2
|
+
mcp_tool,
|
|
3
|
+
protected_by_AuthSec,
|
|
4
|
+
run_mcp_server_with_oauth,
|
|
5
|
+
ServiceAccessSDK,
|
|
6
|
+
ServiceAccessError,
|
|
7
|
+
configure_auth,
|
|
8
|
+
get_config,
|
|
9
|
+
is_configured,
|
|
10
|
+
test_auth_service,
|
|
11
|
+
test_services
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Import CIBA SDK for voice clients and passwordless authentication
|
|
15
|
+
from .ciba_sdk import CIBAClient
|
|
16
|
+
|
|
17
|
+
# Import standalone SPIFFE Workload API SDK
|
|
18
|
+
from .spiffe_workload_api import QuickStartSVID, WorkloadAPIClient
|
|
19
|
+
|
|
20
|
+
# Also import SDK Manager SPIRE integration (optional)
|
|
21
|
+
try:
|
|
22
|
+
from .spire_sdk import WorkloadSVID
|
|
23
|
+
except ImportError:
|
|
24
|
+
WorkloadSVID = None
|
|
25
|
+
|
|
26
|
+
__version__ = "4.0.0"
|
|
27
|
+
__all__ = [
|
|
28
|
+
# MCP Auth & Services
|
|
29
|
+
"protected_by_AuthSec",
|
|
30
|
+
"run_mcp_server_with_oauth",
|
|
31
|
+
"ServiceAccessSDK",
|
|
32
|
+
"ServiceAccessError",
|
|
33
|
+
"configure_auth",
|
|
34
|
+
"get_config",
|
|
35
|
+
"is_configured",
|
|
36
|
+
"test_auth_service",
|
|
37
|
+
"test_services",
|
|
38
|
+
# CIBA SDK for Voice Clients
|
|
39
|
+
"CIBAClient",
|
|
40
|
+
# SPIRE Workload Identity (Standalone SDK)
|
|
41
|
+
"QuickStartSVID",
|
|
42
|
+
"WorkloadAPIClient",
|
|
43
|
+
"WorkloadSVID"
|
|
44
|
+
]
|
authsec_sdk/ciba_sdk.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AuthSec CIBA SDK - Passwordless Authentication for Voice Clients
|
|
3
|
+
|
|
4
|
+
Python SDK for integrating CIBA (Client-Initiated Backchannel Authentication)
|
|
5
|
+
and TOTP verification into voice clients and other applications.
|
|
6
|
+
|
|
7
|
+
Supports both Admin and End-User (tenant) authentication flows:
|
|
8
|
+
- Admin flow: email only (original flow)
|
|
9
|
+
- Tenant flow: email + client_id (multi-client architecture)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CIBAClient:
|
|
17
|
+
"""
|
|
18
|
+
Python SDK for customers to integrate AuthSec into their own voice clients.
|
|
19
|
+
Handles the technical execution of polling, verification, and initiation.
|
|
20
|
+
|
|
21
|
+
Supports both Admin and End-User (tenant) authentication flows:
|
|
22
|
+
- Admin flow: email only (original flow)
|
|
23
|
+
- Tenant flow: email + client_id (multi-client architecture)
|
|
24
|
+
|
|
25
|
+
Example usage:
|
|
26
|
+
# Initialize for tenant flow
|
|
27
|
+
from authsec_sdk import CIBAClient
|
|
28
|
+
|
|
29
|
+
client = CIBAClient(client_id="your_tenant_client_id")
|
|
30
|
+
|
|
31
|
+
# CIBA: Send push notification
|
|
32
|
+
result = client.initiate_app_approval("user@example.com")
|
|
33
|
+
auth_req_id = result["auth_req_id"]
|
|
34
|
+
|
|
35
|
+
# Poll for approval
|
|
36
|
+
approval = client.poll_for_approval("user@example.com", auth_req_id)
|
|
37
|
+
if approval["status"] == "approved":
|
|
38
|
+
token = approval["token"]
|
|
39
|
+
|
|
40
|
+
# Or use TOTP verification
|
|
41
|
+
result = client.verify_totp("user@example.com", "123456")
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, client_id=None, base_url=None):
|
|
45
|
+
"""
|
|
46
|
+
Initialize the AuthSec SDK.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
client_id: Optional client ID for tenant/end-user flow. If provided, uses tenant endpoints.
|
|
50
|
+
If None, uses admin endpoints.
|
|
51
|
+
base_url: Optional base URL override. Defaults to production API.
|
|
52
|
+
"""
|
|
53
|
+
self.base_url = base_url or "https://dev.api.authsec.dev"
|
|
54
|
+
self.client_id = client_id # Optional: for tenant multi-client architecture
|
|
55
|
+
self.active_polls = {} # Map email -> cancellation_flag
|
|
56
|
+
self.retry_counts = {} # Map email -> int
|
|
57
|
+
|
|
58
|
+
def initiate_app_approval(self, email):
|
|
59
|
+
"""
|
|
60
|
+
Triggers a CIBA push notification and cancels any existing poll for this user.
|
|
61
|
+
|
|
62
|
+
- If client_id is set: uses tenant endpoint (/tenant/ciba/initiate)
|
|
63
|
+
- If client_id is None: uses admin endpoint (/ciba/initiate)
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
email: User's email address
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
dict: Response containing auth_req_id for polling
|
|
70
|
+
"""
|
|
71
|
+
self.retry_counts[email] = 0
|
|
72
|
+
if email in self.active_polls:
|
|
73
|
+
self.active_polls[email] = True
|
|
74
|
+
|
|
75
|
+
if self.client_id:
|
|
76
|
+
# Tenant/End-User flow
|
|
77
|
+
endpoint = f"{self.base_url}/uflow/auth/tenant/ciba/initiate"
|
|
78
|
+
payload = {
|
|
79
|
+
"client_id": self.client_id,
|
|
80
|
+
"email": email,
|
|
81
|
+
"binding_message": "Authentication requested via Voice SDK"
|
|
82
|
+
}
|
|
83
|
+
else:
|
|
84
|
+
# Admin flow
|
|
85
|
+
endpoint = f"{self.base_url}/uflow/auth/ciba/initiate"
|
|
86
|
+
payload = {"login_hint": email, "binding_message": "Authentication requested via Voice SDK"}
|
|
87
|
+
|
|
88
|
+
response = requests.post(endpoint, json=payload)
|
|
89
|
+
return response.json()
|
|
90
|
+
|
|
91
|
+
def verify_totp(self, email, code):
|
|
92
|
+
"""
|
|
93
|
+
Verifies a TOTP code for authentication.
|
|
94
|
+
|
|
95
|
+
- If client_id is set: uses tenant endpoint (/tenant/totp/login)
|
|
96
|
+
- If client_id is None: uses admin endpoint (/totp/login)
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
email: User's email address
|
|
100
|
+
code: 6-digit TOTP code
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
dict: Result with success status, token (if successful), and remaining retries
|
|
104
|
+
"""
|
|
105
|
+
if email not in self.retry_counts:
|
|
106
|
+
self.retry_counts[email] = 0
|
|
107
|
+
if self.retry_counts[email] >= 3:
|
|
108
|
+
return {"success": False, "error": "too_many_retries", "remaining": 0}
|
|
109
|
+
|
|
110
|
+
if self.client_id:
|
|
111
|
+
# Tenant/End-User flow
|
|
112
|
+
endpoint = f"{self.base_url}/uflow/auth/tenant/totp/login"
|
|
113
|
+
payload = {"client_id": self.client_id, "email": email, "totp_code": code}
|
|
114
|
+
else:
|
|
115
|
+
# Admin flow (fallback to dev.api if base_url is localhost for compatibility)
|
|
116
|
+
if "localhost" in self.base_url or "127.0.0.1" in self.base_url:
|
|
117
|
+
endpoint = "https://dev.api.authsec.dev/uflow/auth/totp/login"
|
|
118
|
+
else:
|
|
119
|
+
endpoint = f"{self.base_url}/uflow/auth/totp/login"
|
|
120
|
+
payload = {"email": email, "totp_code": code}
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
response = requests.post(endpoint, json=payload, timeout=10)
|
|
124
|
+
res_data = response.json()
|
|
125
|
+
|
|
126
|
+
# The API returns 'token' or 'access_token'
|
|
127
|
+
token = res_data.get("token") or res_data.get("access_token")
|
|
128
|
+
|
|
129
|
+
if token or res_data.get("success") is True:
|
|
130
|
+
self.retry_counts[email] = 0
|
|
131
|
+
return {**res_data, "success": True, "token": token, "remaining": 3}
|
|
132
|
+
else:
|
|
133
|
+
self.retry_counts[email] += 1
|
|
134
|
+
return {"success": False, "error": "invalid_code", "remaining": 3 - self.retry_counts[email]}
|
|
135
|
+
except Exception as e:
|
|
136
|
+
return {"success": False, "error": str(e), "remaining": 3 - self.retry_counts[email]}
|
|
137
|
+
|
|
138
|
+
def poll_for_approval(self, email, auth_req_id, interval=5, timeout=300):
|
|
139
|
+
"""
|
|
140
|
+
Polls for CIBA approval status.
|
|
141
|
+
|
|
142
|
+
- If client_id is set: uses tenant endpoint (/tenant/ciba/token)
|
|
143
|
+
- If client_id is None: uses admin endpoint (/ciba/token)
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
email: User's email address
|
|
147
|
+
auth_req_id: The auth request ID from initiate_app_approval
|
|
148
|
+
interval: Polling interval in seconds (default: 5)
|
|
149
|
+
timeout: Maximum time to poll in seconds (default: 300)
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
dict: Status with 'approved', 'cancelled', 'timeout', or error status
|
|
153
|
+
"""
|
|
154
|
+
self.active_polls[email] = False
|
|
155
|
+
|
|
156
|
+
if self.client_id:
|
|
157
|
+
# Tenant/End-User flow
|
|
158
|
+
endpoint = f"{self.base_url}/uflow/auth/tenant/ciba/token"
|
|
159
|
+
payload = {"client_id": self.client_id, "auth_req_id": auth_req_id}
|
|
160
|
+
else:
|
|
161
|
+
# Admin flow
|
|
162
|
+
endpoint = f"{self.base_url}/uflow/auth/ciba/token"
|
|
163
|
+
payload = {"auth_req_id": auth_req_id}
|
|
164
|
+
|
|
165
|
+
start_time = time.time()
|
|
166
|
+
while time.time() - start_time < timeout:
|
|
167
|
+
if self.active_polls.get(email) is True:
|
|
168
|
+
return {"status": "cancelled"}
|
|
169
|
+
response = requests.post(endpoint, json=payload)
|
|
170
|
+
data = response.json()
|
|
171
|
+
|
|
172
|
+
# Handle both key names
|
|
173
|
+
token = data.get("access_token") or data.get("token")
|
|
174
|
+
|
|
175
|
+
if token:
|
|
176
|
+
return {"status": "approved", "token": token}
|
|
177
|
+
if data.get("error") in ["access_denied", "expired_token"]:
|
|
178
|
+
return {"status": data.get("error")}
|
|
179
|
+
time.sleep(interval)
|
|
180
|
+
if timeout <= 2:
|
|
181
|
+
break # Short check for manual check
|
|
182
|
+
return {"status": "timeout"}
|
|
183
|
+
|
|
184
|
+
def cancel_approval(self, email):
|
|
185
|
+
"""
|
|
186
|
+
Cancels any ongoing poll and resets retry logic for the user.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
email: User's email address
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
dict: Cancellation status
|
|
193
|
+
"""
|
|
194
|
+
self.active_polls[email] = True
|
|
195
|
+
self.retry_counts[email] = 0
|
|
196
|
+
return {"status": "cancelled"}
|