account: fix Wolf card unlock flow
This commit is contained in:
parent
b4373b9325
commit
1a231dd474
@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from flask import jsonify, request
|
from flask import jsonify, request
|
||||||
@ -8,10 +9,45 @@ from .. import ariadne_client
|
|||||||
from ..keycloak import require_auth, require_account_access
|
from ..keycloak import require_auth, require_account_access
|
||||||
|
|
||||||
|
|
||||||
|
def _valid_ip(value: str) -> str:
|
||||||
|
return str(ipaddress.ip_address(value.strip()))
|
||||||
|
|
||||||
|
|
||||||
|
def _public_ip_from_chain(values: list[str]) -> str:
|
||||||
|
"""Return the nearest public client IP from a trusted proxy chain."""
|
||||||
|
|
||||||
|
candidates: list[str] = []
|
||||||
|
for value in values:
|
||||||
|
for item in value.split(","):
|
||||||
|
item = item.strip()
|
||||||
|
if item:
|
||||||
|
candidates.append(item)
|
||||||
|
|
||||||
|
for candidate in reversed(candidates):
|
||||||
|
try:
|
||||||
|
ip = ipaddress.ip_address(candidate)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
if ip.is_global:
|
||||||
|
return str(ip)
|
||||||
|
|
||||||
|
for candidate in reversed(candidates):
|
||||||
|
try:
|
||||||
|
return _valid_ip(candidate)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _client_ip() -> str:
|
def _client_ip() -> str:
|
||||||
# ProxyFix normalizes this from the trusted Traefik hop. Reading raw
|
# Walk right-to-left through proxy-added headers. This ignores spoofed
|
||||||
# forwarding headers here would let a browser request choose an unlock IP.
|
# left-most values while still finding the public IP before cluster hops.
|
||||||
return request.remote_addr or ""
|
chain = [
|
||||||
|
request.headers.get("X-Forwarded-For", ""),
|
||||||
|
request.headers.get("X-Real-IP", ""),
|
||||||
|
request.remote_addr or "",
|
||||||
|
]
|
||||||
|
return _public_ip_from_chain(chain)
|
||||||
|
|
||||||
|
|
||||||
def _json_payload() -> dict[str, Any]:
|
def _json_payload() -> dict[str, Any]:
|
||||||
|
|||||||
@ -38,7 +38,7 @@ def test_wolf_status_proxies_source_ip(monkeypatch) -> None:
|
|||||||
resp = client.get(
|
resp = client.get(
|
||||||
"/api/account/wolf",
|
"/api/account/wolf",
|
||||||
environ_base={"REMOTE_ADDR": "1.2.3.4"},
|
environ_base={"REMOTE_ADDR": "1.2.3.4"},
|
||||||
headers={"X-Forwarded-For": "9.9.9.9, 10.0.0.1"},
|
headers={"X-Forwarded-For": "9.9.9.9, 181.1.87.186, 10.0.0.1"},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
@ -52,13 +52,27 @@ def test_wolf_unlock_uses_current_source_ip(monkeypatch) -> None:
|
|||||||
"/api/account/wolf/firewall/unlock",
|
"/api/account/wolf/firewall/unlock",
|
||||||
json={"ttl_seconds": 120},
|
json={"ttl_seconds": 120},
|
||||||
environ_base={"REMOTE_ADDR": "5.6.7.8"},
|
environ_base={"REMOTE_ADDR": "5.6.7.8"},
|
||||||
headers={"X-Real-IP": "9.9.9.9"},
|
headers={"X-Real-IP": "10.42.28.0"},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ttl_seconds": 120, "ip": "5.6.7.8"}, None)]
|
assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ttl_seconds": 120, "ip": "5.6.7.8"}, None)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_wolf_source_ip_prefers_nearest_public_proxy_value(monkeypatch) -> None:
|
||||||
|
client, ariadne = make_client(monkeypatch)
|
||||||
|
|
||||||
|
resp = client.post(
|
||||||
|
"/api/account/wolf/firewall/unlock",
|
||||||
|
json={},
|
||||||
|
environ_base={"REMOTE_ADDR": "10.42.28.0"},
|
||||||
|
headers={"X-Forwarded-For": "9.9.9.9, 181.1.87.186, 10.42.28.0"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert ariadne.calls == [("POST", "/api/game-stream/firewall/unlock", {"ip": "181.1.87.186"}, None)]
|
||||||
|
|
||||||
|
|
||||||
def test_wolf_pairing_and_admin_actions_proxy(monkeypatch) -> None:
|
def test_wolf_pairing_and_admin_actions_proxy(monkeypatch) -> None:
|
||||||
client, ariadne = make_client(monkeypatch)
|
client, ariadne = make_client(monkeypatch)
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,7 @@ export async function initAuth() {
|
|||||||
pkceMethod: "S256",
|
pkceMethod: "S256",
|
||||||
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
|
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
|
||||||
checkLoginIframe: true,
|
checkLoginIframe: true,
|
||||||
scope: "openid profile email",
|
scope: "openid profile email groups",
|
||||||
});
|
});
|
||||||
|
|
||||||
auth.authenticated = authenticated;
|
auth.authenticated = authenticated;
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="kv">
|
<div class="kv">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="k mono">Moonlight</span>
|
<span class="k mono">Moonlight host</span>
|
||||||
<span class="v mono">{{ wolf.moonlightHost }}</span>
|
<span class="v mono">{{ wolf.moonlightHost }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -44,8 +44,9 @@
|
|||||||
<button class="primary" type="button" :disabled="wolf.unlocking" @click="$emit('unlock')">
|
<button class="primary" type="button" :disabled="wolf.unlocking" @click="$emit('unlock')">
|
||||||
{{ wolf.unlocking ? "Unlocking..." : "Unlock Moonlight" }}
|
{{ wolf.unlocking ? "Unlocking..." : "Unlock Moonlight" }}
|
||||||
</button>
|
</button>
|
||||||
<button class="copy mono" type="button" :disabled="wolf.loading" @click="$emit('refresh')">refresh</button>
|
<button class="copy mono" type="button" :disabled="wolf.loading" @click="$emit('refresh')">Refresh</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="hint mono">Use the host above in Moonlight after unlocking your current IP.</div>
|
||||||
|
|
||||||
<div v-if="wolf.pendingPairRequests.length" class="secret-box">
|
<div v-if="wolf.pendingPairRequests.length" class="secret-box">
|
||||||
<div class="secret-head">
|
<div class="secret-head">
|
||||||
@ -113,3 +114,5 @@ defineProps({
|
|||||||
|
|
||||||
defineEmits(["refresh", "unlock", "pair", "game-mode", "admin-unlock"]);
|
defineEmits(["refresh", "unlock", "pair", "game-mode", "admin-unlock"]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped src="../styles/account.css"></style>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user