fix devpi gateway auth

This commit is contained in:
Nathan Trudeau
2026-05-03 07:25:31 -04:00
parent c5bcd24e06
commit 6dca4aceb9
3 changed files with 28 additions and 19 deletions

View File

@@ -12,11 +12,12 @@ On every ``get`` the helper:
1. Reads the auth file (same path the gateway uses:
``~/.forge-stack-devpi-gateway-gitea/client-auth.json`` by default,
or whatever ``FSDGG_AUTH_STORE_PATH`` / ``FSDGG_RUNTIME_DIR`` point
at). If the file is missing, the helper passes git's input through
unchanged so the normal prompt chain continues.
at). If the file is missing, the helper emits no output so the next
helper in Git's chain can answer without receiving echoed fields from
this helper.
2. Checks that the git request host matches the host recorded in
``_forge_gitea_base_url`` (or ``FORGE_GITEA_URL``). If not, passes
through. This prevents OAuth token disclosure to unrelated
``_forge_gitea_base_url`` (or ``FORGE_GITEA_URL``). If not, emits no
output. This prevents OAuth token disclosure to unrelated
hosts even if git mis-scopes its lookup.
3. If ``gitea_access_token`` is live, emits
``username=<stored-user>`` and ``password=<gitea_access_token>``.
@@ -29,7 +30,7 @@ Security notes
--------------
* The helper never writes to stdout except the credential key=value
block. Logs go to stderr.
* On OAuth refresh failure the helper exits **non-zero** rather than silently
* On OAuth refresh failure the helper emits no credentials rather than
returning stale credentials.
"""
from __future__ import annotations
@@ -82,6 +83,10 @@ def emit(fields: dict[str, str]) -> None:
sys.stdout.write(f"{key}={value}\n")
def emit_no_credentials() -> None:
return
# --------------------------------------------------------------------
# Host matching
# --------------------------------------------------------------------
@@ -150,10 +155,10 @@ def _request_matches(
def cmd_get(fields: dict[str, str]) -> int:
configured = _configured_host()
if configured is None:
emit(fields) # pass-through: helper scope is unknown
emit_no_credentials()
return 0
if not _request_matches(fields, configured):
emit(fields) # pass-through: request targets a different host
emit_no_credentials()
return 0
state = forge_auth.AuthFile.read(forge_auth.auth_store_path())
@@ -172,7 +177,7 @@ def cmd_get(fields: dict[str, str]) -> int:
"FSDGG_CLI_CLIENT_ID are not set in the environment; "
"cannot refresh. Run 'just login' to re-authenticate."
)
emit(fields)
emit_no_credentials()
return 0
try:
refreshed = forge_auth.run_refresh(config, must_refresh=True)
@@ -181,7 +186,7 @@ def cmd_get(fields: dict[str, str]) -> int:
f"token refresh failed: {exc}. "
f"Run 'just login' to re-authenticate."
)
emit(fields)
emit_no_credentials()
return 0
_emit_credentials(fields, refreshed)
return 0
@@ -190,7 +195,7 @@ def cmd_get(fields: dict[str, str]) -> int:
def _emit_credentials(fields: dict[str, str], state: forge_auth.AuthFile) -> None:
token = state.gitea_access_token
if not token:
emit(fields)
emit_no_credentials()
return
out = dict(fields)
out["username"] = state.username or fields.get("username") or "oauth"