Cleanup of origins handling. Added site_url and site_path such that these can be determined reliably, and we print it in the startbox.

This commit is contained in:
2025-12-06 03:39:05 +00:00
parent df5c176bcd
commit a1b73711e6
6 changed files with 157 additions and 89 deletions
+61 -21
View File
@@ -7,6 +7,8 @@ from urllib.parse import urlparse
import uvicorn
from paskia.util.hostutil import normalize_origin
DEFAULT_HOST = "localhost"
DEFAULT_SERVE_PORT = 4401
@@ -172,53 +174,91 @@ def main():
else:
host = port = uds = all_ifaces = None # type: ignore
# Collect origins and handle auth_host
origins = getattr(args, "origins", None) or []
# Collect and normalize origins, handle auth_host
origins = [normalize_origin(o) for o in (getattr(args, "origins", None) or [])]
if args.auth_host:
# Normalize auth_host with scheme
if "://" not in args.auth_host:
args.auth_host = f"https://{args.auth_host}"
validate_auth_host(args.auth_host, args.rp_id)
from paskia.util import hostutil as _hostutil # local import
_hostutil.reload_config()
# If origins are configured, ensure auth_host is included at top
if origins:
# Insert auth_host at the beginning (Passkey.__init__ will normalize/dedupe)
# Insert auth_host at the beginning (Passkey.__init__ will dedupe)
origins.insert(0, args.auth_host)
# Compute site_url and site_path for reset links
# Priority: auth_host > first origin with localhost > http://localhost:port
if args.auth_host:
site_url = args.auth_host.rstrip("/")
site_path = "/"
elif origins:
# Find localhost origin if rp_id is localhost, else use first origin
localhost_origin = (
next((o for o in origins if "://localhost" in o), None)
if args.rp_id == "localhost"
else None
)
site_url = (localhost_origin or origins[0]).rstrip("/")
site_path = "/auth/"
elif args.rp_id == "localhost" and port:
# Dev mode: use http with port
site_url = f"http://localhost:{port}"
site_path = "/auth/"
else:
site_url = f"https://{args.rp_id}"
site_path = "/auth/"
# Build runtime configuration
from paskia.config import PaskiaConfig
config = PaskiaConfig(
rp_id=args.rp_id,
rp_name=args.rp_name or None,
origins=origins or None,
auth_host=args.auth_host or None,
site_url=site_url,
site_path=site_path,
host=host,
port=port,
uds=uds,
)
# Export configuration via single JSON env variable for worker processes
# (PASKIA_DEVMODE is kept separate as it's externally defined)
# All keys are always present; None is used where no value is configured
import json
config = {
"rp_id": args.rp_id,
"rp_name": args.rp_name or None,
"origins": origins or None,
"auth_host": args.auth_host or None,
config_json = {
"rp_id": config.rp_id,
"rp_name": config.rp_name,
"origins": config.origins,
"auth_host": config.auth_host,
"site_url": config.site_url,
"site_path": config.site_path,
}
os.environ["PASKIA_CONFIG"] = json.dumps(config)
os.environ["PASKIA_CONFIG"] = json.dumps(config_json)
# One-time initialization + bootstrap before starting any server processes.
# Lifespan in worker processes will call globals.init with bootstrap disabled.
# Initialize globals (without bootstrap yet)
from paskia import globals as _globals # local import
asyncio.run(
_globals.init(
rp_id=config["rp_id"],
rp_name=config["rp_name"],
origins=config["origins"],
bootstrap=True,
rp_id=config.rp_id,
rp_name=config.rp_name,
origins=config.origins,
bootstrap=False,
)
)
# Print startup configuration
from paskia.util import startupbox
startupbox.print_startup_config(_globals.passkey.instance, args, host, port, uds)
startupbox.print_startup_config(config)
# Bootstrap after startup box is printed
from paskia.bootstrap import bootstrap_if_needed
asyncio.run(bootstrap_if_needed())
# Handle recover-admin command (no server start)
if args.command == "reset":
+21 -1
View File
@@ -1,15 +1,19 @@
import logging
import os
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, HTTPException, Request, Response
from fastapi.responses import RedirectResponse
from fastapi.responses import FileResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from paskia.fastapi import admin, api, auth_host, ws
from paskia.fastapi.session import AUTH_COOKIE
from paskia.util import frontend, hostutil, passphrase
# Path to examples/index.html when running from source tree
_EXAMPLES_DIR = Path(__file__).parent.parent.parent / "examples"
@asynccontextmanager
async def lifespan(app: FastAPI): # pragma: no cover - startup path
@@ -92,6 +96,22 @@ async def admin_root(request: Request, auth=AUTH_COOKIE):
return await admin.adminapp(request, auth) # Delegated to admin app
@app.get("/auth/examples/", include_in_schema=False)
async def examples_page():
"""Serve examples/index.html when running from source tree.
This provides a simple test page for API mode authentication flows
without depending on the Vue frontend build.
"""
index_file = _EXAMPLES_DIR / "index.html"
if not index_file.is_file():
raise HTTPException(
status_code=404,
detail="Examples not available (not running from source tree)",
)
return FileResponse(index_file, media_type="text/html")
# Note: this catch-all handler must be the last route defined
@app.get("/{reset}")
@app.get("/auth/{reset}")