refactor(auth): table-drive loopback redirect host canonicalisation

The host check was a three-branch if/elif mapping each accepted loopback
literal to its URL authority spelling; a lookup table states the same
allow-list in one place. The final urlunparse also re-set scheme, netloc
and fragment even though every earlier check already guarantees they are
canonical (http, netloc == canonical_netloc, '#' rejected), so only the
empty-path -> "/" normalisation is applied. Behaviour and 400 messages are
unchanged.
This commit is contained in:
kshitijk4poor
2026-10-01 21:51:09 +05:30
committed by kshitij
parent 37fea25c2b
commit 1223ffbe38

View File

@@ -215,6 +215,10 @@ async def auth_login(request: Request, provider: str, next: str = ""):
# --- Public: RFC 8252 native-app authorization (system browser + loopback + PKCE)
# RFC 8252 loopback IP literals -> their canonical URL authority spelling.
_LOOPBACK_NETLOC_HOSTS = {"127.0.0.1": "127.0.0.1", "::1": "[::1]"}
def _validate_loopback_redirect_uri(raw: str) -> str:
"""Return a canonical RFC 8252 loopback URI or reject it.
@@ -242,23 +246,14 @@ def _validate_loopback_redirect_uri(raw: str) -> str:
raise _http(400, "native redirect_uri must not contain a fragment")
if parsed.username is not None or parsed.password is not None:
raise _http(400, "native redirect_uri must not contain userinfo")
if hostname == "127.0.0.1":
canonical_host = "127.0.0.1"
elif hostname == "::1":
canonical_host = "[::1]"
else:
canonical_host = _LOOPBACK_NETLOC_HOSTS.get(hostname)
if canonical_host is None:
raise _http(400, "native redirect_uri host must be a loopback IP literal (127.0.0.1 / ::1)")
canonical_netloc = canonical_host + (f":{port}" if port is not None else "")
if parsed.netloc != canonical_netloc:
raise _http(400, "native redirect_uri must use a canonical loopback authority")
return urlunparse(
parsed._replace(
scheme="http",
netloc=canonical_netloc,
path=parsed.path or "/",
fragment="",
)
)
# scheme/netloc are already canonical and fragments rejected; only an empty path needs "/".
return urlunparse(parsed._replace(path=parsed.path or "/"))
def _select_native_provider(provider: str):