Add browser-friendly remote token bootstrap
This commit is contained in:
+63
-1
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user