api-mocker 0.4.0__py3-none-any.whl → 0.5.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.
- api_mocker/auth_system.py +610 -0
- api_mocker/cli.py +252 -0
- api_mocker/database_integration.py +566 -0
- api_mocker/graphql_mock.py +593 -0
- api_mocker/ml_integration.py +709 -0
- api_mocker/websocket_mock.py +476 -0
- {api_mocker-0.4.0.dist-info → api_mocker-0.5.0.dist-info}/METADATA +15 -2
- {api_mocker-0.4.0.dist-info → api_mocker-0.5.0.dist-info}/RECORD +12 -7
- {api_mocker-0.4.0.dist-info → api_mocker-0.5.0.dist-info}/WHEEL +0 -0
- {api_mocker-0.4.0.dist-info → api_mocker-0.5.0.dist-info}/entry_points.txt +0 -0
- {api_mocker-0.4.0.dist-info → api_mocker-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {api_mocker-0.4.0.dist-info → api_mocker-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Advanced Authentication System
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive authentication capabilities including:
|
|
5
|
+
- OAuth2 with multiple providers (Google, GitHub, Microsoft, etc.)
|
|
6
|
+
- JWT token management with refresh tokens
|
|
7
|
+
- API key authentication
|
|
8
|
+
- Role-based access control (RBAC)
|
|
9
|
+
- Multi-factor authentication (MFA)
|
|
10
|
+
- Session management
|
|
11
|
+
- Password policies and validation
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import jwt
|
|
15
|
+
import hashlib
|
|
16
|
+
import secrets
|
|
17
|
+
import time
|
|
18
|
+
from typing import Any, Dict, List, Optional, Union, Callable
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from datetime import datetime, timedelta
|
|
22
|
+
import json
|
|
23
|
+
import base64
|
|
24
|
+
import hmac
|
|
25
|
+
import pyotp
|
|
26
|
+
import qrcode
|
|
27
|
+
from io import BytesIO
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AuthProvider(Enum):
|
|
31
|
+
"""Authentication providers"""
|
|
32
|
+
LOCAL = "local"
|
|
33
|
+
GOOGLE = "google"
|
|
34
|
+
GITHUB = "github"
|
|
35
|
+
MICROSOFT = "microsoft"
|
|
36
|
+
FACEBOOK = "facebook"
|
|
37
|
+
TWITTER = "twitter"
|
|
38
|
+
LINKEDIN = "linkedin"
|
|
39
|
+
DISCORD = "discord"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TokenType(Enum):
|
|
43
|
+
"""Token types"""
|
|
44
|
+
ACCESS = "access"
|
|
45
|
+
REFRESH = "refresh"
|
|
46
|
+
API_KEY = "api_key"
|
|
47
|
+
MFA = "mfa"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class UserRole(Enum):
|
|
51
|
+
"""User roles"""
|
|
52
|
+
ADMIN = "admin"
|
|
53
|
+
USER = "user"
|
|
54
|
+
MODERATOR = "moderator"
|
|
55
|
+
GUEST = "guest"
|
|
56
|
+
API_USER = "api_user"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class User:
|
|
61
|
+
"""User model"""
|
|
62
|
+
id: str
|
|
63
|
+
username: str
|
|
64
|
+
email: str
|
|
65
|
+
password_hash: str
|
|
66
|
+
roles: List[UserRole] = field(default_factory=lambda: [UserRole.USER])
|
|
67
|
+
is_active: bool = True
|
|
68
|
+
is_verified: bool = False
|
|
69
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
70
|
+
last_login: Optional[datetime] = None
|
|
71
|
+
mfa_secret: Optional[str] = None
|
|
72
|
+
mfa_enabled: bool = False
|
|
73
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class Token:
|
|
78
|
+
"""Token model"""
|
|
79
|
+
token: str
|
|
80
|
+
token_type: TokenType
|
|
81
|
+
user_id: str
|
|
82
|
+
expires_at: datetime
|
|
83
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
84
|
+
is_revoked: bool = False
|
|
85
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class APIKey:
|
|
90
|
+
"""API Key model"""
|
|
91
|
+
key: str
|
|
92
|
+
name: str
|
|
93
|
+
user_id: str
|
|
94
|
+
permissions: List[str] = field(default_factory=list)
|
|
95
|
+
rate_limit: Optional[int] = None
|
|
96
|
+
expires_at: Optional[datetime] = None
|
|
97
|
+
is_active: bool = True
|
|
98
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
99
|
+
last_used: Optional[datetime] = None
|
|
100
|
+
usage_count: int = 0
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class OAuth2Provider:
|
|
105
|
+
"""OAuth2 provider configuration"""
|
|
106
|
+
name: AuthProvider
|
|
107
|
+
client_id: str
|
|
108
|
+
client_secret: str
|
|
109
|
+
authorization_url: str
|
|
110
|
+
token_url: str
|
|
111
|
+
user_info_url: str
|
|
112
|
+
scope: List[str] = field(default_factory=list)
|
|
113
|
+
redirect_uri: str = ""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass
|
|
117
|
+
class AuthSession:
|
|
118
|
+
"""Authentication session"""
|
|
119
|
+
session_id: str
|
|
120
|
+
user_id: str
|
|
121
|
+
ip_address: str
|
|
122
|
+
user_agent: str
|
|
123
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
124
|
+
last_activity: datetime = field(default_factory=datetime.now)
|
|
125
|
+
expires_at: datetime = field(default_factory=lambda: datetime.now() + timedelta(hours=24))
|
|
126
|
+
is_active: bool = True
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class PasswordValidator:
|
|
130
|
+
"""Password validation utility"""
|
|
131
|
+
|
|
132
|
+
def __init__(self, min_length: int = 8, require_uppercase: bool = True,
|
|
133
|
+
require_lowercase: bool = True, require_numbers: bool = True,
|
|
134
|
+
require_special: bool = True):
|
|
135
|
+
self.min_length = min_length
|
|
136
|
+
self.require_uppercase = require_uppercase
|
|
137
|
+
self.require_lowercase = require_lowercase
|
|
138
|
+
self.require_numbers = require_numbers
|
|
139
|
+
self.require_special = require_special
|
|
140
|
+
|
|
141
|
+
def validate(self, password: str) -> Dict[str, Any]:
|
|
142
|
+
"""Validate password and return validation result"""
|
|
143
|
+
errors = []
|
|
144
|
+
|
|
145
|
+
if len(password) < self.min_length:
|
|
146
|
+
errors.append(f"Password must be at least {self.min_length} characters long")
|
|
147
|
+
|
|
148
|
+
if self.require_uppercase and not any(c.isupper() for c in password):
|
|
149
|
+
errors.append("Password must contain at least one uppercase letter")
|
|
150
|
+
|
|
151
|
+
if self.require_lowercase and not any(c.islower() for c in password):
|
|
152
|
+
errors.append("Password must contain at least one lowercase letter")
|
|
153
|
+
|
|
154
|
+
if self.require_numbers and not any(c.isdigit() for c in password):
|
|
155
|
+
errors.append("Password must contain at least one number")
|
|
156
|
+
|
|
157
|
+
if self.require_special and not any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password):
|
|
158
|
+
errors.append("Password must contain at least one special character")
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
"is_valid": len(errors) == 0,
|
|
162
|
+
"errors": errors,
|
|
163
|
+
"strength": self._calculate_strength(password)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
def _calculate_strength(self, password: str) -> str:
|
|
167
|
+
"""Calculate password strength"""
|
|
168
|
+
score = 0
|
|
169
|
+
|
|
170
|
+
if len(password) >= 8:
|
|
171
|
+
score += 1
|
|
172
|
+
if len(password) >= 12:
|
|
173
|
+
score += 1
|
|
174
|
+
if any(c.isupper() for c in password):
|
|
175
|
+
score += 1
|
|
176
|
+
if any(c.islower() for c in password):
|
|
177
|
+
score += 1
|
|
178
|
+
if any(c.isdigit() for c in password):
|
|
179
|
+
score += 1
|
|
180
|
+
if any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password):
|
|
181
|
+
score += 1
|
|
182
|
+
|
|
183
|
+
if score <= 2:
|
|
184
|
+
return "weak"
|
|
185
|
+
elif score <= 4:
|
|
186
|
+
return "medium"
|
|
187
|
+
else:
|
|
188
|
+
return "strong"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class JWTManager:
|
|
192
|
+
"""JWT token management"""
|
|
193
|
+
|
|
194
|
+
def __init__(self, secret_key: str, algorithm: str = "HS256"):
|
|
195
|
+
self.secret_key = secret_key
|
|
196
|
+
self.algorithm = algorithm
|
|
197
|
+
|
|
198
|
+
def create_token(self, user_id: str, token_type: TokenType,
|
|
199
|
+
expires_in: int = 3600, **kwargs) -> str:
|
|
200
|
+
"""Create a JWT token"""
|
|
201
|
+
now = datetime.utcnow()
|
|
202
|
+
payload = {
|
|
203
|
+
"user_id": user_id,
|
|
204
|
+
"token_type": token_type.value,
|
|
205
|
+
"iat": now,
|
|
206
|
+
"exp": now + timedelta(seconds=expires_in),
|
|
207
|
+
**kwargs
|
|
208
|
+
}
|
|
209
|
+
return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
|
|
210
|
+
|
|
211
|
+
def verify_token(self, token: str) -> Dict[str, Any]:
|
|
212
|
+
"""Verify and decode a JWT token"""
|
|
213
|
+
try:
|
|
214
|
+
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
|
|
215
|
+
return {"valid": True, "payload": payload}
|
|
216
|
+
except jwt.ExpiredSignatureError:
|
|
217
|
+
return {"valid": False, "error": "Token expired"}
|
|
218
|
+
except jwt.InvalidTokenError:
|
|
219
|
+
return {"valid": False, "error": "Invalid token"}
|
|
220
|
+
|
|
221
|
+
def refresh_token(self, refresh_token: str) -> Optional[str]:
|
|
222
|
+
"""Refresh an access token using refresh token"""
|
|
223
|
+
result = self.verify_token(refresh_token)
|
|
224
|
+
if result["valid"] and result["payload"]["token_type"] == "refresh":
|
|
225
|
+
user_id = result["payload"]["user_id"]
|
|
226
|
+
return self.create_token(user_id, TokenType.ACCESS)
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class MFAHandler:
|
|
231
|
+
"""Multi-factor authentication handler"""
|
|
232
|
+
|
|
233
|
+
def __init__(self):
|
|
234
|
+
self.totp = pyotp.TOTP
|
|
235
|
+
|
|
236
|
+
def generate_secret(self) -> str:
|
|
237
|
+
"""Generate a TOTP secret"""
|
|
238
|
+
return pyotp.random_base32()
|
|
239
|
+
|
|
240
|
+
def generate_qr_code(self, user_email: str, secret: str, issuer: str = "API-Mocker") -> str:
|
|
241
|
+
"""Generate QR code for MFA setup"""
|
|
242
|
+
totp_uri = pyotp.totp.TOTP(secret).provisioning_uri(
|
|
243
|
+
name=user_email,
|
|
244
|
+
issuer_name=issuer
|
|
245
|
+
)
|
|
246
|
+
return totp_uri
|
|
247
|
+
|
|
248
|
+
def verify_code(self, secret: str, code: str) -> bool:
|
|
249
|
+
"""Verify TOTP code"""
|
|
250
|
+
totp = pyotp.TOTP(secret)
|
|
251
|
+
return totp.verify(code, valid_window=1)
|
|
252
|
+
|
|
253
|
+
def generate_backup_codes(self, count: int = 10) -> List[str]:
|
|
254
|
+
"""Generate backup codes for MFA"""
|
|
255
|
+
return [secrets.token_hex(4).upper() for _ in range(count)]
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class AdvancedAuthSystem:
|
|
259
|
+
"""Main authentication system"""
|
|
260
|
+
|
|
261
|
+
def __init__(self, secret_key: str = None):
|
|
262
|
+
self.secret_key = secret_key or secrets.token_hex(32)
|
|
263
|
+
self.jwt_manager = JWTManager(self.secret_key)
|
|
264
|
+
self.mfa_handler = MFAHandler()
|
|
265
|
+
self.password_validator = PasswordValidator()
|
|
266
|
+
|
|
267
|
+
# Storage
|
|
268
|
+
self.users: Dict[str, User] = {}
|
|
269
|
+
self.tokens: Dict[str, Token] = {}
|
|
270
|
+
self.api_keys: Dict[str, APIKey] = {}
|
|
271
|
+
self.sessions: Dict[str, AuthSession] = {}
|
|
272
|
+
self.oauth_providers: Dict[AuthProvider, OAuth2Provider] = {}
|
|
273
|
+
|
|
274
|
+
# Rate limiting
|
|
275
|
+
self.login_attempts: Dict[str, List[datetime]] = {}
|
|
276
|
+
self.max_login_attempts = 5
|
|
277
|
+
self.lockout_duration = 300 # 5 minutes
|
|
278
|
+
|
|
279
|
+
def hash_password(self, password: str) -> str:
|
|
280
|
+
"""Hash a password using PBKDF2"""
|
|
281
|
+
salt = secrets.token_hex(16)
|
|
282
|
+
pwd_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000)
|
|
283
|
+
return f"{salt}:{pwd_hash.hex()}"
|
|
284
|
+
|
|
285
|
+
def verify_password(self, password: str, password_hash: str) -> bool:
|
|
286
|
+
"""Verify a password against its hash"""
|
|
287
|
+
try:
|
|
288
|
+
salt, hash_hex = password_hash.split(':')
|
|
289
|
+
pwd_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000)
|
|
290
|
+
return hmac.compare_digest(hash_hex, pwd_hash.hex())
|
|
291
|
+
except ValueError:
|
|
292
|
+
return False
|
|
293
|
+
|
|
294
|
+
def register_user(self, username: str, email: str, password: str,
|
|
295
|
+
roles: List[UserRole] = None) -> Dict[str, Any]:
|
|
296
|
+
"""Register a new user"""
|
|
297
|
+
# Validate password
|
|
298
|
+
password_validation = self.password_validator.validate(password)
|
|
299
|
+
if not password_validation["is_valid"]:
|
|
300
|
+
return {
|
|
301
|
+
"success": False,
|
|
302
|
+
"errors": password_validation["errors"]
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# Check if user already exists
|
|
306
|
+
if any(user.email == email for user in self.users.values()):
|
|
307
|
+
return {"success": False, "error": "Email already registered"}
|
|
308
|
+
|
|
309
|
+
if any(user.username == username for user in self.users.values()):
|
|
310
|
+
return {"success": False, "error": "Username already taken"}
|
|
311
|
+
|
|
312
|
+
# Create user
|
|
313
|
+
user_id = secrets.token_hex(16)
|
|
314
|
+
user = User(
|
|
315
|
+
id=user_id,
|
|
316
|
+
username=username,
|
|
317
|
+
email=email,
|
|
318
|
+
password_hash=self.hash_password(password),
|
|
319
|
+
roles=roles or [UserRole.USER]
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
self.users[user_id] = user
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
"success": True,
|
|
326
|
+
"user_id": user_id,
|
|
327
|
+
"message": "User registered successfully"
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
def authenticate_user(self, email: str, password: str,
|
|
331
|
+
ip_address: str = None, user_agent: str = None) -> Dict[str, Any]:
|
|
332
|
+
"""Authenticate a user with email and password"""
|
|
333
|
+
# Check rate limiting
|
|
334
|
+
if self._is_rate_limited(email):
|
|
335
|
+
return {"success": False, "error": "Too many login attempts"}
|
|
336
|
+
|
|
337
|
+
# Find user
|
|
338
|
+
user = None
|
|
339
|
+
for u in self.users.values():
|
|
340
|
+
if u.email == email:
|
|
341
|
+
user = u
|
|
342
|
+
break
|
|
343
|
+
|
|
344
|
+
if not user:
|
|
345
|
+
self._record_failed_attempt(email)
|
|
346
|
+
return {"success": False, "error": "Invalid credentials"}
|
|
347
|
+
|
|
348
|
+
# Verify password
|
|
349
|
+
if not self.verify_password(password, user.password_hash):
|
|
350
|
+
self._record_failed_attempt(email)
|
|
351
|
+
return {"success": False, "error": "Invalid credentials"}
|
|
352
|
+
|
|
353
|
+
# Check if user is active
|
|
354
|
+
if not user.is_active:
|
|
355
|
+
return {"success": False, "error": "Account is disabled"}
|
|
356
|
+
|
|
357
|
+
# Update last login
|
|
358
|
+
user.last_login = datetime.now()
|
|
359
|
+
|
|
360
|
+
# Create tokens
|
|
361
|
+
access_token = self.jwt_manager.create_token(
|
|
362
|
+
user.id, TokenType.ACCESS, expires_in=3600
|
|
363
|
+
)
|
|
364
|
+
refresh_token = self.jwt_manager.create_token(
|
|
365
|
+
user.id, TokenType.REFRESH, expires_in=86400 * 7 # 7 days
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# Create session
|
|
369
|
+
session_id = secrets.token_hex(32)
|
|
370
|
+
session = AuthSession(
|
|
371
|
+
session_id=session_id,
|
|
372
|
+
user_id=user.id,
|
|
373
|
+
ip_address=ip_address or "unknown",
|
|
374
|
+
user_agent=user_agent or "unknown"
|
|
375
|
+
)
|
|
376
|
+
self.sessions[session_id] = session
|
|
377
|
+
|
|
378
|
+
# Clear failed attempts
|
|
379
|
+
if email in self.login_attempts:
|
|
380
|
+
del self.login_attempts[email]
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
"success": True,
|
|
384
|
+
"access_token": access_token,
|
|
385
|
+
"refresh_token": refresh_token,
|
|
386
|
+
"session_id": session_id,
|
|
387
|
+
"user": {
|
|
388
|
+
"id": user.id,
|
|
389
|
+
"username": user.username,
|
|
390
|
+
"email": user.email,
|
|
391
|
+
"roles": [role.value for role in user.roles],
|
|
392
|
+
"mfa_enabled": user.mfa_enabled
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
def verify_token(self, token: str) -> Dict[str, Any]:
|
|
397
|
+
"""Verify a JWT token"""
|
|
398
|
+
result = self.jwt_manager.verify_token(token)
|
|
399
|
+
if not result["valid"]:
|
|
400
|
+
return result
|
|
401
|
+
|
|
402
|
+
payload = result["payload"]
|
|
403
|
+
user_id = payload.get("user_id")
|
|
404
|
+
|
|
405
|
+
if user_id not in self.users:
|
|
406
|
+
return {"valid": False, "error": "User not found"}
|
|
407
|
+
|
|
408
|
+
user = self.users[user_id]
|
|
409
|
+
if not user.is_active:
|
|
410
|
+
return {"valid": False, "error": "User account is disabled"}
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
"valid": True,
|
|
414
|
+
"user_id": user_id,
|
|
415
|
+
"payload": payload
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
def create_api_key(self, user_id: str, name: str, permissions: List[str] = None,
|
|
419
|
+
rate_limit: int = None, expires_in: int = None) -> Dict[str, Any]:
|
|
420
|
+
"""Create an API key for a user"""
|
|
421
|
+
if user_id not in self.users:
|
|
422
|
+
return {"success": False, "error": "User not found"}
|
|
423
|
+
|
|
424
|
+
# Generate API key
|
|
425
|
+
api_key = f"ak_{secrets.token_hex(32)}"
|
|
426
|
+
|
|
427
|
+
# Set expiration
|
|
428
|
+
expires_at = None
|
|
429
|
+
if expires_in:
|
|
430
|
+
expires_at = datetime.now() + timedelta(seconds=expires_in)
|
|
431
|
+
|
|
432
|
+
key_obj = APIKey(
|
|
433
|
+
key=api_key,
|
|
434
|
+
name=name,
|
|
435
|
+
user_id=user_id,
|
|
436
|
+
permissions=permissions or [],
|
|
437
|
+
rate_limit=rate_limit,
|
|
438
|
+
expires_at=expires_at
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
self.api_keys[api_key] = key_obj
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
"success": True,
|
|
445
|
+
"api_key": api_key,
|
|
446
|
+
"expires_at": expires_at.isoformat() if expires_at else None
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
def verify_api_key(self, api_key: str) -> Dict[str, Any]:
|
|
450
|
+
"""Verify an API key"""
|
|
451
|
+
if api_key not in self.api_keys:
|
|
452
|
+
return {"valid": False, "error": "Invalid API key"}
|
|
453
|
+
|
|
454
|
+
key_obj = self.api_keys[api_key]
|
|
455
|
+
|
|
456
|
+
if not key_obj.is_active:
|
|
457
|
+
return {"valid": False, "error": "API key is disabled"}
|
|
458
|
+
|
|
459
|
+
if key_obj.expires_at and datetime.now() > key_obj.expires_at:
|
|
460
|
+
return {"valid": False, "error": "API key expired"}
|
|
461
|
+
|
|
462
|
+
# Update usage
|
|
463
|
+
key_obj.last_used = datetime.now()
|
|
464
|
+
key_obj.usage_count += 1
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
"valid": True,
|
|
468
|
+
"user_id": key_obj.user_id,
|
|
469
|
+
"permissions": key_obj.permissions,
|
|
470
|
+
"rate_limit": key_obj.rate_limit
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
def setup_mfa(self, user_id: str) -> Dict[str, Any]:
|
|
474
|
+
"""Setup MFA for a user"""
|
|
475
|
+
if user_id not in self.users:
|
|
476
|
+
return {"success": False, "error": "User not found"}
|
|
477
|
+
|
|
478
|
+
user = self.users[user_id]
|
|
479
|
+
secret = self.mfa_handler.generate_secret()
|
|
480
|
+
user.mfa_secret = secret
|
|
481
|
+
|
|
482
|
+
qr_code_uri = self.mfa_handler.generate_qr_code(user.email, secret)
|
|
483
|
+
backup_codes = self.mfa_handler.generate_backup_codes()
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
"success": True,
|
|
487
|
+
"secret": secret,
|
|
488
|
+
"qr_code_uri": qr_code_uri,
|
|
489
|
+
"backup_codes": backup_codes
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
def enable_mfa(self, user_id: str, code: str) -> Dict[str, Any]:
|
|
493
|
+
"""Enable MFA for a user"""
|
|
494
|
+
if user_id not in self.users:
|
|
495
|
+
return {"success": False, "error": "User not found"}
|
|
496
|
+
|
|
497
|
+
user = self.users[user_id]
|
|
498
|
+
if not user.mfa_secret:
|
|
499
|
+
return {"success": False, "error": "MFA not set up"}
|
|
500
|
+
|
|
501
|
+
if not self.mfa_handler.verify_code(user.mfa_secret, code):
|
|
502
|
+
return {"success": False, "error": "Invalid MFA code"}
|
|
503
|
+
|
|
504
|
+
user.mfa_enabled = True
|
|
505
|
+
return {"success": True, "message": "MFA enabled successfully"}
|
|
506
|
+
|
|
507
|
+
def verify_mfa(self, user_id: str, code: str) -> bool:
|
|
508
|
+
"""Verify MFA code"""
|
|
509
|
+
if user_id not in self.users:
|
|
510
|
+
return False
|
|
511
|
+
|
|
512
|
+
user = self.users[user_id]
|
|
513
|
+
if not user.mfa_enabled or not user.mfa_secret:
|
|
514
|
+
return False
|
|
515
|
+
|
|
516
|
+
return self.mfa_handler.verify_code(user.mfa_secret, code)
|
|
517
|
+
|
|
518
|
+
def _is_rate_limited(self, email: str) -> bool:
|
|
519
|
+
"""Check if email is rate limited"""
|
|
520
|
+
if email not in self.login_attempts:
|
|
521
|
+
return False
|
|
522
|
+
|
|
523
|
+
now = datetime.now()
|
|
524
|
+
attempts = self.login_attempts[email]
|
|
525
|
+
|
|
526
|
+
# Remove old attempts
|
|
527
|
+
attempts = [attempt for attempt in attempts if now - attempt < timedelta(seconds=self.lockout_duration)]
|
|
528
|
+
self.login_attempts[email] = attempts
|
|
529
|
+
|
|
530
|
+
return len(attempts) >= self.max_login_attempts
|
|
531
|
+
|
|
532
|
+
def _record_failed_attempt(self, email: str) -> None:
|
|
533
|
+
"""Record a failed login attempt"""
|
|
534
|
+
if email not in self.login_attempts:
|
|
535
|
+
self.login_attempts[email] = []
|
|
536
|
+
|
|
537
|
+
self.login_attempts[email].append(datetime.now())
|
|
538
|
+
|
|
539
|
+
def add_oauth_provider(self, provider: OAuth2Provider) -> None:
|
|
540
|
+
"""Add an OAuth2 provider"""
|
|
541
|
+
self.oauth_providers[provider.name] = provider
|
|
542
|
+
|
|
543
|
+
def get_oauth_authorization_url(self, provider: AuthProvider, state: str = None) -> str:
|
|
544
|
+
"""Get OAuth2 authorization URL"""
|
|
545
|
+
if provider not in self.oauth_providers:
|
|
546
|
+
raise ValueError(f"OAuth provider {provider} not configured")
|
|
547
|
+
|
|
548
|
+
oauth_provider = self.oauth_providers[provider]
|
|
549
|
+
state = state or secrets.token_urlsafe(32)
|
|
550
|
+
|
|
551
|
+
params = {
|
|
552
|
+
"client_id": oauth_provider.client_id,
|
|
553
|
+
"redirect_uri": oauth_provider.redirect_uri,
|
|
554
|
+
"scope": " ".join(oauth_provider.scope),
|
|
555
|
+
"state": state,
|
|
556
|
+
"response_type": "code"
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
query_string = "&".join([f"{k}={v}" for k, v in params.items()])
|
|
560
|
+
return f"{oauth_provider.authorization_url}?{query_string}"
|
|
561
|
+
|
|
562
|
+
def get_user_permissions(self, user_id: str) -> List[str]:
|
|
563
|
+
"""Get user permissions based on roles"""
|
|
564
|
+
if user_id not in self.users:
|
|
565
|
+
return []
|
|
566
|
+
|
|
567
|
+
user = self.users[user_id]
|
|
568
|
+
permissions = []
|
|
569
|
+
|
|
570
|
+
for role in user.roles:
|
|
571
|
+
if role == UserRole.ADMIN:
|
|
572
|
+
permissions.extend(["read", "write", "delete", "admin"])
|
|
573
|
+
elif role == UserRole.MODERATOR:
|
|
574
|
+
permissions.extend(["read", "write", "moderate"])
|
|
575
|
+
elif role == UserRole.USER:
|
|
576
|
+
permissions.extend(["read", "write"])
|
|
577
|
+
elif role == UserRole.API_USER:
|
|
578
|
+
permissions.extend(["api_access"])
|
|
579
|
+
|
|
580
|
+
return list(set(permissions))
|
|
581
|
+
|
|
582
|
+
def check_permission(self, user_id: str, permission: str) -> bool:
|
|
583
|
+
"""Check if user has a specific permission"""
|
|
584
|
+
user_permissions = self.get_user_permissions(user_id)
|
|
585
|
+
return permission in user_permissions
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
# Global authentication system instance
|
|
589
|
+
auth_system = AdvancedAuthSystem()
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
# Convenience functions
|
|
593
|
+
def create_user(username: str, email: str, password: str, roles: List[UserRole] = None) -> Dict[str, Any]:
|
|
594
|
+
"""Create a new user"""
|
|
595
|
+
return auth_system.register_user(username, email, password, roles)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def authenticate(email: str, password: str) -> Dict[str, Any]:
|
|
599
|
+
"""Authenticate a user"""
|
|
600
|
+
return auth_system.authenticate_user(email, password)
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def create_api_key(user_id: str, name: str, permissions: List[str] = None) -> Dict[str, Any]:
|
|
604
|
+
"""Create an API key"""
|
|
605
|
+
return auth_system.create_api_key(user_id, name, permissions)
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def setup_mfa(user_id: str) -> Dict[str, Any]:
|
|
609
|
+
"""Setup MFA for a user"""
|
|
610
|
+
return auth_system.setup_mfa(user_id)
|