Add OAuth2/OIDC per-user Gitea authentication
Introduce a GiteaOAuthValidator for JWT and userinfo validation and fallbacks, add /oauth/token proxy, and thread per-user tokens through the request context and automation paths. Update config and .env.example for OAuth-first mode, add OpenAPI, extensive unit/integration tests, GitHub/Gitea CI workflows, docs, and lint/test enforcement (>=80% cov).
This commit is contained in:
@@ -19,7 +19,7 @@ class GiteaAuthenticationError(GiteaError):
|
||||
|
||||
|
||||
class GiteaAuthorizationError(GiteaError):
|
||||
"""Raised when bot user lacks permission for an operation."""
|
||||
"""Raised when the authenticated user lacks permission for an operation."""
|
||||
|
||||
|
||||
class GiteaNotFoundError(GiteaError):
|
||||
@@ -27,19 +27,21 @@ class GiteaNotFoundError(GiteaError):
|
||||
|
||||
|
||||
class GiteaClient:
|
||||
"""Client for interacting with Gitea API as a bot user."""
|
||||
"""Client for interacting with Gitea API as the authenticated end-user."""
|
||||
|
||||
def __init__(self, base_url: str | None = None, token: str | None = None) -> None:
|
||||
def __init__(self, token: str, base_url: str | None = None) -> None:
|
||||
"""Initialize Gitea client.
|
||||
|
||||
Args:
|
||||
token: OAuth access token for the authenticated user.
|
||||
base_url: Optional base URL override.
|
||||
token: Optional token override.
|
||||
"""
|
||||
self.settings = get_settings()
|
||||
self.audit = get_audit_logger()
|
||||
self.base_url = (base_url or self.settings.gitea_base_url).rstrip("/")
|
||||
self.token = token or self.settings.gitea_token
|
||||
self.token = token.strip()
|
||||
if not self.token:
|
||||
raise ValueError("GiteaClient requires a non-empty per-user OAuth token")
|
||||
self.client: AsyncClient | None = None
|
||||
|
||||
async def __aenter__(self) -> GiteaClient:
|
||||
@@ -47,7 +49,7 @@ class GiteaClient:
|
||||
self.client = AsyncClient(
|
||||
base_url=self.base_url,
|
||||
headers={
|
||||
"Authorization": f"token {self.token}",
|
||||
"Authorization": f"Bearer {self.token}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout=self.settings.request_timeout_seconds,
|
||||
@@ -79,15 +81,15 @@ class GiteaClient:
|
||||
severity="high",
|
||||
metadata={"correlation_id": correlation_id},
|
||||
)
|
||||
raise GiteaAuthenticationError("Authentication failed - check bot token")
|
||||
raise GiteaAuthenticationError("Authentication failed - user token rejected")
|
||||
|
||||
if response.status_code == 403:
|
||||
self.audit.log_access_denied(
|
||||
tool_name="gitea_api",
|
||||
reason="bot user lacks permission",
|
||||
reason="authenticated user lacks permission",
|
||||
correlation_id=correlation_id,
|
||||
)
|
||||
raise GiteaAuthorizationError("Bot user lacks permission for this operation")
|
||||
raise GiteaAuthorizationError("Authenticated user lacks permission for this operation")
|
||||
|
||||
if response.status_code == 404:
|
||||
raise GiteaNotFoundError("Resource not found")
|
||||
@@ -123,7 +125,7 @@ class GiteaClient:
|
||||
return self._handle_response(response, correlation_id)
|
||||
|
||||
async def get_current_user(self) -> dict[str, Any]:
|
||||
"""Get current bot user profile."""
|
||||
"""Get current authenticated user profile."""
|
||||
correlation_id = self.audit.log_tool_invocation(
|
||||
tool_name="get_current_user",
|
||||
result_status="pending",
|
||||
@@ -146,7 +148,7 @@ class GiteaClient:
|
||||
raise
|
||||
|
||||
async def list_repositories(self) -> list[dict[str, Any]]:
|
||||
"""List all repositories visible to the bot user."""
|
||||
"""List repositories visible to the authenticated user."""
|
||||
correlation_id = self.audit.log_tool_invocation(
|
||||
tool_name="list_repositories",
|
||||
result_status="pending",
|
||||
|
||||
Reference in New Issue
Block a user