fix(ci): fix black/ruff failures, add CoC and PR template
- Fix Python formatting (sync no longer strips blank lines from .py files) - Remove test_community_build.py (tests private sync module) - Fix ruff warnings in test files - Add CODE_OF_CONDUCT.md - Add .github/PULL_REQUEST_TEMPLATE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ from .schemas import OAuthClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClientRegistry:
|
||||
"""
|
||||
OAuth Client Registry with JSON storage.
|
||||
@@ -143,9 +144,11 @@ class ClientRegistry:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# Singleton instance
|
||||
_client_registry: ClientRegistry | None = None
|
||||
|
||||
|
||||
def get_client_registry() -> ClientRegistry:
|
||||
"""Get singleton ClientRegistry instance"""
|
||||
global _client_registry
|
||||
|
||||
@@ -10,6 +10,7 @@ Part of Phase E: Custom OAuth Authorization Page
|
||||
import secrets
|
||||
import time
|
||||
|
||||
|
||||
class CSRFTokenManager:
|
||||
"""
|
||||
Manages CSRF tokens for OAuth authorization requests.
|
||||
@@ -107,9 +108,11 @@ class CSRFTokenManager:
|
||||
"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.
|
||||
|
||||
@@ -8,6 +8,7 @@ import hashlib
|
||||
import secrets
|
||||
from typing import Literal
|
||||
|
||||
|
||||
def generate_code_verifier(length: int = 64) -> str:
|
||||
"""
|
||||
Generate PKCE code verifier.
|
||||
@@ -34,6 +35,7 @@ def generate_code_verifier(length: int = 64) -> str:
|
||||
|
||||
return verifier
|
||||
|
||||
|
||||
def generate_code_challenge(code_verifier: str, method: Literal["S256"] = "S256") -> str:
|
||||
"""
|
||||
Generate PKCE code challenge from verifier.
|
||||
@@ -63,6 +65,7 @@ def generate_code_challenge(code_verifier: str, method: Literal["S256"] = "S256"
|
||||
|
||||
return challenge
|
||||
|
||||
|
||||
def validate_code_challenge(
|
||||
code_verifier: str, code_challenge: str, method: Literal["S256"] = "S256"
|
||||
) -> bool:
|
||||
|
||||
@@ -6,6 +6,7 @@ from datetime import UTC, datetime
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class OAuthClient(BaseModel):
|
||||
"""OAuth 2.1 Client Model"""
|
||||
|
||||
@@ -44,6 +45,7 @@ class OAuthClient(BaseModel):
|
||||
raise ValueError(f"Invalid grant type: {grant}")
|
||||
return v
|
||||
|
||||
|
||||
class AuthorizationCode(BaseModel):
|
||||
"""Authorization Code Model"""
|
||||
|
||||
@@ -77,6 +79,7 @@ class AuthorizationCode(BaseModel):
|
||||
raise ValueError("Only S256 code_challenge_method is supported (OAuth 2.1)")
|
||||
return v
|
||||
|
||||
|
||||
class AccessToken(BaseModel):
|
||||
"""Access Token Model"""
|
||||
|
||||
@@ -96,6 +99,7 @@ class AccessToken(BaseModel):
|
||||
expires = expires.replace(tzinfo=UTC)
|
||||
return now > expires
|
||||
|
||||
|
||||
class RefreshToken(BaseModel):
|
||||
"""Refresh Token Model"""
|
||||
|
||||
@@ -115,6 +119,7 @@ class RefreshToken(BaseModel):
|
||||
expires = expires.replace(tzinfo=UTC)
|
||||
return now > expires
|
||||
|
||||
|
||||
class TokenRequest(BaseModel):
|
||||
"""Token Request Model"""
|
||||
|
||||
@@ -135,6 +140,7 @@ class TokenRequest(BaseModel):
|
||||
client_secret: str | None = None
|
||||
scope: str | None = None
|
||||
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
"""Token Response Model"""
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from .token_manager import get_token_manager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OAuthError(Exception):
|
||||
"""OAuth error with error code and description"""
|
||||
|
||||
@@ -25,6 +26,7 @@ class OAuthError(Exception):
|
||||
self.status_code = status_code
|
||||
super().__init__(error_description)
|
||||
|
||||
|
||||
class OAuthServer:
|
||||
"""
|
||||
OAuth 2.1 Authorization Server
|
||||
@@ -385,9 +387,11 @@ class OAuthServer:
|
||||
scope=" ".join(requested_scopes),
|
||||
)
|
||||
|
||||
|
||||
# Singleton
|
||||
_oauth_server: OAuthServer | None = None
|
||||
|
||||
|
||||
def get_oauth_server() -> OAuthServer:
|
||||
"""Get singleton OAuthServer instance"""
|
||||
global _oauth_server
|
||||
|
||||
@@ -13,6 +13,7 @@ from .schemas import AccessToken, AuthorizationCode, RefreshToken
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseStorage:
|
||||
"""Base storage interface"""
|
||||
|
||||
@@ -43,6 +44,7 @@ class BaseStorage:
|
||||
def revoke_refresh_token(self, token: str) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class JSONStorage(BaseStorage):
|
||||
"""
|
||||
JSON file storage for OAuth tokens.
|
||||
@@ -199,6 +201,7 @@ class JSONStorage(BaseStorage):
|
||||
logger.info(f"Cleaned up {len(codes) - len(cleaned_codes)} expired authorization codes")
|
||||
logger.info(f"Cleaned up {len(tokens) - len(cleaned_tokens)} expired access tokens")
|
||||
|
||||
|
||||
def get_storage() -> BaseStorage:
|
||||
"""
|
||||
Get storage instance based on environment.
|
||||
|
||||
@@ -17,11 +17,13 @@ from .storage import get_storage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SecurityError(Exception):
|
||||
"""Security-related error (e.g., token reuse)"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TokenManager:
|
||||
"""
|
||||
OAuth 2.1 Token Manager.
|
||||
@@ -302,9 +304,11 @@ class TokenManager:
|
||||
# Access tokens cannot be revoked (JWT stateless)
|
||||
# They expire naturally after TTL
|
||||
|
||||
|
||||
# Singleton
|
||||
_token_manager: TokenManager | None = None
|
||||
|
||||
|
||||
def get_token_manager() -> TokenManager:
|
||||
"""Get singleton TokenManager instance"""
|
||||
global _token_manager
|
||||
|
||||
Reference in New Issue
Block a user