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/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