authix-python-sdk 1.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.
- authix/__init__.py +23 -0
- authix/api.py +81 -0
- authix/cli.py +252 -0
- authix/client.py +110 -0
- authix/decorators.py +109 -0
- authix/exceptions.py +42 -0
- authix/handlers.py +570 -0
- authix/middleware.py +237 -0
- authix_python_sdk-1.0.0.dist-info/METADATA +666 -0
- authix_python_sdk-1.0.0.dist-info/RECORD +14 -0
- authix_python_sdk-1.0.0.dist-info/WHEEL +5 -0
- authix_python_sdk-1.0.0.dist-info/entry_points.txt +2 -0
- authix_python_sdk-1.0.0.dist-info/licenses/LICENSE +21 -0
- authix_python_sdk-1.0.0.dist-info/top_level.txt +1 -0
authix/middleware.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SecureAuth Flask Middleware
|
|
3
|
+
Provides Flask integration for SecureAuth
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from flask import Flask, request, session, g
|
|
7
|
+
from functools import wraps
|
|
8
|
+
from typing import Dict, Optional, Any
|
|
9
|
+
|
|
10
|
+
from .client import SecureAuthClient, SecureAuthError, SecureAuthException
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SecureAuthMiddleware:
|
|
14
|
+
"""SecureAuth middleware for Flask applications"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, client: SecureAuthClient):
|
|
17
|
+
"""
|
|
18
|
+
Initialize middleware
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
client: SecureAuthClient instance
|
|
22
|
+
"""
|
|
23
|
+
self.client = client
|
|
24
|
+
|
|
25
|
+
def authenticate_request(self, headers: Dict[str, str]) -> tuple[Optional[Dict], Optional[str]]:
|
|
26
|
+
"""
|
|
27
|
+
Authenticate incoming request
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
headers: Request headers
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Tuple of (user_info, error_message)
|
|
34
|
+
"""
|
|
35
|
+
auth_header = headers.get('Authorization', '')
|
|
36
|
+
api_key = headers.get('X-API-Key', '')
|
|
37
|
+
|
|
38
|
+
# Try JWT authentication first
|
|
39
|
+
if auth_header.startswith('Bearer '):
|
|
40
|
+
token = auth_header[7:]
|
|
41
|
+
try:
|
|
42
|
+
result = self.client.verify_token(token)
|
|
43
|
+
if result.get('success'):
|
|
44
|
+
return result.get('user'), None
|
|
45
|
+
else:
|
|
46
|
+
return None, result.get('error', 'Token verification failed')
|
|
47
|
+
except SecureAuthError as e:
|
|
48
|
+
return None, str(e)
|
|
49
|
+
|
|
50
|
+
# Try API key authentication
|
|
51
|
+
elif api_key:
|
|
52
|
+
try:
|
|
53
|
+
# This would need to be implemented in the API
|
|
54
|
+
# For now, return error
|
|
55
|
+
return None, "API key authentication not implemented in this version"
|
|
56
|
+
except SecureAuthError as e:
|
|
57
|
+
return None, str(e)
|
|
58
|
+
|
|
59
|
+
return None, "Authentication required"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class SecureAuthFlask:
|
|
63
|
+
"""Flask extension for SecureAuth"""
|
|
64
|
+
|
|
65
|
+
def __init__(self, app: Flask = None):
|
|
66
|
+
"""
|
|
67
|
+
Initialize Flask extension
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
app: Flask application instance
|
|
71
|
+
"""
|
|
72
|
+
self.app = app
|
|
73
|
+
self.client = None
|
|
74
|
+
self.middleware = None
|
|
75
|
+
|
|
76
|
+
if app is not None:
|
|
77
|
+
self.init_app(app)
|
|
78
|
+
|
|
79
|
+
def init_app(self, app: Flask):
|
|
80
|
+
"""
|
|
81
|
+
Initialize Flask application
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
app: Flask application instance
|
|
85
|
+
"""
|
|
86
|
+
# Get configuration
|
|
87
|
+
api_key = app.config.get('SECUREAUTH_API_KEY')
|
|
88
|
+
api_secret = app.config.get('SECUREAUTH_API_SECRET')
|
|
89
|
+
base_url = app.config.get('SECUREAUTH_BASE_URL', 'http://localhost:3000')
|
|
90
|
+
|
|
91
|
+
if not api_key or not api_secret:
|
|
92
|
+
raise ValueError("SECUREAUTH_API_KEY and SECUREAUTH_API_SECRET must be configured")
|
|
93
|
+
|
|
94
|
+
# Initialize client
|
|
95
|
+
self.client = SecureAuthClient(api_key, api_secret, base_url)
|
|
96
|
+
self.middleware = SecureAuthMiddleware(self.client)
|
|
97
|
+
|
|
98
|
+
# Register before request handler
|
|
99
|
+
app.before_request(self._before_request)
|
|
100
|
+
|
|
101
|
+
# Store extension in app
|
|
102
|
+
app.secureauth = self
|
|
103
|
+
|
|
104
|
+
def _before_request(self):
|
|
105
|
+
"""Handle authentication before each request"""
|
|
106
|
+
# Skip authentication for certain paths
|
|
107
|
+
if request.endpoint and request.endpoint.startswith('static'):
|
|
108
|
+
return # Skip static files
|
|
109
|
+
|
|
110
|
+
# Skip authentication for health checks and options
|
|
111
|
+
if request.path in ['/health', '/favicon.ico'] or request.method == 'OPTIONS':
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
# Authenticate request
|
|
115
|
+
user_info, error = self.middleware.authenticate_request(dict(request.headers))
|
|
116
|
+
|
|
117
|
+
if error:
|
|
118
|
+
# Return JSON error for API requests
|
|
119
|
+
if request.path.startswith('/api/'):
|
|
120
|
+
return {'success': False, 'error': error}, 401
|
|
121
|
+
# For web requests, continue without authentication (let decorators handle it)
|
|
122
|
+
g.current_user = None
|
|
123
|
+
g.auth_error = error
|
|
124
|
+
else:
|
|
125
|
+
g.current_user = user_info
|
|
126
|
+
g.auth_error = None
|
|
127
|
+
|
|
128
|
+
def get_current_user(self) -> Optional[Dict]:
|
|
129
|
+
"""Get current authenticated user"""
|
|
130
|
+
return getattr(g, 'current_user', None)
|
|
131
|
+
|
|
132
|
+
def is_authenticated(self) -> bool:
|
|
133
|
+
"""Check if current request is authenticated"""
|
|
134
|
+
return self.get_current_user() is not None
|
|
135
|
+
|
|
136
|
+
def has_role(self, *roles: str) -> bool:
|
|
137
|
+
"""Check if current user has any of the specified roles"""
|
|
138
|
+
user = self.get_current_user()
|
|
139
|
+
if not user:
|
|
140
|
+
return False
|
|
141
|
+
return user.get('role') in roles
|
|
142
|
+
|
|
143
|
+
def is_verified(self) -> bool:
|
|
144
|
+
"""Check if current user is verified"""
|
|
145
|
+
user = self.get_current_user()
|
|
146
|
+
if not user:
|
|
147
|
+
return False
|
|
148
|
+
return user.get('is_verified', False)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# Decorators for Flask routes
|
|
152
|
+
def require_auth(f):
|
|
153
|
+
"""Decorator to require authentication"""
|
|
154
|
+
@wraps(f)
|
|
155
|
+
def decorated_function(*args, **kwargs):
|
|
156
|
+
if not hasattr(g, 'current_user') or not g.current_user:
|
|
157
|
+
if request.path.startswith('/api/'):
|
|
158
|
+
return {'success': False, 'error': 'Authentication required'}, 401
|
|
159
|
+
else:
|
|
160
|
+
from flask import redirect, url_for, session
|
|
161
|
+
return redirect(url_for('login'))
|
|
162
|
+
return f(*args, **kwargs)
|
|
163
|
+
return decorated_function
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def require_role(*required_roles: str):
|
|
167
|
+
"""Decorator to require specific role(s)"""
|
|
168
|
+
def decorator(f):
|
|
169
|
+
@wraps(f)
|
|
170
|
+
def decorated_function(*args, **kwargs):
|
|
171
|
+
if not hasattr(g, 'current_user') or not g.current_user:
|
|
172
|
+
if request.path.startswith('/api/'):
|
|
173
|
+
return {'success': False, 'error': 'Authentication required'}, 401
|
|
174
|
+
else:
|
|
175
|
+
from flask import redirect, url_for
|
|
176
|
+
return redirect(url_for('login'))
|
|
177
|
+
|
|
178
|
+
user_role = g.current_user.get('role')
|
|
179
|
+
if user_role not in required_roles:
|
|
180
|
+
if request.path.startswith('/api/'):
|
|
181
|
+
return {'success': False, 'error': 'Insufficient permissions'}, 403
|
|
182
|
+
else:
|
|
183
|
+
from flask import abort
|
|
184
|
+
abort(403)
|
|
185
|
+
|
|
186
|
+
return f(*args, **kwargs)
|
|
187
|
+
return decorated_function
|
|
188
|
+
return decorator
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def require_verification(f):
|
|
192
|
+
"""Decorator to require user verification"""
|
|
193
|
+
@wraps(f)
|
|
194
|
+
def decorated_function(*args, **kwargs):
|
|
195
|
+
if not hasattr(g, 'current_user') or not g.current_user:
|
|
196
|
+
if request.path.startswith('/api/'):
|
|
197
|
+
return {'success': False, 'error': 'Authentication required'}, 401
|
|
198
|
+
else:
|
|
199
|
+
from flask import redirect, url_for
|
|
200
|
+
return redirect(url_for('login'))
|
|
201
|
+
|
|
202
|
+
if not g.current_user.get('is_verified', False):
|
|
203
|
+
if request.path.startswith('/api/'):
|
|
204
|
+
return {'success': False, 'error': 'Account not verified'}, 403
|
|
205
|
+
else:
|
|
206
|
+
from flask import abort
|
|
207
|
+
abort(403)
|
|
208
|
+
|
|
209
|
+
return f(*args, **kwargs)
|
|
210
|
+
return decorated_function
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def require_permission(permission: str):
|
|
214
|
+
"""Decorator to require specific permission"""
|
|
215
|
+
def decorator(f):
|
|
216
|
+
@wraps(f)
|
|
217
|
+
def decorated_function(*args, **kwargs):
|
|
218
|
+
if not hasattr(g, 'current_user') or not g.current_user:
|
|
219
|
+
if request.path.startswith('/api/'):
|
|
220
|
+
return {'success': False, 'error': 'Authentication required'}, 401
|
|
221
|
+
else:
|
|
222
|
+
from flask import redirect, url_for
|
|
223
|
+
return redirect(url_for('login'))
|
|
224
|
+
|
|
225
|
+
# Check permission (this would need to be implemented in the API)
|
|
226
|
+
# For now, check if user is admin
|
|
227
|
+
user_role = g.current_user.get('role')
|
|
228
|
+
if user_role != 'admin':
|
|
229
|
+
if request.path.startswith('/api/'):
|
|
230
|
+
return {'success': False, 'error': 'Insufficient permissions'}, 403
|
|
231
|
+
else:
|
|
232
|
+
from flask import abort
|
|
233
|
+
abort(403)
|
|
234
|
+
|
|
235
|
+
return f(*args, **kwargs)
|
|
236
|
+
return decorated_function
|
|
237
|
+
return decorator
|