docs: add AI code generation requirements and comprehensive Google-style docstrings
- Add AI code generation requirements to AGENTS.md - Add module-level docstrings to all 46 Python modules - Add detailed Google-style docstrings to all classes and functions - Remove all inline comments following self-documenting code principle - Include Args, Returns, Raises sections in function docstrings - Add Attributes and Examples sections to class docstrings
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
"""Authentication infrastructure package."""
|
||||
"""Authentication infrastructure package.
|
||||
|
||||
This module provides Keycloak authentication client and models
|
||||
for token validation and user info retrieval.
|
||||
"""
|
||||
|
||||
from app.infrastructure.auth.client import KeycloakAuthClient
|
||||
from app.infrastructure.auth.models import KeycloakUser, TokenInfo
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
"""Keycloak authentication client."""
|
||||
"""Keycloak authentication client.
|
||||
|
||||
This module provides a client for Keycloak authentication operations
|
||||
including token introspection and user info retrieval.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
@@ -9,10 +13,30 @@ from app.infrastructure.config.settings import Settings
|
||||
|
||||
|
||||
class KeycloakAuthClient:
|
||||
"""Client for Keycloak authentication operations."""
|
||||
"""Client for Keycloak authentication operations.
|
||||
|
||||
Handles token validation via introspection and user info retrieval.
|
||||
Implements token caching to reduce Keycloak server load.
|
||||
|
||||
Attributes:
|
||||
_settings: Application settings with Keycloak config.
|
||||
_base_url: Keycloak realm base URL.
|
||||
_client_id: OAuth client identifier.
|
||||
_client_secret: OAuth client secret.
|
||||
_cache: Token info cache for performance.
|
||||
_cache_ttl: Cache time-to-live in seconds.
|
||||
|
||||
Example:
|
||||
>>> client = KeycloakAuthClient(settings)
|
||||
>>> token_info = await client.introspect_token(token)
|
||||
"""
|
||||
|
||||
def __init__(self, settings: Settings) -> None:
|
||||
"""Initialize Keycloak client with settings."""
|
||||
"""Initialize Keycloak client with settings.
|
||||
|
||||
Args:
|
||||
settings: Application settings with Keycloak configuration.
|
||||
"""
|
||||
self._settings = settings
|
||||
self._base_url = f"{settings.kc.server_url}/realms/{settings.kc.realm}"
|
||||
self._client_id = settings.kc.client_id
|
||||
@@ -21,15 +45,30 @@ class KeycloakAuthClient:
|
||||
self._cache_ttl = settings.kc.token_cache_ttl
|
||||
|
||||
def _get_introspection_url(self) -> str:
|
||||
"""Get token introspection endpoint URL."""
|
||||
"""Get token introspection endpoint URL.
|
||||
|
||||
Returns:
|
||||
Full URL for token introspection endpoint.
|
||||
"""
|
||||
return f"{self._base_url}/protocol/openid-connect/token/introspection"
|
||||
|
||||
def _get_userinfo_url(self) -> str:
|
||||
"""Get userinfo endpoint URL."""
|
||||
"""Get userinfo endpoint URL.
|
||||
|
||||
Returns:
|
||||
Full URL for userinfo endpoint.
|
||||
"""
|
||||
return f"{self._base_url}/protocol/openid-connect/userinfo"
|
||||
|
||||
def _get_cached_token(self, token: str) -> TokenInfo | None:
|
||||
"""Get cached token info if valid."""
|
||||
"""Get cached token info if valid.
|
||||
|
||||
Args:
|
||||
token: Access token string.
|
||||
|
||||
Returns:
|
||||
Cached TokenInfo if valid and not expired, None otherwise.
|
||||
"""
|
||||
if token not in self._cache:
|
||||
return None
|
||||
|
||||
@@ -41,9 +80,13 @@ class KeycloakAuthClient:
|
||||
return token_info
|
||||
|
||||
def _cache_token(self, token: str, token_info: TokenInfo) -> None:
|
||||
"""Cache token info."""
|
||||
"""Cache token info.
|
||||
|
||||
Args:
|
||||
token: Access token string as cache key.
|
||||
token_info: TokenInfo to cache.
|
||||
"""
|
||||
self._cache[token] = (token_info, time.time())
|
||||
# Simple cleanup of old entries
|
||||
current_time = time.time()
|
||||
expired_keys = [
|
||||
k for k, (_, t) in self._cache.items() if current_time - t > self._cache_ttl
|
||||
@@ -52,13 +95,21 @@ class KeycloakAuthClient:
|
||||
del self._cache[k]
|
||||
|
||||
async def introspect_token(self, token: str) -> TokenInfo:
|
||||
"""Introspect access token using Keycloak."""
|
||||
# Check cache first
|
||||
"""Introspect access token using Keycloak.
|
||||
|
||||
Validates token with Keycloak server and extracts user information.
|
||||
Uses cache to reduce server requests for recently validated tokens.
|
||||
|
||||
Args:
|
||||
token: Access token to validate.
|
||||
|
||||
Returns:
|
||||
TokenInfo with validation result and user claims.
|
||||
"""
|
||||
cached = self._get_cached_token(token)
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
# Prepare introspection request
|
||||
data = {
|
||||
"token": token,
|
||||
"client_id": self._client_id,
|
||||
@@ -81,7 +132,6 @@ class KeycloakAuthClient:
|
||||
if not result.get("active", False):
|
||||
return TokenInfo(active=False, raw_claims=result)
|
||||
|
||||
# Extract roles from realm_access or resource_access
|
||||
roles: list[str] = []
|
||||
realm_access = result.get("realm_access", {})
|
||||
if isinstance(realm_access, dict):
|
||||
@@ -96,13 +146,21 @@ class KeycloakAuthClient:
|
||||
raw_claims=result,
|
||||
)
|
||||
|
||||
# Cache valid token
|
||||
self._cache_token(token, token_info)
|
||||
|
||||
return token_info
|
||||
|
||||
async def get_userinfo(self, token: str) -> KeycloakUser | None:
|
||||
"""Get user information from Keycloak using access token."""
|
||||
"""Get user information from Keycloak using access token.
|
||||
|
||||
Fetches detailed user profile from Keycloak userinfo endpoint.
|
||||
|
||||
Args:
|
||||
token: Valid access token.
|
||||
|
||||
Returns:
|
||||
KeycloakUser with profile data, or None on error.
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
"""Keycloak authentication models."""
|
||||
"""Keycloak authentication models.
|
||||
|
||||
This module defines data models for Keycloak authentication data
|
||||
including token info and user profiles.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
@@ -6,7 +10,24 @@ from typing import Any
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TokenInfo:
|
||||
"""Information about validated token from Keycloak."""
|
||||
"""Information about validated token from Keycloak.
|
||||
|
||||
Contains the result of token introspection including user claims
|
||||
and role information.
|
||||
|
||||
Attributes:
|
||||
active: Whether the token is active and valid.
|
||||
user_id: Subject identifier from token.
|
||||
username: Username from token claims.
|
||||
email: Email from token claims.
|
||||
roles: List of roles from token.
|
||||
raw_claims: Complete raw claims from token.
|
||||
|
||||
Example:
|
||||
>>> token_info = TokenInfo(active=True, user_id="123", roles=["user"])
|
||||
>>> if token_info.is_valid:
|
||||
... grant_access()
|
||||
"""
|
||||
|
||||
active: bool
|
||||
user_id: str = ""
|
||||
@@ -17,13 +38,32 @@ class TokenInfo:
|
||||
|
||||
@property
|
||||
def is_valid(self) -> bool:
|
||||
"""Check if token is valid and active."""
|
||||
"""Check if token is valid and active.
|
||||
|
||||
Returns:
|
||||
True if token is active and has user_id.
|
||||
"""
|
||||
return self.active and bool(self.user_id)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class KeycloakUser:
|
||||
"""User information from Keycloak."""
|
||||
"""User information from Keycloak.
|
||||
|
||||
Contains user profile data from Keycloak userinfo endpoint.
|
||||
|
||||
Attributes:
|
||||
id: User subject identifier.
|
||||
username: Username.
|
||||
email: Email address.
|
||||
first_name: First name.
|
||||
last_name: Last name.
|
||||
roles: List of user roles.
|
||||
is_active: Whether user account is active.
|
||||
|
||||
Example:
|
||||
>>> user = KeycloakUser(id="123", username="john", email="john@example.com")
|
||||
"""
|
||||
|
||||
id: str
|
||||
username: str
|
||||
|
||||
Reference in New Issue
Block a user