mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 01:18:05 +08:00
feat: add end user oauth apis (#29683)
This commit is contained in:
@ -54,6 +54,7 @@ from .app import (
|
||||
completion,
|
||||
conversation,
|
||||
conversation_variables,
|
||||
enduser_auth,
|
||||
generator,
|
||||
mcp_server,
|
||||
message,
|
||||
@ -162,6 +163,7 @@ __all__ = [
|
||||
"datasource_content_preview",
|
||||
"email_register",
|
||||
"endpoint",
|
||||
"enduser_auth",
|
||||
"extension",
|
||||
"external",
|
||||
"feature",
|
||||
|
||||
230
api/controllers/console/app/enduser_auth.py
Normal file
230
api/controllers/console/app/enduser_auth.py
Normal file
@ -0,0 +1,230 @@
|
||||
"""
|
||||
End-user authentication API controllers.
|
||||
|
||||
Provides API endpoints for managing end-user credentials for tool authentication.
|
||||
"""
|
||||
|
||||
from flask import request
|
||||
from flask_restx import Resource
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.app.wraps import get_app_model
|
||||
from controllers.console.wraps import account_initialization_required, setup_required
|
||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||
from libs.login import current_account_with_tenant, login_required
|
||||
from models import AppMode
|
||||
from services.tools.app_auth_requirement_service import AppAuthRequirementService
|
||||
from services.tools.enduser_auth_service import EndUserAuthService
|
||||
from services.tools.enduser_oauth_service import EndUserOAuthService
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/auth/providers")
|
||||
class AppAuthProvidersApi(Resource):
|
||||
"""
|
||||
Get list of authentication providers required for an app.
|
||||
|
||||
Returns providers that require end-user authentication based on app configuration.
|
||||
"""
|
||||
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.COMPLETION, AppMode.WORKFLOW])
|
||||
def get(self, app_model):
|
||||
"""Get authentication providers required for the app."""
|
||||
_, tenant_id = current_account_with_tenant()
|
||||
|
||||
providers = AppAuthRequirementService.get_required_providers(
|
||||
tenant_id=tenant_id,
|
||||
app_id=str(app_model.id),
|
||||
)
|
||||
|
||||
return jsonable_encoder(providers)
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/auth/providers/<path:provider_id>/credentials")
|
||||
class AppAuthProviderCredentialsApi(Resource):
|
||||
"""
|
||||
Manage end-user credentials for a specific provider.
|
||||
|
||||
Allows listing, creating, and deleting end-user credentials.
|
||||
"""
|
||||
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.COMPLETION, AppMode.WORKFLOW])
|
||||
def get(self, app_model, provider_id: str):
|
||||
"""List end-user's credentials for this provider."""
|
||||
user, tenant_id = current_account_with_tenant()
|
||||
|
||||
# For console API, use the current account user as end_user_id
|
||||
# In production, this would be the actual end-user ID from the chat/completion request
|
||||
end_user_id = str(user.id)
|
||||
|
||||
credentials = EndUserAuthService.list_credentials(
|
||||
tenant_id=tenant_id,
|
||||
end_user_id=end_user_id,
|
||||
provider_id=provider_id,
|
||||
)
|
||||
|
||||
return jsonable_encoder(credentials)
|
||||
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.COMPLETION, AppMode.WORKFLOW])
|
||||
def post(self, app_model, provider_id: str):
|
||||
"""Create a new credential (API key only)."""
|
||||
user, tenant_id = current_account_with_tenant()
|
||||
end_user_id = str(user.id)
|
||||
|
||||
payload = request.get_json()
|
||||
if not payload:
|
||||
raise BadRequest("Request body is required")
|
||||
|
||||
credential_type = payload.get("credential_type")
|
||||
credentials = payload.get("credentials")
|
||||
|
||||
if not credential_type or not credentials:
|
||||
raise BadRequest("credential_type and credentials are required")
|
||||
|
||||
if credential_type != "api-key":
|
||||
raise BadRequest(
|
||||
"Only 'api-key' credential type can be created via this endpoint. "
|
||||
"Use OAuth flow for OAuth credentials."
|
||||
)
|
||||
|
||||
credential = EndUserAuthService.create_api_key_credential(
|
||||
tenant_id=tenant_id,
|
||||
end_user_id=end_user_id,
|
||||
provider_id=provider_id,
|
||||
credentials=credentials,
|
||||
)
|
||||
|
||||
return jsonable_encoder(credential)
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/auth/providers/<path:provider_id>/credentials/<string:credential_id>")
|
||||
class AppAuthProviderCredentialApi(Resource):
|
||||
"""
|
||||
Manage a specific end-user credential.
|
||||
|
||||
Allows getting, updating, or deleting a credential.
|
||||
"""
|
||||
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.COMPLETION, AppMode.WORKFLOW])
|
||||
def delete(self, app_model, provider_id: str, credential_id: str):
|
||||
"""Delete a credential."""
|
||||
user, tenant_id = current_account_with_tenant()
|
||||
end_user_id = str(user.id)
|
||||
|
||||
EndUserAuthService.delete_credential(
|
||||
tenant_id=tenant_id,
|
||||
end_user_id=end_user_id,
|
||||
provider_id=provider_id,
|
||||
credential_id=credential_id,
|
||||
)
|
||||
|
||||
return {"result": "success"}
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/auth/oauth/<path:provider_id>/authorization-url")
|
||||
class AppAuthOAuthAuthorizationUrlApi(Resource):
|
||||
"""
|
||||
Get OAuth authorization URL for end-user authentication.
|
||||
|
||||
Returns the URL where the user should be redirected to authenticate with the provider.
|
||||
"""
|
||||
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.COMPLETION, AppMode.WORKFLOW])
|
||||
def get(self, app_model, provider_id: str):
|
||||
"""Get OAuth authorization URL."""
|
||||
user, tenant_id = current_account_with_tenant()
|
||||
end_user_id = str(user.id)
|
||||
|
||||
result = EndUserOAuthService.get_authorization_url(
|
||||
end_user_id=end_user_id,
|
||||
tenant_id=tenant_id,
|
||||
app_id=str(app_model.id),
|
||||
provider=provider_id,
|
||||
)
|
||||
|
||||
# Set OAuth context cookie for callback
|
||||
response = jsonable_encoder({
|
||||
"authorization_url": result["authorization_url"],
|
||||
})
|
||||
|
||||
# Store context_id in response for frontend to set as cookie
|
||||
response["context_id"] = result["context_id"]
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/auth/oauth/<path:provider_id>/callback")
|
||||
class AppAuthOAuthCallbackApi(Resource):
|
||||
"""
|
||||
Handle OAuth callback for end-user authentication.
|
||||
|
||||
This endpoint is called by the OAuth provider after user authorization.
|
||||
"""
|
||||
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.COMPLETION, AppMode.WORKFLOW])
|
||||
def get(self, app_model, provider_id: str):
|
||||
"""Handle OAuth callback and store credentials."""
|
||||
# Get OAuth context ID from cookie
|
||||
context_id = request.cookies.get("oauth_context_id")
|
||||
|
||||
if not context_id:
|
||||
raise BadRequest("Missing OAuth context")
|
||||
|
||||
# Get OAuth error if any
|
||||
error = request.args.get("error")
|
||||
error_description = request.args.get("error_description")
|
||||
|
||||
if error:
|
||||
raise BadRequest(f"OAuth error: {error} - {error_description}")
|
||||
|
||||
# Handle callback and create credential
|
||||
result = EndUserOAuthService.handle_oauth_callback(
|
||||
context_id=context_id,
|
||||
request=request,
|
||||
)
|
||||
|
||||
return jsonable_encoder(result)
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/auth/providers/<path:provider_id>/credentials/<string:credential_id>/refresh")
|
||||
class AppAuthProviderRefreshApi(Resource):
|
||||
"""
|
||||
Manually refresh OAuth token for a credential.
|
||||
|
||||
This endpoint allows refreshing an expired OAuth token.
|
||||
"""
|
||||
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT, AppMode.COMPLETION, AppMode.WORKFLOW])
|
||||
def post(self, app_model, provider_id: str, credential_id: str):
|
||||
"""Refresh OAuth token."""
|
||||
user, tenant_id = current_account_with_tenant()
|
||||
end_user_id = str(user.id)
|
||||
|
||||
result = EndUserOAuthService.refresh_oauth_token(
|
||||
credential_id=credential_id,
|
||||
end_user_id=end_user_id,
|
||||
tenant_id=tenant_id,
|
||||
)
|
||||
|
||||
return jsonable_encoder(result)
|
||||
Reference in New Issue
Block a user