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:
@@ -31,7 +31,10 @@ class Settings(BaseSettings):
|
||||
|
||||
# Gitea configuration
|
||||
gitea_url: HttpUrl = Field(..., description="Base URL of the Gitea instance")
|
||||
gitea_token: str = Field(..., description="Bot user access token for Gitea API", min_length=1)
|
||||
gitea_token: str = Field(
|
||||
default="",
|
||||
description=("Deprecated shared bot token. Not used for MCP tool execution in OAuth mode."),
|
||||
)
|
||||
|
||||
# MCP server configuration
|
||||
mcp_host: str = Field(
|
||||
@@ -96,6 +99,40 @@ class Settings(BaseSettings):
|
||||
description="Secret detection mode: off, mask, or block",
|
||||
)
|
||||
|
||||
# OAuth2 configuration (for ChatGPT per-user Gitea authentication)
|
||||
oauth_mode: bool = Field(
|
||||
default=False,
|
||||
description=(
|
||||
"Enable per-user OAuth2 authentication mode. "
|
||||
"When true, each ChatGPT user authenticates with their own Gitea account. "
|
||||
"GITEA_TOKEN and MCP_API_KEYS are not required in this mode."
|
||||
),
|
||||
)
|
||||
gitea_oauth_client_id: str = Field(
|
||||
default="",
|
||||
description="Gitea OAuth2 application client ID (required when oauth_mode=true)",
|
||||
)
|
||||
gitea_oauth_client_secret: str = Field(
|
||||
default="",
|
||||
description="Gitea OAuth2 application client secret (required when oauth_mode=true)",
|
||||
)
|
||||
oauth_expected_audience: str = Field(
|
||||
default="",
|
||||
description=(
|
||||
"Expected OIDC audience for access tokens. "
|
||||
"Defaults to GITEA_OAUTH_CLIENT_ID when unset."
|
||||
),
|
||||
)
|
||||
oauth_cache_ttl_seconds: int = Field(
|
||||
default=300,
|
||||
description="OIDC discovery/JWKS cache TTL in seconds",
|
||||
ge=30,
|
||||
)
|
||||
oauth_resource_documentation: str = Field(
|
||||
default="https://hiddenden.cafe/docs/mcp-gitea",
|
||||
description="Public documentation URL for OAuth-protected MCP resource behavior",
|
||||
)
|
||||
|
||||
# Authentication configuration
|
||||
auth_enabled: bool = Field(
|
||||
default=True,
|
||||
@@ -170,10 +207,10 @@ class Settings(BaseSettings):
|
||||
@field_validator("gitea_token")
|
||||
@classmethod
|
||||
def validate_token_not_empty(cls, value: str) -> str:
|
||||
"""Validate Gitea token is non-empty and trimmed."""
|
||||
"""Validate Gitea token is trimmed (empty string allowed for oauth_mode)."""
|
||||
cleaned = value.strip()
|
||||
if not cleaned:
|
||||
raise ValueError("gitea_token cannot be empty or whitespace")
|
||||
if value and not cleaned:
|
||||
raise ValueError("gitea_token cannot be whitespace-only")
|
||||
return cleaned
|
||||
|
||||
@field_validator("secret_detection_mode")
|
||||
@@ -217,11 +254,21 @@ class Settings(BaseSettings):
|
||||
"Set ALLOW_INSECURE_BIND=true to explicitly permit this."
|
||||
)
|
||||
|
||||
if self.auth_enabled and not parsed_keys:
|
||||
raise ValueError(
|
||||
"At least one API key must be configured when auth_enabled=True. "
|
||||
"Set MCP_API_KEYS or disable auth explicitly for controlled testing."
|
||||
)
|
||||
if self.oauth_mode:
|
||||
# In OAuth mode, per-user Gitea tokens are used; no shared bot token or API keys needed.
|
||||
if not self.gitea_oauth_client_id.strip():
|
||||
raise ValueError("GITEA_OAUTH_CLIENT_ID is required when OAUTH_MODE=true.")
|
||||
if not self.gitea_oauth_client_secret.strip():
|
||||
raise ValueError("GITEA_OAUTH_CLIENT_SECRET is required when OAUTH_MODE=true.")
|
||||
else:
|
||||
# Standard API key mode: require bot token and at least one API key.
|
||||
if not self.gitea_token.strip():
|
||||
raise ValueError("GITEA_TOKEN is required unless OAUTH_MODE=true.")
|
||||
if self.auth_enabled and not parsed_keys:
|
||||
raise ValueError(
|
||||
"At least one API key must be configured when auth_enabled=True. "
|
||||
"Set MCP_API_KEYS or disable auth explicitly for controlled testing."
|
||||
)
|
||||
|
||||
# Enforce minimum key length to reduce brute-force success probability.
|
||||
for key in parsed_keys:
|
||||
|
||||
Reference in New Issue
Block a user