fix: allow OAuth trigger builder verification without credentials (#39199)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
非法操作
2026-08-25 05:32:27 +00:00
committed by GitHub
parent caf7e90ca5
commit f360a7bf94
7 changed files with 147 additions and 12 deletions
@@ -54,6 +54,10 @@ class TriggerSubscriptionBuilderCreatePayload(BaseModel):
class TriggerSubscriptionBuilderVerifyPayload(BaseModel):
credentials: dict[str, Any] | None = None
class TriggerSubscriptionVerifyPayload(BaseModel):
credentials: dict[str, Any]
@@ -120,6 +124,7 @@ register_schema_models(
TriggerSubscriptionBuilderCreatePayload,
TriggerSubscriptionBuilderVerifyPayload,
TriggerSubscriptionBuilderUpdatePayload,
TriggerSubscriptionVerifyPayload,
TriggerOAuthClientPayload,
)
register_response_schema_models(
@@ -812,7 +817,7 @@ class TriggerOAuthClientManageApi(Resource):
"/workspaces/current/trigger-provider/<path:provider>/subscriptions/verify/<path:subscription_id>",
)
class TriggerSubscriptionVerifyApi(Resource):
@console_ns.expect(console_ns.models[TriggerSubscriptionBuilderVerifyPayload.__name__])
@console_ns.expect(console_ns.models[TriggerSubscriptionVerifyPayload.__name__])
@console_ns.response(
200,
"Trigger subscription verified successfully",
@@ -825,10 +830,10 @@ class TriggerSubscriptionVerifyApi(Resource):
@account_initialization_required
@with_current_user
@with_current_tenant_id
@model_validate(TriggerSubscriptionBuilderVerifyPayload)
@model_validate(TriggerSubscriptionVerifyPayload)
def post(
self,
req_data: TriggerSubscriptionBuilderVerifyPayload,
req_data: TriggerSubscriptionVerifyPayload,
tenant_id: str,
user: Account,
provider: str,
+7 -1
View File
@@ -12868,7 +12868,7 @@ Import a Skill zip package from multipart form field `file`.
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [TriggerSubscriptionBuilderVerifyPayload](#triggersubscriptionbuilderverifypayload)<br> |
| Yes | **application/json**: [TriggerSubscriptionVerifyPayload](#triggersubscriptionverifypayload)<br> |
#### Responses
@@ -23453,6 +23453,12 @@ The identity of the trigger provider
#### TriggerSubscriptionBuilderVerifyPayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| credentials | object | | No |
#### TriggerSubscriptionVerifyPayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| credentials | object | | Yes |
@@ -7,10 +7,15 @@ from unittest.mock import patch
import pytest
from flask import Flask
from pydantic import ValidationError
from sqlalchemy.engine import Engine
from werkzeug.exceptions import BadRequest
from controllers.console.workspace.trigger_providers import TriggerSubscriptionDeleteApi
from controllers.console.workspace.trigger_providers import (
TriggerSubscriptionBuilderVerifyPayload,
TriggerSubscriptionDeleteApi,
TriggerSubscriptionVerifyPayload,
)
from models.engine import db
trigger_provider_module = import_module("controllers.console.workspace.trigger_providers")
@@ -59,3 +64,14 @@ def test_delete_subscription_translates_value_error(flask_sqlite_engine: Engine)
pytest.raises(BadRequest, match="bad"),
):
method(api, "t1", "sub1")
def test_builder_verify_payload_allows_credentials_to_be_omitted() -> None:
payload = TriggerSubscriptionBuilderVerifyPayload.model_validate({})
assert payload.credentials is None
def test_subscription_verify_payload_requires_credentials() -> None:
with pytest.raises(ValidationError):
TriggerSubscriptionVerifyPayload.model_validate({})
@@ -1191,9 +1191,9 @@ export type SubscriptionBuilderApiEntity = {
}
export type TriggerSubscriptionBuilderVerifyPayload = {
credentials: {
credentials?: {
[key: string]: unknown
}
} | null
}
export type TriggerVerificationResponse = {
@@ -1212,6 +1212,12 @@ export type TriggerOAuthAuthorizeResponse = {
subscription_builder_id: string
}
export type TriggerSubscriptionVerifyPayload = {
credentials: {
[key: string]: unknown
}
}
export type TriggerProviderListResponse = Array<TriggerProviderApiEntity>
export type WorkspaceCustomConfigResponse = {
@@ -6314,7 +6320,7 @@ export type GetWorkspacesCurrentTriggerProviderByProviderSubscriptionsOauthAutho
export type PostWorkspacesCurrentTriggerProviderByProviderSubscriptionsVerifyBySubscriptionIdData =
{
body: TriggerSubscriptionBuilderVerifyPayload
body: TriggerSubscriptionVerifyPayload
path: {
provider: string
subscription_id: string
@@ -727,7 +727,7 @@ export const zTriggerSubscriptionBuilderCreatePayload = z.object({
* TriggerSubscriptionBuilderVerifyPayload
*/
export const zTriggerSubscriptionBuilderVerifyPayload = z.object({
credentials: z.record(z.string(), z.unknown()),
credentials: z.record(z.string(), z.unknown()).nullish(),
})
/**
@@ -744,6 +744,13 @@ export const zTriggerProviderErrorResponse = z.object({
error: z.string(),
})
/**
* TriggerSubscriptionVerifyPayload
*/
export const zTriggerSubscriptionVerifyPayload = z.object({
credentials: z.record(z.string(), z.unknown()),
})
/**
* WorkspaceCustomConfigResponse
*/
@@ -6031,7 +6038,7 @@ export const zGetWorkspacesCurrentTriggerProviderByProviderSubscriptionsOauthAut
zTriggerOAuthAuthorizeResponse
export const zPostWorkspacesCurrentTriggerProviderByProviderSubscriptionsVerifyBySubscriptionIdBody =
zTriggerSubscriptionBuilderVerifyPayload
zTriggerSubscriptionVerifyPayload
export const zPostWorkspacesCurrentTriggerProviderByProviderSubscriptionsVerifyBySubscriptionIdPath =
z.object({
+96 -1
View File
@@ -1,6 +1,73 @@
import type { TriggerProviderApiEntity as GeneratedTriggerProvider } from '@dify/contracts/api/console/workspaces/types.gen'
import type { ReactNode } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { act, renderHook } from '@testing-library/react'
import { createElement } from 'react'
import { describe, expect, it } from 'vite-plus/test'
import { convertToTriggerWithProvider, normalizeTriggerProvider } from '../use-triggers'
import {
convertToTriggerWithProvider,
normalizeTriggerProvider,
useVerifyAndUpdateTriggerSubscriptionBuilder,
} from '../use-triggers'
const mocks = vi.hoisted(() => ({
mutationKey: vi.fn(() => ['trigger-builder-verify']),
verifyAndUpdate: vi.fn().mockResolvedValue({ verified: false }),
}))
vi.mock('@/service/client', () => ({
consoleClient: {
workspaces: {
current: {
triggerProvider: {
byProvider: {
subscriptions: {
builder: {
verifyAndUpdate: {
bySubscriptionBuilderId: {
post: mocks.verifyAndUpdate,
},
},
},
},
},
},
},
},
},
consoleQuery: {
workspaces: {
current: {
triggerProvider: {
byProvider: {
subscriptions: {
builder: {
verifyAndUpdate: {
bySubscriptionBuilderId: {
post: {
mutationKey: mocks.mutationKey,
},
},
},
},
},
},
},
},
},
},
}))
const createWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: {
mutations: { retry: false },
},
})
return ({ children }: { children: ReactNode }) =>
createElement(QueryClientProvider, { client: queryClient }, children)
}
const createGeneratedTriggerProvider = (): GeneratedTriggerProvider => ({
author: 'Dify',
@@ -42,3 +109,31 @@ describe('trigger provider normalization', () => {
expect(triggerWithProvider.events[0]?.parameters[0]?.default).toBe(0)
})
})
describe('trigger subscription builder verification', () => {
it('should omit credentials while polling OAuth authorization', async () => {
const { result } = renderHook(() => useVerifyAndUpdateTriggerSubscriptionBuilder(), {
wrapper: createWrapper(),
})
await act(async () => {
await result.current.mutateAsync({
provider: 'langgenius/gmail_trigger/gmail_trigger',
subscriptionBuilderId: 'builder-1',
})
})
expect(mocks.verifyAndUpdate).toHaveBeenCalledWith(
{
params: {
provider: 'langgenius/gmail_trigger/gmail_trigger',
subscription_builder_id: 'builder-1',
},
body: {},
},
{
context: { silent: true },
},
)
})
})
+1 -1
View File
@@ -657,7 +657,7 @@ export const useVerifyAndUpdateTriggerSubscriptionBuilder = () => {
return consoleClient.workspaces.current.triggerProvider.byProvider.subscriptions.builder.verifyAndUpdate.bySubscriptionBuilderId.post(
{
params: { provider, subscription_builder_id: subscriptionBuilderId },
body: { credentials: credentials ?? {} },
body: credentials === undefined ? {} : { credentials },
},
{
context: { silent: true },