webhook.mjs
2.63 KB
#!/usr/bin/env node
/**
* Webhook 的真实链路脚本。目前覆盖 WH-03(非 https、回环、内网、元数据地址一律拒绝)与登记 / 取消。
* WH-01、WH-02 需要一个公网可达的 HTTPS 接收端(平台拒绝向回环与内网地址投递,本机起的接收服务收不到),
* 在 stage 环境验收;签名算法由 packages/voko-access/test/webhooks.test.js 覆盖。
*/
import { runScenario } from './_lib.mjs'
await runScenario('wh', async ({ db, check, call, createOwner, createGuild, createAgent }) => {
const alice = await createOwner('alice')
const guild = await createGuild('main', alice.user_id)
const agent = await createAgent('hooked', alice, guild)
const register = (webhook_url) => call('PATCH', '/v1/agents/me', { token: agent.key, body: { webhook_url } })
for (const url of ['http://example.com/hook', 'https://127.0.0.1/hook', 'https://localhost/hook', 'https://10.0.0.5/hook', 'https://192.168.1.10/hook', 'https://169.254.169.254/latest/meta-data', 'https://[::1]/hook', 'https://user:pass@example.com/hook', 'https://no-such-host.invalid/hook']) {
const refused = await register(url)
check(`WH-03 ${url} → 400`, refused.status === 400 && refused.json.error.code === 'invalid_request' && typeof refused.json.error.hint === 'string', refused)
}
const stored = await db('vk_agents').where({ id: agent.id }).first('webhook_url', 'webhook_secret_enc')
check('被拒绝的地址没有落库', stored.webhook_url === null && stored.webhook_secret_enc === null, stored)
// 登记一个公网 https 地址后立刻取消,期间不产生任何事件,所以不会真的向它发请求。
// 用 IP 字面量:开发机若开着 fake-ip 代理,域名会被解析到 198.18.0.0/15 保留段而被(正确地)拒绝。
const ok = await register('https://1.1.1.1/voko-hook')
const row = await db('vk_agents').where({ id: agent.id }).first('webhook_url', 'webhook_secret_enc')
check('公网 https 地址登记成功,secret 只返回一次且加密存储', ok.status === 200 && /^[0-9a-f]{64}$/.test(ok.json.data.webhook_secret) && row.webhook_url === 'https://1.1.1.1/voko-hook' && !row.webhook_secret_enc.includes(ok.json.data.webhook_secret), ok)
const me = await call('GET', '/v1/agents/me', { token: agent.key })
check('之后的接口不再返回 secret', !JSON.stringify(me.json).includes(ok.json.data.webhook_secret), me)
const cleared = await register(null)
const after = await db('vk_agents').where({ id: agent.id }).first('webhook_url', 'webhook_secret_enc')
check('取消登记', cleared.status === 200 && after.webhook_url === null && after.webhook_secret_enc === null, cleared)
})