feat: add subscription verification endpoint and enhance subscription handling

- Introduced `SubscriptionVerifyRequest` model for verifying subscription credentials.
- Implemented `TriggerSubscriptionVerifyApi` to handle verification requests for existing subscriptions.
- Updated `TriggerSubscriptionBuilderVerifyApi` to reflect changes in functionality, now verifying and updating subscriptions.
- Enhanced `TriggerProviderService` with a new method for verifying subscription credentials without updating them, improving validation processes.
This commit is contained in:
Harry
2025-12-18 17:04:40 +08:00
parent c2af26e9df
commit 311fddb4ef
2 changed files with 102 additions and 8 deletions

View File

@@ -45,10 +45,17 @@ class TriggerSubscriptionUpdateRequest(BaseModel):
class SubscriptionRebuildRequest(BaseModel):
"""Request payload for rebuilding an existing subscription."""
name: str | None = Field(default=None, description="The name for the subscription")
credentials: Mapping[str, Any] | None = Field(default=None, description="The credentials for the subscription")
parameters: Mapping[str, Any] = Field(default_factory=dict, description="The parameters for the subscription")
class SubscriptionVerifyRequest(BaseModel):
"""Request payload for verifying subscription credentials."""
credentials: Mapping[str, Any] = Field(description="The credentials to verify")
console_ns.schema_model(
TriggerSubscriptionUpdateRequest.__name__,
TriggerSubscriptionUpdateRequest.model_json_schema(ref_template="#/definitions/{model}"),
@@ -59,6 +66,11 @@ console_ns.schema_model(
SubscriptionRebuildRequest.model_json_schema(ref_template="#/definitions/{model}"),
)
console_ns.schema_model(
SubscriptionVerifyRequest.__name__,
SubscriptionVerifyRequest.model_json_schema(ref_template="#/definitions/{model}"),
)
@console_ns.route("/workspaces/current/trigger-provider/<path:provider>/icon")
class TriggerProviderIconApi(Resource):
@@ -183,16 +195,16 @@ parser_api = (
@console_ns.route(
"/workspaces/current/trigger-provider/<path:provider>/subscriptions/builder/verify/<path:subscription_builder_id>",
"/workspaces/current/trigger-provider/<path:provider>/subscriptions/builder/verify-and-update/<path:subscription_builder_id>",
)
class TriggerSubscriptionBuilderVerifyApi(Resource):
class TriggerSubscriptionBuilderVerifyAndUpdateApi(Resource):
@console_ns.expect(parser_api)
@setup_required
@login_required
@edit_permission_required
@account_initialization_required
def post(self, provider, subscription_builder_id):
"""Verify a subscription instance for a trigger provider"""
"""Verify and update a subscription instance for a trigger provider"""
user = current_user
assert user.current_tenant_id is not None
@@ -421,6 +433,7 @@ class TriggerSubscriptionRebuildApi(Resource):
try:
TriggerProviderService.rebuild_trigger_subscription(
tenant_id=user.current_tenant_id,
name=rebuild_request.name,
provider_id=TriggerProviderID(provider),
subscription_id=subscription_id,
credentials=rebuild_request.credentials or {},
@@ -686,3 +699,36 @@ class TriggerOAuthClientManageApi(Resource):
except Exception as e:
logger.exception("Error removing OAuth client", exc_info=e)
raise
@console_ns.route(
"/workspaces/current/trigger-provider/<path:provider>/subscriptions/verify/<path:subscription_id>",
)
class TriggerSubscriptionVerifyApi(Resource):
@console_ns.expect(console_ns.models[SubscriptionVerifyRequest.__name__])
@setup_required
@login_required
@edit_permission_required
@account_initialization_required
def post(self, provider, subscription_id):
"""Verify credentials for an existing subscription (edit mode only)"""
user = current_user
assert user.current_tenant_id is not None
verify_request: SubscriptionVerifyRequest = SubscriptionVerifyRequest.model_validate(console_ns.payload)
try:
result = TriggerProviderService.verify_subscription_credentials(
tenant_id=user.current_tenant_id,
user_id=user.id,
provider_id=TriggerProviderID(provider),
subscription_id=subscription_id,
credentials=verify_request.credentials,
)
return result
except ValueError as e:
logger.warning("Credential verification failed", exc_info=e)
raise BadRequest(str(e)) from e
except Exception as e:
logger.exception("Error verifying subscription credentials", exc_info=e)
raise BadRequest(str(e)) from e

View File

@@ -360,7 +360,7 @@ class TriggerProviderService:
credential_type: CredentialType = CredentialType.of(subscription.credential_type)
is_auto_created: bool = credential_type in [CredentialType.OAUTH2, CredentialType.API_KEY]
if is_auto_created:
if not is_auto_created:
return None
provider_id = TriggerProviderID(subscription.provider_id)
@@ -384,8 +384,8 @@ class TriggerProviderService:
except Exception as e:
logger.exception("Error unsubscribing trigger", exc_info=e)
# Clear cache
session.delete(subscription)
# Clear cache
delete_cache_for_subscription(
tenant_id=tenant_id,
provider_id=subscription.provider_id,
@@ -793,6 +793,50 @@ class TriggerProviderService:
subscription.properties = dict(properties_encrypter.decrypt(subscription.properties))
return subscription
@classmethod
def verify_subscription_credentials(
cls,
tenant_id: str,
user_id: str,
provider_id: TriggerProviderID,
subscription_id: str,
credentials: Mapping[str, Any],
) -> dict[str, Any]:
"""
Verify credentials for an existing subscription without updating it.
This is used in edit mode to validate new credentials before rebuild.
:param tenant_id: Tenant ID
:param user_id: User ID
:param provider_id: Provider identifier
:param subscription_id: Subscription ID
:param credentials: New credentials to verify
:return: dict with 'verified' boolean
"""
provider_controller = TriggerManager.get_trigger_provider(tenant_id, provider_id)
if not provider_controller:
raise ValueError(f"Provider {provider_id} not found")
subscription = cls.get_subscription_by_id(
tenant_id=tenant_id,
subscription_id=subscription_id,
)
if not subscription:
raise ValueError(f"Subscription {subscription_id} not found")
credential_type = CredentialType.of(subscription.credential_type)
# For API Key, validate the new credentials
if credential_type == CredentialType.API_KEY:
try:
provider_controller.validate_credentials(user_id, credentials)
return {"verified": True}
except Exception as e:
raise ValueError(f"Invalid credentials: {e}") from e
return {"verified": True}
@classmethod
def rebuild_trigger_subscription(
cls,
@@ -801,6 +845,7 @@ class TriggerProviderService:
subscription_id: str,
credentials: Mapping[str, Any],
parameters: Mapping[str, Any],
name: str | None = None,
) -> None:
"""
Create a subscription builder for rebuilding an existing subscription.
@@ -809,6 +854,7 @@ class TriggerProviderService:
keeping the same subscription_id and endpoint_id so the webhook URL remains unchanged.
:param tenant_id: Tenant ID
:param name: Name for the subscription
:param subscription_id: Subscription ID
:param provider_id: Provider identifier
:param credentials: Credentials for the subscription
@@ -849,7 +895,7 @@ class TriggerProviderService:
credentials=encrypter.decrypt(subscription.credentials),
credential_type=credential_type,
)
new_credentials = credentials or subscription.credentials
# Create a new subscription with the same subscription_id and endpoint_id
new_subscription: TriggerSubscriptionEntity = TriggerManager.subscribe_trigger(
tenant_id=tenant_id,
@@ -857,13 +903,15 @@ class TriggerProviderService:
provider_id=provider_id,
endpoint=generate_plugin_trigger_endpoint_url(subscription.endpoint_id),
parameters=parameters,
credentials=credentials or subscription.credentials,
credentials=new_credentials,
credential_type=credential_type,
)
TriggerProviderService.update_trigger_subscription(
tenant_id=tenant_id,
subscription_id=subscription.id,
name=name,
parameters=parameters,
credentials=credentials,
credentials=new_credentials,
properties=new_subscription.properties,
expires_at=new_subscription.expires_at,
)