perf,feat(events),hardening: second review pass

Fleet overview N+1:
- InstanceResourceService gains a 30s TTL cache with single-flight
  coalescing; the overview no longer fires six live upstream requests per
  device on every render (plus the re-login storm), while the per-device
  detail route probes live via force:true

Event journal becomes live:
- job terminal transitions (manual executions and the interrupted sweep)
  now append to the journal, so /api/v1/events SSE feeds the frontend's
  invalidation controller that was built but never received events
- journal pruning moves off the append hot path (was an unindexable
  full-table json_extract scan per insert) onto the retention timer

Console auth hardening:
- scrypt upgraded from N=16384 to N=2^16 (OWASP interactive guidance);
  a new password_kdf column records the derivation per row and legacy
  hashes rehash transparently on the next successful login without
  invalidating sessions (migration 18)

Legacy stack:
- instance URL validation blocks IPv4-compatible IPv6 after WHATWG
  canonicalization (::a9fe:a9fe metadata, ::7f00:1 loopback slipped past)
- status polls cool down auto-login for 60s after a failed attempt so a
  stale saved password cannot hammer the device into an account lockout

Build hygiene:
- web bundle splits app (410kB) from vendor (212kB) so framework code
  stays cacheable across releases; stale root package-lock.json removed
  (pnpm is the only lockfile)
This commit is contained in:
chick
2026-09-07 02:20:41 +08:00
parent 29a0eee854
commit c1be714ba4
14 changed files with 268 additions and 3859 deletions
+9
View File
@@ -190,6 +190,7 @@ export function createSimAdminClient(instance, { fetchImpl = fetch, timeoutMs =
}
let ephemeralSecret = ''
let lastFailedAutoLoginAt = 0
async function ensureAuthenticated({ credential } = {}) {
const status = await fetchJson('/api/auth/status')
const auth = apiData(status.data) || {}
@@ -203,12 +204,20 @@ export function createSimAdminClient(instance, { fetchImpl = fetch, timeoutMs =
return { configured: auth.configured ?? null, authenticated: false, loginAttempted: false, statusCode: status.status, reason: 'password_required' }
}
if (credential !== undefined) ephemeralSecret = ''
// Status polls auto-login with the saved password; without a cooldown a
// stale password turns every refresh into a fresh login attempt, which
// some devices punish with account lockouts.
if (credential === undefined && !ephemeralSecret && Date.now() - lastFailedAutoLoginAt < 60_000) {
return { configured: auth.configured ?? null, authenticated: false, loginAttempted: false, statusCode: status.status, reason: 'login_cooldown' }
}
const login = await request('/api/auth/login', {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({ password }),
})
if (credential !== undefined && login.ok) ephemeralSecret = password
if (login.ok) lastFailedAutoLoginAt = 0
else if (credential === undefined) lastFailedAutoLoginAt = Date.now()
return { configured: auth.configured ?? null, authenticated: login.ok, loginAttempted: true, statusCode: login.status, reason: login.ok ? null : 'login_failed' }
}