kailash 0.4.1__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.
- kailash/__init__.py +3 -4
- kailash/middleware/__init__.py +4 -2
- kailash/middleware/auth/__init__.py +55 -12
- kailash/middleware/auth/exceptions.py +80 -0
- kailash/middleware/auth/jwt_auth.py +265 -123
- kailash/middleware/auth/models.py +137 -0
- kailash/middleware/auth/utils.py +257 -0
- kailash/middleware/communication/api_gateway.py +49 -7
- kailash/middleware/core/agent_ui.py +108 -1
- kailash/middleware/database/repositories.py +3 -1
- kailash/middleware/mcp/enhanced_server.py +2 -2
- kailash/nodes/admin/audit_log.py +364 -6
- kailash/nodes/admin/user_management.py +1006 -20
- kailash/nodes/api/http.py +95 -71
- kailash/nodes/base.py +281 -164
- kailash/nodes/base_async.py +30 -31
- kailash/nodes/code/python.py +18 -0
- kailash/nodes/data/async_sql.py +3 -22
- kailash/utils/resource_manager.py +420 -0
- kailash/workflow/builder.py +93 -10
- kailash/workflow/cyclic_runner.py +4 -25
- {kailash-0.4.1.dist-info → kailash-0.5.0.dist-info}/METADATA +6 -4
- {kailash-0.4.1.dist-info → kailash-0.5.0.dist-info}/RECORD +27 -24
- kailash/middleware/auth/kailash_jwt_auth.py +0 -616
- {kailash-0.4.1.dist-info → kailash-0.5.0.dist-info}/WHEEL +0 -0
- {kailash-0.4.1.dist-info → kailash-0.5.0.dist-info}/entry_points.txt +0 -0
- {kailash-0.4.1.dist-info → kailash-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.4.1.dist-info → kailash-0.5.0.dist-info}/top_level.txt +0 -0
kailash/__init__.py
CHANGED
@@ -3,9 +3,8 @@
|
|
3
3
|
The Kailash SDK provides a comprehensive framework for creating nodes and workflows
|
4
4
|
that align with container-node architecture while allowing rapid prototyping.
|
5
5
|
|
6
|
-
New in v0.4.
|
7
|
-
|
8
|
-
and universal vision capabilities across OpenAI, Anthropic, and Ollama providers.
|
6
|
+
New in v0.4.2: Bug fixes including circular import resolution and JWT implementation
|
7
|
+
consolidation. Improved changelog organization with individual release files.
|
9
8
|
"""
|
10
9
|
|
11
10
|
from kailash.nodes.base import Node, NodeMetadata, NodeParameter
|
@@ -34,7 +33,7 @@ except ImportError:
|
|
34
33
|
# For backward compatibility
|
35
34
|
WorkflowGraph = Workflow
|
36
35
|
|
37
|
-
__version__ = "0.4.
|
36
|
+
__version__ = "0.4.2"
|
38
37
|
|
39
38
|
__all__ = [
|
40
39
|
# Core workflow components
|
kailash/middleware/__init__.py
CHANGED
@@ -36,7 +36,7 @@ The middleware consists of these interconnected layers:
|
|
36
36
|
|
37
37
|
**API Gateway Layer**:
|
38
38
|
- RESTful API endpoints with OpenAPI documentation
|
39
|
-
- JWT-based authentication using
|
39
|
+
- JWT-based authentication using JWTAuthManager
|
40
40
|
- Request/response middleware with comprehensive logging
|
41
41
|
- Dynamic schema generation for node discovery
|
42
42
|
|
@@ -181,9 +181,11 @@ Usage Examples
|
|
181
181
|
>>> workflow_id = await agent_ui.create_dynamic_workflow(
|
182
182
|
... session_id, workflow_config
|
183
183
|
... )
|
184
|
-
>>>
|
184
|
+
>>> # Use execute() for consistency with runtime API (preferred)
|
185
|
+
>>> execution_id = await agent_ui.execute(
|
185
186
|
... session_id, workflow_id, inputs={}
|
186
187
|
... )
|
188
|
+
>>> # Note: execute_workflow() is deprecated and will be removed in v1.0.0
|
187
189
|
|
188
190
|
Production Deployment
|
189
191
|
--------------------
|
@@ -15,19 +15,62 @@ Features:
|
|
15
15
|
- Audit logging
|
16
16
|
"""
|
17
17
|
|
18
|
-
from .
|
19
|
-
|
20
|
-
|
18
|
+
from .exceptions import (
|
19
|
+
AuthenticationError,
|
20
|
+
InvalidTokenError,
|
21
|
+
PermissionDeniedError,
|
22
|
+
TokenBlacklistedError,
|
23
|
+
TokenExpiredError,
|
21
24
|
)
|
22
|
-
|
23
|
-
|
25
|
+
|
26
|
+
# Import without circular dependencies
|
27
|
+
from .jwt_auth import JWTAuthManager
|
28
|
+
from .models import AuthenticationResult, JWTConfig, TokenPair, TokenPayload, UserClaims
|
29
|
+
from .utils import generate_key_pair, generate_secret_key, parse_bearer_token
|
30
|
+
|
31
|
+
# Import other components (check for circular deps)
|
32
|
+
try:
|
33
|
+
from .access_control import (
|
34
|
+
MiddlewareAccessControlManager,
|
35
|
+
MiddlewareAuthenticationMiddleware,
|
36
|
+
)
|
37
|
+
from .auth_manager import AuthLevel, MiddlewareAuthManager
|
38
|
+
|
39
|
+
_has_access_control = True
|
40
|
+
except ImportError:
|
41
|
+
# These imports might fail due to circular dependencies with communication
|
42
|
+
_has_access_control = False
|
43
|
+
|
44
|
+
# KailashJWTAuthManager has been consolidated into JWTAuthManager
|
45
|
+
# For backward compatibility, use JWTAuthManager directly
|
24
46
|
|
25
47
|
__all__ = [
|
26
|
-
# Core authentication
|
27
|
-
"
|
28
|
-
"
|
29
|
-
"
|
30
|
-
|
31
|
-
"
|
32
|
-
"
|
48
|
+
# Core authentication (always available)
|
49
|
+
"JWTAuthManager",
|
50
|
+
"JWTConfig",
|
51
|
+
"TokenPayload",
|
52
|
+
"TokenPair",
|
53
|
+
"UserClaims",
|
54
|
+
"AuthenticationResult",
|
55
|
+
# Exceptions
|
56
|
+
"AuthenticationError",
|
57
|
+
"TokenExpiredError",
|
58
|
+
"InvalidTokenError",
|
59
|
+
"TokenBlacklistedError",
|
60
|
+
"PermissionDeniedError",
|
61
|
+
# Utilities
|
62
|
+
"generate_secret_key",
|
63
|
+
"generate_key_pair",
|
64
|
+
"parse_bearer_token",
|
33
65
|
]
|
66
|
+
|
67
|
+
# Add access control components if available
|
68
|
+
if _has_access_control:
|
69
|
+
__all__.extend(
|
70
|
+
[
|
71
|
+
"MiddlewareAuthManager",
|
72
|
+
"AuthLevel",
|
73
|
+
"MiddlewareAccessControlManager",
|
74
|
+
"MiddlewareAuthenticationMiddleware",
|
75
|
+
]
|
76
|
+
)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
"""
|
2
|
+
Authentication Exceptions for Kailash Middleware
|
3
|
+
|
4
|
+
Provides exception classes for authentication errors without circular dependencies.
|
5
|
+
"""
|
6
|
+
|
7
|
+
|
8
|
+
class AuthenticationError(Exception):
|
9
|
+
"""Base exception for authentication errors."""
|
10
|
+
|
11
|
+
def __init__(self, message: str, error_code: str = None):
|
12
|
+
super().__init__(message)
|
13
|
+
self.error_code = error_code
|
14
|
+
|
15
|
+
|
16
|
+
class TokenExpiredError(AuthenticationError):
|
17
|
+
"""Raised when a JWT token has expired."""
|
18
|
+
|
19
|
+
def __init__(self, message: str = "Token has expired"):
|
20
|
+
super().__init__(message, "TOKEN_EXPIRED")
|
21
|
+
|
22
|
+
|
23
|
+
class InvalidTokenError(AuthenticationError):
|
24
|
+
"""Raised when a JWT token is invalid."""
|
25
|
+
|
26
|
+
def __init__(self, message: str = "Invalid token"):
|
27
|
+
super().__init__(message, "INVALID_TOKEN")
|
28
|
+
|
29
|
+
|
30
|
+
class TokenBlacklistedError(AuthenticationError):
|
31
|
+
"""Raised when a JWT token has been blacklisted/revoked."""
|
32
|
+
|
33
|
+
def __init__(self, message: str = "Token has been revoked"):
|
34
|
+
super().__init__(message, "TOKEN_BLACKLISTED")
|
35
|
+
|
36
|
+
|
37
|
+
class KeyRotationError(AuthenticationError):
|
38
|
+
"""Raised when key rotation fails."""
|
39
|
+
|
40
|
+
def __init__(self, message: str = "Key rotation failed"):
|
41
|
+
super().__init__(message, "KEY_ROTATION_ERROR")
|
42
|
+
|
43
|
+
|
44
|
+
class RefreshTokenError(AuthenticationError):
|
45
|
+
"""Raised when refresh token operations fail."""
|
46
|
+
|
47
|
+
def __init__(self, message: str = "Refresh token error"):
|
48
|
+
super().__init__(message, "REFRESH_TOKEN_ERROR")
|
49
|
+
|
50
|
+
|
51
|
+
class PermissionDeniedError(AuthenticationError):
|
52
|
+
"""Raised when user lacks required permissions."""
|
53
|
+
|
54
|
+
def __init__(
|
55
|
+
self, message: str = "Permission denied", required_permission: str = None
|
56
|
+
):
|
57
|
+
super().__init__(message, "PERMISSION_DENIED")
|
58
|
+
self.required_permission = required_permission
|
59
|
+
|
60
|
+
|
61
|
+
class RateLimitError(AuthenticationError):
|
62
|
+
"""Raised when authentication rate limit is exceeded."""
|
63
|
+
|
64
|
+
def __init__(self, message: str = "Rate limit exceeded", retry_after: int = None):
|
65
|
+
super().__init__(message, "RATE_LIMIT_EXCEEDED")
|
66
|
+
self.retry_after = retry_after
|
67
|
+
|
68
|
+
|
69
|
+
class InvalidCredentialsError(AuthenticationError):
|
70
|
+
"""Raised when login credentials are invalid."""
|
71
|
+
|
72
|
+
def __init__(self, message: str = "Invalid credentials"):
|
73
|
+
super().__init__(message, "INVALID_CREDENTIALS")
|
74
|
+
|
75
|
+
|
76
|
+
class SessionExpiredError(AuthenticationError):
|
77
|
+
"""Raised when a session has expired."""
|
78
|
+
|
79
|
+
def __init__(self, message: str = "Session has expired"):
|
80
|
+
super().__init__(message, "SESSION_EXPIRED")
|
@@ -1,16 +1,14 @@
|
|
1
1
|
"""
|
2
2
|
JWT Authentication Manager for Kailash Middleware
|
3
3
|
|
4
|
-
Provides enterprise-grade JWT authentication
|
5
|
-
|
4
|
+
Provides enterprise-grade JWT authentication with support for both HS256 and RSA algorithms.
|
5
|
+
This consolidates the functionality of both JWTAuthManager and KailashJWTAuthManager.
|
6
6
|
"""
|
7
7
|
|
8
|
-
import json
|
9
8
|
import logging
|
10
9
|
import secrets
|
11
10
|
import time
|
12
11
|
import uuid
|
13
|
-
from dataclasses import dataclass
|
14
12
|
from datetime import datetime, timedelta, timezone
|
15
13
|
from typing import Any, Dict, List, Optional, Union
|
16
14
|
|
@@ -22,82 +20,18 @@ except ImportError:
|
|
22
20
|
jwt = None
|
23
21
|
rsa = None
|
24
22
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
from kailash.workflow.builder import WorkflowBuilder
|
23
|
+
from .exceptions import (
|
24
|
+
InvalidTokenError,
|
25
|
+
RefreshTokenError,
|
26
|
+
TokenBlacklistedError,
|
27
|
+
TokenExpiredError,
|
28
|
+
)
|
32
29
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
@dataclass
|
37
|
-
class JWTConfig:
|
38
|
-
"""Configuration for JWT authentication using only Python standard library."""
|
39
|
-
|
40
|
-
# Signing configuration
|
41
|
-
algorithm: str = (
|
42
|
-
"HS256" # Use HMAC instead of RSA to avoid external crypto dependencies
|
43
|
-
)
|
44
|
-
access_token_expire_minutes: int = 15
|
45
|
-
refresh_token_expire_days: int = 7
|
46
|
-
|
47
|
-
# Security settings
|
48
|
-
issuer: str = "kailash-middleware"
|
49
|
-
audience: str = "kailash-api"
|
50
|
-
|
51
|
-
# Key management
|
52
|
-
auto_generate_keys: bool = True
|
53
|
-
key_rotation_days: int = 30
|
54
|
-
|
55
|
-
# Token settings
|
56
|
-
include_user_claims: bool = True
|
57
|
-
include_permissions: bool = True
|
58
|
-
max_refresh_count: int = 10
|
59
|
-
|
60
|
-
|
61
|
-
@dataclass
|
62
|
-
class TokenPayload:
|
63
|
-
"""JWT token payload structure using standard Python."""
|
30
|
+
# Import models and utilities (no circular dependencies)
|
31
|
+
from .models import JWTConfig, RefreshTokenData, TokenPair, TokenPayload
|
32
|
+
from .utils import generate_secret_key, is_token_expired
|
64
33
|
|
65
|
-
|
66
|
-
sub: str # Subject (user ID)
|
67
|
-
iss: str # Issuer
|
68
|
-
aud: str # Audience
|
69
|
-
exp: int # Expiration time
|
70
|
-
iat: int # Issued at
|
71
|
-
jti: str # JWT ID
|
72
|
-
|
73
|
-
# Custom claims
|
74
|
-
tenant_id: Optional[str] = None
|
75
|
-
session_id: Optional[str] = None
|
76
|
-
user_type: str = "user"
|
77
|
-
permissions: List[str] = None
|
78
|
-
roles: List[str] = None
|
79
|
-
|
80
|
-
# Token metadata
|
81
|
-
token_type: str = "access" # access, refresh
|
82
|
-
refresh_count: int = 0
|
83
|
-
|
84
|
-
def __post_init__(self):
|
85
|
-
if self.permissions is None:
|
86
|
-
self.permissions = []
|
87
|
-
if self.roles is None:
|
88
|
-
self.roles = []
|
89
|
-
|
90
|
-
|
91
|
-
@dataclass
|
92
|
-
class TokenPair:
|
93
|
-
"""Access and refresh token pair using standard Python."""
|
94
|
-
|
95
|
-
access_token: str
|
96
|
-
refresh_token: str
|
97
|
-
token_type: str = "Bearer"
|
98
|
-
expires_in: int = 0
|
99
|
-
expires_at: Optional[datetime] = None
|
100
|
-
scope: Optional[str] = None
|
34
|
+
logger = logging.getLogger(__name__)
|
101
35
|
|
102
36
|
|
103
37
|
class JWTAuthManager:
|
@@ -105,30 +39,111 @@ class JWTAuthManager:
|
|
105
39
|
Enterprise JWT Authentication Manager.
|
106
40
|
|
107
41
|
Provides comprehensive JWT token management with security best practices:
|
108
|
-
-
|
42
|
+
- Support for both HS256 (default) and RSA algorithms
|
43
|
+
- RSA key pair generation and rotation (when using RSA)
|
109
44
|
- Refresh token management
|
110
45
|
- Token blacklisting
|
111
46
|
- Comprehensive audit logging
|
112
47
|
- Rate limiting protection
|
48
|
+
|
49
|
+
This consolidates both JWTAuthManager and KailashJWTAuthManager functionality.
|
113
50
|
"""
|
114
51
|
|
115
|
-
def __init__(
|
52
|
+
def __init__(
|
53
|
+
self,
|
54
|
+
config: JWTConfig = None,
|
55
|
+
secret_key: str = None,
|
56
|
+
algorithm: str = None,
|
57
|
+
use_rsa: bool = None,
|
58
|
+
**kwargs,
|
59
|
+
):
|
60
|
+
"""
|
61
|
+
Initialize JWT Auth Manager.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
config: JWTConfig object with full configuration
|
65
|
+
secret_key: Secret key for HS256 (overrides config)
|
66
|
+
algorithm: Algorithm to use (overrides config)
|
67
|
+
use_rsa: Whether to use RSA (overrides config)
|
68
|
+
**kwargs: Additional config parameters
|
69
|
+
"""
|
116
70
|
self.config = config or JWTConfig()
|
117
71
|
|
72
|
+
# Override config with direct parameters for backward compatibility
|
73
|
+
if secret_key is not None:
|
74
|
+
self.config.secret_key = secret_key
|
75
|
+
if algorithm is not None:
|
76
|
+
self.config.algorithm = algorithm
|
77
|
+
if use_rsa is not None:
|
78
|
+
self.config.use_rsa = use_rsa
|
79
|
+
if use_rsa:
|
80
|
+
self.config.algorithm = "RS256"
|
81
|
+
|
82
|
+
# Apply any additional kwargs to config
|
83
|
+
for key, value in kwargs.items():
|
84
|
+
if hasattr(self.config, key):
|
85
|
+
setattr(self.config, key, value)
|
86
|
+
|
118
87
|
# Key management
|
119
88
|
self._private_key: Optional[rsa.RSAPrivateKey] = None
|
120
89
|
self._public_key: Optional[rsa.RSAPublicKey] = None
|
90
|
+
self._secret_key: Optional[str] = self.config.secret_key
|
121
91
|
self._key_id = str(uuid.uuid4())
|
122
92
|
self._key_generated_at = datetime.now(timezone.utc)
|
123
93
|
|
124
94
|
# Token tracking
|
125
|
-
self._blacklisted_tokens: set = set()
|
95
|
+
self._blacklisted_tokens: set = set() if self.config.enable_blacklist else None
|
126
96
|
self._refresh_tokens: Dict[str, Dict[str, Any]] = {}
|
127
97
|
self._failed_attempts: Dict[str, List[datetime]] = {}
|
128
98
|
|
129
|
-
# Initialize keys
|
130
|
-
|
131
|
-
|
99
|
+
# Initialize keys based on algorithm
|
100
|
+
self._initialize_keys()
|
101
|
+
|
102
|
+
def _initialize_keys(self):
|
103
|
+
"""Initialize keys based on configured algorithm."""
|
104
|
+
if self.config.use_rsa or self.config.algorithm.startswith("RS"):
|
105
|
+
# RSA mode
|
106
|
+
if self.config.private_key and self.config.public_key:
|
107
|
+
# Load provided keys
|
108
|
+
self._load_rsa_keys()
|
109
|
+
elif self.config.auto_generate_keys:
|
110
|
+
# Generate new RSA keys
|
111
|
+
self._generate_key_pair()
|
112
|
+
else:
|
113
|
+
raise ValueError(
|
114
|
+
"RSA mode requires either provided keys or auto_generate_keys=True"
|
115
|
+
)
|
116
|
+
else:
|
117
|
+
# HS256 mode
|
118
|
+
if not self._secret_key:
|
119
|
+
if self.config.auto_generate_keys:
|
120
|
+
# Generate random secret
|
121
|
+
self._secret_key = secrets.token_urlsafe(32)
|
122
|
+
self.config.secret_key = self._secret_key
|
123
|
+
logger.info("Generated new HS256 secret key")
|
124
|
+
else:
|
125
|
+
raise ValueError(
|
126
|
+
"HS256 mode requires secret_key or auto_generate_keys=True"
|
127
|
+
)
|
128
|
+
|
129
|
+
def _load_rsa_keys(self):
|
130
|
+
"""Load RSA keys from PEM strings."""
|
131
|
+
try:
|
132
|
+
from cryptography.hazmat.backends import default_backend
|
133
|
+
from cryptography.hazmat.primitives import serialization
|
134
|
+
|
135
|
+
self._private_key = serialization.load_pem_private_key(
|
136
|
+
self.config.private_key.encode(),
|
137
|
+
password=None,
|
138
|
+
backend=default_backend(),
|
139
|
+
)
|
140
|
+
self._public_key = serialization.load_pem_public_key(
|
141
|
+
self.config.public_key.encode(), backend=default_backend()
|
142
|
+
)
|
143
|
+
logger.info("Loaded RSA keys from configuration")
|
144
|
+
except Exception as e:
|
145
|
+
logger.error(f"Failed to load RSA keys: {e}")
|
146
|
+
raise
|
132
147
|
|
133
148
|
def _generate_key_pair(self):
|
134
149
|
"""Generate new RSA key pair for token signing."""
|
@@ -193,7 +208,8 @@ class JWTAuthManager:
|
|
193
208
|
**kwargs,
|
194
209
|
) -> str:
|
195
210
|
"""Create JWT access token."""
|
196
|
-
|
211
|
+
# Only rotate keys in RSA mode
|
212
|
+
if self.config.use_rsa and self._should_rotate_keys():
|
197
213
|
self._generate_key_pair()
|
198
214
|
|
199
215
|
payload = self._create_token_payload(
|
@@ -206,16 +222,39 @@ class JWTAuthManager:
|
|
206
222
|
**kwargs,
|
207
223
|
)
|
208
224
|
|
209
|
-
#
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
payload.
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
225
|
+
# Convert payload to dict for encoding
|
226
|
+
payload_dict = {
|
227
|
+
"sub": payload.sub,
|
228
|
+
"iss": payload.iss,
|
229
|
+
"aud": payload.aud,
|
230
|
+
"exp": payload.exp,
|
231
|
+
"iat": payload.iat,
|
232
|
+
"jti": payload.jti,
|
233
|
+
"tenant_id": payload.tenant_id,
|
234
|
+
"session_id": payload.session_id,
|
235
|
+
"token_type": payload.token_type,
|
236
|
+
"permissions": payload.permissions,
|
237
|
+
"roles": payload.roles,
|
238
|
+
}
|
239
|
+
payload_dict.update(kwargs)
|
240
|
+
|
241
|
+
# Sign token based on algorithm
|
242
|
+
if self.config.use_rsa or self.config.algorithm.startswith("RS"):
|
243
|
+
# RSA signing
|
244
|
+
headers = {"kid": self._key_id}
|
245
|
+
token = jwt.encode(
|
246
|
+
payload_dict,
|
247
|
+
self._private_key,
|
248
|
+
algorithm=self.config.algorithm,
|
249
|
+
headers=headers,
|
250
|
+
)
|
251
|
+
else:
|
252
|
+
# HS256 signing
|
253
|
+
token = jwt.encode(
|
254
|
+
payload_dict,
|
255
|
+
self._secret_key,
|
256
|
+
algorithm=self.config.algorithm,
|
257
|
+
)
|
219
258
|
|
220
259
|
logger.debug(f"Created access token for user {user_id}")
|
221
260
|
return token
|
@@ -232,14 +271,36 @@ class JWTAuthManager:
|
|
232
271
|
**kwargs,
|
233
272
|
)
|
234
273
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
payload.
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
274
|
+
# Convert payload to dict
|
275
|
+
payload_dict = {
|
276
|
+
"sub": payload.sub,
|
277
|
+
"iss": payload.iss,
|
278
|
+
"aud": payload.aud,
|
279
|
+
"exp": payload.exp,
|
280
|
+
"iat": payload.iat,
|
281
|
+
"jti": payload.jti,
|
282
|
+
"tenant_id": payload.tenant_id,
|
283
|
+
"session_id": payload.session_id,
|
284
|
+
"token_type": payload.token_type,
|
285
|
+
"refresh_count": payload.refresh_count,
|
286
|
+
}
|
287
|
+
payload_dict.update(kwargs)
|
288
|
+
|
289
|
+
# Sign token based on algorithm
|
290
|
+
if self.config.use_rsa or self.config.algorithm.startswith("RS"):
|
291
|
+
headers = {"kid": self._key_id}
|
292
|
+
token = jwt.encode(
|
293
|
+
payload_dict,
|
294
|
+
self._private_key,
|
295
|
+
algorithm=self.config.algorithm,
|
296
|
+
headers=headers,
|
297
|
+
)
|
298
|
+
else:
|
299
|
+
token = jwt.encode(
|
300
|
+
payload_dict,
|
301
|
+
self._secret_key,
|
302
|
+
algorithm=self.config.algorithm,
|
303
|
+
)
|
243
304
|
|
244
305
|
# Store refresh token metadata
|
245
306
|
self._refresh_tokens[payload.jti] = {
|
@@ -291,27 +352,39 @@ class JWTAuthManager:
|
|
291
352
|
"""
|
292
353
|
try:
|
293
354
|
# Check if token is blacklisted
|
294
|
-
if token in self._blacklisted_tokens:
|
355
|
+
if self._blacklisted_tokens and token in self._blacklisted_tokens:
|
295
356
|
raise jwt.InvalidTokenError("Token has been revoked")
|
296
357
|
|
297
|
-
#
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
#
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
358
|
+
# Get verification key based on algorithm
|
359
|
+
if self.config.use_rsa or self.config.algorithm.startswith("RS"):
|
360
|
+
# RSA verification
|
361
|
+
# Decode without verification first to get header
|
362
|
+
unverified_header = jwt.get_unverified_header(token)
|
363
|
+
key_id = unverified_header.get("kid")
|
364
|
+
|
365
|
+
# Verify key ID matches current key (optional check)
|
366
|
+
if key_id and key_id != self._key_id:
|
367
|
+
logger.warning(f"Token signed with unknown key ID: {key_id}")
|
368
|
+
# In production, you might want to support multiple keys
|
369
|
+
# for graceful key rotation
|
370
|
+
|
371
|
+
# Verify and decode token
|
372
|
+
payload = jwt.decode(
|
373
|
+
token,
|
374
|
+
self._public_key,
|
375
|
+
algorithms=[self.config.algorithm],
|
376
|
+
issuer=self.config.issuer,
|
377
|
+
audience=self.config.audience,
|
378
|
+
)
|
379
|
+
else:
|
380
|
+
# HS256 verification
|
381
|
+
payload = jwt.decode(
|
382
|
+
token,
|
383
|
+
self._secret_key,
|
384
|
+
algorithms=[self.config.algorithm],
|
385
|
+
issuer=self.config.issuer,
|
386
|
+
audience=self.config.audience,
|
387
|
+
)
|
315
388
|
|
316
389
|
return payload
|
317
390
|
|
@@ -461,7 +534,9 @@ class JWTAuthManager:
|
|
461
534
|
"""Get authentication manager statistics."""
|
462
535
|
return {
|
463
536
|
"active_refresh_tokens": len(self._refresh_tokens),
|
464
|
-
"blacklisted_tokens":
|
537
|
+
"blacklisted_tokens": (
|
538
|
+
len(self._blacklisted_tokens) if self._blacklisted_tokens else 0
|
539
|
+
),
|
465
540
|
"key_id": self._key_id,
|
466
541
|
"key_age_days": (
|
467
542
|
(datetime.now(timezone.utc) - self._key_generated_at).days
|
@@ -475,3 +550,70 @@ class JWTAuthManager:
|
|
475
550
|
"refresh_token_expire_days": self.config.refresh_token_expire_days,
|
476
551
|
},
|
477
552
|
}
|
553
|
+
|
554
|
+
# Backward compatibility methods for KailashJWTAuthManager
|
555
|
+
def generate_token(self, user_id: str, **claims) -> str:
|
556
|
+
"""
|
557
|
+
Generate access token (backward compatibility).
|
558
|
+
|
559
|
+
.. deprecated:: 0.5.0
|
560
|
+
Use :meth:`create_access_token` instead. This method will be removed in version 1.0.0.
|
561
|
+
|
562
|
+
This method exists for compatibility with KailashJWTAuthManager.
|
563
|
+
"""
|
564
|
+
import warnings
|
565
|
+
|
566
|
+
warnings.warn(
|
567
|
+
"generate_token() is deprecated and will be removed in version 1.0.0. "
|
568
|
+
"Use create_access_token() instead.",
|
569
|
+
DeprecationWarning,
|
570
|
+
stacklevel=2,
|
571
|
+
)
|
572
|
+
return self.create_access_token(user_id, **claims)
|
573
|
+
|
574
|
+
def verify_and_decode_token(self, token: str) -> Dict[str, Any]:
|
575
|
+
"""
|
576
|
+
Verify and decode token (backward compatibility).
|
577
|
+
|
578
|
+
.. deprecated:: 0.5.0
|
579
|
+
Use :meth:`verify_token` instead. This method will be removed in version 1.0.0.
|
580
|
+
|
581
|
+
This method exists for compatibility with KailashJWTAuthManager.
|
582
|
+
"""
|
583
|
+
import warnings
|
584
|
+
|
585
|
+
warnings.warn(
|
586
|
+
"verify_and_decode_token() is deprecated and will be removed in version 1.0.0. "
|
587
|
+
"Use verify_token() instead.",
|
588
|
+
DeprecationWarning,
|
589
|
+
stacklevel=2,
|
590
|
+
)
|
591
|
+
return self.verify_token(token)
|
592
|
+
|
593
|
+
def blacklist_token(self, token: str):
|
594
|
+
"""
|
595
|
+
Blacklist token (backward compatibility).
|
596
|
+
|
597
|
+
This method exists for compatibility with KailashJWTAuthManager.
|
598
|
+
"""
|
599
|
+
# Just call the main revoke_token method
|
600
|
+
self.revoke_token(token)
|
601
|
+
|
602
|
+
def generate_refresh_token(self, user_id: str, **claims) -> str:
|
603
|
+
"""
|
604
|
+
Generate refresh token (backward compatibility).
|
605
|
+
|
606
|
+
.. deprecated:: 0.5.0
|
607
|
+
Use :meth:`create_refresh_token` instead. This method will be removed in version 1.0.0.
|
608
|
+
|
609
|
+
This method exists for compatibility with KailashJWTAuthManager.
|
610
|
+
"""
|
611
|
+
import warnings
|
612
|
+
|
613
|
+
warnings.warn(
|
614
|
+
"generate_refresh_token() is deprecated and will be removed in version 1.0.0. "
|
615
|
+
"Use create_refresh_token() instead.",
|
616
|
+
DeprecationWarning,
|
617
|
+
stacklevel=2,
|
618
|
+
)
|
619
|
+
return self.create_refresh_token(user_id, **claims)
|