Initial commit: MCP Hub Community Edition v3.0.0
Community edition generated from private repo via sync pipeline. Includes 9 plugins (WordPress, WooCommerce, WP Advanced, Gitea, n8n, Supabase, OpenPanel, Appwrite, Directus) with ~587 tools. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
123
core/oauth/csrf.py
Normal file
123
core/oauth/csrf.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
CSRF Protection for OAuth Authorization
|
||||
|
||||
Implements token-based CSRF protection for the OAuth authorization flow.
|
||||
Tokens are stored in-memory with 10-minute expiry.
|
||||
|
||||
Part of Phase E: Custom OAuth Authorization Page
|
||||
"""
|
||||
|
||||
import secrets
|
||||
import time
|
||||
|
||||
class CSRFTokenManager:
|
||||
"""
|
||||
Manages CSRF tokens for OAuth authorization requests.
|
||||
|
||||
Features:
|
||||
- Generates cryptographically secure random tokens
|
||||
- In-memory storage with automatic expiry
|
||||
- 10-minute token lifetime
|
||||
- Automatic cleanup of expired tokens
|
||||
"""
|
||||
|
||||
def __init__(self, token_lifetime_seconds: int = 600):
|
||||
"""
|
||||
Initialize CSRF token manager.
|
||||
|
||||
Args:
|
||||
token_lifetime_seconds: Token lifetime in seconds (default: 600 = 10 minutes)
|
||||
"""
|
||||
self._tokens: dict[str, float] = {} # token -> expiry_timestamp
|
||||
self._token_lifetime = token_lifetime_seconds
|
||||
|
||||
def generate_token(self) -> str:
|
||||
"""
|
||||
Generate a new CSRF token.
|
||||
|
||||
Returns:
|
||||
Secure random token (32 bytes, hex-encoded = 64 characters)
|
||||
"""
|
||||
token = secrets.token_hex(32)
|
||||
expiry = time.time() + self._token_lifetime
|
||||
self._tokens[token] = expiry
|
||||
|
||||
# Cleanup expired tokens (lazy cleanup)
|
||||
self._cleanup_expired()
|
||||
|
||||
return token
|
||||
|
||||
def validate_token(self, token: str, consume: bool = True) -> bool:
|
||||
"""
|
||||
Validate a CSRF token.
|
||||
|
||||
Args:
|
||||
token: CSRF token to validate
|
||||
consume: If True, token is removed after validation (one-time use)
|
||||
|
||||
Returns:
|
||||
True if token is valid and not expired, False otherwise
|
||||
"""
|
||||
if not token or token not in self._tokens:
|
||||
return False
|
||||
|
||||
expiry = self._tokens[token]
|
||||
now = time.time()
|
||||
|
||||
# Check if token is expired
|
||||
if now > expiry:
|
||||
# Remove expired token
|
||||
self._tokens.pop(token, None)
|
||||
return False
|
||||
|
||||
# Token is valid
|
||||
if consume:
|
||||
# Remove token (one-time use)
|
||||
self._tokens.pop(token, None)
|
||||
|
||||
return True
|
||||
|
||||
def _cleanup_expired(self):
|
||||
"""
|
||||
Remove expired tokens from storage.
|
||||
|
||||
Called automatically during token generation to prevent memory leaks.
|
||||
"""
|
||||
now = time.time()
|
||||
expired_tokens = [token for token, expiry in self._tokens.items() if now > expiry]
|
||||
|
||||
for token in expired_tokens:
|
||||
self._tokens.pop(token, None)
|
||||
|
||||
def get_stats(self) -> dict[str, int]:
|
||||
"""
|
||||
Get statistics about stored tokens.
|
||||
|
||||
Returns:
|
||||
Dictionary with token statistics
|
||||
"""
|
||||
now = time.time()
|
||||
active_tokens = sum(1 for expiry in self._tokens.values() if expiry > now)
|
||||
expired_tokens = len(self._tokens) - active_tokens
|
||||
|
||||
return {
|
||||
"total_tokens": len(self._tokens),
|
||||
"active_tokens": active_tokens,
|
||||
"expired_tokens": expired_tokens,
|
||||
"token_lifetime_seconds": self._token_lifetime,
|
||||
}
|
||||
|
||||
# Global CSRF token manager instance
|
||||
_csrf_manager: CSRFTokenManager | None = None
|
||||
|
||||
def get_csrf_manager() -> CSRFTokenManager:
|
||||
"""
|
||||
Get the global CSRF token manager instance.
|
||||
|
||||
Returns:
|
||||
Global CSRFTokenManager instance (singleton)
|
||||
"""
|
||||
global _csrf_manager
|
||||
if _csrf_manager is None:
|
||||
_csrf_manager = CSRFTokenManager()
|
||||
return _csrf_manager
|
||||
Reference in New Issue
Block a user