mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
fix: clear DingTalk bot credentials through explicit unbind
DingTalk bot unbind was routed through the generic adapter config update, but undefined credential fields are dropped during JSON serialization. That left stored Client ID and Secret values intact after clicking the unbind button. Match the working WeChat path with a dedicated server endpoint and frontend API call so credential removal is explicit. Constraint: JSON request bodies cannot preserve undefined fields for generic config clearing Rejected: Keep using updateConfig with undefined fields | serialized requests omit the fields before the server can clear them Confidence: high Scope-risk: narrow Directive: Keep credential-clearing flows on explicit unbind endpoints rather than relying on partial config merges Tested: bun test src/server/__tests__/adapters.test.ts Tested: cd desktop && bun run test src/stores/adapterStore.test.ts Tested: bun run check:desktop Tested: bun run check:server Not-tested: bun run quality:pr is blocked by repository policy requiring allow-cli-core-change approval for the broader branch impact
This commit is contained in:
parent
432a58d725
commit
71f851d60a
@ -41,6 +41,10 @@ export const adaptersApi = {
|
||||
return api.post<AdapterFileConfig>('/api/adapters/wechat/unbind', {})
|
||||
},
|
||||
|
||||
unbindDingtalk() {
|
||||
return api.post<AdapterFileConfig>('/api/adapters/dingtalk/unbind', {})
|
||||
},
|
||||
|
||||
beginDingtalkRegistration() {
|
||||
return api.post<DingtalkRegistrationBegin>('/api/adapters/dingtalk/registration/begin', {})
|
||||
},
|
||||
|
||||
@ -7,6 +7,7 @@ describe('adapterStore IM pairing behavior', () => {
|
||||
startWechatLogin: vi.fn(),
|
||||
pollWechatLogin: vi.fn(),
|
||||
unbindWechat: vi.fn(),
|
||||
unbindDingtalk: vi.fn(),
|
||||
beginDingtalkRegistration: vi.fn(),
|
||||
pollDingtalkRegistration: vi.fn(),
|
||||
}
|
||||
@ -59,4 +60,17 @@ describe('adapterStore IM pairing behavior', () => {
|
||||
expect(adaptersApi.unbindWechat).toHaveBeenCalledTimes(1)
|
||||
expect(useAdapterStore.getState().config).toBe(nextConfig)
|
||||
})
|
||||
|
||||
it('unbinds the DingTalk bot through the explicit bot action', async () => {
|
||||
const nextConfig = { dingtalk: { pairedUsers: [], allowedUsers: [] } }
|
||||
adaptersApi.unbindDingtalk.mockResolvedValue(nextConfig)
|
||||
|
||||
const { useAdapterStore } = await import('./adapterStore')
|
||||
|
||||
await useAdapterStore.getState().unbindDingtalkBot()
|
||||
|
||||
expect(adaptersApi.updateConfig).not.toHaveBeenCalled()
|
||||
expect(adaptersApi.unbindDingtalk).toHaveBeenCalledTimes(1)
|
||||
expect(useAdapterStore.getState().config).toBe(nextConfig)
|
||||
})
|
||||
})
|
||||
|
||||
@ -132,13 +132,9 @@ export const useAdapterStore = create<AdapterStore>((set, get) => ({
|
||||
},
|
||||
|
||||
unbindDingtalkBot: async () => {
|
||||
await get().updateConfig({
|
||||
dingtalk: {
|
||||
clientId: undefined,
|
||||
clientSecret: undefined,
|
||||
pairedUsers: [],
|
||||
},
|
||||
})
|
||||
const config = await adaptersApi.unbindDingtalk()
|
||||
set({ config })
|
||||
void notifyTauriRestartAdapters()
|
||||
},
|
||||
|
||||
removePairedUser: async (platform, userId) => {
|
||||
|
||||
@ -125,4 +125,27 @@ describe('Adapters API', () => {
|
||||
expect(json.wechat.allowedUsers).toEqual([])
|
||||
expect(json.wechat.pairedUsers).toEqual([])
|
||||
})
|
||||
|
||||
it('clears DingTalk credentials on unbind', async () => {
|
||||
const put = makeRequest('PUT', '/api/adapters', {
|
||||
dingtalk: {
|
||||
clientId: 'ding-client-1',
|
||||
clientSecret: 'dingtalk-client-secret',
|
||||
allowedUsers: ['ding-allowed-user'],
|
||||
permissionCardTemplateId: 'permission-template',
|
||||
pairedUsers: [{ userId: 'ding-user', displayName: 'DingTalk User', pairedAt: 1 }],
|
||||
},
|
||||
})
|
||||
await handleAdaptersApi(put.req, put.url, put.segments)
|
||||
|
||||
const unbind = makeRequest('POST', '/api/adapters/dingtalk/unbind')
|
||||
const res = await handleAdaptersApi(unbind.req, unbind.url, unbind.segments)
|
||||
expect(res.status).toBe(200)
|
||||
const json = await res.json() as any
|
||||
expect(json.dingtalk.clientId).toBeUndefined()
|
||||
expect(json.dingtalk.clientSecret).toBeUndefined()
|
||||
expect(json.dingtalk.allowedUsers).toEqual([])
|
||||
expect(json.dingtalk.permissionCardTemplateId).toBeUndefined()
|
||||
expect(json.dingtalk.pairedUsers).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
@ -143,6 +143,18 @@ export async function handleAdaptersApi(
|
||||
if (tail[0] === 'wechat') {
|
||||
return handleWechatAdaptersApi(req, tail.slice(1))
|
||||
}
|
||||
if (tail[0] === 'dingtalk' && req.method === 'POST' && tail[1] === 'unbind') {
|
||||
await adapterService.updateConfig({
|
||||
dingtalk: {
|
||||
clientId: undefined,
|
||||
clientSecret: undefined,
|
||||
allowedUsers: [],
|
||||
pairedUsers: [],
|
||||
permissionCardTemplateId: undefined,
|
||||
},
|
||||
})
|
||||
return Response.json(await adapterService.getConfig())
|
||||
}
|
||||
if (tail[0] === 'dingtalk' && tail[1] === 'registration') {
|
||||
if (req.method === 'POST' && tail[2] === 'begin') {
|
||||
return Response.json(await beginDingtalkRegistration())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user