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:
@@ -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' }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user