fix: 终审修复——SSE 鉴权 401/503 分类、touch.workflow 默认拒、/subscribe 非数组 422
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e13d86fe4f
commit
9a02094281
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, beforeAll, vi } from 'vitest';
|
||||
import { Hono } from 'hono';
|
||||
import { SseHub } from '../../src/modules/sse/hub';
|
||||
import { SseHub, isSubscribable } from '../../src/modules/sse/hub';
|
||||
import type { Connection, SseEvent } from '../../src/modules/sse/hub';
|
||||
import { sseRoutes } from '../../src/modules/sse/routes';
|
||||
import { createHttpTaskAccess } from '../../src/modules/sse/task-access';
|
||||
@@ -62,6 +62,24 @@ describe('SseHub 路由', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('I6:touch.workflow.* 默认拒——没有授权规则的主题不进可订阅集', async () => {
|
||||
// 形状白名单曾经收着 touch.workflow.,而 subscribe 的鉴权分支只覆盖 touch.task./stream.task.:
|
||||
// 形状一过就直接进订阅集,任何登录用户订 touch.workflow.<任意 id> 都能收别人流程的推送。
|
||||
// N3 给工作流补上可见性规则之后再放回来。
|
||||
expect(isSubscribable('touch.workflow.w1')).toBe(false);
|
||||
expect(isSubscribable('touch.task.t1')).toBe(true);
|
||||
expect(isSubscribable('stream.task.t1')).toBe(true);
|
||||
const hub = new SseHub();
|
||||
const a = conn('a', 'sub-a');
|
||||
hub.register(a.c);
|
||||
// 放行一切的 access 口:证明拒绝来自形状白名单本身,而不是碰巧被授权挡住了
|
||||
const res = await hub.subscribe('a', 'touch.workflow.w1', { allowed: async () => true });
|
||||
expect(res).toMatchObject({ ok: false, reason: 'bad-shape' });
|
||||
expect(a.c.topics.size).toBe(0);
|
||||
hub.deliver('touch.workflow.w1', '{}');
|
||||
expect(a.got).toEqual([]);
|
||||
});
|
||||
|
||||
it('D12:shutdown 逐条调 close 把手并摘除连接', () => {
|
||||
const hub = new SseHub();
|
||||
let closed = 0;
|
||||
@@ -119,6 +137,32 @@ describe('SSE HTTP 面', () => {
|
||||
expect(accessCalls).toBe(0); // 且一次授权往返都不该白跑
|
||||
});
|
||||
|
||||
it('I7:add/remove 不是数组 → 422(不是 500)', async () => {
|
||||
// `as string[]` 只是断言。字符串上没有 .find,此前直接 TypeError 冒到 500——
|
||||
// 请求写错却报成服务端故障,而 500 在网关这一层还会被客户端/重试层当成可重试。
|
||||
const hub = new SseHub();
|
||||
const a = conn('live-7', 'test-sub-1');
|
||||
hub.register(a.c);
|
||||
const app = new Hono().route('/api/stream', sseRoutes(hub, { allowed: async () => true }));
|
||||
const token = await makeToken({ roles: ['user'], aud: 'api-gateway' });
|
||||
const post = async (payload: unknown): Promise<Response> =>
|
||||
app.request('/api/stream/subscribe', {
|
||||
method: 'POST',
|
||||
headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
const badAdd = await post({ connectionId: 'live-7', add: 'touch.task.t1' });
|
||||
expect(badAdd.status).toBe(422);
|
||||
expect(await badAdd.json()).toMatchObject({ error: 'validation_failed' });
|
||||
const badRemove = await post({ connectionId: 'live-7', remove: { '0': 'touch.task.t1' } });
|
||||
expect(badRemove.status).toBe(422);
|
||||
expect(a.c.topics.size).toBe(0); // 422 零副作用
|
||||
// 缺省(undefined)仍然合法:只订不退、只退不订都是正常用法
|
||||
expect((await post({ connectionId: 'live-7', add: ['touch.task.t1'] })).status).toBe(200);
|
||||
expect(a.c.topics.has('touch.task.t1')).toBe(true);
|
||||
});
|
||||
|
||||
it('F2:连接归属校验——他人 connectionId 一律 403(add 与 remove 同守)', async () => {
|
||||
const hub = new SseHub();
|
||||
const app = buildApp(hub);
|
||||
|
||||
Reference in New Issue
Block a user