mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-05-22 08:34:44 +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
|
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({
|
setAuthStatus({
|
||||||
loading: false,
|
loading: false,
|
||||||
authEnabled: data.auth_enabled,
|
authEnabled: data.auth_enabled,
|
||||||
|
|||||||
@@ -26,6 +26,21 @@ export function Login({ onLogin }: LoginProps) {
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
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 savedUsername = localStorage.getItem("proxmenux-saved-username")
|
||||||
const savedPassword = localStorage.getItem("proxmenux-saved-password")
|
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
|
// (rotated per-install). Drop the stale token and force a single
|
||||||
// reload so the page-level auth gate (`app/page.tsx`) can render
|
// reload so the page-level auth gate (`app/page.tsx`) can render
|
||||||
// <Login> instead of cascading 401s from every authenticated
|
// <Login> instead of cascading 401s from every authenticated
|
||||||
// component on mount. The sessionStorage flag is essential: a
|
// component on mount.
|
||||||
// page like Hardware/Storage fires 10-20 SWR fetches in parallel,
|
//
|
||||||
// and without dedup each of them would race to reload the tab —
|
// Only react when we actually had a token to invalidate. A 401
|
||||||
// observed in the wild as ~180 "Invalid token" log lines per
|
// without any token in localStorage means the caller is the
|
||||||
// second from a single browser running an upgraded Monitor.
|
// 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") {
|
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 {
|
try {
|
||||||
localStorage.removeItem("proxmenux-auth-token")
|
localStorage.removeItem("proxmenux-auth-token")
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -191,24 +191,6 @@ def _bad_request(msg: str):
|
|||||||
return jsonify({'error': msg}), 400
|
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:
|
def _validate_event_type(value: str) -> bool:
|
||||||
return isinstance(value, str) and bool(_EVENT_TYPE_RE.match(value))
|
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)
|
_reject = lambda code, error, status: (jsonify({'accepted': False, 'error': error}), status)
|
||||||
|
|
||||||
client_ip = request.remote_addr or ''
|
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`
|
# CSRF defence-in-depth: reject `application/x-www-form-urlencoded`
|
||||||
# bodies. PVE always sends `application/json`; form-encoded bodies
|
# bodies. PVE always sends `application/json`; form-encoded bodies
|
||||||
|
|||||||
Reference in New Issue
Block a user