fix: BUG-1 CSRF, TASK-8 H2/H3/H4, BUG-20/37/40/41 — 7 bugs fixed

BUG-1 (P0): CSRF tokens now HMAC-derived from session token instead of
random — survives backend restarts, eliminates cookie/header race conditions.
Frontend retries 403s as belt-and-suspenders.

TASK-8 H2: federation.peer-joined verifies ed25519 signature on join messages.
TASK-8 H3: federation.peer-address-changed requires signed proof from known peer.
TASK-8 H4: Rust backend default bind 0.0.0.0 → 127.0.0.1 (nginx proxies all).

BUG-20: ElectrumX index estimate string fixed from ~55GB to ~130GB.
BUG-37: App card Start/Stop buttons split into loading vs interactive states
        to prevent WebSocket state flicker during container scans.
BUG-40: Uninstall modal uses Teleport to body with z-[3000] for full overlay.
BUG-41: Uninstalling overlay on card + optimistic store removal.

Updated MASTER_PLAN.md and BETA-PROGRESS.md to reflect all completed work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-18 22:05:21 +00:00
co-authored by Claude Opus 4.6
parent 00bfd62393
commit 41ff1021ad
12 changed files with 404 additions and 271 deletions
+32 -20
View File
@@ -249,17 +249,25 @@
</div>
</div>
<div class="flex items-center gap-2">
<!-- Incoming Transactions Badge -->
<!-- Transactions button switches to incoming state when pending txs exist -->
<button
v-if="incomingTxCount > 0"
@click="showIncomingTxPanel = !showIncomingTxPanel"
class="incoming-tx-badge shrink-0"
@click="incomingTxCount > 0 ? showIncomingTxPanel = !showIncomingTxPanel : showTransactionsModal = true"
:class="incomingTxCount > 0 ? 'incoming-tx-badge' : 'text-white/50 hover:text-white/80 text-xs px-2 py-1 rounded-lg bg-white/5 hover:bg-white/10 transition-colors'"
class="shrink-0"
>
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
</svg>
<span>Incoming {{ incomingTxCount }}</span>
<span class="incoming-tx-ping"></span>
<template v-if="incomingTxCount > 0">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
</svg>
<span>Incoming {{ incomingTxCount }}</span>
<span class="incoming-tx-ping"></span>
</template>
<template v-else>
<svg class="w-3.5 h-3.5 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" />
</svg>
<span>Transactions</span>
</template>
</button>
<RouterLink to="/dashboard/web5" :aria-label="t('home.goToWeb5')" class="text-white/60 hover:text-white transition-colors">
<svg class="w-5 h-5" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -345,19 +353,19 @@
<span class="text-purple-400 text-sm font-medium">{{ walletEcash.toLocaleString() }} sats</span>
</div>
</div>
<div class="home-card-buttons grid grid-cols-4 gap-2 mt-auto pt-4 shrink-0">
<div class="home-card-buttons grid gap-2 mt-auto pt-4 shrink-0" :class="isDev ? 'grid-cols-4' : 'grid-cols-3'">
<button @click="showSendModal = true" class="home-card-btn px-3 py-2 glass-button rounded-lg text-sm font-medium text-center transition-colors">
{{ t('common.send') }}
</button>
<button @click="showReceiveModal = true" class="home-card-btn px-3 py-2 glass-button rounded-lg text-sm font-medium text-center transition-colors">
{{ t('web5.receiveBitcoin') }}
</button>
<button @click="devFaucet" class="home-card-btn px-3 py-2 glass-button rounded-lg text-sm font-medium text-center transition-colors text-green-400">
<button @click="showTransactionsModal = true" class="home-card-btn px-3 py-2 glass-button rounded-lg text-sm font-medium text-center transition-colors">
Transactions
</button>
<button v-if="isDev" @click="devFaucet" class="home-card-btn px-3 py-2 glass-button rounded-lg text-sm font-medium text-center transition-colors text-green-400">
Faucet
</button>
<RouterLink to="/dashboard/web5" class="home-card-btn px-3 py-2 glass-button rounded-lg text-sm font-medium text-center transition-colors">
Web5
</RouterLink>
</div>
</div>
</div>
@@ -397,7 +405,7 @@
<div class="p-4 bg-white/5 rounded-lg">
<div class="flex items-center justify-between mb-2">
<p class="text-xs text-white/60">{{ t('home.cpu') }}</p>
<p class="text-sm font-medium" :class="gaugeTextColor(systemStats.cpuPercent)">{{ systemStats.cpuPercent.toFixed(0) }}%</p>
<p class="text-sm font-medium" :class="gaugeTextColor(systemStats.cpuPercent)">{{ (systemStats.cpuPercent ?? 0).toFixed(0) }}%</p>
</div>
<div class="w-full h-2 bg-white/10 rounded-full overflow-hidden">
<div class="h-full rounded-full transition-all duration-500" :class="gaugeBarColor(systemStats.cpuPercent)" :style="{ width: systemStats.cpuPercent + '%' }"></div>
@@ -477,9 +485,10 @@
</div>
</div>
<!-- Send/Receive Bitcoin Modals -->
<!-- Wallet Modals -->
<SendBitcoinModal :show="showSendModal" @close="showSendModal = false" @sent="loadWeb5Status()" />
<ReceiveBitcoinModal :show="showReceiveModal" @close="showReceiveModal = false" @received="loadWeb5Status()" />
<TransactionsModal :show="showTransactionsModal" :transactions="walletTransactions" @close="showTransactionsModal = false" />
</template>
<script setup lang="ts">
@@ -488,6 +497,7 @@ import { RouterLink, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import SendBitcoinModal from '@/components/SendBitcoinModal.vue'
import ReceiveBitcoinModal from '@/components/ReceiveBitcoinModal.vue'
import TransactionsModal from '@/components/TransactionsModal.vue'
import { useAppStore } from '../stores/app'
const { t } = useI18n()
@@ -503,6 +513,7 @@ import { rpcClient } from '@/api/rpc-client'
const router = useRouter()
const uiMode = useUIModeStore()
const isDev = import.meta.env.DEV
const homeTab = ref<'dashboard' | 'setup'>('dashboard')
const topGoals = GOALS.slice(0, 3)
@@ -718,18 +729,19 @@ onMounted(async () => {
loadWeb5Status()
})
// Send/Receive modals
// Wallet modals
const showSendModal = ref(false)
const showReceiveModal = ref(false)
const showTransactionsModal = ref(false)
// Dev faucet — adds mock funds to all wallet types
// Dev faucet — adds mock funds to all wallet types (dev mode only)
async function devFaucet() {
try {
const res = await rpcClient.call<{ message: string }>({ method: 'dev.faucet', params: { amount_sats: 1_000_000 } })
console.log('[Faucet]', res.message)
if (import.meta.env.DEV) console.log('[Faucet]', res.message)
await loadWeb5Status()
} catch (err) {
console.error('[Faucet] Error:', err)
if (import.meta.env.DEV) console.error('[Faucet] Error:', err)
}
}