Add browser-friendly remote token bootstrap

This commit is contained in:
Dymas
2026-05-17 14:59:48 +02:00
parent c91db90c1b
commit 47480efa30
6 changed files with 130 additions and 4 deletions
+63 -1
View File
@@ -8,10 +8,11 @@ import os
import shutil
import traceback
from dataclasses import dataclass
from http.cookies import SimpleCookie
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler
from pathlib import Path
from urllib.parse import parse_qs, unquote, urlparse
from urllib.parse import parse_qs, unquote, urlencode, urlparse, urlunparse
from app_support import (
ANI_CLI,
@@ -68,6 +69,7 @@ def dependency_status():
class Handler(BaseHTTPRequestHandler):
server_version = "AniCliWeb/1.0"
remote_token_cookie_name = "ani_cli_web_token"
def _context(self):
context = getattr(self, "handler_context", None) or getattr(type(self), "handler_context", None)
@@ -116,6 +118,8 @@ class Handler(BaseHTTPRequestHandler):
self.headers.get("X-AniCli-Web-Token")
or self.headers.get("x-ani-cli-web-token")
or Handler._bearer_token(self)
or Handler._cookie_token(self)
or Handler._query_token(self)
or ""
)
@@ -125,6 +129,62 @@ class Handler(BaseHTTPRequestHandler):
return ""
return header[len("Bearer ") :].strip()
def _cookie_token(self):
raw = str(self.headers.get("Cookie") or self.headers.get("cookie") or "").strip()
if not raw:
return ""
try:
cookie = SimpleCookie()
cookie.load(raw)
except Exception:
return ""
morsel = cookie.get(Handler.remote_token_cookie_name)
if not morsel:
return ""
return morsel.value.strip()
def _query_token(self):
parsed = urlparse(self.path)
params = parse_qs(parsed.query, keep_blank_values=True)
for key in ("token", "remote_token"):
values = params.get(key) or []
for value in values:
text = str(value).strip()
if text:
return text
return ""
def _remote_cookie_bootstrap_response(self, parsed):
client_host = Handler._effective_client_host(self)
if client_address_is_local(client_host):
return False
if not remote_access_allowed() or not remote_access_token_configured():
return False
if Handler._cookie_token(self):
return False
token = Handler._query_token(self)
if not token or not remote_access_token_matches(token):
return False
params = parse_qs(parsed.query, keep_blank_values=True)
params.pop("token", None)
params.pop("remote_token", None)
query = urlencode(params, doseq=True)
location = urlunparse(("", "", parsed.path or "/", "", query, ""))
cookie = (
f"{Handler.remote_token_cookie_name}={token}; "
"Path=/; HttpOnly; SameSite=Lax"
)
Handler.write_response_bytes(
self,
b"",
HTTPStatus.SEE_OTHER,
{
"Location": location,
"Set-Cookie": cookie,
},
)
return True
def ensure_client_access(self):
client_host = Handler._effective_client_host(self)
if client_address_is_local(client_host):
@@ -143,6 +203,8 @@ class Handler(BaseHTTPRequestHandler):
def do_GET(self):
parsed = urlparse(self.path)
try:
if Handler._remote_cookie_bootstrap_response(self, parsed):
return
self.ensure_client_access()
if parsed.path == "/":
self.html(Handler._context(self).index_html)