Fix code formatting and linting issues
- Updated GitHub Actions workflow to use correct flake8 configuration - Fixed line length issues by using 88 characters as configured - Removed unused imports and trailing whitespace - Fixed f-string placeholders and unused variables - All linting checks now pass with project configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,13 +9,13 @@ Supported authentication methods:
|
||||
- No authentication for testing (NoAuth)
|
||||
"""
|
||||
|
||||
from .base import AuthHandler, NoAuth
|
||||
from .api_key import APIKeyAuth
|
||||
from .base import AuthHandler, NoAuth
|
||||
from .jwt import JWTAuth
|
||||
|
||||
__all__ = [
|
||||
"AuthHandler",
|
||||
"NoAuth",
|
||||
"NoAuth",
|
||||
"APIKeyAuth",
|
||||
"JWTAuth",
|
||||
]
|
||||
]
|
||||
|
||||
@@ -4,20 +4,20 @@ This module implements API key authentication for Wiki.js instances.
|
||||
API keys are typically used for server-to-server authentication.
|
||||
"""
|
||||
|
||||
from typing import Dict, Optional
|
||||
from typing import Dict
|
||||
|
||||
from .base import AuthHandler
|
||||
|
||||
|
||||
class APIKeyAuth(AuthHandler):
|
||||
"""API key authentication handler for Wiki.js.
|
||||
|
||||
|
||||
This handler implements authentication using an API key, which is
|
||||
included in the Authorization header as a Bearer token.
|
||||
|
||||
|
||||
Args:
|
||||
api_key: The API key string from Wiki.js admin panel.
|
||||
|
||||
|
||||
Example:
|
||||
>>> auth = APIKeyAuth("your-api-key-here")
|
||||
>>> client = WikiJSClient("https://wiki.example.com", auth=auth)
|
||||
@@ -25,35 +25,35 @@ class APIKeyAuth(AuthHandler):
|
||||
|
||||
def __init__(self, api_key: str) -> None:
|
||||
"""Initialize API key authentication.
|
||||
|
||||
|
||||
Args:
|
||||
api_key: The API key from Wiki.js admin panel.
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: If api_key is empty or None.
|
||||
"""
|
||||
if not api_key or not api_key.strip():
|
||||
raise ValueError("API key cannot be empty")
|
||||
|
||||
|
||||
self._api_key = api_key.strip()
|
||||
|
||||
def get_headers(self) -> Dict[str, str]:
|
||||
"""Get authentication headers with API key.
|
||||
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: Headers containing the Authorization header.
|
||||
"""
|
||||
return {
|
||||
"Authorization": f"Bearer {self._api_key}",
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
"""Check if API key is valid.
|
||||
|
||||
|
||||
For API keys, we assume they're valid if they're not empty.
|
||||
Actual validation happens on the server side.
|
||||
|
||||
|
||||
Returns:
|
||||
bool: True if API key exists, False otherwise.
|
||||
"""
|
||||
@@ -61,29 +61,30 @@ class APIKeyAuth(AuthHandler):
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""Refresh authentication credentials.
|
||||
|
||||
|
||||
API keys don't typically need refreshing, so this is a no-op.
|
||||
If the API key becomes invalid, a new one must be provided.
|
||||
"""
|
||||
# API keys don't refresh - they're static until manually replaced
|
||||
pass
|
||||
|
||||
@property
|
||||
def api_key(self) -> str:
|
||||
"""Get the masked API key for logging/debugging.
|
||||
|
||||
|
||||
Returns:
|
||||
str: Masked API key showing only first 4 and last 4 characters.
|
||||
"""
|
||||
if len(self._api_key) <= 8:
|
||||
return "*" * len(self._api_key)
|
||||
|
||||
return f"{self._api_key[:4]}{'*' * (len(self._api_key) - 8)}{self._api_key[-4:]}"
|
||||
|
||||
return (
|
||||
f"{self._api_key[:4]}{'*' * (len(self._api_key) - 8)}{self._api_key[-4:]}"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""String representation of the auth handler.
|
||||
|
||||
|
||||
Returns:
|
||||
str: Safe representation with masked API key.
|
||||
"""
|
||||
return f"APIKeyAuth(api_key='{self.api_key}')"
|
||||
return f"APIKeyAuth(api_key='{self.api_key}')"
|
||||
|
||||
@@ -5,12 +5,12 @@ providing a consistent interface for different authentication methods.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Dict, Optional
|
||||
from typing import Dict
|
||||
|
||||
|
||||
class AuthHandler(ABC):
|
||||
"""Abstract base class for Wiki.js authentication handlers.
|
||||
|
||||
|
||||
This class defines the interface that all authentication implementations
|
||||
must follow, ensuring consistent behavior across different auth methods.
|
||||
"""
|
||||
@@ -18,56 +18,54 @@ class AuthHandler(ABC):
|
||||
@abstractmethod
|
||||
def get_headers(self) -> Dict[str, str]:
|
||||
"""Get authentication headers for HTTP requests.
|
||||
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: Dictionary of headers to include in requests.
|
||||
|
||||
|
||||
Raises:
|
||||
AuthenticationError: If authentication is invalid or expired.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_valid(self) -> bool:
|
||||
"""Check if the current authentication is valid.
|
||||
|
||||
|
||||
Returns:
|
||||
bool: True if authentication is valid, False otherwise.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def refresh(self) -> None:
|
||||
"""Refresh the authentication if possible.
|
||||
|
||||
|
||||
For token-based authentication, this should refresh the token.
|
||||
For API key authentication, this is typically a no-op.
|
||||
|
||||
|
||||
Raises:
|
||||
AuthenticationError: If refresh fails.
|
||||
"""
|
||||
pass
|
||||
|
||||
def validate_credentials(self) -> None:
|
||||
"""Validate credentials and refresh if necessary.
|
||||
|
||||
|
||||
This is a convenience method that checks validity and refreshes
|
||||
if needed. Subclasses can override for custom behavior.
|
||||
|
||||
|
||||
Raises:
|
||||
AuthenticationError: If credentials are invalid or refresh fails.
|
||||
"""
|
||||
if not self.is_valid():
|
||||
self.refresh()
|
||||
|
||||
|
||||
if not self.is_valid():
|
||||
from ..exceptions import AuthenticationError
|
||||
|
||||
raise AuthenticationError("Authentication credentials are invalid")
|
||||
|
||||
|
||||
class NoAuth(AuthHandler):
|
||||
"""No-authentication handler for testing or public instances.
|
||||
|
||||
|
||||
This handler provides an empty authentication implementation,
|
||||
useful for testing or when accessing public Wiki.js instances
|
||||
that don't require authentication.
|
||||
@@ -75,7 +73,7 @@ class NoAuth(AuthHandler):
|
||||
|
||||
def get_headers(self) -> Dict[str, str]:
|
||||
"""Return empty headers dict.
|
||||
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: Empty dictionary.
|
||||
"""
|
||||
@@ -83,7 +81,7 @@ class NoAuth(AuthHandler):
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
"""Always return True for no-auth.
|
||||
|
||||
|
||||
Returns:
|
||||
bool: Always True.
|
||||
"""
|
||||
@@ -91,7 +89,6 @@ class NoAuth(AuthHandler):
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""No-op for no-auth.
|
||||
|
||||
|
||||
This method does nothing since there's no authentication to refresh.
|
||||
"""
|
||||
pass
|
||||
@@ -5,7 +5,7 @@ JWT tokens are typically used for user-based authentication and have expiration
|
||||
"""
|
||||
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
from typing import Dict, Optional
|
||||
|
||||
from .base import AuthHandler
|
||||
@@ -13,39 +13,39 @@ from .base import AuthHandler
|
||||
|
||||
class JWTAuth(AuthHandler):
|
||||
"""JWT token authentication handler for Wiki.js.
|
||||
|
||||
|
||||
This handler manages JWT tokens with automatic refresh capabilities.
|
||||
JWT tokens typically expire and need to be refreshed periodically.
|
||||
|
||||
|
||||
Args:
|
||||
token: The JWT token string.
|
||||
refresh_token: Optional refresh token for automatic renewal.
|
||||
expires_at: Optional expiration timestamp (Unix timestamp).
|
||||
|
||||
|
||||
Example:
|
||||
>>> auth = JWTAuth("eyJ0eXAiOiJKV1QiLCJhbGc...")
|
||||
>>> client = WikiJSClient("https://wiki.example.com", auth=auth)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
token: str,
|
||||
self,
|
||||
token: str,
|
||||
refresh_token: Optional[str] = None,
|
||||
expires_at: Optional[float] = None
|
||||
expires_at: Optional[float] = None,
|
||||
) -> None:
|
||||
"""Initialize JWT authentication.
|
||||
|
||||
|
||||
Args:
|
||||
token: The JWT token string.
|
||||
refresh_token: Optional refresh token for automatic renewal.
|
||||
expires_at: Optional expiration timestamp (Unix timestamp).
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: If token is empty or None.
|
||||
"""
|
||||
if not token or not token.strip():
|
||||
raise ValueError("JWT token cannot be empty")
|
||||
|
||||
|
||||
self._token = token.strip()
|
||||
self._refresh_token = refresh_token.strip() if refresh_token else None
|
||||
self._expires_at = expires_at
|
||||
@@ -53,66 +53,66 @@ class JWTAuth(AuthHandler):
|
||||
|
||||
def get_headers(self) -> Dict[str, str]:
|
||||
"""Get authentication headers with JWT token.
|
||||
|
||||
|
||||
Automatically attempts to refresh the token if it's expired.
|
||||
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: Headers containing the Authorization header.
|
||||
|
||||
|
||||
Raises:
|
||||
AuthenticationError: If token is expired and cannot be refreshed.
|
||||
"""
|
||||
# Try to refresh if token is near expiration
|
||||
if not self.is_valid():
|
||||
self.refresh()
|
||||
|
||||
|
||||
return {
|
||||
"Authorization": f"Bearer {self._token}",
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
"""Check if JWT token is valid and not expired.
|
||||
|
||||
|
||||
Returns:
|
||||
bool: True if token exists and is not expired, False otherwise.
|
||||
"""
|
||||
if not self._token or not self._token.strip():
|
||||
return False
|
||||
|
||||
|
||||
# If no expiration time is set, assume token is valid
|
||||
if self._expires_at is None:
|
||||
return True
|
||||
|
||||
|
||||
# Check if token is expired (with buffer for refresh)
|
||||
current_time = time.time()
|
||||
return current_time < (self._expires_at - self._refresh_buffer)
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""Refresh the JWT token using the refresh token.
|
||||
|
||||
|
||||
This method attempts to refresh the JWT token using the refresh token.
|
||||
If no refresh token is available, it raises an AuthenticationError.
|
||||
|
||||
|
||||
Note: This is a placeholder implementation. In a real implementation,
|
||||
this would make an HTTP request to the Wiki.js token refresh endpoint.
|
||||
|
||||
|
||||
Raises:
|
||||
AuthenticationError: If refresh token is not available or refresh fails.
|
||||
"""
|
||||
from ..exceptions import AuthenticationError
|
||||
|
||||
|
||||
if not self._refresh_token:
|
||||
raise AuthenticationError(
|
||||
"JWT token expired and no refresh token available"
|
||||
)
|
||||
|
||||
|
||||
# TODO: Implement actual token refresh logic
|
||||
# This would typically involve:
|
||||
# 1. Making a POST request to /auth/refresh endpoint
|
||||
# 2. Sending the refresh token
|
||||
# 3. Updating self._token and self._expires_at with the response
|
||||
|
||||
|
||||
raise AuthenticationError(
|
||||
"JWT token refresh not yet implemented. "
|
||||
"Please provide a new token or use API key authentication."
|
||||
@@ -120,46 +120,46 @@ class JWTAuth(AuthHandler):
|
||||
|
||||
def is_expired(self) -> bool:
|
||||
"""Check if the JWT token is expired.
|
||||
|
||||
|
||||
Returns:
|
||||
bool: True if token is expired, False otherwise.
|
||||
"""
|
||||
if self._expires_at is None:
|
||||
return False
|
||||
|
||||
|
||||
return time.time() >= self._expires_at
|
||||
|
||||
def time_until_expiry(self) -> Optional[timedelta]:
|
||||
"""Get time until token expires.
|
||||
|
||||
|
||||
Returns:
|
||||
Optional[timedelta]: Time until expiration, or None if no expiration set.
|
||||
"""
|
||||
if self._expires_at is None:
|
||||
return None
|
||||
|
||||
|
||||
remaining_seconds = self._expires_at - time.time()
|
||||
return timedelta(seconds=max(0, remaining_seconds))
|
||||
|
||||
@property
|
||||
def token_preview(self) -> str:
|
||||
"""Get a preview of the JWT token for logging/debugging.
|
||||
|
||||
|
||||
Returns:
|
||||
str: Masked token showing only first and last few characters.
|
||||
"""
|
||||
if not self._token:
|
||||
return "None"
|
||||
|
||||
|
||||
if len(self._token) <= 20:
|
||||
return "*" * len(self._token)
|
||||
|
||||
|
||||
return f"{self._token[:10]}...{self._token[-10:]}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""String representation of the auth handler.
|
||||
|
||||
|
||||
Returns:
|
||||
str: Safe representation with masked token.
|
||||
"""
|
||||
return f"JWTAuth(token='{self.token_preview}', expires_at={self._expires_at})"
|
||||
return f"JWTAuth(token='{self.token_preview}', expires_at={self._expires_at})"
|
||||
|
||||
Reference in New Issue
Block a user