mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-05-20 07:45:01 +00:00
Update appimage
This commit is contained in:
Binary file not shown.
@@ -1 +1 @@
|
||||
78dee82f335a54bd82a22f778de7e5391272b91c28bc7cfd1bffcd9091c62395 /tmp/ProxMenux-1.2.1.1-beta.AppImage
|
||||
a1a3e1d1f028a837efc34ef3507a03a8a899e244824381ef3ae3b3e60a5a8396 /tmp/ProxMenux-1.2.1.1-beta.AppImage
|
||||
|
||||
@@ -65,6 +65,21 @@ export default function Home() {
|
||||
|
||||
const authenticated = data.auth_enabled ? data.authenticated : true
|
||||
|
||||
// Clear the 401 cascade-prevention flag when we successfully end
|
||||
// up in the authenticated state. The flag is meant to dedupe a
|
||||
// burst of 401s during a single page load; once we've confirmed
|
||||
// the user is in, a future 401 (token rotation, restart, etc.)
|
||||
// should be allowed to reload again. Without this, a stale flag
|
||||
// can prevent the post-2FA dashboard from recovering from any
|
||||
// transient 401 and leaves the UI blocked.
|
||||
if (authenticated) {
|
||||
try {
|
||||
sessionStorage.removeItem("proxmenux-auth-401-handled")
|
||||
} catch {
|
||||
// private browsing — best-effort
|
||||
}
|
||||
}
|
||||
|
||||
setAuthStatus({
|
||||
loading: false,
|
||||
authEnabled: data.auth_enabled,
|
||||
|
||||
@@ -26,6 +26,21 @@ export function Login({ onLogin }: LoginProps) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
// The Login screen is, by construction, the recovery path from any
|
||||
// 401 cascade (the api-config wrapper redirects here when an
|
||||
// expired/invalid JWT is detected). Clear the cascade-prevention
|
||||
// flag on mount so a successful login can subsequently fire a fresh
|
||||
// reload if a NEW 401 ever occurs. Without this clear, any 401 set
|
||||
// earlier in the session sticks around forever and the next 401
|
||||
// (e.g. mid-2FA, or right after a successful login if the token was
|
||||
// briefly stale) is silently swallowed by the de-dup — the user
|
||||
// sees a blank/stuck dashboard.
|
||||
try {
|
||||
sessionStorage.removeItem("proxmenux-auth-401-handled")
|
||||
} catch {
|
||||
// private browsing — best-effort
|
||||
}
|
||||
|
||||
const savedUsername = localStorage.getItem("proxmenux-saved-username")
|
||||
const savedPassword = localStorage.getItem("proxmenux-saved-password")
|
||||
|
||||
|
||||
@@ -95,12 +95,27 @@ export async function fetchApi<T>(endpoint: string, options?: RequestInit): Prom
|
||||
// (rotated per-install). Drop the stale token and force a single
|
||||
// reload so the page-level auth gate (`app/page.tsx`) can render
|
||||
// <Login> instead of cascading 401s from every authenticated
|
||||
// component on mount. The sessionStorage flag is essential: a
|
||||
// page like Hardware/Storage fires 10-20 SWR fetches in parallel,
|
||||
// and without dedup each of them would race to reload the tab —
|
||||
// observed in the wild as ~180 "Invalid token" log lines per
|
||||
// second from a single browser running an upgraded Monitor.
|
||||
// component on mount.
|
||||
//
|
||||
// Only react when we actually had a token to invalidate. A 401
|
||||
// without any token in localStorage means the caller is the
|
||||
// Login screen itself, or a leftover fetch from a recently
|
||||
// unmounted Dashboard — reloading there does nothing but waste
|
||||
// the user's keystrokes and can leave the cascade flag set
|
||||
// forever, swallowing the very 401 that we'd want to recover
|
||||
// from after a successful re-login. The fix: bail out early
|
||||
// if we have no token to invalidate.
|
||||
if (typeof window !== "undefined") {
|
||||
let hadToken = false
|
||||
try {
|
||||
hadToken = !!localStorage.getItem("proxmenux-auth-token")
|
||||
} catch {
|
||||
// private browsing — assume yes so we attempt recovery.
|
||||
hadToken = true
|
||||
}
|
||||
if (!hadToken) {
|
||||
throw new Error(`Unauthorized: ${endpoint}`)
|
||||
}
|
||||
try {
|
||||
localStorage.removeItem("proxmenux-auth-token")
|
||||
} catch {
|
||||
|
||||
@@ -191,24 +191,6 @@ def _bad_request(msg: str):
|
||||
return jsonify({'error': msg}), 400
|
||||
|
||||
|
||||
def _is_loopback_addr(value: str) -> bool:
|
||||
"""Return True for IPv4, IPv6 and IPv4-mapped loopback addresses.
|
||||
|
||||
When Flask is bound to ``::`` for dual-stack support, an HTTP request
|
||||
sent to ``127.0.0.1`` can be reported as ``::ffff:127.0.0.1``. Treat it
|
||||
as local so the PVE webhook keeps the intended localhost trust path.
|
||||
"""
|
||||
try:
|
||||
import ipaddress
|
||||
addr = ipaddress.ip_address(value)
|
||||
if addr.is_loopback:
|
||||
return True
|
||||
ipv4_mapped = getattr(addr, 'ipv4_mapped', None)
|
||||
return bool(ipv4_mapped and ipv4_mapped.is_loopback)
|
||||
except ValueError:
|
||||
return value == 'localhost'
|
||||
|
||||
|
||||
def _validate_event_type(value: str) -> bool:
|
||||
return isinstance(value, str) and bool(_EVENT_TYPE_RE.match(value))
|
||||
|
||||
@@ -1243,7 +1225,7 @@ def proxmox_webhook():
|
||||
_reject = lambda code, error, status: (jsonify({'accepted': False, 'error': error}), status)
|
||||
|
||||
client_ip = request.remote_addr or ''
|
||||
is_localhost = _is_loopback_addr(client_ip)
|
||||
is_localhost = client_ip in ('127.0.0.1', '::1')
|
||||
|
||||
# CSRF defence-in-depth: reject `application/x-www-form-urlencoded`
|
||||
# bodies. PVE always sends `application/json`; form-encoded bodies
|
||||
|
||||
Reference in New Issue
Block a user