perf(web): route-level code-splitting; harden channel config rendering
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwxdSt4reTm7Dj1oxRvpP3
This commit is contained in:
@@ -48,6 +48,16 @@ test("отрисовывает список каналов без секрета
|
||||
expect(screen.queryByDisplayValue(/123456/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("formatConfig — вайтлист по типу канала не печатает незнакомые поля config", async () => {
|
||||
vi.spyOn(api, "listChannels").mockResolvedValue([
|
||||
{ id: "c5", type: "webhook", config: { url: "https://x", token: "LEAK" }, enabled: true },
|
||||
])
|
||||
renderPage()
|
||||
|
||||
expect(await screen.findByText(/url: https:\/\/x/)).toBeInTheDocument()
|
||||
expect(document.body.textContent).not.toMatch(/LEAK/)
|
||||
})
|
||||
|
||||
test("создание telegram-канала собирает config.chat_id + secret=bot_token", async () => {
|
||||
const createSpy = vi.spyOn(api, "createChannel").mockResolvedValue({
|
||||
id: "c3", type: "telegram", config: { chat_id: "999" }, enabled: true,
|
||||
|
||||
@@ -69,13 +69,15 @@ type ChannelForm = z.infer<typeof channelFormSchema>
|
||||
|
||||
const EMPTY_FORM: ChannelForm = { type: "telegram", chatId: "", botToken: "", url: "" }
|
||||
|
||||
// Channel.config is a generic `object` end-to-end (T5/T7) — it never carries
|
||||
// the secret (bot_token/signing key), only the public half (chat_id/url), so
|
||||
// rendering every key is always safe to show in the list.
|
||||
function formatConfig(config: object): string {
|
||||
const entries = Object.entries(config as Record<string, unknown>)
|
||||
if (entries.length === 0) return "—"
|
||||
return entries.map(([k, v]) => `${k}=${String(v)}`).join(" · ")
|
||||
// Печатаем ТОЛЬКО известные несекретные поля по типу канала — чтобы новый
|
||||
// тип канала с чувствительным полем в config не «протёк» в DOM автоматически
|
||||
// (Object.entries по всему config печатал бы любое поле, включая случайно
|
||||
// сохранённый секрет).
|
||||
function formatConfig(type: string, config: object): string {
|
||||
const c = config as Record<string, unknown>
|
||||
if (type === "telegram") return c.chat_id ? `chat_id: ${String(c.chat_id)}` : ""
|
||||
if (type === "webhook") return c.url ? `url: ${String(c.url)}` : ""
|
||||
return ""
|
||||
}
|
||||
|
||||
function ChannelForm({ onCreated }: { onCreated: () => void }) {
|
||||
@@ -294,7 +296,7 @@ export function ChannelsPage() {
|
||||
<TableRow key={c.id}>
|
||||
<TableCell className="font-dns">{c.type}</TableCell>
|
||||
<TableCell className="font-dns text-xs text-muted-foreground">
|
||||
{formatConfig(c.config)}
|
||||
{formatConfig(c.type, c.config)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span
|
||||
|
||||
Reference in New Issue
Block a user