@@ -167,6 +167,9 @@ def evict_user_from_cache(auth_uid: UUID) -> None:
167167# Every Basic-auth rejection repeats this, because the field placement is the
168168# thing callers get wrong and a bare "Not authenticated" gives them nothing to
169169# act on.
170+ # Advertised in the Basic challenge on the proxied OSM surface.
171+ OSM_BASIC_REALM = "TDEI Workspaces"
172+
170173_BASIC_USAGE_HINT = (
171174 "Supply the TDEI token as the HTTP Basic *username*; the password is "
172175 'ignored. For example: `curl -u "$TDEI_TOKEN:" ...`, or a URL of the form '
@@ -178,7 +181,9 @@ def _basic_auth_error(reason: str) -> HTTPException:
178181 return HTTPException (
179182 status_code = status .HTTP_401_UNAUTHORIZED ,
180183 detail = f"{ reason } { _BASIC_USAGE_HINT } " ,
181- headers = {"WWW-Authenticate" : "Bearer" },
184+ # Challenge with the scheme the caller was using, so a client that only speaks Basic can
185+ # correct itself and retry.
186+ headers = {"WWW-Authenticate" : f'Basic realm="{ OSM_BASIC_REALM } "' },
182187 )
183188
184189
@@ -275,11 +280,25 @@ async def __call__( # type: ignore[override]
275280 scheme , param = get_authorization_scheme_param (
276281 request .headers .get ("Authorization" )
277282 )
283+ basic_allowed = not request .url .path .startswith (BEARER_ONLY_PATH_PREFIXES )
284+
278285 if scheme .lower () != "basic" :
286+ # No credentials at all, on a path where Basic is the accepted scheme: answer with a
287+ # Basic challenge. HTTPBearer answers `WWW-Authenticate: Bearer`, which a client that
288+ # only speaks Basic -- JOSM and other OSM editors -- cannot act on: it never sends its
289+ # credentials and reports that it could not reach the server. The /api/v1 surface is
290+ # unaffected, since Basic is not accepted there.
291+ if not scheme and basic_allowed :
292+ raise HTTPException (
293+ status_code = status .HTTP_401_UNAUTHORIZED ,
294+ detail = "Not authenticated" ,
295+ headers = {"WWW-Authenticate" : f'Basic realm="{ OSM_BASIC_REALM } "' },
296+ )
297+
279298 # Bearer (and every rejection path) keeps FastAPI's own behavior.
280299 return await super ().__call__ (request )
281300
282- if request . url . path . startswith ( BEARER_ONLY_PATH_PREFIXES ) :
301+ if not basic_allowed :
283302 raise HTTPException (
284303 status_code = status .HTTP_401_UNAUTHORIZED ,
285304 detail = (
0 commit comments