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.
@@ -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
+ ]
@@ -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"}