fix(network): защитить ключи WireGuard и режим просмотра операций

This commit is contained in:
Denozordec committed 2026-10-13 20:12:33 +07:00
1 parent c8f9c83f45
commit a404da768d
23 files changed
+1648 -36

No files matched your search

+21 -12
View File
@@ -184,6 +184,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf
}) {
const { mode, backendUrl } = useDataSource()
const isLive = mode === "live"
const canWrite = !isLive || can("mm:network:write")
const [liveIfaces, setLiveIfaces] = useState<WgIfaceWithServer[]>([])
const [liveServers, setLiveServers] = useState<Server[]>([])
@@ -446,6 +447,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf
setCreateError(null)
setBusy(true)
try {
if (!can("mm:network:write")) throw new Error("Нет прав на изменение сети или экспорт ключей")
const ep = parseEndpoint(form.peerEndpoint)
await createWireGuardInterface(backendUrl, {
serverId: form.serverId,
@@ -495,6 +497,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf
setImportError(null)
setBusy(true)
try {
if (!can("mm:network:write")) throw new Error("Нет прав на изменение сети или экспорт ключей")
const res = await importWireGuard(backendUrl, {
serverId: args.serverId,
content: args.content,
@@ -538,6 +541,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf
setTogglePending(context)
setToggleError(null)
try {
if (!can("mm:network:write")) throw new Error("Нет прав на изменение сети или экспорт ключей")
const result = await patchWireGuardInterface(backendUrl, iface.serverId, iface.rosId, {
disabled: iface.enabled,
})
@@ -564,6 +568,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf
setDeleteBusy(true)
setDeleteError(null)
try {
if (!can("mm:network:write")) throw new Error("Нет прав на изменение сети или экспорт ключей")
if (pendingDelete.kind === "iface") {
if (!pendingDelete.iface.rosId) {
if (active.current) toast.info("Доступно только в live-режиме")
@@ -599,6 +604,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf
setPeerError(null)
setBusy(true)
try {
if (!can("mm:network:write")) throw new Error("Нет прав на изменение сети или экспорт ключей")
const ep = parseEndpoint(form.endpoint)
await createWireGuardPeer(backendUrl, {
serverId: peerIface.serverId,
@@ -630,6 +636,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf
exportPending.current = epoch
setExportBusy(true)
try {
if (!can("mm:network:write")) throw new Error("Нет прав на изменение сети или экспорт ключей")
const peer = format === "peer-conf" ? selectWgExportPeer(exportIface.peers, exportPeerId) : undefined
if (format === "peer-conf" && !peer) throw new Error("Выбранный пир не найден. Обновите список и выберите пира заново.")
const res = await exportWireGuard(backendUrl, {
@@ -671,7 +678,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf
}
const emptyCreateAction = (
<Button size="sm" disabled={!!urlError || !catalogKnown || loadError} onClick={() => { setCreateError(null); setCreateOpen(true) }}>
<Button size="sm" disabled={!canWrite || !!urlError || !catalogKnown || loadError} onClick={() => { setCreateError(null); setCreateOpen(true) }}>
<PlusIcon className="size-4" />
Новый интерфейс
</Button>
@@ -687,6 +694,7 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf
showAll
allCount={completeScope ? displayIfaces.length : undefined}
loading={isLive && loading && displayServers.length === 0}
banner={!canWrite ? <p role="status" className="border-b px-4 py-2 text-sm text-muted-foreground">Режим просмотра. Изменение конфигурации и экспорт ключей недоступны.</p> : undefined}
header={
<PageHeader
crumbs={[{ label: "Управление" }, { label: "WireGuard" }]}
@@ -719,11 +727,11 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf
</Button>
</>
)}
<Button size="sm" variant="outline" disabled={!!urlError || !catalogKnown || loadError} onClick={() => { setImportError(null); setImportOpen(true) }}>
<Button size="sm" variant="outline" disabled={!canWrite || !!urlError || !catalogKnown || loadError} onClick={() => { setImportError(null); setImportOpen(true) }}>
<UploadIcon className="size-4" />
Импорт
</Button>
<Button size="sm" disabled={!!urlError || !catalogKnown || loadError} onClick={() => { setCreateError(null); setCreateOpen(true) }}>
<Button size="sm" disabled={!canWrite || !!urlError || !catalogKnown || loadError} onClick={() => { setCreateError(null); setCreateOpen(true) }}>
<PlusIcon className="size-4" />
Новый интерфейс
</Button>
@@ -843,15 +851,16 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf
/>
{catalogKnown && !urlError && (completeScope || filteredIfaces.length > 0) ? <WireguardDataGrid
interfaces={filteredIfaces}
canWrite={canWrite}
compactServer={compactServer}
emptyAction={emptyCreateAction}
onExport={(iface) => openExport(iface, "rsc")}
onAddPeer={selectedSourceError ? undefined : iface => { setPeerError(null); setPeerIface(iface) }}
onToggle={selectedSourceError ? undefined : handleToggle}
onAddPeer={!canWrite || selectedSourceError ? undefined : iface => { setPeerError(null); setPeerIface(iface) }}
onToggle={!canWrite || selectedSourceError ? undefined : handleToggle}
toggleBusy={togglePending !== null}
onDelete={selectedSourceError ? undefined : (iface) => setPendingDelete({ kind: "iface", iface })}
onDeletePeer={selectedSourceError ? undefined : (iface, peerId) => setPendingDelete({ kind: "peer", iface, peerId })}
onExportPeer={(iface, peerId) => openExport(iface, "peer", peerId)}
onDelete={!canWrite || selectedSourceError ? undefined : (iface) => setPendingDelete({ kind: "iface", iface })}
onDeletePeer={!canWrite || selectedSourceError ? undefined : (iface, peerId) => setPendingDelete({ kind: "peer", iface, peerId })}
onExportPeer={canWrite ? (iface, peerId) => openExport(iface, "peer", peerId) : undefined}
/> : <p role="status" className="px-5 py-8 text-sm text-muted-foreground">Каталог интерфейсов пока неизвестен</p>}
</DataPageCard>
</TabsContent>
@@ -869,22 +878,22 @@ function WireGuardPageContent({ selectedServerId, selectedInterfaceId, allInterf
compactServer={compactServer}
emptyAction={
scopedIfaces.length === 1 ? (
<Button size="sm" disabled={selectedSourceError} onClick={() => { setPeerError(null); setPeerIface(scopedIfaces[0]) }}>
<Button size="sm" disabled={!canWrite || selectedSourceError} onClick={() => { setPeerError(null); setPeerIface(scopedIfaces[0]) }}>
<PlusIcon className="size-4" />
Добавить пира
</Button>
) : emptyCreateAction
}
onDeletePeer={selectedSourceError ? undefined : (row) => {
onDeletePeer={!canWrite || selectedSourceError ? undefined : (row) => {
const iface = scopedIfaces.find((i) => i.id === row.ifaceId && i.serverId === row.serverId)
if (!iface) return
setPendingDelete({ kind: "peer", iface, peerId: row.id })
}}
onExportPeer={(row) => {
onExportPeer={canWrite ? (row) => {
const iface = scopedIfaces.find((i) => i.id === row.ifaceId && i.serverId === row.serverId)
if (!iface) return
openExport(iface, "peer", row.id)
}}
} : undefined}
/> : <p role="status" className="px-5 py-8 text-sm text-muted-foreground">Каталог пиров пока неизвестен</p>}
</DataPageCard>
</TabsContent>